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

463 statements  

« prev     ^ index     » next       coverage.py v7.6.10, created at 2025-01-22 06:42 +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_reference(session, from_user_id, to_user_id, reference_age, *, surfing=True, host_request_id=None): 

93 if host_request_id: 

94 actual_host_request_id = host_request_id 

95 else: 

96 if surfing: 

97 actual_host_request_id = host_request_id or create_host_request( 

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

99 ) 

100 else: 

101 actual_host_request_id = host_request_id or create_host_request( 

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

103 ) 

104 

105 host_request = session.execute( 

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

107 ).scalar_one() 

108 

109 reference = Reference( 

110 time=now() - reference_age, 

111 from_user_id=from_user_id, 

112 host_request_id=host_request.conversation_id, 

113 text="Dummy reference", 

114 rating=0.5, 

115 was_appropriate=True, 

116 ) 

117 

118 if host_request.surfer_user_id == from_user_id: 

119 reference.reference_type = ReferenceType.surfed 

120 reference.to_user_id = host_request.host_user_id 

121 assert from_user_id == host_request.surfer_user_id 

122 else: 

123 reference.reference_type = ReferenceType.hosted 

124 reference.to_user_id = host_request.surfer_user_id 

125 assert from_user_id == host_request.host_user_id 

126 

127 session.add(reference) 

128 session.commit() 

129 return reference.id, actual_host_request_id 

130 

131 

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

133 reference = Reference( 

134 time=now() - reference_age, 

135 from_user_id=from_user_id, 

136 to_user_id=to_user_id, 

137 reference_type=ReferenceType.friend, 

138 text="Test friend request", 

139 rating=0.4, 

140 was_appropriate=True, 

141 ) 

142 session.add(reference) 

143 session.commit() 

144 return reference.id 

145 

146 

147def test_ListPagination(db): 

148 user1, token1 = generate_user() 

149 user2, token2 = generate_user() 

150 user3, token3 = generate_user() 

151 user4, token4 = generate_user() 

152 user5, token5 = generate_user() 

153 user6, token6 = generate_user() 

154 user7, token7 = generate_user() 

155 user8, token8 = generate_user() 

156 user9, token9 = generate_user() 

157 

158 with session_scope() as session: 

159 # bidirectional references 

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

161 ref2b, _ = create_host_reference( 

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

163 ) 

164 

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

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

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

168 

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

170 ref5b, _ = create_host_reference( 

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

172 ) 

173 

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

175 

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

177 

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

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

180 

181 # should be visible even under 2 weeks 

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

183 

184 # hidden because it's less than 2 weeks 

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

186 

187 # visible because both were written 

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

189 ref8c, _ = create_host_reference( 

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

191 ) 

192 

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

194 

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

196 

197 with references_session(token2) as api: 

198 # written by user1 

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

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

201 

202 res = api.ListReferences( 

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

204 ) 

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

206 

207 res = api.ListReferences( 

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

209 ) 

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

211 assert not res.next_page_token 

212 

213 # received by user1 

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

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

216 

217 res = api.ListReferences( 

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

219 ) 

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

221 assert not res.next_page_token 

222 

223 # same thing but with filters 

224 res = api.ListReferences( 

225 references_pb2.ListReferencesReq( 

226 to_user_id=user1.id, 

227 reference_type_filter=[ 

228 references_pb2.REFERENCE_TYPE_HOSTED, 

229 references_pb2.REFERENCE_TYPE_SURFED, 

230 references_pb2.REFERENCE_TYPE_FRIEND, 

231 ], 

232 page_size=5, 

233 ) 

234 ) 

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

236 

237 res = api.ListReferences( 

238 references_pb2.ListReferencesReq( 

239 to_user_id=user1.id, 

240 reference_type_filter=[ 

241 references_pb2.REFERENCE_TYPE_HOSTED, 

242 references_pb2.REFERENCE_TYPE_SURFED, 

243 references_pb2.REFERENCE_TYPE_FRIEND, 

244 ], 

245 page_token=res.next_page_token, 

246 page_size=5, 

247 ) 

248 ) 

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

250 assert not res.next_page_token 

251 

252 # received hosting references 

253 res = api.ListReferences( 

254 references_pb2.ListReferencesReq( 

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

256 ) 

257 ) 

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

259 

260 res = api.ListReferences( 

261 references_pb2.ListReferencesReq( 

262 to_user_id=user1.id, 

263 reference_type_filter=[references_pb2.REFERENCE_TYPE_HOSTED], 

264 page_token=res.next_page_token, 

265 page_size=3, 

266 ) 

267 ) 

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

269 assert not res.next_page_token 

270 

271 # written friend references 

272 res = api.ListReferences( 

273 references_pb2.ListReferencesReq( 

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

275 ) 

276 ) 

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

278 assert not res.next_page_token 

279 

280 # written surfing references 

281 res = api.ListReferences( 

282 references_pb2.ListReferencesReq( 

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

284 ) 

285 ) 

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

287 assert not res.next_page_token 

288 

289 with references_session(token7) as api: 

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

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

292 api.ListReferences( 

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

294 ) 

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

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

297 

298 with references_session(token5) as api: 

299 # from user1 to user2 

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

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

302 assert not res.next_page_token 

303 

304 # from user5 to user1 

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

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

307 assert not res.next_page_token 

308 

309 

310def test_ListReference_banned_deleted_users(db): 

311 user1, token1 = generate_user() 

312 user2, token2 = generate_user() 

313 user3, token3 = generate_user() 

314 

315 with session_scope() as session: 

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

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

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

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

320 

321 with references_session(token1) as api: 

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

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

324 assert len(refs_rec) == 2 

325 assert len(refs_sent) == 2 

326 

327 # ban user2 

328 with session_scope() as session: 

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

330 user2.is_banned = True 

331 session.commit() 

332 

333 # reference to and from banned user is hidden 

334 with references_session(token1) as api: 

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

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

337 assert len(refs_rec) == 1 

338 assert len(refs_sent) == 1 

339 

340 # delete user3 

341 with session_scope() as session: 

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

343 user3.is_deleted = True 

344 session.commit() 

345 

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

347 with references_session(token1) as api: 

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

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

350 assert len(refs_rec) == 1 

351 assert len(refs_sent) == 1 

352 

353 

354def test_WriteFriendReference(db): 

355 user1, token1 = generate_user() 

356 user2, token2 = generate_user() 

357 user3, token3 = generate_user() 

358 

359 with references_session(token1) as api: 

360 # can write normal friend reference 

361 res = api.WriteFriendReference( 

362 references_pb2.WriteFriendReferenceReq( 

363 to_user_id=user2.id, 

364 text="A test reference", 

365 was_appropriate=True, 

366 rating=0.5, 

367 ) 

368 ) 

369 assert res.from_user_id == user1.id 

370 assert res.to_user_id == user2.id 

371 assert res.reference_type == references_pb2.REFERENCE_TYPE_FRIEND 

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

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

374 assert not res.host_request_id 

375 

376 with references_session(token3) as api: 

377 # check it shows up 

378 res = api.ListReferences( 

379 references_pb2.ListReferencesReq( 

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

381 ) 

382 ) 

383 assert len(res.references) == 1 

384 ref = res.references[0] 

385 assert ref.from_user_id == user1.id 

386 assert ref.to_user_id == user2.id 

387 assert ref.reference_type == references_pb2.REFERENCE_TYPE_FRIEND 

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

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

390 assert not ref.host_request_id 

391 

392 with references_session(token1) as api: 

393 # can't write a second friend reference 

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

395 api.WriteFriendReference( 

396 references_pb2.WriteFriendReferenceReq( 

397 to_user_id=user2.id, 

398 text="A test reference", 

399 was_appropriate=True, 

400 rating=0.5, 

401 ) 

402 ) 

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

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

405 

406 with references_session(token2) as api: 

407 # can't write a reference about yourself 

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

409 api.WriteFriendReference( 

410 references_pb2.WriteFriendReferenceReq( 

411 to_user_id=user2.id, 

412 text="I'm really awesome", 

413 was_appropriate=True, 

414 rating=1.0, 

415 ) 

416 ) 

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

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

419 

420 

421def test_WriteFriendReference_with_empty_text(db): 

422 user1, token1 = generate_user() 

423 user2, token2 = generate_user() 

424 

425 with references_session(token1) as api: 

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

427 api.WriteFriendReference( 

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

429 ) 

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

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

432 

433 

434def test_WriteFriendReference_with_private_text(db, push_collector): 

435 user1, token1 = generate_user() 

436 user2, token2 = generate_user() 

437 

438 with references_session(token1) as api: 

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

440 with mock_notification_email() as mock2: 

441 api.WriteFriendReference( 

442 references_pb2.WriteFriendReferenceReq( 

443 to_user_id=user2.id, 

444 text="They were nice!", 

445 was_appropriate=True, 

446 rating=0.6, 

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

448 ) 

449 ) 

450 

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

452 assert mock1.call_count == 1 

453 assert mock2.call_count == 1 

454 e = email_fields(mock2) 

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

456 assert e.recipient == user2.email 

457 

458 push_collector.assert_user_has_single_matching( 

459 user2.id, 

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

461 body="They were nice!", 

462 ) 

463 

464 

465def test_host_request_states_references(db): 

466 user1, token1 = generate_user() 

467 user2, token2 = generate_user() 

468 

469 with session_scope() as session: 

470 # can't write ref 

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

472 # can write ref 

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

474 # can't write ref 

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

476 # can write ref 

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

478 # can't write ref 

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

480 

481 with references_session(token1) as api: 

482 # pending 

483 api.WriteHostRequestReference( 

484 references_pb2.WriteHostRequestReferenceReq( 

485 host_request_id=hr2, 

486 text="Should work!", 

487 was_appropriate=True, 

488 rating=0.9, 

489 ) 

490 ) 

491 

492 # accepted 

493 api.WriteHostRequestReference( 

494 references_pb2.WriteHostRequestReferenceReq( 

495 host_request_id=hr4, 

496 text="Should work!", 

497 was_appropriate=True, 

498 rating=0.9, 

499 ) 

500 ) 

501 

502 # rejected 

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

504 api.WriteHostRequestReference( 

505 references_pb2.WriteHostRequestReferenceReq( 

506 host_request_id=hr1, 

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

508 was_appropriate=True, 

509 rating=0.9, 

510 ) 

511 ) 

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

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

514 

515 # confirmed 

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

517 api.WriteHostRequestReference( 

518 references_pb2.WriteHostRequestReferenceReq( 

519 host_request_id=hr3, 

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

521 was_appropriate=True, 

522 rating=0.9, 

523 ) 

524 ) 

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

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

527 

528 # cancelled 

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

530 api.WriteHostRequestReference( 

531 references_pb2.WriteHostRequestReferenceReq( 

532 host_request_id=hr5, 

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

534 was_appropriate=True, 

535 rating=0.9, 

536 ) 

537 ) 

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

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

540 

541 

542def test_WriteHostRequestReference(db): 

543 user1, token1 = generate_user() 

544 user2, token2 = generate_user() 

545 user3, token3 = generate_user() 

546 user4, token4 = generate_user() 

547 

548 with session_scope() as session: 

549 # too old 

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

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

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

553 # valid surfing req 

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

555 # not yet complete 

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

557 # we indicated we didn't meet 

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

559 # we will indicate we didn't meet 

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

561 

562 with references_session(token3) as api: 

563 # can write for this one 

564 api.WriteHostRequestReference( 

565 references_pb2.WriteHostRequestReferenceReq( 

566 host_request_id=hr3, 

567 text="Should work!", 

568 was_appropriate=True, 

569 rating=0.9, 

570 ) 

571 ) 

572 

573 with references_session(token1) as api: 

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

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

576 api.WriteHostRequestReference( 

577 references_pb2.WriteHostRequestReferenceReq( 

578 host_request_id=hr4, 

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

580 was_appropriate=True, 

581 rating=0.9, 

582 ) 

583 ) 

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

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

586 

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

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

589 api.WriteHostRequestReference( 

590 references_pb2.WriteHostRequestReferenceReq( 

591 host_request_id=hr1, 

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

593 was_appropriate=True, 

594 rating=0.9, 

595 ) 

596 ) 

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

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

599 

600 # can write for this one 

601 api.WriteHostRequestReference( 

602 references_pb2.WriteHostRequestReferenceReq( 

603 host_request_id=hr2, 

604 text="Should work!", 

605 was_appropriate=True, 

606 rating=0.9, 

607 ) 

608 ) 

609 

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

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

612 api.WriteHostRequestReference( 

613 references_pb2.WriteHostRequestReferenceReq( 

614 host_request_id=hr2, 

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

616 was_appropriate=True, 

617 rating=0.9, 

618 ) 

619 ) 

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

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

622 

623 # can write for this one too 

624 api.WriteHostRequestReference( 

625 references_pb2.WriteHostRequestReferenceReq( 

626 host_request_id=hr3, 

627 text="Should work!", 

628 was_appropriate=True, 

629 rating=0.9, 

630 ) 

631 ) 

632 

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

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

635 api.WriteHostRequestReference( 

636 references_pb2.WriteHostRequestReferenceReq( 

637 host_request_id=hr5, 

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

639 was_appropriate=True, 

640 rating=0.9, 

641 ) 

642 ) 

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

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

645 

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

647 api.HostRequestIndicateDidntMeetup( 

648 references_pb2.HostRequestIndicateDidntMeetupReq( 

649 host_request_id=hr6, 

650 reason_didnt_meetup="No clue?", 

651 ) 

652 ) 

653 

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

655 api.WriteHostRequestReference( 

656 references_pb2.WriteHostRequestReferenceReq( 

657 host_request_id=hr6, 

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

659 was_appropriate=True, 

660 rating=0.9, 

661 ) 

662 ) 

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

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

665 

666 with references_session(token4) as api: 

667 # they can still write one 

668 api.WriteHostRequestReference( 

669 references_pb2.WriteHostRequestReferenceReq( 

670 host_request_id=hr6, 

671 text="Should work!", 

672 was_appropriate=True, 

673 rating=0.9, 

674 ) 

675 ) 

676 

677 

678def test_WriteHostRequestReference_private_text(db, push_collector): 

679 user1, token1 = generate_user() 

680 user2, token2 = generate_user() 

681 

682 with session_scope() as session: 

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

684 

685 with references_session(token1) as api: 

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

687 with mock_notification_email() as mock2: 

688 api.WriteHostRequestReference( 

689 references_pb2.WriteHostRequestReferenceReq( 

690 host_request_id=hr, 

691 text="Should work!", 

692 was_appropriate=True, 

693 rating=0.9, 

694 private_text="Something", 

695 ) 

696 ) 

697 

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

699 assert mock1.call_count == 1 

700 assert mock2.call_count == 1 

701 

702 e = email_fields(mock2) 

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

704 assert e.recipient == user2.email 

705 

706 push_collector.assert_user_has_single_matching( 

707 user2.id, 

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

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

710 ) 

711 

712 

713def test_AvailableWriteReferences_and_ListPendingReferencesToWrite(db): 

714 user1, token1 = generate_user() 

715 user2, token2 = generate_user() 

716 user3, token3 = generate_user() 

717 user4, token4 = generate_user() 

718 user5, token5 = generate_user(delete_user=True) 

719 user6, token6 = generate_user() 

720 user7, token7 = generate_user() 

721 user8, token8 = generate_user() 

722 user9, token9 = generate_user() 

723 user10, token10 = generate_user() 

724 user11, token11 = generate_user() 

725 make_user_block(user1, user6) 

726 make_user_block(user7, user1) 

727 

728 with session_scope() as session: 

729 # too old 

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

731 

732 # already wrote friend ref to user3 

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

734 

735 # already given 

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

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

738 

739 # valid hosted 

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

741 

742 # valid surfed 

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

744 

745 # not yet complete 

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

747 

748 # already wrote friend ref to user2 

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

750 

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

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

753 

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

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

756 

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

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

759 

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

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

762 

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

764 create_host_request( 

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

766 ) 

767 

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

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

770 

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

772 hr7 = create_host_request( 

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

774 ) 

775 

776 with references_session(token1) as api: 

777 # can't write reference for invisible user 

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

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

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

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

782 

783 # can't write reference for blocking user 

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

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

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

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

788 

789 # can't write reference for blocked user 

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

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

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

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

794 

795 # can't write anything to myself 

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

797 assert not res.can_write_friend_reference 

798 assert len(res.available_write_references) == 0 

799 

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

801 # can't write friend ref to user2 

802 assert not res.can_write_friend_reference 

803 # none we can write for user2 

804 assert len(res.available_write_references) == 0 

805 

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

807 # can't write friend ref to user3 

808 assert not res.can_write_friend_reference 

809 # can write one reference because we hosted user3 

810 assert len(res.available_write_references) == 1 

811 w = res.available_write_references[0] 

812 assert w.host_request_id == hr3 

813 assert w.reference_type == references_pb2.REFERENCE_TYPE_HOSTED 

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

815 

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

817 # can write friend ref to user4 

818 assert res.can_write_friend_reference 

819 # can write one reference because we surfed with user4 

820 assert len(res.available_write_references) == 1 

821 w = res.available_write_references[0] 

822 assert w.host_request_id == hr4 

823 assert w.reference_type == references_pb2.REFERENCE_TYPE_SURFED 

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

825 

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

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

828 assert len(res.available_write_references) == 0 

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

830 assert len(res.available_write_references) == 0 

831 

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

833 # surfed with them 

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

835 assert len(res.available_write_references) == 1 

836 w = res.available_write_references[0] 

837 assert w.host_request_id == hr6 

838 assert w.reference_type == references_pb2.REFERENCE_TYPE_SURFED 

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

840 # hosted them 

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

842 assert len(res.available_write_references) == 1 

843 w = res.available_write_references[0] 

844 assert w.host_request_id == hr7 

845 assert w.reference_type == references_pb2.REFERENCE_TYPE_HOSTED 

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

847 

848 # finally check the general list 

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

850 assert len(res.pending_references) == 4 

851 w = res.pending_references[0] 

852 assert w.host_request_id == hr3 

853 assert w.reference_type == references_pb2.REFERENCE_TYPE_HOSTED 

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

855 w = res.pending_references[1] 

856 assert w.host_request_id == hr4 

857 assert w.reference_type == references_pb2.REFERENCE_TYPE_SURFED 

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

859 w = res.pending_references[2] 

860 assert w.host_request_id == hr6 

861 assert w.reference_type == references_pb2.REFERENCE_TYPE_SURFED 

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

863 w = res.pending_references[3] 

864 assert w.host_request_id == hr7 

865 assert w.reference_type == references_pb2.REFERENCE_TYPE_HOSTED 

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

867 

868 

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

870def test_regression_disappearing_refs(db, hs): 

871 """ 

872 Roughly the reproduction steps are: 

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

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

875 * On the surfer account, leave a reference 

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

877 """ 

878 user1, token1 = generate_user() 

879 user2, token2 = generate_user() 

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

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

882 with requests_session(token1) as api: 

883 res = api.CreateHostRequest( 

884 requests_pb2.CreateHostRequestReq( 

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

886 ) 

887 ) 

888 host_request_id = res.host_request_id 

889 assert ( 

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

891 .host_requests[0] 

892 .latest_message.text.text 

893 == "Test request" 

894 ) 

895 

896 with requests_session(token2) as api: 

897 api.RespondHostRequest( 

898 requests_pb2.RespondHostRequestReq( 

899 host_request_id=host_request_id, status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED 

900 ) 

901 ) 

902 

903 with requests_session(token1) as api: 

904 api.RespondHostRequest( 

905 requests_pb2.RespondHostRequestReq( 

906 host_request_id=host_request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CONFIRMED 

907 ) 

908 ) 

909 

910 with references_session(token1) as api: 

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

912 assert len(res.pending_references) == 0 

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

914 assert len(res.available_write_references) == 0 

915 

916 with references_session(token2) as api: 

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

918 assert len(res.pending_references) == 0 

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

920 assert len(res.available_write_references) == 0 

921 

922 # hack the time backwards 

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

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

925 with session_scope() as session: 

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

927 assert host_request.conversation_id == host_request_id 

928 host_request.from_date = hack_req_start 

929 host_request.to_date = hack_req_end 

930 

931 with references_session(token1) as api: 

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

933 assert len(res.pending_references) == 1 

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

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

936 

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

938 assert len(res.available_write_references) == 1 

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

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

941 

942 with references_session(token2) as api: 

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

944 assert len(res.pending_references) == 1 

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

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

947 

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

949 assert len(res.available_write_references) == 1 

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

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

952 

953 if hs == "host": 

954 with references_session(token2) as api: 

955 api.WriteHostRequestReference( 

956 references_pb2.WriteHostRequestReferenceReq( 

957 host_request_id=host_request_id, 

958 text="Good stuff", 

959 was_appropriate=True, 

960 rating=0.86, 

961 ) 

962 ) 

963 

964 with references_session(token2) as api: 

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

966 assert len(res.pending_references) == 0 

967 

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

969 assert len(res.available_write_references) == 0 

970 

971 with references_session(token1) as api: 

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

973 assert len(res.pending_references) == 1 

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

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

976 

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

978 assert len(res.available_write_references) == 1 

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

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

981 else: 

982 with references_session(token1) as api: 

983 api.WriteHostRequestReference( 

984 references_pb2.WriteHostRequestReferenceReq( 

985 host_request_id=host_request_id, 

986 text="Good stuff", 

987 was_appropriate=True, 

988 rating=0.86, 

989 ) 

990 ) 

991 

992 with references_session(token1) as api: 

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

994 assert len(res.pending_references) == 0 

995 

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

997 assert len(res.available_write_references) == 0 

998 

999 with references_session(token2) as api: 

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

1001 assert len(res.pending_references) == 1 

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

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

1004 

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

1006 assert len(res.available_write_references) == 1 

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

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