Coverage for src/tests/test_references.py: 100%
491 statements
« prev ^ index » next coverage.py v7.6.10, created at 2025-09-14 15:31 +0000
« prev ^ index » next coverage.py v7.6.10, created at 2025-09-14 15:31 +0000
1from datetime import timedelta
2from unittest.mock import patch
4import grpc
5import pytest
6from google.protobuf import empty_pb2
8from couchers import errors
9from couchers.db import session_scope
10from couchers.materialized_views import refresh_materialized_views_rapid
11from couchers.models import (
12 Conversation,
13 HostRequest,
14 HostRequestStatus,
15 Message,
16 MessageType,
17 Reference,
18 ReferenceType,
19 User,
20)
21from couchers.sql import couchers_select as select
22from couchers.utils import create_coordinate, now, to_aware_datetime, today
23from proto import conversations_pb2, references_pb2, requests_pb2
24from tests.test_fixtures import ( # noqa
25 account_session,
26 db,
27 email_fields,
28 generate_user,
29 make_user_block,
30 mock_notification_email,
31 push_collector,
32 references_session,
33 requests_session,
34 testconfig,
35)
38@pytest.fixture(autouse=True)
39def _(testconfig):
40 pass
43def create_host_request(
44 session,
45 surfer_user_id,
46 host_user_id,
47 host_request_age=timedelta(days=15),
48 status=HostRequestStatus.confirmed,
49 host_reason_didnt_meetup=None,
50 surfer_reason_didnt_meetup=None,
51):
52 """
53 Create a host request that's `host_request_age` old
54 """
55 from_date = today() - host_request_age - timedelta(days=2)
56 to_date = today() - host_request_age
57 fake_created = now() - host_request_age - timedelta(days=3)
58 conversation = Conversation()
59 session.add(conversation)
60 session.flush()
61 session.add(
62 Message(
63 time=fake_created + timedelta(seconds=1),
64 conversation_id=conversation.id,
65 author_id=surfer_user_id,
66 message_type=MessageType.chat_created,
67 )
68 )
69 message = Message(
70 time=fake_created + timedelta(seconds=2),
71 conversation_id=conversation.id,
72 author_id=surfer_user_id,
73 text="Hi, I'm requesting to be hosted.",
74 message_type=MessageType.text,
75 )
76 session.add(message)
77 session.flush()
78 host_request = HostRequest(
79 conversation_id=conversation.id,
80 surfer_user_id=surfer_user_id,
81 host_user_id=host_user_id,
82 from_date=from_date,
83 to_date=to_date,
84 status=status,
85 surfer_last_seen_message_id=message.id,
86 host_reason_didnt_meetup=host_reason_didnt_meetup,
87 surfer_reason_didnt_meetup=surfer_reason_didnt_meetup,
88 hosting_city="Test City",
89 hosting_location=create_coordinate(0, 0),
90 hosting_radius=10,
91 )
92 session.add(host_request)
93 session.commit()
94 return host_request.conversation_id
97def create_host_request_by_date(
98 session,
99 surfer_user_id,
100 host_user_id,
101 from_date,
102 to_date,
103 status,
104 host_sent_request_reminders,
105 last_sent_request_reminder_time,
106):
107 conversation = Conversation()
108 session.add(conversation)
109 session.flush()
111 session.add(
112 Message(
113 time=from_date + timedelta(seconds=1),
114 conversation_id=conversation.id,
115 author_id=surfer_user_id,
116 message_type=MessageType.chat_created,
117 )
118 )
120 message = Message(
121 time=from_date + timedelta(seconds=2),
122 conversation_id=conversation.id,
123 author_id=surfer_user_id,
124 text="Hi, I'm requesting to be hosted.",
125 message_type=MessageType.text,
126 )
128 host_request = HostRequest(
129 conversation_id=conversation.id,
130 surfer_user_id=surfer_user_id,
131 host_user_id=host_user_id,
132 from_date=from_date,
133 to_date=to_date,
134 status=status,
135 host_sent_request_reminders=host_sent_request_reminders,
136 last_sent_request_reminder_time=last_sent_request_reminder_time,
137 hosting_city="Test City",
138 hosting_location=create_coordinate(0, 0),
139 hosting_radius=10,
140 )
142 session.add(host_request)
143 session.commit()
144 return host_request.conversation_id
147def create_host_reference(session, from_user_id, to_user_id, reference_age, *, surfing=True, host_request_id=None):
148 if host_request_id:
149 actual_host_request_id = host_request_id
150 else:
151 if surfing:
152 actual_host_request_id = host_request_id or create_host_request(
153 session, from_user_id, to_user_id, reference_age + timedelta(days=1)
154 )
155 else:
156 actual_host_request_id = host_request_id or create_host_request(
157 session, to_user_id, from_user_id, reference_age + timedelta(days=1)
158 )
160 host_request = session.execute(
161 select(HostRequest).where(HostRequest.conversation_id == actual_host_request_id)
162 ).scalar_one()
164 reference = Reference(
165 time=now() - reference_age,
166 from_user_id=from_user_id,
167 host_request_id=host_request.conversation_id,
168 text="Dummy reference",
169 rating=0.5,
170 was_appropriate=True,
171 )
173 if host_request.surfer_user_id == from_user_id:
174 reference.reference_type = ReferenceType.surfed
175 reference.to_user_id = host_request.host_user_id
176 assert from_user_id == host_request.surfer_user_id
177 else:
178 reference.reference_type = ReferenceType.hosted
179 reference.to_user_id = host_request.surfer_user_id
180 assert from_user_id == host_request.host_user_id
182 session.add(reference)
183 session.commit()
184 return reference.id, actual_host_request_id
187def create_friend_reference(session, from_user_id, to_user_id, reference_age):
188 reference = Reference(
189 time=now() - reference_age,
190 from_user_id=from_user_id,
191 to_user_id=to_user_id,
192 reference_type=ReferenceType.friend,
193 text="Test friend request",
194 rating=0.4,
195 was_appropriate=True,
196 )
197 session.add(reference)
198 session.commit()
199 return reference.id
202def test_ListPagination(db):
203 user1, token1 = generate_user()
204 user2, token2 = generate_user()
205 user3, token3 = generate_user()
206 user4, token4 = generate_user()
207 user5, token5 = generate_user()
208 user6, token6 = generate_user()
209 user7, token7 = generate_user()
210 user8, token8 = generate_user()
211 user9, token9 = generate_user()
213 with session_scope() as session:
214 # bidirectional references
215 ref2, hr2 = create_host_reference(session, user2.id, user1.id, timedelta(days=16, seconds=110), surfing=True)
216 ref2b, _ = create_host_reference(
217 session, user1.id, user2.id, timedelta(days=16, seconds=100), host_request_id=hr2
218 )
220 ref3, _ = create_host_reference(session, user3.id, user1.id, timedelta(days=16, seconds=90), surfing=False)
221 ref4, _ = create_host_reference(session, user4.id, user1.id, timedelta(days=16, seconds=80), surfing=True)
222 ref4b = create_friend_reference(session, user1.id, user4.id, timedelta(days=16, seconds=70))
224 ref5, hr5 = create_host_reference(session, user5.id, user1.id, timedelta(days=16, seconds=60), surfing=False)
225 ref5b, _ = create_host_reference(
226 session, user1.id, user5.id, timedelta(days=16, seconds=50), host_request_id=hr5
227 )
229 ref6, _ = create_host_reference(session, user6.id, user1.id, timedelta(days=16, seconds=40), surfing=True)
231 ref7 = create_friend_reference(session, user7.id, user1.id, timedelta(days=16, seconds=30))
233 ref8, _ = create_host_reference(session, user8.id, user1.id, timedelta(days=16, seconds=20), surfing=False)
234 ref9, _ = create_host_reference(session, user9.id, user1.id, timedelta(days=16, seconds=10), surfing=False)
236 # should be visible even under 2 weeks
237 ref7b = create_friend_reference(session, user1.id, user7.id, timedelta(days=9))
239 # hidden because it's less than 2 weeks
240 ref6hidden, _ = create_host_reference(session, user6.id, user1.id, timedelta(days=5), surfing=False)
242 # visible because both were written
243 ref8b, hr8 = create_host_reference(session, user8.id, user1.id, timedelta(days=3, seconds=20), surfing=False)
244 ref8c, _ = create_host_reference(
245 session, user1.id, user8.id, timedelta(days=3, seconds=10), host_request_id=hr8
246 )
248 # note that visibility tests don't really test real logic
250 # these check the right refs are in the right requests and appear in the right order (latest first)
252 with references_session(token2) as api:
253 # written by user1
254 res = api.ListReferences(references_pb2.ListReferencesReq(from_user_id=user1.id, page_size=2))
255 assert [ref.reference_id for ref in res.references] == [ref8c, ref7b]
257 res = api.ListReferences(
258 references_pb2.ListReferencesReq(from_user_id=user1.id, page_token=res.next_page_token, page_size=2)
259 )
260 assert [ref.reference_id for ref in res.references] == [ref5b, ref4b]
262 res = api.ListReferences(
263 references_pb2.ListReferencesReq(from_user_id=user1.id, page_token=res.next_page_token, page_size=2)
264 )
265 assert [ref.reference_id for ref in res.references] == [ref2b]
266 assert not res.next_page_token
268 # received by user1
269 res = api.ListReferences(references_pb2.ListReferencesReq(to_user_id=user1.id, page_size=5))
270 assert [ref.reference_id for ref in res.references] == [ref8b, ref9, ref8, ref7, ref6]
272 res = api.ListReferences(
273 references_pb2.ListReferencesReq(to_user_id=user1.id, page_token=res.next_page_token, page_size=5)
274 )
275 assert [ref.reference_id for ref in res.references] == [ref5, ref4, ref3, ref2]
276 assert not res.next_page_token
278 # same thing but with filters
279 res = api.ListReferences(
280 references_pb2.ListReferencesReq(
281 to_user_id=user1.id,
282 reference_type_filter=[
283 references_pb2.REFERENCE_TYPE_HOSTED,
284 references_pb2.REFERENCE_TYPE_SURFED,
285 references_pb2.REFERENCE_TYPE_FRIEND,
286 ],
287 page_size=5,
288 )
289 )
290 assert [ref.reference_id for ref in res.references] == [ref8b, ref9, ref8, ref7, ref6]
292 res = api.ListReferences(
293 references_pb2.ListReferencesReq(
294 to_user_id=user1.id,
295 reference_type_filter=[
296 references_pb2.REFERENCE_TYPE_HOSTED,
297 references_pb2.REFERENCE_TYPE_SURFED,
298 references_pb2.REFERENCE_TYPE_FRIEND,
299 ],
300 page_token=res.next_page_token,
301 page_size=5,
302 )
303 )
304 assert [ref.reference_id for ref in res.references] == [ref5, ref4, ref3, ref2]
305 assert not res.next_page_token
307 # received hosting references
308 res = api.ListReferences(
309 references_pb2.ListReferencesReq(
310 to_user_id=user1.id, reference_type_filter=[references_pb2.REFERENCE_TYPE_HOSTED], page_size=3
311 )
312 )
313 assert [ref.reference_id for ref in res.references] == [ref8b, ref9, ref8]
315 res = api.ListReferences(
316 references_pb2.ListReferencesReq(
317 to_user_id=user1.id,
318 reference_type_filter=[references_pb2.REFERENCE_TYPE_HOSTED],
319 page_token=res.next_page_token,
320 page_size=3,
321 )
322 )
323 assert [ref.reference_id for ref in res.references] == [ref5, ref3]
324 assert not res.next_page_token
326 # written friend references
327 res = api.ListReferences(
328 references_pb2.ListReferencesReq(
329 from_user_id=user1.id, reference_type_filter=[references_pb2.REFERENCE_TYPE_FRIEND]
330 )
331 )
332 assert [ref.reference_id for ref in res.references] == [ref7b, ref4b]
333 assert not res.next_page_token
335 # written surfing references
336 res = api.ListReferences(
337 references_pb2.ListReferencesReq(
338 from_user_id=user1.id, reference_type_filter=[references_pb2.REFERENCE_TYPE_SURFED]
339 )
340 )
341 assert [ref.reference_id for ref in res.references] == [ref8c, ref5b]
342 assert not res.next_page_token
344 with references_session(token7) as api:
345 # need to set at least one of from or to user
346 with pytest.raises(grpc.RpcError) as e:
347 api.ListReferences(
348 references_pb2.ListReferencesReq(reference_type_filter=[references_pb2.REFERENCE_TYPE_SURFED])
349 )
350 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
351 assert e.value.details() == errors.NEED_TO_SPECIFY_AT_LEAST_ONE_USER
353 with references_session(token5) as api:
354 # from user1 to user2
355 res = api.ListReferences(references_pb2.ListReferencesReq(from_user_id=user1.id, to_user_id=user2.id))
356 assert [ref.reference_id for ref in res.references] == [ref2b]
357 assert not res.next_page_token
359 # from user5 to user1
360 res = api.ListReferences(references_pb2.ListReferencesReq(from_user_id=user5.id, to_user_id=user1.id))
361 assert [ref.reference_id for ref in res.references] == [ref5]
362 assert not res.next_page_token
365def test_ListReference_banned_deleted_users(db):
366 user1, token1 = generate_user()
367 user2, token2 = generate_user()
368 user3, token3 = generate_user()
370 with session_scope() as session:
371 create_friend_reference(session, user2.id, user1.id, timedelta(days=15))
372 create_friend_reference(session, user3.id, user1.id, timedelta(days=16))
373 create_friend_reference(session, user1.id, user2.id, timedelta(days=15))
374 create_friend_reference(session, user1.id, user3.id, timedelta(days=16))
376 with references_session(token1) as api:
377 refs_rec = api.ListReferences(references_pb2.ListReferencesReq(to_user_id=user1.id)).references
378 refs_sent = api.ListReferences(references_pb2.ListReferencesReq(from_user_id=user1.id)).references
379 assert len(refs_rec) == 2
380 assert len(refs_sent) == 2
382 # ban user2
383 with session_scope() as session:
384 user2 = session.execute(select(User).where(User.username == user2.username)).scalar_one()
385 user2.is_banned = True
386 session.commit()
388 # reference to and from banned user is hidden
389 with references_session(token1) as api:
390 refs_rec = api.ListReferences(references_pb2.ListReferencesReq(to_user_id=user1.id)).references
391 refs_sent = api.ListReferences(references_pb2.ListReferencesReq(from_user_id=user1.id)).references
392 assert len(refs_rec) == 1
393 assert len(refs_sent) == 1
395 # delete user3
396 with session_scope() as session:
397 user3 = session.execute(select(User).where(User.username == user3.username)).scalar_one()
398 user3.is_deleted = True
399 session.commit()
401 # doesn't change; references to and from deleted users remain
402 with references_session(token1) as api:
403 refs_rec = api.ListReferences(references_pb2.ListReferencesReq(to_user_id=user1.id)).references
404 refs_sent = api.ListReferences(references_pb2.ListReferencesReq(from_user_id=user1.id)).references
405 assert len(refs_rec) == 1
406 assert len(refs_sent) == 1
409def test_WriteFriendReference(db):
410 user1, token1 = generate_user()
411 user2, token2 = generate_user()
412 user3, token3 = generate_user()
414 with references_session(token1) as api:
415 # can write normal friend reference
416 res = api.WriteFriendReference(
417 references_pb2.WriteFriendReferenceReq(
418 to_user_id=user2.id,
419 text="A test reference",
420 was_appropriate=True,
421 rating=0.5,
422 )
423 )
424 assert res.from_user_id == user1.id
425 assert res.to_user_id == user2.id
426 assert res.reference_type == references_pb2.REFERENCE_TYPE_FRIEND
427 assert res.text == "A test reference"
428 assert now() - timedelta(hours=24) <= to_aware_datetime(res.written_time) <= now()
429 assert not res.host_request_id
431 with references_session(token3) as api:
432 # check it shows up
433 res = api.ListReferences(
434 references_pb2.ListReferencesReq(
435 from_user_id=user1.id, to_user_id=user2.id, reference_type_filter=[references_pb2.REFERENCE_TYPE_FRIEND]
436 )
437 )
438 assert len(res.references) == 1
439 ref = res.references[0]
440 assert ref.from_user_id == user1.id
441 assert ref.to_user_id == user2.id
442 assert ref.reference_type == references_pb2.REFERENCE_TYPE_FRIEND
443 assert ref.text == "A test reference"
444 assert now() - timedelta(hours=24) <= to_aware_datetime(ref.written_time) <= now()
445 assert not ref.host_request_id
447 with references_session(token1) as api:
448 # can't write a second friend reference
449 with pytest.raises(grpc.RpcError) as e:
450 api.WriteFriendReference(
451 references_pb2.WriteFriendReferenceReq(
452 to_user_id=user2.id,
453 text="A test reference",
454 was_appropriate=True,
455 rating=0.5,
456 )
457 )
458 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
459 assert e.value.details() == errors.REFERENCE_ALREADY_GIVEN
461 with references_session(token2) as api:
462 # can't write a reference about yourself
463 with pytest.raises(grpc.RpcError) as e:
464 api.WriteFriendReference(
465 references_pb2.WriteFriendReferenceReq(
466 to_user_id=user2.id,
467 text="I'm really awesome",
468 was_appropriate=True,
469 rating=1.0,
470 )
471 )
472 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
473 assert e.value.details() == errors.CANT_REFER_SELF
476def test_WriteFriendReference_with_empty_text(db):
477 user1, token1 = generate_user()
478 user2, token2 = generate_user()
480 with references_session(token1) as api:
481 with pytest.raises(grpc.RpcError) as e:
482 api.WriteFriendReference(
483 references_pb2.WriteFriendReferenceReq(to_user_id=user2.id, text=" ", was_appropriate=True, rating=0.8)
484 )
485 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
486 assert e.value.details() == errors.REFERENCE_NO_TEXT
489def test_WriteFriendReference_with_private_text(db, push_collector):
490 user1, token1 = generate_user()
491 user2, token2 = generate_user()
493 with references_session(token1) as api:
494 with patch("couchers.email.queue_email") as mock1:
495 with mock_notification_email() as mock2:
496 api.WriteFriendReference(
497 references_pb2.WriteFriendReferenceReq(
498 to_user_id=user2.id,
499 text="They were nice!",
500 was_appropriate=True,
501 rating=0.6,
502 private_text="A bit of an odd ball, but a nice person nonetheless.",
503 )
504 )
506 # make sure an email was sent to the user receiving the ref as well as the mods
507 assert mock1.call_count == 1
508 assert mock2.call_count == 1
509 e = email_fields(mock2)
510 assert e.subject == f"[TEST] You've received a friend reference from {user1.name}!"
511 assert e.recipient == user2.email
513 push_collector.assert_user_has_single_matching(
514 user2.id,
515 title=f"You've received a friend reference from {user1.name}!",
516 body="They were nice!",
517 )
520def test_host_request_states_references(db):
521 user1, token1 = generate_user()
522 user2, token2 = generate_user()
524 with session_scope() as session:
525 # can't write ref
526 hr1 = create_host_request(session, user2.id, user1.id, timedelta(days=10), status=HostRequestStatus.pending)
527 # can write ref
528 hr2 = create_host_request(session, user2.id, user1.id, timedelta(days=10), status=HostRequestStatus.accepted)
529 # can't write ref
530 hr3 = create_host_request(session, user2.id, user1.id, timedelta(days=10), status=HostRequestStatus.rejected)
531 # can write ref
532 hr4 = create_host_request(session, user2.id, user1.id, timedelta(days=10), status=HostRequestStatus.confirmed)
533 # can't write ref
534 hr5 = create_host_request(session, user2.id, user1.id, timedelta(days=10), status=HostRequestStatus.cancelled)
536 with references_session(token1) as api:
537 # pending
538 api.WriteHostRequestReference(
539 references_pb2.WriteHostRequestReferenceReq(
540 host_request_id=hr2,
541 text="Should work!",
542 was_appropriate=True,
543 rating=0.9,
544 )
545 )
547 # accepted
548 api.WriteHostRequestReference(
549 references_pb2.WriteHostRequestReferenceReq(
550 host_request_id=hr4,
551 text="Should work!",
552 was_appropriate=True,
553 rating=0.9,
554 )
555 )
557 # rejected
558 with pytest.raises(grpc.RpcError) as e:
559 api.WriteHostRequestReference(
560 references_pb2.WriteHostRequestReferenceReq(
561 host_request_id=hr1,
562 text="Shouldn't work...",
563 was_appropriate=True,
564 rating=0.9,
565 )
566 )
567 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
568 assert e.value.details() == errors.CANT_WRITE_REFERENCE_FOR_REQUEST
570 # confirmed
571 with pytest.raises(grpc.RpcError) as e:
572 api.WriteHostRequestReference(
573 references_pb2.WriteHostRequestReferenceReq(
574 host_request_id=hr3,
575 text="Shouldn't work...",
576 was_appropriate=True,
577 rating=0.9,
578 )
579 )
580 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
581 assert e.value.details() == errors.CANT_WRITE_REFERENCE_FOR_REQUEST
583 # cancelled
584 with pytest.raises(grpc.RpcError) as e:
585 api.WriteHostRequestReference(
586 references_pb2.WriteHostRequestReferenceReq(
587 host_request_id=hr5,
588 text="Shouldn't work...",
589 was_appropriate=True,
590 rating=0.9,
591 )
592 )
593 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
594 assert e.value.details() == errors.CANT_WRITE_REFERENCE_FOR_REQUEST
597def test_WriteHostRequestReference(db):
598 user1, token1 = generate_user()
599 user2, token2 = generate_user()
600 user3, token3 = generate_user()
601 user4, token4 = generate_user()
603 with session_scope() as session:
604 # too old
605 hr1 = create_host_request(session, user3.id, user1.id, timedelta(days=20))
606 # valid host req, surfer said we didn't show up but we can still write a req
607 hr2 = create_host_request(session, user3.id, user1.id, timedelta(days=10), surfer_reason_didnt_meetup="No show")
608 # valid surfing req
609 hr3 = create_host_request(session, user1.id, user3.id, timedelta(days=7))
610 # not yet complete
611 hr4 = create_host_request(session, user2.id, user1.id, timedelta(days=1), status=HostRequestStatus.pending)
612 # we indicated we didn't meet
613 hr5 = create_host_request(session, user4.id, user1.id, timedelta(days=7), host_reason_didnt_meetup="")
614 # we will indicate we didn't meet
615 hr6 = create_host_request(session, user4.id, user1.id, timedelta(days=8))
617 with references_session(token3) as api:
618 # can write for this one
619 api.WriteHostRequestReference(
620 references_pb2.WriteHostRequestReferenceReq(
621 host_request_id=hr3,
622 text="Should work!",
623 was_appropriate=True,
624 rating=0.9,
625 )
626 )
628 with references_session(token1) as api:
629 # can't write reference for a HR that's not yet finished
630 with pytest.raises(grpc.RpcError) as e:
631 api.WriteHostRequestReference(
632 references_pb2.WriteHostRequestReferenceReq(
633 host_request_id=hr4,
634 text="Shouldn't work...",
635 was_appropriate=True,
636 rating=0.9,
637 )
638 )
639 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
640 assert e.value.details() == errors.CANT_WRITE_REFERENCE_FOR_REQUEST
642 # can't write reference that's more than 2 weeks old
643 with pytest.raises(grpc.RpcError) as e:
644 api.WriteHostRequestReference(
645 references_pb2.WriteHostRequestReferenceReq(
646 host_request_id=hr1,
647 text="Shouldn't work...",
648 was_appropriate=True,
649 rating=0.9,
650 )
651 )
652 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
653 assert e.value.details() == errors.CANT_WRITE_REFERENCE_FOR_REQUEST
655 # can write for this one
656 api.WriteHostRequestReference(
657 references_pb2.WriteHostRequestReferenceReq(
658 host_request_id=hr2,
659 text="Should work!",
660 was_appropriate=True,
661 rating=0.9,
662 )
663 )
665 # but can't write a second one for the same one
666 with pytest.raises(grpc.RpcError) as e:
667 api.WriteHostRequestReference(
668 references_pb2.WriteHostRequestReferenceReq(
669 host_request_id=hr2,
670 text="Shouldn't work...",
671 was_appropriate=True,
672 rating=0.9,
673 )
674 )
675 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
676 assert e.value.details() == errors.REFERENCE_ALREADY_GIVEN
678 # can write for this one too
679 api.WriteHostRequestReference(
680 references_pb2.WriteHostRequestReferenceReq(
681 host_request_id=hr3,
682 text="Should work!",
683 was_appropriate=True,
684 rating=0.9,
685 )
686 )
688 # can't write reference for a HR that we indicated we didn't show up
689 with pytest.raises(grpc.RpcError) as e:
690 api.WriteHostRequestReference(
691 references_pb2.WriteHostRequestReferenceReq(
692 host_request_id=hr5,
693 text="Shouldn't work...",
694 was_appropriate=True,
695 rating=0.9,
696 )
697 )
698 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
699 assert e.value.details() == errors.CANT_WRITE_REFERENCE_INDICATED_DIDNT_MEETUP
701 # can't write reference for a HR that we indicate we didn't show up for
702 api.HostRequestIndicateDidntMeetup(
703 references_pb2.HostRequestIndicateDidntMeetupReq(
704 host_request_id=hr6,
705 reason_didnt_meetup="No clue?",
706 )
707 )
709 with pytest.raises(grpc.RpcError) as e:
710 api.WriteHostRequestReference(
711 references_pb2.WriteHostRequestReferenceReq(
712 host_request_id=hr6,
713 text="Shouldn't work...",
714 was_appropriate=True,
715 rating=0.9,
716 )
717 )
718 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
719 assert e.value.details() == errors.CANT_WRITE_REFERENCE_INDICATED_DIDNT_MEETUP
721 with references_session(token4) as api:
722 # they can still write one
723 api.WriteHostRequestReference(
724 references_pb2.WriteHostRequestReferenceReq(
725 host_request_id=hr6,
726 text="Should work!",
727 was_appropriate=True,
728 rating=0.9,
729 )
730 )
733def test_WriteHostRequestReference_private_text(db, push_collector):
734 user1, token1 = generate_user()
735 user2, token2 = generate_user()
737 with session_scope() as session:
738 hr = create_host_request(session, user1.id, user2.id, timedelta(days=10))
740 with references_session(token1) as api:
741 with patch("couchers.email.queue_email") as mock1:
742 with mock_notification_email() as mock2:
743 api.WriteHostRequestReference(
744 references_pb2.WriteHostRequestReferenceReq(
745 host_request_id=hr,
746 text="Should work!",
747 was_appropriate=True,
748 rating=0.9,
749 private_text="Something",
750 )
751 )
753 # make sure an email was sent to the user receiving the ref as well as the mods
754 assert mock1.call_count == 1
755 assert mock2.call_count == 1
757 e = email_fields(mock2)
758 assert e.subject == f"[TEST] You've received a reference from {user1.name}!"
759 assert e.recipient == user2.email
761 push_collector.assert_user_has_single_matching(
762 user2.id,
763 title=f"You've received a reference from {user1.name}!",
764 body="Please go and write a reference for them too. It's a nice gesture and helps us build a community together!",
765 )
768def test_AvailableWriteReferences_and_ListPendingReferencesToWrite(db):
769 user1, token1 = generate_user()
770 user2, token2 = generate_user()
771 user3, token3 = generate_user()
772 user4, token4 = generate_user()
773 user5, token5 = generate_user(delete_user=True)
774 user6, token6 = generate_user()
775 user7, token7 = generate_user()
776 user8, token8 = generate_user()
777 user9, token9 = generate_user()
778 user10, token10 = generate_user()
779 user11, token11 = generate_user()
780 make_user_block(user1, user6)
781 make_user_block(user7, user1)
783 with session_scope() as session:
784 # too old
785 hr1 = create_host_request(session, user3.id, user1.id, timedelta(days=20))
787 # already wrote friend ref to user3
788 create_friend_reference(session, user1.id, user3.id, timedelta(days=15, seconds=70))
790 # already given
791 _, hr2 = create_host_reference(session, user2.id, user1.id, timedelta(days=10, seconds=110), surfing=True)
792 create_host_reference(session, user1.id, user2.id, timedelta(days=10, seconds=100), host_request_id=hr2)
794 # valid hosted
795 hr3 = create_host_request(session, user3.id, user1.id, timedelta(days=8))
797 # valid surfed
798 hr4 = create_host_request(session, user1.id, user4.id, timedelta(days=5))
800 # not yet complete
801 hr5 = create_host_request(session, user2.id, user1.id, timedelta(days=2), status=HostRequestStatus.pending)
803 # already wrote friend ref to user2
804 create_friend_reference(session, user1.id, user2.id, timedelta(days=1))
806 # user5 deleted, reference won't show up as pending
807 create_host_request(session, user1.id, user5.id, timedelta(days=5))
809 # user6 blocked, reference won't show up as pending
810 create_host_request(session, user1.id, user6.id, timedelta(days=5))
812 # user7 blocking, reference won't show up as pending
813 create_host_request(session, user1.id, user7.id, timedelta(days=5))
815 # hosted but we indicated we didn't meet up, no reason; should not show up
816 create_host_request(session, user8.id, user1.id, timedelta(days=11), host_reason_didnt_meetup="")
818 # surfed but we indicated we didn't meet up, has reason; should not show up
819 create_host_request(
820 session, user1.id, user9.id, timedelta(days=10), surfer_reason_didnt_meetup="They never showed up!"
821 )
823 # surfed but they indicated we didn't meet up, no reason; should show up
824 hr6 = create_host_request(session, user1.id, user10.id, timedelta(days=4), host_reason_didnt_meetup="")
826 # hosted but they indicated we didn't meet up, has reason; should show up
827 hr7 = create_host_request(
828 session, user11.id, user1.id, timedelta(days=3), surfer_reason_didnt_meetup="They never showed up!!"
829 )
831 refresh_materialized_views_rapid(None)
833 with references_session(token1) as api:
834 # can't write reference for invisible user
835 with pytest.raises(grpc.RpcError) as e:
836 api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user5.id))
837 assert e.value.code() == grpc.StatusCode.NOT_FOUND
838 assert e.value.details() == errors.USER_NOT_FOUND
840 # can't write reference for blocking user
841 with pytest.raises(grpc.RpcError) as e:
842 api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user7.id))
843 assert e.value.code() == grpc.StatusCode.NOT_FOUND
844 assert e.value.details() == errors.USER_NOT_FOUND
846 # can't write reference for blocked user
847 with pytest.raises(grpc.RpcError) as e:
848 api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user6.id))
849 assert e.value.code() == grpc.StatusCode.NOT_FOUND
850 assert e.value.details() == errors.USER_NOT_FOUND
852 # can't write anything to myself
853 res = api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user1.id))
854 assert not res.can_write_friend_reference
855 assert len(res.available_write_references) == 0
857 res = api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user2.id))
858 # can't write friend ref to user2
859 assert not res.can_write_friend_reference
860 # none we can write for user2
861 assert len(res.available_write_references) == 0
863 res = api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user3.id))
864 # can't write friend ref to user3
865 assert not res.can_write_friend_reference
866 # can write one reference because we hosted user3
867 assert len(res.available_write_references) == 1
868 w = res.available_write_references[0]
869 assert w.host_request_id == hr3
870 assert w.reference_type == references_pb2.REFERENCE_TYPE_HOSTED
871 assert now() + timedelta(days=6) <= to_aware_datetime(w.time_expires) <= now() + timedelta(days=7)
873 res = api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user4.id))
874 # can write friend ref to user4
875 assert res.can_write_friend_reference
876 # can write one reference because we surfed with user4
877 assert len(res.available_write_references) == 1
878 w = res.available_write_references[0]
879 assert w.host_request_id == hr4
880 assert w.reference_type == references_pb2.REFERENCE_TYPE_SURFED
881 assert now() + timedelta(days=9) <= to_aware_datetime(w.time_expires) <= now() + timedelta(days=10)
883 # can't write a req if we indicated we didn't meet up
884 res = api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user8.id))
885 assert len(res.available_write_references) == 0
886 res = api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user9.id))
887 assert len(res.available_write_references) == 0
889 # can still write ref if the other person indicated we didn't meet up
890 # surfed with them
891 res = api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user10.id))
892 assert len(res.available_write_references) == 1
893 w = res.available_write_references[0]
894 assert w.host_request_id == hr6
895 assert w.reference_type == references_pb2.REFERENCE_TYPE_SURFED
896 assert now() + timedelta(days=10) <= to_aware_datetime(w.time_expires) <= now() + timedelta(days=11)
897 # hosted them
898 res = api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user11.id))
899 assert len(res.available_write_references) == 1
900 w = res.available_write_references[0]
901 assert w.host_request_id == hr7
902 assert w.reference_type == references_pb2.REFERENCE_TYPE_HOSTED
903 assert now() + timedelta(days=11) <= to_aware_datetime(w.time_expires) <= now() + timedelta(days=12)
905 # finally check the general list
906 res = api.ListPendingReferencesToWrite(empty_pb2.Empty())
907 assert len(res.pending_references) == 4
908 w = res.pending_references[0]
909 assert w.host_request_id == hr3
910 assert w.reference_type == references_pb2.REFERENCE_TYPE_HOSTED
911 assert now() + timedelta(days=6) <= to_aware_datetime(w.time_expires) <= now() + timedelta(days=7)
912 w = res.pending_references[1]
913 assert w.host_request_id == hr4
914 assert w.reference_type == references_pb2.REFERENCE_TYPE_SURFED
915 assert now() + timedelta(days=9) <= to_aware_datetime(w.time_expires) <= now() + timedelta(days=10)
916 w = res.pending_references[2]
917 assert w.host_request_id == hr6
918 assert w.reference_type == references_pb2.REFERENCE_TYPE_SURFED
919 assert now() + timedelta(days=10) <= to_aware_datetime(w.time_expires) <= now() + timedelta(days=11)
920 w = res.pending_references[3]
921 assert w.host_request_id == hr7
922 assert w.reference_type == references_pb2.REFERENCE_TYPE_HOSTED
923 assert now() + timedelta(days=11) <= to_aware_datetime(w.time_expires) <= now() + timedelta(days=12)
925 with account_session(token1) as account:
926 reminders = account.GetReminders(empty_pb2.Empty()).reminders
927 assert [reminder.WhichOneof("reminder") for reminder in reminders] == [
928 "write_reference_reminder",
929 "write_reference_reminder",
930 "write_reference_reminder",
931 "write_reference_reminder",
932 "complete_verification_reminder",
933 ]
934 assert reminders[0].write_reference_reminder.host_request_id == hr3
935 assert reminders[0].write_reference_reminder.reference_type == references_pb2.REFERENCE_TYPE_HOSTED
936 assert reminders[0].write_reference_reminder.other_user.user_id == user3.id
937 assert reminders[1].write_reference_reminder.host_request_id == hr4
938 assert reminders[1].write_reference_reminder.reference_type == references_pb2.REFERENCE_TYPE_SURFED
939 assert reminders[1].write_reference_reminder.other_user.user_id == user4.id
940 assert reminders[2].write_reference_reminder.host_request_id == hr6
941 assert reminders[2].write_reference_reminder.reference_type == references_pb2.REFERENCE_TYPE_SURFED
942 assert reminders[2].write_reference_reminder.other_user.user_id == user10.id
943 assert reminders[3].write_reference_reminder.host_request_id == hr7
944 assert reminders[3].write_reference_reminder.reference_type == references_pb2.REFERENCE_TYPE_HOSTED
945 assert reminders[3].write_reference_reminder.other_user.user_id == user11.id
948@pytest.mark.parametrize("hs", ["host", "surfer"])
949def test_regression_disappearing_refs(db, hs):
950 """
951 Roughly the reproduction steps are:
952 * Send a host request, then have both host and surfer accept
953 * Wait for it to elapse (or hack it with SQL like what you told me to do)
954 * On the surfer account, leave a reference
955 * Then on the host account, the option to leave a reference is then not available
956 """
957 user1, token1 = generate_user()
958 user2, token2 = generate_user()
959 req_start = (today() + timedelta(days=2)).isoformat()
960 req_end = (today() + timedelta(days=3)).isoformat()
961 with requests_session(token1) as api:
962 res = api.CreateHostRequest(
963 requests_pb2.CreateHostRequestReq(
964 host_user_id=user2.id, from_date=req_start, to_date=req_end, text="Test request"
965 )
966 )
967 host_request_id = res.host_request_id
968 assert (
969 api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_sent=True))
970 .host_requests[0]
971 .latest_message.text.text
972 == "Test request"
973 )
975 with requests_session(token2) as api:
976 api.RespondHostRequest(
977 requests_pb2.RespondHostRequestReq(
978 host_request_id=host_request_id, status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED
979 )
980 )
982 with requests_session(token1) as api:
983 api.RespondHostRequest(
984 requests_pb2.RespondHostRequestReq(
985 host_request_id=host_request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CONFIRMED
986 )
987 )
989 refresh_materialized_views_rapid(None)
991 with references_session(token1) as api:
992 res = api.ListPendingReferencesToWrite(empty_pb2.Empty())
993 assert len(res.pending_references) == 0
994 res = api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user2.id))
995 assert len(res.available_write_references) == 0
997 with references_session(token2) as api:
998 res = api.ListPendingReferencesToWrite(empty_pb2.Empty())
999 assert len(res.pending_references) == 0
1000 res = api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user1.id))
1001 assert len(res.available_write_references) == 0
1003 # hack the time backwards
1004 hack_req_start = today() - timedelta(days=10) + timedelta(days=2)
1005 hack_req_end = today() - timedelta(days=10) + timedelta(days=3)
1006 with session_scope() as session:
1007 host_request = session.execute(select(HostRequest)).scalar_one()
1008 assert host_request.conversation_id == host_request_id
1009 host_request.from_date = hack_req_start
1010 host_request.to_date = hack_req_end
1012 with references_session(token1) as api:
1013 res = api.ListPendingReferencesToWrite(empty_pb2.Empty())
1014 assert len(res.pending_references) == 1
1015 assert res.pending_references[0].host_request_id == host_request_id
1016 assert res.pending_references[0].reference_type == references_pb2.REFERENCE_TYPE_SURFED
1018 res = api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user2.id))
1019 assert len(res.available_write_references) == 1
1020 assert res.available_write_references[0].host_request_id == host_request_id
1021 assert res.available_write_references[0].reference_type == references_pb2.REFERENCE_TYPE_SURFED
1023 with references_session(token2) as api:
1024 res = api.ListPendingReferencesToWrite(empty_pb2.Empty())
1025 assert len(res.pending_references) == 1
1026 assert res.pending_references[0].host_request_id == host_request_id
1027 assert res.pending_references[0].reference_type == references_pb2.REFERENCE_TYPE_HOSTED
1029 res = api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user1.id))
1030 assert len(res.available_write_references) == 1
1031 assert res.available_write_references[0].host_request_id == host_request_id
1032 assert res.available_write_references[0].reference_type == references_pb2.REFERENCE_TYPE_HOSTED
1034 if hs == "host":
1035 with references_session(token2) as api:
1036 api.WriteHostRequestReference(
1037 references_pb2.WriteHostRequestReferenceReq(
1038 host_request_id=host_request_id,
1039 text="Good stuff",
1040 was_appropriate=True,
1041 rating=0.86,
1042 )
1043 )
1045 with references_session(token2) as api:
1046 res = api.ListPendingReferencesToWrite(empty_pb2.Empty())
1047 assert len(res.pending_references) == 0
1049 res = api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user2.id))
1050 assert len(res.available_write_references) == 0
1052 with references_session(token1) as api:
1053 res = api.ListPendingReferencesToWrite(empty_pb2.Empty())
1054 assert len(res.pending_references) == 1
1055 assert res.pending_references[0].host_request_id == host_request_id
1056 assert res.pending_references[0].reference_type == references_pb2.REFERENCE_TYPE_SURFED
1058 res = api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user2.id))
1059 assert len(res.available_write_references) == 1
1060 assert res.available_write_references[0].host_request_id == host_request_id
1061 assert res.available_write_references[0].reference_type == references_pb2.REFERENCE_TYPE_SURFED
1062 else:
1063 with references_session(token1) as api:
1064 api.WriteHostRequestReference(
1065 references_pb2.WriteHostRequestReferenceReq(
1066 host_request_id=host_request_id,
1067 text="Good stuff",
1068 was_appropriate=True,
1069 rating=0.86,
1070 )
1071 )
1073 with references_session(token1) as api:
1074 res = api.ListPendingReferencesToWrite(empty_pb2.Empty())
1075 assert len(res.pending_references) == 0
1077 res = api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user1.id))
1078 assert len(res.available_write_references) == 0
1080 with references_session(token2) as api:
1081 res = api.ListPendingReferencesToWrite(empty_pb2.Empty())
1082 assert len(res.pending_references) == 1
1083 assert res.pending_references[0].host_request_id == host_request_id
1084 assert res.pending_references[0].reference_type == references_pb2.REFERENCE_TYPE_HOSTED
1086 res = api.AvailableWriteReferences(references_pb2.AvailableWriteReferencesReq(to_user_id=user1.id))
1087 assert len(res.available_write_references) == 1
1088 assert res.available_write_references[0].host_request_id == host_request_id
1089 assert res.available_write_references[0].reference_type == references_pb2.REFERENCE_TYPE_HOSTED