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

649 statements  

« prev     ^ index     » next       coverage.py v7.14.2, created at 2026-06-21 09:29 +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 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 account_session, real_moderation_session, references_session, requests_session 

32from tests.test_requests import valid_request_text 

33 

34 

35@pytest.fixture(autouse=True) 

36def _(testconfig): 

37 pass 

38 

39 

40def create_host_request( 

41 session: Session, 

42 surfer_user_id: int, 

43 host_user_id: int, 

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

45 status: HostRequestStatus = HostRequestStatus.confirmed, 

46 host_reason_didnt_meetup: str | None = None, 

47 surfer_reason_didnt_meetup: str | None = None, 

48) -> int: 

49 """ 

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

51 """ 

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

53 to_date = today() - host_request_age 

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

55 conversation = Conversation() 

56 session.add(conversation) 

57 session.flush() 

58 

59 msg1 = Message( 

60 conversation_id=conversation.id, 

61 author_id=surfer_user_id, 

62 message_type=MessageType.chat_created, 

63 ) 

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

65 session.add(msg1) 

66 

67 msg2 = Message( 

68 conversation_id=conversation.id, 

69 author_id=surfer_user_id, 

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

71 message_type=MessageType.text, 

72 ) 

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

74 session.add(msg2) 

75 session.flush() 

76 

77 moderation_state = create_moderation( 

78 session, 

79 ModerationObjectType.host_request, 

80 conversation.id, 

81 surfer_user_id, 

82 ) 

83 

84 host_request = HostRequest( 

85 conversation_id=conversation.id, 

86 initiator_user_id=surfer_user_id, 

87 recipient_user_id=host_user_id, 

88 from_date=from_date, 

89 to_date=to_date, 

90 status=status, 

91 initiator_last_seen_message_id=msg2.id, 

92 recipient_reason_didnt_meetup=host_reason_didnt_meetup, 

93 initiator_reason_didnt_meetup=surfer_reason_didnt_meetup, 

94 hosting_city="Test City", 

95 hosting_location=create_coordinate(0, 0), 

96 hosting_radius=10, 

97 moderation_state_id=moderation_state.id, 

98 ) 

99 session.add(host_request) 

100 session.commit() 

101 return host_request.conversation_id 

102 

103 

104def create_host_request_by_date( 

105 session: Session, 

106 surfer_user_id: int, 

107 host_user_id: int, 

108 from_date: date, 

109 to_date: date, 

110 status: HostRequestStatus, 

111 host_sent_request_reminders: int, 

112 last_sent_request_reminder_time: datetime, 

113) -> int: 

114 conversation = Conversation() 

115 session.add(conversation) 

116 session.flush() 

117 

118 msg1 = Message( 

119 conversation_id=conversation.id, 

120 author_id=surfer_user_id, 

121 message_type=MessageType.chat_created, 

122 ) 

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

124 session.add(msg1) 

125 

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

127 msg2 = Message( 

128 conversation_id=conversation.id, 

129 author_id=surfer_user_id, 

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

131 message_type=MessageType.text, 

132 ) 

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

134 session.add(msg2) 

135 session.flush() 

136 

137 moderation_state = create_moderation( 

138 session, 

139 ModerationObjectType.host_request, 

140 conversation.id, 

141 surfer_user_id, 

142 ) 

143 

144 host_request = HostRequest( 

145 conversation_id=conversation.id, 

146 initiator_user_id=surfer_user_id, 

147 recipient_user_id=host_user_id, 

148 from_date=from_date, 

149 to_date=to_date, 

150 status=status, 

151 hosting_city="Test City", 

152 hosting_location=create_coordinate(0, 0), 

153 hosting_radius=10, 

154 moderation_state_id=moderation_state.id, 

155 ) 

156 host_request.recipient_sent_request_reminders = host_sent_request_reminders 

157 host_request.last_sent_request_reminder_time = last_sent_request_reminder_time 

158 

159 session.add(host_request) 

160 session.commit() 

161 return host_request.conversation_id 

162 

163 

164def create_host_reference( 

165 session: Session, 

166 from_user_id: int, 

167 to_user_id: int, 

168 reference_age: timedelta, 

169 *, 

170 surfing: bool = True, 

171 host_request_id: int | None = None, 

172) -> tuple[int, int]: 

173 if host_request_id: 

174 actual_host_request_id = host_request_id 

175 else: 

176 if surfing: 

177 actual_host_request_id = host_request_id or create_host_request( 

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

179 ) 

180 else: 

181 actual_host_request_id = host_request_id or create_host_request( 

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

183 ) 

184 

185 host_request = session.execute( 

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

187 ).scalar_one() 

188 

189 if host_request.initiator_user_id == from_user_id: 

190 reference_type = ReferenceType.surfed 

191 to_user_id = host_request.recipient_user_id 

192 assert from_user_id == host_request.initiator_user_id 

193 else: 

194 reference_type = ReferenceType.hosted 

195 to_user_id = host_request.initiator_user_id 

196 assert from_user_id == host_request.recipient_user_id 

197 

198 moderation_state = ModerationState( 

199 object_type=ModerationObjectType.reference, 

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

201 visibility=ModerationVisibility.visible, 

202 ) 

203 session.add(moderation_state) 

204 session.flush() 

205 

206 reference = Reference( 

207 from_user_id=from_user_id, 

208 to_user_id=to_user_id, 

209 host_request_id=host_request.conversation_id, 

210 text="Dummy reference", 

211 rating=0.5, 

212 was_appropriate=True, 

213 reference_type=reference_type, 

214 moderation_state_id=moderation_state.id, 

215 ) 

216 reference.time = now() - reference_age 

217 

218 session.add(reference) 

219 session.flush() 

220 moderation_state.object_id = reference.id 

221 session.commit() 

222 return reference.id, actual_host_request_id 

223 

224 

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

226 moderation_state = ModerationState( 

227 object_type=ModerationObjectType.reference, 

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

229 visibility=ModerationVisibility.visible, 

230 ) 

231 session.add(moderation_state) 

232 session.flush() 

233 

234 reference = Reference( 

235 from_user_id=from_user_id, 

236 to_user_id=to_user_id, 

237 reference_type=ReferenceType.friend, 

238 text="Test friend request", 

239 rating=0.4, 

240 was_appropriate=True, 

241 moderation_state_id=moderation_state.id, 

242 ) 

243 reference.time = now() - reference_age 

244 session.add(reference) 

245 session.flush() 

246 moderation_state.object_id = reference.id 

247 session.commit() 

248 return reference.id 

249 

250 

251def test_ListPagination(db): 

252 user1, token1 = generate_user() 

253 user2, token2 = generate_user() 

254 user3, token3 = generate_user() 

255 user4, token4 = generate_user() 

256 user5, token5 = generate_user() 

257 user6, token6 = generate_user() 

258 user7, token7 = generate_user() 

259 user8, token8 = generate_user() 

260 user9, token9 = generate_user() 

261 

262 with session_scope() as session: 

263 # bidirectional references 

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

265 ref2b, _ = create_host_reference( 

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

267 ) 

268 

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

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

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

272 

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

274 ref5b, _ = create_host_reference( 

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

276 ) 

277 

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

279 

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

281 

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

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

284 

285 # should be visible even under 2 weeks 

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

287 

288 # hidden because it's less than 2 weeks 

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

290 

291 # visible because both were written 

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

293 ref8c, _ = create_host_reference( 

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

295 ) 

296 

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

298 

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

300 

301 with references_session(token2) as api: 

302 # written by user1 

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

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

305 

306 res = api.ListReferences( 

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

308 ) 

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

310 

311 res = api.ListReferences( 

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

313 ) 

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

315 assert not res.next_page_token 

316 

317 # received by user1 

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

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

320 

321 res = api.ListReferences( 

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

323 ) 

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

325 assert not res.next_page_token 

326 

327 # same thing but with filters 

328 res = api.ListReferences( 

329 references_pb2.ListReferencesReq( 

330 to_user_id=user1.id, 

331 reference_type_filter=[ 

332 references_pb2.REFERENCE_TYPE_HOSTED, 

333 references_pb2.REFERENCE_TYPE_SURFED, 

334 references_pb2.REFERENCE_TYPE_FRIEND, 

335 ], 

336 page_size=5, 

337 ) 

338 ) 

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

340 

341 res = api.ListReferences( 

342 references_pb2.ListReferencesReq( 

343 to_user_id=user1.id, 

344 reference_type_filter=[ 

345 references_pb2.REFERENCE_TYPE_HOSTED, 

346 references_pb2.REFERENCE_TYPE_SURFED, 

347 references_pb2.REFERENCE_TYPE_FRIEND, 

348 ], 

349 page_token=res.next_page_token, 

350 page_size=5, 

351 ) 

352 ) 

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

354 assert not res.next_page_token 

355 

356 # received hosting references 

357 res = api.ListReferences( 

358 references_pb2.ListReferencesReq( 

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

360 ) 

361 ) 

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

363 

364 res = api.ListReferences( 

365 references_pb2.ListReferencesReq( 

366 to_user_id=user1.id, 

367 reference_type_filter=[references_pb2.REFERENCE_TYPE_HOSTED], 

368 page_token=res.next_page_token, 

369 page_size=3, 

370 ) 

371 ) 

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

373 assert not res.next_page_token 

374 

375 # written friend references 

376 res = api.ListReferences( 

377 references_pb2.ListReferencesReq( 

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

379 ) 

380 ) 

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

382 assert not res.next_page_token 

383 

384 # written surfing references 

385 res = api.ListReferences( 

386 references_pb2.ListReferencesReq( 

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

388 ) 

389 ) 

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

391 assert not res.next_page_token 

392 

393 with references_session(token7) as api: 

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

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

396 api.ListReferences( 

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

398 ) 

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

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

401 

402 with references_session(token5) as api: 

403 # from user1 to user2 

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

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

406 assert not res.next_page_token 

407 

408 # from user5 to user1 

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

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

411 assert not res.next_page_token 

412 

413 

414def test_ListReference_banned_deleted_users(db): 

415 user1, token1 = generate_user() 

416 user2, token2 = generate_user() 

417 user3, token3 = generate_user() 

418 

419 with session_scope() as session: 

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

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

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

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

424 

425 with references_session(token1) as api: 

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

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

428 assert len(refs_rec) == 2 

429 assert len(refs_sent) == 2 

430 

431 # ban user2 

432 with session_scope() as session: 

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

434 

435 # reference to and from banned user is hidden 

436 with references_session(token1) as api: 

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

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

439 assert len(refs_rec) == 1 

440 assert len(refs_sent) == 1 

441 

442 # delete user3 

443 with session_scope() as session: 

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

445 

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

447 with references_session(token1) as api: 

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

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

450 assert len(refs_rec) == 1 

451 assert len(refs_sent) == 1 

452 

453 

454def test_WriteFriendReference(db, moderator): 

455 user1, token1 = generate_user() 

456 user2, token2 = generate_user() 

457 user3, token3 = generate_user() 

458 

459 # Make user1 and user2 friends 

460 make_friends(user1, user2) 

461 

462 with references_session(token1) as api: 

463 # can write normal friend reference 

464 res = api.WriteFriendReference( 

465 references_pb2.WriteFriendReferenceReq( 

466 to_user_id=user2.id, 

467 text="A test reference", 

468 was_appropriate=True, 

469 rating=0.5, 

470 ) 

471 ) 

472 assert res.from_user_id == user1.id 

473 assert res.to_user_id == user2.id 

474 assert res.reference_type == references_pb2.REFERENCE_TYPE_FRIEND 

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

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

477 assert not res.host_request_id 

478 

479 moderator.approve_reference(res.reference_id) 

480 

481 with references_session(token3) as api: 

482 # check it shows up 

483 res = api.ListReferences( 

484 references_pb2.ListReferencesReq( 

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

486 ) 

487 ) 

488 assert len(res.references) == 1 

489 ref = res.references[0] 

490 assert ref.from_user_id == user1.id 

491 assert ref.to_user_id == user2.id 

492 assert ref.reference_type == references_pb2.REFERENCE_TYPE_FRIEND 

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

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

495 assert not ref.host_request_id 

496 

497 with references_session(token1) as api: 

498 # can't write a second friend reference 

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

500 api.WriteFriendReference( 

501 references_pb2.WriteFriendReferenceReq( 

502 to_user_id=user2.id, 

503 text="A test reference", 

504 was_appropriate=True, 

505 rating=0.5, 

506 ) 

507 ) 

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

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

510 

511 with references_session(token2) as api: 

512 # can't write a reference about yourself 

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

514 api.WriteFriendReference( 

515 references_pb2.WriteFriendReferenceReq( 

516 to_user_id=user2.id, 

517 text="I'm really awesome", 

518 was_appropriate=True, 

519 rating=1.0, 

520 ) 

521 ) 

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

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

524 

525 

526def test_WriteFriendReference_with_empty_text(db): 

527 user1, token1 = generate_user() 

528 user2, token2 = generate_user() 

529 

530 with references_session(token1) as api: 

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

532 api.WriteFriendReference( 

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

534 ) 

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

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

537 

538 

539def test_WriteFriendReference_with_private_text( 

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

541): 

542 user1, token1 = generate_user() 

543 user2, token2 = generate_user() 

544 

545 # Make users friends 

546 make_friends(user1, user2) 

547 

548 with references_session(token1) as api: 

549 ref = api.WriteFriendReference( 

550 references_pb2.WriteFriendReferenceReq( 

551 to_user_id=user2.id, 

552 text="They were nice!", 

553 was_appropriate=True, 

554 rating=0.6, 

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

556 ) 

557 ) 

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

559 moderator.approve_reference(ref.reference_id) 

560 

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

562 email_collector.pop_for_reports(last=True) 

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

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

565 assert email.recipient == user2.email 

566 

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

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

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

570 

571 

572def test_WriteFriendReference_requires_friendship(db): 

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

574 user1, token1 = generate_user() 

575 user2, token2 = generate_user() 

576 

577 # Try to write friend reference without being friends 

578 with references_session(token1) as api: 

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

580 api.WriteFriendReference( 

581 references_pb2.WriteFriendReferenceReq( 

582 to_user_id=user2.id, 

583 text="A test reference", 

584 was_appropriate=True, 

585 rating=0.5, 

586 ) 

587 ) 

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

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

590 

591 # Now make them friends 

592 make_friends(user1, user2) 

593 

594 # Should now be able to write a reference 

595 with references_session(token1) as api: 

596 res = api.WriteFriendReference( 

597 references_pb2.WriteFriendReferenceReq( 

598 to_user_id=user2.id, 

599 text="A test reference", 

600 was_appropriate=True, 

601 rating=0.5, 

602 ) 

603 ) 

604 assert res.from_user_id == user1.id 

605 assert res.to_user_id == user2.id 

606 

607 # Test the unfriending scenario: delete the friendship 

608 with session_scope() as session: 

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

610 session.execute( 

611 update(FriendRelationship) 

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

613 .values(status=FriendStatus.cancelled) 

614 ) 

615 

616 # Try to write another friend reference after unfriending 

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

618 with references_session(token2) as api: 

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

620 api.WriteFriendReference( 

621 references_pb2.WriteFriendReferenceReq( 

622 to_user_id=user1.id, 

623 text="Another test reference", 

624 was_appropriate=True, 

625 rating=0.8, 

626 ) 

627 ) 

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

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

630 

631 

632def test_host_request_states_references(db, moderator): 

633 user1, token1 = generate_user() 

634 user2, token2 = generate_user() 

635 

636 with session_scope() as session: 

637 # can't write ref 

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

639 # can write ref 

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

641 # can't write ref 

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

643 # can write ref 

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

645 # can't write ref 

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

647 

648 # Approve host requests so both participants can see them 

649 moderator.approve_host_request(hr1) 

650 moderator.approve_host_request(hr2) 

651 moderator.approve_host_request(hr3) 

652 moderator.approve_host_request(hr4) 

653 moderator.approve_host_request(hr5) 

654 

655 with references_session(token1) as api: 

656 # pending 

657 api.WriteHostRequestReference( 

658 references_pb2.WriteHostRequestReferenceReq( 

659 host_request_id=hr2, 

660 text="Should work!", 

661 was_appropriate=True, 

662 rating=0.9, 

663 ) 

664 ) 

665 

666 # accepted 

667 api.WriteHostRequestReference( 

668 references_pb2.WriteHostRequestReferenceReq( 

669 host_request_id=hr4, 

670 text="Should work!", 

671 was_appropriate=True, 

672 rating=0.9, 

673 ) 

674 ) 

675 

676 # rejected 

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

678 api.WriteHostRequestReference( 

679 references_pb2.WriteHostRequestReferenceReq( 

680 host_request_id=hr1, 

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

682 was_appropriate=True, 

683 rating=0.9, 

684 ) 

685 ) 

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

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

688 

689 # confirmed 

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

691 api.WriteHostRequestReference( 

692 references_pb2.WriteHostRequestReferenceReq( 

693 host_request_id=hr3, 

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

695 was_appropriate=True, 

696 rating=0.9, 

697 ) 

698 ) 

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

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

701 

702 # cancelled 

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

704 api.WriteHostRequestReference( 

705 references_pb2.WriteHostRequestReferenceReq( 

706 host_request_id=hr5, 

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

708 was_appropriate=True, 

709 rating=0.9, 

710 ) 

711 ) 

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

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

714 

715 

716def test_WriteHostRequestReference(db, moderator): 

717 user1, token1 = generate_user() 

718 user2, token2 = generate_user() 

719 user3, token3 = generate_user() 

720 user4, token4 = generate_user() 

721 

722 with session_scope() as session: 

723 # too old 

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

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

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

727 # valid surfing req 

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

729 # not yet complete 

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

731 # we indicated we didn't meet 

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

733 # we will indicate we didn't meet 

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

735 

736 # Approve host requests so both participants can see them 

737 moderator.approve_host_request(hr1) 

738 moderator.approve_host_request(hr2) 

739 moderator.approve_host_request(hr3) 

740 moderator.approve_host_request(hr4) 

741 moderator.approve_host_request(hr5) 

742 moderator.approve_host_request(hr6) 

743 

744 with references_session(token3) as api: 

745 # can write for this one 

746 api.WriteHostRequestReference( 

747 references_pb2.WriteHostRequestReferenceReq( 

748 host_request_id=hr3, 

749 text="Should work!", 

750 was_appropriate=True, 

751 rating=0.9, 

752 ) 

753 ) 

754 

755 with references_session(token1) as api: 

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

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

758 api.WriteHostRequestReference( 

759 references_pb2.WriteHostRequestReferenceReq( 

760 host_request_id=hr4, 

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

762 was_appropriate=True, 

763 rating=0.9, 

764 ) 

765 ) 

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

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

768 

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

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

771 api.WriteHostRequestReference( 

772 references_pb2.WriteHostRequestReferenceReq( 

773 host_request_id=hr1, 

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

775 was_appropriate=True, 

776 rating=0.9, 

777 ) 

778 ) 

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

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

781 

782 # can write for this one 

783 api.WriteHostRequestReference( 

784 references_pb2.WriteHostRequestReferenceReq( 

785 host_request_id=hr2, 

786 text="Should work!", 

787 was_appropriate=True, 

788 rating=0.9, 

789 ) 

790 ) 

791 

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

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

794 api.WriteHostRequestReference( 

795 references_pb2.WriteHostRequestReferenceReq( 

796 host_request_id=hr2, 

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() == "Reference already given." 

804 

805 # can write for this one too 

806 api.WriteHostRequestReference( 

807 references_pb2.WriteHostRequestReferenceReq( 

808 host_request_id=hr3, 

809 text="Should work!", 

810 was_appropriate=True, 

811 rating=0.9, 

812 ) 

813 ) 

814 

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

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

817 api.WriteHostRequestReference( 

818 references_pb2.WriteHostRequestReferenceReq( 

819 host_request_id=hr5, 

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

821 was_appropriate=True, 

822 rating=0.9, 

823 ) 

824 ) 

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

826 assert ( 

827 e.value.details() 

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

829 ) 

830 

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

832 api.HostRequestIndicateDidntMeetup( 

833 references_pb2.HostRequestIndicateDidntMeetupReq( 

834 host_request_id=hr6, 

835 reason_didnt_meetup="No clue?", 

836 ) 

837 ) 

838 

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

840 api.WriteHostRequestReference( 

841 references_pb2.WriteHostRequestReferenceReq( 

842 host_request_id=hr6, 

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

844 was_appropriate=True, 

845 rating=0.9, 

846 ) 

847 ) 

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

849 assert ( 

850 e.value.details() 

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

852 ) 

853 

854 with references_session(token4) as api: 

855 # they can still write one 

856 api.WriteHostRequestReference( 

857 references_pb2.WriteHostRequestReferenceReq( 

858 host_request_id=hr6, 

859 text="Should work!", 

860 was_appropriate=True, 

861 rating=0.9, 

862 ) 

863 ) 

864 

865 

866def test_WriteHostRequestReference_private_text( 

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

868): 

869 user1, token1 = generate_user() 

870 user2, token2 = generate_user() 

871 

872 with session_scope() as session: 

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

874 moderator.approve_host_request(hr) 

875 

876 with references_session(token1) as api: 

877 ref = api.WriteHostRequestReference( 

878 references_pb2.WriteHostRequestReferenceReq( 

879 host_request_id=hr, 

880 text="Should work!", 

881 was_appropriate=True, 

882 rating=0.9, 

883 private_text="Something", 

884 ) 

885 ) 

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

887 moderator.approve_reference(ref.reference_id) 

888 

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

890 email_collector.pop_for_reports(last=True) 

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

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

893 assert email.recipient == user2.email 

894 

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

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

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

898 

899 

900def test_GetHostRequestReferenceStatus(db, moderator): 

901 user1, token1 = generate_user() 

902 user2, token2 = generate_user() 

903 

904 # user1 writes; RPC returns has_given True 

905 with session_scope() as session: 

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

907 moderator.approve_host_request(hr1) 

908 with references_session(token1) as api: 

909 api.WriteHostRequestReference( 

910 references_pb2.WriteHostRequestReferenceReq( 

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

912 ) 

913 ) 

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

915 assert res.has_given is True 

916 

917 # false: no reference written yet 

918 with session_scope() as session: 

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

920 moderator.approve_host_request(hr2) 

921 with references_session(token1) as api: 

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

923 assert res.has_given is False 

924 

925 # false: other user wrote a reference 

926 with session_scope() as session: 

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

928 moderator.approve_host_request(hr3) 

929 with references_session(token2) as api: 

930 api.WriteHostRequestReference( 

931 references_pb2.WriteHostRequestReferenceReq( 

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

933 ) 

934 ) 

935 with references_session(token1) as api: 

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

937 assert res.has_given is False 

938 

939 # false: nonexistent host request id 

940 with references_session(token1) as api: 

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

942 assert res.has_given is False 

943 

944 # Additional status flags 

945 with session_scope() as session: 

946 # expired (too old) 

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

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

949 hr_didnt_stay_host = create_host_request( 

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

951 ) 

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

953 hr_other_didnt_stay = create_host_request( 

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

955 ) 

956 

957 moderator.approve_host_request(hr_expired) 

958 moderator.approve_host_request(hr_didnt_stay_host) 

959 moderator.approve_host_request(hr_other_didnt_stay) 

960 

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

962 with references_session(token1) as api: 

963 res = api.GetHostRequestReferenceStatus( 

964 references_pb2.GetHostRequestReferenceStatusReq(host_request_id=hr_expired) 

965 ) 

966 assert res.has_given is False 

967 assert res.is_expired is True 

968 assert res.can_write is False 

969 assert res.didnt_stay is False 

970 

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

972 with references_session(token1) as api: 

973 res = api.GetHostRequestReferenceStatus( 

974 references_pb2.GetHostRequestReferenceStatusReq(host_request_id=hr_didnt_stay_host) 

975 ) 

976 assert res.has_given is False 

977 assert res.is_expired is False 

978 assert res.didnt_stay is True 

979 assert res.can_write is False 

980 

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

982 with references_session(token1) as api: 

983 res = api.GetHostRequestReferenceStatus( 

984 references_pb2.GetHostRequestReferenceStatusReq(host_request_id=hr_other_didnt_stay) 

985 ) 

986 assert res.has_given is False 

987 assert res.is_expired is False 

988 assert res.didnt_stay is False 

989 assert res.can_write is True 

990 

991 

992def test_AvailableWriteReferences_and_ListPendingReferencesToWrite(db, moderator): 

993 user1, token1 = generate_user() 

994 user2, token2 = generate_user() 

995 user3, token3 = generate_user() 

996 user4, token4 = generate_user() 

997 user5, token5 = generate_user(delete_user=True) 

998 user6, token6 = generate_user() 

999 user7, token7 = generate_user() 

1000 user8, token8 = generate_user() 

1001 user9, token9 = generate_user() 

1002 user10, token10 = generate_user() 

1003 user11, token11 = generate_user() 

1004 make_user_block(user1, user6) 

1005 make_user_block(user7, user1) 

1006 

1007 with session_scope() as session: 

1008 # too old 

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

1010 

1011 # already wrote friend ref to user3 

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

1013 

1014 # already given 

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

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

1017 

1018 # valid hosted 

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

1020 

1021 # valid surfed 

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

1023 

1024 # not yet complete 

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

1026 

1027 # already wrote friend ref to user2 

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

1029 

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

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

1032 

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

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

1035 

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

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

1038 

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

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

1041 

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

1043 hr_user9 = create_host_request( 

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

1045 ) 

1046 

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

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

1049 

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

1051 hr7 = create_host_request( 

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

1053 ) 

1054 

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

1056 moderator.approve_host_request(hr1) 

1057 moderator.approve_host_request(hr2) 

1058 moderator.approve_host_request(hr3) 

1059 moderator.approve_host_request(hr4) 

1060 moderator.approve_host_request(hr5) 

1061 moderator.approve_host_request(hr_user5) 

1062 moderator.approve_host_request(hr_user6) 

1063 moderator.approve_host_request(hr_user7) 

1064 moderator.approve_host_request(hr_user8) 

1065 moderator.approve_host_request(hr_user9) 

1066 moderator.approve_host_request(hr6) 

1067 moderator.approve_host_request(hr7) 

1068 

1069 refresh_materialized_views_rapid(empty_pb2.Empty()) 

1070 

1071 with references_session(token1) as api: 

1072 # can't write reference for invisible user 

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

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

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

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

1077 

1078 # can't write reference for blocking user 

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

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

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

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

1083 

1084 # can't write reference for blocked user 

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

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

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

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

1089 

1090 # can't write anything to myself 

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

1092 assert not res.can_write_friend_reference 

1093 assert len(res.available_write_references) == 0 

1094 

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

1096 # can't write friend ref to user2 

1097 assert not res.can_write_friend_reference 

1098 # none we can write for user2 

1099 assert len(res.available_write_references) == 0 

1100 

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

1102 # can't write friend ref to user3 

1103 assert not res.can_write_friend_reference 

1104 # can write one reference because we hosted user3 

1105 assert len(res.available_write_references) == 1 

1106 w = res.available_write_references[0] 

1107 assert w.host_request_id == hr3 

1108 assert w.reference_type == references_pb2.REFERENCE_TYPE_HOSTED 

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

1110 

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

1112 # can write friend ref to user4 

1113 assert res.can_write_friend_reference 

1114 # can write one reference because we surfed with user4 

1115 assert len(res.available_write_references) == 1 

1116 w = res.available_write_references[0] 

1117 assert w.host_request_id == hr4 

1118 assert w.reference_type == references_pb2.REFERENCE_TYPE_SURFED 

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

1120 

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

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

1123 assert len(res.available_write_references) == 0 

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

1125 assert len(res.available_write_references) == 0 

1126 

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

1128 # surfed with them 

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

1130 assert len(res.available_write_references) == 1 

1131 w = res.available_write_references[0] 

1132 assert w.host_request_id == hr6 

1133 assert w.reference_type == references_pb2.REFERENCE_TYPE_SURFED 

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

1135 # hosted them 

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

1137 assert len(res.available_write_references) == 1 

1138 w = res.available_write_references[0] 

1139 assert w.host_request_id == hr7 

1140 assert w.reference_type == references_pb2.REFERENCE_TYPE_HOSTED 

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

1142 

1143 # finally check the general list 

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

1145 assert len(res.pending_references) == 4 

1146 w = res.pending_references[0] 

1147 assert w.host_request_id == hr3 

1148 assert w.reference_type == references_pb2.REFERENCE_TYPE_HOSTED 

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

1150 w = res.pending_references[1] 

1151 assert w.host_request_id == hr4 

1152 assert w.reference_type == references_pb2.REFERENCE_TYPE_SURFED 

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

1154 w = res.pending_references[2] 

1155 assert w.host_request_id == hr6 

1156 assert w.reference_type == references_pb2.REFERENCE_TYPE_SURFED 

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

1158 w = res.pending_references[3] 

1159 assert w.host_request_id == hr7 

1160 assert w.reference_type == references_pb2.REFERENCE_TYPE_HOSTED 

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

1162 

1163 with account_session(token1) as account: 

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

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

1166 "write_reference_reminder", 

1167 "write_reference_reminder", 

1168 "write_reference_reminder", 

1169 "write_reference_reminder", 

1170 ] 

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

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

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

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

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

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

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

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

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

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

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

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

1183 

1184 

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

1186def test_regression_disappearing_refs(db, hs, moderator): 

1187 """ 

1188 Roughly the reproduction steps are: 

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

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

1191 * On the surfer account, leave a reference 

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

1193 """ 

1194 user1, token1 = generate_user() 

1195 user2, token2 = generate_user() 

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

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

1198 with requests_session(token1) as api: 

1199 res = api.CreateHostRequest( 

1200 requests_pb2.CreateHostRequestReq( 

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

1202 ) 

1203 ) 

1204 host_request_id = res.host_request_id 

1205 

1206 moderator.approve_host_request(host_request_id) 

1207 

1208 assert ( 

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

1210 .host_requests[0] 

1211 .latest_message.text.text 

1212 == valid_request_text() 

1213 ) 

1214 

1215 with requests_session(token2) as api: 

1216 api.RespondHostRequest( 

1217 requests_pb2.RespondHostRequestReq( 

1218 host_request_id=host_request_id, status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED 

1219 ) 

1220 ) 

1221 

1222 with requests_session(token1) as api: 

1223 api.RespondHostRequest( 

1224 requests_pb2.RespondHostRequestReq( 

1225 host_request_id=host_request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CONFIRMED 

1226 ) 

1227 ) 

1228 

1229 refresh_materialized_views_rapid(empty_pb2.Empty()) 

1230 

1231 with references_session(token1) as api: 

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

1233 assert len(res.pending_references) == 0 

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

1235 assert len(res.available_write_references) == 0 

1236 

1237 with references_session(token2) as api: 

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

1239 assert len(res.pending_references) == 0 

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

1241 assert len(res.available_write_references) == 0 

1242 

1243 # hack the time backwards 

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

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

1246 with session_scope() as session: 

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

1248 assert host_request.conversation_id == host_request_id 

1249 host_request.from_date = hack_req_start 

1250 host_request.to_date = hack_req_end 

1251 

1252 with references_session(token1) as api: 

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

1254 assert len(res.pending_references) == 1 

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

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

1257 

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

1259 assert len(res.available_write_references) == 1 

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

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

1262 

1263 with references_session(token2) as api: 

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

1265 assert len(res.pending_references) == 1 

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

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

1268 

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

1270 assert len(res.available_write_references) == 1 

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

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

1273 

1274 if hs == "host": 

1275 with references_session(token2) as api: 

1276 api.WriteHostRequestReference( 

1277 references_pb2.WriteHostRequestReferenceReq( 

1278 host_request_id=host_request_id, 

1279 text="Good stuff", 

1280 was_appropriate=True, 

1281 rating=0.86, 

1282 ) 

1283 ) 

1284 

1285 with references_session(token2) as api: 

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

1287 assert len(res.pending_references) == 0 

1288 

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

1290 assert len(res.available_write_references) == 0 

1291 

1292 with references_session(token1) as api: 

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

1294 assert len(res.pending_references) == 1 

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

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

1297 

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

1299 assert len(res.available_write_references) == 1 

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

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

1302 else: 

1303 with references_session(token1) as api: 

1304 api.WriteHostRequestReference( 

1305 references_pb2.WriteHostRequestReferenceReq( 

1306 host_request_id=host_request_id, 

1307 text="Good stuff", 

1308 was_appropriate=True, 

1309 rating=0.86, 

1310 ) 

1311 ) 

1312 

1313 with references_session(token1) as api: 

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

1315 assert len(res.pending_references) == 0 

1316 

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

1318 assert len(res.available_write_references) == 0 

1319 

1320 with references_session(token2) as api: 

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

1322 assert len(res.pending_references) == 1 

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

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

1325 

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

1327 assert len(res.available_write_references) == 1 

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

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

1330 

1331 

1332def test_WriteFriendReference_creates_shadowed_moderation_state(db): 

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

1334 user1, token1 = generate_user() 

1335 user2, token2 = generate_user() 

1336 user3, token3 = generate_user() 

1337 make_friends(user1, user2) 

1338 

1339 with references_session(token1) as api: 

1340 ref = api.WriteFriendReference( 

1341 references_pb2.WriteFriendReferenceReq( 

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

1343 ) 

1344 ) 

1345 

1346 with session_scope() as session: 

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

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

1349 assert reference.moderation_state.object_type == ModerationObjectType.reference 

1350 assert reference.moderation_state.object_id == reference.id 

1351 

1352 # Author can see their own shadowed reference. 

1353 with references_session(token1) as api: 

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

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

1356 

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

1358 with references_session(token3) as api: 

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

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

1361 

1362 

1363def test_reference_hidden_via_ums_disappears_from_listings(db, moderator): 

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

1365 user1, token1 = generate_user() 

1366 user2, token2 = generate_user() 

1367 user3, token3 = generate_user() 

1368 make_friends(user1, user2) 

1369 

1370 with references_session(token1) as api: 

1371 ref = api.WriteFriendReference( 

1372 references_pb2.WriteFriendReferenceReq( 

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

1374 ) 

1375 ) 

1376 moderator.approve_reference(ref.reference_id) 

1377 

1378 with references_session(token3) as api: 

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

1380 

1381 # Hide the reference via the moderation API. 

1382 with real_moderation_session(moderator.token) as mod_api: 

1383 state_res = mod_api.GetModerationState( 

1384 moderation_pb2.GetModerationStateReq( 

1385 object_type=moderation_pb2.MODERATION_OBJECT_TYPE_REFERENCE, 

1386 object_id=ref.reference_id, 

1387 ) 

1388 ) 

1389 mod_api.ModerateContent( 

1390 moderation_pb2.ModerateContentReq( 

1391 moderation_state_id=state_res.moderation_state.moderation_state_id, 

1392 action=moderation_pb2.MODERATION_ACTION_HIDE, 

1393 visibility=moderation_pb2.MODERATION_VISIBILITY_HIDDEN, 

1394 reason="test", 

1395 ) 

1396 ) 

1397 

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

1399 with references_session(token1) as api: 

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

1401 with references_session(token3) as api: 

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