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

699 statements  

« prev     ^ index     » next       coverage.py v7.15.3, created at 2026-08-04 22:32 +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, messages_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 with api_session(token1) as api: 

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

511 

512 

513def test_ListReference_blocked_shadowed_users(db): 

514 user1, token1 = generate_user() 

515 user2, token2 = generate_user() 

516 user3, token3 = generate_user() 

517 

518 with session_scope() as session: 

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

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

521 

522 # user1 blocks user2: reference from blocked user remains in both list and count 

523 make_user_block(user1, user2) 

524 

525 with references_session(token1) as api: 

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

527 assert len(refs_rec) == 2 

528 

529 with api_session(token1) as api: 

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

531 

532 # shadow user3: their reference is hidden from both list and count 

533 with session_scope() as session: 

534 session.execute(update(User).where(User.username == user3.username).values(shadowed_at=func.now())) 

535 

536 with references_session(token1) as api: 

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

538 assert len(refs_rec) == 1 

539 

540 with api_session(token1) as api: 

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

542 

543 

544def test_WriteFriendReference(db, moderator): 

545 user1, token1 = generate_user() 

546 user2, token2 = generate_user() 

547 user3, token3 = generate_user() 

548 

549 # Make user1 and user2 friends 

550 make_friends(user1, user2) 

551 

552 with references_session(token1) as api: 

553 # can write normal friend reference 

554 res = api.WriteFriendReference( 

555 references_pb2.WriteFriendReferenceReq( 

556 to_user_id=user2.id, 

557 text="A test reference", 

558 was_appropriate=True, 

559 rating=0.5, 

560 ) 

561 ) 

562 assert res.from_user_id == user1.id 

563 assert res.to_user_id == user2.id 

564 assert res.reference_type == references_pb2.REFERENCE_TYPE_FRIEND 

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

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

567 assert not res.host_request_id 

568 

569 moderator.approve_reference(res.reference_id) 

570 

571 with references_session(token3) as api: 

572 # check it shows up 

573 res = api.ListReferences( 

574 references_pb2.ListReferencesReq( 

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

576 ) 

577 ) 

578 assert len(res.references) == 1 

579 ref = res.references[0] 

580 assert ref.from_user_id == user1.id 

581 assert ref.to_user_id == user2.id 

582 assert ref.reference_type == references_pb2.REFERENCE_TYPE_FRIEND 

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

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

585 assert not ref.host_request_id 

586 

587 with references_session(token1) as api: 

588 # can't write a second friend reference 

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

590 api.WriteFriendReference( 

591 references_pb2.WriteFriendReferenceReq( 

592 to_user_id=user2.id, 

593 text="A test reference", 

594 was_appropriate=True, 

595 rating=0.5, 

596 ) 

597 ) 

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

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

600 

601 with references_session(token2) as api: 

602 # can't write a reference about yourself 

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

604 api.WriteFriendReference( 

605 references_pb2.WriteFriendReferenceReq( 

606 to_user_id=user2.id, 

607 text="I'm really awesome", 

608 was_appropriate=True, 

609 rating=1.0, 

610 ) 

611 ) 

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

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

614 

615 

616def test_WriteFriendReference_with_empty_text(db): 

617 user1, token1 = generate_user() 

618 user2, token2 = generate_user() 

619 

620 with references_session(token1) as api: 

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

622 api.WriteFriendReference( 

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

624 ) 

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

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

627 

628 

629def test_WriteFriendReference_with_private_text( 

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

631): 

632 user1, token1 = generate_user() 

633 user2, token2 = generate_user() 

634 

635 # Make users friends 

636 make_friends(user1, user2) 

637 

638 with references_session(token1) as api: 

639 ref = api.WriteFriendReference( 

640 references_pb2.WriteFriendReferenceReq( 

641 to_user_id=user2.id, 

642 text="They were nice!", 

643 was_appropriate=True, 

644 rating=0.6, 

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

646 ) 

647 ) 

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

649 moderator.approve_reference(ref.reference_id) 

650 

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

652 email_collector.pop_for_reports(last=True) 

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

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

655 assert email.recipient == user2.email 

656 

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

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

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

660 

661 

662def test_WriteFriendReference_requires_friendship(db): 

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

664 user1, token1 = generate_user() 

665 user2, token2 = generate_user() 

666 

667 # Try to write friend reference without being friends 

668 with references_session(token1) as api: 

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

670 api.WriteFriendReference( 

671 references_pb2.WriteFriendReferenceReq( 

672 to_user_id=user2.id, 

673 text="A test reference", 

674 was_appropriate=True, 

675 rating=0.5, 

676 ) 

677 ) 

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

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

680 

681 # Now make them friends 

682 make_friends(user1, user2) 

683 

684 # Should now be able to write a reference 

685 with references_session(token1) as api: 

686 res = api.WriteFriendReference( 

687 references_pb2.WriteFriendReferenceReq( 

688 to_user_id=user2.id, 

689 text="A test reference", 

690 was_appropriate=True, 

691 rating=0.5, 

692 ) 

693 ) 

694 assert res.from_user_id == user1.id 

695 assert res.to_user_id == user2.id 

696 

697 # Test the unfriending scenario: delete the friendship 

698 with session_scope() as session: 

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

700 session.execute( 

701 update(FriendRelationship) 

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

703 .values(status=FriendStatus.cancelled) 

704 ) 

705 

706 # Try to write another friend reference after unfriending 

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

708 with references_session(token2) as api: 

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

710 api.WriteFriendReference( 

711 references_pb2.WriteFriendReferenceReq( 

712 to_user_id=user1.id, 

713 text="Another test reference", 

714 was_appropriate=True, 

715 rating=0.8, 

716 ) 

717 ) 

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

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

720 

721 

722def test_host_request_states_references(db, moderator): 

723 user1, token1 = generate_user() 

724 user2, token2 = generate_user() 

725 

726 with session_scope() as session: 

727 # can't write ref 

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

729 # can write ref 

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

731 # can't write ref 

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

733 # can write ref 

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

735 # can't write ref 

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

737 

738 # Approve host requests so both participants can see them 

739 moderator.approve_host_request(hr1) 

740 moderator.approve_host_request(hr2) 

741 moderator.approve_host_request(hr3) 

742 moderator.approve_host_request(hr4) 

743 moderator.approve_host_request(hr5) 

744 

745 with references_session(token1) as api: 

746 # pending 

747 api.WriteHostRequestReference( 

748 references_pb2.WriteHostRequestReferenceReq( 

749 host_request_id=hr2, 

750 text="Should work!", 

751 was_appropriate=True, 

752 rating=0.9, 

753 ) 

754 ) 

755 

756 # accepted 

757 api.WriteHostRequestReference( 

758 references_pb2.WriteHostRequestReferenceReq( 

759 host_request_id=hr4, 

760 text="Should work!", 

761 was_appropriate=True, 

762 rating=0.9, 

763 ) 

764 ) 

765 

766 # rejected 

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

768 api.WriteHostRequestReference( 

769 references_pb2.WriteHostRequestReferenceReq( 

770 host_request_id=hr1, 

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

772 was_appropriate=True, 

773 rating=0.9, 

774 ) 

775 ) 

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

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

778 

779 # confirmed 

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

781 api.WriteHostRequestReference( 

782 references_pb2.WriteHostRequestReferenceReq( 

783 host_request_id=hr3, 

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

785 was_appropriate=True, 

786 rating=0.9, 

787 ) 

788 ) 

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

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

791 

792 # cancelled 

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

794 api.WriteHostRequestReference( 

795 references_pb2.WriteHostRequestReferenceReq( 

796 host_request_id=hr5, 

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

798 was_appropriate=True, 

799 rating=0.9, 

800 ) 

801 ) 

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

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

804 

805 

806def test_WriteHostRequestReference(db, moderator): 

807 user1, token1 = generate_user() 

808 user2, token2 = generate_user() 

809 user3, token3 = generate_user() 

810 user4, token4 = generate_user() 

811 

812 with session_scope() as session: 

813 # too old 

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

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

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

817 # valid surfing req 

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

819 # not yet complete 

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

821 # we indicated we didn't meet 

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

823 # we will indicate we didn't meet 

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

825 

826 # Approve host requests so both participants can see them 

827 moderator.approve_host_request(hr1) 

828 moderator.approve_host_request(hr2) 

829 moderator.approve_host_request(hr3) 

830 moderator.approve_host_request(hr4) 

831 moderator.approve_host_request(hr5) 

832 moderator.approve_host_request(hr6) 

833 

834 with references_session(token3) as api: 

835 # can write for this one 

836 api.WriteHostRequestReference( 

837 references_pb2.WriteHostRequestReferenceReq( 

838 host_request_id=hr3, 

839 text="Should work!", 

840 was_appropriate=True, 

841 rating=0.9, 

842 ) 

843 ) 

844 

845 with references_session(token1) as api: 

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

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

848 api.WriteHostRequestReference( 

849 references_pb2.WriteHostRequestReferenceReq( 

850 host_request_id=hr4, 

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

852 was_appropriate=True, 

853 rating=0.9, 

854 ) 

855 ) 

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

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

858 

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

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

861 api.WriteHostRequestReference( 

862 references_pb2.WriteHostRequestReferenceReq( 

863 host_request_id=hr1, 

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

865 was_appropriate=True, 

866 rating=0.9, 

867 ) 

868 ) 

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

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

871 

872 # can write for this one 

873 api.WriteHostRequestReference( 

874 references_pb2.WriteHostRequestReferenceReq( 

875 host_request_id=hr2, 

876 text="Should work!", 

877 was_appropriate=True, 

878 rating=0.9, 

879 ) 

880 ) 

881 

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

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

884 api.WriteHostRequestReference( 

885 references_pb2.WriteHostRequestReferenceReq( 

886 host_request_id=hr2, 

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

888 was_appropriate=True, 

889 rating=0.9, 

890 ) 

891 ) 

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

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

894 

895 # can write for this one too 

896 api.WriteHostRequestReference( 

897 references_pb2.WriteHostRequestReferenceReq( 

898 host_request_id=hr3, 

899 text="Should work!", 

900 was_appropriate=True, 

901 rating=0.9, 

902 ) 

903 ) 

904 

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

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

907 api.WriteHostRequestReference( 

908 references_pb2.WriteHostRequestReferenceReq( 

909 host_request_id=hr5, 

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

911 was_appropriate=True, 

912 rating=0.9, 

913 ) 

914 ) 

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

916 assert ( 

917 e.value.details() 

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

919 ) 

920 

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

922 api.HostRequestIndicateDidntMeetup( 

923 references_pb2.HostRequestIndicateDidntMeetupReq( 

924 host_request_id=hr6, 

925 reason_didnt_meetup="No clue?", 

926 ) 

927 ) 

928 

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

930 api.WriteHostRequestReference( 

931 references_pb2.WriteHostRequestReferenceReq( 

932 host_request_id=hr6, 

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

934 was_appropriate=True, 

935 rating=0.9, 

936 ) 

937 ) 

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

939 assert ( 

940 e.value.details() 

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

942 ) 

943 

944 with references_session(token4) as api: 

945 # they can still write one 

946 api.WriteHostRequestReference( 

947 references_pb2.WriteHostRequestReferenceReq( 

948 host_request_id=hr6, 

949 text="Should work!", 

950 was_appropriate=True, 

951 rating=0.9, 

952 ) 

953 ) 

954 

955 

956def test_WriteHostRequestReference_private_text( 

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

958): 

959 user1, token1 = generate_user() 

960 user2, token2 = generate_user() 

961 

962 with session_scope() as session: 

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

964 moderator.approve_host_request(hr) 

965 

966 with references_session(token1) as api: 

967 ref = api.WriteHostRequestReference( 

968 references_pb2.WriteHostRequestReferenceReq( 

969 host_request_id=hr, 

970 text="Should work!", 

971 was_appropriate=True, 

972 rating=0.9, 

973 private_text="Something", 

974 ) 

975 ) 

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

977 moderator.approve_reference(ref.reference_id) 

978 

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

980 email_collector.pop_for_reports(last=True) 

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

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

983 assert email.recipient == user2.email 

984 

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

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

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

988 

989 

990def test_GetHostRequestReferenceStatus(db, moderator): 

991 user1, token1 = generate_user() 

992 user2, token2 = generate_user() 

993 

994 # user1 writes; RPC returns has_given True 

995 with session_scope() as session: 

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

997 moderator.approve_host_request(hr1) 

998 with references_session(token1) as api: 

999 api.WriteHostRequestReference( 

1000 references_pb2.WriteHostRequestReferenceReq( 

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

1002 ) 

1003 ) 

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

1005 assert res.has_given is True 

1006 

1007 # false: no reference written yet 

1008 with session_scope() as session: 

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

1010 moderator.approve_host_request(hr2) 

1011 with references_session(token1) as api: 

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

1013 assert res.has_given is False 

1014 

1015 # false: other user wrote a reference 

1016 with session_scope() as session: 

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

1018 moderator.approve_host_request(hr3) 

1019 with references_session(token2) as api: 

1020 api.WriteHostRequestReference( 

1021 references_pb2.WriteHostRequestReferenceReq( 

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

1023 ) 

1024 ) 

1025 with references_session(token1) as api: 

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

1027 assert res.has_given is False 

1028 

1029 # false: nonexistent host request id 

1030 with references_session(token1) as api: 

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

1032 assert res.has_given is False 

1033 

1034 # Additional status flags 

1035 with session_scope() as session: 

1036 # expired (too old) 

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

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

1039 hr_didnt_stay_host = create_host_request( 

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

1041 ) 

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

1043 hr_other_didnt_stay = create_host_request( 

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

1045 ) 

1046 

1047 moderator.approve_host_request(hr_expired) 

1048 moderator.approve_host_request(hr_didnt_stay_host) 

1049 moderator.approve_host_request(hr_other_didnt_stay) 

1050 

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

1052 with references_session(token1) as api: 

1053 res = api.GetHostRequestReferenceStatus( 

1054 references_pb2.GetHostRequestReferenceStatusReq(host_request_id=hr_expired) 

1055 ) 

1056 assert res.has_given is False 

1057 assert res.is_expired is True 

1058 assert res.can_write is False 

1059 assert res.didnt_stay is False 

1060 

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

1062 with references_session(token1) as api: 

1063 res = api.GetHostRequestReferenceStatus( 

1064 references_pb2.GetHostRequestReferenceStatusReq(host_request_id=hr_didnt_stay_host) 

1065 ) 

1066 assert res.has_given is False 

1067 assert res.is_expired is False 

1068 assert res.didnt_stay is True 

1069 assert res.can_write is False 

1070 

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

1072 with references_session(token1) as api: 

1073 res = api.GetHostRequestReferenceStatus( 

1074 references_pb2.GetHostRequestReferenceStatusReq(host_request_id=hr_other_didnt_stay) 

1075 ) 

1076 assert res.has_given is False 

1077 assert res.is_expired is False 

1078 assert res.didnt_stay is False 

1079 assert res.can_write is True 

1080 

1081 

1082def test_AvailableWriteReferences_and_ListPendingReferencesToWrite(db, moderator): 

1083 user1, token1 = generate_user() 

1084 user2, token2 = generate_user() 

1085 user3, token3 = generate_user() 

1086 user4, token4 = generate_user() 

1087 user5, token5 = generate_user(delete_user=True) 

1088 user6, token6 = generate_user() 

1089 user7, token7 = generate_user() 

1090 user8, token8 = generate_user() 

1091 user9, token9 = generate_user() 

1092 user10, token10 = generate_user() 

1093 user11, token11 = generate_user() 

1094 make_user_block(user1, user6) 

1095 make_user_block(user7, user1) 

1096 

1097 with session_scope() as session: 

1098 # too old 

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

1100 

1101 # already wrote friend ref to user3 

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

1103 

1104 # already given 

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

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

1107 

1108 # valid hosted 

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

1110 

1111 # valid surfed 

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

1113 

1114 # not yet complete 

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

1116 

1117 # already wrote friend ref to user2 

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

1119 

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

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

1122 

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

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

1125 

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

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

1128 

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

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

1131 

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

1133 hr_user9 = create_host_request( 

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

1135 ) 

1136 

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

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

1139 

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

1141 hr7 = create_host_request( 

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

1143 ) 

1144 

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

1146 moderator.approve_host_request(hr1) 

1147 moderator.approve_host_request(hr2) 

1148 moderator.approve_host_request(hr3) 

1149 moderator.approve_host_request(hr4) 

1150 moderator.approve_host_request(hr5) 

1151 moderator.approve_host_request(hr_user5) 

1152 moderator.approve_host_request(hr_user6) 

1153 moderator.approve_host_request(hr_user7) 

1154 moderator.approve_host_request(hr_user8) 

1155 moderator.approve_host_request(hr_user9) 

1156 moderator.approve_host_request(hr6) 

1157 moderator.approve_host_request(hr7) 

1158 

1159 refresh_materialized_views_rapid(empty_pb2.Empty()) 

1160 

1161 with references_session(token1) as api: 

1162 # can't write reference for invisible user 

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

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

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

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

1167 

1168 # can't write reference for blocking user 

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

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

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

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

1173 

1174 # can't write reference for blocked user 

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

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

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

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

1179 

1180 # can't write anything to myself 

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

1182 assert not res.can_write_friend_reference 

1183 assert len(res.available_write_references) == 0 

1184 

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

1186 # can't write friend ref to user2 

1187 assert not res.can_write_friend_reference 

1188 # none we can write for user2 

1189 assert len(res.available_write_references) == 0 

1190 

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

1192 # can't write friend ref to user3 

1193 assert not res.can_write_friend_reference 

1194 # can write one reference because we hosted user3 

1195 assert len(res.available_write_references) == 1 

1196 w = res.available_write_references[0] 

1197 assert w.host_request_id == hr3 

1198 assert w.reference_type == references_pb2.REFERENCE_TYPE_HOSTED 

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

1200 

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

1202 # can write friend ref to user4 

1203 assert res.can_write_friend_reference 

1204 # can write one reference because we surfed with user4 

1205 assert len(res.available_write_references) == 1 

1206 w = res.available_write_references[0] 

1207 assert w.host_request_id == hr4 

1208 assert w.reference_type == references_pb2.REFERENCE_TYPE_SURFED 

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

1210 

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

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

1213 assert len(res.available_write_references) == 0 

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

1215 assert len(res.available_write_references) == 0 

1216 

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

1218 # surfed with them 

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

1220 assert len(res.available_write_references) == 1 

1221 w = res.available_write_references[0] 

1222 assert w.host_request_id == hr6 

1223 assert w.reference_type == references_pb2.REFERENCE_TYPE_SURFED 

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

1225 # hosted them 

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

1227 assert len(res.available_write_references) == 1 

1228 w = res.available_write_references[0] 

1229 assert w.host_request_id == hr7 

1230 assert w.reference_type == references_pb2.REFERENCE_TYPE_HOSTED 

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

1232 

1233 # finally check the general list 

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

1235 assert len(res.pending_references) == 4 

1236 w = res.pending_references[0] 

1237 assert w.host_request_id == hr3 

1238 assert w.reference_type == references_pb2.REFERENCE_TYPE_HOSTED 

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

1240 w = res.pending_references[1] 

1241 assert w.host_request_id == hr4 

1242 assert w.reference_type == references_pb2.REFERENCE_TYPE_SURFED 

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

1244 w = res.pending_references[2] 

1245 assert w.host_request_id == hr6 

1246 assert w.reference_type == references_pb2.REFERENCE_TYPE_SURFED 

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

1248 w = res.pending_references[3] 

1249 assert w.host_request_id == hr7 

1250 assert w.reference_type == references_pb2.REFERENCE_TYPE_HOSTED 

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

1252 

1253 with account_session(token1) as account: 

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

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

1256 "write_reference_reminder", 

1257 "write_reference_reminder", 

1258 "write_reference_reminder", 

1259 "write_reference_reminder", 

1260 ] 

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

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

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

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

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

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

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

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

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

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

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

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

1273 

1274 

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

1276def test_regression_disappearing_refs(db, hs, moderator): 

1277 """ 

1278 Roughly the reproduction steps are: 

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

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

1281 * On the surfer account, leave a reference 

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

1283 """ 

1284 user1, token1 = generate_user() 

1285 user2, token2 = generate_user() 

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

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

1288 with requests_session(token1) as api: 

1289 res = api.CreateHostRequest( 

1290 requests_pb2.CreateHostRequestReq( 

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

1292 ) 

1293 ) 

1294 host_request_id = res.host_request_id 

1295 

1296 moderator.approve_host_request(host_request_id) 

1297 

1298 assert ( 

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

1300 .host_requests[0] 

1301 .latest_message.text.text 

1302 == valid_request_text() 

1303 ) 

1304 

1305 with requests_session(token2) as api: 

1306 api.RespondHostRequest( 

1307 requests_pb2.RespondHostRequestReq( 

1308 host_request_id=host_request_id, status=messages_pb2.HOST_REQUEST_STATUS_ACCEPTED 

1309 ) 

1310 ) 

1311 

1312 with requests_session(token1) as api: 

1313 api.RespondHostRequest( 

1314 requests_pb2.RespondHostRequestReq( 

1315 host_request_id=host_request_id, status=messages_pb2.HOST_REQUEST_STATUS_CONFIRMED 

1316 ) 

1317 ) 

1318 

1319 refresh_materialized_views_rapid(empty_pb2.Empty()) 

1320 

1321 with references_session(token1) as api: 

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

1323 assert len(res.pending_references) == 0 

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

1325 assert len(res.available_write_references) == 0 

1326 

1327 with references_session(token2) as api: 

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

1329 assert len(res.pending_references) == 0 

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

1331 assert len(res.available_write_references) == 0 

1332 

1333 # hack the time backwards 

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

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

1336 with session_scope() as session: 

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

1338 assert host_request.conversation_id == host_request_id 

1339 host_request.from_date = hack_req_start 

1340 host_request.to_date = hack_req_end 

1341 

1342 with references_session(token1) as api: 

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

1344 assert len(res.pending_references) == 1 

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

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

1347 

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

1349 assert len(res.available_write_references) == 1 

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

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

1352 

1353 with references_session(token2) as api: 

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

1355 assert len(res.pending_references) == 1 

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

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

1358 

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

1360 assert len(res.available_write_references) == 1 

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

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

1363 

1364 if hs == "host": 

1365 with references_session(token2) 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(token2) 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=user2.id)) 

1380 assert len(res.available_write_references) == 0 

1381 

1382 with references_session(token1) 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_SURFED 

1387 

1388 res = api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user2.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_SURFED 

1392 else: 

1393 with references_session(token1) as api: 

1394 api.WriteHostRequestReference( 

1395 references_pb2.WriteHostRequestReferenceReq( 

1396 host_request_id=host_request_id, 

1397 text="Good stuff", 

1398 was_appropriate=True, 

1399 rating=0.86, 

1400 ) 

1401 ) 

1402 

1403 with references_session(token1) as api: 

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

1405 assert len(res.pending_references) == 0 

1406 

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

1408 assert len(res.available_write_references) == 0 

1409 

1410 with references_session(token2) as api: 

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

1412 assert len(res.pending_references) == 1 

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

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

1415 

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

1417 assert len(res.available_write_references) == 1 

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

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

1420 

1421 

1422def test_WriteFriendReference_creates_shadowed_moderation_state(db): 

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

1424 user1, token1 = generate_user() 

1425 user2, token2 = generate_user() 

1426 user3, token3 = generate_user() 

1427 make_friends(user1, user2) 

1428 

1429 with references_session(token1) as api: 

1430 ref = api.WriteFriendReference( 

1431 references_pb2.WriteFriendReferenceReq( 

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

1433 ) 

1434 ) 

1435 

1436 with session_scope() as session: 

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

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

1439 assert reference.moderation_state.object_type == ModerationObjectType.reference 

1440 assert reference.moderation_state.object_id == reference.id 

1441 

1442 # Author can see their own shadowed reference. 

1443 with references_session(token1) as api: 

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

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

1446 

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

1448 with references_session(token3) as api: 

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

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

1451 

1452 

1453def test_reference_hidden_via_ums_disappears_from_listings(db, moderator): 

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

1455 user1, token1 = generate_user() 

1456 user2, token2 = generate_user() 

1457 user3, token3 = generate_user() 

1458 make_friends(user1, user2) 

1459 

1460 with references_session(token1) as api: 

1461 ref = api.WriteFriendReference( 

1462 references_pb2.WriteFriendReferenceReq( 

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

1464 ) 

1465 ) 

1466 moderator.approve_reference(ref.reference_id) 

1467 

1468 with references_session(token3) as api: 

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

1470 

1471 # Hide the reference via the moderation API. 

1472 with real_moderation_session(moderator.token) as mod_api: 

1473 state_res = mod_api.GetModerationState( 

1474 moderation_pb2.GetModerationStateReq( 

1475 object_type=moderation_pb2.MODERATION_OBJECT_TYPE_REFERENCE, 

1476 object_id=ref.reference_id, 

1477 ) 

1478 ) 

1479 mod_api.ModerateContent( 

1480 moderation_pb2.ModerateContentReq( 

1481 moderation_state_id=state_res.moderation_state.moderation_state_id, 

1482 action=moderation_pb2.MODERATION_ACTION_HIDE, 

1483 visibility=moderation_pb2.MODERATION_VISIBILITY_HIDDEN, 

1484 reason="test", 

1485 ) 

1486 ) 

1487 

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

1489 with references_session(token1) as api: 

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

1491 with references_session(token3) as api: 

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