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

473 statements  

« prev     ^ index     » next       coverage.py v7.6.10, created at 2025-07-09 00:05 +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.models import ( 

11 Conversation, 

12 HostRequest, 

13 HostRequestStatus, 

14 Message, 

15 MessageType, 

16 Reference, 

17 ReferenceType, 

18 User, 

19) 

20from couchers.sql import couchers_select as select 

21from couchers.utils import now, to_aware_datetime, today 

22from proto import conversations_pb2, references_pb2, requests_pb2 

23from tests.test_fixtures import ( # noqa 

24 db, 

25 email_fields, 

26 generate_user, 

27 make_user_block, 

28 mock_notification_email, 

29 push_collector, 

30 references_session, 

31 requests_session, 

32 testconfig, 

33) 

34 

35 

36@pytest.fixture(autouse=True) 

37def _(testconfig): 

38 pass 

39 

40 

41def create_host_request( 

42 session, 

43 surfer_user_id, 

44 host_user_id, 

45 host_request_age=timedelta(days=15), 

46 status=HostRequestStatus.confirmed, 

47 host_reason_didnt_meetup=None, 

48 surfer_reason_didnt_meetup=None, 

49): 

50 """ 

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

52 """ 

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

54 to_date = today() - host_request_age 

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

56 conversation = Conversation() 

57 session.add(conversation) 

58 session.flush() 

59 session.add( 

60 Message( 

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

62 conversation_id=conversation.id, 

63 author_id=surfer_user_id, 

64 message_type=MessageType.chat_created, 

65 ) 

66 ) 

67 message = Message( 

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

69 conversation_id=conversation.id, 

70 author_id=surfer_user_id, 

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

72 message_type=MessageType.text, 

73 ) 

74 session.add(message) 

75 session.flush() 

76 host_request = HostRequest( 

77 conversation_id=conversation.id, 

78 surfer_user_id=surfer_user_id, 

79 host_user_id=host_user_id, 

80 from_date=from_date, 

81 to_date=to_date, 

82 status=status, 

83 surfer_last_seen_message_id=message.id, 

84 host_reason_didnt_meetup=host_reason_didnt_meetup, 

85 surfer_reason_didnt_meetup=surfer_reason_didnt_meetup, 

86 ) 

87 session.add(host_request) 

88 session.commit() 

89 return host_request.conversation_id 

90 

91 

92def create_host_request_by_date( 

93 session, 

94 surfer_user_id, 

95 host_user_id, 

96 from_date, 

97 to_date, 

98 status, 

99 host_sent_request_reminders, 

100 last_sent_request_reminder_time, 

101): 

102 conversation = Conversation() 

103 session.add(conversation) 

104 session.flush() 

105 

106 session.add( 

107 Message( 

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

109 conversation_id=conversation.id, 

110 author_id=surfer_user_id, 

111 message_type=MessageType.chat_created, 

112 ) 

113 ) 

114 

115 message = Message( 

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

117 conversation_id=conversation.id, 

118 author_id=surfer_user_id, 

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

120 message_type=MessageType.text, 

121 ) 

122 

123 host_request = HostRequest( 

124 conversation_id=conversation.id, 

125 surfer_user_id=surfer_user_id, 

126 host_user_id=host_user_id, 

127 from_date=from_date, 

128 to_date=to_date, 

129 status=status, 

130 host_sent_request_reminders=host_sent_request_reminders, 

131 last_sent_request_reminder_time=last_sent_request_reminder_time, 

132 ) 

133 

134 session.add(host_request) 

135 session.commit() 

136 return host_request.conversation_id 

137 

138 

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

140 if host_request_id: 

141 actual_host_request_id = host_request_id 

142 else: 

143 if surfing: 

144 actual_host_request_id = host_request_id or create_host_request( 

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

146 ) 

147 else: 

148 actual_host_request_id = host_request_id or create_host_request( 

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

150 ) 

151 

152 host_request = session.execute( 

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

154 ).scalar_one() 

155 

156 reference = Reference( 

157 time=now() - reference_age, 

158 from_user_id=from_user_id, 

159 host_request_id=host_request.conversation_id, 

160 text="Dummy reference", 

161 rating=0.5, 

162 was_appropriate=True, 

163 ) 

164 

165 if host_request.surfer_user_id == from_user_id: 

166 reference.reference_type = ReferenceType.surfed 

167 reference.to_user_id = host_request.host_user_id 

168 assert from_user_id == host_request.surfer_user_id 

169 else: 

170 reference.reference_type = ReferenceType.hosted 

171 reference.to_user_id = host_request.surfer_user_id 

172 assert from_user_id == host_request.host_user_id 

173 

174 session.add(reference) 

175 session.commit() 

176 return reference.id, actual_host_request_id 

177 

178 

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

180 reference = Reference( 

181 time=now() - reference_age, 

182 from_user_id=from_user_id, 

183 to_user_id=to_user_id, 

184 reference_type=ReferenceType.friend, 

185 text="Test friend request", 

186 rating=0.4, 

187 was_appropriate=True, 

188 ) 

189 session.add(reference) 

190 session.commit() 

191 return reference.id 

192 

193 

194def test_ListPagination(db): 

195 user1, token1 = generate_user() 

196 user2, token2 = generate_user() 

197 user3, token3 = generate_user() 

198 user4, token4 = generate_user() 

199 user5, token5 = generate_user() 

200 user6, token6 = generate_user() 

201 user7, token7 = generate_user() 

202 user8, token8 = generate_user() 

203 user9, token9 = generate_user() 

204 

205 with session_scope() as session: 

206 # bidirectional references 

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

208 ref2b, _ = create_host_reference( 

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

210 ) 

211 

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

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

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

215 

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

217 ref5b, _ = create_host_reference( 

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

219 ) 

220 

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

222 

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

224 

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

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

227 

228 # should be visible even under 2 weeks 

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

230 

231 # hidden because it's less than 2 weeks 

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

233 

234 # visible because both were written 

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

236 ref8c, _ = create_host_reference( 

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

238 ) 

239 

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

241 

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

243 

244 with references_session(token2) as api: 

245 # written by user1 

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

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

248 

249 res = api.ListReferences( 

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

251 ) 

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

253 

254 res = api.ListReferences( 

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

256 ) 

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

258 assert not res.next_page_token 

259 

260 # received by user1 

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

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

263 

264 res = api.ListReferences( 

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

266 ) 

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

268 assert not res.next_page_token 

269 

270 # same thing but with filters 

271 res = api.ListReferences( 

272 references_pb2.ListReferencesReq( 

273 to_user_id=user1.id, 

274 reference_type_filter=[ 

275 references_pb2.REFERENCE_TYPE_HOSTED, 

276 references_pb2.REFERENCE_TYPE_SURFED, 

277 references_pb2.REFERENCE_TYPE_FRIEND, 

278 ], 

279 page_size=5, 

280 ) 

281 ) 

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

283 

284 res = api.ListReferences( 

285 references_pb2.ListReferencesReq( 

286 to_user_id=user1.id, 

287 reference_type_filter=[ 

288 references_pb2.REFERENCE_TYPE_HOSTED, 

289 references_pb2.REFERENCE_TYPE_SURFED, 

290 references_pb2.REFERENCE_TYPE_FRIEND, 

291 ], 

292 page_token=res.next_page_token, 

293 page_size=5, 

294 ) 

295 ) 

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

297 assert not res.next_page_token 

298 

299 # received hosting references 

300 res = api.ListReferences( 

301 references_pb2.ListReferencesReq( 

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

303 ) 

304 ) 

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

306 

307 res = api.ListReferences( 

308 references_pb2.ListReferencesReq( 

309 to_user_id=user1.id, 

310 reference_type_filter=[references_pb2.REFERENCE_TYPE_HOSTED], 

311 page_token=res.next_page_token, 

312 page_size=3, 

313 ) 

314 ) 

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

316 assert not res.next_page_token 

317 

318 # written friend references 

319 res = api.ListReferences( 

320 references_pb2.ListReferencesReq( 

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

322 ) 

323 ) 

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

325 assert not res.next_page_token 

326 

327 # written surfing references 

328 res = api.ListReferences( 

329 references_pb2.ListReferencesReq( 

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

331 ) 

332 ) 

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

334 assert not res.next_page_token 

335 

336 with references_session(token7) as api: 

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

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

339 api.ListReferences( 

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

341 ) 

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

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

344 

345 with references_session(token5) as api: 

346 # from user1 to user2 

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

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

349 assert not res.next_page_token 

350 

351 # from user5 to user1 

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

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

354 assert not res.next_page_token 

355 

356 

357def test_ListReference_banned_deleted_users(db): 

358 user1, token1 = generate_user() 

359 user2, token2 = generate_user() 

360 user3, token3 = generate_user() 

361 

362 with session_scope() as session: 

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

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

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

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

367 

368 with references_session(token1) as api: 

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

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

371 assert len(refs_rec) == 2 

372 assert len(refs_sent) == 2 

373 

374 # ban user2 

375 with session_scope() as session: 

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

377 user2.is_banned = True 

378 session.commit() 

379 

380 # reference to and from banned user is hidden 

381 with references_session(token1) as api: 

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

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

384 assert len(refs_rec) == 1 

385 assert len(refs_sent) == 1 

386 

387 # delete user3 

388 with session_scope() as session: 

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

390 user3.is_deleted = True 

391 session.commit() 

392 

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

394 with references_session(token1) as api: 

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

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

397 assert len(refs_rec) == 1 

398 assert len(refs_sent) == 1 

399 

400 

401def test_WriteFriendReference(db): 

402 user1, token1 = generate_user() 

403 user2, token2 = generate_user() 

404 user3, token3 = generate_user() 

405 

406 with references_session(token1) as api: 

407 # can write normal friend reference 

408 res = api.WriteFriendReference( 

409 references_pb2.WriteFriendReferenceReq( 

410 to_user_id=user2.id, 

411 text="A test reference", 

412 was_appropriate=True, 

413 rating=0.5, 

414 ) 

415 ) 

416 assert res.from_user_id == user1.id 

417 assert res.to_user_id == user2.id 

418 assert res.reference_type == references_pb2.REFERENCE_TYPE_FRIEND 

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

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

421 assert not res.host_request_id 

422 

423 with references_session(token3) as api: 

424 # check it shows up 

425 res = api.ListReferences( 

426 references_pb2.ListReferencesReq( 

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

428 ) 

429 ) 

430 assert len(res.references) == 1 

431 ref = res.references[0] 

432 assert ref.from_user_id == user1.id 

433 assert ref.to_user_id == user2.id 

434 assert ref.reference_type == references_pb2.REFERENCE_TYPE_FRIEND 

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

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

437 assert not ref.host_request_id 

438 

439 with references_session(token1) as api: 

440 # can't write a second friend reference 

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

442 api.WriteFriendReference( 

443 references_pb2.WriteFriendReferenceReq( 

444 to_user_id=user2.id, 

445 text="A test reference", 

446 was_appropriate=True, 

447 rating=0.5, 

448 ) 

449 ) 

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

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

452 

453 with references_session(token2) as api: 

454 # can't write a reference about yourself 

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

456 api.WriteFriendReference( 

457 references_pb2.WriteFriendReferenceReq( 

458 to_user_id=user2.id, 

459 text="I'm really awesome", 

460 was_appropriate=True, 

461 rating=1.0, 

462 ) 

463 ) 

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

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

466 

467 

468def test_WriteFriendReference_with_empty_text(db): 

469 user1, token1 = generate_user() 

470 user2, token2 = generate_user() 

471 

472 with references_session(token1) as api: 

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

474 api.WriteFriendReference( 

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

476 ) 

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

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

479 

480 

481def test_WriteFriendReference_with_private_text(db, push_collector): 

482 user1, token1 = generate_user() 

483 user2, token2 = generate_user() 

484 

485 with references_session(token1) as api: 

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

487 with mock_notification_email() as mock2: 

488 api.WriteFriendReference( 

489 references_pb2.WriteFriendReferenceReq( 

490 to_user_id=user2.id, 

491 text="They were nice!", 

492 was_appropriate=True, 

493 rating=0.6, 

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

495 ) 

496 ) 

497 

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

499 assert mock1.call_count == 1 

500 assert mock2.call_count == 1 

501 e = email_fields(mock2) 

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

503 assert e.recipient == user2.email 

504 

505 push_collector.assert_user_has_single_matching( 

506 user2.id, 

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

508 body="They were nice!", 

509 ) 

510 

511 

512def test_host_request_states_references(db): 

513 user1, token1 = generate_user() 

514 user2, token2 = generate_user() 

515 

516 with session_scope() as session: 

517 # can't write ref 

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

519 # can write ref 

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

521 # can't write ref 

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

523 # can write ref 

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

525 # can't write ref 

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

527 

528 with references_session(token1) as api: 

529 # pending 

530 api.WriteHostRequestReference( 

531 references_pb2.WriteHostRequestReferenceReq( 

532 host_request_id=hr2, 

533 text="Should work!", 

534 was_appropriate=True, 

535 rating=0.9, 

536 ) 

537 ) 

538 

539 # accepted 

540 api.WriteHostRequestReference( 

541 references_pb2.WriteHostRequestReferenceReq( 

542 host_request_id=hr4, 

543 text="Should work!", 

544 was_appropriate=True, 

545 rating=0.9, 

546 ) 

547 ) 

548 

549 # rejected 

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

551 api.WriteHostRequestReference( 

552 references_pb2.WriteHostRequestReferenceReq( 

553 host_request_id=hr1, 

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

555 was_appropriate=True, 

556 rating=0.9, 

557 ) 

558 ) 

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

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

561 

562 # confirmed 

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

564 api.WriteHostRequestReference( 

565 references_pb2.WriteHostRequestReferenceReq( 

566 host_request_id=hr3, 

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

568 was_appropriate=True, 

569 rating=0.9, 

570 ) 

571 ) 

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

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

574 

575 # cancelled 

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

577 api.WriteHostRequestReference( 

578 references_pb2.WriteHostRequestReferenceReq( 

579 host_request_id=hr5, 

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

581 was_appropriate=True, 

582 rating=0.9, 

583 ) 

584 ) 

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

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

587 

588 

589def test_WriteHostRequestReference(db): 

590 user1, token1 = generate_user() 

591 user2, token2 = generate_user() 

592 user3, token3 = generate_user() 

593 user4, token4 = generate_user() 

594 

595 with session_scope() as session: 

596 # too old 

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

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

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

600 # valid surfing req 

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

602 # not yet complete 

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

604 # we indicated we didn't meet 

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

606 # we will indicate we didn't meet 

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

608 

609 with references_session(token3) as api: 

610 # can write for this one 

611 api.WriteHostRequestReference( 

612 references_pb2.WriteHostRequestReferenceReq( 

613 host_request_id=hr3, 

614 text="Should work!", 

615 was_appropriate=True, 

616 rating=0.9, 

617 ) 

618 ) 

619 

620 with references_session(token1) as api: 

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

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

623 api.WriteHostRequestReference( 

624 references_pb2.WriteHostRequestReferenceReq( 

625 host_request_id=hr4, 

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

627 was_appropriate=True, 

628 rating=0.9, 

629 ) 

630 ) 

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

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

633 

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

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

636 api.WriteHostRequestReference( 

637 references_pb2.WriteHostRequestReferenceReq( 

638 host_request_id=hr1, 

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

640 was_appropriate=True, 

641 rating=0.9, 

642 ) 

643 ) 

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

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

646 

647 # can write for this one 

648 api.WriteHostRequestReference( 

649 references_pb2.WriteHostRequestReferenceReq( 

650 host_request_id=hr2, 

651 text="Should work!", 

652 was_appropriate=True, 

653 rating=0.9, 

654 ) 

655 ) 

656 

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

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

659 api.WriteHostRequestReference( 

660 references_pb2.WriteHostRequestReferenceReq( 

661 host_request_id=hr2, 

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

663 was_appropriate=True, 

664 rating=0.9, 

665 ) 

666 ) 

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

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

669 

670 # can write for this one too 

671 api.WriteHostRequestReference( 

672 references_pb2.WriteHostRequestReferenceReq( 

673 host_request_id=hr3, 

674 text="Should work!", 

675 was_appropriate=True, 

676 rating=0.9, 

677 ) 

678 ) 

679 

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

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

682 api.WriteHostRequestReference( 

683 references_pb2.WriteHostRequestReferenceReq( 

684 host_request_id=hr5, 

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

686 was_appropriate=True, 

687 rating=0.9, 

688 ) 

689 ) 

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

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

692 

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

694 api.HostRequestIndicateDidntMeetup( 

695 references_pb2.HostRequestIndicateDidntMeetupReq( 

696 host_request_id=hr6, 

697 reason_didnt_meetup="No clue?", 

698 ) 

699 ) 

700 

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

702 api.WriteHostRequestReference( 

703 references_pb2.WriteHostRequestReferenceReq( 

704 host_request_id=hr6, 

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

706 was_appropriate=True, 

707 rating=0.9, 

708 ) 

709 ) 

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

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

712 

713 with references_session(token4) as api: 

714 # they can still write one 

715 api.WriteHostRequestReference( 

716 references_pb2.WriteHostRequestReferenceReq( 

717 host_request_id=hr6, 

718 text="Should work!", 

719 was_appropriate=True, 

720 rating=0.9, 

721 ) 

722 ) 

723 

724 

725def test_WriteHostRequestReference_private_text(db, push_collector): 

726 user1, token1 = generate_user() 

727 user2, token2 = generate_user() 

728 

729 with session_scope() as session: 

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

731 

732 with references_session(token1) as api: 

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

734 with mock_notification_email() as mock2: 

735 api.WriteHostRequestReference( 

736 references_pb2.WriteHostRequestReferenceReq( 

737 host_request_id=hr, 

738 text="Should work!", 

739 was_appropriate=True, 

740 rating=0.9, 

741 private_text="Something", 

742 ) 

743 ) 

744 

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

746 assert mock1.call_count == 1 

747 assert mock2.call_count == 1 

748 

749 e = email_fields(mock2) 

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

751 assert e.recipient == user2.email 

752 

753 push_collector.assert_user_has_single_matching( 

754 user2.id, 

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

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

757 ) 

758 

759 

760def test_AvailableWriteReferences_and_ListPendingReferencesToWrite(db): 

761 user1, token1 = generate_user() 

762 user2, token2 = generate_user() 

763 user3, token3 = generate_user() 

764 user4, token4 = generate_user() 

765 user5, token5 = generate_user(delete_user=True) 

766 user6, token6 = generate_user() 

767 user7, token7 = generate_user() 

768 user8, token8 = generate_user() 

769 user9, token9 = generate_user() 

770 user10, token10 = generate_user() 

771 user11, token11 = generate_user() 

772 make_user_block(user1, user6) 

773 make_user_block(user7, user1) 

774 

775 with session_scope() as session: 

776 # too old 

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

778 

779 # already wrote friend ref to user3 

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

781 

782 # already given 

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

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

785 

786 # valid hosted 

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

788 

789 # valid surfed 

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

791 

792 # not yet complete 

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

794 

795 # already wrote friend ref to user2 

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

797 

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

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

800 

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

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

803 

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

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

806 

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

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

809 

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

811 create_host_request( 

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

813 ) 

814 

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

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

817 

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

819 hr7 = create_host_request( 

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

821 ) 

822 

823 with references_session(token1) as api: 

824 # can't write reference for invisible user 

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

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

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

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

829 

830 # can't write reference for blocking user 

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

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

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

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

835 

836 # can't write reference for blocked user 

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

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

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

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

841 

842 # can't write anything to myself 

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

844 assert not res.can_write_friend_reference 

845 assert len(res.available_write_references) == 0 

846 

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

848 # can't write friend ref to user2 

849 assert not res.can_write_friend_reference 

850 # none we can write for user2 

851 assert len(res.available_write_references) == 0 

852 

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

854 # can't write friend ref to user3 

855 assert not res.can_write_friend_reference 

856 # can write one reference because we hosted user3 

857 assert len(res.available_write_references) == 1 

858 w = res.available_write_references[0] 

859 assert w.host_request_id == hr3 

860 assert w.reference_type == references_pb2.REFERENCE_TYPE_HOSTED 

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

862 

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

864 # can write friend ref to user4 

865 assert res.can_write_friend_reference 

866 # can write one reference because we surfed with user4 

867 assert len(res.available_write_references) == 1 

868 w = res.available_write_references[0] 

869 assert w.host_request_id == hr4 

870 assert w.reference_type == references_pb2.REFERENCE_TYPE_SURFED 

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

872 

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

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

875 assert len(res.available_write_references) == 0 

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

877 assert len(res.available_write_references) == 0 

878 

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

880 # surfed with them 

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

882 assert len(res.available_write_references) == 1 

883 w = res.available_write_references[0] 

884 assert w.host_request_id == hr6 

885 assert w.reference_type == references_pb2.REFERENCE_TYPE_SURFED 

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

887 # hosted them 

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

889 assert len(res.available_write_references) == 1 

890 w = res.available_write_references[0] 

891 assert w.host_request_id == hr7 

892 assert w.reference_type == references_pb2.REFERENCE_TYPE_HOSTED 

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

894 

895 # finally check the general list 

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

897 assert len(res.pending_references) == 4 

898 w = res.pending_references[0] 

899 assert w.host_request_id == hr3 

900 assert w.reference_type == references_pb2.REFERENCE_TYPE_HOSTED 

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

902 w = res.pending_references[1] 

903 assert w.host_request_id == hr4 

904 assert w.reference_type == references_pb2.REFERENCE_TYPE_SURFED 

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

906 w = res.pending_references[2] 

907 assert w.host_request_id == hr6 

908 assert w.reference_type == references_pb2.REFERENCE_TYPE_SURFED 

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

910 w = res.pending_references[3] 

911 assert w.host_request_id == hr7 

912 assert w.reference_type == references_pb2.REFERENCE_TYPE_HOSTED 

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

914 

915 

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

917def test_regression_disappearing_refs(db, hs): 

918 """ 

919 Roughly the reproduction steps are: 

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

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

922 * On the surfer account, leave a reference 

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

924 """ 

925 user1, token1 = generate_user() 

926 user2, token2 = generate_user() 

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

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

929 with requests_session(token1) as api: 

930 res = api.CreateHostRequest( 

931 requests_pb2.CreateHostRequestReq( 

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

933 ) 

934 ) 

935 host_request_id = res.host_request_id 

936 assert ( 

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

938 .host_requests[0] 

939 .latest_message.text.text 

940 == "Test request" 

941 ) 

942 

943 with requests_session(token2) as api: 

944 api.RespondHostRequest( 

945 requests_pb2.RespondHostRequestReq( 

946 host_request_id=host_request_id, status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED 

947 ) 

948 ) 

949 

950 with requests_session(token1) as api: 

951 api.RespondHostRequest( 

952 requests_pb2.RespondHostRequestReq( 

953 host_request_id=host_request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CONFIRMED 

954 ) 

955 ) 

956 

957 with references_session(token1) as api: 

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

959 assert len(res.pending_references) == 0 

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

961 assert len(res.available_write_references) == 0 

962 

963 with references_session(token2) as api: 

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

965 assert len(res.pending_references) == 0 

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

967 assert len(res.available_write_references) == 0 

968 

969 # hack the time backwards 

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

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

972 with session_scope() as session: 

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

974 assert host_request.conversation_id == host_request_id 

975 host_request.from_date = hack_req_start 

976 host_request.to_date = hack_req_end 

977 

978 with references_session(token1) as api: 

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

980 assert len(res.pending_references) == 1 

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

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

983 

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

985 assert len(res.available_write_references) == 1 

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

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

988 

989 with references_session(token2) as api: 

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

991 assert len(res.pending_references) == 1 

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

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

994 

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

996 assert len(res.available_write_references) == 1 

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

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

999 

1000 if hs == "host": 

1001 with references_session(token2) as api: 

1002 api.WriteHostRequestReference( 

1003 references_pb2.WriteHostRequestReferenceReq( 

1004 host_request_id=host_request_id, 

1005 text="Good stuff", 

1006 was_appropriate=True, 

1007 rating=0.86, 

1008 ) 

1009 ) 

1010 

1011 with references_session(token2) as api: 

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

1013 assert len(res.pending_references) == 0 

1014 

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

1016 assert len(res.available_write_references) == 0 

1017 

1018 with references_session(token1) as api: 

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

1020 assert len(res.pending_references) == 1 

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

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

1023 

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

1025 assert len(res.available_write_references) == 1 

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

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

1028 else: 

1029 with references_session(token1) as api: 

1030 api.WriteHostRequestReference( 

1031 references_pb2.WriteHostRequestReferenceReq( 

1032 host_request_id=host_request_id, 

1033 text="Good stuff", 

1034 was_appropriate=True, 

1035 rating=0.86, 

1036 ) 

1037 ) 

1038 

1039 with references_session(token1) as api: 

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

1041 assert len(res.pending_references) == 0 

1042 

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

1044 assert len(res.available_write_references) == 0 

1045 

1046 with references_session(token2) as api: 

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

1048 assert len(res.pending_references) == 1 

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

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

1051 

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

1053 assert len(res.available_write_references) == 1 

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

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