Coverage for app/backend/src/tests/test_references.py: 99%
747 statements
« prev ^ index » next coverage.py v7.16.1, created at 2026-09-19 15:47 +0000
« prev ^ index » next coverage.py v7.16.1, created at 2026-09-19 15:47 +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 Node,
23 NodeType,
24 Reference,
25 ReferenceType,
26 User,
27)
28from couchers.models.public_trips import PublicTrip, PublicTripStatus
29from couchers.moderation.utils import create_moderation
30from couchers.proto import api_pb2, messages_pb2, moderation_pb2, references_pb2, requests_pb2
31from couchers.utils import create_coordinate, create_polygon_lat_lng, now, to_aware_datetime, to_multi, today
32from tests.fixtures.db import generate_user, make_friends, make_user_block
33from tests.fixtures.misc import EmailCollector, PushCollector
34from tests.fixtures.sessions import (
35 account_session,
36 api_session,
37 real_moderation_session,
38 references_session,
39 requests_session,
40)
41from tests.test_requests import valid_request_text
44def create_public_trip(session: Session, user_id: int, from_date: date, to_date: date) -> int:
45 node = session.execute(select(Node).limit(1)).scalar_one_or_none()
46 if node is None: 46 ↛ 53line 46 didn't jump to line 53 because the condition on line 46 was always true
47 node = Node(
48 geom=to_multi(create_polygon_lat_lng([[0, 0], [0, 2], [2, 2], [2, 0], [0, 0]])),
49 node_type=NodeType.locality,
50 )
51 session.add(node)
52 session.flush()
53 moderation_state = ModerationState(
54 object_type=ModerationObjectType.public_trip,
55 object_id=0,
56 visibility=ModerationVisibility.visible,
57 )
58 session.add(moderation_state)
59 session.flush()
60 trip = PublicTrip(
61 user_id=user_id,
62 node_id=node.id,
63 from_date=from_date,
64 to_date=to_date,
65 description="Looking for a host!",
66 status=PublicTripStatus.searching_for_host,
67 moderation_state_id=moderation_state.id,
68 )
69 session.add(trip)
70 session.flush()
71 moderation_state.object_id = trip.id
72 return trip.id
75def create_host_request(
76 session: Session,
77 surfer_user_id: int,
78 host_user_id: int,
79 host_request_age: timedelta = timedelta(days=15),
80 status: HostRequestStatus = HostRequestStatus.confirmed,
81 host_reason_didnt_meetup: str | None = None,
82 surfer_reason_didnt_meetup: str | None = None,
83 is_offer: bool = False,
84) -> int:
85 """
86 Create a host request that's `host_request_age` old
88 If `is_offer`, it's an offer on a public trip, so the host initiated it and the surfer received it.
89 """
90 from_date = today() - host_request_age - timedelta(days=2)
91 to_date = today() - host_request_age
92 fake_created = now() - host_request_age - timedelta(days=3)
94 if is_offer:
95 initiator_user_id, recipient_user_id = host_user_id, surfer_user_id
96 initiator_reason, recipient_reason = host_reason_didnt_meetup, surfer_reason_didnt_meetup
97 else:
98 initiator_user_id, recipient_user_id = surfer_user_id, host_user_id
99 initiator_reason, recipient_reason = surfer_reason_didnt_meetup, host_reason_didnt_meetup
101 public_trip_id = create_public_trip(session, surfer_user_id, from_date, to_date) if is_offer else None
103 conversation = Conversation()
104 session.add(conversation)
105 session.flush()
107 msg1 = Message(
108 conversation_id=conversation.id,
109 author_id=initiator_user_id,
110 message_type=MessageType.chat_created,
111 )
112 msg1.time = fake_created + timedelta(seconds=1)
113 session.add(msg1)
115 msg2 = Message(
116 conversation_id=conversation.id,
117 author_id=initiator_user_id,
118 text="Hi, I'm requesting to be hosted." if not is_offer else "Hi, I'd love to host you.",
119 message_type=MessageType.text,
120 )
121 msg2.time = fake_created + timedelta(seconds=2)
122 session.add(msg2)
123 session.flush()
125 moderation_state = create_moderation(
126 session,
127 ModerationObjectType.host_request,
128 conversation.id,
129 initiator_user_id,
130 )
132 host_request = HostRequest(
133 conversation_id=conversation.id,
134 initiator_user_id=initiator_user_id,
135 recipient_user_id=recipient_user_id,
136 from_date=from_date,
137 to_date=to_date,
138 status=status,
139 initiator_last_seen_message_id=msg2.id,
140 recipient_reason_didnt_meetup=recipient_reason,
141 initiator_reason_didnt_meetup=initiator_reason,
142 hosting_city="Test City",
143 hosting_location=create_coordinate(0, 0),
144 hosting_radius=10,
145 moderation_state_id=moderation_state.id,
146 public_trip_id=public_trip_id,
147 )
148 session.add(host_request)
149 session.commit()
150 return host_request.conversation_id
153def create_host_request_by_date(
154 session: Session,
155 surfer_user_id: int,
156 host_user_id: int,
157 from_date: date,
158 to_date: date,
159 status: HostRequestStatus,
160 host_sent_request_reminders: int,
161 last_sent_request_reminder_time: datetime,
162) -> int:
163 conversation = Conversation()
164 session.add(conversation)
165 session.flush()
167 msg1 = Message(
168 conversation_id=conversation.id,
169 author_id=surfer_user_id,
170 message_type=MessageType.chat_created,
171 )
172 msg1.time = from_date + timedelta(seconds=1) # type: ignore[assignment]
173 session.add(msg1)
175 # Unused for now, but every host request must have a message.
176 msg2 = Message(
177 conversation_id=conversation.id,
178 author_id=surfer_user_id,
179 text="Hi, I'm requesting to be hosted.",
180 message_type=MessageType.text,
181 )
182 msg2.time = from_date + timedelta(seconds=2) # type: ignore[assignment]
183 session.add(msg2)
184 session.flush()
186 moderation_state = create_moderation(
187 session,
188 ModerationObjectType.host_request,
189 conversation.id,
190 surfer_user_id,
191 )
193 host_request = HostRequest(
194 conversation_id=conversation.id,
195 initiator_user_id=surfer_user_id,
196 recipient_user_id=host_user_id,
197 from_date=from_date,
198 to_date=to_date,
199 status=status,
200 hosting_city="Test City",
201 hosting_location=create_coordinate(0, 0),
202 hosting_radius=10,
203 moderation_state_id=moderation_state.id,
204 )
205 host_request.recipient_sent_request_reminders = host_sent_request_reminders
206 host_request.last_sent_request_reminder_time = last_sent_request_reminder_time
208 session.add(host_request)
209 session.commit()
210 return host_request.conversation_id
213def create_host_reference(
214 session: Session,
215 from_user_id: int,
216 to_user_id: int,
217 reference_age: timedelta,
218 *,
219 surfing: bool = True,
220 host_request_id: int | None = None,
221) -> tuple[int, int]:
222 if host_request_id:
223 actual_host_request_id = host_request_id
224 else:
225 if surfing:
226 actual_host_request_id = host_request_id or create_host_request(
227 session, from_user_id, to_user_id, reference_age + timedelta(days=1)
228 )
229 else:
230 actual_host_request_id = host_request_id or create_host_request(
231 session, to_user_id, from_user_id, reference_age + timedelta(days=1)
232 )
234 host_request = session.execute(
235 select(HostRequest).where(HostRequest.conversation_id == actual_host_request_id)
236 ).scalar_one()
238 if host_request.initiator_user_id == from_user_id:
239 reference_type = ReferenceType.surfed
240 to_user_id = host_request.recipient_user_id
241 assert from_user_id == host_request.initiator_user_id
242 else:
243 reference_type = ReferenceType.hosted
244 to_user_id = host_request.initiator_user_id
245 assert from_user_id == host_request.recipient_user_id
247 moderation_state = ModerationState(
248 object_type=ModerationObjectType.reference,
249 object_id=0, # placeholder, set after Reference flush
250 visibility=ModerationVisibility.visible,
251 )
252 session.add(moderation_state)
253 session.flush()
255 reference = Reference(
256 from_user_id=from_user_id,
257 to_user_id=to_user_id,
258 host_request_id=host_request.conversation_id,
259 text="Dummy reference",
260 rating=0.5,
261 was_appropriate=True,
262 reference_type=reference_type,
263 moderation_state_id=moderation_state.id,
264 )
265 reference.time = now() - reference_age
267 session.add(reference)
268 session.flush()
269 moderation_state.object_id = reference.id
270 session.commit()
271 return reference.id, actual_host_request_id
274def create_friend_reference(session: Session, from_user_id: int, to_user_id: int, reference_age: timedelta) -> int:
275 moderation_state = ModerationState(
276 object_type=ModerationObjectType.reference,
277 object_id=0, # placeholder, set after Reference flush
278 visibility=ModerationVisibility.visible,
279 )
280 session.add(moderation_state)
281 session.flush()
283 reference = Reference(
284 from_user_id=from_user_id,
285 to_user_id=to_user_id,
286 reference_type=ReferenceType.friend,
287 text="Test friend request",
288 rating=0.4,
289 was_appropriate=True,
290 moderation_state_id=moderation_state.id,
291 )
292 reference.time = now() - reference_age
293 session.add(reference)
294 session.flush()
295 moderation_state.object_id = reference.id
296 session.commit()
297 return reference.id
300def test_ListPagination(db):
301 user1, token1 = generate_user()
302 user2, token2 = generate_user()
303 user3, token3 = generate_user()
304 user4, token4 = generate_user()
305 user5, token5 = generate_user()
306 user6, token6 = generate_user()
307 user7, token7 = generate_user()
308 user8, token8 = generate_user()
309 user9, token9 = generate_user()
311 with session_scope() as session:
312 # bidirectional references
313 ref2, hr2 = create_host_reference(session, user2.id, user1.id, timedelta(days=16, seconds=110), surfing=True)
314 ref2b, _ = create_host_reference(
315 session, user1.id, user2.id, timedelta(days=16, seconds=100), host_request_id=hr2
316 )
318 ref3, _ = create_host_reference(session, user3.id, user1.id, timedelta(days=16, seconds=90), surfing=False)
319 ref4, _ = create_host_reference(session, user4.id, user1.id, timedelta(days=16, seconds=80), surfing=True)
320 ref4b = create_friend_reference(session, user1.id, user4.id, timedelta(days=16, seconds=70))
322 ref5, hr5 = create_host_reference(session, user5.id, user1.id, timedelta(days=16, seconds=60), surfing=False)
323 ref5b, _ = create_host_reference(
324 session, user1.id, user5.id, timedelta(days=16, seconds=50), host_request_id=hr5
325 )
327 ref6, _ = create_host_reference(session, user6.id, user1.id, timedelta(days=16, seconds=40), surfing=True)
329 ref7 = create_friend_reference(session, user7.id, user1.id, timedelta(days=16, seconds=30))
331 ref8, _ = create_host_reference(session, user8.id, user1.id, timedelta(days=16, seconds=20), surfing=False)
332 ref9, _ = create_host_reference(session, user9.id, user1.id, timedelta(days=16, seconds=10), surfing=False)
334 # should be visible even under 2 weeks
335 ref7b = create_friend_reference(session, user1.id, user7.id, timedelta(days=9))
337 # hidden because it's less than 2 weeks
338 ref6hidden, _ = create_host_reference(session, user6.id, user1.id, timedelta(days=5), surfing=False)
340 # visible because both were written
341 ref8b, hr8 = create_host_reference(session, user8.id, user1.id, timedelta(days=3, seconds=20), surfing=False)
342 ref8c, _ = create_host_reference(
343 session, user1.id, user8.id, timedelta(days=3, seconds=10), host_request_id=hr8
344 )
346 # note that visibility tests don't really test real logic
348 # these check the right refs are in the right requests and appear in the right order (latest first)
350 with references_session(token2) as api:
351 # written by user1
352 res = api.ListReferences(references_pb2.ListReferencesReq(from_user_id=user1.id, page_size=2))
353 assert [ref.reference_id for ref in res.references] == [ref8c, ref7b]
355 res = api.ListReferences(
356 references_pb2.ListReferencesReq(from_user_id=user1.id, page_token=res.next_page_token, page_size=2)
357 )
358 assert [ref.reference_id for ref in res.references] == [ref5b, ref4b]
360 res = api.ListReferences(
361 references_pb2.ListReferencesReq(from_user_id=user1.id, page_token=res.next_page_token, page_size=2)
362 )
363 assert [ref.reference_id for ref in res.references] == [ref2b]
364 assert not res.next_page_token
366 # received by user1
367 res = api.ListReferences(references_pb2.ListReferencesReq(to_user_id=user1.id, page_size=5))
368 assert [ref.reference_id for ref in res.references] == [ref8b, ref9, ref8, ref7, ref6]
370 res = api.ListReferences(
371 references_pb2.ListReferencesReq(to_user_id=user1.id, page_token=res.next_page_token, page_size=5)
372 )
373 assert [ref.reference_id for ref in res.references] == [ref5, ref4, ref3, ref2]
374 assert not res.next_page_token
376 # same thing but with filters
377 res = api.ListReferences(
378 references_pb2.ListReferencesReq(
379 to_user_id=user1.id,
380 reference_type_filter=[
381 references_pb2.REFERENCE_TYPE_HOSTED,
382 references_pb2.REFERENCE_TYPE_SURFED,
383 references_pb2.REFERENCE_TYPE_FRIEND,
384 ],
385 page_size=5,
386 )
387 )
388 assert [ref.reference_id for ref in res.references] == [ref8b, ref9, ref8, ref7, ref6]
390 res = api.ListReferences(
391 references_pb2.ListReferencesReq(
392 to_user_id=user1.id,
393 reference_type_filter=[
394 references_pb2.REFERENCE_TYPE_HOSTED,
395 references_pb2.REFERENCE_TYPE_SURFED,
396 references_pb2.REFERENCE_TYPE_FRIEND,
397 ],
398 page_token=res.next_page_token,
399 page_size=5,
400 )
401 )
402 assert [ref.reference_id for ref in res.references] == [ref5, ref4, ref3, ref2]
403 assert not res.next_page_token
405 # received hosting references
406 res = api.ListReferences(
407 references_pb2.ListReferencesReq(
408 to_user_id=user1.id, reference_type_filter=[references_pb2.REFERENCE_TYPE_HOSTED], page_size=3
409 )
410 )
411 assert [ref.reference_id for ref in res.references] == [ref8b, ref9, ref8]
413 res = api.ListReferences(
414 references_pb2.ListReferencesReq(
415 to_user_id=user1.id,
416 reference_type_filter=[references_pb2.REFERENCE_TYPE_HOSTED],
417 page_token=res.next_page_token,
418 page_size=3,
419 )
420 )
421 assert [ref.reference_id for ref in res.references] == [ref5, ref3]
422 assert not res.next_page_token
424 # written friend references
425 res = api.ListReferences(
426 references_pb2.ListReferencesReq(
427 from_user_id=user1.id, reference_type_filter=[references_pb2.REFERENCE_TYPE_FRIEND]
428 )
429 )
430 assert [ref.reference_id for ref in res.references] == [ref7b, ref4b]
431 assert not res.next_page_token
433 # written surfing references
434 res = api.ListReferences(
435 references_pb2.ListReferencesReq(
436 from_user_id=user1.id, reference_type_filter=[references_pb2.REFERENCE_TYPE_SURFED]
437 )
438 )
439 assert [ref.reference_id for ref in res.references] == [ref8c, ref5b]
440 assert not res.next_page_token
442 with references_session(token7) as api:
443 # need to set at least one of from or to user
444 with pytest.raises(grpc.RpcError) as e:
445 api.ListReferences(
446 references_pb2.ListReferencesReq(reference_type_filter=[references_pb2.REFERENCE_TYPE_SURFED])
447 )
448 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
449 assert e.value.details() == "You need to specify at least one user."
451 with references_session(token5) as api:
452 # from user1 to user2
453 res = api.ListReferences(references_pb2.ListReferencesReq(from_user_id=user1.id, to_user_id=user2.id))
454 assert [ref.reference_id for ref in res.references] == [ref2b]
455 assert not res.next_page_token
457 # from user5 to user1
458 res = api.ListReferences(references_pb2.ListReferencesReq(from_user_id=user5.id, to_user_id=user1.id))
459 assert [ref.reference_id for ref in res.references] == [ref5]
460 assert not res.next_page_token
462 with api_session(token2) as api:
463 # the reference count matches the 9 references visible in the list; ref6hidden is excluded
464 # because it's still hidden by the reciprocal-reference rule
465 assert api.GetUser(api_pb2.GetUserReq(user=user1.username)).num_references == 9
466 # user8 received ref8c, visible because both references of the pair were written
467 assert api.GetUser(api_pb2.GetUserReq(user=user8.username)).num_references == 1
468 # user6 wrote references but received none
469 assert api.GetUser(api_pb2.GetUserReq(user=user6.username)).num_references == 0
472def test_num_references_matches_visible_list(db):
473 # Regression test: the reference count (User.num_references) must not include references that
474 # are still hidden by the reciprocal-reference rule, otherwise the count leaks the existence
475 # of a hidden reference and disagrees with what ListReferences shows.
476 user1, token1 = generate_user()
477 user2, token2 = generate_user()
479 with session_scope() as session:
480 # user2 wrote user1 a reference for a recent stay; the 2-week window is still open and
481 # user1 has not written their reciprocal reference yet, so this reference is hidden.
482 create_host_reference(session, user2.id, user1.id, timedelta(days=3), surfing=False)
484 with references_session(token2) as api:
485 res = api.ListReferences(references_pb2.ListReferencesReq(to_user_id=user1.id))
486 assert len(res.references) == 0
488 with api_session(token2) as api:
489 res = api.GetUser(api_pb2.GetUserReq(user=user1.username))
490 # count must agree with the (empty) list, not leak the hidden reference
491 assert res.num_references == 0
493 with session_scope() as session:
494 host_request_id = session.execute(select(HostRequest.conversation_id)).scalar_one()
495 # user1 writes the reciprocal reference: now both references become visible
496 create_host_reference(session, user1.id, user2.id, timedelta(days=2), host_request_id=host_request_id)
498 with references_session(token2) as api:
499 res = api.ListReferences(references_pb2.ListReferencesReq(to_user_id=user1.id))
500 assert len(res.references) == 1
502 with api_session(token2) as api:
503 res = api.GetUser(api_pb2.GetUserReq(user=user1.username))
504 assert res.num_references == 1
507def test_ListReference_banned_deleted_users(db):
508 user1, token1 = generate_user()
509 user2, token2 = generate_user()
510 user3, token3 = generate_user()
512 with session_scope() as session:
513 create_friend_reference(session, user2.id, user1.id, timedelta(days=15))
514 create_friend_reference(session, user3.id, user1.id, timedelta(days=16))
515 create_friend_reference(session, user1.id, user2.id, timedelta(days=15))
516 create_friend_reference(session, user1.id, user3.id, timedelta(days=16))
518 with references_session(token1) as api:
519 refs_rec = api.ListReferences(references_pb2.ListReferencesReq(to_user_id=user1.id)).references
520 refs_sent = api.ListReferences(references_pb2.ListReferencesReq(from_user_id=user1.id)).references
521 assert len(refs_rec) == 2
522 assert len(refs_sent) == 2
524 with api_session(token1) as api:
525 assert api.GetUser(api_pb2.GetUserReq(user=user1.username)).num_references == 2
527 # ban user2
528 with session_scope() as session:
529 session.execute(update(User).where(User.username == user2.username).values(banned_at=func.now()))
531 # reference to and from banned user is hidden
532 with references_session(token1) as api:
533 refs_rec = api.ListReferences(references_pb2.ListReferencesReq(to_user_id=user1.id)).references
534 refs_sent = api.ListReferences(references_pb2.ListReferencesReq(from_user_id=user1.id)).references
535 assert len(refs_rec) == 1
536 assert len(refs_sent) == 1
538 with api_session(token1) as api:
539 assert api.GetUser(api_pb2.GetUserReq(user=user1.username)).num_references == 1
541 # delete user3
542 with session_scope() as session:
543 session.execute(update(User).where(User.username == user3.username).values(deleted_at=func.now()))
545 # doesn't change; references to and from deleted users remain
546 with references_session(token1) as api:
547 refs_rec = api.ListReferences(references_pb2.ListReferencesReq(to_user_id=user1.id)).references
548 refs_sent = api.ListReferences(references_pb2.ListReferencesReq(from_user_id=user1.id)).references
549 assert len(refs_rec) == 1
550 assert len(refs_sent) == 1
552 with api_session(token1) as api:
553 assert api.GetUser(api_pb2.GetUserReq(user=user1.username)).num_references == 1
556def test_ListReference_blocked_shadowed_users(db):
557 user1, token1 = generate_user()
558 user2, token2 = generate_user()
559 user3, token3 = generate_user()
561 with session_scope() as session:
562 create_friend_reference(session, user2.id, user1.id, timedelta(days=15))
563 create_friend_reference(session, user3.id, user1.id, timedelta(days=16))
565 # user1 blocks user2: reference from blocked user remains in both list and count
566 make_user_block(user1, user2)
568 with references_session(token1) as api:
569 refs_rec = api.ListReferences(references_pb2.ListReferencesReq(to_user_id=user1.id)).references
570 assert len(refs_rec) == 2
572 with api_session(token1) as api:
573 assert api.GetUser(api_pb2.GetUserReq(user=user1.username)).num_references == 2
575 # shadow user3: their reference is hidden from both list and count
576 with session_scope() as session:
577 session.execute(update(User).where(User.username == user3.username).values(shadowed_at=func.now()))
579 with references_session(token1) as api:
580 refs_rec = api.ListReferences(references_pb2.ListReferencesReq(to_user_id=user1.id)).references
581 assert len(refs_rec) == 1
583 with api_session(token1) as api:
584 assert api.GetUser(api_pb2.GetUserReq(user=user1.username)).num_references == 1
586 # the shadowed author still sees their own reference in both, so the shadow ban isn't leaked
587 with references_session(token3) as api:
588 refs_rec = api.ListReferences(references_pb2.ListReferencesReq(to_user_id=user1.id)).references
589 assert len(refs_rec) == 2
591 with api_session(token3) as api:
592 assert api.GetUser(api_pb2.GetUserReq(user=user1.username)).num_references == 2
595def test_WriteFriendReference(db, moderator):
596 user1, token1 = generate_user()
597 user2, token2 = generate_user()
598 user3, token3 = generate_user()
600 # Make user1 and user2 friends
601 make_friends(user1, user2)
603 with references_session(token1) as api:
604 # can write normal friend reference
605 res = api.WriteFriendReference(
606 references_pb2.WriteFriendReferenceReq(
607 to_user_id=user2.id,
608 text="A test reference",
609 was_appropriate=True,
610 rating=0.5,
611 )
612 )
613 assert res.from_user_id == user1.id
614 assert res.to_user_id == user2.id
615 assert res.reference_type == references_pb2.REFERENCE_TYPE_FRIEND
616 assert res.text == "A test reference"
617 assert now() - timedelta(hours=24) <= to_aware_datetime(res.written_time) <= now()
618 assert not res.host_request_id
620 moderator.approve_reference(res.reference_id)
622 with references_session(token3) as api:
623 # check it shows up
624 res = api.ListReferences(
625 references_pb2.ListReferencesReq(
626 from_user_id=user1.id, to_user_id=user2.id, reference_type_filter=[references_pb2.REFERENCE_TYPE_FRIEND]
627 )
628 )
629 assert len(res.references) == 1
630 ref = res.references[0]
631 assert ref.from_user_id == user1.id
632 assert ref.to_user_id == user2.id
633 assert ref.reference_type == references_pb2.REFERENCE_TYPE_FRIEND
634 assert ref.text == "A test reference"
635 assert now() - timedelta(hours=24) <= to_aware_datetime(ref.written_time) <= now()
636 assert not ref.host_request_id
638 with references_session(token1) as api:
639 # can't write a second friend reference
640 with pytest.raises(grpc.RpcError) as e:
641 api.WriteFriendReference(
642 references_pb2.WriteFriendReferenceReq(
643 to_user_id=user2.id,
644 text="A test reference",
645 was_appropriate=True,
646 rating=0.5,
647 )
648 )
649 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
650 assert e.value.details() == "Reference already given."
652 with references_session(token2) as api:
653 # can't write a reference about yourself
654 with pytest.raises(grpc.RpcError) as e:
655 api.WriteFriendReference(
656 references_pb2.WriteFriendReferenceReq(
657 to_user_id=user2.id,
658 text="I'm really awesome",
659 was_appropriate=True,
660 rating=1.0,
661 )
662 )
663 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
664 assert e.value.details() == "You can't refer yourself."
667def test_WriteFriendReference_with_empty_text(db):
668 user1, token1 = generate_user()
669 user2, token2 = generate_user()
671 with references_session(token1) as api:
672 with pytest.raises(grpc.RpcError) as e:
673 api.WriteFriendReference(
674 references_pb2.WriteFriendReferenceReq(to_user_id=user2.id, text=" ", was_appropriate=True, rating=0.8)
675 )
676 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
677 assert e.value.details() == "The text of a reference must not be empty"
680def test_WriteFriendReference_with_private_text(
681 db, email_collector: EmailCollector, push_collector: PushCollector, moderator
682):
683 user1, token1 = generate_user()
684 user2, token2 = generate_user()
686 # Make users friends
687 make_friends(user1, user2)
689 with references_session(token1) as api:
690 ref = api.WriteFriendReference(
691 references_pb2.WriteFriendReferenceReq(
692 to_user_id=user2.id,
693 text="They were nice!",
694 was_appropriate=True,
695 rating=0.6,
696 private_text="A bit of an odd ball, but a nice person nonetheless.",
697 )
698 )
699 # Approve before patches/jobs exit so the pending notification is delivered while mocked.
700 moderator.approve_reference(ref.reference_id)
702 # make sure an email was sent to the user receiving the ref as well as the mods
703 email_collector.pop_for_reports(last=True)
704 email = email_collector.pop_for_recipient(user2.email, last=True)
705 assert email.subject == f"[TEST] You've received a friend reference from {user1.name}!"
706 assert email.recipient == user2.email
708 push = push_collector.pop_for_user(user2.id, last=True)
709 assert push.content.title == f"New friend reference from {user1.name}"
710 assert push.content.body == "They were nice!"
713def test_WriteFriendReference_requires_friendship(db):
714 """Test that users must be friends to write friend references"""
715 user1, token1 = generate_user()
716 user2, token2 = generate_user()
718 # Try to write friend reference without being friends
719 with references_session(token1) as api:
720 with pytest.raises(grpc.RpcError) as e:
721 api.WriteFriendReference(
722 references_pb2.WriteFriendReferenceReq(
723 to_user_id=user2.id,
724 text="A test reference",
725 was_appropriate=True,
726 rating=0.5,
727 )
728 )
729 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
730 assert e.value.details() == "You can only write friend references for confirmed friends."
732 # Now make them friends
733 make_friends(user1, user2)
735 # Should now be able to write a reference
736 with references_session(token1) as api:
737 res = api.WriteFriendReference(
738 references_pb2.WriteFriendReferenceReq(
739 to_user_id=user2.id,
740 text="A test reference",
741 was_appropriate=True,
742 rating=0.5,
743 )
744 )
745 assert res.from_user_id == user1.id
746 assert res.to_user_id == user2.id
748 # Test the unfriending scenario: delete the friendship
749 with session_scope() as session:
750 # Change the friendship status to cancelled (simulating unfriending)
751 session.execute(
752 update(FriendRelationship)
753 .where(FriendRelationship.from_user_id == user1.id, FriendRelationship.to_user_id == user2.id)
754 .values(status=FriendStatus.cancelled)
755 )
757 # Try to write another friend reference after unfriending
758 # (Note: This assumes user1 didn't already write a reference, or we test with user2 writing to user1)
759 with references_session(token2) as api:
760 with pytest.raises(grpc.RpcError) as e:
761 api.WriteFriendReference(
762 references_pb2.WriteFriendReferenceReq(
763 to_user_id=user1.id,
764 text="Another test reference",
765 was_appropriate=True,
766 rating=0.8,
767 )
768 )
769 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
770 assert e.value.details() == "You can only write friend references for confirmed friends."
773def test_host_request_states_references(db, moderator):
774 user1, token1 = generate_user()
775 user2, token2 = generate_user()
777 with session_scope() as session:
778 # can't write ref
779 hr1 = create_host_request(session, user2.id, user1.id, timedelta(days=10), status=HostRequestStatus.pending)
780 # can write ref
781 hr2 = create_host_request(session, user2.id, user1.id, timedelta(days=10), status=HostRequestStatus.accepted)
782 # can't write ref
783 hr3 = create_host_request(session, user2.id, user1.id, timedelta(days=10), status=HostRequestStatus.rejected)
784 # can write ref
785 hr4 = create_host_request(session, user2.id, user1.id, timedelta(days=10), status=HostRequestStatus.confirmed)
786 # can't write ref
787 hr5 = create_host_request(session, user2.id, user1.id, timedelta(days=10), status=HostRequestStatus.cancelled)
789 # Approve host requests so both participants can see them
790 moderator.approve_host_request(hr1)
791 moderator.approve_host_request(hr2)
792 moderator.approve_host_request(hr3)
793 moderator.approve_host_request(hr4)
794 moderator.approve_host_request(hr5)
796 with references_session(token1) as api:
797 # pending
798 api.WriteHostRequestReference(
799 references_pb2.WriteHostRequestReferenceReq(
800 host_request_id=hr2,
801 text="Should work!",
802 was_appropriate=True,
803 rating=0.9,
804 )
805 )
807 # accepted
808 api.WriteHostRequestReference(
809 references_pb2.WriteHostRequestReferenceReq(
810 host_request_id=hr4,
811 text="Should work!",
812 was_appropriate=True,
813 rating=0.9,
814 )
815 )
817 # rejected
818 with pytest.raises(grpc.RpcError) as e:
819 api.WriteHostRequestReference(
820 references_pb2.WriteHostRequestReferenceReq(
821 host_request_id=hr1,
822 text="Shouldn't work...",
823 was_appropriate=True,
824 rating=0.9,
825 )
826 )
827 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
828 assert e.value.details() == "You can't write a reference for that host request, or it wasn't found."
830 # confirmed
831 with pytest.raises(grpc.RpcError) as e:
832 api.WriteHostRequestReference(
833 references_pb2.WriteHostRequestReferenceReq(
834 host_request_id=hr3,
835 text="Shouldn't work...",
836 was_appropriate=True,
837 rating=0.9,
838 )
839 )
840 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
841 assert e.value.details() == "You can't write a reference for that host request, or it wasn't found."
843 # cancelled
844 with pytest.raises(grpc.RpcError) as e:
845 api.WriteHostRequestReference(
846 references_pb2.WriteHostRequestReferenceReq(
847 host_request_id=hr5,
848 text="Shouldn't work...",
849 was_appropriate=True,
850 rating=0.9,
851 )
852 )
853 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
854 assert e.value.details() == "You can't write a reference for that host request, or it wasn't found."
857def test_public_trip_offer_reference_roles(db, moderator):
858 """An offer reverses initiator/recipient, so the reference types have to come from the stay role."""
859 surfer, surfer_token = generate_user()
860 host, host_token = generate_user()
862 with session_scope() as session:
863 host_request_id = create_host_request(session, surfer.id, host.id, timedelta(days=7), is_offer=True)
865 moderator.approve_host_request(host_request_id)
866 # ListPendingReferencesToWrite reads the other user out of the LiteUser matview
867 refresh_materialized_views_rapid(empty_pb2.Empty())
869 with references_session(surfer_token) as api:
870 available = api.AvailableWriteReferences(
871 references_pb2.AvailableWriteReferencesReq(to_user_id=host.id)
872 ).available_write_references
873 assert [r.reference_type for r in available] == [references_pb2.REFERENCE_TYPE_SURFED]
874 pending = api.ListPendingReferencesToWrite(empty_pb2.Empty()).pending_references
875 assert [r.reference_type for r in pending] == [references_pb2.REFERENCE_TYPE_SURFED]
877 api.WriteHostRequestReference(
878 references_pb2.WriteHostRequestReferenceReq(
879 host_request_id=host_request_id,
880 text="Thanks for having me!",
881 was_appropriate=True,
882 rating=0.9,
883 )
884 )
886 with references_session(host_token) as api:
887 available = api.AvailableWriteReferences(
888 references_pb2.AvailableWriteReferencesReq(to_user_id=surfer.id)
889 ).available_write_references
890 assert [r.reference_type for r in available] == [references_pb2.REFERENCE_TYPE_HOSTED]
891 pending = api.ListPendingReferencesToWrite(empty_pb2.Empty()).pending_references
892 assert [r.reference_type for r in pending] == [references_pb2.REFERENCE_TYPE_HOSTED]
894 api.WriteHostRequestReference(
895 references_pb2.WriteHostRequestReferenceReq(
896 host_request_id=host_request_id,
897 text="Great guest!",
898 was_appropriate=True,
899 rating=0.9,
900 )
901 )
903 with session_scope() as session:
904 references = {
905 reference.from_user_id: reference
906 for reference in session.execute(
907 select(Reference).where(Reference.host_request_id == host_request_id)
908 ).scalars()
909 }
910 assert references[surfer.id].reference_type == ReferenceType.surfed
911 assert references[surfer.id].to_user_id == host.id
912 assert references[host.id].reference_type == ReferenceType.hosted
913 assert references[host.id].to_user_id == surfer.id
916def test_WriteHostRequestReference(db, moderator):
917 user1, token1 = generate_user()
918 user2, token2 = generate_user()
919 user3, token3 = generate_user()
920 user4, token4 = generate_user()
922 with session_scope() as session:
923 # too old
924 hr1 = create_host_request(session, user3.id, user1.id, timedelta(days=20))
925 # valid host req, surfer said we didn't show up but we can still write a req
926 hr2 = create_host_request(session, user3.id, user1.id, timedelta(days=10), surfer_reason_didnt_meetup="No show")
927 # valid surfing req
928 hr3 = create_host_request(session, user1.id, user3.id, timedelta(days=7))
929 # not yet complete
930 hr4 = create_host_request(session, user2.id, user1.id, timedelta(days=1), status=HostRequestStatus.pending)
931 # we indicated we didn't meet
932 hr5 = create_host_request(session, user4.id, user1.id, timedelta(days=7), host_reason_didnt_meetup="")
933 # we will indicate we didn't meet
934 hr6 = create_host_request(session, user4.id, user1.id, timedelta(days=8))
936 # Approve host requests so both participants can see them
937 moderator.approve_host_request(hr1)
938 moderator.approve_host_request(hr2)
939 moderator.approve_host_request(hr3)
940 moderator.approve_host_request(hr4)
941 moderator.approve_host_request(hr5)
942 moderator.approve_host_request(hr6)
944 with references_session(token3) as api:
945 # can write for this one
946 api.WriteHostRequestReference(
947 references_pb2.WriteHostRequestReferenceReq(
948 host_request_id=hr3,
949 text="Should work!",
950 was_appropriate=True,
951 rating=0.9,
952 )
953 )
955 with references_session(token1) as api:
956 # can't write reference for a HR that's not yet finished
957 with pytest.raises(grpc.RpcError) as e:
958 api.WriteHostRequestReference(
959 references_pb2.WriteHostRequestReferenceReq(
960 host_request_id=hr4,
961 text="Shouldn't work...",
962 was_appropriate=True,
963 rating=0.9,
964 )
965 )
966 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
967 assert e.value.details() == "You can't write a reference for that host request, or it wasn't found."
969 # can't write reference that's more than 2 weeks old
970 with pytest.raises(grpc.RpcError) as e:
971 api.WriteHostRequestReference(
972 references_pb2.WriteHostRequestReferenceReq(
973 host_request_id=hr1,
974 text="Shouldn't work...",
975 was_appropriate=True,
976 rating=0.9,
977 )
978 )
979 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
980 assert e.value.details() == "You can't write a reference for that host request, or it wasn't found."
982 # can write for this one
983 api.WriteHostRequestReference(
984 references_pb2.WriteHostRequestReferenceReq(
985 host_request_id=hr2,
986 text="Should work!",
987 was_appropriate=True,
988 rating=0.9,
989 )
990 )
992 # but can't write a second one for the same one
993 with pytest.raises(grpc.RpcError) as e:
994 api.WriteHostRequestReference(
995 references_pb2.WriteHostRequestReferenceReq(
996 host_request_id=hr2,
997 text="Shouldn't work...",
998 was_appropriate=True,
999 rating=0.9,
1000 )
1001 )
1002 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
1003 assert e.value.details() == "Reference already given."
1005 # can write for this one too
1006 api.WriteHostRequestReference(
1007 references_pb2.WriteHostRequestReferenceReq(
1008 host_request_id=hr3,
1009 text="Should work!",
1010 was_appropriate=True,
1011 rating=0.9,
1012 )
1013 )
1015 # can't write reference for a HR that we indicated we didn't show up
1016 with pytest.raises(grpc.RpcError) as e:
1017 api.WriteHostRequestReference(
1018 references_pb2.WriteHostRequestReferenceReq(
1019 host_request_id=hr5,
1020 text="Shouldn't work...",
1021 was_appropriate=True,
1022 rating=0.9,
1023 )
1024 )
1025 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
1026 assert (
1027 e.value.details()
1028 == "You can't write a reference for that host request because you indicated that you didn't meet up."
1029 )
1031 # can't write reference for a HR that we indicate we didn't show up for
1032 api.HostRequestIndicateDidntMeetup(
1033 references_pb2.HostRequestIndicateDidntMeetupReq(
1034 host_request_id=hr6,
1035 reason_didnt_meetup="No clue?",
1036 )
1037 )
1039 with pytest.raises(grpc.RpcError) as e:
1040 api.WriteHostRequestReference(
1041 references_pb2.WriteHostRequestReferenceReq(
1042 host_request_id=hr6,
1043 text="Shouldn't work...",
1044 was_appropriate=True,
1045 rating=0.9,
1046 )
1047 )
1048 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
1049 assert (
1050 e.value.details()
1051 == "You can't write a reference for that host request because you indicated that you didn't meet up."
1052 )
1054 with references_session(token4) as api:
1055 # they can still write one
1056 api.WriteHostRequestReference(
1057 references_pb2.WriteHostRequestReferenceReq(
1058 host_request_id=hr6,
1059 text="Should work!",
1060 was_appropriate=True,
1061 rating=0.9,
1062 )
1063 )
1066def test_WriteHostRequestReference_private_text(
1067 db, email_collector: EmailCollector, push_collector: PushCollector, moderator
1068):
1069 user1, token1 = generate_user()
1070 user2, token2 = generate_user()
1072 with session_scope() as session:
1073 hr = create_host_request(session, user1.id, user2.id, timedelta(days=10))
1074 moderator.approve_host_request(hr)
1076 with references_session(token1) as api:
1077 ref = api.WriteHostRequestReference(
1078 references_pb2.WriteHostRequestReferenceReq(
1079 host_request_id=hr,
1080 text="Should work!",
1081 was_appropriate=True,
1082 rating=0.9,
1083 private_text="Something",
1084 )
1085 )
1086 # Approve before patches/jobs exit so the pending notification is delivered while mocked.
1087 moderator.approve_reference(ref.reference_id)
1089 # make sure an email was sent to the user receiving the ref as well as the mods
1090 email_collector.pop_for_reports(last=True)
1091 email = email_collector.pop_for_recipient(user2.email, last=True)
1092 assert email.subject == f"[TEST] You've received a reference from {user1.name}!"
1093 assert email.recipient == user2.email
1095 push = push_collector.pop_for_user(user2.id, last=True)
1096 assert push.content.title == f"New reference from {user1.name}"
1097 assert push.content.body == f"{user1.name} left you a reference, now it's your turn to write theirs!"
1100def test_GetHostRequestReferenceStatus(db, moderator):
1101 user1, token1 = generate_user()
1102 user2, token2 = generate_user()
1104 # user1 writes; RPC returns has_given True
1105 with session_scope() as session:
1106 hr1 = create_host_request(session, user1.id, user2.id, timedelta(days=7))
1107 moderator.approve_host_request(hr1)
1108 with references_session(token1) as api:
1109 api.WriteHostRequestReference(
1110 references_pb2.WriteHostRequestReferenceReq(
1111 host_request_id=hr1, text="Great stay!", was_appropriate=True, rating=0.9
1112 )
1113 )
1114 res = api.GetHostRequestReferenceStatus(references_pb2.GetHostRequestReferenceStatusReq(host_request_id=hr1))
1115 assert res.has_given is True
1117 # false: no reference written yet
1118 with session_scope() as session:
1119 hr2 = create_host_request(session, user1.id, user2.id, timedelta(days=7))
1120 moderator.approve_host_request(hr2)
1121 with references_session(token1) as api:
1122 res = api.GetHostRequestReferenceStatus(references_pb2.GetHostRequestReferenceStatusReq(host_request_id=hr2))
1123 assert res.has_given is False
1125 # false: other user wrote a reference
1126 with session_scope() as session:
1127 hr3 = create_host_request(session, user1.id, user2.id, timedelta(days=7))
1128 moderator.approve_host_request(hr3)
1129 with references_session(token2) as api:
1130 api.WriteHostRequestReference(
1131 references_pb2.WriteHostRequestReferenceReq(
1132 host_request_id=hr3, text="Lovely guest!", was_appropriate=True, rating=0.95
1133 )
1134 )
1135 with references_session(token1) as api:
1136 res = api.GetHostRequestReferenceStatus(references_pb2.GetHostRequestReferenceStatusReq(host_request_id=hr3))
1137 assert res.has_given is False
1139 # false: nonexistent host request id
1140 with references_session(token1) as api:
1141 res = api.GetHostRequestReferenceStatus(references_pb2.GetHostRequestReferenceStatusReq(host_request_id=999999))
1142 assert res.has_given is False
1144 # Additional status flags
1145 with session_scope() as session:
1146 # expired (too old)
1147 hr_expired = create_host_request(session, user2.id, user1.id, timedelta(days=20))
1148 # current user (host) indicated didn't meet up
1149 hr_didnt_stay_host = create_host_request(
1150 session, user2.id, user1.id, timedelta(days=10), host_reason_didnt_meetup=""
1151 )
1152 # other user (surfer) indicated didn't meet up
1153 hr_other_didnt_stay = create_host_request(
1154 session, user2.id, user1.id, timedelta(days=10), surfer_reason_didnt_meetup="No show"
1155 )
1157 moderator.approve_host_request(hr_expired)
1158 moderator.approve_host_request(hr_didnt_stay_host)
1159 moderator.approve_host_request(hr_other_didnt_stay)
1161 # expired: is_expired true, can_write false, didnt_stay false
1162 with references_session(token1) as api:
1163 res = api.GetHostRequestReferenceStatus(
1164 references_pb2.GetHostRequestReferenceStatusReq(host_request_id=hr_expired)
1165 )
1166 assert res.has_given is False
1167 assert res.is_expired is True
1168 assert res.can_write is False
1169 assert res.didnt_stay is False
1171 # current user indicated didn't meet up: didnt_stay true, can_write false, not expired
1172 with references_session(token1) as api:
1173 res = api.GetHostRequestReferenceStatus(
1174 references_pb2.GetHostRequestReferenceStatusReq(host_request_id=hr_didnt_stay_host)
1175 )
1176 assert res.has_given is False
1177 assert res.is_expired is False
1178 assert res.didnt_stay is True
1179 assert res.can_write is False
1181 # other party indicated didn't meet up: didnt_stay false, can_write true (within window), not expired
1182 with references_session(token1) as api:
1183 res = api.GetHostRequestReferenceStatus(
1184 references_pb2.GetHostRequestReferenceStatusReq(host_request_id=hr_other_didnt_stay)
1185 )
1186 assert res.has_given is False
1187 assert res.is_expired is False
1188 assert res.didnt_stay is False
1189 assert res.can_write is True
1192def test_AvailableWriteReferences_and_ListPendingReferencesToWrite(db, moderator):
1193 user1, token1 = generate_user()
1194 user2, token2 = generate_user()
1195 user3, token3 = generate_user()
1196 user4, token4 = generate_user()
1197 user5, token5 = generate_user(delete_user=True)
1198 user6, token6 = generate_user()
1199 user7, token7 = generate_user()
1200 user8, token8 = generate_user()
1201 user9, token9 = generate_user()
1202 user10, token10 = generate_user()
1203 user11, token11 = generate_user()
1204 make_user_block(user1, user6)
1205 make_user_block(user7, user1)
1207 with session_scope() as session:
1208 # too old
1209 hr1 = create_host_request(session, user3.id, user1.id, timedelta(days=20))
1211 # already wrote friend ref to user3
1212 create_friend_reference(session, user1.id, user3.id, timedelta(days=15, seconds=70))
1214 # already given
1215 _, hr2 = create_host_reference(session, user2.id, user1.id, timedelta(days=10, seconds=110), surfing=True)
1216 create_host_reference(session, user1.id, user2.id, timedelta(days=10, seconds=100), host_request_id=hr2)
1218 # valid hosted
1219 hr3 = create_host_request(session, user3.id, user1.id, timedelta(days=8))
1221 # valid surfed
1222 hr4 = create_host_request(session, user1.id, user4.id, timedelta(days=5))
1224 # not yet complete
1225 hr5 = create_host_request(session, user2.id, user1.id, timedelta(days=2), status=HostRequestStatus.pending)
1227 # already wrote friend ref to user2
1228 create_friend_reference(session, user1.id, user2.id, timedelta(days=1))
1230 # user5 deleted, reference won't show up as pending
1231 hr_user5 = create_host_request(session, user1.id, user5.id, timedelta(days=5))
1233 # user6 blocked, reference won't show up as pending
1234 hr_user6 = create_host_request(session, user1.id, user6.id, timedelta(days=5))
1236 # user7 blocking, reference won't show up as pending
1237 hr_user7 = create_host_request(session, user1.id, user7.id, timedelta(days=5))
1239 # hosted but we indicated we didn't meet up, no reason; should not show up
1240 hr_user8 = create_host_request(session, user8.id, user1.id, timedelta(days=11), host_reason_didnt_meetup="")
1242 # surfed but we indicated we didn't meet up, has reason; should not show up
1243 hr_user9 = create_host_request(
1244 session, user1.id, user9.id, timedelta(days=10), surfer_reason_didnt_meetup="They never showed up!"
1245 )
1247 # surfed but they indicated we didn't meet up, no reason; should show up
1248 hr6 = create_host_request(session, user1.id, user10.id, timedelta(days=4), host_reason_didnt_meetup="")
1250 # hosted but they indicated we didn't meet up, has reason; should show up
1251 hr7 = create_host_request(
1252 session, user11.id, user1.id, timedelta(days=3), surfer_reason_didnt_meetup="They never showed up!!"
1253 )
1255 # Approve all host requests so both participants can see them
1256 moderator.approve_host_request(hr1)
1257 moderator.approve_host_request(hr2)
1258 moderator.approve_host_request(hr3)
1259 moderator.approve_host_request(hr4)
1260 moderator.approve_host_request(hr5)
1261 moderator.approve_host_request(hr_user5)
1262 moderator.approve_host_request(hr_user6)
1263 moderator.approve_host_request(hr_user7)
1264 moderator.approve_host_request(hr_user8)
1265 moderator.approve_host_request(hr_user9)
1266 moderator.approve_host_request(hr6)
1267 moderator.approve_host_request(hr7)
1269 refresh_materialized_views_rapid(empty_pb2.Empty())
1271 with references_session(token1) as api:
1272 # can't write reference for invisible user
1273 with pytest.raises(grpc.RpcError) as e:
1274 api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user5.id))
1275 assert e.value.code() == grpc.StatusCode.NOT_FOUND
1276 assert e.value.details() == "Couldn't find that user."
1278 # can't write reference for blocking user
1279 with pytest.raises(grpc.RpcError) as e:
1280 api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user7.id))
1281 assert e.value.code() == grpc.StatusCode.NOT_FOUND
1282 assert e.value.details() == "Couldn't find that user."
1284 # can't write reference for blocked user
1285 with pytest.raises(grpc.RpcError) as e:
1286 api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user6.id))
1287 assert e.value.code() == grpc.StatusCode.NOT_FOUND
1288 assert e.value.details() == "Couldn't find that user."
1290 # can't write anything to myself
1291 res = api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user1.id))
1292 assert not res.can_write_friend_reference
1293 assert len(res.available_write_references) == 0
1295 res = api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user2.id))
1296 # can't write friend ref to user2
1297 assert not res.can_write_friend_reference
1298 # none we can write for user2
1299 assert len(res.available_write_references) == 0
1301 res = api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user3.id))
1302 # can't write friend ref to user3
1303 assert not res.can_write_friend_reference
1304 # can write one reference because we hosted user3
1305 assert len(res.available_write_references) == 1
1306 w = res.available_write_references[0]
1307 assert w.host_request_id == hr3
1308 assert w.reference_type == references_pb2.REFERENCE_TYPE_HOSTED
1309 assert now() + timedelta(days=6) <= to_aware_datetime(w.time_expires) <= now() + timedelta(days=7)
1311 res = api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user4.id))
1312 # can write friend ref to user4
1313 assert res.can_write_friend_reference
1314 # can write one reference because we surfed with user4
1315 assert len(res.available_write_references) == 1
1316 w = res.available_write_references[0]
1317 assert w.host_request_id == hr4
1318 assert w.reference_type == references_pb2.REFERENCE_TYPE_SURFED
1319 assert now() + timedelta(days=9) <= to_aware_datetime(w.time_expires) <= now() + timedelta(days=10)
1321 # can't write a req if we indicated we didn't meet up
1322 res = api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user8.id))
1323 assert len(res.available_write_references) == 0
1324 res = api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user9.id))
1325 assert len(res.available_write_references) == 0
1327 # can still write ref if the other person indicated we didn't meet up
1328 # surfed with them
1329 res = api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user10.id))
1330 assert len(res.available_write_references) == 1
1331 w = res.available_write_references[0]
1332 assert w.host_request_id == hr6
1333 assert w.reference_type == references_pb2.REFERENCE_TYPE_SURFED
1334 assert now() + timedelta(days=10) <= to_aware_datetime(w.time_expires) <= now() + timedelta(days=11)
1335 # hosted them
1336 res = api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user11.id))
1337 assert len(res.available_write_references) == 1
1338 w = res.available_write_references[0]
1339 assert w.host_request_id == hr7
1340 assert w.reference_type == references_pb2.REFERENCE_TYPE_HOSTED
1341 assert now() + timedelta(days=11) <= to_aware_datetime(w.time_expires) <= now() + timedelta(days=12)
1343 # finally check the general list
1344 res = api.ListPendingReferencesToWrite(empty_pb2.Empty())
1345 assert len(res.pending_references) == 4
1346 w = res.pending_references[0]
1347 assert w.host_request_id == hr3
1348 assert w.reference_type == references_pb2.REFERENCE_TYPE_HOSTED
1349 assert now() + timedelta(days=6) <= to_aware_datetime(w.time_expires) <= now() + timedelta(days=7)
1350 w = res.pending_references[1]
1351 assert w.host_request_id == hr4
1352 assert w.reference_type == references_pb2.REFERENCE_TYPE_SURFED
1353 assert now() + timedelta(days=9) <= to_aware_datetime(w.time_expires) <= now() + timedelta(days=10)
1354 w = res.pending_references[2]
1355 assert w.host_request_id == hr6
1356 assert w.reference_type == references_pb2.REFERENCE_TYPE_SURFED
1357 assert now() + timedelta(days=10) <= to_aware_datetime(w.time_expires) <= now() + timedelta(days=11)
1358 w = res.pending_references[3]
1359 assert w.host_request_id == hr7
1360 assert w.reference_type == references_pb2.REFERENCE_TYPE_HOSTED
1361 assert now() + timedelta(days=11) <= to_aware_datetime(w.time_expires) <= now() + timedelta(days=12)
1363 with account_session(token1) as account:
1364 reminders = account.GetReminders(empty_pb2.Empty()).reminders
1365 assert [reminder.WhichOneof("reminder") for reminder in reminders] == [
1366 "write_reference_reminder",
1367 "write_reference_reminder",
1368 "write_reference_reminder",
1369 "write_reference_reminder",
1370 ]
1371 assert reminders[0].write_reference_reminder.host_request_id == hr3
1372 assert reminders[0].write_reference_reminder.reference_type == references_pb2.REFERENCE_TYPE_HOSTED
1373 assert reminders[0].write_reference_reminder.other_user.user_id == user3.id
1374 assert reminders[1].write_reference_reminder.host_request_id == hr4
1375 assert reminders[1].write_reference_reminder.reference_type == references_pb2.REFERENCE_TYPE_SURFED
1376 assert reminders[1].write_reference_reminder.other_user.user_id == user4.id
1377 assert reminders[2].write_reference_reminder.host_request_id == hr6
1378 assert reminders[2].write_reference_reminder.reference_type == references_pb2.REFERENCE_TYPE_SURFED
1379 assert reminders[2].write_reference_reminder.other_user.user_id == user10.id
1380 assert reminders[3].write_reference_reminder.host_request_id == hr7
1381 assert reminders[3].write_reference_reminder.reference_type == references_pb2.REFERENCE_TYPE_HOSTED
1382 assert reminders[3].write_reference_reminder.other_user.user_id == user11.id
1385@pytest.mark.parametrize("hs", ["host", "surfer"])
1386def test_regression_disappearing_refs(db, hs, moderator):
1387 """
1388 Roughly the reproduction steps are:
1389 * Send a host request, then have both host and surfer accept
1390 * Wait for it to elapse (or hack it with SQL like what you told me to do)
1391 * On the surfer account, leave a reference
1392 * Then on the host account, the option to leave a reference is then not available
1393 """
1394 user1, token1 = generate_user()
1395 user2, token2 = generate_user()
1396 req_start = (today() + timedelta(days=2)).isoformat()
1397 req_end = (today() + timedelta(days=3)).isoformat()
1398 with requests_session(token1) as api:
1399 res = api.CreateHostRequest(
1400 requests_pb2.CreateHostRequestReq(
1401 host_user_id=user2.id, from_date=req_start, to_date=req_end, text=valid_request_text()
1402 )
1403 )
1404 host_request_id = res.host_request_id
1406 moderator.approve_host_request(host_request_id)
1408 assert (
1409 api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_sent=True))
1410 .host_requests[0]
1411 .latest_message.text.text
1412 == valid_request_text()
1413 )
1415 with requests_session(token2) as api:
1416 api.RespondHostRequest(
1417 requests_pb2.RespondHostRequestReq(
1418 host_request_id=host_request_id, status=messages_pb2.HOST_REQUEST_STATUS_ACCEPTED
1419 )
1420 )
1422 with requests_session(token1) as api:
1423 api.RespondHostRequest(
1424 requests_pb2.RespondHostRequestReq(
1425 host_request_id=host_request_id, status=messages_pb2.HOST_REQUEST_STATUS_CONFIRMED
1426 )
1427 )
1429 refresh_materialized_views_rapid(empty_pb2.Empty())
1431 with references_session(token1) as api:
1432 res = api.ListPendingReferencesToWrite(empty_pb2.Empty())
1433 assert len(res.pending_references) == 0
1434 res = api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user2.id))
1435 assert len(res.available_write_references) == 0
1437 with references_session(token2) as api:
1438 res = api.ListPendingReferencesToWrite(empty_pb2.Empty())
1439 assert len(res.pending_references) == 0
1440 res = api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user1.id))
1441 assert len(res.available_write_references) == 0
1443 # hack the time backwards
1444 hack_req_start = today() - timedelta(days=10) + timedelta(days=2)
1445 hack_req_end = today() - timedelta(days=10) + timedelta(days=3)
1446 with session_scope() as session:
1447 host_request = session.execute(select(HostRequest)).scalar_one()
1448 assert host_request.conversation_id == host_request_id
1449 host_request.from_date = hack_req_start
1450 host_request.to_date = hack_req_end
1452 with references_session(token1) as api:
1453 res = api.ListPendingReferencesToWrite(empty_pb2.Empty())
1454 assert len(res.pending_references) == 1
1455 assert res.pending_references[0].host_request_id == host_request_id
1456 assert res.pending_references[0].reference_type == references_pb2.REFERENCE_TYPE_SURFED
1458 res = api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user2.id))
1459 assert len(res.available_write_references) == 1
1460 assert res.available_write_references[0].host_request_id == host_request_id
1461 assert res.available_write_references[0].reference_type == references_pb2.REFERENCE_TYPE_SURFED
1463 with references_session(token2) as api:
1464 res = api.ListPendingReferencesToWrite(empty_pb2.Empty())
1465 assert len(res.pending_references) == 1
1466 assert res.pending_references[0].host_request_id == host_request_id
1467 assert res.pending_references[0].reference_type == references_pb2.REFERENCE_TYPE_HOSTED
1469 res = api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user1.id))
1470 assert len(res.available_write_references) == 1
1471 assert res.available_write_references[0].host_request_id == host_request_id
1472 assert res.available_write_references[0].reference_type == references_pb2.REFERENCE_TYPE_HOSTED
1474 if hs == "host":
1475 with references_session(token2) as api:
1476 api.WriteHostRequestReference(
1477 references_pb2.WriteHostRequestReferenceReq(
1478 host_request_id=host_request_id,
1479 text="Good stuff",
1480 was_appropriate=True,
1481 rating=0.86,
1482 )
1483 )
1485 with references_session(token2) as api:
1486 res = api.ListPendingReferencesToWrite(empty_pb2.Empty())
1487 assert len(res.pending_references) == 0
1489 res = api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user2.id))
1490 assert len(res.available_write_references) == 0
1492 with references_session(token1) as api:
1493 res = api.ListPendingReferencesToWrite(empty_pb2.Empty())
1494 assert len(res.pending_references) == 1
1495 assert res.pending_references[0].host_request_id == host_request_id
1496 assert res.pending_references[0].reference_type == references_pb2.REFERENCE_TYPE_SURFED
1498 res = api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user2.id))
1499 assert len(res.available_write_references) == 1
1500 assert res.available_write_references[0].host_request_id == host_request_id
1501 assert res.available_write_references[0].reference_type == references_pb2.REFERENCE_TYPE_SURFED
1502 else:
1503 with references_session(token1) as api:
1504 api.WriteHostRequestReference(
1505 references_pb2.WriteHostRequestReferenceReq(
1506 host_request_id=host_request_id,
1507 text="Good stuff",
1508 was_appropriate=True,
1509 rating=0.86,
1510 )
1511 )
1513 with references_session(token1) as api:
1514 res = api.ListPendingReferencesToWrite(empty_pb2.Empty())
1515 assert len(res.pending_references) == 0
1517 res = api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user1.id))
1518 assert len(res.available_write_references) == 0
1520 with references_session(token2) as api:
1521 res = api.ListPendingReferencesToWrite(empty_pb2.Empty())
1522 assert len(res.pending_references) == 1
1523 assert res.pending_references[0].host_request_id == host_request_id
1524 assert res.pending_references[0].reference_type == references_pb2.REFERENCE_TYPE_HOSTED
1526 res = api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user1.id))
1527 assert len(res.available_write_references) == 1
1528 assert res.available_write_references[0].host_request_id == host_request_id
1529 assert res.available_write_references[0].reference_type == references_pb2.REFERENCE_TYPE_HOSTED
1532def test_WriteFriendReference_creates_shadowed_moderation_state(db):
1533 """New friend references start out shadowed and are not visible to non-authors."""
1534 user1, token1 = generate_user()
1535 user2, token2 = generate_user()
1536 user3, token3 = generate_user()
1537 make_friends(user1, user2)
1539 with references_session(token1) as api:
1540 ref = api.WriteFriendReference(
1541 references_pb2.WriteFriendReferenceReq(
1542 to_user_id=user2.id, text="Nice friend", was_appropriate=True, rating=0.9
1543 )
1544 )
1546 with session_scope() as session:
1547 reference = session.execute(select(Reference).where(Reference.id == ref.reference_id)).scalar_one()
1548 assert reference.moderation_state.visibility == ModerationVisibility.shadowed
1549 assert reference.moderation_state.object_type == ModerationObjectType.reference
1550 assert reference.moderation_state.object_id == reference.id
1552 # Author can see their own shadowed reference.
1553 with references_session(token1) as api:
1554 res = api.ListReferences(references_pb2.ListReferencesReq(from_user_id=user1.id))
1555 assert [r.reference_id for r in res.references] == [ref.reference_id]
1557 # Non-author user cannot see it while shadowed.
1558 with references_session(token3) as api:
1559 res = api.ListReferences(references_pb2.ListReferencesReq(to_user_id=user2.id))
1560 assert [r.reference_id for r in res.references] == []
1563def test_reference_hidden_via_ums_disappears_from_listings(db, moderator):
1564 """Hiding a reference through UMS removes it from listings, including for the author."""
1565 user1, token1 = generate_user()
1566 user2, token2 = generate_user()
1567 user3, token3 = generate_user()
1568 make_friends(user1, user2)
1570 with references_session(token1) as api:
1571 ref = api.WriteFriendReference(
1572 references_pb2.WriteFriendReferenceReq(
1573 to_user_id=user2.id, text="Visible for now", was_appropriate=True, rating=0.9
1574 )
1575 )
1576 moderator.approve_reference(ref.reference_id)
1578 with references_session(token3) as api:
1579 assert api.ListReferences(references_pb2.ListReferencesReq(to_user_id=user2.id)).references
1581 # Hide the reference via the moderation API.
1582 with real_moderation_session(moderator.token) as mod_api:
1583 state_res = mod_api.GetModerationState(
1584 moderation_pb2.GetModerationStateReq(
1585 object_type=moderation_pb2.MODERATION_OBJECT_TYPE_REFERENCE,
1586 object_id=ref.reference_id,
1587 )
1588 )
1589 mod_api.ModerateContent(
1590 moderation_pb2.ModerateContentReq(
1591 moderation_state_id=state_res.moderation_state.moderation_state_id,
1592 action=moderation_pb2.MODERATION_ACTION_HIDE,
1593 visibility=moderation_pb2.MODERATION_VISIBILITY_HIDDEN,
1594 reason="test",
1595 )
1596 )
1598 # Hidden references are invisible to the author and to other users.
1599 with references_session(token1) as api:
1600 assert not api.ListReferences(references_pb2.ListReferencesReq(from_user_id=user1.id)).references
1601 with references_session(token3) as api:
1602 assert not api.ListReferences(references_pb2.ListReferencesReq(to_user_id=user2.id)).references