Coverage for src/tests/test_references.py: 100%

491 statements  

« prev     ^ index     » next       coverage.py v7.6.10, created at 2025-09-14 15:31 +0000

1from datetime import timedelta 

2from unittest.mock import patch 

3 

4import grpc 

5import pytest 

6from google.protobuf import empty_pb2 

7 

8from couchers import errors 

9from couchers.db import session_scope 

10from couchers.materialized_views import refresh_materialized_views_rapid 

11from couchers.models import ( 

12 Conversation, 

13 HostRequest, 

14 HostRequestStatus, 

15 Message, 

16 MessageType, 

17 Reference, 

18 ReferenceType, 

19 User, 

20) 

21from couchers.sql import couchers_select as select 

22from couchers.utils import create_coordinate, now, to_aware_datetime, today 

23from proto import conversations_pb2, references_pb2, requests_pb2 

24from tests.test_fixtures import ( # noqa 

25 account_session, 

26 db, 

27 email_fields, 

28 generate_user, 

29 make_user_block, 

30 mock_notification_email, 

31 push_collector, 

32 references_session, 

33 requests_session, 

34 testconfig, 

35) 

36 

37 

38@pytest.fixture(autouse=True) 

39def _(testconfig): 

40 pass 

41 

42 

43def create_host_request( 

44 session, 

45 surfer_user_id, 

46 host_user_id, 

47 host_request_age=timedelta(days=15), 

48 status=HostRequestStatus.confirmed, 

49 host_reason_didnt_meetup=None, 

50 surfer_reason_didnt_meetup=None, 

51): 

52 """ 

53 Create a host request that's `host_request_age` old 

54 """ 

55 from_date = today() - host_request_age - timedelta(days=2) 

56 to_date = today() - host_request_age 

57 fake_created = now() - host_request_age - timedelta(days=3) 

58 conversation = Conversation() 

59 session.add(conversation) 

60 session.flush() 

61 session.add( 

62 Message( 

63 time=fake_created + timedelta(seconds=1), 

64 conversation_id=conversation.id, 

65 author_id=surfer_user_id, 

66 message_type=MessageType.chat_created, 

67 ) 

68 ) 

69 message = Message( 

70 time=fake_created + timedelta(seconds=2), 

71 conversation_id=conversation.id, 

72 author_id=surfer_user_id, 

73 text="Hi, I'm requesting to be hosted.", 

74 message_type=MessageType.text, 

75 ) 

76 session.add(message) 

77 session.flush() 

78 host_request = HostRequest( 

79 conversation_id=conversation.id, 

80 surfer_user_id=surfer_user_id, 

81 host_user_id=host_user_id, 

82 from_date=from_date, 

83 to_date=to_date, 

84 status=status, 

85 surfer_last_seen_message_id=message.id, 

86 host_reason_didnt_meetup=host_reason_didnt_meetup, 

87 surfer_reason_didnt_meetup=surfer_reason_didnt_meetup, 

88 hosting_city="Test City", 

89 hosting_location=create_coordinate(0, 0), 

90 hosting_radius=10, 

91 ) 

92 session.add(host_request) 

93 session.commit() 

94 return host_request.conversation_id 

95 

96 

97def create_host_request_by_date( 

98 session, 

99 surfer_user_id, 

100 host_user_id, 

101 from_date, 

102 to_date, 

103 status, 

104 host_sent_request_reminders, 

105 last_sent_request_reminder_time, 

106): 

107 conversation = Conversation() 

108 session.add(conversation) 

109 session.flush() 

110 

111 session.add( 

112 Message( 

113 time=from_date + timedelta(seconds=1), 

114 conversation_id=conversation.id, 

115 author_id=surfer_user_id, 

116 message_type=MessageType.chat_created, 

117 ) 

118 ) 

119 

120 message = Message( 

121 time=from_date + timedelta(seconds=2), 

122 conversation_id=conversation.id, 

123 author_id=surfer_user_id, 

124 text="Hi, I'm requesting to be hosted.", 

125 message_type=MessageType.text, 

126 ) 

127 

128 host_request = HostRequest( 

129 conversation_id=conversation.id, 

130 surfer_user_id=surfer_user_id, 

131 host_user_id=host_user_id, 

132 from_date=from_date, 

133 to_date=to_date, 

134 status=status, 

135 host_sent_request_reminders=host_sent_request_reminders, 

136 last_sent_request_reminder_time=last_sent_request_reminder_time, 

137 hosting_city="Test City", 

138 hosting_location=create_coordinate(0, 0), 

139 hosting_radius=10, 

140 ) 

141 

142 session.add(host_request) 

143 session.commit() 

144 return host_request.conversation_id 

145 

146 

147def create_host_reference(session, from_user_id, to_user_id, reference_age, *, surfing=True, host_request_id=None): 

148 if host_request_id: 

149 actual_host_request_id = host_request_id 

150 else: 

151 if surfing: 

152 actual_host_request_id = host_request_id or create_host_request( 

153 session, from_user_id, to_user_id, reference_age + timedelta(days=1) 

154 ) 

155 else: 

156 actual_host_request_id = host_request_id or create_host_request( 

157 session, to_user_id, from_user_id, reference_age + timedelta(days=1) 

158 ) 

159 

160 host_request = session.execute( 

161 select(HostRequest).where(HostRequest.conversation_id == actual_host_request_id) 

162 ).scalar_one() 

163 

164 reference = Reference( 

165 time=now() - reference_age, 

166 from_user_id=from_user_id, 

167 host_request_id=host_request.conversation_id, 

168 text="Dummy reference", 

169 rating=0.5, 

170 was_appropriate=True, 

171 ) 

172 

173 if host_request.surfer_user_id == from_user_id: 

174 reference.reference_type = ReferenceType.surfed 

175 reference.to_user_id = host_request.host_user_id 

176 assert from_user_id == host_request.surfer_user_id 

177 else: 

178 reference.reference_type = ReferenceType.hosted 

179 reference.to_user_id = host_request.surfer_user_id 

180 assert from_user_id == host_request.host_user_id 

181 

182 session.add(reference) 

183 session.commit() 

184 return reference.id, actual_host_request_id 

185 

186 

187def create_friend_reference(session, from_user_id, to_user_id, reference_age): 

188 reference = Reference( 

189 time=now() - reference_age, 

190 from_user_id=from_user_id, 

191 to_user_id=to_user_id, 

192 reference_type=ReferenceType.friend, 

193 text="Test friend request", 

194 rating=0.4, 

195 was_appropriate=True, 

196 ) 

197 session.add(reference) 

198 session.commit() 

199 return reference.id 

200 

201 

202def test_ListPagination(db): 

203 user1, token1 = generate_user() 

204 user2, token2 = generate_user() 

205 user3, token3 = generate_user() 

206 user4, token4 = generate_user() 

207 user5, token5 = generate_user() 

208 user6, token6 = generate_user() 

209 user7, token7 = generate_user() 

210 user8, token8 = generate_user() 

211 user9, token9 = generate_user() 

212 

213 with session_scope() as session: 

214 # bidirectional references 

215 ref2, hr2 = create_host_reference(session, user2.id, user1.id, timedelta(days=16, seconds=110), surfing=True) 

216 ref2b, _ = create_host_reference( 

217 session, user1.id, user2.id, timedelta(days=16, seconds=100), host_request_id=hr2 

218 ) 

219 

220 ref3, _ = create_host_reference(session, user3.id, user1.id, timedelta(days=16, seconds=90), surfing=False) 

221 ref4, _ = create_host_reference(session, user4.id, user1.id, timedelta(days=16, seconds=80), surfing=True) 

222 ref4b = create_friend_reference(session, user1.id, user4.id, timedelta(days=16, seconds=70)) 

223 

224 ref5, hr5 = create_host_reference(session, user5.id, user1.id, timedelta(days=16, seconds=60), surfing=False) 

225 ref5b, _ = create_host_reference( 

226 session, user1.id, user5.id, timedelta(days=16, seconds=50), host_request_id=hr5 

227 ) 

228 

229 ref6, _ = create_host_reference(session, user6.id, user1.id, timedelta(days=16, seconds=40), surfing=True) 

230 

231 ref7 = create_friend_reference(session, user7.id, user1.id, timedelta(days=16, seconds=30)) 

232 

233 ref8, _ = create_host_reference(session, user8.id, user1.id, timedelta(days=16, seconds=20), surfing=False) 

234 ref9, _ = create_host_reference(session, user9.id, user1.id, timedelta(days=16, seconds=10), surfing=False) 

235 

236 # should be visible even under 2 weeks 

237 ref7b = create_friend_reference(session, user1.id, user7.id, timedelta(days=9)) 

238 

239 # hidden because it's less than 2 weeks 

240 ref6hidden, _ = create_host_reference(session, user6.id, user1.id, timedelta(days=5), surfing=False) 

241 

242 # visible because both were written 

243 ref8b, hr8 = create_host_reference(session, user8.id, user1.id, timedelta(days=3, seconds=20), surfing=False) 

244 ref8c, _ = create_host_reference( 

245 session, user1.id, user8.id, timedelta(days=3, seconds=10), host_request_id=hr8 

246 ) 

247 

248 # note that visibility tests don't really test real logic 

249 

250 # these check the right refs are in the right requests and appear in the right order (latest first) 

251 

252 with references_session(token2) as api: 

253 # written by user1 

254 res = api.ListReferences(references_pb2.ListReferencesReq(from_user_id=user1.id, page_size=2)) 

255 assert [ref.reference_id for ref in res.references] == [ref8c, ref7b] 

256 

257 res = api.ListReferences( 

258 references_pb2.ListReferencesReq(from_user_id=user1.id, page_token=res.next_page_token, page_size=2) 

259 ) 

260 assert [ref.reference_id for ref in res.references] == [ref5b, ref4b] 

261 

262 res = api.ListReferences( 

263 references_pb2.ListReferencesReq(from_user_id=user1.id, page_token=res.next_page_token, page_size=2) 

264 ) 

265 assert [ref.reference_id for ref in res.references] == [ref2b] 

266 assert not res.next_page_token 

267 

268 # received by user1 

269 res = api.ListReferences(references_pb2.ListReferencesReq(to_user_id=user1.id, page_size=5)) 

270 assert [ref.reference_id for ref in res.references] == [ref8b, ref9, ref8, ref7, ref6] 

271 

272 res = api.ListReferences( 

273 references_pb2.ListReferencesReq(to_user_id=user1.id, page_token=res.next_page_token, page_size=5) 

274 ) 

275 assert [ref.reference_id for ref in res.references] == [ref5, ref4, ref3, ref2] 

276 assert not res.next_page_token 

277 

278 # same thing but with filters 

279 res = api.ListReferences( 

280 references_pb2.ListReferencesReq( 

281 to_user_id=user1.id, 

282 reference_type_filter=[ 

283 references_pb2.REFERENCE_TYPE_HOSTED, 

284 references_pb2.REFERENCE_TYPE_SURFED, 

285 references_pb2.REFERENCE_TYPE_FRIEND, 

286 ], 

287 page_size=5, 

288 ) 

289 ) 

290 assert [ref.reference_id for ref in res.references] == [ref8b, ref9, ref8, ref7, ref6] 

291 

292 res = api.ListReferences( 

293 references_pb2.ListReferencesReq( 

294 to_user_id=user1.id, 

295 reference_type_filter=[ 

296 references_pb2.REFERENCE_TYPE_HOSTED, 

297 references_pb2.REFERENCE_TYPE_SURFED, 

298 references_pb2.REFERENCE_TYPE_FRIEND, 

299 ], 

300 page_token=res.next_page_token, 

301 page_size=5, 

302 ) 

303 ) 

304 assert [ref.reference_id for ref in res.references] == [ref5, ref4, ref3, ref2] 

305 assert not res.next_page_token 

306 

307 # received hosting references 

308 res = api.ListReferences( 

309 references_pb2.ListReferencesReq( 

310 to_user_id=user1.id, reference_type_filter=[references_pb2.REFERENCE_TYPE_HOSTED], page_size=3 

311 ) 

312 ) 

313 assert [ref.reference_id for ref in res.references] == [ref8b, ref9, ref8] 

314 

315 res = api.ListReferences( 

316 references_pb2.ListReferencesReq( 

317 to_user_id=user1.id, 

318 reference_type_filter=[references_pb2.REFERENCE_TYPE_HOSTED], 

319 page_token=res.next_page_token, 

320 page_size=3, 

321 ) 

322 ) 

323 assert [ref.reference_id for ref in res.references] == [ref5, ref3] 

324 assert not res.next_page_token 

325 

326 # written friend references 

327 res = api.ListReferences( 

328 references_pb2.ListReferencesReq( 

329 from_user_id=user1.id, reference_type_filter=[references_pb2.REFERENCE_TYPE_FRIEND] 

330 ) 

331 ) 

332 assert [ref.reference_id for ref in res.references] == [ref7b, ref4b] 

333 assert not res.next_page_token 

334 

335 # written surfing references 

336 res = api.ListReferences( 

337 references_pb2.ListReferencesReq( 

338 from_user_id=user1.id, reference_type_filter=[references_pb2.REFERENCE_TYPE_SURFED] 

339 ) 

340 ) 

341 assert [ref.reference_id for ref in res.references] == [ref8c, ref5b] 

342 assert not res.next_page_token 

343 

344 with references_session(token7) as api: 

345 # need to set at least one of from or to user 

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

347 api.ListReferences( 

348 references_pb2.ListReferencesReq(reference_type_filter=[references_pb2.REFERENCE_TYPE_SURFED]) 

349 ) 

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

351 assert e.value.details() == errors.NEED_TO_SPECIFY_AT_LEAST_ONE_USER 

352 

353 with references_session(token5) as api: 

354 # from user1 to user2 

355 res = api.ListReferences(references_pb2.ListReferencesReq(from_user_id=user1.id, to_user_id=user2.id)) 

356 assert [ref.reference_id for ref in res.references] == [ref2b] 

357 assert not res.next_page_token 

358 

359 # from user5 to user1 

360 res = api.ListReferences(references_pb2.ListReferencesReq(from_user_id=user5.id, to_user_id=user1.id)) 

361 assert [ref.reference_id for ref in res.references] == [ref5] 

362 assert not res.next_page_token 

363 

364 

365def test_ListReference_banned_deleted_users(db): 

366 user1, token1 = generate_user() 

367 user2, token2 = generate_user() 

368 user3, token3 = generate_user() 

369 

370 with session_scope() as session: 

371 create_friend_reference(session, user2.id, user1.id, timedelta(days=15)) 

372 create_friend_reference(session, user3.id, user1.id, timedelta(days=16)) 

373 create_friend_reference(session, user1.id, user2.id, timedelta(days=15)) 

374 create_friend_reference(session, user1.id, user3.id, timedelta(days=16)) 

375 

376 with references_session(token1) as api: 

377 refs_rec = api.ListReferences(references_pb2.ListReferencesReq(to_user_id=user1.id)).references 

378 refs_sent = api.ListReferences(references_pb2.ListReferencesReq(from_user_id=user1.id)).references 

379 assert len(refs_rec) == 2 

380 assert len(refs_sent) == 2 

381 

382 # ban user2 

383 with session_scope() as session: 

384 user2 = session.execute(select(User).where(User.username == user2.username)).scalar_one() 

385 user2.is_banned = True 

386 session.commit() 

387 

388 # reference to and from banned user is hidden 

389 with references_session(token1) as api: 

390 refs_rec = api.ListReferences(references_pb2.ListReferencesReq(to_user_id=user1.id)).references 

391 refs_sent = api.ListReferences(references_pb2.ListReferencesReq(from_user_id=user1.id)).references 

392 assert len(refs_rec) == 1 

393 assert len(refs_sent) == 1 

394 

395 # delete user3 

396 with session_scope() as session: 

397 user3 = session.execute(select(User).where(User.username == user3.username)).scalar_one() 

398 user3.is_deleted = True 

399 session.commit() 

400 

401 # doesn't change; references to and from deleted users remain 

402 with references_session(token1) as api: 

403 refs_rec = api.ListReferences(references_pb2.ListReferencesReq(to_user_id=user1.id)).references 

404 refs_sent = api.ListReferences(references_pb2.ListReferencesReq(from_user_id=user1.id)).references 

405 assert len(refs_rec) == 1 

406 assert len(refs_sent) == 1 

407 

408 

409def test_WriteFriendReference(db): 

410 user1, token1 = generate_user() 

411 user2, token2 = generate_user() 

412 user3, token3 = generate_user() 

413 

414 with references_session(token1) as api: 

415 # can write normal friend reference 

416 res = api.WriteFriendReference( 

417 references_pb2.WriteFriendReferenceReq( 

418 to_user_id=user2.id, 

419 text="A test reference", 

420 was_appropriate=True, 

421 rating=0.5, 

422 ) 

423 ) 

424 assert res.from_user_id == user1.id 

425 assert res.to_user_id == user2.id 

426 assert res.reference_type == references_pb2.REFERENCE_TYPE_FRIEND 

427 assert res.text == "A test reference" 

428 assert now() - timedelta(hours=24) <= to_aware_datetime(res.written_time) <= now() 

429 assert not res.host_request_id 

430 

431 with references_session(token3) as api: 

432 # check it shows up 

433 res = api.ListReferences( 

434 references_pb2.ListReferencesReq( 

435 from_user_id=user1.id, to_user_id=user2.id, reference_type_filter=[references_pb2.REFERENCE_TYPE_FRIEND] 

436 ) 

437 ) 

438 assert len(res.references) == 1 

439 ref = res.references[0] 

440 assert ref.from_user_id == user1.id 

441 assert ref.to_user_id == user2.id 

442 assert ref.reference_type == references_pb2.REFERENCE_TYPE_FRIEND 

443 assert ref.text == "A test reference" 

444 assert now() - timedelta(hours=24) <= to_aware_datetime(ref.written_time) <= now() 

445 assert not ref.host_request_id 

446 

447 with references_session(token1) as api: 

448 # can't write a second friend reference 

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

450 api.WriteFriendReference( 

451 references_pb2.WriteFriendReferenceReq( 

452 to_user_id=user2.id, 

453 text="A test reference", 

454 was_appropriate=True, 

455 rating=0.5, 

456 ) 

457 ) 

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

459 assert e.value.details() == errors.REFERENCE_ALREADY_GIVEN 

460 

461 with references_session(token2) as api: 

462 # can't write a reference about yourself 

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

464 api.WriteFriendReference( 

465 references_pb2.WriteFriendReferenceReq( 

466 to_user_id=user2.id, 

467 text="I'm really awesome", 

468 was_appropriate=True, 

469 rating=1.0, 

470 ) 

471 ) 

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

473 assert e.value.details() == errors.CANT_REFER_SELF 

474 

475 

476def test_WriteFriendReference_with_empty_text(db): 

477 user1, token1 = generate_user() 

478 user2, token2 = generate_user() 

479 

480 with references_session(token1) as api: 

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

482 api.WriteFriendReference( 

483 references_pb2.WriteFriendReferenceReq(to_user_id=user2.id, text=" ", was_appropriate=True, rating=0.8) 

484 ) 

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

486 assert e.value.details() == errors.REFERENCE_NO_TEXT 

487 

488 

489def test_WriteFriendReference_with_private_text(db, push_collector): 

490 user1, token1 = generate_user() 

491 user2, token2 = generate_user() 

492 

493 with references_session(token1) as api: 

494 with patch("couchers.email.queue_email") as mock1: 

495 with mock_notification_email() as mock2: 

496 api.WriteFriendReference( 

497 references_pb2.WriteFriendReferenceReq( 

498 to_user_id=user2.id, 

499 text="They were nice!", 

500 was_appropriate=True, 

501 rating=0.6, 

502 private_text="A bit of an odd ball, but a nice person nonetheless.", 

503 ) 

504 ) 

505 

506 # make sure an email was sent to the user receiving the ref as well as the mods 

507 assert mock1.call_count == 1 

508 assert mock2.call_count == 1 

509 e = email_fields(mock2) 

510 assert e.subject == f"[TEST] You've received a friend reference from {user1.name}!" 

511 assert e.recipient == user2.email 

512 

513 push_collector.assert_user_has_single_matching( 

514 user2.id, 

515 title=f"You've received a friend reference from {user1.name}!", 

516 body="They were nice!", 

517 ) 

518 

519 

520def test_host_request_states_references(db): 

521 user1, token1 = generate_user() 

522 user2, token2 = generate_user() 

523 

524 with session_scope() as session: 

525 # can't write ref 

526 hr1 = create_host_request(session, user2.id, user1.id, timedelta(days=10), status=HostRequestStatus.pending) 

527 # can write ref 

528 hr2 = create_host_request(session, user2.id, user1.id, timedelta(days=10), status=HostRequestStatus.accepted) 

529 # can't write ref 

530 hr3 = create_host_request(session, user2.id, user1.id, timedelta(days=10), status=HostRequestStatus.rejected) 

531 # can write ref 

532 hr4 = create_host_request(session, user2.id, user1.id, timedelta(days=10), status=HostRequestStatus.confirmed) 

533 # can't write ref 

534 hr5 = create_host_request(session, user2.id, user1.id, timedelta(days=10), status=HostRequestStatus.cancelled) 

535 

536 with references_session(token1) as api: 

537 # pending 

538 api.WriteHostRequestReference( 

539 references_pb2.WriteHostRequestReferenceReq( 

540 host_request_id=hr2, 

541 text="Should work!", 

542 was_appropriate=True, 

543 rating=0.9, 

544 ) 

545 ) 

546 

547 # accepted 

548 api.WriteHostRequestReference( 

549 references_pb2.WriteHostRequestReferenceReq( 

550 host_request_id=hr4, 

551 text="Should work!", 

552 was_appropriate=True, 

553 rating=0.9, 

554 ) 

555 ) 

556 

557 # rejected 

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

559 api.WriteHostRequestReference( 

560 references_pb2.WriteHostRequestReferenceReq( 

561 host_request_id=hr1, 

562 text="Shouldn't work...", 

563 was_appropriate=True, 

564 rating=0.9, 

565 ) 

566 ) 

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

568 assert e.value.details() == errors.CANT_WRITE_REFERENCE_FOR_REQUEST 

569 

570 # confirmed 

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

572 api.WriteHostRequestReference( 

573 references_pb2.WriteHostRequestReferenceReq( 

574 host_request_id=hr3, 

575 text="Shouldn't work...", 

576 was_appropriate=True, 

577 rating=0.9, 

578 ) 

579 ) 

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

581 assert e.value.details() == errors.CANT_WRITE_REFERENCE_FOR_REQUEST 

582 

583 # cancelled 

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

585 api.WriteHostRequestReference( 

586 references_pb2.WriteHostRequestReferenceReq( 

587 host_request_id=hr5, 

588 text="Shouldn't work...", 

589 was_appropriate=True, 

590 rating=0.9, 

591 ) 

592 ) 

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

594 assert e.value.details() == errors.CANT_WRITE_REFERENCE_FOR_REQUEST 

595 

596 

597def test_WriteHostRequestReference(db): 

598 user1, token1 = generate_user() 

599 user2, token2 = generate_user() 

600 user3, token3 = generate_user() 

601 user4, token4 = generate_user() 

602 

603 with session_scope() as session: 

604 # too old 

605 hr1 = create_host_request(session, user3.id, user1.id, timedelta(days=20)) 

606 # valid host req, surfer said we didn't show up but we can still write a req 

607 hr2 = create_host_request(session, user3.id, user1.id, timedelta(days=10), surfer_reason_didnt_meetup="No show") 

608 # valid surfing req 

609 hr3 = create_host_request(session, user1.id, user3.id, timedelta(days=7)) 

610 # not yet complete 

611 hr4 = create_host_request(session, user2.id, user1.id, timedelta(days=1), status=HostRequestStatus.pending) 

612 # we indicated we didn't meet 

613 hr5 = create_host_request(session, user4.id, user1.id, timedelta(days=7), host_reason_didnt_meetup="") 

614 # we will indicate we didn't meet 

615 hr6 = create_host_request(session, user4.id, user1.id, timedelta(days=8)) 

616 

617 with references_session(token3) as api: 

618 # can write for this one 

619 api.WriteHostRequestReference( 

620 references_pb2.WriteHostRequestReferenceReq( 

621 host_request_id=hr3, 

622 text="Should work!", 

623 was_appropriate=True, 

624 rating=0.9, 

625 ) 

626 ) 

627 

628 with references_session(token1) as api: 

629 # can't write reference for a HR that's not yet finished 

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

631 api.WriteHostRequestReference( 

632 references_pb2.WriteHostRequestReferenceReq( 

633 host_request_id=hr4, 

634 text="Shouldn't work...", 

635 was_appropriate=True, 

636 rating=0.9, 

637 ) 

638 ) 

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

640 assert e.value.details() == errors.CANT_WRITE_REFERENCE_FOR_REQUEST 

641 

642 # can't write reference that's more than 2 weeks old 

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

644 api.WriteHostRequestReference( 

645 references_pb2.WriteHostRequestReferenceReq( 

646 host_request_id=hr1, 

647 text="Shouldn't work...", 

648 was_appropriate=True, 

649 rating=0.9, 

650 ) 

651 ) 

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

653 assert e.value.details() == errors.CANT_WRITE_REFERENCE_FOR_REQUEST 

654 

655 # can write for this one 

656 api.WriteHostRequestReference( 

657 references_pb2.WriteHostRequestReferenceReq( 

658 host_request_id=hr2, 

659 text="Should work!", 

660 was_appropriate=True, 

661 rating=0.9, 

662 ) 

663 ) 

664 

665 # but can't write a second one for the same one 

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

667 api.WriteHostRequestReference( 

668 references_pb2.WriteHostRequestReferenceReq( 

669 host_request_id=hr2, 

670 text="Shouldn't work...", 

671 was_appropriate=True, 

672 rating=0.9, 

673 ) 

674 ) 

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

676 assert e.value.details() == errors.REFERENCE_ALREADY_GIVEN 

677 

678 # can write for this one too 

679 api.WriteHostRequestReference( 

680 references_pb2.WriteHostRequestReferenceReq( 

681 host_request_id=hr3, 

682 text="Should work!", 

683 was_appropriate=True, 

684 rating=0.9, 

685 ) 

686 ) 

687 

688 # can't write reference for a HR that we indicated we didn't show up 

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

690 api.WriteHostRequestReference( 

691 references_pb2.WriteHostRequestReferenceReq( 

692 host_request_id=hr5, 

693 text="Shouldn't work...", 

694 was_appropriate=True, 

695 rating=0.9, 

696 ) 

697 ) 

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

699 assert e.value.details() == errors.CANT_WRITE_REFERENCE_INDICATED_DIDNT_MEETUP 

700 

701 # can't write reference for a HR that we indicate we didn't show up for 

702 api.HostRequestIndicateDidntMeetup( 

703 references_pb2.HostRequestIndicateDidntMeetupReq( 

704 host_request_id=hr6, 

705 reason_didnt_meetup="No clue?", 

706 ) 

707 ) 

708 

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

710 api.WriteHostRequestReference( 

711 references_pb2.WriteHostRequestReferenceReq( 

712 host_request_id=hr6, 

713 text="Shouldn't work...", 

714 was_appropriate=True, 

715 rating=0.9, 

716 ) 

717 ) 

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

719 assert e.value.details() == errors.CANT_WRITE_REFERENCE_INDICATED_DIDNT_MEETUP 

720 

721 with references_session(token4) as api: 

722 # they can still write one 

723 api.WriteHostRequestReference( 

724 references_pb2.WriteHostRequestReferenceReq( 

725 host_request_id=hr6, 

726 text="Should work!", 

727 was_appropriate=True, 

728 rating=0.9, 

729 ) 

730 ) 

731 

732 

733def test_WriteHostRequestReference_private_text(db, push_collector): 

734 user1, token1 = generate_user() 

735 user2, token2 = generate_user() 

736 

737 with session_scope() as session: 

738 hr = create_host_request(session, user1.id, user2.id, timedelta(days=10)) 

739 

740 with references_session(token1) as api: 

741 with patch("couchers.email.queue_email") as mock1: 

742 with mock_notification_email() as mock2: 

743 api.WriteHostRequestReference( 

744 references_pb2.WriteHostRequestReferenceReq( 

745 host_request_id=hr, 

746 text="Should work!", 

747 was_appropriate=True, 

748 rating=0.9, 

749 private_text="Something", 

750 ) 

751 ) 

752 

753 # make sure an email was sent to the user receiving the ref as well as the mods 

754 assert mock1.call_count == 1 

755 assert mock2.call_count == 1 

756 

757 e = email_fields(mock2) 

758 assert e.subject == f"[TEST] You've received a reference from {user1.name}!" 

759 assert e.recipient == user2.email 

760 

761 push_collector.assert_user_has_single_matching( 

762 user2.id, 

763 title=f"You've received a reference from {user1.name}!", 

764 body="Please go and write a reference for them too. It's a nice gesture and helps us build a community together!", 

765 ) 

766 

767 

768def test_AvailableWriteReferences_and_ListPendingReferencesToWrite(db): 

769 user1, token1 = generate_user() 

770 user2, token2 = generate_user() 

771 user3, token3 = generate_user() 

772 user4, token4 = generate_user() 

773 user5, token5 = generate_user(delete_user=True) 

774 user6, token6 = generate_user() 

775 user7, token7 = generate_user() 

776 user8, token8 = generate_user() 

777 user9, token9 = generate_user() 

778 user10, token10 = generate_user() 

779 user11, token11 = generate_user() 

780 make_user_block(user1, user6) 

781 make_user_block(user7, user1) 

782 

783 with session_scope() as session: 

784 # too old 

785 hr1 = create_host_request(session, user3.id, user1.id, timedelta(days=20)) 

786 

787 # already wrote friend ref to user3 

788 create_friend_reference(session, user1.id, user3.id, timedelta(days=15, seconds=70)) 

789 

790 # already given 

791 _, hr2 = create_host_reference(session, user2.id, user1.id, timedelta(days=10, seconds=110), surfing=True) 

792 create_host_reference(session, user1.id, user2.id, timedelta(days=10, seconds=100), host_request_id=hr2) 

793 

794 # valid hosted 

795 hr3 = create_host_request(session, user3.id, user1.id, timedelta(days=8)) 

796 

797 # valid surfed 

798 hr4 = create_host_request(session, user1.id, user4.id, timedelta(days=5)) 

799 

800 # not yet complete 

801 hr5 = create_host_request(session, user2.id, user1.id, timedelta(days=2), status=HostRequestStatus.pending) 

802 

803 # already wrote friend ref to user2 

804 create_friend_reference(session, user1.id, user2.id, timedelta(days=1)) 

805 

806 # user5 deleted, reference won't show up as pending 

807 create_host_request(session, user1.id, user5.id, timedelta(days=5)) 

808 

809 # user6 blocked, reference won't show up as pending 

810 create_host_request(session, user1.id, user6.id, timedelta(days=5)) 

811 

812 # user7 blocking, reference won't show up as pending 

813 create_host_request(session, user1.id, user7.id, timedelta(days=5)) 

814 

815 # hosted but we indicated we didn't meet up, no reason; should not show up 

816 create_host_request(session, user8.id, user1.id, timedelta(days=11), host_reason_didnt_meetup="") 

817 

818 # surfed but we indicated we didn't meet up, has reason; should not show up 

819 create_host_request( 

820 session, user1.id, user9.id, timedelta(days=10), surfer_reason_didnt_meetup="They never showed up!" 

821 ) 

822 

823 # surfed but they indicated we didn't meet up, no reason; should show up 

824 hr6 = create_host_request(session, user1.id, user10.id, timedelta(days=4), host_reason_didnt_meetup="") 

825 

826 # hosted but they indicated we didn't meet up, has reason; should show up 

827 hr7 = create_host_request( 

828 session, user11.id, user1.id, timedelta(days=3), surfer_reason_didnt_meetup="They never showed up!!" 

829 ) 

830 

831 refresh_materialized_views_rapid(None) 

832 

833 with references_session(token1) as api: 

834 # can't write reference for invisible user 

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

836 api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user5.id)) 

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

838 assert e.value.details() == errors.USER_NOT_FOUND 

839 

840 # can't write reference for blocking user 

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

842 api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user7.id)) 

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

844 assert e.value.details() == errors.USER_NOT_FOUND 

845 

846 # can't write reference for blocked user 

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

848 api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user6.id)) 

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

850 assert e.value.details() == errors.USER_NOT_FOUND 

851 

852 # can't write anything to myself 

853 res = api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user1.id)) 

854 assert not res.can_write_friend_reference 

855 assert len(res.available_write_references) == 0 

856 

857 res = api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user2.id)) 

858 # can't write friend ref to user2 

859 assert not res.can_write_friend_reference 

860 # none we can write for user2 

861 assert len(res.available_write_references) == 0 

862 

863 res = api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user3.id)) 

864 # can't write friend ref to user3 

865 assert not res.can_write_friend_reference 

866 # can write one reference because we hosted user3 

867 assert len(res.available_write_references) == 1 

868 w = res.available_write_references[0] 

869 assert w.host_request_id == hr3 

870 assert w.reference_type == references_pb2.REFERENCE_TYPE_HOSTED 

871 assert now() + timedelta(days=6) <= to_aware_datetime(w.time_expires) <= now() + timedelta(days=7) 

872 

873 res = api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user4.id)) 

874 # can write friend ref to user4 

875 assert res.can_write_friend_reference 

876 # can write one reference because we surfed with user4 

877 assert len(res.available_write_references) == 1 

878 w = res.available_write_references[0] 

879 assert w.host_request_id == hr4 

880 assert w.reference_type == references_pb2.REFERENCE_TYPE_SURFED 

881 assert now() + timedelta(days=9) <= to_aware_datetime(w.time_expires) <= now() + timedelta(days=10) 

882 

883 # can't write a req if we indicated we didn't meet up 

884 res = api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user8.id)) 

885 assert len(res.available_write_references) == 0 

886 res = api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user9.id)) 

887 assert len(res.available_write_references) == 0 

888 

889 # can still write ref if the other person indicated we didn't meet up 

890 # surfed with them 

891 res = api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user10.id)) 

892 assert len(res.available_write_references) == 1 

893 w = res.available_write_references[0] 

894 assert w.host_request_id == hr6 

895 assert w.reference_type == references_pb2.REFERENCE_TYPE_SURFED 

896 assert now() + timedelta(days=10) <= to_aware_datetime(w.time_expires) <= now() + timedelta(days=11) 

897 # hosted them 

898 res = api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user11.id)) 

899 assert len(res.available_write_references) == 1 

900 w = res.available_write_references[0] 

901 assert w.host_request_id == hr7 

902 assert w.reference_type == references_pb2.REFERENCE_TYPE_HOSTED 

903 assert now() + timedelta(days=11) <= to_aware_datetime(w.time_expires) <= now() + timedelta(days=12) 

904 

905 # finally check the general list 

906 res = api.ListPendingReferencesToWrite(empty_pb2.Empty()) 

907 assert len(res.pending_references) == 4 

908 w = res.pending_references[0] 

909 assert w.host_request_id == hr3 

910 assert w.reference_type == references_pb2.REFERENCE_TYPE_HOSTED 

911 assert now() + timedelta(days=6) <= to_aware_datetime(w.time_expires) <= now() + timedelta(days=7) 

912 w = res.pending_references[1] 

913 assert w.host_request_id == hr4 

914 assert w.reference_type == references_pb2.REFERENCE_TYPE_SURFED 

915 assert now() + timedelta(days=9) <= to_aware_datetime(w.time_expires) <= now() + timedelta(days=10) 

916 w = res.pending_references[2] 

917 assert w.host_request_id == hr6 

918 assert w.reference_type == references_pb2.REFERENCE_TYPE_SURFED 

919 assert now() + timedelta(days=10) <= to_aware_datetime(w.time_expires) <= now() + timedelta(days=11) 

920 w = res.pending_references[3] 

921 assert w.host_request_id == hr7 

922 assert w.reference_type == references_pb2.REFERENCE_TYPE_HOSTED 

923 assert now() + timedelta(days=11) <= to_aware_datetime(w.time_expires) <= now() + timedelta(days=12) 

924 

925 with account_session(token1) as account: 

926 reminders = account.GetReminders(empty_pb2.Empty()).reminders 

927 assert [reminder.WhichOneof("reminder") for reminder in reminders] == [ 

928 "write_reference_reminder", 

929 "write_reference_reminder", 

930 "write_reference_reminder", 

931 "write_reference_reminder", 

932 "complete_verification_reminder", 

933 ] 

934 assert reminders[0].write_reference_reminder.host_request_id == hr3 

935 assert reminders[0].write_reference_reminder.reference_type == references_pb2.REFERENCE_TYPE_HOSTED 

936 assert reminders[0].write_reference_reminder.other_user.user_id == user3.id 

937 assert reminders[1].write_reference_reminder.host_request_id == hr4 

938 assert reminders[1].write_reference_reminder.reference_type == references_pb2.REFERENCE_TYPE_SURFED 

939 assert reminders[1].write_reference_reminder.other_user.user_id == user4.id 

940 assert reminders[2].write_reference_reminder.host_request_id == hr6 

941 assert reminders[2].write_reference_reminder.reference_type == references_pb2.REFERENCE_TYPE_SURFED 

942 assert reminders[2].write_reference_reminder.other_user.user_id == user10.id 

943 assert reminders[3].write_reference_reminder.host_request_id == hr7 

944 assert reminders[3].write_reference_reminder.reference_type == references_pb2.REFERENCE_TYPE_HOSTED 

945 assert reminders[3].write_reference_reminder.other_user.user_id == user11.id 

946 

947 

948@pytest.mark.parametrize("hs", ["host", "surfer"]) 

949def test_regression_disappearing_refs(db, hs): 

950 """ 

951 Roughly the reproduction steps are: 

952 * Send a host request, then have both host and surfer accept 

953 * Wait for it to elapse (or hack it with SQL like what you told me to do) 

954 * On the surfer account, leave a reference 

955 * Then on the host account, the option to leave a reference is then not available 

956 """ 

957 user1, token1 = generate_user() 

958 user2, token2 = generate_user() 

959 req_start = (today() + timedelta(days=2)).isoformat() 

960 req_end = (today() + timedelta(days=3)).isoformat() 

961 with requests_session(token1) as api: 

962 res = api.CreateHostRequest( 

963 requests_pb2.CreateHostRequestReq( 

964 host_user_id=user2.id, from_date=req_start, to_date=req_end, text="Test request" 

965 ) 

966 ) 

967 host_request_id = res.host_request_id 

968 assert ( 

969 api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_sent=True)) 

970 .host_requests[0] 

971 .latest_message.text.text 

972 == "Test request" 

973 ) 

974 

975 with requests_session(token2) as api: 

976 api.RespondHostRequest( 

977 requests_pb2.RespondHostRequestReq( 

978 host_request_id=host_request_id, status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED 

979 ) 

980 ) 

981 

982 with requests_session(token1) as api: 

983 api.RespondHostRequest( 

984 requests_pb2.RespondHostRequestReq( 

985 host_request_id=host_request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CONFIRMED 

986 ) 

987 ) 

988 

989 refresh_materialized_views_rapid(None) 

990 

991 with references_session(token1) as api: 

992 res = api.ListPendingReferencesToWrite(empty_pb2.Empty()) 

993 assert len(res.pending_references) == 0 

994 res = api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user2.id)) 

995 assert len(res.available_write_references) == 0 

996 

997 with references_session(token2) as api: 

998 res = api.ListPendingReferencesToWrite(empty_pb2.Empty()) 

999 assert len(res.pending_references) == 0 

1000 res = api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user1.id)) 

1001 assert len(res.available_write_references) == 0 

1002 

1003 # hack the time backwards 

1004 hack_req_start = today() - timedelta(days=10) + timedelta(days=2) 

1005 hack_req_end = today() - timedelta(days=10) + timedelta(days=3) 

1006 with session_scope() as session: 

1007 host_request = session.execute(select(HostRequest)).scalar_one() 

1008 assert host_request.conversation_id == host_request_id 

1009 host_request.from_date = hack_req_start 

1010 host_request.to_date = hack_req_end 

1011 

1012 with references_session(token1) as api: 

1013 res = api.ListPendingReferencesToWrite(empty_pb2.Empty()) 

1014 assert len(res.pending_references) == 1 

1015 assert res.pending_references[0].host_request_id == host_request_id 

1016 assert res.pending_references[0].reference_type == references_pb2.REFERENCE_TYPE_SURFED 

1017 

1018 res = api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user2.id)) 

1019 assert len(res.available_write_references) == 1 

1020 assert res.available_write_references[0].host_request_id == host_request_id 

1021 assert res.available_write_references[0].reference_type == references_pb2.REFERENCE_TYPE_SURFED 

1022 

1023 with references_session(token2) as api: 

1024 res = api.ListPendingReferencesToWrite(empty_pb2.Empty()) 

1025 assert len(res.pending_references) == 1 

1026 assert res.pending_references[0].host_request_id == host_request_id 

1027 assert res.pending_references[0].reference_type == references_pb2.REFERENCE_TYPE_HOSTED 

1028 

1029 res = api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user1.id)) 

1030 assert len(res.available_write_references) == 1 

1031 assert res.available_write_references[0].host_request_id == host_request_id 

1032 assert res.available_write_references[0].reference_type == references_pb2.REFERENCE_TYPE_HOSTED 

1033 

1034 if hs == "host": 

1035 with references_session(token2) as api: 

1036 api.WriteHostRequestReference( 

1037 references_pb2.WriteHostRequestReferenceReq( 

1038 host_request_id=host_request_id, 

1039 text="Good stuff", 

1040 was_appropriate=True, 

1041 rating=0.86, 

1042 ) 

1043 ) 

1044 

1045 with references_session(token2) as api: 

1046 res = api.ListPendingReferencesToWrite(empty_pb2.Empty()) 

1047 assert len(res.pending_references) == 0 

1048 

1049 res = api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user2.id)) 

1050 assert len(res.available_write_references) == 0 

1051 

1052 with references_session(token1) as api: 

1053 res = api.ListPendingReferencesToWrite(empty_pb2.Empty()) 

1054 assert len(res.pending_references) == 1 

1055 assert res.pending_references[0].host_request_id == host_request_id 

1056 assert res.pending_references[0].reference_type == references_pb2.REFERENCE_TYPE_SURFED 

1057 

1058 res = api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user2.id)) 

1059 assert len(res.available_write_references) == 1 

1060 assert res.available_write_references[0].host_request_id == host_request_id 

1061 assert res.available_write_references[0].reference_type == references_pb2.REFERENCE_TYPE_SURFED 

1062 else: 

1063 with references_session(token1) as api: 

1064 api.WriteHostRequestReference( 

1065 references_pb2.WriteHostRequestReferenceReq( 

1066 host_request_id=host_request_id, 

1067 text="Good stuff", 

1068 was_appropriate=True, 

1069 rating=0.86, 

1070 ) 

1071 ) 

1072 

1073 with references_session(token1) as api: 

1074 res = api.ListPendingReferencesToWrite(empty_pb2.Empty()) 

1075 assert len(res.pending_references) == 0 

1076 

1077 res = api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user1.id)) 

1078 assert len(res.available_write_references) == 0 

1079 

1080 with references_session(token2) as api: 

1081 res = api.ListPendingReferencesToWrite(empty_pb2.Empty()) 

1082 assert len(res.pending_references) == 1 

1083 assert res.pending_references[0].host_request_id == host_request_id 

1084 assert res.pending_references[0].reference_type == references_pb2.REFERENCE_TYPE_HOSTED 

1085 

1086 res = api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user1.id)) 

1087 assert len(res.available_write_references) == 1 

1088 assert res.available_write_references[0].host_request_id == host_request_id 

1089 assert res.available_write_references[0].reference_type == references_pb2.REFERENCE_TYPE_HOSTED