Coverage for app / backend / src / tests / test_requests.py: 99%

732 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-03-19 14:14 +0000

1import html 

2import re 

3from datetime import timedelta 

4from urllib.parse import parse_qs, urlparse 

5 

6import grpc 

7import pytest 

8from sqlalchemy import select 

9from sqlalchemy_utils import refresh_materialized_view 

10 

11from couchers.constants import HOST_REQUEST_MIN_LENGTH_UTF16 

12from couchers.crypto import b64decode 

13from couchers.db import session_scope 

14from couchers.i18n import LocalizationContext 

15from couchers.models import ( 

16 Message, 

17 MessageType, 

18 RateLimitAction, 

19) 

20from couchers.proto import ( 

21 api_pb2, 

22 auth_pb2, 

23 conversations_pb2, 

24 requests_pb2, 

25) 

26from couchers.proto.internal import unsubscribe_pb2 

27from couchers.rate_limits.definitions import RATE_LIMIT_DEFINITIONS, RATE_LIMIT_HOURS 

28from couchers.utils import create_coordinate, now, today 

29from tests.fixtures.db import generate_user 

30from tests.fixtures.misc import PushCollector, email_fields, mock_notification_email 

31from tests.fixtures.sessions import api_session, auth_api_session, requests_session 

32 

33 

34@pytest.fixture(autouse=True) 

35def _(testconfig): 

36 pass 

37 

38 

39def valid_request_text(text: str = "Test request") -> str: 

40 """Pads a request text to a valid length.""" 

41 # Request lengths are measured in utf-16 code units to match the frontend. 

42 utf16_length = len(text.encode("utf-16-le")) // 2 

43 if utf16_length >= HOST_REQUEST_MIN_LENGTH_UTF16: 43 ↛ 44line 43 didn't jump to line 44 because the condition on line 43 was never true

44 return text 

45 padding_length = HOST_REQUEST_MIN_LENGTH_UTF16 - utf16_length 

46 return text + ("_" * padding_length) # Each "_" adds one utf16 code unit. 

47 

48 

49def test_create_request(db, moderator): 

50 user1, token1 = generate_user() 

51 hosting_city = "Morningside Heights, New York City" 

52 hosting_lat = 40.8086 

53 hosting_lng = -73.9616 

54 hosting_radius = 500 

55 user2, token2 = generate_user( 

56 city=hosting_city, 

57 geom=create_coordinate(hosting_lat, hosting_lng), 

58 geom_radius=hosting_radius, 

59 ) 

60 

61 today_plus_2 = today() + timedelta(days=2) 

62 today_plus_3 = today() + timedelta(days=3) 

63 today_minus_2 = today() - timedelta(days=2) 

64 today_minus_3 = today() - timedelta(days=3) 

65 

66 with requests_session(token1) as api: 

67 with pytest.raises(grpc.RpcError) as e: 

68 api.CreateHostRequest( 

69 requests_pb2.CreateHostRequestReq( 

70 host_user_id=user1.id, 

71 from_date=today_plus_2.isoformat(), 

72 to_date=today_plus_3.isoformat(), 

73 text=valid_request_text(), 

74 ) 

75 ) 

76 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT 

77 assert e.value.details() == "Can't request hosting from yourself." 

78 

79 with pytest.raises(grpc.RpcError) as e: 

80 api.CreateHostRequest( 

81 requests_pb2.CreateHostRequestReq( 

82 host_user_id=999, 

83 from_date=today_plus_2.isoformat(), 

84 to_date=today_plus_3.isoformat(), 

85 text=valid_request_text(), 

86 ) 

87 ) 

88 assert e.value.code() == grpc.StatusCode.NOT_FOUND 

89 assert e.value.details() == "Couldn't find that user." 

90 

91 with pytest.raises(grpc.RpcError) as e: 

92 api.CreateHostRequest( 

93 requests_pb2.CreateHostRequestReq( 

94 host_user_id=user2.id, 

95 from_date=today_plus_3.isoformat(), 

96 to_date=today_plus_2.isoformat(), 

97 text=valid_request_text(), 

98 ) 

99 ) 

100 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT 

101 assert e.value.details() == "From date can't be after to date." 

102 

103 with pytest.raises(grpc.RpcError) as e: 

104 api.CreateHostRequest( 

105 requests_pb2.CreateHostRequestReq( 

106 host_user_id=user2.id, 

107 from_date=today_minus_3.isoformat(), 

108 to_date=today_plus_2.isoformat(), 

109 text=valid_request_text(), 

110 ) 

111 ) 

112 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT 

113 assert e.value.details() == "From date must be today or later." 

114 

115 with pytest.raises(grpc.RpcError) as e: 

116 api.CreateHostRequest( 

117 requests_pb2.CreateHostRequestReq( 

118 host_user_id=user2.id, 

119 from_date=today_plus_2.isoformat(), 

120 to_date=today_minus_2.isoformat(), 

121 text=valid_request_text(), 

122 ) 

123 ) 

124 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT 

125 assert e.value.details() == "From date can't be after to date." 

126 

127 with pytest.raises(grpc.RpcError) as e: 

128 api.CreateHostRequest( 

129 requests_pb2.CreateHostRequestReq( 

130 host_user_id=user2.id, 

131 from_date="2020-00-06", 

132 to_date=today_minus_2.isoformat(), 

133 text=valid_request_text(), 

134 ) 

135 ) 

136 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT 

137 assert e.value.details() == "Invalid date." 

138 

139 with pytest.raises(grpc.RpcError) as e: 

140 api.CreateHostRequest( 

141 requests_pb2.CreateHostRequestReq( 

142 host_user_id=user2.id, 

143 from_date=today_plus_2.isoformat(), 

144 to_date=today_plus_3.isoformat(), 

145 text="Too short.", 

146 ) 

147 ) 

148 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT 

149 assert e.value.details() == "Host request cannot be shorter than 250 characters." 

150 

151 res = api.CreateHostRequest( 

152 requests_pb2.CreateHostRequestReq( 

153 host_user_id=user2.id, 

154 from_date=today_plus_2.isoformat(), 

155 to_date=today_plus_3.isoformat(), 

156 text=valid_request_text(), 

157 ) 

158 ) 

159 host_request_id = res.host_request_id 

160 

161 moderator.approve_host_request(host_request_id) 

162 

163 with requests_session(token1) as api: 

164 host_requests = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_sent=True)).host_requests 

165 

166 assert len(host_requests) == 1 

167 hr = host_requests[0] 

168 

169 assert hr.latest_message.text.text == valid_request_text() 

170 

171 assert hr.hosting_city == hosting_city 

172 assert round(hr.hosting_lat, 4) == hosting_lat 

173 assert round(hr.hosting_lng, 4) == hosting_lng 

174 assert hr.hosting_radius == hosting_radius 

175 

176 today_ = today() 

177 today_plus_one_year = today_ + timedelta(days=365) 

178 today_plus_one_year_plus_2 = today_plus_one_year + timedelta(days=2) 

179 today_plus_one_year_plus_3 = today_plus_one_year + timedelta(days=3) 

180 with pytest.raises(grpc.RpcError) as e: 

181 api.CreateHostRequest( 

182 requests_pb2.CreateHostRequestReq( 

183 host_user_id=user2.id, 

184 from_date=today_plus_one_year_plus_2.isoformat(), 

185 to_date=today_plus_one_year_plus_3.isoformat(), 

186 text=valid_request_text("Test from date after one year"), 

187 ) 

188 ) 

189 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT 

190 assert e.value.details() == "The start date must be within one year from today." 

191 

192 with pytest.raises(grpc.RpcError) as e: 

193 api.CreateHostRequest( 

194 requests_pb2.CreateHostRequestReq( 

195 host_user_id=user2.id, 

196 from_date=today_plus_2.isoformat(), 

197 to_date=today_plus_one_year_plus_3.isoformat(), 

198 text=valid_request_text("Test to date one year after from date"), 

199 ) 

200 ) 

201 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT 

202 assert e.value.details() == "You cannot request to stay with someone for longer than one year." 

203 

204 

205def test_create_request_incomplete_profile(db): 

206 user1, token1 = generate_user(complete_profile=False) 

207 user2, _ = generate_user() 

208 today_plus_2 = today() + timedelta(days=2) 

209 today_plus_3 = today() + timedelta(days=3) 

210 with requests_session(token1) as api: 

211 with pytest.raises(grpc.RpcError) as e: 

212 api.CreateHostRequest( 

213 requests_pb2.CreateHostRequestReq( 

214 host_user_id=user2.id, 

215 from_date=today_plus_2.isoformat(), 

216 to_date=today_plus_3.isoformat(), 

217 text=valid_request_text(), 

218 ) 

219 ) 

220 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION 

221 assert e.value.details() == "You have to complete your profile before you can send a request." 

222 

223 

224def test_excessive_requests_are_reported(db): 

225 """Test that excessive host requests are first reported in a warning email and finally lead blocking of further requests.""" 

226 user, token = generate_user() 

227 today_plus_2 = today() + timedelta(days=2) 

228 today_plus_3 = today() + timedelta(days=3) 

229 rate_limit_definition = RATE_LIMIT_DEFINITIONS[RateLimitAction.host_request] 

230 with requests_session(token) as api: 

231 # Test warning email 

232 with mock_notification_email() as mock_email: 

233 for _ in range(rate_limit_definition.warning_limit): 

234 host_user, _ = generate_user() 

235 _ = api.CreateHostRequest( 

236 requests_pb2.CreateHostRequestReq( 

237 host_user_id=host_user.id, 

238 from_date=today_plus_2.isoformat(), 

239 to_date=today_plus_3.isoformat(), 

240 text=valid_request_text(), 

241 ) 

242 ) 

243 

244 assert mock_email.call_count == 0 

245 host_user, _ = generate_user() 

246 _ = api.CreateHostRequest( 

247 requests_pb2.CreateHostRequestReq( 

248 host_user_id=host_user.id, 

249 from_date=today_plus_2.isoformat(), 

250 to_date=today_plus_3.isoformat(), 

251 text=valid_request_text("Excessive test request"), 

252 ) 

253 ) 

254 assert mock_email.call_count == 1 

255 email = email_fields(mock_email).plain 

256 assert email.startswith( 

257 f"User {user.username} has sent {rate_limit_definition.warning_limit} host requests in the past {RATE_LIMIT_HOURS} hours." 

258 ) 

259 

260 # Test ban after exceeding HOST_REQUEST_HARD_LIMIT 

261 with mock_notification_email() as mock_email: 

262 for _ in range(rate_limit_definition.hard_limit - rate_limit_definition.warning_limit - 1): 

263 host_user, _ = generate_user() 

264 _ = api.CreateHostRequest( 

265 requests_pb2.CreateHostRequestReq( 

266 host_user_id=host_user.id, 

267 from_date=today_plus_2.isoformat(), 

268 to_date=today_plus_3.isoformat(), 

269 text=valid_request_text(), 

270 ) 

271 ) 

272 

273 assert mock_email.call_count == 0 

274 host_user, _ = generate_user() 

275 with pytest.raises(grpc.RpcError) as exc_info: 

276 _ = api.CreateHostRequest( 

277 requests_pb2.CreateHostRequestReq( 

278 host_user_id=host_user.id, 

279 from_date=today_plus_2.isoformat(), 

280 to_date=today_plus_3.isoformat(), 

281 text=valid_request_text("Excessive test request"), 

282 ) 

283 ) 

284 assert exc_info.value.code() == grpc.StatusCode.RESOURCE_EXHAUSTED 

285 assert ( 

286 exc_info.value.details() 

287 == "You have sent a lot of host requests in the past 24 hours. To avoid spam, you can't send any more for now." 

288 ) 

289 

290 assert mock_email.call_count == 1 

291 email = email_fields(mock_email).plain 

292 assert email.startswith( 

293 f"User {user.username} has sent {rate_limit_definition.hard_limit} host requests in the past {RATE_LIMIT_HOURS} hours." 

294 ) 

295 assert "The user has been blocked from sending further host requests for now." in email 

296 

297 

298def add_message(db, text, author_id, conversation_id): 

299 with session_scope() as session: 

300 message = Message( 

301 conversation_id=conversation_id, author_id=author_id, text=text, message_type=MessageType.text 

302 ) 

303 

304 session.add(message) 

305 

306 

307def test_GetHostRequest(db): 

308 user1, token1 = generate_user() 

309 user2, token2 = generate_user() 

310 user3, token3 = generate_user() 

311 today_plus_2 = today() + timedelta(days=2) 

312 today_plus_3 = today() + timedelta(days=3) 

313 with requests_session(token1) as api: 

314 host_request_id = api.CreateHostRequest( 

315 requests_pb2.CreateHostRequestReq( 

316 host_user_id=user2.id, 

317 from_date=today_plus_2.isoformat(), 

318 to_date=today_plus_3.isoformat(), 

319 text=valid_request_text("Test request 1"), 

320 ) 

321 ).host_request_id 

322 

323 with pytest.raises(grpc.RpcError) as e: 

324 api.GetHostRequest(requests_pb2.GetHostRequestReq(host_request_id=999)) 

325 assert e.value.code() == grpc.StatusCode.NOT_FOUND 

326 assert e.value.details() == "Couldn't find that host request." 

327 

328 api.SendHostRequestMessage( 

329 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 1") 

330 ) 

331 

332 res = api.GetHostRequest(requests_pb2.GetHostRequestReq(host_request_id=host_request_id)) 

333 assert res.latest_message.text.text == "Test message 1" 

334 

335 

336def test_ListHostRequests(db, moderator): 

337 user1, token1 = generate_user() 

338 user2, token2 = generate_user() 

339 user3, token3 = generate_user() 

340 today_plus_2 = today() + timedelta(days=2) 

341 today_plus_3 = today() + timedelta(days=3) 

342 with requests_session(token1) as api: 

343 host_request_1 = api.CreateHostRequest( 

344 requests_pb2.CreateHostRequestReq( 

345 host_user_id=user2.id, 

346 from_date=today_plus_2.isoformat(), 

347 to_date=today_plus_3.isoformat(), 

348 text=valid_request_text("Test request 1"), 

349 ) 

350 ).host_request_id 

351 

352 host_request_2 = api.CreateHostRequest( 

353 requests_pb2.CreateHostRequestReq( 

354 host_user_id=user3.id, 

355 from_date=today_plus_2.isoformat(), 

356 to_date=today_plus_3.isoformat(), 

357 text=valid_request_text("Test request 2"), 

358 ) 

359 ).host_request_id 

360 

361 moderator.approve_host_request(host_request_1) 

362 moderator.approve_host_request(host_request_2) 

363 

364 with requests_session(token1) as api: 

365 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_sent=True)) 

366 assert res.no_more 

367 assert len(res.host_requests) == 2 

368 

369 with requests_session(token2) as api: 

370 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_received=True)) 

371 assert res.no_more 

372 assert len(res.host_requests) == 1 

373 assert res.host_requests[0].latest_message.text.text == valid_request_text("Test request 1") 

374 assert res.host_requests[0].surfer_user_id == user1.id 

375 assert res.host_requests[0].host_user_id == user2.id 

376 assert res.host_requests[0].status == conversations_pb2.HOST_REQUEST_STATUS_PENDING 

377 

378 add_message(db, "Test request 1 message 1", user2.id, host_request_1) 

379 add_message(db, "Test request 1 message 2", user2.id, host_request_1) 

380 add_message(db, "Test request 1 message 3", user2.id, host_request_1) 

381 

382 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_received=True)) 

383 assert res.host_requests[0].latest_message.text.text == "Test request 1 message 3" 

384 

385 host_request_3 = api.CreateHostRequest( 

386 requests_pb2.CreateHostRequestReq( 

387 host_user_id=user1.id, 

388 from_date=today_plus_2.isoformat(), 

389 to_date=today_plus_3.isoformat(), 

390 text=valid_request_text("Test request 3"), 

391 ) 

392 ).host_request_id 

393 

394 moderator.approve_host_request(host_request_3) 

395 

396 add_message(db, "Test request 2 message 1", user1.id, host_request_2) 

397 add_message(db, "Test request 2 message 2", user3.id, host_request_2) 

398 

399 with requests_session(token3) as api: 

400 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_received=True)) 

401 assert res.no_more 

402 assert len(res.host_requests) == 1 

403 assert res.host_requests[0].latest_message.text.text == "Test request 2 message 2" 

404 

405 with requests_session(token1) as api: 

406 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_received=True)) 

407 assert len(res.host_requests) == 1 

408 

409 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq()) 

410 assert len(res.host_requests) == 3 

411 

412 

413def test_ListHostRequests_pagination_regression(db, moderator): 

414 """ 

415 ListHostRequests was skipping a request when getting multiple pages 

416 """ 

417 user1, token1 = generate_user() 

418 user2, token2 = generate_user() 

419 today_plus_2 = today() + timedelta(days=2) 

420 today_plus_3 = today() + timedelta(days=3) 

421 with requests_session(token1) as api: 

422 host_request_1 = api.CreateHostRequest( 

423 requests_pb2.CreateHostRequestReq( 

424 host_user_id=user2.id, 

425 from_date=today_plus_2.isoformat(), 

426 to_date=today_plus_3.isoformat(), 

427 text=valid_request_text("Test request 1"), 

428 ) 

429 ).host_request_id 

430 

431 host_request_2 = api.CreateHostRequest( 

432 requests_pb2.CreateHostRequestReq( 

433 host_user_id=user2.id, 

434 from_date=today_plus_2.isoformat(), 

435 to_date=today_plus_3.isoformat(), 

436 text=valid_request_text("Test request 2"), 

437 ) 

438 ).host_request_id 

439 

440 host_request_3 = api.CreateHostRequest( 

441 requests_pb2.CreateHostRequestReq( 

442 host_user_id=user2.id, 

443 from_date=today_plus_2.isoformat(), 

444 to_date=today_plus_3.isoformat(), 

445 text=valid_request_text("Test request 3"), 

446 ) 

447 ).host_request_id 

448 

449 moderator.approve_host_request(host_request_1) 

450 moderator.approve_host_request(host_request_2) 

451 moderator.approve_host_request(host_request_3) 

452 

453 with requests_session(token2) as api: 

454 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_received=True)) 

455 assert res.no_more 

456 assert len(res.host_requests) == 3 

457 assert res.host_requests[0].latest_message.text.text == valid_request_text("Test request 3") 

458 assert res.host_requests[1].latest_message.text.text == valid_request_text("Test request 2") 

459 assert res.host_requests[2].latest_message.text.text == valid_request_text("Test request 1") 

460 

461 with requests_session(token2) as api: 

462 api.RespondHostRequest( 

463 requests_pb2.RespondHostRequestReq( 

464 host_request_id=host_request_2, 

465 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED, 

466 text="Accepting host request 2", 

467 ) 

468 ) 

469 api.RespondHostRequest( 

470 requests_pb2.RespondHostRequestReq( 

471 host_request_id=host_request_1, 

472 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED, 

473 text="Accepting host request 1", 

474 ) 

475 ) 

476 api.RespondHostRequest( 

477 requests_pb2.RespondHostRequestReq( 

478 host_request_id=host_request_3, 

479 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED, 

480 text="Accepting host request 3", 

481 ) 

482 ) 

483 

484 with requests_session(token2) as api: 

485 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_received=True)) 

486 assert res.no_more 

487 assert len(res.host_requests) == 3 

488 assert res.host_requests[0].latest_message.text.text == "Accepting host request 3" 

489 assert res.host_requests[1].latest_message.text.text == "Accepting host request 1" 

490 assert res.host_requests[2].latest_message.text.text == "Accepting host request 2" 

491 

492 with requests_session(token2) as api: 

493 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_received=True, number=1)) 

494 assert not res.no_more 

495 assert len(res.host_requests) == 1 

496 assert res.host_requests[0].latest_message.text.text == "Accepting host request 3" 

497 res = api.ListHostRequests( 

498 requests_pb2.ListHostRequestsReq(only_received=True, number=1, last_request_id=res.last_request_id) 

499 ) 

500 assert not res.no_more 

501 assert len(res.host_requests) == 1 

502 assert res.host_requests[0].latest_message.text.text == "Accepting host request 1" 

503 res = api.ListHostRequests( 

504 requests_pb2.ListHostRequestsReq(only_received=True, number=1, last_request_id=res.last_request_id) 

505 ) 

506 assert res.no_more 

507 assert len(res.host_requests) == 1 

508 assert res.host_requests[0].latest_message.text.text == "Accepting host request 2" 

509 

510 

511def test_ListHostRequests_active_filter(db, moderator): 

512 user1, token1 = generate_user() 

513 user2, token2 = generate_user() 

514 today_plus_2 = today() + timedelta(days=2) 

515 today_plus_3 = today() + timedelta(days=3) 

516 

517 with requests_session(token1) as api: 

518 request_id = api.CreateHostRequest( 

519 requests_pb2.CreateHostRequestReq( 

520 host_user_id=user2.id, 

521 from_date=today_plus_2.isoformat(), 

522 to_date=today_plus_3.isoformat(), 

523 text=valid_request_text("Test request 1"), 

524 ) 

525 ).host_request_id 

526 

527 moderator.approve_host_request(request_id) 

528 

529 with requests_session(token1) as api: 

530 api.RespondHostRequest( 

531 requests_pb2.RespondHostRequestReq( 

532 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED 

533 ) 

534 ) 

535 

536 with requests_session(token2) as api: 

537 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_received=True)) 

538 assert len(res.host_requests) == 1 

539 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_active=True)) 

540 assert len(res.host_requests) == 0 

541 

542 

543def test_RespondHostRequests(db, moderator): 

544 user1, token1 = generate_user() 

545 user2, token2 = generate_user() 

546 user3, token3 = generate_user() 

547 today_plus_2 = today() + timedelta(days=2) 

548 today_plus_3 = today() + timedelta(days=3) 

549 

550 with requests_session(token1) as api: 

551 request_id = api.CreateHostRequest( 

552 requests_pb2.CreateHostRequestReq( 

553 host_user_id=user2.id, 

554 from_date=today_plus_2.isoformat(), 

555 to_date=today_plus_3.isoformat(), 

556 text=valid_request_text("Test request 1"), 

557 ) 

558 ).host_request_id 

559 

560 moderator.approve_host_request(request_id) 

561 

562 # another user can't access 

563 with requests_session(token3) as api: 

564 with pytest.raises(grpc.RpcError) as e: 

565 api.RespondHostRequest( 

566 requests_pb2.RespondHostRequestReq( 

567 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED 

568 ) 

569 ) 

570 assert e.value.code() == grpc.StatusCode.NOT_FOUND 

571 assert e.value.details() == "Couldn't find that host request." 

572 

573 with requests_session(token1) as api: 

574 with pytest.raises(grpc.RpcError) as e: 

575 api.RespondHostRequest( 

576 requests_pb2.RespondHostRequestReq( 

577 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED 

578 ) 

579 ) 

580 assert e.value.code() == grpc.StatusCode.PERMISSION_DENIED 

581 assert e.value.details() == "You are not the host of this request." 

582 

583 with requests_session(token2) as api: 

584 # non existing id 

585 with pytest.raises(grpc.RpcError) as e: 

586 api.RespondHostRequest( 

587 requests_pb2.RespondHostRequestReq( 

588 host_request_id=9999, status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED 

589 ) 

590 ) 

591 assert e.value.code() == grpc.StatusCode.NOT_FOUND 

592 

593 # host can't confirm or cancel (host should accept/reject) 

594 with pytest.raises(grpc.RpcError) as e: 

595 api.RespondHostRequest( 

596 requests_pb2.RespondHostRequestReq( 

597 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CONFIRMED 

598 ) 

599 ) 

600 assert e.value.code() == grpc.StatusCode.PERMISSION_DENIED 

601 assert e.value.details() == "You can't set the host request status to that." 

602 with pytest.raises(grpc.RpcError) as e: 

603 api.RespondHostRequest( 

604 requests_pb2.RespondHostRequestReq( 

605 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED 

606 ) 

607 ) 

608 assert e.value.code() == grpc.StatusCode.PERMISSION_DENIED 

609 assert e.value.details() == "You can't set the host request status to that." 

610 

611 api.RespondHostRequest( 

612 requests_pb2.RespondHostRequestReq( 

613 host_request_id=request_id, 

614 status=conversations_pb2.HOST_REQUEST_STATUS_REJECTED, 

615 text="Test rejection message", 

616 ) 

617 ) 

618 res = api.GetHostRequestMessages(requests_pb2.GetHostRequestMessagesReq(host_request_id=request_id)) 

619 assert res.messages[0].text.text == "Test rejection message" 

620 assert res.messages[1].WhichOneof("content") == "host_request_status_changed" 

621 assert res.messages[1].host_request_status_changed.status == conversations_pb2.HOST_REQUEST_STATUS_REJECTED 

622 # should be able to move from rejected -> accepted 

623 api.RespondHostRequest( 

624 requests_pb2.RespondHostRequestReq( 

625 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED 

626 ) 

627 ) 

628 

629 with requests_session(token1) as api: 

630 # can't make pending 

631 with pytest.raises(grpc.RpcError) as e: 

632 api.RespondHostRequest( 

633 requests_pb2.RespondHostRequestReq( 

634 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_PENDING 

635 ) 

636 ) 

637 assert e.value.code() == grpc.StatusCode.PERMISSION_DENIED 

638 assert e.value.details() == "You can't set the host request status to that." 

639 

640 # can confirm then cancel 

641 api.RespondHostRequest( 

642 requests_pb2.RespondHostRequestReq( 

643 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CONFIRMED 

644 ) 

645 ) 

646 

647 api.RespondHostRequest( 

648 requests_pb2.RespondHostRequestReq( 

649 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED 

650 ) 

651 ) 

652 

653 # can't confirm after having cancelled 

654 with pytest.raises(grpc.RpcError) as e: 

655 api.RespondHostRequest( 

656 requests_pb2.RespondHostRequestReq( 

657 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CONFIRMED 

658 ) 

659 ) 

660 assert e.value.code() == grpc.StatusCode.PERMISSION_DENIED 

661 assert e.value.details() == "You can't set the host request status to that." 

662 

663 # at this point there should be 7 messages 

664 # 2 for creation, 2 for the status change with message, 3 for the other status changed 

665 with requests_session(token1) as api: 

666 res = api.GetHostRequestMessages(requests_pb2.GetHostRequestMessagesReq(host_request_id=request_id)) 

667 assert len(res.messages) == 7 

668 assert res.messages[0].host_request_status_changed.status == conversations_pb2.HOST_REQUEST_STATUS_CANCELLED 

669 assert res.messages[1].host_request_status_changed.status == conversations_pb2.HOST_REQUEST_STATUS_CONFIRMED 

670 assert res.messages[2].host_request_status_changed.status == conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED 

671 assert res.messages[4].host_request_status_changed.status == conversations_pb2.HOST_REQUEST_STATUS_REJECTED 

672 assert res.messages[6].WhichOneof("content") == "chat_created" 

673 

674 

675def test_get_host_request_messages(db, moderator): 

676 user1, token1 = generate_user() 

677 user2, token2 = generate_user() 

678 today_plus_2 = today() + timedelta(days=2) 

679 today_plus_3 = today() + timedelta(days=3) 

680 with requests_session(token1) as api: 

681 res = api.CreateHostRequest( 

682 requests_pb2.CreateHostRequestReq( 

683 host_user_id=user2.id, 

684 from_date=today_plus_2.isoformat(), 

685 to_date=today_plus_3.isoformat(), 

686 text=valid_request_text("Test request 1"), 

687 ) 

688 ) 

689 conversation_id = res.host_request_id 

690 

691 moderator.approve_host_request(conversation_id) 

692 

693 add_message(db, "Test request 1 message 1", user1.id, conversation_id) 

694 add_message(db, "Test request 1 message 2", user1.id, conversation_id) 

695 add_message(db, "Test request 1 message 3", user1.id, conversation_id) 

696 

697 with requests_session(token2) as api: 

698 api.RespondHostRequest( 

699 requests_pb2.RespondHostRequestReq( 

700 host_request_id=conversation_id, status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED 

701 ) 

702 ) 

703 

704 add_message(db, "Test request 1 message 4", user2.id, conversation_id) 

705 add_message(db, "Test request 1 message 5", user2.id, conversation_id) 

706 

707 api.RespondHostRequest( 

708 requests_pb2.RespondHostRequestReq( 

709 host_request_id=conversation_id, status=conversations_pb2.HOST_REQUEST_STATUS_REJECTED 

710 ) 

711 ) 

712 

713 with requests_session(token1) as api: 

714 # 9 including initial message 

715 res = api.GetHostRequestMessages(requests_pb2.GetHostRequestMessagesReq(host_request_id=conversation_id)) 

716 assert len(res.messages) == 9 

717 assert res.no_more 

718 

719 res = api.GetHostRequestMessages( 

720 requests_pb2.GetHostRequestMessagesReq(host_request_id=conversation_id, number=3) 

721 ) 

722 assert not res.no_more 

723 assert len(res.messages) == 3 

724 assert res.messages[0].host_request_status_changed.status == conversations_pb2.HOST_REQUEST_STATUS_REJECTED 

725 assert res.messages[0].WhichOneof("content") == "host_request_status_changed" 

726 assert res.messages[1].text.text == "Test request 1 message 5" 

727 assert res.messages[2].text.text == "Test request 1 message 4" 

728 

729 res = api.GetHostRequestMessages( 

730 requests_pb2.GetHostRequestMessagesReq( 

731 host_request_id=conversation_id, 

732 last_message_id=res.messages[2].message_id, 

733 number=6, 

734 ) 

735 ) 

736 assert res.no_more 

737 assert len(res.messages) == 6 

738 assert res.messages[0].host_request_status_changed.status == conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED 

739 assert res.messages[0].WhichOneof("content") == "host_request_status_changed" 

740 assert res.messages[1].text.text == "Test request 1 message 3" 

741 assert res.messages[2].text.text == "Test request 1 message 2" 

742 assert res.messages[3].text.text == "Test request 1 message 1" 

743 assert res.messages[4].text.text == valid_request_text("Test request 1") 

744 assert res.messages[5].WhichOneof("content") == "chat_created" 

745 

746 

747def test_SendHostRequestMessage(db, moderator): 

748 user1, token1 = generate_user() 

749 user2, token2 = generate_user() 

750 user3, token3 = generate_user() 

751 today_plus_2 = today() + timedelta(days=2) 

752 today_plus_3 = today() + timedelta(days=3) 

753 with requests_session(token1) as api: 

754 host_request_id = api.CreateHostRequest( 

755 requests_pb2.CreateHostRequestReq( 

756 host_user_id=user2.id, 

757 from_date=today_plus_2.isoformat(), 

758 to_date=today_plus_3.isoformat(), 

759 text=valid_request_text("Test request 1"), 

760 ) 

761 ).host_request_id 

762 

763 moderator.approve_host_request(host_request_id) 

764 

765 with requests_session(token1) as api: 

766 with pytest.raises(grpc.RpcError) as e: 

767 api.SendHostRequestMessage( 

768 requests_pb2.SendHostRequestMessageReq(host_request_id=999, text="Test message 1") 

769 ) 

770 assert e.value.code() == grpc.StatusCode.NOT_FOUND 

771 

772 with pytest.raises(grpc.RpcError) as e: 

773 api.SendHostRequestMessage(requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="")) 

774 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT 

775 assert e.value.details() == "Invalid message." 

776 

777 api.SendHostRequestMessage( 

778 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 1") 

779 ) 

780 res = api.GetHostRequestMessages(requests_pb2.GetHostRequestMessagesReq(host_request_id=host_request_id)) 

781 assert res.messages[0].text.text == "Test message 1" 

782 assert res.messages[0].author_user_id == user1.id 

783 

784 with requests_session(token3) as api: 

785 # other user can't send 

786 with pytest.raises(grpc.RpcError) as e: 

787 api.SendHostRequestMessage( 

788 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 2") 

789 ) 

790 assert e.value.code() == grpc.StatusCode.NOT_FOUND 

791 assert e.value.details() == "Couldn't find that host request." 

792 

793 with requests_session(token2) as api: 

794 api.SendHostRequestMessage( 

795 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 2") 

796 ) 

797 res = api.GetHostRequestMessages(requests_pb2.GetHostRequestMessagesReq(host_request_id=host_request_id)) 

798 # including 2 for creation control message and message 

799 assert len(res.messages) == 4 

800 assert res.messages[0].text.text == "Test message 2" 

801 assert res.messages[0].author_user_id == user2.id 

802 

803 # CAN send messages to a rejected, confirmed or cancelled request, and for accepted 

804 api.RespondHostRequest( 

805 requests_pb2.RespondHostRequestReq( 

806 host_request_id=host_request_id, status=conversations_pb2.HOST_REQUEST_STATUS_REJECTED 

807 ) 

808 ) 

809 api.SendHostRequestMessage( 

810 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 3") 

811 ) 

812 

813 api.RespondHostRequest( 

814 requests_pb2.RespondHostRequestReq( 

815 host_request_id=host_request_id, status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED 

816 ) 

817 ) 

818 

819 with requests_session(token1) as api: 

820 api.RespondHostRequest( 

821 requests_pb2.RespondHostRequestReq( 

822 host_request_id=host_request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CONFIRMED 

823 ) 

824 ) 

825 api.SendHostRequestMessage( 

826 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 3") 

827 ) 

828 

829 api.RespondHostRequest( 

830 requests_pb2.RespondHostRequestReq( 

831 host_request_id=host_request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED 

832 ) 

833 ) 

834 api.SendHostRequestMessage( 

835 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 3") 

836 ) 

837 

838 

839def test_get_updates(db, moderator): 

840 user1, token1 = generate_user() 

841 user2, token2 = generate_user() 

842 user3, token3 = generate_user() 

843 today_plus_2 = today() + timedelta(days=2) 

844 today_plus_3 = today() + timedelta(days=3) 

845 with requests_session(token1) as api: 

846 host_request_id = api.CreateHostRequest( 

847 requests_pb2.CreateHostRequestReq( 

848 host_user_id=user2.id, 

849 from_date=today_plus_2.isoformat(), 

850 to_date=today_plus_3.isoformat(), 

851 text=valid_request_text("Test message 0"), 

852 ) 

853 ).host_request_id 

854 

855 moderator.approve_host_request(host_request_id) 

856 

857 with requests_session(token1) as api: 

858 api.SendHostRequestMessage( 

859 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 1") 

860 ) 

861 api.SendHostRequestMessage( 

862 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 2") 

863 ) 

864 api.RespondHostRequest( 

865 requests_pb2.RespondHostRequestReq( 

866 host_request_id=host_request_id, 

867 status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED, 

868 text="Test message 3", 

869 ) 

870 ) 

871 

872 api.CreateHostRequest( 

873 requests_pb2.CreateHostRequestReq( 

874 host_user_id=user2.id, 

875 from_date=today_plus_2.isoformat(), 

876 to_date=today_plus_3.isoformat(), 

877 text=valid_request_text("Test message 4"), 

878 ) 

879 ) 

880 

881 res = api.GetHostRequestMessages(requests_pb2.GetHostRequestMessagesReq(host_request_id=host_request_id)) 

882 assert len(res.messages) == 6 

883 assert res.messages[0].text.text == "Test message 3" 

884 assert res.messages[1].host_request_status_changed.status == conversations_pb2.HOST_REQUEST_STATUS_CANCELLED 

885 assert res.messages[2].text.text == "Test message 2" 

886 assert res.messages[3].text.text == "Test message 1" 

887 assert res.messages[4].text.text == valid_request_text("Test message 0") 

888 message_id_3 = res.messages[0].message_id 

889 message_id_cancel = res.messages[1].message_id 

890 message_id_2 = res.messages[2].message_id 

891 message_id_1 = res.messages[3].message_id 

892 message_id_0 = res.messages[4].message_id 

893 

894 with pytest.raises(grpc.RpcError) as e: 

895 api.GetHostRequestUpdates(requests_pb2.GetHostRequestUpdatesReq(newest_message_id=0)) 

896 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT 

897 

898 res = api.GetHostRequestUpdates(requests_pb2.GetHostRequestUpdatesReq(newest_message_id=message_id_1)) 

899 assert res.no_more 

900 assert len(res.updates) == 5 

901 assert res.updates[0].message.text.text == "Test message 2" 

902 assert ( 

903 res.updates[1].message.host_request_status_changed.status == conversations_pb2.HOST_REQUEST_STATUS_CANCELLED 

904 ) 

905 assert res.updates[1].status == conversations_pb2.HOST_REQUEST_STATUS_CANCELLED 

906 assert res.updates[2].message.text.text == "Test message 3" 

907 assert res.updates[3].message.WhichOneof("content") == "chat_created" 

908 assert res.updates[3].status == conversations_pb2.HOST_REQUEST_STATUS_PENDING 

909 assert res.updates[4].message.text.text == valid_request_text("Test message 4") 

910 

911 res = api.GetHostRequestUpdates(requests_pb2.GetHostRequestUpdatesReq(newest_message_id=message_id_1, number=1)) 

912 assert not res.no_more 

913 assert len(res.updates) == 1 

914 assert res.updates[0].message.text.text == "Test message 2" 

915 assert res.updates[0].status == conversations_pb2.HOST_REQUEST_STATUS_CANCELLED 

916 

917 with requests_session(token3) as api: 

918 # other user can't access 

919 res = api.GetHostRequestUpdates(requests_pb2.GetHostRequestUpdatesReq(newest_message_id=message_id_1)) 

920 assert len(res.updates) == 0 

921 

922 

923def test_archive_host_request(db, moderator): 

924 user1, token1 = generate_user() 

925 user2, token2 = generate_user() 

926 

927 today_plus_2 = today() + timedelta(days=2) 

928 today_plus_3 = today() + timedelta(days=3) 

929 

930 with requests_session(token1) as api: 

931 host_request_id = api.CreateHostRequest( 

932 requests_pb2.CreateHostRequestReq( 

933 host_user_id=user2.id, 

934 from_date=today_plus_2.isoformat(), 

935 to_date=today_plus_3.isoformat(), 

936 text=valid_request_text("Test message 0"), 

937 ) 

938 ).host_request_id 

939 

940 api.SendHostRequestMessage( 

941 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 1") 

942 ) 

943 api.SendHostRequestMessage( 

944 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 2") 

945 ) 

946 

947 moderator.approve_host_request(host_request_id) 

948 

949 # happy path archiving host request 

950 with requests_session(token1) as api: 

951 api.RespondHostRequest( 

952 requests_pb2.RespondHostRequestReq( 

953 host_request_id=host_request_id, 

954 status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED, 

955 text="Test message 3", 

956 ) 

957 ) 

958 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_sent=True)) 

959 assert len(res.host_requests) == 1 

960 assert res.host_requests[0].status == conversations_pb2.HOST_REQUEST_STATUS_CANCELLED 

961 

962 # Verify is_archived is False before archiving 

963 res = api.GetHostRequest(requests_pb2.GetHostRequestReq(host_request_id=host_request_id)) 

964 assert not res.is_archived 

965 

966 api.SetHostRequestArchiveStatus( 

967 requests_pb2.SetHostRequestArchiveStatusReq(host_request_id=host_request_id, is_archived=True) 

968 ) 

969 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_archived=True)) 

970 assert len(res.host_requests) == 1 

971 

972 # Verify is_archived is True after archiving 

973 res = api.GetHostRequest(requests_pb2.GetHostRequestReq(host_request_id=host_request_id)) 

974 assert res.is_archived 

975 

976 

977def test_mark_last_seen(db, moderator): 

978 user1, token1 = generate_user() 

979 user2, token2 = generate_user() 

980 user3, token3 = generate_user() 

981 today_plus_2 = today() + timedelta(days=2) 

982 today_plus_3 = today() + timedelta(days=3) 

983 with requests_session(token1) as api: 

984 host_request_id = api.CreateHostRequest( 

985 requests_pb2.CreateHostRequestReq( 

986 host_user_id=user2.id, 

987 from_date=today_plus_2.isoformat(), 

988 to_date=today_plus_3.isoformat(), 

989 text=valid_request_text("Test message 0"), 

990 ) 

991 ).host_request_id 

992 

993 host_request_id_2 = api.CreateHostRequest( 

994 requests_pb2.CreateHostRequestReq( 

995 host_user_id=user2.id, 

996 from_date=today_plus_2.isoformat(), 

997 to_date=today_plus_3.isoformat(), 

998 text=valid_request_text("Test message 0a"), 

999 ) 

1000 ).host_request_id 

1001 

1002 moderator.approve_host_request(host_request_id) 

1003 moderator.approve_host_request(host_request_id_2) 

1004 

1005 with requests_session(token1) as api: 

1006 api.SendHostRequestMessage( 

1007 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 1") 

1008 ) 

1009 api.SendHostRequestMessage( 

1010 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 2") 

1011 ) 

1012 api.RespondHostRequest( 

1013 requests_pb2.RespondHostRequestReq( 

1014 host_request_id=host_request_id, 

1015 status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED, 

1016 text="Test message 3", 

1017 ) 

1018 ) 

1019 

1020 moderator.approve_host_request(host_request_id) 

1021 moderator.approve_host_request(host_request_id_2) 

1022 

1023 # test Ping unseen host request count, should be automarked after sending 

1024 with api_session(token1) as api: 

1025 assert api.Ping(api_pb2.PingReq()).unseen_received_host_request_count == 0 

1026 assert api.Ping(api_pb2.PingReq()).unseen_sent_host_request_count == 0 

1027 

1028 with api_session(token2) as api: 

1029 assert api.Ping(api_pb2.PingReq()).unseen_received_host_request_count == 2 

1030 assert api.Ping(api_pb2.PingReq()).unseen_sent_host_request_count == 0 

1031 

1032 with requests_session(token2) as api: 

1033 assert api.ListHostRequests(requests_pb2.ListHostRequestsReq()).host_requests[0].last_seen_message_id == 0 

1034 

1035 api.MarkLastSeenHostRequest( 

1036 requests_pb2.MarkLastSeenHostRequestReq(host_request_id=host_request_id, last_seen_message_id=3) 

1037 ) 

1038 

1039 assert api.ListHostRequests(requests_pb2.ListHostRequestsReq()).host_requests[0].last_seen_message_id == 3 

1040 

1041 with pytest.raises(grpc.RpcError) as e: 

1042 api.MarkLastSeenHostRequest( 

1043 requests_pb2.MarkLastSeenHostRequestReq(host_request_id=host_request_id, last_seen_message_id=1) 

1044 ) 

1045 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION 

1046 assert e.value.details() == "You can't unsee messages." 

1047 

1048 # this will be used to test sent request notifications 

1049 host_request_id_3 = api.CreateHostRequest( 

1050 requests_pb2.CreateHostRequestReq( 

1051 host_user_id=user1.id, 

1052 from_date=today_plus_2.isoformat(), 

1053 to_date=today_plus_3.isoformat(), 

1054 text=valid_request_text("Another test request"), 

1055 ) 

1056 ).host_request_id 

1057 

1058 moderator.approve_host_request(host_request_id_3) 

1059 

1060 with requests_session(token2) as api: 

1061 # this should make id_2 all read 

1062 api.SendHostRequestMessage( 

1063 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id_2, text="Test") 

1064 ) 

1065 

1066 with api_session(token2) as api: 

1067 assert api.Ping(api_pb2.PingReq()).unseen_received_host_request_count == 1 

1068 assert api.Ping(api_pb2.PingReq()).unseen_sent_host_request_count == 0 

1069 

1070 # make sure sent and received count for unseen notifications 

1071 with requests_session(token1) as api: 

1072 api.SendHostRequestMessage( 

1073 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id_3, text="Test message") 

1074 ) 

1075 

1076 with api_session(token2) as api: 

1077 assert api.Ping(api_pb2.PingReq()).unseen_received_host_request_count == 1 

1078 assert api.Ping(api_pb2.PingReq()).unseen_sent_host_request_count == 1 

1079 

1080 

1081def test_response_rate(db, moderator): 

1082 user1, token1 = generate_user() 

1083 user2, token2 = generate_user() 

1084 user3, token3 = generate_user(delete_user=True) 

1085 

1086 today_plus_2 = today() + timedelta(days=2) 

1087 today_plus_3 = today() + timedelta(days=3) 

1088 

1089 with session_scope() as session: 

1090 refresh_materialized_view(session, "user_response_rates") 

1091 

1092 with requests_session(token1) as api: 

1093 # deleted: not found 

1094 with pytest.raises(grpc.RpcError) as e: 

1095 api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user3.id)) 

1096 assert e.value.code() == grpc.StatusCode.NOT_FOUND 

1097 assert e.value.details() == "Couldn't find that user." 

1098 

1099 # no requests: insufficient 

1100 res = api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user2.id)) 

1101 assert res.HasField("insufficient_data") 

1102 

1103 # send a request and back date it by 36 hours 

1104 host_request_1 = api.CreateHostRequest( 

1105 requests_pb2.CreateHostRequestReq( 

1106 host_user_id=user2.id, 

1107 from_date=today_plus_2.isoformat(), 

1108 to_date=today_plus_3.isoformat(), 

1109 text=valid_request_text("Test request"), 

1110 ) 

1111 ).host_request_id 

1112 moderator.approve_host_request(host_request_1) 

1113 with session_scope() as session: 

1114 session.execute( 

1115 select(Message) 

1116 .where(Message.conversation_id == host_request_1) 

1117 .where(Message.message_type == MessageType.chat_created) 

1118 ).scalar_one().time = now() - timedelta(hours=36) 

1119 refresh_materialized_view(session, "user_response_rates") 

1120 

1121 # still insufficient 

1122 res = api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user2.id)) 

1123 assert res.HasField("insufficient_data") 

1124 

1125 # send a request and back date it by 35 hours 

1126 host_request_2 = api.CreateHostRequest( 

1127 requests_pb2.CreateHostRequestReq( 

1128 host_user_id=user2.id, 

1129 from_date=today_plus_2.isoformat(), 

1130 to_date=today_plus_3.isoformat(), 

1131 text=valid_request_text("Test request"), 

1132 ) 

1133 ).host_request_id 

1134 moderator.approve_host_request(host_request_2) 

1135 with session_scope() as session: 

1136 session.execute( 

1137 select(Message) 

1138 .where(Message.conversation_id == host_request_2) 

1139 .where(Message.message_type == MessageType.chat_created) 

1140 ).scalar_one().time = now() - timedelta(hours=35) 

1141 refresh_materialized_view(session, "user_response_rates") 

1142 

1143 # still insufficient 

1144 res = api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user2.id)) 

1145 assert res.HasField("insufficient_data") 

1146 

1147 # send a request and back date it by 34 hours 

1148 host_request_3 = api.CreateHostRequest( 

1149 requests_pb2.CreateHostRequestReq( 

1150 host_user_id=user2.id, 

1151 from_date=today_plus_2.isoformat(), 

1152 to_date=today_plus_3.isoformat(), 

1153 text=valid_request_text("Test request"), 

1154 ) 

1155 ).host_request_id 

1156 moderator.approve_host_request(host_request_3) 

1157 with session_scope() as session: 

1158 session.execute( 

1159 select(Message) 

1160 .where(Message.conversation_id == host_request_3) 

1161 .where(Message.message_type == MessageType.chat_created) 

1162 ).scalar_one().time = now() - timedelta(hours=34) 

1163 refresh_materialized_view(session, "user_response_rates") 

1164 

1165 # now low 

1166 res = api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user2.id)) 

1167 assert res.HasField("low") 

1168 

1169 with requests_session(token2) as api: 

1170 # accept a host req 

1171 api.RespondHostRequest( 

1172 requests_pb2.RespondHostRequestReq( 

1173 host_request_id=host_request_2, 

1174 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED, 

1175 text="Accepting host request", 

1176 ) 

1177 ) 

1178 

1179 with session_scope() as session: 

1180 refresh_materialized_view(session, "user_response_rates") 

1181 

1182 with requests_session(token1) as api: 

1183 # now some w p33 = 35h 

1184 res = api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user2.id)) 

1185 assert res.HasField("some") 

1186 assert res.some.response_time_p33.ToTimedelta() == timedelta(hours=35) 

1187 

1188 with requests_session(token2) as api: 

1189 # accept another host req 

1190 api.RespondHostRequest( 

1191 requests_pb2.RespondHostRequestReq( 

1192 host_request_id=host_request_3, 

1193 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED, 

1194 text="Accepting host request", 

1195 ) 

1196 ) 

1197 

1198 with session_scope() as session: 

1199 refresh_materialized_view(session, "user_response_rates") 

1200 

1201 with requests_session(token1) as api: 

1202 # now most w p33 = 34h, p66 = 35h 

1203 res = api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user2.id)) 

1204 assert res.HasField("most") 

1205 assert res.most.response_time_p33.ToTimedelta() == timedelta(hours=34) 

1206 assert res.most.response_time_p66.ToTimedelta() == timedelta(hours=35) 

1207 

1208 with requests_session(token2) as api: 

1209 # accept last host req 

1210 api.RespondHostRequest( 

1211 requests_pb2.RespondHostRequestReq( 

1212 host_request_id=host_request_1, 

1213 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED, 

1214 text="Accepting host request", 

1215 ) 

1216 ) 

1217 

1218 with session_scope() as session: 

1219 refresh_materialized_view(session, "user_response_rates") 

1220 

1221 with requests_session(token1) as api: 

1222 # now all w p33 = 34h, p66 = 35h 

1223 res = api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user2.id)) 

1224 assert res.HasField("almost_all") 

1225 assert res.almost_all.response_time_p33.ToTimedelta() == timedelta(hours=34) 

1226 assert res.almost_all.response_time_p66.ToTimedelta() == timedelta(hours=35) 

1227 

1228 # send a request and back date it by 2 hours 

1229 host_request_4 = api.CreateHostRequest( 

1230 requests_pb2.CreateHostRequestReq( 

1231 host_user_id=user2.id, 

1232 from_date=today_plus_2.isoformat(), 

1233 to_date=today_plus_3.isoformat(), 

1234 text=valid_request_text("Test request"), 

1235 ) 

1236 ).host_request_id 

1237 moderator.approve_host_request(host_request_4) 

1238 with session_scope() as session: 

1239 session.execute( 

1240 select(Message) 

1241 .where(Message.conversation_id == host_request_4) 

1242 .where(Message.message_type == MessageType.chat_created) 

1243 ).scalar_one().time = now() - timedelta(hours=2) 

1244 refresh_materialized_view(session, "user_response_rates") 

1245 

1246 # send a request and back date it by 4 hours 

1247 host_request_5 = api.CreateHostRequest( 

1248 requests_pb2.CreateHostRequestReq( 

1249 host_user_id=user2.id, 

1250 from_date=today_plus_2.isoformat(), 

1251 to_date=today_plus_3.isoformat(), 

1252 text=valid_request_text("Test request"), 

1253 ) 

1254 ).host_request_id 

1255 moderator.approve_host_request(host_request_5) 

1256 with session_scope() as session: 

1257 session.execute( 

1258 select(Message) 

1259 .where(Message.conversation_id == host_request_5) 

1260 .where(Message.message_type == MessageType.chat_created) 

1261 ).scalar_one().time = now() - timedelta(hours=4) 

1262 refresh_materialized_view(session, "user_response_rates") 

1263 

1264 # now some w p33 = 35h 

1265 res = api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user2.id)) 

1266 assert res.HasField("some") 

1267 assert res.some.response_time_p33.ToTimedelta() == timedelta(hours=35) 

1268 

1269 with requests_session(token2) as api: 

1270 # accept host req 

1271 api.RespondHostRequest( 

1272 requests_pb2.RespondHostRequestReq( 

1273 host_request_id=host_request_5, 

1274 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED, 

1275 text="Accepting host request", 

1276 ) 

1277 ) 

1278 

1279 with session_scope() as session: 

1280 refresh_materialized_view(session, "user_response_rates") 

1281 

1282 with requests_session(token1) as api: 

1283 # now most w p33 = 34h, p66 = 36h 

1284 res = api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user2.id)) 

1285 assert res.HasField("most") 

1286 assert res.most.response_time_p33.ToTimedelta() == timedelta(hours=34) 

1287 assert res.most.response_time_p66.ToTimedelta() == timedelta(hours=36) 

1288 

1289 with requests_session(token2) as api: 

1290 # accept host req 

1291 api.RespondHostRequest( 

1292 requests_pb2.RespondHostRequestReq( 

1293 host_request_id=host_request_4, 

1294 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED, 

1295 text="Accepting host request", 

1296 ) 

1297 ) 

1298 

1299 with session_scope() as session: 

1300 refresh_materialized_view(session, "user_response_rates") 

1301 

1302 with requests_session(token1) as api: 

1303 # now most w p33 = 4h, p66 = 35h 

1304 res = api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user2.id)) 

1305 assert res.HasField("almost_all") 

1306 assert res.almost_all.response_time_p33.ToTimedelta() == timedelta(hours=4) 

1307 assert res.almost_all.response_time_p66.ToTimedelta() == timedelta(hours=35) 

1308 

1309 

1310def test_request_notifications(db, push_collector: PushCollector, moderator): 

1311 host, host_token = generate_user(complete_profile=True) 

1312 surfer, surfer_token = generate_user(complete_profile=True) 

1313 

1314 host_loc_context = LocalizationContext.from_user(host) 

1315 surfer_loc_context = LocalizationContext.from_user(surfer) 

1316 

1317 today_plus_2 = today() + timedelta(days=2) 

1318 today_plus_3 = today() + timedelta(days=3) 

1319 

1320 with requests_session(surfer_token) as api: 

1321 hr_id = api.CreateHostRequest( 

1322 requests_pb2.CreateHostRequestReq( 

1323 host_user_id=host.id, 

1324 from_date=today_plus_2.isoformat(), 

1325 to_date=today_plus_3.isoformat(), 

1326 text=valid_request_text("can i stay plz"), 

1327 ) 

1328 ).host_request_id 

1329 

1330 with mock_notification_email() as mock: 

1331 moderator.approve_host_request(hr_id) 

1332 

1333 mock.assert_called_once() 

1334 e = email_fields(mock) 

1335 assert e.recipient == host.email 

1336 assert "host request" in e.subject.lower() 

1337 assert host.name in e.plain 

1338 assert host.name in e.html 

1339 assert "quick decline" in e.plain.lower(), e.plain 

1340 assert "quick decline" in e.html.lower() 

1341 assert surfer.name in e.plain 

1342 assert surfer.name in e.html 

1343 assert host_loc_context.localize_date(today_plus_2) in e.plain 

1344 assert host_loc_context.localize_date(today_plus_2) in e.html 

1345 assert host_loc_context.localize_date(today_plus_3) in e.plain 

1346 assert host_loc_context.localize_date(today_plus_3) in e.html 

1347 assert "http://localhost:5001/img/thumbnail/" not in e.plain 

1348 assert "http://localhost:5001/img/thumbnail/" in e.html 

1349 assert f"http://localhost:3000/messages/request/{hr_id}" in e.plain 

1350 assert f"http://localhost:3000/messages/request/{hr_id}" in e.html 

1351 

1352 assert push_collector.pop_for_user(host.id, last=True).content.title == f"New host request from {surfer.name}" 

1353 

1354 with requests_session(host_token) as api: 

1355 with mock_notification_email() as mock: 

1356 api.RespondHostRequest( 

1357 requests_pb2.RespondHostRequestReq( 

1358 host_request_id=hr_id, 

1359 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED, 

1360 text="Accepting host request", 

1361 ) 

1362 ) 

1363 

1364 e = email_fields(mock) 

1365 assert e.recipient == surfer.email 

1366 assert "host request" in e.subject.lower() 

1367 assert host.name in e.plain 

1368 assert host.name in e.html 

1369 assert surfer.name in e.plain 

1370 assert surfer.name in e.html 

1371 assert surfer_loc_context.localize_date(today_plus_2) in e.plain 

1372 assert surfer_loc_context.localize_date(today_plus_2) in e.html 

1373 assert surfer_loc_context.localize_date(today_plus_3) in e.plain 

1374 assert surfer_loc_context.localize_date(today_plus_3) in e.html 

1375 assert "http://localhost:5001/img/thumbnail/" not in e.plain 

1376 assert "http://localhost:5001/img/thumbnail/" in e.html 

1377 assert f"http://localhost:3000/messages/request/{hr_id}" in e.plain 

1378 assert f"http://localhost:3000/messages/request/{hr_id}" in e.html 

1379 

1380 assert push_collector.pop_for_user(surfer.id, last=True).content.title == f"{host.name} accepted your host request" 

1381 

1382 

1383def test_quick_decline(db, push_collector: PushCollector, moderator): 

1384 host, host_token = generate_user(complete_profile=True) 

1385 surfer, surfer_token = generate_user(complete_profile=True) 

1386 

1387 host_loc_context = LocalizationContext.from_user(host) 

1388 

1389 today_plus_2 = today() + timedelta(days=2) 

1390 today_plus_3 = today() + timedelta(days=3) 

1391 

1392 with requests_session(surfer_token) as api: 

1393 hr_id = api.CreateHostRequest( 

1394 requests_pb2.CreateHostRequestReq( 

1395 host_user_id=host.id, 

1396 from_date=today_plus_2.isoformat(), 

1397 to_date=today_plus_3.isoformat(), 

1398 text=valid_request_text("can i stay plz"), 

1399 ) 

1400 ).host_request_id 

1401 

1402 with mock_notification_email() as mock: 

1403 moderator.approve_host_request(hr_id) 

1404 

1405 mock.assert_called_once() 

1406 e = email_fields(mock) 

1407 assert e.recipient == host.email 

1408 assert "host request" in e.subject.lower() 

1409 assert host.name in e.plain 

1410 assert host.name in e.html 

1411 assert "quick decline" in e.plain.lower(), e.plain 

1412 assert "quick decline" in e.html.lower() 

1413 assert surfer.name in e.plain 

1414 assert surfer.name in e.html 

1415 assert host_loc_context.localize_date(today_plus_2) in e.plain 

1416 assert host_loc_context.localize_date(today_plus_2) in e.html 

1417 assert host_loc_context.localize_date(today_plus_3) in e.plain 

1418 assert host_loc_context.localize_date(today_plus_3) in e.html 

1419 assert "http://localhost:5001/img/thumbnail/" not in e.plain 

1420 assert "http://localhost:5001/img/thumbnail/" in e.html 

1421 assert f"http://localhost:3000/messages/request/{hr_id}" in e.plain 

1422 assert f"http://localhost:3000/messages/request/{hr_id}" in e.html 

1423 

1424 assert push_collector.pop_for_user(host.id, last=True).content.title == f"New host request from {surfer.name}" 

1425 

1426 # very ugly 

1427 # http://localhost:3000/quick-link?payload=CAEiGAoOZnJpZW5kX3JlcXVlc3QSBmFjY2VwdA==&sig=BQdk024NTATm8zlR0krSXTBhP5U9TlFv7VhJeIHZtUg= 

1428 for link in re.findall(r'<a href="(.*?)"', email_fields(mock).html): 1428 ↛ 1447line 1428 didn't jump to line 1447 because the loop on line 1428 didn't complete

1429 if "payload" not in link: 

1430 continue 

1431 print(link) 

1432 url_parts = urlparse(html.unescape(link)) 

1433 params = parse_qs(url_parts.query) 

1434 print(params["payload"][0]) 

1435 payload = unsubscribe_pb2.UnsubscribePayload.FromString(b64decode(params["payload"][0])) 

1436 if payload.HasField("host_request_quick_decline"): 1436 ↛ 1428line 1436 didn't jump to line 1428 because the condition on line 1436 was always true

1437 with auth_api_session() as (auth_api, metadata_interceptor): 

1438 res = auth_api.Unsubscribe( 

1439 auth_pb2.UnsubscribeReq( 

1440 payload=b64decode(params["payload"][0]), 

1441 sig=b64decode(params["sig"][0]), 

1442 ) 

1443 ) 

1444 assert res.response == "Thank you for responding to the host request!" 

1445 break 

1446 else: 

1447 raise Exception("Didn't find link") 

1448 

1449 with requests_session(surfer_token) as api: 

1450 res = api.GetHostRequest(requests_pb2.GetHostRequestReq(host_request_id=hr_id)) 

1451 assert res.status == conversations_pb2.HOST_REQUEST_STATUS_REJECTED 

1452 

1453 

1454def test_host_req_feedback(db, moderator): 

1455 host, host_token = generate_user(complete_profile=True) 

1456 host2, host2_token = generate_user(complete_profile=True) 

1457 host3, host3_token = generate_user(complete_profile=True) 

1458 surfer, surfer_token = generate_user(complete_profile=True) 

1459 

1460 today_plus_2 = today() + timedelta(days=2) 

1461 today_plus_3 = today() + timedelta(days=3) 

1462 

1463 with requests_session(surfer_token) as api: 

1464 hr_id = api.CreateHostRequest( 

1465 requests_pb2.CreateHostRequestReq( 

1466 host_user_id=host.id, 

1467 from_date=today_plus_2.isoformat(), 

1468 to_date=today_plus_3.isoformat(), 

1469 text=valid_request_text("can i stay plz"), 

1470 ) 

1471 ).host_request_id 

1472 hr2_id = api.CreateHostRequest( 

1473 requests_pb2.CreateHostRequestReq( 

1474 host_user_id=host2.id, 

1475 from_date=today_plus_2.isoformat(), 

1476 to_date=today_plus_3.isoformat(), 

1477 text=valid_request_text("can i stay plz"), 

1478 ) 

1479 ).host_request_id 

1480 hr3_id = api.CreateHostRequest( 

1481 requests_pb2.CreateHostRequestReq( 

1482 host_user_id=host3.id, 

1483 from_date=today_plus_2.isoformat(), 

1484 to_date=today_plus_3.isoformat(), 

1485 text=valid_request_text("can i stay plz"), 

1486 ) 

1487 ).host_request_id 

1488 

1489 moderator.approve_host_request(hr_id) 

1490 moderator.approve_host_request(hr2_id) 

1491 moderator.approve_host_request(hr3_id) 

1492 

1493 with requests_session(host_token) as api: 

1494 res = api.GetHostRequest(requests_pb2.GetHostRequestReq(host_request_id=hr_id)) 

1495 assert not res.need_host_request_feedback 

1496 

1497 api.RespondHostRequest( 

1498 requests_pb2.RespondHostRequestReq( 

1499 host_request_id=hr_id, 

1500 status=conversations_pb2.HOST_REQUEST_STATUS_REJECTED, 

1501 ) 

1502 ) 

1503 

1504 res = api.GetHostRequest(requests_pb2.GetHostRequestReq(host_request_id=hr_id)) 

1505 assert res.need_host_request_feedback 

1506 

1507 # surfer can't leave feedback 

1508 with requests_session(surfer_token) as api: 

1509 with pytest.raises(grpc.RpcError) as e: 

1510 api.SendHostRequestFeedback( 

1511 requests_pb2.SendHostRequestFeedbackReq( 

1512 host_request_id=hr_id, 

1513 ) 

1514 ) 

1515 assert e.value.code() == grpc.StatusCode.NOT_FOUND 

1516 assert e.value.details() == "Couldn't find that host request." 

1517 

1518 with requests_session(host_token) as api: 

1519 api.SendHostRequestFeedback( 

1520 requests_pb2.SendHostRequestFeedbackReq( 

1521 host_request_id=hr_id, 

1522 host_request_quality=requests_pb2.HOST_REQUEST_QUALITY_LOW, 

1523 ) 

1524 ) 

1525 res = api.GetHostRequest(requests_pb2.GetHostRequestReq(host_request_id=hr_id)) 

1526 assert not res.need_host_request_feedback 

1527 

1528 # can't leave it twice 

1529 with requests_session(host_token) as api: 

1530 with pytest.raises(grpc.RpcError) as e: 

1531 api.SendHostRequestFeedback( 

1532 requests_pb2.SendHostRequestFeedbackReq( 

1533 host_request_id=hr_id, 

1534 ) 

1535 ) 

1536 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION 

1537 assert e.value.details() == "You have already left feedback for this host request!" 

1538 

1539 res = api.GetHostRequest(requests_pb2.GetHostRequestReq(host_request_id=hr_id)) 

1540 assert not res.need_host_request_feedback 

1541 

1542 with requests_session(host2_token) as api: 

1543 api.RespondHostRequest( 

1544 requests_pb2.RespondHostRequestReq( 

1545 host_request_id=hr2_id, status=conversations_pb2.HOST_REQUEST_STATUS_REJECTED 

1546 ) 

1547 ) 

1548 # can't leave feedback on the wrong one 

1549 with pytest.raises(grpc.RpcError) as e: 

1550 api.SendHostRequestFeedback( 

1551 requests_pb2.SendHostRequestFeedbackReq( 

1552 host_request_id=hr_id, 

1553 ) 

1554 ) 

1555 assert e.value.code() == grpc.StatusCode.NOT_FOUND 

1556 assert e.value.details() == "Couldn't find that host request." 

1557 

1558 # null feedback is still feedback 

1559 api.SendHostRequestFeedback(requests_pb2.SendHostRequestFeedbackReq(host_request_id=hr2_id)) 

1560 

1561 with requests_session(host3_token) as api: 

1562 api.RespondHostRequest( 

1563 requests_pb2.RespondHostRequestReq( 

1564 host_request_id=hr3_id, status=conversations_pb2.HOST_REQUEST_STATUS_REJECTED 

1565 ) 

1566 ) 

1567 

1568 api.SendHostRequestFeedback( 

1569 requests_pb2.SendHostRequestFeedbackReq(host_request_id=hr3_id, decline_reason="bad req") 

1570 )