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

677 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-07-23 04:10 +0000

1from datetime import date, datetime, timedelta 

2 

3import grpc 

4import pytest 

5from google.protobuf import empty_pb2 

6from sqlalchemy import func, select, update 

7from sqlalchemy.orm import Session 

8 

9from couchers.db import session_scope 

10from couchers.materialized_views import refresh_materialized_views_rapid 

11from couchers.models import ( 

12 Conversation, 

13 FriendRelationship, 

14 FriendStatus, 

15 HostRequest, 

16 HostRequestStatus, 

17 Message, 

18 MessageType, 

19 ModerationObjectType, 

20 ModerationState, 

21 ModerationVisibility, 

22 Reference, 

23 ReferenceType, 

24 User, 

25) 

26from couchers.moderation.utils import create_moderation 

27from couchers.proto import api_pb2, conversations_pb2, moderation_pb2, references_pb2, requests_pb2 

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

29from tests.fixtures.db import generate_user, make_friends, make_user_block 

30from tests.fixtures.misc import EmailCollector, PushCollector 

31from tests.fixtures.sessions import ( 

32 account_session, 

33 api_session, 

34 real_moderation_session, 

35 references_session, 

36 requests_session, 

37) 

38from tests.test_requests import valid_request_text 

39 

40 

41@pytest.fixture(autouse=True) 

42def _(testconfig): 

43 pass 

44 

45 

46def create_host_request( 

47 session: Session, 

48 surfer_user_id: int, 

49 host_user_id: int, 

50 host_request_age: timedelta = timedelta(days=15), 

51 status: HostRequestStatus = HostRequestStatus.confirmed, 

52 host_reason_didnt_meetup: str | None = None, 

53 surfer_reason_didnt_meetup: str | None = None, 

54) -> int: 

55 """ 

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

57 """ 

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

59 to_date = today() - host_request_age 

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

61 conversation = Conversation() 

62 session.add(conversation) 

63 session.flush() 

64 

65 msg1 = Message( 

66 conversation_id=conversation.id, 

67 author_id=surfer_user_id, 

68 message_type=MessageType.chat_created, 

69 ) 

70 msg1.time = fake_created + timedelta(seconds=1) 

71 session.add(msg1) 

72 

73 msg2 = Message( 

74 conversation_id=conversation.id, 

75 author_id=surfer_user_id, 

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

77 message_type=MessageType.text, 

78 ) 

79 msg2.time = fake_created + timedelta(seconds=2) 

80 session.add(msg2) 

81 session.flush() 

82 

83 moderation_state = create_moderation( 

84 session, 

85 ModerationObjectType.host_request, 

86 conversation.id, 

87 surfer_user_id, 

88 ) 

89 

90 host_request = HostRequest( 

91 conversation_id=conversation.id, 

92 initiator_user_id=surfer_user_id, 

93 recipient_user_id=host_user_id, 

94 from_date=from_date, 

95 to_date=to_date, 

96 status=status, 

97 initiator_last_seen_message_id=msg2.id, 

98 recipient_reason_didnt_meetup=host_reason_didnt_meetup, 

99 initiator_reason_didnt_meetup=surfer_reason_didnt_meetup, 

100 hosting_city="Test City", 

101 hosting_location=create_coordinate(0, 0), 

102 hosting_radius=10, 

103 moderation_state_id=moderation_state.id, 

104 ) 

105 session.add(host_request) 

106 session.commit() 

107 return host_request.conversation_id 

108 

109 

110def create_host_request_by_date( 

111 session: Session, 

112 surfer_user_id: int, 

113 host_user_id: int, 

114 from_date: date, 

115 to_date: date, 

116 status: HostRequestStatus, 

117 host_sent_request_reminders: int, 

118 last_sent_request_reminder_time: datetime, 

119) -> int: 

120 conversation = Conversation() 

121 session.add(conversation) 

122 session.flush() 

123 

124 msg1 = Message( 

125 conversation_id=conversation.id, 

126 author_id=surfer_user_id, 

127 message_type=MessageType.chat_created, 

128 ) 

129 msg1.time = from_date + timedelta(seconds=1) # type: ignore[assignment] 

130 session.add(msg1) 

131 

132 # Unused for now, but every host request must have a message. 

133 msg2 = Message( 

134 conversation_id=conversation.id, 

135 author_id=surfer_user_id, 

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

137 message_type=MessageType.text, 

138 ) 

139 msg2.time = from_date + timedelta(seconds=2) # type: ignore[assignment] 

140 session.add(msg2) 

141 session.flush() 

142 

143 moderation_state = create_moderation( 

144 session, 

145 ModerationObjectType.host_request, 

146 conversation.id, 

147 surfer_user_id, 

148 ) 

149 

150 host_request = HostRequest( 

151 conversation_id=conversation.id, 

152 initiator_user_id=surfer_user_id, 

153 recipient_user_id=host_user_id, 

154 from_date=from_date, 

155 to_date=to_date, 

156 status=status, 

157 hosting_city="Test City", 

158 hosting_location=create_coordinate(0, 0), 

159 hosting_radius=10, 

160 moderation_state_id=moderation_state.id, 

161 ) 

162 host_request.recipient_sent_request_reminders = host_sent_request_reminders 

163 host_request.last_sent_request_reminder_time = last_sent_request_reminder_time 

164 

165 session.add(host_request) 

166 session.commit() 

167 return host_request.conversation_id 

168 

169 

170def create_host_reference( 

171 session: Session, 

172 from_user_id: int, 

173 to_user_id: int, 

174 reference_age: timedelta, 

175 *, 

176 surfing: bool = True, 

177 host_request_id: int | None = None, 

178) -> tuple[int, int]: 

179 if host_request_id: 

180 actual_host_request_id = host_request_id 

181 else: 

182 if surfing: 

183 actual_host_request_id = host_request_id or create_host_request( 

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

185 ) 

186 else: 

187 actual_host_request_id = host_request_id or create_host_request( 

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

189 ) 

190 

191 host_request = session.execute( 

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

193 ).scalar_one() 

194 

195 if host_request.initiator_user_id == from_user_id: 

196 reference_type = ReferenceType.surfed 

197 to_user_id = host_request.recipient_user_id 

198 assert from_user_id == host_request.initiator_user_id 

199 else: 

200 reference_type = ReferenceType.hosted 

201 to_user_id = host_request.initiator_user_id 

202 assert from_user_id == host_request.recipient_user_id 

203 

204 moderation_state = ModerationState( 

205 object_type=ModerationObjectType.reference, 

206 object_id=0, # placeholder, set after Reference flush 

207 visibility=ModerationVisibility.visible, 

208 ) 

209 session.add(moderation_state) 

210 session.flush() 

211 

212 reference = Reference( 

213 from_user_id=from_user_id, 

214 to_user_id=to_user_id, 

215 host_request_id=host_request.conversation_id, 

216 text="Dummy reference", 

217 rating=0.5, 

218 was_appropriate=True, 

219 reference_type=reference_type, 

220 moderation_state_id=moderation_state.id, 

221 ) 

222 reference.time = now() - reference_age 

223 

224 session.add(reference) 

225 session.flush() 

226 moderation_state.object_id = reference.id 

227 session.commit() 

228 return reference.id, actual_host_request_id 

229 

230 

231def create_friend_reference(session: Session, from_user_id: int, to_user_id: int, reference_age: timedelta) -> int: 

232 moderation_state = ModerationState( 

233 object_type=ModerationObjectType.reference, 

234 object_id=0, # placeholder, set after Reference flush 

235 visibility=ModerationVisibility.visible, 

236 ) 

237 session.add(moderation_state) 

238 session.flush() 

239 

240 reference = Reference( 

241 from_user_id=from_user_id, 

242 to_user_id=to_user_id, 

243 reference_type=ReferenceType.friend, 

244 text="Test friend request", 

245 rating=0.4, 

246 was_appropriate=True, 

247 moderation_state_id=moderation_state.id, 

248 ) 

249 reference.time = now() - reference_age 

250 session.add(reference) 

251 session.flush() 

252 moderation_state.object_id = reference.id 

253 session.commit() 

254 return reference.id 

255 

256 

257def test_ListPagination(db): 

258 user1, token1 = generate_user() 

259 user2, token2 = generate_user() 

260 user3, token3 = generate_user() 

261 user4, token4 = generate_user() 

262 user5, token5 = generate_user() 

263 user6, token6 = generate_user() 

264 user7, token7 = generate_user() 

265 user8, token8 = generate_user() 

266 user9, token9 = generate_user() 

267 

268 with session_scope() as session: 

269 # bidirectional references 

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

271 ref2b, _ = create_host_reference( 

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

273 ) 

274 

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

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

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

278 

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

280 ref5b, _ = create_host_reference( 

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

282 ) 

283 

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

285 

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

287 

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

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

290 

291 # should be visible even under 2 weeks 

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

293 

294 # hidden because it's less than 2 weeks 

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

296 

297 # visible because both were written 

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

299 ref8c, _ = create_host_reference( 

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

301 ) 

302 

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

304 

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

306 

307 with references_session(token2) as api: 

308 # written by user1 

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

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

311 

312 res = api.ListReferences( 

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

314 ) 

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

316 

317 res = api.ListReferences( 

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

319 ) 

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

321 assert not res.next_page_token 

322 

323 # received by user1 

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

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

326 

327 res = api.ListReferences( 

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

329 ) 

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

331 assert not res.next_page_token 

332 

333 # same thing but with filters 

334 res = api.ListReferences( 

335 references_pb2.ListReferencesReq( 

336 to_user_id=user1.id, 

337 reference_type_filter=[ 

338 references_pb2.REFERENCE_TYPE_HOSTED, 

339 references_pb2.REFERENCE_TYPE_SURFED, 

340 references_pb2.REFERENCE_TYPE_FRIEND, 

341 ], 

342 page_size=5, 

343 ) 

344 ) 

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

346 

347 res = api.ListReferences( 

348 references_pb2.ListReferencesReq( 

349 to_user_id=user1.id, 

350 reference_type_filter=[ 

351 references_pb2.REFERENCE_TYPE_HOSTED, 

352 references_pb2.REFERENCE_TYPE_SURFED, 

353 references_pb2.REFERENCE_TYPE_FRIEND, 

354 ], 

355 page_token=res.next_page_token, 

356 page_size=5, 

357 ) 

358 ) 

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

360 assert not res.next_page_token 

361 

362 # received hosting references 

363 res = api.ListReferences( 

364 references_pb2.ListReferencesReq( 

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

366 ) 

367 ) 

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

369 

370 res = api.ListReferences( 

371 references_pb2.ListReferencesReq( 

372 to_user_id=user1.id, 

373 reference_type_filter=[references_pb2.REFERENCE_TYPE_HOSTED], 

374 page_token=res.next_page_token, 

375 page_size=3, 

376 ) 

377 ) 

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

379 assert not res.next_page_token 

380 

381 # written friend references 

382 res = api.ListReferences( 

383 references_pb2.ListReferencesReq( 

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

385 ) 

386 ) 

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

388 assert not res.next_page_token 

389 

390 # written surfing references 

391 res = api.ListReferences( 

392 references_pb2.ListReferencesReq( 

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

394 ) 

395 ) 

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

397 assert not res.next_page_token 

398 

399 with references_session(token7) as api: 

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

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

402 api.ListReferences( 

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

404 ) 

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

406 assert e.value.details() == "You need to specify at least one user." 

407 

408 with references_session(token5) as api: 

409 # from user1 to user2 

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

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

412 assert not res.next_page_token 

413 

414 # from user5 to user1 

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

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

417 assert not res.next_page_token 

418 

419 with api_session(token2) as api: 

420 # the reference count matches the 9 references visible in the list; ref6hidden is excluded 

421 # because it's still hidden by the reciprocal-reference rule 

422 assert api.GetUser(api_pb2.GetUserReq(user=user1.username)).num_references == 9 

423 # user8 received ref8c, visible because both references of the pair were written 

424 assert api.GetUser(api_pb2.GetUserReq(user=user8.username)).num_references == 1 

425 # user6 wrote references but received none 

426 assert api.GetUser(api_pb2.GetUserReq(user=user6.username)).num_references == 0 

427 

428 

429def test_num_references_matches_visible_list(db): 

430 # Regression test: the reference count (User.num_references) must not include references that 

431 # are still hidden by the reciprocal-reference rule, otherwise the count leaks the existence 

432 # of a hidden reference and disagrees with what ListReferences shows. 

433 user1, token1 = generate_user() 

434 user2, token2 = generate_user() 

435 

436 with session_scope() as session: 

437 # user2 wrote user1 a reference for a recent stay; the 2-week window is still open and 

438 # user1 has not written their reciprocal reference yet, so this reference is hidden. 

439 create_host_reference(session, user2.id, user1.id, timedelta(days=3), surfing=False) 

440 

441 with references_session(token2) as api: 

442 res = api.ListReferences(references_pb2.ListReferencesReq(to_user_id=user1.id)) 

443 assert len(res.references) == 0 

444 

445 with api_session(token2) as api: 

446 res = api.GetUser(api_pb2.GetUserReq(user=user1.username)) 

447 # count must agree with the (empty) list, not leak the hidden reference 

448 assert res.num_references == 0 

449 

450 with session_scope() as session: 

451 host_request_id = session.execute(select(HostRequest.conversation_id)).scalar_one() 

452 # user1 writes the reciprocal reference: now both references become visible 

453 create_host_reference(session, user1.id, user2.id, timedelta(days=2), host_request_id=host_request_id) 

454 

455 with references_session(token2) as api: 

456 res = api.ListReferences(references_pb2.ListReferencesReq(to_user_id=user1.id)) 

457 assert len(res.references) == 1 

458 

459 with api_session(token2) as api: 

460 res = api.GetUser(api_pb2.GetUserReq(user=user1.username)) 

461 assert res.num_references == 1 

462 

463 

464def test_ListReference_banned_deleted_users(db): 

465 user1, token1 = generate_user() 

466 user2, token2 = generate_user() 

467 user3, token3 = generate_user() 

468 

469 with session_scope() as session: 

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

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

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

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

474 

475 with references_session(token1) as api: 

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

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

478 assert len(refs_rec) == 2 

479 assert len(refs_sent) == 2 

480 

481 with api_session(token1) as api: 

482 assert api.GetUser(api_pb2.GetUserReq(user=user1.username)).num_references == 2 

483 

484 # ban user2 

485 with session_scope() as session: 

486 session.execute(update(User).where(User.username == user2.username).values(banned_at=func.now())) 

487 

488 # reference to and from banned user is hidden 

489 with references_session(token1) as api: 

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

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

492 assert len(refs_rec) == 1 

493 assert len(refs_sent) == 1 

494 

495 with api_session(token1) as api: 

496 assert api.GetUser(api_pb2.GetUserReq(user=user1.username)).num_references == 1 

497 

498 # delete user3 

499 with session_scope() as session: 

500 session.execute(update(User).where(User.username == user3.username).values(deleted_at=func.now())) 

501 

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

503 with references_session(token1) as api: 

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

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

506 assert len(refs_rec) == 1 

507 assert len(refs_sent) == 1 

508 

509 # note: this assert currently fails due to a pre-existing divergence: get_num_references 

510 # excludes deleted authors' references (via User.is_visible) while ListReferences keeps 

511 # showing them, so the count here says 0 while the list shows 1 

512 # with api_session(token1) as api: 

513 # assert api.GetUser(api_pb2.GetUserReq(user=user1.username)).num_references == 1 

514 

515 

516def test_WriteFriendReference(db, moderator): 

517 user1, token1 = generate_user() 

518 user2, token2 = generate_user() 

519 user3, token3 = generate_user() 

520 

521 # Make user1 and user2 friends 

522 make_friends(user1, user2) 

523 

524 with references_session(token1) as api: 

525 # can write normal friend reference 

526 res = api.WriteFriendReference( 

527 references_pb2.WriteFriendReferenceReq( 

528 to_user_id=user2.id, 

529 text="A test reference", 

530 was_appropriate=True, 

531 rating=0.5, 

532 ) 

533 ) 

534 assert res.from_user_id == user1.id 

535 assert res.to_user_id == user2.id 

536 assert res.reference_type == references_pb2.REFERENCE_TYPE_FRIEND 

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

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

539 assert not res.host_request_id 

540 

541 moderator.approve_reference(res.reference_id) 

542 

543 with references_session(token3) as api: 

544 # check it shows up 

545 res = api.ListReferences( 

546 references_pb2.ListReferencesReq( 

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

548 ) 

549 ) 

550 assert len(res.references) == 1 

551 ref = res.references[0] 

552 assert ref.from_user_id == user1.id 

553 assert ref.to_user_id == user2.id 

554 assert ref.reference_type == references_pb2.REFERENCE_TYPE_FRIEND 

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

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

557 assert not ref.host_request_id 

558 

559 with references_session(token1) as api: 

560 # can't write a second friend reference 

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

562 api.WriteFriendReference( 

563 references_pb2.WriteFriendReferenceReq( 

564 to_user_id=user2.id, 

565 text="A test reference", 

566 was_appropriate=True, 

567 rating=0.5, 

568 ) 

569 ) 

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

571 assert e.value.details() == "Reference already given." 

572 

573 with references_session(token2) as api: 

574 # can't write a reference about yourself 

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

576 api.WriteFriendReference( 

577 references_pb2.WriteFriendReferenceReq( 

578 to_user_id=user2.id, 

579 text="I'm really awesome", 

580 was_appropriate=True, 

581 rating=1.0, 

582 ) 

583 ) 

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

585 assert e.value.details() == "You can't refer yourself." 

586 

587 

588def test_WriteFriendReference_with_empty_text(db): 

589 user1, token1 = generate_user() 

590 user2, token2 = generate_user() 

591 

592 with references_session(token1) as api: 

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

594 api.WriteFriendReference( 

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

596 ) 

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

598 assert e.value.details() == "The text of a reference must not be empty" 

599 

600 

601def test_WriteFriendReference_with_private_text( 

602 db, email_collector: EmailCollector, push_collector: PushCollector, moderator 

603): 

604 user1, token1 = generate_user() 

605 user2, token2 = generate_user() 

606 

607 # Make users friends 

608 make_friends(user1, user2) 

609 

610 with references_session(token1) as api: 

611 ref = api.WriteFriendReference( 

612 references_pb2.WriteFriendReferenceReq( 

613 to_user_id=user2.id, 

614 text="They were nice!", 

615 was_appropriate=True, 

616 rating=0.6, 

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

618 ) 

619 ) 

620 # Approve before patches/jobs exit so the pending notification is delivered while mocked. 

621 moderator.approve_reference(ref.reference_id) 

622 

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

624 email_collector.pop_for_reports(last=True) 

625 email = email_collector.pop_for_recipient(user2.email, last=True) 

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

627 assert email.recipient == user2.email 

628 

629 push = push_collector.pop_for_user(user2.id, last=True) 

630 assert push.content.title == f"New friend reference from {user1.name}" 

631 assert push.content.body == "They were nice!" 

632 

633 

634def test_WriteFriendReference_requires_friendship(db): 

635 """Test that users must be friends to write friend references""" 

636 user1, token1 = generate_user() 

637 user2, token2 = generate_user() 

638 

639 # Try to write friend reference without being friends 

640 with references_session(token1) as api: 

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

642 api.WriteFriendReference( 

643 references_pb2.WriteFriendReferenceReq( 

644 to_user_id=user2.id, 

645 text="A test reference", 

646 was_appropriate=True, 

647 rating=0.5, 

648 ) 

649 ) 

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

651 assert e.value.details() == "You can only write friend references for confirmed friends." 

652 

653 # Now make them friends 

654 make_friends(user1, user2) 

655 

656 # Should now be able to write a reference 

657 with references_session(token1) as api: 

658 res = api.WriteFriendReference( 

659 references_pb2.WriteFriendReferenceReq( 

660 to_user_id=user2.id, 

661 text="A test reference", 

662 was_appropriate=True, 

663 rating=0.5, 

664 ) 

665 ) 

666 assert res.from_user_id == user1.id 

667 assert res.to_user_id == user2.id 

668 

669 # Test the unfriending scenario: delete the friendship 

670 with session_scope() as session: 

671 # Change the friendship status to cancelled (simulating unfriending) 

672 session.execute( 

673 update(FriendRelationship) 

674 .where(FriendRelationship.from_user_id == user1.id, FriendRelationship.to_user_id == user2.id) 

675 .values(status=FriendStatus.cancelled) 

676 ) 

677 

678 # Try to write another friend reference after unfriending 

679 # (Note: This assumes user1 didn't already write a reference, or we test with user2 writing to user1) 

680 with references_session(token2) as api: 

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

682 api.WriteFriendReference( 

683 references_pb2.WriteFriendReferenceReq( 

684 to_user_id=user1.id, 

685 text="Another test reference", 

686 was_appropriate=True, 

687 rating=0.8, 

688 ) 

689 ) 

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

691 assert e.value.details() == "You can only write friend references for confirmed friends." 

692 

693 

694def test_host_request_states_references(db, moderator): 

695 user1, token1 = generate_user() 

696 user2, token2 = generate_user() 

697 

698 with session_scope() as session: 

699 # can't write ref 

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

701 # can write ref 

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

703 # can't write ref 

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

705 # can write ref 

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

707 # can't write ref 

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

709 

710 # Approve host requests so both participants can see them 

711 moderator.approve_host_request(hr1) 

712 moderator.approve_host_request(hr2) 

713 moderator.approve_host_request(hr3) 

714 moderator.approve_host_request(hr4) 

715 moderator.approve_host_request(hr5) 

716 

717 with references_session(token1) as api: 

718 # pending 

719 api.WriteHostRequestReference( 

720 references_pb2.WriteHostRequestReferenceReq( 

721 host_request_id=hr2, 

722 text="Should work!", 

723 was_appropriate=True, 

724 rating=0.9, 

725 ) 

726 ) 

727 

728 # accepted 

729 api.WriteHostRequestReference( 

730 references_pb2.WriteHostRequestReferenceReq( 

731 host_request_id=hr4, 

732 text="Should work!", 

733 was_appropriate=True, 

734 rating=0.9, 

735 ) 

736 ) 

737 

738 # rejected 

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

740 api.WriteHostRequestReference( 

741 references_pb2.WriteHostRequestReferenceReq( 

742 host_request_id=hr1, 

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

744 was_appropriate=True, 

745 rating=0.9, 

746 ) 

747 ) 

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

749 assert e.value.details() == "You can't write a reference for that host request, or it wasn't found." 

750 

751 # confirmed 

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

753 api.WriteHostRequestReference( 

754 references_pb2.WriteHostRequestReferenceReq( 

755 host_request_id=hr3, 

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

757 was_appropriate=True, 

758 rating=0.9, 

759 ) 

760 ) 

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

762 assert e.value.details() == "You can't write a reference for that host request, or it wasn't found." 

763 

764 # cancelled 

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

766 api.WriteHostRequestReference( 

767 references_pb2.WriteHostRequestReferenceReq( 

768 host_request_id=hr5, 

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

770 was_appropriate=True, 

771 rating=0.9, 

772 ) 

773 ) 

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

775 assert e.value.details() == "You can't write a reference for that host request, or it wasn't found." 

776 

777 

778def test_WriteHostRequestReference(db, moderator): 

779 user1, token1 = generate_user() 

780 user2, token2 = generate_user() 

781 user3, token3 = generate_user() 

782 user4, token4 = generate_user() 

783 

784 with session_scope() as session: 

785 # too old 

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

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

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

789 # valid surfing req 

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

791 # not yet complete 

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

793 # we indicated we didn't meet 

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

795 # we will indicate we didn't meet 

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

797 

798 # Approve host requests so both participants can see them 

799 moderator.approve_host_request(hr1) 

800 moderator.approve_host_request(hr2) 

801 moderator.approve_host_request(hr3) 

802 moderator.approve_host_request(hr4) 

803 moderator.approve_host_request(hr5) 

804 moderator.approve_host_request(hr6) 

805 

806 with references_session(token3) as api: 

807 # can write for this one 

808 api.WriteHostRequestReference( 

809 references_pb2.WriteHostRequestReferenceReq( 

810 host_request_id=hr3, 

811 text="Should work!", 

812 was_appropriate=True, 

813 rating=0.9, 

814 ) 

815 ) 

816 

817 with references_session(token1) as api: 

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

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

820 api.WriteHostRequestReference( 

821 references_pb2.WriteHostRequestReferenceReq( 

822 host_request_id=hr4, 

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

824 was_appropriate=True, 

825 rating=0.9, 

826 ) 

827 ) 

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

829 assert e.value.details() == "You can't write a reference for that host request, or it wasn't found." 

830 

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

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

833 api.WriteHostRequestReference( 

834 references_pb2.WriteHostRequestReferenceReq( 

835 host_request_id=hr1, 

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

837 was_appropriate=True, 

838 rating=0.9, 

839 ) 

840 ) 

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

842 assert e.value.details() == "You can't write a reference for that host request, or it wasn't found." 

843 

844 # can write for this one 

845 api.WriteHostRequestReference( 

846 references_pb2.WriteHostRequestReferenceReq( 

847 host_request_id=hr2, 

848 text="Should work!", 

849 was_appropriate=True, 

850 rating=0.9, 

851 ) 

852 ) 

853 

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

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

856 api.WriteHostRequestReference( 

857 references_pb2.WriteHostRequestReferenceReq( 

858 host_request_id=hr2, 

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

860 was_appropriate=True, 

861 rating=0.9, 

862 ) 

863 ) 

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

865 assert e.value.details() == "Reference already given." 

866 

867 # can write for this one too 

868 api.WriteHostRequestReference( 

869 references_pb2.WriteHostRequestReferenceReq( 

870 host_request_id=hr3, 

871 text="Should work!", 

872 was_appropriate=True, 

873 rating=0.9, 

874 ) 

875 ) 

876 

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

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

879 api.WriteHostRequestReference( 

880 references_pb2.WriteHostRequestReferenceReq( 

881 host_request_id=hr5, 

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

883 was_appropriate=True, 

884 rating=0.9, 

885 ) 

886 ) 

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

888 assert ( 

889 e.value.details() 

890 == "You can't write a reference for that host request because you indicated that you didn't meet up." 

891 ) 

892 

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

894 api.HostRequestIndicateDidntMeetup( 

895 references_pb2.HostRequestIndicateDidntMeetupReq( 

896 host_request_id=hr6, 

897 reason_didnt_meetup="No clue?", 

898 ) 

899 ) 

900 

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

902 api.WriteHostRequestReference( 

903 references_pb2.WriteHostRequestReferenceReq( 

904 host_request_id=hr6, 

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

906 was_appropriate=True, 

907 rating=0.9, 

908 ) 

909 ) 

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

911 assert ( 

912 e.value.details() 

913 == "You can't write a reference for that host request because you indicated that you didn't meet up." 

914 ) 

915 

916 with references_session(token4) as api: 

917 # they can still write one 

918 api.WriteHostRequestReference( 

919 references_pb2.WriteHostRequestReferenceReq( 

920 host_request_id=hr6, 

921 text="Should work!", 

922 was_appropriate=True, 

923 rating=0.9, 

924 ) 

925 ) 

926 

927 

928def test_WriteHostRequestReference_private_text( 

929 db, email_collector: EmailCollector, push_collector: PushCollector, moderator 

930): 

931 user1, token1 = generate_user() 

932 user2, token2 = generate_user() 

933 

934 with session_scope() as session: 

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

936 moderator.approve_host_request(hr) 

937 

938 with references_session(token1) as api: 

939 ref = api.WriteHostRequestReference( 

940 references_pb2.WriteHostRequestReferenceReq( 

941 host_request_id=hr, 

942 text="Should work!", 

943 was_appropriate=True, 

944 rating=0.9, 

945 private_text="Something", 

946 ) 

947 ) 

948 # Approve before patches/jobs exit so the pending notification is delivered while mocked. 

949 moderator.approve_reference(ref.reference_id) 

950 

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

952 email_collector.pop_for_reports(last=True) 

953 email = email_collector.pop_for_recipient(user2.email, last=True) 

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

955 assert email.recipient == user2.email 

956 

957 push = push_collector.pop_for_user(user2.id, last=True) 

958 assert push.content.title == f"New reference from {user1.name}" 

959 assert push.content.body == f"{user1.name} left you a reference, now it's your turn to write theirs!" 

960 

961 

962def test_GetHostRequestReferenceStatus(db, moderator): 

963 user1, token1 = generate_user() 

964 user2, token2 = generate_user() 

965 

966 # user1 writes; RPC returns has_given True 

967 with session_scope() as session: 

968 hr1 = create_host_request(session, user1.id, user2.id, timedelta(days=7)) 

969 moderator.approve_host_request(hr1) 

970 with references_session(token1) as api: 

971 api.WriteHostRequestReference( 

972 references_pb2.WriteHostRequestReferenceReq( 

973 host_request_id=hr1, text="Great stay!", was_appropriate=True, rating=0.9 

974 ) 

975 ) 

976 res = api.GetHostRequestReferenceStatus(references_pb2.GetHostRequestReferenceStatusReq(host_request_id=hr1)) 

977 assert res.has_given is True 

978 

979 # false: no reference written yet 

980 with session_scope() as session: 

981 hr2 = create_host_request(session, user1.id, user2.id, timedelta(days=7)) 

982 moderator.approve_host_request(hr2) 

983 with references_session(token1) as api: 

984 res = api.GetHostRequestReferenceStatus(references_pb2.GetHostRequestReferenceStatusReq(host_request_id=hr2)) 

985 assert res.has_given is False 

986 

987 # false: other user wrote a reference 

988 with session_scope() as session: 

989 hr3 = create_host_request(session, user1.id, user2.id, timedelta(days=7)) 

990 moderator.approve_host_request(hr3) 

991 with references_session(token2) as api: 

992 api.WriteHostRequestReference( 

993 references_pb2.WriteHostRequestReferenceReq( 

994 host_request_id=hr3, text="Lovely guest!", was_appropriate=True, rating=0.95 

995 ) 

996 ) 

997 with references_session(token1) as api: 

998 res = api.GetHostRequestReferenceStatus(references_pb2.GetHostRequestReferenceStatusReq(host_request_id=hr3)) 

999 assert res.has_given is False 

1000 

1001 # false: nonexistent host request id 

1002 with references_session(token1) as api: 

1003 res = api.GetHostRequestReferenceStatus(references_pb2.GetHostRequestReferenceStatusReq(host_request_id=999999)) 

1004 assert res.has_given is False 

1005 

1006 # Additional status flags 

1007 with session_scope() as session: 

1008 # expired (too old) 

1009 hr_expired = create_host_request(session, user2.id, user1.id, timedelta(days=20)) 

1010 # current user (host) indicated didn't meet up 

1011 hr_didnt_stay_host = create_host_request( 

1012 session, user2.id, user1.id, timedelta(days=10), host_reason_didnt_meetup="" 

1013 ) 

1014 # other user (surfer) indicated didn't meet up 

1015 hr_other_didnt_stay = create_host_request( 

1016 session, user2.id, user1.id, timedelta(days=10), surfer_reason_didnt_meetup="No show" 

1017 ) 

1018 

1019 moderator.approve_host_request(hr_expired) 

1020 moderator.approve_host_request(hr_didnt_stay_host) 

1021 moderator.approve_host_request(hr_other_didnt_stay) 

1022 

1023 # expired: is_expired true, can_write false, didnt_stay false 

1024 with references_session(token1) as api: 

1025 res = api.GetHostRequestReferenceStatus( 

1026 references_pb2.GetHostRequestReferenceStatusReq(host_request_id=hr_expired) 

1027 ) 

1028 assert res.has_given is False 

1029 assert res.is_expired is True 

1030 assert res.can_write is False 

1031 assert res.didnt_stay is False 

1032 

1033 # current user indicated didn't meet up: didnt_stay true, can_write false, not expired 

1034 with references_session(token1) as api: 

1035 res = api.GetHostRequestReferenceStatus( 

1036 references_pb2.GetHostRequestReferenceStatusReq(host_request_id=hr_didnt_stay_host) 

1037 ) 

1038 assert res.has_given is False 

1039 assert res.is_expired is False 

1040 assert res.didnt_stay is True 

1041 assert res.can_write is False 

1042 

1043 # other party indicated didn't meet up: didnt_stay false, can_write true (within window), not expired 

1044 with references_session(token1) as api: 

1045 res = api.GetHostRequestReferenceStatus( 

1046 references_pb2.GetHostRequestReferenceStatusReq(host_request_id=hr_other_didnt_stay) 

1047 ) 

1048 assert res.has_given is False 

1049 assert res.is_expired is False 

1050 assert res.didnt_stay is False 

1051 assert res.can_write is True 

1052 

1053 

1054def test_AvailableWriteReferences_and_ListPendingReferencesToWrite(db, moderator): 

1055 user1, token1 = generate_user() 

1056 user2, token2 = generate_user() 

1057 user3, token3 = generate_user() 

1058 user4, token4 = generate_user() 

1059 user5, token5 = generate_user(delete_user=True) 

1060 user6, token6 = generate_user() 

1061 user7, token7 = generate_user() 

1062 user8, token8 = generate_user() 

1063 user9, token9 = generate_user() 

1064 user10, token10 = generate_user() 

1065 user11, token11 = generate_user() 

1066 make_user_block(user1, user6) 

1067 make_user_block(user7, user1) 

1068 

1069 with session_scope() as session: 

1070 # too old 

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

1072 

1073 # already wrote friend ref to user3 

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

1075 

1076 # already given 

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

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

1079 

1080 # valid hosted 

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

1082 

1083 # valid surfed 

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

1085 

1086 # not yet complete 

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

1088 

1089 # already wrote friend ref to user2 

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

1091 

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

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

1094 

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

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

1097 

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

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

1100 

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

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

1103 

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

1105 hr_user9 = create_host_request( 

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

1107 ) 

1108 

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

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

1111 

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

1113 hr7 = create_host_request( 

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

1115 ) 

1116 

1117 # Approve all host requests so both participants can see them 

1118 moderator.approve_host_request(hr1) 

1119 moderator.approve_host_request(hr2) 

1120 moderator.approve_host_request(hr3) 

1121 moderator.approve_host_request(hr4) 

1122 moderator.approve_host_request(hr5) 

1123 moderator.approve_host_request(hr_user5) 

1124 moderator.approve_host_request(hr_user6) 

1125 moderator.approve_host_request(hr_user7) 

1126 moderator.approve_host_request(hr_user8) 

1127 moderator.approve_host_request(hr_user9) 

1128 moderator.approve_host_request(hr6) 

1129 moderator.approve_host_request(hr7) 

1130 

1131 refresh_materialized_views_rapid(empty_pb2.Empty()) 

1132 

1133 with references_session(token1) as api: 

1134 # can't write reference for invisible user 

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

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

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

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

1139 

1140 # can't write reference for blocking user 

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

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

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

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

1145 

1146 # can't write reference for blocked user 

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

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

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

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

1151 

1152 # can't write anything to myself 

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

1154 assert not res.can_write_friend_reference 

1155 assert len(res.available_write_references) == 0 

1156 

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

1158 # can't write friend ref to user2 

1159 assert not res.can_write_friend_reference 

1160 # none we can write for user2 

1161 assert len(res.available_write_references) == 0 

1162 

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

1164 # can't write friend ref to user3 

1165 assert not res.can_write_friend_reference 

1166 # can write one reference because we hosted user3 

1167 assert len(res.available_write_references) == 1 

1168 w = res.available_write_references[0] 

1169 assert w.host_request_id == hr3 

1170 assert w.reference_type == references_pb2.REFERENCE_TYPE_HOSTED 

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

1172 

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

1174 # can write friend ref to user4 

1175 assert res.can_write_friend_reference 

1176 # can write one reference because we surfed with user4 

1177 assert len(res.available_write_references) == 1 

1178 w = res.available_write_references[0] 

1179 assert w.host_request_id == hr4 

1180 assert w.reference_type == references_pb2.REFERENCE_TYPE_SURFED 

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

1182 

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

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

1185 assert len(res.available_write_references) == 0 

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

1187 assert len(res.available_write_references) == 0 

1188 

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

1190 # surfed with them 

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

1192 assert len(res.available_write_references) == 1 

1193 w = res.available_write_references[0] 

1194 assert w.host_request_id == hr6 

1195 assert w.reference_type == references_pb2.REFERENCE_TYPE_SURFED 

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

1197 # hosted them 

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

1199 assert len(res.available_write_references) == 1 

1200 w = res.available_write_references[0] 

1201 assert w.host_request_id == hr7 

1202 assert w.reference_type == references_pb2.REFERENCE_TYPE_HOSTED 

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

1204 

1205 # finally check the general list 

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

1207 assert len(res.pending_references) == 4 

1208 w = res.pending_references[0] 

1209 assert w.host_request_id == hr3 

1210 assert w.reference_type == references_pb2.REFERENCE_TYPE_HOSTED 

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

1212 w = res.pending_references[1] 

1213 assert w.host_request_id == hr4 

1214 assert w.reference_type == references_pb2.REFERENCE_TYPE_SURFED 

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

1216 w = res.pending_references[2] 

1217 assert w.host_request_id == hr6 

1218 assert w.reference_type == references_pb2.REFERENCE_TYPE_SURFED 

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

1220 w = res.pending_references[3] 

1221 assert w.host_request_id == hr7 

1222 assert w.reference_type == references_pb2.REFERENCE_TYPE_HOSTED 

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

1224 

1225 with account_session(token1) as account: 

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

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

1228 "write_reference_reminder", 

1229 "write_reference_reminder", 

1230 "write_reference_reminder", 

1231 "write_reference_reminder", 

1232 ] 

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

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

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

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

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

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

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

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

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

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

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

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

1245 

1246 

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

1248def test_regression_disappearing_refs(db, hs, moderator): 

1249 """ 

1250 Roughly the reproduction steps are: 

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

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

1253 * On the surfer account, leave a reference 

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

1255 """ 

1256 user1, token1 = generate_user() 

1257 user2, token2 = generate_user() 

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

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

1260 with requests_session(token1) as api: 

1261 res = api.CreateHostRequest( 

1262 requests_pb2.CreateHostRequestReq( 

1263 host_user_id=user2.id, from_date=req_start, to_date=req_end, text=valid_request_text() 

1264 ) 

1265 ) 

1266 host_request_id = res.host_request_id 

1267 

1268 moderator.approve_host_request(host_request_id) 

1269 

1270 assert ( 

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

1272 .host_requests[0] 

1273 .latest_message.text.text 

1274 == valid_request_text() 

1275 ) 

1276 

1277 with requests_session(token2) as api: 

1278 api.RespondHostRequest( 

1279 requests_pb2.RespondHostRequestReq( 

1280 host_request_id=host_request_id, status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED 

1281 ) 

1282 ) 

1283 

1284 with requests_session(token1) as api: 

1285 api.RespondHostRequest( 

1286 requests_pb2.RespondHostRequestReq( 

1287 host_request_id=host_request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CONFIRMED 

1288 ) 

1289 ) 

1290 

1291 refresh_materialized_views_rapid(empty_pb2.Empty()) 

1292 

1293 with references_session(token1) as api: 

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

1295 assert len(res.pending_references) == 0 

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

1297 assert len(res.available_write_references) == 0 

1298 

1299 with references_session(token2) as api: 

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

1301 assert len(res.pending_references) == 0 

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

1303 assert len(res.available_write_references) == 0 

1304 

1305 # hack the time backwards 

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

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

1308 with session_scope() as session: 

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

1310 assert host_request.conversation_id == host_request_id 

1311 host_request.from_date = hack_req_start 

1312 host_request.to_date = hack_req_end 

1313 

1314 with references_session(token1) as api: 

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

1316 assert len(res.pending_references) == 1 

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

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

1319 

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

1321 assert len(res.available_write_references) == 1 

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

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

1324 

1325 with references_session(token2) as api: 

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

1327 assert len(res.pending_references) == 1 

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

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

1330 

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

1332 assert len(res.available_write_references) == 1 

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

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

1335 

1336 if hs == "host": 

1337 with references_session(token2) as api: 

1338 api.WriteHostRequestReference( 

1339 references_pb2.WriteHostRequestReferenceReq( 

1340 host_request_id=host_request_id, 

1341 text="Good stuff", 

1342 was_appropriate=True, 

1343 rating=0.86, 

1344 ) 

1345 ) 

1346 

1347 with references_session(token2) as api: 

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

1349 assert len(res.pending_references) == 0 

1350 

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

1352 assert len(res.available_write_references) == 0 

1353 

1354 with references_session(token1) as api: 

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

1356 assert len(res.pending_references) == 1 

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

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

1359 

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

1361 assert len(res.available_write_references) == 1 

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

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

1364 else: 

1365 with references_session(token1) as api: 

1366 api.WriteHostRequestReference( 

1367 references_pb2.WriteHostRequestReferenceReq( 

1368 host_request_id=host_request_id, 

1369 text="Good stuff", 

1370 was_appropriate=True, 

1371 rating=0.86, 

1372 ) 

1373 ) 

1374 

1375 with references_session(token1) as api: 

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

1377 assert len(res.pending_references) == 0 

1378 

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

1380 assert len(res.available_write_references) == 0 

1381 

1382 with references_session(token2) as api: 

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

1384 assert len(res.pending_references) == 1 

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

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

1387 

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

1389 assert len(res.available_write_references) == 1 

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

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

1392 

1393 

1394def test_WriteFriendReference_creates_shadowed_moderation_state(db): 

1395 """New friend references start out shadowed and are not visible to non-authors.""" 

1396 user1, token1 = generate_user() 

1397 user2, token2 = generate_user() 

1398 user3, token3 = generate_user() 

1399 make_friends(user1, user2) 

1400 

1401 with references_session(token1) as api: 

1402 ref = api.WriteFriendReference( 

1403 references_pb2.WriteFriendReferenceReq( 

1404 to_user_id=user2.id, text="Nice friend", was_appropriate=True, rating=0.9 

1405 ) 

1406 ) 

1407 

1408 with session_scope() as session: 

1409 reference = session.execute(select(Reference).where(Reference.id == ref.reference_id)).scalar_one() 

1410 assert reference.moderation_state.visibility == ModerationVisibility.shadowed 

1411 assert reference.moderation_state.object_type == ModerationObjectType.reference 

1412 assert reference.moderation_state.object_id == reference.id 

1413 

1414 # Author can see their own shadowed reference. 

1415 with references_session(token1) as api: 

1416 res = api.ListReferences(references_pb2.ListReferencesReq(from_user_id=user1.id)) 

1417 assert [r.reference_id for r in res.references] == [ref.reference_id] 

1418 

1419 # Non-author user cannot see it while shadowed. 

1420 with references_session(token3) as api: 

1421 res = api.ListReferences(references_pb2.ListReferencesReq(to_user_id=user2.id)) 

1422 assert [r.reference_id for r in res.references] == [] 

1423 

1424 

1425def test_reference_hidden_via_ums_disappears_from_listings(db, moderator): 

1426 """Hiding a reference through UMS removes it from listings, including for the author.""" 

1427 user1, token1 = generate_user() 

1428 user2, token2 = generate_user() 

1429 user3, token3 = generate_user() 

1430 make_friends(user1, user2) 

1431 

1432 with references_session(token1) as api: 

1433 ref = api.WriteFriendReference( 

1434 references_pb2.WriteFriendReferenceReq( 

1435 to_user_id=user2.id, text="Visible for now", was_appropriate=True, rating=0.9 

1436 ) 

1437 ) 

1438 moderator.approve_reference(ref.reference_id) 

1439 

1440 with references_session(token3) as api: 

1441 assert api.ListReferences(references_pb2.ListReferencesReq(to_user_id=user2.id)).references 

1442 

1443 # Hide the reference via the moderation API. 

1444 with real_moderation_session(moderator.token) as mod_api: 

1445 state_res = mod_api.GetModerationState( 

1446 moderation_pb2.GetModerationStateReq( 

1447 object_type=moderation_pb2.MODERATION_OBJECT_TYPE_REFERENCE, 

1448 object_id=ref.reference_id, 

1449 ) 

1450 ) 

1451 mod_api.ModerateContent( 

1452 moderation_pb2.ModerateContentReq( 

1453 moderation_state_id=state_res.moderation_state.moderation_state_id, 

1454 action=moderation_pb2.MODERATION_ACTION_HIDE, 

1455 visibility=moderation_pb2.MODERATION_VISIBILITY_HIDDEN, 

1456 reason="test", 

1457 ) 

1458 ) 

1459 

1460 # Hidden references are invisible to the author and to other users. 

1461 with references_session(token1) as api: 

1462 assert not api.ListReferences(references_pb2.ListReferencesReq(from_user_id=user1.id)).references 

1463 with references_session(token3) as api: 

1464 assert not api.ListReferences(references_pb2.ListReferencesReq(to_user_id=user2.id)).references