Coverage for app/backend/src/tests/test_references.py: 100%
677 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-07-23 04:10 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-07-23 04:10 +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, conversations_pb2, moderation_pb2, references_pb2, requests_pb2
28from couchers.utils import create_coordinate, now, to_aware_datetime, today
29from tests.fixtures.db import generate_user, make_friends, make_user_block
30from tests.fixtures.misc import EmailCollector, PushCollector
31from tests.fixtures.sessions import (
32 account_session,
33 api_session,
34 real_moderation_session,
35 references_session,
36 requests_session,
37)
38from tests.test_requests import valid_request_text
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 # note: this assert currently fails due to a pre-existing divergence: get_num_references
510 # excludes deleted authors' references (via User.is_visible) while ListReferences keeps
511 # showing them, so the count here says 0 while the list shows 1
512 # with api_session(token1) as api:
513 # assert api.GetUser(api_pb2.GetUserReq(user=user1.username)).num_references == 1
516def test_WriteFriendReference(db, moderator):
517 user1, token1 = generate_user()
518 user2, token2 = generate_user()
519 user3, token3 = generate_user()
521 # Make user1 and user2 friends
522 make_friends(user1, user2)
524 with references_session(token1) as api:
525 # can write normal friend reference
526 res = api.WriteFriendReference(
527 references_pb2.WriteFriendReferenceReq(
528 to_user_id=user2.id,
529 text="A test reference",
530 was_appropriate=True,
531 rating=0.5,
532 )
533 )
534 assert res.from_user_id == user1.id
535 assert res.to_user_id == user2.id
536 assert res.reference_type == references_pb2.REFERENCE_TYPE_FRIEND
537 assert res.text == "A test reference"
538 assert now() - timedelta(hours=24) <= to_aware_datetime(res.written_time) <= now()
539 assert not res.host_request_id
541 moderator.approve_reference(res.reference_id)
543 with references_session(token3) as api:
544 # check it shows up
545 res = api.ListReferences(
546 references_pb2.ListReferencesReq(
547 from_user_id=user1.id, to_user_id=user2.id, reference_type_filter=[references_pb2.REFERENCE_TYPE_FRIEND]
548 )
549 )
550 assert len(res.references) == 1
551 ref = res.references[0]
552 assert ref.from_user_id == user1.id
553 assert ref.to_user_id == user2.id
554 assert ref.reference_type == references_pb2.REFERENCE_TYPE_FRIEND
555 assert ref.text == "A test reference"
556 assert now() - timedelta(hours=24) <= to_aware_datetime(ref.written_time) <= now()
557 assert not ref.host_request_id
559 with references_session(token1) as api:
560 # can't write a second friend reference
561 with pytest.raises(grpc.RpcError) as e:
562 api.WriteFriendReference(
563 references_pb2.WriteFriendReferenceReq(
564 to_user_id=user2.id,
565 text="A test reference",
566 was_appropriate=True,
567 rating=0.5,
568 )
569 )
570 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
571 assert e.value.details() == "Reference already given."
573 with references_session(token2) as api:
574 # can't write a reference about yourself
575 with pytest.raises(grpc.RpcError) as e:
576 api.WriteFriendReference(
577 references_pb2.WriteFriendReferenceReq(
578 to_user_id=user2.id,
579 text="I'm really awesome",
580 was_appropriate=True,
581 rating=1.0,
582 )
583 )
584 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
585 assert e.value.details() == "You can't refer yourself."
588def test_WriteFriendReference_with_empty_text(db):
589 user1, token1 = generate_user()
590 user2, token2 = generate_user()
592 with references_session(token1) as api:
593 with pytest.raises(grpc.RpcError) as e:
594 api.WriteFriendReference(
595 references_pb2.WriteFriendReferenceReq(to_user_id=user2.id, text=" ", was_appropriate=True, rating=0.8)
596 )
597 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
598 assert e.value.details() == "The text of a reference must not be empty"
601def test_WriteFriendReference_with_private_text(
602 db, email_collector: EmailCollector, push_collector: PushCollector, moderator
603):
604 user1, token1 = generate_user()
605 user2, token2 = generate_user()
607 # Make users friends
608 make_friends(user1, user2)
610 with references_session(token1) as api:
611 ref = api.WriteFriendReference(
612 references_pb2.WriteFriendReferenceReq(
613 to_user_id=user2.id,
614 text="They were nice!",
615 was_appropriate=True,
616 rating=0.6,
617 private_text="A bit of an odd ball, but a nice person nonetheless.",
618 )
619 )
620 # Approve before patches/jobs exit so the pending notification is delivered while mocked.
621 moderator.approve_reference(ref.reference_id)
623 # make sure an email was sent to the user receiving the ref as well as the mods
624 email_collector.pop_for_reports(last=True)
625 email = email_collector.pop_for_recipient(user2.email, last=True)
626 assert email.subject == f"[TEST] You've received a friend reference from {user1.name}!"
627 assert email.recipient == user2.email
629 push = push_collector.pop_for_user(user2.id, last=True)
630 assert push.content.title == f"New friend reference from {user1.name}"
631 assert push.content.body == "They were nice!"
634def test_WriteFriendReference_requires_friendship(db):
635 """Test that users must be friends to write friend references"""
636 user1, token1 = generate_user()
637 user2, token2 = generate_user()
639 # Try to write friend reference without being friends
640 with references_session(token1) as api:
641 with pytest.raises(grpc.RpcError) as e:
642 api.WriteFriendReference(
643 references_pb2.WriteFriendReferenceReq(
644 to_user_id=user2.id,
645 text="A test reference",
646 was_appropriate=True,
647 rating=0.5,
648 )
649 )
650 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
651 assert e.value.details() == "You can only write friend references for confirmed friends."
653 # Now make them friends
654 make_friends(user1, user2)
656 # Should now be able to write a reference
657 with references_session(token1) as api:
658 res = api.WriteFriendReference(
659 references_pb2.WriteFriendReferenceReq(
660 to_user_id=user2.id,
661 text="A test reference",
662 was_appropriate=True,
663 rating=0.5,
664 )
665 )
666 assert res.from_user_id == user1.id
667 assert res.to_user_id == user2.id
669 # Test the unfriending scenario: delete the friendship
670 with session_scope() as session:
671 # Change the friendship status to cancelled (simulating unfriending)
672 session.execute(
673 update(FriendRelationship)
674 .where(FriendRelationship.from_user_id == user1.id, FriendRelationship.to_user_id == user2.id)
675 .values(status=FriendStatus.cancelled)
676 )
678 # Try to write another friend reference after unfriending
679 # (Note: This assumes user1 didn't already write a reference, or we test with user2 writing to user1)
680 with references_session(token2) as api:
681 with pytest.raises(grpc.RpcError) as e:
682 api.WriteFriendReference(
683 references_pb2.WriteFriendReferenceReq(
684 to_user_id=user1.id,
685 text="Another test reference",
686 was_appropriate=True,
687 rating=0.8,
688 )
689 )
690 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
691 assert e.value.details() == "You can only write friend references for confirmed friends."
694def test_host_request_states_references(db, moderator):
695 user1, token1 = generate_user()
696 user2, token2 = generate_user()
698 with session_scope() as session:
699 # can't write ref
700 hr1 = create_host_request(session, user2.id, user1.id, timedelta(days=10), status=HostRequestStatus.pending)
701 # can write ref
702 hr2 = create_host_request(session, user2.id, user1.id, timedelta(days=10), status=HostRequestStatus.accepted)
703 # can't write ref
704 hr3 = create_host_request(session, user2.id, user1.id, timedelta(days=10), status=HostRequestStatus.rejected)
705 # can write ref
706 hr4 = create_host_request(session, user2.id, user1.id, timedelta(days=10), status=HostRequestStatus.confirmed)
707 # can't write ref
708 hr5 = create_host_request(session, user2.id, user1.id, timedelta(days=10), status=HostRequestStatus.cancelled)
710 # Approve host requests so both participants can see them
711 moderator.approve_host_request(hr1)
712 moderator.approve_host_request(hr2)
713 moderator.approve_host_request(hr3)
714 moderator.approve_host_request(hr4)
715 moderator.approve_host_request(hr5)
717 with references_session(token1) as api:
718 # pending
719 api.WriteHostRequestReference(
720 references_pb2.WriteHostRequestReferenceReq(
721 host_request_id=hr2,
722 text="Should work!",
723 was_appropriate=True,
724 rating=0.9,
725 )
726 )
728 # accepted
729 api.WriteHostRequestReference(
730 references_pb2.WriteHostRequestReferenceReq(
731 host_request_id=hr4,
732 text="Should work!",
733 was_appropriate=True,
734 rating=0.9,
735 )
736 )
738 # rejected
739 with pytest.raises(grpc.RpcError) as e:
740 api.WriteHostRequestReference(
741 references_pb2.WriteHostRequestReferenceReq(
742 host_request_id=hr1,
743 text="Shouldn't work...",
744 was_appropriate=True,
745 rating=0.9,
746 )
747 )
748 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
749 assert e.value.details() == "You can't write a reference for that host request, or it wasn't found."
751 # confirmed
752 with pytest.raises(grpc.RpcError) as e:
753 api.WriteHostRequestReference(
754 references_pb2.WriteHostRequestReferenceReq(
755 host_request_id=hr3,
756 text="Shouldn't work...",
757 was_appropriate=True,
758 rating=0.9,
759 )
760 )
761 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
762 assert e.value.details() == "You can't write a reference for that host request, or it wasn't found."
764 # cancelled
765 with pytest.raises(grpc.RpcError) as e:
766 api.WriteHostRequestReference(
767 references_pb2.WriteHostRequestReferenceReq(
768 host_request_id=hr5,
769 text="Shouldn't work...",
770 was_appropriate=True,
771 rating=0.9,
772 )
773 )
774 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
775 assert e.value.details() == "You can't write a reference for that host request, or it wasn't found."
778def test_WriteHostRequestReference(db, moderator):
779 user1, token1 = generate_user()
780 user2, token2 = generate_user()
781 user3, token3 = generate_user()
782 user4, token4 = generate_user()
784 with session_scope() as session:
785 # too old
786 hr1 = create_host_request(session, user3.id, user1.id, timedelta(days=20))
787 # valid host req, surfer said we didn't show up but we can still write a req
788 hr2 = create_host_request(session, user3.id, user1.id, timedelta(days=10), surfer_reason_didnt_meetup="No show")
789 # valid surfing req
790 hr3 = create_host_request(session, user1.id, user3.id, timedelta(days=7))
791 # not yet complete
792 hr4 = create_host_request(session, user2.id, user1.id, timedelta(days=1), status=HostRequestStatus.pending)
793 # we indicated we didn't meet
794 hr5 = create_host_request(session, user4.id, user1.id, timedelta(days=7), host_reason_didnt_meetup="")
795 # we will indicate we didn't meet
796 hr6 = create_host_request(session, user4.id, user1.id, timedelta(days=8))
798 # Approve host requests so both participants can see them
799 moderator.approve_host_request(hr1)
800 moderator.approve_host_request(hr2)
801 moderator.approve_host_request(hr3)
802 moderator.approve_host_request(hr4)
803 moderator.approve_host_request(hr5)
804 moderator.approve_host_request(hr6)
806 with references_session(token3) as api:
807 # can write for this one
808 api.WriteHostRequestReference(
809 references_pb2.WriteHostRequestReferenceReq(
810 host_request_id=hr3,
811 text="Should work!",
812 was_appropriate=True,
813 rating=0.9,
814 )
815 )
817 with references_session(token1) as api:
818 # can't write reference for a HR that's not yet finished
819 with pytest.raises(grpc.RpcError) as e:
820 api.WriteHostRequestReference(
821 references_pb2.WriteHostRequestReferenceReq(
822 host_request_id=hr4,
823 text="Shouldn't work...",
824 was_appropriate=True,
825 rating=0.9,
826 )
827 )
828 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
829 assert e.value.details() == "You can't write a reference for that host request, or it wasn't found."
831 # can't write reference that's more than 2 weeks old
832 with pytest.raises(grpc.RpcError) as e:
833 api.WriteHostRequestReference(
834 references_pb2.WriteHostRequestReferenceReq(
835 host_request_id=hr1,
836 text="Shouldn't work...",
837 was_appropriate=True,
838 rating=0.9,
839 )
840 )
841 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
842 assert e.value.details() == "You can't write a reference for that host request, or it wasn't found."
844 # can write for this one
845 api.WriteHostRequestReference(
846 references_pb2.WriteHostRequestReferenceReq(
847 host_request_id=hr2,
848 text="Should work!",
849 was_appropriate=True,
850 rating=0.9,
851 )
852 )
854 # but can't write a second one for the same one
855 with pytest.raises(grpc.RpcError) as e:
856 api.WriteHostRequestReference(
857 references_pb2.WriteHostRequestReferenceReq(
858 host_request_id=hr2,
859 text="Shouldn't work...",
860 was_appropriate=True,
861 rating=0.9,
862 )
863 )
864 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
865 assert e.value.details() == "Reference already given."
867 # can write for this one too
868 api.WriteHostRequestReference(
869 references_pb2.WriteHostRequestReferenceReq(
870 host_request_id=hr3,
871 text="Should work!",
872 was_appropriate=True,
873 rating=0.9,
874 )
875 )
877 # can't write reference for a HR that we indicated we didn't show up
878 with pytest.raises(grpc.RpcError) as e:
879 api.WriteHostRequestReference(
880 references_pb2.WriteHostRequestReferenceReq(
881 host_request_id=hr5,
882 text="Shouldn't work...",
883 was_appropriate=True,
884 rating=0.9,
885 )
886 )
887 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
888 assert (
889 e.value.details()
890 == "You can't write a reference for that host request because you indicated that you didn't meet up."
891 )
893 # can't write reference for a HR that we indicate we didn't show up for
894 api.HostRequestIndicateDidntMeetup(
895 references_pb2.HostRequestIndicateDidntMeetupReq(
896 host_request_id=hr6,
897 reason_didnt_meetup="No clue?",
898 )
899 )
901 with pytest.raises(grpc.RpcError) as e:
902 api.WriteHostRequestReference(
903 references_pb2.WriteHostRequestReferenceReq(
904 host_request_id=hr6,
905 text="Shouldn't work...",
906 was_appropriate=True,
907 rating=0.9,
908 )
909 )
910 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
911 assert (
912 e.value.details()
913 == "You can't write a reference for that host request because you indicated that you didn't meet up."
914 )
916 with references_session(token4) as api:
917 # they can still write one
918 api.WriteHostRequestReference(
919 references_pb2.WriteHostRequestReferenceReq(
920 host_request_id=hr6,
921 text="Should work!",
922 was_appropriate=True,
923 rating=0.9,
924 )
925 )
928def test_WriteHostRequestReference_private_text(
929 db, email_collector: EmailCollector, push_collector: PushCollector, moderator
930):
931 user1, token1 = generate_user()
932 user2, token2 = generate_user()
934 with session_scope() as session:
935 hr = create_host_request(session, user1.id, user2.id, timedelta(days=10))
936 moderator.approve_host_request(hr)
938 with references_session(token1) as api:
939 ref = api.WriteHostRequestReference(
940 references_pb2.WriteHostRequestReferenceReq(
941 host_request_id=hr,
942 text="Should work!",
943 was_appropriate=True,
944 rating=0.9,
945 private_text="Something",
946 )
947 )
948 # Approve before patches/jobs exit so the pending notification is delivered while mocked.
949 moderator.approve_reference(ref.reference_id)
951 # make sure an email was sent to the user receiving the ref as well as the mods
952 email_collector.pop_for_reports(last=True)
953 email = email_collector.pop_for_recipient(user2.email, last=True)
954 assert email.subject == f"[TEST] You've received a reference from {user1.name}!"
955 assert email.recipient == user2.email
957 push = push_collector.pop_for_user(user2.id, last=True)
958 assert push.content.title == f"New reference from {user1.name}"
959 assert push.content.body == f"{user1.name} left you a reference, now it's your turn to write theirs!"
962def test_GetHostRequestReferenceStatus(db, moderator):
963 user1, token1 = generate_user()
964 user2, token2 = generate_user()
966 # user1 writes; RPC returns has_given True
967 with session_scope() as session:
968 hr1 = create_host_request(session, user1.id, user2.id, timedelta(days=7))
969 moderator.approve_host_request(hr1)
970 with references_session(token1) as api:
971 api.WriteHostRequestReference(
972 references_pb2.WriteHostRequestReferenceReq(
973 host_request_id=hr1, text="Great stay!", was_appropriate=True, rating=0.9
974 )
975 )
976 res = api.GetHostRequestReferenceStatus(references_pb2.GetHostRequestReferenceStatusReq(host_request_id=hr1))
977 assert res.has_given is True
979 # false: no reference written yet
980 with session_scope() as session:
981 hr2 = create_host_request(session, user1.id, user2.id, timedelta(days=7))
982 moderator.approve_host_request(hr2)
983 with references_session(token1) as api:
984 res = api.GetHostRequestReferenceStatus(references_pb2.GetHostRequestReferenceStatusReq(host_request_id=hr2))
985 assert res.has_given is False
987 # false: other user wrote a reference
988 with session_scope() as session:
989 hr3 = create_host_request(session, user1.id, user2.id, timedelta(days=7))
990 moderator.approve_host_request(hr3)
991 with references_session(token2) as api:
992 api.WriteHostRequestReference(
993 references_pb2.WriteHostRequestReferenceReq(
994 host_request_id=hr3, text="Lovely guest!", was_appropriate=True, rating=0.95
995 )
996 )
997 with references_session(token1) as api:
998 res = api.GetHostRequestReferenceStatus(references_pb2.GetHostRequestReferenceStatusReq(host_request_id=hr3))
999 assert res.has_given is False
1001 # false: nonexistent host request id
1002 with references_session(token1) as api:
1003 res = api.GetHostRequestReferenceStatus(references_pb2.GetHostRequestReferenceStatusReq(host_request_id=999999))
1004 assert res.has_given is False
1006 # Additional status flags
1007 with session_scope() as session:
1008 # expired (too old)
1009 hr_expired = create_host_request(session, user2.id, user1.id, timedelta(days=20))
1010 # current user (host) indicated didn't meet up
1011 hr_didnt_stay_host = create_host_request(
1012 session, user2.id, user1.id, timedelta(days=10), host_reason_didnt_meetup=""
1013 )
1014 # other user (surfer) indicated didn't meet up
1015 hr_other_didnt_stay = create_host_request(
1016 session, user2.id, user1.id, timedelta(days=10), surfer_reason_didnt_meetup="No show"
1017 )
1019 moderator.approve_host_request(hr_expired)
1020 moderator.approve_host_request(hr_didnt_stay_host)
1021 moderator.approve_host_request(hr_other_didnt_stay)
1023 # expired: is_expired true, can_write false, didnt_stay false
1024 with references_session(token1) as api:
1025 res = api.GetHostRequestReferenceStatus(
1026 references_pb2.GetHostRequestReferenceStatusReq(host_request_id=hr_expired)
1027 )
1028 assert res.has_given is False
1029 assert res.is_expired is True
1030 assert res.can_write is False
1031 assert res.didnt_stay is False
1033 # current user indicated didn't meet up: didnt_stay true, can_write false, not expired
1034 with references_session(token1) as api:
1035 res = api.GetHostRequestReferenceStatus(
1036 references_pb2.GetHostRequestReferenceStatusReq(host_request_id=hr_didnt_stay_host)
1037 )
1038 assert res.has_given is False
1039 assert res.is_expired is False
1040 assert res.didnt_stay is True
1041 assert res.can_write is False
1043 # other party indicated didn't meet up: didnt_stay false, can_write true (within window), not expired
1044 with references_session(token1) as api:
1045 res = api.GetHostRequestReferenceStatus(
1046 references_pb2.GetHostRequestReferenceStatusReq(host_request_id=hr_other_didnt_stay)
1047 )
1048 assert res.has_given is False
1049 assert res.is_expired is False
1050 assert res.didnt_stay is False
1051 assert res.can_write is True
1054def test_AvailableWriteReferences_and_ListPendingReferencesToWrite(db, moderator):
1055 user1, token1 = generate_user()
1056 user2, token2 = generate_user()
1057 user3, token3 = generate_user()
1058 user4, token4 = generate_user()
1059 user5, token5 = generate_user(delete_user=True)
1060 user6, token6 = generate_user()
1061 user7, token7 = generate_user()
1062 user8, token8 = generate_user()
1063 user9, token9 = generate_user()
1064 user10, token10 = generate_user()
1065 user11, token11 = generate_user()
1066 make_user_block(user1, user6)
1067 make_user_block(user7, user1)
1069 with session_scope() as session:
1070 # too old
1071 hr1 = create_host_request(session, user3.id, user1.id, timedelta(days=20))
1073 # already wrote friend ref to user3
1074 create_friend_reference(session, user1.id, user3.id, timedelta(days=15, seconds=70))
1076 # already given
1077 _, hr2 = create_host_reference(session, user2.id, user1.id, timedelta(days=10, seconds=110), surfing=True)
1078 create_host_reference(session, user1.id, user2.id, timedelta(days=10, seconds=100), host_request_id=hr2)
1080 # valid hosted
1081 hr3 = create_host_request(session, user3.id, user1.id, timedelta(days=8))
1083 # valid surfed
1084 hr4 = create_host_request(session, user1.id, user4.id, timedelta(days=5))
1086 # not yet complete
1087 hr5 = create_host_request(session, user2.id, user1.id, timedelta(days=2), status=HostRequestStatus.pending)
1089 # already wrote friend ref to user2
1090 create_friend_reference(session, user1.id, user2.id, timedelta(days=1))
1092 # user5 deleted, reference won't show up as pending
1093 hr_user5 = create_host_request(session, user1.id, user5.id, timedelta(days=5))
1095 # user6 blocked, reference won't show up as pending
1096 hr_user6 = create_host_request(session, user1.id, user6.id, timedelta(days=5))
1098 # user7 blocking, reference won't show up as pending
1099 hr_user7 = create_host_request(session, user1.id, user7.id, timedelta(days=5))
1101 # hosted but we indicated we didn't meet up, no reason; should not show up
1102 hr_user8 = create_host_request(session, user8.id, user1.id, timedelta(days=11), host_reason_didnt_meetup="")
1104 # surfed but we indicated we didn't meet up, has reason; should not show up
1105 hr_user9 = create_host_request(
1106 session, user1.id, user9.id, timedelta(days=10), surfer_reason_didnt_meetup="They never showed up!"
1107 )
1109 # surfed but they indicated we didn't meet up, no reason; should show up
1110 hr6 = create_host_request(session, user1.id, user10.id, timedelta(days=4), host_reason_didnt_meetup="")
1112 # hosted but they indicated we didn't meet up, has reason; should show up
1113 hr7 = create_host_request(
1114 session, user11.id, user1.id, timedelta(days=3), surfer_reason_didnt_meetup="They never showed up!!"
1115 )
1117 # Approve all host requests so both participants can see them
1118 moderator.approve_host_request(hr1)
1119 moderator.approve_host_request(hr2)
1120 moderator.approve_host_request(hr3)
1121 moderator.approve_host_request(hr4)
1122 moderator.approve_host_request(hr5)
1123 moderator.approve_host_request(hr_user5)
1124 moderator.approve_host_request(hr_user6)
1125 moderator.approve_host_request(hr_user7)
1126 moderator.approve_host_request(hr_user8)
1127 moderator.approve_host_request(hr_user9)
1128 moderator.approve_host_request(hr6)
1129 moderator.approve_host_request(hr7)
1131 refresh_materialized_views_rapid(empty_pb2.Empty())
1133 with references_session(token1) as api:
1134 # can't write reference for invisible user
1135 with pytest.raises(grpc.RpcError) as e:
1136 api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user5.id))
1137 assert e.value.code() == grpc.StatusCode.NOT_FOUND
1138 assert e.value.details() == "Couldn't find that user."
1140 # can't write reference for blocking user
1141 with pytest.raises(grpc.RpcError) as e:
1142 api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user7.id))
1143 assert e.value.code() == grpc.StatusCode.NOT_FOUND
1144 assert e.value.details() == "Couldn't find that user."
1146 # can't write reference for blocked user
1147 with pytest.raises(grpc.RpcError) as e:
1148 api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user6.id))
1149 assert e.value.code() == grpc.StatusCode.NOT_FOUND
1150 assert e.value.details() == "Couldn't find that user."
1152 # can't write anything to myself
1153 res = api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user1.id))
1154 assert not res.can_write_friend_reference
1155 assert len(res.available_write_references) == 0
1157 res = api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user2.id))
1158 # can't write friend ref to user2
1159 assert not res.can_write_friend_reference
1160 # none we can write for user2
1161 assert len(res.available_write_references) == 0
1163 res = api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user3.id))
1164 # can't write friend ref to user3
1165 assert not res.can_write_friend_reference
1166 # can write one reference because we hosted user3
1167 assert len(res.available_write_references) == 1
1168 w = res.available_write_references[0]
1169 assert w.host_request_id == hr3
1170 assert w.reference_type == references_pb2.REFERENCE_TYPE_HOSTED
1171 assert now() + timedelta(days=6) <= to_aware_datetime(w.time_expires) <= now() + timedelta(days=7)
1173 res = api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user4.id))
1174 # can write friend ref to user4
1175 assert res.can_write_friend_reference
1176 # can write one reference because we surfed with user4
1177 assert len(res.available_write_references) == 1
1178 w = res.available_write_references[0]
1179 assert w.host_request_id == hr4
1180 assert w.reference_type == references_pb2.REFERENCE_TYPE_SURFED
1181 assert now() + timedelta(days=9) <= to_aware_datetime(w.time_expires) <= now() + timedelta(days=10)
1183 # can't write a req if we indicated we didn't meet up
1184 res = api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user8.id))
1185 assert len(res.available_write_references) == 0
1186 res = api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user9.id))
1187 assert len(res.available_write_references) == 0
1189 # can still write ref if the other person indicated we didn't meet up
1190 # surfed with them
1191 res = api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user10.id))
1192 assert len(res.available_write_references) == 1
1193 w = res.available_write_references[0]
1194 assert w.host_request_id == hr6
1195 assert w.reference_type == references_pb2.REFERENCE_TYPE_SURFED
1196 assert now() + timedelta(days=10) <= to_aware_datetime(w.time_expires) <= now() + timedelta(days=11)
1197 # hosted them
1198 res = api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user11.id))
1199 assert len(res.available_write_references) == 1
1200 w = res.available_write_references[0]
1201 assert w.host_request_id == hr7
1202 assert w.reference_type == references_pb2.REFERENCE_TYPE_HOSTED
1203 assert now() + timedelta(days=11) <= to_aware_datetime(w.time_expires) <= now() + timedelta(days=12)
1205 # finally check the general list
1206 res = api.ListPendingReferencesToWrite(empty_pb2.Empty())
1207 assert len(res.pending_references) == 4
1208 w = res.pending_references[0]
1209 assert w.host_request_id == hr3
1210 assert w.reference_type == references_pb2.REFERENCE_TYPE_HOSTED
1211 assert now() + timedelta(days=6) <= to_aware_datetime(w.time_expires) <= now() + timedelta(days=7)
1212 w = res.pending_references[1]
1213 assert w.host_request_id == hr4
1214 assert w.reference_type == references_pb2.REFERENCE_TYPE_SURFED
1215 assert now() + timedelta(days=9) <= to_aware_datetime(w.time_expires) <= now() + timedelta(days=10)
1216 w = res.pending_references[2]
1217 assert w.host_request_id == hr6
1218 assert w.reference_type == references_pb2.REFERENCE_TYPE_SURFED
1219 assert now() + timedelta(days=10) <= to_aware_datetime(w.time_expires) <= now() + timedelta(days=11)
1220 w = res.pending_references[3]
1221 assert w.host_request_id == hr7
1222 assert w.reference_type == references_pb2.REFERENCE_TYPE_HOSTED
1223 assert now() + timedelta(days=11) <= to_aware_datetime(w.time_expires) <= now() + timedelta(days=12)
1225 with account_session(token1) as account:
1226 reminders = account.GetReminders(empty_pb2.Empty()).reminders
1227 assert [reminder.WhichOneof("reminder") for reminder in reminders] == [
1228 "write_reference_reminder",
1229 "write_reference_reminder",
1230 "write_reference_reminder",
1231 "write_reference_reminder",
1232 ]
1233 assert reminders[0].write_reference_reminder.host_request_id == hr3
1234 assert reminders[0].write_reference_reminder.reference_type == references_pb2.REFERENCE_TYPE_HOSTED
1235 assert reminders[0].write_reference_reminder.other_user.user_id == user3.id
1236 assert reminders[1].write_reference_reminder.host_request_id == hr4
1237 assert reminders[1].write_reference_reminder.reference_type == references_pb2.REFERENCE_TYPE_SURFED
1238 assert reminders[1].write_reference_reminder.other_user.user_id == user4.id
1239 assert reminders[2].write_reference_reminder.host_request_id == hr6
1240 assert reminders[2].write_reference_reminder.reference_type == references_pb2.REFERENCE_TYPE_SURFED
1241 assert reminders[2].write_reference_reminder.other_user.user_id == user10.id
1242 assert reminders[3].write_reference_reminder.host_request_id == hr7
1243 assert reminders[3].write_reference_reminder.reference_type == references_pb2.REFERENCE_TYPE_HOSTED
1244 assert reminders[3].write_reference_reminder.other_user.user_id == user11.id
1247@pytest.mark.parametrize("hs", ["host", "surfer"])
1248def test_regression_disappearing_refs(db, hs, moderator):
1249 """
1250 Roughly the reproduction steps are:
1251 * Send a host request, then have both host and surfer accept
1252 * Wait for it to elapse (or hack it with SQL like what you told me to do)
1253 * On the surfer account, leave a reference
1254 * Then on the host account, the option to leave a reference is then not available
1255 """
1256 user1, token1 = generate_user()
1257 user2, token2 = generate_user()
1258 req_start = (today() + timedelta(days=2)).isoformat()
1259 req_end = (today() + timedelta(days=3)).isoformat()
1260 with requests_session(token1) as api:
1261 res = api.CreateHostRequest(
1262 requests_pb2.CreateHostRequestReq(
1263 host_user_id=user2.id, from_date=req_start, to_date=req_end, text=valid_request_text()
1264 )
1265 )
1266 host_request_id = res.host_request_id
1268 moderator.approve_host_request(host_request_id)
1270 assert (
1271 api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_sent=True))
1272 .host_requests[0]
1273 .latest_message.text.text
1274 == valid_request_text()
1275 )
1277 with requests_session(token2) as api:
1278 api.RespondHostRequest(
1279 requests_pb2.RespondHostRequestReq(
1280 host_request_id=host_request_id, status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED
1281 )
1282 )
1284 with requests_session(token1) as api:
1285 api.RespondHostRequest(
1286 requests_pb2.RespondHostRequestReq(
1287 host_request_id=host_request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CONFIRMED
1288 )
1289 )
1291 refresh_materialized_views_rapid(empty_pb2.Empty())
1293 with references_session(token1) as api:
1294 res = api.ListPendingReferencesToWrite(empty_pb2.Empty())
1295 assert len(res.pending_references) == 0
1296 res = api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user2.id))
1297 assert len(res.available_write_references) == 0
1299 with references_session(token2) as api:
1300 res = api.ListPendingReferencesToWrite(empty_pb2.Empty())
1301 assert len(res.pending_references) == 0
1302 res = api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user1.id))
1303 assert len(res.available_write_references) == 0
1305 # hack the time backwards
1306 hack_req_start = today() - timedelta(days=10) + timedelta(days=2)
1307 hack_req_end = today() - timedelta(days=10) + timedelta(days=3)
1308 with session_scope() as session:
1309 host_request = session.execute(select(HostRequest)).scalar_one()
1310 assert host_request.conversation_id == host_request_id
1311 host_request.from_date = hack_req_start
1312 host_request.to_date = hack_req_end
1314 with references_session(token1) as api:
1315 res = api.ListPendingReferencesToWrite(empty_pb2.Empty())
1316 assert len(res.pending_references) == 1
1317 assert res.pending_references[0].host_request_id == host_request_id
1318 assert res.pending_references[0].reference_type == references_pb2.REFERENCE_TYPE_SURFED
1320 res = api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user2.id))
1321 assert len(res.available_write_references) == 1
1322 assert res.available_write_references[0].host_request_id == host_request_id
1323 assert res.available_write_references[0].reference_type == references_pb2.REFERENCE_TYPE_SURFED
1325 with references_session(token2) as api:
1326 res = api.ListPendingReferencesToWrite(empty_pb2.Empty())
1327 assert len(res.pending_references) == 1
1328 assert res.pending_references[0].host_request_id == host_request_id
1329 assert res.pending_references[0].reference_type == references_pb2.REFERENCE_TYPE_HOSTED
1331 res = api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user1.id))
1332 assert len(res.available_write_references) == 1
1333 assert res.available_write_references[0].host_request_id == host_request_id
1334 assert res.available_write_references[0].reference_type == references_pb2.REFERENCE_TYPE_HOSTED
1336 if hs == "host":
1337 with references_session(token2) as api:
1338 api.WriteHostRequestReference(
1339 references_pb2.WriteHostRequestReferenceReq(
1340 host_request_id=host_request_id,
1341 text="Good stuff",
1342 was_appropriate=True,
1343 rating=0.86,
1344 )
1345 )
1347 with references_session(token2) as api:
1348 res = api.ListPendingReferencesToWrite(empty_pb2.Empty())
1349 assert len(res.pending_references) == 0
1351 res = api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user2.id))
1352 assert len(res.available_write_references) == 0
1354 with references_session(token1) as api:
1355 res = api.ListPendingReferencesToWrite(empty_pb2.Empty())
1356 assert len(res.pending_references) == 1
1357 assert res.pending_references[0].host_request_id == host_request_id
1358 assert res.pending_references[0].reference_type == references_pb2.REFERENCE_TYPE_SURFED
1360 res = api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user2.id))
1361 assert len(res.available_write_references) == 1
1362 assert res.available_write_references[0].host_request_id == host_request_id
1363 assert res.available_write_references[0].reference_type == references_pb2.REFERENCE_TYPE_SURFED
1364 else:
1365 with references_session(token1) as api:
1366 api.WriteHostRequestReference(
1367 references_pb2.WriteHostRequestReferenceReq(
1368 host_request_id=host_request_id,
1369 text="Good stuff",
1370 was_appropriate=True,
1371 rating=0.86,
1372 )
1373 )
1375 with references_session(token1) 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=user1.id))
1380 assert len(res.available_write_references) == 0
1382 with references_session(token2) as api:
1383 res = api.ListPendingReferencesToWrite(empty_pb2.Empty())
1384 assert len(res.pending_references) == 1
1385 assert res.pending_references[0].host_request_id == host_request_id
1386 assert res.pending_references[0].reference_type == references_pb2.REFERENCE_TYPE_HOSTED
1388 res = api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user1.id))
1389 assert len(res.available_write_references) == 1
1390 assert res.available_write_references[0].host_request_id == host_request_id
1391 assert res.available_write_references[0].reference_type == references_pb2.REFERENCE_TYPE_HOSTED
1394def test_WriteFriendReference_creates_shadowed_moderation_state(db):
1395 """New friend references start out shadowed and are not visible to non-authors."""
1396 user1, token1 = generate_user()
1397 user2, token2 = generate_user()
1398 user3, token3 = generate_user()
1399 make_friends(user1, user2)
1401 with references_session(token1) as api:
1402 ref = api.WriteFriendReference(
1403 references_pb2.WriteFriendReferenceReq(
1404 to_user_id=user2.id, text="Nice friend", was_appropriate=True, rating=0.9
1405 )
1406 )
1408 with session_scope() as session:
1409 reference = session.execute(select(Reference).where(Reference.id == ref.reference_id)).scalar_one()
1410 assert reference.moderation_state.visibility == ModerationVisibility.shadowed
1411 assert reference.moderation_state.object_type == ModerationObjectType.reference
1412 assert reference.moderation_state.object_id == reference.id
1414 # Author can see their own shadowed reference.
1415 with references_session(token1) as api:
1416 res = api.ListReferences(references_pb2.ListReferencesReq(from_user_id=user1.id))
1417 assert [r.reference_id for r in res.references] == [ref.reference_id]
1419 # Non-author user cannot see it while shadowed.
1420 with references_session(token3) as api:
1421 res = api.ListReferences(references_pb2.ListReferencesReq(to_user_id=user2.id))
1422 assert [r.reference_id for r in res.references] == []
1425def test_reference_hidden_via_ums_disappears_from_listings(db, moderator):
1426 """Hiding a reference through UMS removes it from listings, including for the author."""
1427 user1, token1 = generate_user()
1428 user2, token2 = generate_user()
1429 user3, token3 = generate_user()
1430 make_friends(user1, user2)
1432 with references_session(token1) as api:
1433 ref = api.WriteFriendReference(
1434 references_pb2.WriteFriendReferenceReq(
1435 to_user_id=user2.id, text="Visible for now", was_appropriate=True, rating=0.9
1436 )
1437 )
1438 moderator.approve_reference(ref.reference_id)
1440 with references_session(token3) as api:
1441 assert api.ListReferences(references_pb2.ListReferencesReq(to_user_id=user2.id)).references
1443 # Hide the reference via the moderation API.
1444 with real_moderation_session(moderator.token) as mod_api:
1445 state_res = mod_api.GetModerationState(
1446 moderation_pb2.GetModerationStateReq(
1447 object_type=moderation_pb2.MODERATION_OBJECT_TYPE_REFERENCE,
1448 object_id=ref.reference_id,
1449 )
1450 )
1451 mod_api.ModerateContent(
1452 moderation_pb2.ModerateContentReq(
1453 moderation_state_id=state_res.moderation_state.moderation_state_id,
1454 action=moderation_pb2.MODERATION_ACTION_HIDE,
1455 visibility=moderation_pb2.MODERATION_VISIBILITY_HIDDEN,
1456 reason="test",
1457 )
1458 )
1460 # Hidden references are invisible to the author and to other users.
1461 with references_session(token1) as api:
1462 assert not api.ListReferences(references_pb2.ListReferencesReq(from_user_id=user1.id)).references
1463 with references_session(token3) as api:
1464 assert not api.ListReferences(references_pb2.ListReferencesReq(to_user_id=user2.id)).references