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
« prev ^ index » next coverage.py v7.15.3, created at 2026-08-04 22:32 +0000
1from datetime import date, datetime, timedelta
3import grpc
4import pytest
5from google.protobuf import empty_pb2
6from sqlalchemy import func, select, update
7from sqlalchemy.orm import Session
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
41@pytest.fixture(autouse=True)
42def _(testconfig):
43 pass
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()
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)
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()
83 moderation_state = create_moderation(
84 session,
85 ModerationObjectType.host_request,
86 conversation.id,
87 surfer_user_id,
88 )
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
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()
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)
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()
143 moderation_state = create_moderation(
144 session,
145 ModerationObjectType.host_request,
146 conversation.id,
147 surfer_user_id,
148 )
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
165 session.add(host_request)
166 session.commit()
167 return host_request.conversation_id
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 )
191 host_request = session.execute(
192 select(HostRequest).where(HostRequest.conversation_id == actual_host_request_id)
193 ).scalar_one()
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
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()
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
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
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()
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
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()
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 )
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))
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 )
284 ref6, _ = create_host_reference(session, user6.id, user1.id, timedelta(days=16, seconds=40), surfing=True)
286 ref7 = create_friend_reference(session, user7.id, user1.id, timedelta(days=16, seconds=30))
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)
291 # should be visible even under 2 weeks
292 ref7b = create_friend_reference(session, user1.id, user7.id, timedelta(days=9))
294 # hidden because it's less than 2 weeks
295 ref6hidden, _ = create_host_reference(session, user6.id, user1.id, timedelta(days=5), surfing=False)
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 )
303 # note that visibility tests don't really test real logic
305 # these check the right refs are in the right requests and appear in the right order (latest first)
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]
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]
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
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]
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
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]
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
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]
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
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
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
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."
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
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
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
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()
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)
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
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
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)
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
459 with api_session(token2) as api:
460 res = api.GetUser(api_pb2.GetUserReq(user=user1.username))
461 assert res.num_references == 1
464def test_ListReference_banned_deleted_users(db):
465 user1, token1 = generate_user()
466 user2, token2 = generate_user()
467 user3, token3 = generate_user()
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))
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
481 with api_session(token1) as api:
482 assert api.GetUser(api_pb2.GetUserReq(user=user1.username)).num_references == 2
484 # ban user2
485 with session_scope() as session:
486 session.execute(update(User).where(User.username == user2.username).values(banned_at=func.now()))
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
495 with api_session(token1) as api:
496 assert api.GetUser(api_pb2.GetUserReq(user=user1.username)).num_references == 1
498 # delete user3
499 with session_scope() as session:
500 session.execute(update(User).where(User.username == user3.username).values(deleted_at=func.now()))
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
509 with api_session(token1) as api:
510 assert api.GetUser(api_pb2.GetUserReq(user=user1.username)).num_references == 1
513def test_ListReference_blocked_shadowed_users(db):
514 user1, token1 = generate_user()
515 user2, token2 = generate_user()
516 user3, token3 = generate_user()
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))
522 # user1 blocks user2: reference from blocked user remains in both list and count
523 make_user_block(user1, user2)
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
529 with api_session(token1) as api:
530 assert api.GetUser(api_pb2.GetUserReq(user=user1.username)).num_references == 2
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()))
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
540 with api_session(token1) as api:
541 assert api.GetUser(api_pb2.GetUserReq(user=user1.username)).num_references == 1
544def test_WriteFriendReference(db, moderator):
545 user1, token1 = generate_user()
546 user2, token2 = generate_user()
547 user3, token3 = generate_user()
549 # Make user1 and user2 friends
550 make_friends(user1, user2)
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
569 moderator.approve_reference(res.reference_id)
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
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."
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."
616def test_WriteFriendReference_with_empty_text(db):
617 user1, token1 = generate_user()
618 user2, token2 = generate_user()
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"
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()
635 # Make users friends
636 make_friends(user1, user2)
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)
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
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!"
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()
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."
681 # Now make them friends
682 make_friends(user1, user2)
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
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 )
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."
722def test_host_request_states_references(db, moderator):
723 user1, token1 = generate_user()
724 user2, token2 = generate_user()
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)
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)
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 )
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 )
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."
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."
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."
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()
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))
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)
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 )
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."
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."
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 )
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."
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 )
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 )
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 )
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 )
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 )
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()
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)
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)
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
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!"
990def test_GetHostRequestReferenceStatus(db, moderator):
991 user1, token1 = generate_user()
992 user2, token2 = generate_user()
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
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
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
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
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 )
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)
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
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
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
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)
1097 with session_scope() as session:
1098 # too old
1099 hr1 = create_host_request(session, user3.id, user1.id, timedelta(days=20))
1101 # already wrote friend ref to user3
1102 create_friend_reference(session, user1.id, user3.id, timedelta(days=15, seconds=70))
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)
1108 # valid hosted
1109 hr3 = create_host_request(session, user3.id, user1.id, timedelta(days=8))
1111 # valid surfed
1112 hr4 = create_host_request(session, user1.id, user4.id, timedelta(days=5))
1114 # not yet complete
1115 hr5 = create_host_request(session, user2.id, user1.id, timedelta(days=2), status=HostRequestStatus.pending)
1117 # already wrote friend ref to user2
1118 create_friend_reference(session, user1.id, user2.id, timedelta(days=1))
1120 # user5 deleted, reference won't show up as pending
1121 hr_user5 = create_host_request(session, user1.id, user5.id, timedelta(days=5))
1123 # user6 blocked, reference won't show up as pending
1124 hr_user6 = create_host_request(session, user1.id, user6.id, timedelta(days=5))
1126 # user7 blocking, reference won't show up as pending
1127 hr_user7 = create_host_request(session, user1.id, user7.id, timedelta(days=5))
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="")
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 )
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="")
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 )
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)
1159 refresh_materialized_views_rapid(empty_pb2.Empty())
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."
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."
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."
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
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
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)
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)
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
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)
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)
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
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
1296 moderator.approve_host_request(host_request_id)
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 )
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 )
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 )
1319 refresh_materialized_views_rapid(empty_pb2.Empty())
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
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
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
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
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
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
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
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 )
1375 with references_session(token2) as api:
1376 res = api.ListPendingReferencesToWrite(empty_pb2.Empty())
1377 assert len(res.pending_references) == 0
1379 res = api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user2.id))
1380 assert len(res.available_write_references) == 0
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
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 )
1403 with references_session(token1) as api:
1404 res = api.ListPendingReferencesToWrite(empty_pb2.Empty())
1405 assert len(res.pending_references) == 0
1407 res = api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user1.id))
1408 assert len(res.available_write_references) == 0
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
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
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)
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 )
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
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]
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] == []
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)
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)
1468 with references_session(token3) as api:
1469 assert api.ListReferences(references_pb2.ListReferencesReq(to_user_id=user2.id)).references
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 )
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