Coverage for app/backend/src/tests/test_requests.py: 99%
1074 statements
« prev ^ index » next coverage.py v7.15.3, created at 2026-08-04 22:32 +0000
« prev ^ index » next coverage.py v7.15.3, created at 2026-08-04 22:32 +0000
1import html
2import re
3from datetime import date, timedelta
4from unittest.mock import patch
5from urllib.parse import parse_qs, urlparse
7import grpc
8import pytest
9from sqlalchemy import func, select
10from sqlalchemy_utils import refresh_materialized_view
12from couchers.constants import HOST_REQUEST_MIN_LENGTH_UTF16
13from couchers.crypto import b64decode
14from couchers.db import session_scope
15from couchers.i18n import LocalizationContext
16from couchers.models import (
17 Cluster,
18 ClusterRole,
19 ClusterSubscription,
20 HostRequest,
21 Message,
22 MessageType,
23 ModerationObjectType,
24 ModerationState,
25 ModerationVisibility,
26 Node,
27 NodeType,
28 Notification,
29 RateLimitAction,
30)
31from couchers.models.public_trips import PublicTrip, PublicTripStatus
32from couchers.proto import (
33 api_pb2,
34 auth_pb2,
35 messages_pb2,
36 requests_pb2,
37)
38from couchers.proto.internal import unsubscribe_pb2
39from couchers.rate_limits.definitions import RATE_LIMIT_DEFINITIONS, RATE_LIMIT_HOURS
40from couchers.utils import create_coordinate, create_polygon_lat_lng, now, to_multi, today
41from tests.fixtures.db import backdate_conversations, generate_user
42from tests.fixtures.misc import EmailCollector, PushCollector
43from tests.fixtures.sessions import api_session, auth_api_session, requests_session
46@pytest.fixture(autouse=True)
47def _(testconfig):
48 pass
51def valid_request_text(text: str = "Test request") -> str:
52 """Pads a request text to a valid length."""
53 # Request lengths are measured in utf-16 code units to match the frontend.
54 utf16_length = len(text.encode("utf-16-le")) // 2
55 if utf16_length >= HOST_REQUEST_MIN_LENGTH_UTF16: 55 ↛ 56line 55 didn't jump to line 56 because the condition on line 55 was never true
56 return text
57 padding_length = HOST_REQUEST_MIN_LENGTH_UTF16 - utf16_length
58 return text + ("_" * padding_length) # Each "_" adds one utf16 code unit.
61def test_create_request(db, moderator):
62 user1, token1 = generate_user()
63 hosting_city = "Morningside Heights, New York City"
64 hosting_lat = 40.8086
65 hosting_lng = -73.9616
66 hosting_radius = 500
67 user2, token2 = generate_user(
68 city=hosting_city,
69 geom=create_coordinate(hosting_lat, hosting_lng),
70 geom_radius=hosting_radius,
71 )
73 today_plus_2 = today() + timedelta(days=2)
74 today_plus_3 = today() + timedelta(days=3)
75 today_minus_2 = today() - timedelta(days=2)
76 today_minus_3 = today() - timedelta(days=3)
78 with requests_session(token1) as api:
79 with pytest.raises(grpc.RpcError) as e:
80 api.CreateHostRequest(
81 requests_pb2.CreateHostRequestReq(
82 host_user_id=user1.id,
83 from_date=today_plus_2.isoformat(),
84 to_date=today_plus_3.isoformat(),
85 text=valid_request_text(),
86 )
87 )
88 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
89 assert e.value.details() == "Can't request hosting from yourself."
91 with pytest.raises(grpc.RpcError) as e:
92 api.CreateHostRequest(
93 requests_pb2.CreateHostRequestReq(
94 host_user_id=999,
95 from_date=today_plus_2.isoformat(),
96 to_date=today_plus_3.isoformat(),
97 text=valid_request_text(),
98 )
99 )
100 assert e.value.code() == grpc.StatusCode.NOT_FOUND
101 assert e.value.details() == "Couldn't find that user."
103 with pytest.raises(grpc.RpcError) as e:
104 api.CreateHostRequest(
105 requests_pb2.CreateHostRequestReq(
106 host_user_id=user2.id,
107 from_date=today_plus_3.isoformat(),
108 to_date=today_plus_2.isoformat(),
109 text=valid_request_text(),
110 )
111 )
112 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
113 assert e.value.details() == "From date can't be after to date."
115 with pytest.raises(grpc.RpcError) as e:
116 api.CreateHostRequest(
117 requests_pb2.CreateHostRequestReq(
118 host_user_id=user2.id,
119 from_date=today_minus_3.isoformat(),
120 to_date=today_plus_2.isoformat(),
121 text=valid_request_text(),
122 )
123 )
124 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
125 assert e.value.details() == "From date must be today or later."
127 with pytest.raises(grpc.RpcError) as e:
128 api.CreateHostRequest(
129 requests_pb2.CreateHostRequestReq(
130 host_user_id=user2.id,
131 from_date=today_plus_2.isoformat(),
132 to_date=today_minus_2.isoformat(),
133 text=valid_request_text(),
134 )
135 )
136 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
137 assert e.value.details() == "From date can't be after to date."
139 with pytest.raises(grpc.RpcError) as e:
140 api.CreateHostRequest(
141 requests_pb2.CreateHostRequestReq(
142 host_user_id=user2.id,
143 from_date="2020-00-06",
144 to_date=today_minus_2.isoformat(),
145 text=valid_request_text(),
146 )
147 )
148 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
149 assert e.value.details() == "Invalid date."
151 with pytest.raises(grpc.RpcError) as e:
152 api.CreateHostRequest(
153 requests_pb2.CreateHostRequestReq(
154 host_user_id=user2.id,
155 from_date=today_plus_2.isoformat(),
156 to_date=today_plus_3.isoformat(),
157 text="Too short.",
158 )
159 )
160 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
161 assert e.value.details() == "Host request cannot be shorter than 250 characters."
163 res = api.CreateHostRequest(
164 requests_pb2.CreateHostRequestReq(
165 host_user_id=user2.id,
166 from_date=today_plus_2.isoformat(),
167 to_date=today_plus_3.isoformat(),
168 text=valid_request_text(),
169 )
170 )
171 host_request_id = res.host_request_id
173 moderator.approve_host_request(host_request_id)
175 with requests_session(token1) as api:
176 host_requests = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_sent=True)).host_requests
178 assert len(host_requests) == 1
179 hr = host_requests[0]
181 assert hr.latest_message.text.text == valid_request_text()
183 assert hr.hosting_city == hosting_city
184 assert round(hr.hosting_lat, 4) == hosting_lat
185 assert round(hr.hosting_lng, 4) == hosting_lng
186 assert hr.hosting_radius == hosting_radius
188 today_ = today()
189 today_plus_one_year = today_ + timedelta(days=365)
190 today_plus_one_year_plus_2 = today_plus_one_year + timedelta(days=2)
191 today_plus_one_year_plus_3 = today_plus_one_year + timedelta(days=3)
192 with pytest.raises(grpc.RpcError) as e:
193 api.CreateHostRequest(
194 requests_pb2.CreateHostRequestReq(
195 host_user_id=user2.id,
196 from_date=today_plus_one_year_plus_2.isoformat(),
197 to_date=today_plus_one_year_plus_3.isoformat(),
198 text=valid_request_text("Test from date after one year"),
199 )
200 )
201 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
202 assert e.value.details() == "The start date must be within one year from today."
204 with pytest.raises(grpc.RpcError) as e:
205 api.CreateHostRequest(
206 requests_pb2.CreateHostRequestReq(
207 host_user_id=user2.id,
208 from_date=today_plus_2.isoformat(),
209 to_date=today_plus_one_year_plus_3.isoformat(),
210 text=valid_request_text("Test to date one year after from date"),
211 )
212 )
213 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
214 assert e.value.details() == "You cannot request to stay with someone for longer than one year."
217def test_create_host_request_rejects_date_past_in_host_timezone(db):
218 # When the host's timezone has already rolled over to the next day, a
219 # from_date of "today in UTC" is in the past from the host's perspective and
220 # must be rejected. The frontend blocks this date before submission; the
221 # backend enforces the same rule for consistency.
222 user1, token1 = generate_user()
223 # geom inside the fake Europe/Helsinki timezone polygon used in tests
224 user2, _ = generate_user(geom=create_coordinate(61, 25))
226 # Helsinki is already on 2026-01-16; requester submits 2026-01-15.
227 fake_today_by_tz = {"Europe/Helsinki": date(2026, 1, 16)}
229 with patch(
230 "couchers.servicers.requests.today_in_timezone",
231 side_effect=lambda tz: fake_today_by_tz.get(tz, date(2026, 1, 15)),
232 ):
233 with requests_session(token1) as api:
234 with pytest.raises(grpc.RpcError) as e:
235 api.CreateHostRequest(
236 requests_pb2.CreateHostRequestReq(
237 host_user_id=user2.id,
238 from_date="2026-01-15",
239 to_date="2026-01-18",
240 text=valid_request_text(),
241 )
242 )
243 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
246def test_create_host_request_date_valid_when_host_behind_requester(db):
247 # Simulate the opposite timezone direction: the host (America/New_York) is
248 # still on 2026-01-15 while the requester has already rolled into 2026-01-16.
249 # A from_date of 2026-01-16 is "today" for the requester and "tomorrow" for
250 # the host — must be accepted without issue.
251 user1, token1 = generate_user()
252 user2, _ = generate_user() # default geom resolves to America/New_York
254 fake_today_by_tz = {"America/New_York": date(2026, 1, 15)}
256 with patch(
257 "couchers.servicers.requests.today_in_timezone",
258 side_effect=lambda tz: fake_today_by_tz.get(tz, date(2026, 1, 15)),
259 ):
260 with requests_session(token1) as api:
261 res = api.CreateHostRequest(
262 requests_pb2.CreateHostRequestReq(
263 host_user_id=user2.id,
264 from_date="2026-01-16",
265 to_date="2026-01-20",
266 text=valid_request_text(),
267 )
268 )
269 assert res.host_request_id
272def test_create_request_duplicate_within_window(db):
273 """A second request to the same host for overlapping dates inside the window is rejected, so a
274 client that resends (or a user who re-taps Send) doesn't create a pile of duplicates."""
275 user1, token1 = generate_user()
276 user2, _ = generate_user()
277 user3, _ = generate_user()
278 from_date = today() + timedelta(days=10)
279 to_date = today() + timedelta(days=15)
281 with requests_session(token1) as api:
282 assert api.CreateHostRequest(
283 requests_pb2.CreateHostRequestReq(
284 host_user_id=user2.id,
285 from_date=from_date.isoformat(),
286 to_date=to_date.isoformat(),
287 text=valid_request_text(),
288 )
289 ).host_request_id
291 with pytest.raises(grpc.RpcError) as e:
292 api.CreateHostRequest(
293 requests_pb2.CreateHostRequestReq(
294 host_user_id=user2.id,
295 from_date=from_date.isoformat(),
296 to_date=to_date.isoformat(),
297 text=valid_request_text(),
298 )
299 )
300 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
301 assert "past 3 hours" in (e.value.details() or "")
303 # partly overlapping dates are blocked too
304 with pytest.raises(grpc.RpcError) as e:
305 api.CreateHostRequest(
306 requests_pb2.CreateHostRequestReq(
307 host_user_id=user2.id,
308 from_date=(to_date - timedelta(days=1)).isoformat(),
309 to_date=(to_date + timedelta(days=5)).isoformat(),
310 text=valid_request_text(),
311 )
312 )
313 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
315 # a different host is unaffected
316 assert api.CreateHostRequest(
317 requests_pb2.CreateHostRequestReq(
318 host_user_id=user3.id,
319 from_date=from_date.isoformat(),
320 to_date=to_date.isoformat(),
321 text=valid_request_text(),
322 )
323 ).host_request_id
326def test_create_request_duplicate_window_allows_other_dates(db):
327 """Only overlapping stays count as duplicates, including a stay starting the day another one
328 ends."""
329 user1, token1 = generate_user()
330 user2, _ = generate_user()
331 from_date = today() + timedelta(days=10)
332 to_date = today() + timedelta(days=15)
334 with requests_session(token1) as api:
335 assert api.CreateHostRequest(
336 requests_pb2.CreateHostRequestReq(
337 host_user_id=user2.id,
338 from_date=from_date.isoformat(),
339 to_date=to_date.isoformat(),
340 text=valid_request_text(),
341 )
342 ).host_request_id
344 assert api.CreateHostRequest(
345 requests_pb2.CreateHostRequestReq(
346 host_user_id=user2.id,
347 from_date=to_date.isoformat(),
348 to_date=(to_date + timedelta(days=5)).isoformat(),
349 text=valid_request_text(),
350 )
351 ).host_request_id
353 assert api.CreateHostRequest(
354 requests_pb2.CreateHostRequestReq(
355 host_user_id=user2.id,
356 from_date=(from_date + timedelta(days=90)).isoformat(),
357 to_date=(to_date + timedelta(days=90)).isoformat(),
358 text=valid_request_text(),
359 )
360 ).host_request_id
363def test_create_request_duplicate_allowed_after_window(db):
364 user1, token1 = generate_user()
365 user2, _ = generate_user()
366 from_date = today() + timedelta(days=10)
367 to_date = today() + timedelta(days=15)
369 with requests_session(token1) as api:
370 api.CreateHostRequest(
371 requests_pb2.CreateHostRequestReq(
372 host_user_id=user2.id,
373 from_date=from_date.isoformat(),
374 to_date=to_date.isoformat(),
375 text=valid_request_text(),
376 )
377 )
378 backdate_conversations()
380 assert api.CreateHostRequest(
381 requests_pb2.CreateHostRequestReq(
382 host_user_id=user2.id,
383 from_date=from_date.isoformat(),
384 to_date=to_date.isoformat(),
385 text=valid_request_text(),
386 )
387 ).host_request_id
390def test_create_request_duplicate_window_ignores_public_trip_offers(db):
391 """Offers on public trips are deduplicated per trip, so two offers to the same traveler for
392 two different trips must both go through."""
393 surfer, _ = generate_user()
394 host, host_token = generate_user()
396 trip_1_from = today() + timedelta(days=10)
397 trip_1_to = today() + timedelta(days=20)
398 trip_2_from = today() + timedelta(days=40)
399 trip_2_to = today() + timedelta(days=50)
400 trip_1_id = _create_public_trip(surfer.id, trip_1_from, trip_1_to)
401 trip_2_id = _create_public_trip(surfer.id, trip_2_from, trip_2_to)
403 with requests_session(host_token) as api:
404 assert api.CreateHostRequest(
405 requests_pb2.CreateHostRequestReq(
406 host_user_id=surfer.id,
407 from_date=trip_1_from.isoformat(),
408 to_date=trip_1_to.isoformat(),
409 text=valid_request_text(),
410 public_trip_id=trip_1_id,
411 )
412 ).host_request_id
414 assert api.CreateHostRequest(
415 requests_pb2.CreateHostRequestReq(
416 host_user_id=surfer.id,
417 from_date=trip_2_from.isoformat(),
418 to_date=trip_2_to.isoformat(),
419 text=valid_request_text(),
420 public_trip_id=trip_2_id,
421 )
422 ).host_request_id
424 # the window only counts non-trip requests, so trip offers don't block a plain request
425 # even for dates they cover
426 assert api.CreateHostRequest(
427 requests_pb2.CreateHostRequestReq(
428 host_user_id=surfer.id,
429 from_date=(trip_1_from + timedelta(days=2)).isoformat(),
430 to_date=(trip_1_to - timedelta(days=2)).isoformat(),
431 text=valid_request_text(),
432 )
433 ).host_request_id
436def test_create_request_incomplete_profile(db):
437 user1, token1 = generate_user(complete_profile=False)
438 user2, _ = generate_user()
439 today_plus_2 = today() + timedelta(days=2)
440 today_plus_3 = today() + timedelta(days=3)
441 with requests_session(token1) as api:
442 with pytest.raises(grpc.RpcError) as e:
443 api.CreateHostRequest(
444 requests_pb2.CreateHostRequestReq(
445 host_user_id=user2.id,
446 from_date=today_plus_2.isoformat(),
447 to_date=today_plus_3.isoformat(),
448 text=valid_request_text(),
449 )
450 )
451 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
452 assert e.value.details() == "You have to complete your profile before you can send a request."
455def test_excessive_requests_are_reported(db, email_collector: EmailCollector):
456 """Test that excessive host requests are first reported in a warning email and finally lead blocking of further requests."""
457 user, token = generate_user()
458 today_plus_2 = today() + timedelta(days=2)
459 today_plus_3 = today() + timedelta(days=3)
460 rate_limit_definition = RATE_LIMIT_DEFINITIONS[RateLimitAction.host_request]
461 with requests_session(token) as api:
462 # Test warning email
463 for _ in range(rate_limit_definition.warning_limit):
464 host_user, _ = generate_user()
465 _ = api.CreateHostRequest(
466 requests_pb2.CreateHostRequestReq(
467 host_user_id=host_user.id,
468 from_date=today_plus_2.isoformat(),
469 to_date=today_plus_3.isoformat(),
470 text=valid_request_text(),
471 )
472 )
474 assert email_collector.count_for_reports() == 0
475 host_user, _ = generate_user()
476 _ = api.CreateHostRequest(
477 requests_pb2.CreateHostRequestReq(
478 host_user_id=host_user.id,
479 from_date=today_plus_2.isoformat(),
480 to_date=today_plus_3.isoformat(),
481 text=valid_request_text("Excessive test request"),
482 )
483 )
485 email = email_collector.pop_for_reports(last=True)
486 assert email.plain.startswith(
487 f"User {user.username} has sent {rate_limit_definition.warning_limit} host requests in the past {RATE_LIMIT_HOURS} hours."
488 )
490 # Test ban after exceeding HOST_REQUEST_HARD_LIMIT
491 for _ in range(rate_limit_definition.hard_limit - rate_limit_definition.warning_limit - 1):
492 host_user, _ = generate_user()
493 _ = api.CreateHostRequest(
494 requests_pb2.CreateHostRequestReq(
495 host_user_id=host_user.id,
496 from_date=today_plus_2.isoformat(),
497 to_date=today_plus_3.isoformat(),
498 text=valid_request_text(),
499 )
500 )
502 assert email_collector.count_for_reports() == 0
504 host_user, _ = generate_user()
505 with pytest.raises(grpc.RpcError) as exc_info:
506 _ = api.CreateHostRequest(
507 requests_pb2.CreateHostRequestReq(
508 host_user_id=host_user.id,
509 from_date=today_plus_2.isoformat(),
510 to_date=today_plus_3.isoformat(),
511 text=valid_request_text("Excessive test request"),
512 )
513 )
514 assert exc_info.value.code() == grpc.StatusCode.RESOURCE_EXHAUSTED
515 assert (
516 exc_info.value.details()
517 == "You have sent a lot of host requests in the past 24 hours. To avoid spam, you can't send any more for now."
518 )
520 email = email_collector.pop_for_reports(last=True)
521 assert email.plain.startswith(
522 f"User {user.username} has sent {rate_limit_definition.hard_limit} host requests in the past {RATE_LIMIT_HOURS} hours."
523 )
524 assert "The user has been blocked from sending further host requests for now." in email.plain
527def add_message(db, text, author_id, conversation_id):
528 with session_scope() as session:
529 message = Message(
530 conversation_id=conversation_id, author_id=author_id, text=text, message_type=MessageType.text
531 )
533 session.add(message)
536def test_GetHostRequest(db):
537 user1, token1 = generate_user()
538 user2, token2 = generate_user()
539 user3, token3 = generate_user()
540 today_plus_2 = today() + timedelta(days=2)
541 today_plus_3 = today() + timedelta(days=3)
542 with requests_session(token1) as api:
543 host_request_id = api.CreateHostRequest(
544 requests_pb2.CreateHostRequestReq(
545 host_user_id=user2.id,
546 from_date=today_plus_2.isoformat(),
547 to_date=today_plus_3.isoformat(),
548 text=valid_request_text("Test request 1"),
549 )
550 ).host_request_id
552 with pytest.raises(grpc.RpcError) as e:
553 api.GetHostRequest(requests_pb2.GetHostRequestReq(host_request_id=999))
554 assert e.value.code() == grpc.StatusCode.NOT_FOUND
555 assert e.value.details() == "Couldn't find that host request."
557 api.SendHostRequestMessage(
558 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 1")
559 )
561 res = api.GetHostRequest(requests_pb2.GetHostRequestReq(host_request_id=host_request_id))
562 assert res.latest_message.text.text == "Test message 1"
565def test_ListHostRequests(db, moderator):
566 user1, token1 = generate_user()
567 user2, token2 = generate_user()
568 user3, token3 = generate_user()
569 today_plus_2 = today() + timedelta(days=2)
570 today_plus_3 = today() + timedelta(days=3)
571 with requests_session(token1) as api:
572 host_request_1 = api.CreateHostRequest(
573 requests_pb2.CreateHostRequestReq(
574 host_user_id=user2.id,
575 from_date=today_plus_2.isoformat(),
576 to_date=today_plus_3.isoformat(),
577 text=valid_request_text("Test request 1"),
578 )
579 ).host_request_id
581 host_request_2 = api.CreateHostRequest(
582 requests_pb2.CreateHostRequestReq(
583 host_user_id=user3.id,
584 from_date=today_plus_2.isoformat(),
585 to_date=today_plus_3.isoformat(),
586 text=valid_request_text("Test request 2"),
587 )
588 ).host_request_id
590 moderator.approve_host_request(host_request_1)
591 moderator.approve_host_request(host_request_2)
593 with requests_session(token1) as api:
594 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_sent=True))
595 assert res.no_more
596 assert len(res.host_requests) == 2
598 with requests_session(token2) as api:
599 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_received=True))
600 assert res.no_more
601 assert len(res.host_requests) == 1
602 assert res.host_requests[0].latest_message.text.text == valid_request_text("Test request 1")
603 assert res.host_requests[0].surfer_user_id == user1.id
604 assert res.host_requests[0].host_user_id == user2.id
605 assert res.host_requests[0].status == messages_pb2.HOST_REQUEST_STATUS_PENDING
607 add_message(db, "Test request 1 message 1", user2.id, host_request_1)
608 add_message(db, "Test request 1 message 2", user2.id, host_request_1)
609 add_message(db, "Test request 1 message 3", user2.id, host_request_1)
611 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_received=True))
612 assert res.host_requests[0].latest_message.text.text == "Test request 1 message 3"
614 host_request_3 = api.CreateHostRequest(
615 requests_pb2.CreateHostRequestReq(
616 host_user_id=user1.id,
617 from_date=today_plus_2.isoformat(),
618 to_date=today_plus_3.isoformat(),
619 text=valid_request_text("Test request 3"),
620 )
621 ).host_request_id
623 moderator.approve_host_request(host_request_3)
625 add_message(db, "Test request 2 message 1", user1.id, host_request_2)
626 add_message(db, "Test request 2 message 2", user3.id, host_request_2)
628 with requests_session(token3) as api:
629 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_received=True))
630 assert res.no_more
631 assert len(res.host_requests) == 1
632 assert res.host_requests[0].latest_message.text.text == "Test request 2 message 2"
634 with requests_session(token1) as api:
635 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_received=True))
636 assert len(res.host_requests) == 1
638 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq())
639 assert len(res.host_requests) == 3
642def test_ListHostRequests_pagination_regression(db, moderator):
643 """
644 ListHostRequests was skipping a request when getting multiple pages
645 """
646 user1, token1 = generate_user()
647 user2, token2 = generate_user()
648 today_plus_2 = today() + timedelta(days=2)
649 today_plus_3 = today() + timedelta(days=3)
650 with requests_session(token1) as api:
651 host_request_1 = api.CreateHostRequest(
652 requests_pb2.CreateHostRequestReq(
653 host_user_id=user2.id,
654 from_date=today_plus_2.isoformat(),
655 to_date=today_plus_3.isoformat(),
656 text=valid_request_text("Test request 1"),
657 )
658 ).host_request_id
659 backdate_conversations()
661 host_request_2 = api.CreateHostRequest(
662 requests_pb2.CreateHostRequestReq(
663 host_user_id=user2.id,
664 from_date=today_plus_2.isoformat(),
665 to_date=today_plus_3.isoformat(),
666 text=valid_request_text("Test request 2"),
667 )
668 ).host_request_id
669 backdate_conversations()
671 host_request_3 = api.CreateHostRequest(
672 requests_pb2.CreateHostRequestReq(
673 host_user_id=user2.id,
674 from_date=today_plus_2.isoformat(),
675 to_date=today_plus_3.isoformat(),
676 text=valid_request_text("Test request 3"),
677 )
678 ).host_request_id
680 moderator.approve_host_request(host_request_1)
681 moderator.approve_host_request(host_request_2)
682 moderator.approve_host_request(host_request_3)
684 with requests_session(token2) as api:
685 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_received=True))
686 assert res.no_more
687 assert len(res.host_requests) == 3
688 assert res.host_requests[0].latest_message.text.text == valid_request_text("Test request 3")
689 assert res.host_requests[1].latest_message.text.text == valid_request_text("Test request 2")
690 assert res.host_requests[2].latest_message.text.text == valid_request_text("Test request 1")
692 with requests_session(token2) as api:
693 api.RespondHostRequest(
694 requests_pb2.RespondHostRequestReq(
695 host_request_id=host_request_2,
696 status=messages_pb2.HOST_REQUEST_STATUS_ACCEPTED,
697 text="Accepting host request 2",
698 )
699 )
700 api.RespondHostRequest(
701 requests_pb2.RespondHostRequestReq(
702 host_request_id=host_request_1,
703 status=messages_pb2.HOST_REQUEST_STATUS_ACCEPTED,
704 text="Accepting host request 1",
705 )
706 )
707 api.RespondHostRequest(
708 requests_pb2.RespondHostRequestReq(
709 host_request_id=host_request_3,
710 status=messages_pb2.HOST_REQUEST_STATUS_ACCEPTED,
711 text="Accepting host request 3",
712 )
713 )
715 with requests_session(token2) as api:
716 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_received=True))
717 assert res.no_more
718 assert len(res.host_requests) == 3
719 assert res.host_requests[0].latest_message.text.text == "Accepting host request 3"
720 assert res.host_requests[1].latest_message.text.text == "Accepting host request 1"
721 assert res.host_requests[2].latest_message.text.text == "Accepting host request 2"
723 with requests_session(token2) as api:
724 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_received=True, number=1))
725 assert not res.no_more
726 assert len(res.host_requests) == 1
727 assert res.host_requests[0].latest_message.text.text == "Accepting host request 3"
728 res = api.ListHostRequests(
729 requests_pb2.ListHostRequestsReq(only_received=True, number=1, page_token=res.next_page_token)
730 )
731 assert not res.no_more
732 assert len(res.host_requests) == 1
733 assert res.host_requests[0].latest_message.text.text == "Accepting host request 1"
734 res = api.ListHostRequests(
735 requests_pb2.ListHostRequestsReq(only_received=True, number=1, page_token=res.next_page_token)
736 )
737 assert res.no_more
738 assert len(res.host_requests) == 1
739 assert res.host_requests[0].latest_message.text.text == "Accepting host request 2"
742def test_ListHostRequests_sort_by_from_date(db, moderator):
743 user1, token1 = generate_user()
744 user2, token2 = generate_user()
745 today_plus_2 = today() + timedelta(days=2)
746 today_plus_3 = today() + timedelta(days=3)
747 today_plus_5 = today() + timedelta(days=5)
748 today_plus_7 = today() + timedelta(days=7)
749 today_plus_10 = today() + timedelta(days=10)
751 with requests_session(token1) as api:
752 hr_late = api.CreateHostRequest(
753 requests_pb2.CreateHostRequestReq(
754 host_user_id=user2.id,
755 from_date=today_plus_7.isoformat(),
756 to_date=today_plus_10.isoformat(),
757 text=valid_request_text("Late request"),
758 )
759 ).host_request_id
761 hr_early = api.CreateHostRequest(
762 requests_pb2.CreateHostRequestReq(
763 host_user_id=user2.id,
764 from_date=today_plus_2.isoformat(),
765 to_date=today_plus_3.isoformat(),
766 text=valid_request_text("Early request"),
767 )
768 ).host_request_id
770 hr_mid = api.CreateHostRequest(
771 requests_pb2.CreateHostRequestReq(
772 host_user_id=user2.id,
773 from_date=today_plus_5.isoformat(),
774 to_date=today_plus_7.isoformat(),
775 text=valid_request_text("Mid request"),
776 )
777 ).host_request_id
779 moderator.approve_host_request(hr_late)
780 moderator.approve_host_request(hr_early)
781 moderator.approve_host_request(hr_mid)
783 with requests_session(token2) as api:
784 # default sort: latest message first (creation order reversed)
785 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_received=True))
786 assert res.no_more
787 assert [r.host_request_id for r in res.host_requests] == [hr_mid, hr_early, hr_late]
789 # from_date sort: ascending by travel date
790 res = api.ListHostRequests(
791 requests_pb2.ListHostRequestsReq(
792 only_received=True,
793 sort_by=requests_pb2.HOST_REQUEST_SORT_BY_FROM_DATE,
794 )
795 )
796 assert res.no_more
797 assert [r.host_request_id for r in res.host_requests] == [hr_early, hr_mid, hr_late]
800def test_ListHostRequests_sort_by_from_date_pagination(db, moderator):
801 """Pagination cursor correctly handles both different and identical from_dates."""
802 user1, token1 = generate_user()
803 user2, token2 = generate_user()
804 today_plus_2 = today() + timedelta(days=2)
805 today_plus_3 = today() + timedelta(days=3)
806 today_plus_5 = today() + timedelta(days=5)
808 with requests_session(token1) as api:
809 hr_a = api.CreateHostRequest(
810 requests_pb2.CreateHostRequestReq(
811 host_user_id=user2.id,
812 from_date=today_plus_2.isoformat(),
813 to_date=today_plus_3.isoformat(),
814 text=valid_request_text("Request A"),
815 )
816 ).host_request_id
817 backdate_conversations()
819 # Same from_date as A — tiebreaker by conversation_id
820 hr_b = api.CreateHostRequest(
821 requests_pb2.CreateHostRequestReq(
822 host_user_id=user2.id,
823 from_date=today_plus_2.isoformat(),
824 to_date=today_plus_3.isoformat(),
825 text=valid_request_text("Request B"),
826 )
827 ).host_request_id
828 backdate_conversations()
830 hr_c = api.CreateHostRequest(
831 requests_pb2.CreateHostRequestReq(
832 host_user_id=user2.id,
833 from_date=today_plus_5.isoformat(),
834 to_date=(today_plus_5 + timedelta(days=2)).isoformat(),
835 text=valid_request_text("Request C"),
836 )
837 ).host_request_id
839 moderator.approve_host_request(hr_a)
840 moderator.approve_host_request(hr_b)
841 moderator.approve_host_request(hr_c)
843 with requests_session(token2) as api:
844 res = api.ListHostRequests(
845 requests_pb2.ListHostRequestsReq(
846 only_received=True,
847 number=1,
848 sort_by=requests_pb2.HOST_REQUEST_SORT_BY_FROM_DATE,
849 )
850 )
851 assert not res.no_more
852 assert len(res.host_requests) == 1
853 assert res.host_requests[0].host_request_id == hr_a
855 res = api.ListHostRequests(
856 requests_pb2.ListHostRequestsReq(
857 only_received=True,
858 number=1,
859 sort_by=requests_pb2.HOST_REQUEST_SORT_BY_FROM_DATE,
860 page_token=res.next_page_token,
861 )
862 )
863 assert not res.no_more
864 assert len(res.host_requests) == 1
865 assert res.host_requests[0].host_request_id == hr_b
867 res = api.ListHostRequests(
868 requests_pb2.ListHostRequestsReq(
869 only_received=True,
870 number=1,
871 sort_by=requests_pb2.HOST_REQUEST_SORT_BY_FROM_DATE,
872 page_token=res.next_page_token,
873 )
874 )
875 assert res.no_more
876 assert len(res.host_requests) == 1
877 assert res.host_requests[0].host_request_id == hr_c
880def test_ListHostRequests_active_filter(db, moderator):
881 user1, token1 = generate_user()
882 user2, token2 = generate_user()
883 today_plus_2 = today() + timedelta(days=2)
884 today_plus_3 = today() + timedelta(days=3)
886 with requests_session(token1) as api:
887 request_id = api.CreateHostRequest(
888 requests_pb2.CreateHostRequestReq(
889 host_user_id=user2.id,
890 from_date=today_plus_2.isoformat(),
891 to_date=today_plus_3.isoformat(),
892 text=valid_request_text("Test request 1"),
893 )
894 ).host_request_id
896 moderator.approve_host_request(request_id)
898 with requests_session(token1) as api:
899 api.RespondHostRequest(
900 requests_pb2.RespondHostRequestReq(
901 host_request_id=request_id, status=messages_pb2.HOST_REQUEST_STATUS_CANCELLED
902 )
903 )
905 with requests_session(token2) as api:
906 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_received=True))
907 assert len(res.host_requests) == 1
908 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_active=True))
909 assert len(res.host_requests) == 0
912def test_ListHostRequests_active_filter_excludes_past(db, moderator):
913 """only_active must exclude requests whose end date has passed (regression test for <= bug)."""
914 user1, token1 = generate_user()
915 user2, token2 = generate_user()
916 today_plus_2 = today() + timedelta(days=2)
917 today_plus_3 = today() + timedelta(days=3)
919 with requests_session(token1) as api:
920 request_id = api.CreateHostRequest(
921 requests_pb2.CreateHostRequestReq(
922 host_user_id=user2.id,
923 from_date=today_plus_2.isoformat(),
924 to_date=today_plus_3.isoformat(),
925 text=valid_request_text("Past stay regression"),
926 )
927 ).host_request_id
929 moderator.approve_host_request(request_id)
931 with requests_session(token2) as api:
932 api.RespondHostRequest(
933 requests_pb2.RespondHostRequestReq(
934 host_request_id=request_id,
935 status=messages_pb2.HOST_REQUEST_STATUS_ACCEPTED,
936 )
937 )
939 # Future request is visible with only_active
940 with requests_session(token2) as api:
941 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_active=True))
942 assert len(res.host_requests) == 1
944 # Move dates into the past
945 with session_scope() as session:
946 hr = session.execute(select(HostRequest).where(HostRequest.conversation_id == request_id)).scalar_one()
947 hr.from_date = today() - timedelta(days=3)
948 hr.to_date = today() - timedelta(days=2)
950 # Past request must be excluded by only_active
951 with requests_session(token2) as api:
952 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_active=True))
953 assert len(res.host_requests) == 0
955 # Still visible without the filter
956 with requests_session(token2) as api:
957 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_received=True))
958 assert len(res.host_requests) == 1
961def test_ListHostRequests_status_in_filter(db, moderator):
962 """status_in must return only requests with the specified statuses."""
963 user1, token1 = generate_user()
964 user2, token2 = generate_user()
965 today_plus_2 = today() + timedelta(days=2)
966 today_plus_3 = today() + timedelta(days=3)
967 today_plus_4 = today() + timedelta(days=4)
968 today_plus_5 = today() + timedelta(days=5)
970 # Create a pending request
971 with requests_session(token1) as api:
972 pending_id = api.CreateHostRequest(
973 requests_pb2.CreateHostRequestReq(
974 host_user_id=user2.id,
975 from_date=today_plus_2.isoformat(),
976 to_date=today_plus_3.isoformat(),
977 text=valid_request_text("Pending"),
978 )
979 ).host_request_id
981 moderator.approve_host_request(pending_id)
983 # Create an accepted request
984 with requests_session(token1) as api:
985 accepted_id = api.CreateHostRequest(
986 requests_pb2.CreateHostRequestReq(
987 host_user_id=user2.id,
988 from_date=today_plus_4.isoformat(),
989 to_date=today_plus_5.isoformat(),
990 text=valid_request_text("Accepted"),
991 )
992 ).host_request_id
994 moderator.approve_host_request(accepted_id)
996 with requests_session(token2) as api:
997 api.RespondHostRequest(
998 requests_pb2.RespondHostRequestReq(
999 host_request_id=accepted_id,
1000 status=messages_pb2.HOST_REQUEST_STATUS_ACCEPTED,
1001 )
1002 )
1004 # Filter to accepted only
1005 with requests_session(token2) as api:
1006 res = api.ListHostRequests(
1007 requests_pb2.ListHostRequestsReq(
1008 status_in=[messages_pb2.HOST_REQUEST_STATUS_ACCEPTED],
1009 )
1010 )
1011 assert len(res.host_requests) == 1
1012 assert res.host_requests[0].status == messages_pb2.HOST_REQUEST_STATUS_ACCEPTED
1014 # Filter to pending only
1015 with requests_session(token2) as api:
1016 res = api.ListHostRequests(
1017 requests_pb2.ListHostRequestsReq(
1018 status_in=[messages_pb2.HOST_REQUEST_STATUS_PENDING],
1019 )
1020 )
1021 assert len(res.host_requests) == 1
1022 assert res.host_requests[0].status == messages_pb2.HOST_REQUEST_STATUS_PENDING
1024 # Filter to accepted + pending — both appear
1025 with requests_session(token2) as api:
1026 res = api.ListHostRequests(
1027 requests_pb2.ListHostRequestsReq(
1028 status_in=[
1029 messages_pb2.HOST_REQUEST_STATUS_ACCEPTED,
1030 messages_pb2.HOST_REQUEST_STATUS_PENDING,
1031 ],
1032 )
1033 )
1034 assert len(res.host_requests) == 2
1036 # Filter to confirmed — none appear
1037 with requests_session(token2) as api:
1038 res = api.ListHostRequests(
1039 requests_pb2.ListHostRequestsReq(
1040 status_in=[messages_pb2.HOST_REQUEST_STATUS_CONFIRMED],
1041 )
1042 )
1043 assert len(res.host_requests) == 0
1045 # Empty status_in — all requests returned (no filter applied)
1046 with requests_session(token2) as api:
1047 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_received=True))
1048 assert len(res.host_requests) == 2
1051def test_RespondHostRequests(db, moderator):
1052 user1, token1 = generate_user()
1053 user2, token2 = generate_user()
1054 user3, token3 = generate_user()
1055 today_plus_2 = today() + timedelta(days=2)
1056 today_plus_3 = today() + timedelta(days=3)
1058 with requests_session(token1) as api:
1059 request_id = api.CreateHostRequest(
1060 requests_pb2.CreateHostRequestReq(
1061 host_user_id=user2.id,
1062 from_date=today_plus_2.isoformat(),
1063 to_date=today_plus_3.isoformat(),
1064 text=valid_request_text("Test request 1"),
1065 )
1066 ).host_request_id
1068 moderator.approve_host_request(request_id)
1070 # another user can't access
1071 with requests_session(token3) as api:
1072 with pytest.raises(grpc.RpcError) as e:
1073 api.RespondHostRequest(
1074 requests_pb2.RespondHostRequestReq(
1075 host_request_id=request_id, status=messages_pb2.HOST_REQUEST_STATUS_CANCELLED
1076 )
1077 )
1078 assert e.value.code() == grpc.StatusCode.NOT_FOUND
1079 assert e.value.details() == "Couldn't find that host request."
1081 with requests_session(token1) as api:
1082 with pytest.raises(grpc.RpcError) as e:
1083 api.RespondHostRequest(
1084 requests_pb2.RespondHostRequestReq(
1085 host_request_id=request_id, status=messages_pb2.HOST_REQUEST_STATUS_ACCEPTED
1086 )
1087 )
1088 assert e.value.code() == grpc.StatusCode.PERMISSION_DENIED
1089 assert e.value.details() == "You are not the host of this request."
1091 with requests_session(token2) as api:
1092 # non existing id
1093 with pytest.raises(grpc.RpcError) as e:
1094 api.RespondHostRequest(
1095 requests_pb2.RespondHostRequestReq(
1096 host_request_id=9999, status=messages_pb2.HOST_REQUEST_STATUS_CANCELLED
1097 )
1098 )
1099 assert e.value.code() == grpc.StatusCode.NOT_FOUND
1101 # host can't confirm or cancel (host should accept/reject)
1102 with pytest.raises(grpc.RpcError) as e:
1103 api.RespondHostRequest(
1104 requests_pb2.RespondHostRequestReq(
1105 host_request_id=request_id, status=messages_pb2.HOST_REQUEST_STATUS_CONFIRMED
1106 )
1107 )
1108 assert e.value.code() == grpc.StatusCode.PERMISSION_DENIED
1109 assert e.value.details() == "You can't set the host request status to that."
1110 with pytest.raises(grpc.RpcError) as e:
1111 api.RespondHostRequest(
1112 requests_pb2.RespondHostRequestReq(
1113 host_request_id=request_id, status=messages_pb2.HOST_REQUEST_STATUS_CANCELLED
1114 )
1115 )
1116 assert e.value.code() == grpc.StatusCode.PERMISSION_DENIED
1117 assert e.value.details() == "You can't set the host request status to that."
1119 api.RespondHostRequest(
1120 requests_pb2.RespondHostRequestReq(
1121 host_request_id=request_id,
1122 status=messages_pb2.HOST_REQUEST_STATUS_REJECTED,
1123 text="Test rejection message",
1124 )
1125 )
1126 res = api.GetHostRequestMessages(requests_pb2.GetHostRequestMessagesReq(host_request_id=request_id))
1127 assert res.messages[0].text.text == "Test rejection message"
1128 assert res.messages[1].WhichOneof("content") == "host_request_status_changed"
1129 assert res.messages[1].host_request_status_changed.status == messages_pb2.HOST_REQUEST_STATUS_REJECTED
1130 # should be able to move from rejected -> accepted
1131 api.RespondHostRequest(
1132 requests_pb2.RespondHostRequestReq(
1133 host_request_id=request_id, status=messages_pb2.HOST_REQUEST_STATUS_ACCEPTED
1134 )
1135 )
1137 with requests_session(token1) as api:
1138 # can't make pending
1139 with pytest.raises(grpc.RpcError) as e:
1140 api.RespondHostRequest(
1141 requests_pb2.RespondHostRequestReq(
1142 host_request_id=request_id, status=messages_pb2.HOST_REQUEST_STATUS_PENDING
1143 )
1144 )
1145 assert e.value.code() == grpc.StatusCode.PERMISSION_DENIED
1146 assert e.value.details() == "You can't set the host request status to that."
1148 # can confirm then cancel
1149 api.RespondHostRequest(
1150 requests_pb2.RespondHostRequestReq(
1151 host_request_id=request_id, status=messages_pb2.HOST_REQUEST_STATUS_CONFIRMED
1152 )
1153 )
1155 api.RespondHostRequest(
1156 requests_pb2.RespondHostRequestReq(
1157 host_request_id=request_id, status=messages_pb2.HOST_REQUEST_STATUS_CANCELLED
1158 )
1159 )
1161 # can't confirm after having cancelled
1162 with pytest.raises(grpc.RpcError) as e:
1163 api.RespondHostRequest(
1164 requests_pb2.RespondHostRequestReq(
1165 host_request_id=request_id, status=messages_pb2.HOST_REQUEST_STATUS_CONFIRMED
1166 )
1167 )
1168 assert e.value.code() == grpc.StatusCode.PERMISSION_DENIED
1169 assert e.value.details() == "You can't set the host request status to that."
1171 # at this point there should be 7 messages
1172 # 2 for creation, 2 for the status change with message, 3 for the other status changed
1173 with requests_session(token1) as api:
1174 res = api.GetHostRequestMessages(requests_pb2.GetHostRequestMessagesReq(host_request_id=request_id))
1175 assert len(res.messages) == 7
1176 assert res.messages[0].host_request_status_changed.status == messages_pb2.HOST_REQUEST_STATUS_CANCELLED
1177 assert res.messages[1].host_request_status_changed.status == messages_pb2.HOST_REQUEST_STATUS_CONFIRMED
1178 assert res.messages[2].host_request_status_changed.status == messages_pb2.HOST_REQUEST_STATUS_ACCEPTED
1179 assert res.messages[4].host_request_status_changed.status == messages_pb2.HOST_REQUEST_STATUS_REJECTED
1180 assert res.messages[6].WhichOneof("content") == "chat_created"
1183def test_get_host_request_messages(db, moderator):
1184 user1, token1 = generate_user()
1185 user2, token2 = generate_user()
1186 today_plus_2 = today() + timedelta(days=2)
1187 today_plus_3 = today() + timedelta(days=3)
1188 with requests_session(token1) as api:
1189 res = api.CreateHostRequest(
1190 requests_pb2.CreateHostRequestReq(
1191 host_user_id=user2.id,
1192 from_date=today_plus_2.isoformat(),
1193 to_date=today_plus_3.isoformat(),
1194 text=valid_request_text("Test request 1"),
1195 )
1196 )
1197 conversation_id = res.host_request_id
1199 moderator.approve_host_request(conversation_id)
1201 add_message(db, "Test request 1 message 1", user1.id, conversation_id)
1202 add_message(db, "Test request 1 message 2", user1.id, conversation_id)
1203 add_message(db, "Test request 1 message 3", user1.id, conversation_id)
1205 with requests_session(token2) as api:
1206 api.RespondHostRequest(
1207 requests_pb2.RespondHostRequestReq(
1208 host_request_id=conversation_id, status=messages_pb2.HOST_REQUEST_STATUS_ACCEPTED
1209 )
1210 )
1212 add_message(db, "Test request 1 message 4", user2.id, conversation_id)
1213 add_message(db, "Test request 1 message 5", user2.id, conversation_id)
1215 api.RespondHostRequest(
1216 requests_pb2.RespondHostRequestReq(
1217 host_request_id=conversation_id, status=messages_pb2.HOST_REQUEST_STATUS_REJECTED
1218 )
1219 )
1221 with requests_session(token1) as api:
1222 # 9 including initial message
1223 res = api.GetHostRequestMessages(requests_pb2.GetHostRequestMessagesReq(host_request_id=conversation_id))
1224 assert len(res.messages) == 9
1225 assert res.no_more
1227 res = api.GetHostRequestMessages(
1228 requests_pb2.GetHostRequestMessagesReq(host_request_id=conversation_id, number=3)
1229 )
1230 assert not res.no_more
1231 assert len(res.messages) == 3
1232 assert res.messages[0].host_request_status_changed.status == messages_pb2.HOST_REQUEST_STATUS_REJECTED
1233 assert res.messages[0].WhichOneof("content") == "host_request_status_changed"
1234 assert res.messages[1].text.text == "Test request 1 message 5"
1235 assert res.messages[2].text.text == "Test request 1 message 4"
1237 res = api.GetHostRequestMessages(
1238 requests_pb2.GetHostRequestMessagesReq(
1239 host_request_id=conversation_id,
1240 last_message_id=res.messages[2].message_id,
1241 number=6,
1242 )
1243 )
1244 assert res.no_more
1245 assert len(res.messages) == 6
1246 assert res.messages[0].host_request_status_changed.status == messages_pb2.HOST_REQUEST_STATUS_ACCEPTED
1247 assert res.messages[0].WhichOneof("content") == "host_request_status_changed"
1248 assert res.messages[1].text.text == "Test request 1 message 3"
1249 assert res.messages[2].text.text == "Test request 1 message 2"
1250 assert res.messages[3].text.text == "Test request 1 message 1"
1251 assert res.messages[4].text.text == valid_request_text("Test request 1")
1252 assert res.messages[5].WhichOneof("content") == "chat_created"
1255def test_SendHostRequestMessage(db, moderator):
1256 user1, token1 = generate_user()
1257 user2, token2 = generate_user()
1258 user3, token3 = generate_user()
1259 today_plus_2 = today() + timedelta(days=2)
1260 today_plus_3 = today() + timedelta(days=3)
1261 with requests_session(token1) as api:
1262 host_request_id = api.CreateHostRequest(
1263 requests_pb2.CreateHostRequestReq(
1264 host_user_id=user2.id,
1265 from_date=today_plus_2.isoformat(),
1266 to_date=today_plus_3.isoformat(),
1267 text=valid_request_text("Test request 1"),
1268 )
1269 ).host_request_id
1271 moderator.approve_host_request(host_request_id)
1273 with requests_session(token1) as api:
1274 with pytest.raises(grpc.RpcError) as e:
1275 api.SendHostRequestMessage(
1276 requests_pb2.SendHostRequestMessageReq(host_request_id=999, text="Test message 1")
1277 )
1278 assert e.value.code() == grpc.StatusCode.NOT_FOUND
1280 with pytest.raises(grpc.RpcError) as e:
1281 api.SendHostRequestMessage(requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text=""))
1282 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
1283 assert e.value.details() == "Invalid message."
1285 api.SendHostRequestMessage(
1286 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 1")
1287 )
1288 res = api.GetHostRequestMessages(requests_pb2.GetHostRequestMessagesReq(host_request_id=host_request_id))
1289 assert res.messages[0].text.text == "Test message 1"
1290 assert res.messages[0].author_user_id == user1.id
1292 with requests_session(token3) as api:
1293 # other user can't send
1294 with pytest.raises(grpc.RpcError) as e:
1295 api.SendHostRequestMessage(
1296 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 2")
1297 )
1298 assert e.value.code() == grpc.StatusCode.NOT_FOUND
1299 assert e.value.details() == "Couldn't find that host request."
1301 with requests_session(token2) as api:
1302 api.SendHostRequestMessage(
1303 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 2")
1304 )
1305 res = api.GetHostRequestMessages(requests_pb2.GetHostRequestMessagesReq(host_request_id=host_request_id))
1306 # including 2 for creation control message and message
1307 assert len(res.messages) == 4
1308 assert res.messages[0].text.text == "Test message 2"
1309 assert res.messages[0].author_user_id == user2.id
1311 # CAN send messages to a rejected, confirmed or cancelled request, and for accepted
1312 api.RespondHostRequest(
1313 requests_pb2.RespondHostRequestReq(
1314 host_request_id=host_request_id, status=messages_pb2.HOST_REQUEST_STATUS_REJECTED
1315 )
1316 )
1317 api.SendHostRequestMessage(
1318 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 3")
1319 )
1321 api.RespondHostRequest(
1322 requests_pb2.RespondHostRequestReq(
1323 host_request_id=host_request_id, status=messages_pb2.HOST_REQUEST_STATUS_ACCEPTED
1324 )
1325 )
1327 with requests_session(token1) as api:
1328 api.RespondHostRequest(
1329 requests_pb2.RespondHostRequestReq(
1330 host_request_id=host_request_id, status=messages_pb2.HOST_REQUEST_STATUS_CONFIRMED
1331 )
1332 )
1333 api.SendHostRequestMessage(
1334 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 3")
1335 )
1337 api.RespondHostRequest(
1338 requests_pb2.RespondHostRequestReq(
1339 host_request_id=host_request_id, status=messages_pb2.HOST_REQUEST_STATUS_CANCELLED
1340 )
1341 )
1342 api.SendHostRequestMessage(
1343 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 3")
1344 )
1347def test_get_updates(db, moderator):
1348 user1, token1 = generate_user()
1349 user2, token2 = generate_user()
1350 user3, token3 = generate_user()
1351 today_plus_2 = today() + timedelta(days=2)
1352 today_plus_3 = today() + timedelta(days=3)
1353 with requests_session(token1) as api:
1354 host_request_id = api.CreateHostRequest(
1355 requests_pb2.CreateHostRequestReq(
1356 host_user_id=user2.id,
1357 from_date=today_plus_2.isoformat(),
1358 to_date=today_plus_3.isoformat(),
1359 text=valid_request_text("Test message 0"),
1360 )
1361 ).host_request_id
1363 moderator.approve_host_request(host_request_id)
1365 with requests_session(token1) as api:
1366 api.SendHostRequestMessage(
1367 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 1")
1368 )
1369 api.SendHostRequestMessage(
1370 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 2")
1371 )
1372 api.RespondHostRequest(
1373 requests_pb2.RespondHostRequestReq(
1374 host_request_id=host_request_id,
1375 status=messages_pb2.HOST_REQUEST_STATUS_CANCELLED,
1376 text="Test message 3",
1377 )
1378 )
1380 backdate_conversations()
1381 api.CreateHostRequest(
1382 requests_pb2.CreateHostRequestReq(
1383 host_user_id=user2.id,
1384 from_date=today_plus_2.isoformat(),
1385 to_date=today_plus_3.isoformat(),
1386 text=valid_request_text("Test message 4"),
1387 )
1388 )
1390 res = api.GetHostRequestMessages(requests_pb2.GetHostRequestMessagesReq(host_request_id=host_request_id))
1391 assert len(res.messages) == 6
1392 assert res.messages[0].text.text == "Test message 3"
1393 assert res.messages[1].host_request_status_changed.status == messages_pb2.HOST_REQUEST_STATUS_CANCELLED
1394 assert res.messages[2].text.text == "Test message 2"
1395 assert res.messages[3].text.text == "Test message 1"
1396 assert res.messages[4].text.text == valid_request_text("Test message 0")
1397 message_id_3 = res.messages[0].message_id
1398 message_id_cancel = res.messages[1].message_id
1399 message_id_2 = res.messages[2].message_id
1400 message_id_1 = res.messages[3].message_id
1401 message_id_0 = res.messages[4].message_id
1403 with pytest.raises(grpc.RpcError) as e:
1404 api.GetHostRequestUpdates(requests_pb2.GetHostRequestUpdatesReq(newest_message_id=0))
1405 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
1407 res = api.GetHostRequestUpdates(requests_pb2.GetHostRequestUpdatesReq(newest_message_id=message_id_1))
1408 assert res.no_more
1409 assert len(res.updates) == 5
1410 assert res.updates[0].message.text.text == "Test message 2"
1411 assert res.updates[1].message.host_request_status_changed.status == messages_pb2.HOST_REQUEST_STATUS_CANCELLED
1412 assert res.updates[1].status == messages_pb2.HOST_REQUEST_STATUS_CANCELLED
1413 assert res.updates[2].message.text.text == "Test message 3"
1414 assert res.updates[3].message.WhichOneof("content") == "chat_created"
1415 assert res.updates[3].status == messages_pb2.HOST_REQUEST_STATUS_PENDING
1416 assert res.updates[4].message.text.text == valid_request_text("Test message 4")
1418 res = api.GetHostRequestUpdates(requests_pb2.GetHostRequestUpdatesReq(newest_message_id=message_id_1, number=1))
1419 assert not res.no_more
1420 assert len(res.updates) == 1
1421 assert res.updates[0].message.text.text == "Test message 2"
1422 assert res.updates[0].status == messages_pb2.HOST_REQUEST_STATUS_CANCELLED
1424 with requests_session(token3) as api:
1425 # other user can't access
1426 res = api.GetHostRequestUpdates(requests_pb2.GetHostRequestUpdatesReq(newest_message_id=message_id_1))
1427 assert len(res.updates) == 0
1430def test_archive_host_request(db, moderator):
1431 user1, token1 = generate_user()
1432 user2, token2 = generate_user()
1434 today_plus_2 = today() + timedelta(days=2)
1435 today_plus_3 = today() + timedelta(days=3)
1437 with requests_session(token1) as api:
1438 host_request_id = api.CreateHostRequest(
1439 requests_pb2.CreateHostRequestReq(
1440 host_user_id=user2.id,
1441 from_date=today_plus_2.isoformat(),
1442 to_date=today_plus_3.isoformat(),
1443 text=valid_request_text("Test message 0"),
1444 )
1445 ).host_request_id
1447 api.SendHostRequestMessage(
1448 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 1")
1449 )
1450 api.SendHostRequestMessage(
1451 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 2")
1452 )
1454 moderator.approve_host_request(host_request_id)
1456 # happy path archiving host request
1457 with requests_session(token1) as api:
1458 api.RespondHostRequest(
1459 requests_pb2.RespondHostRequestReq(
1460 host_request_id=host_request_id,
1461 status=messages_pb2.HOST_REQUEST_STATUS_CANCELLED,
1462 text="Test message 3",
1463 )
1464 )
1465 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_sent=True))
1466 assert len(res.host_requests) == 1
1467 assert res.host_requests[0].status == messages_pb2.HOST_REQUEST_STATUS_CANCELLED
1469 # Verify is_archived is False before archiving
1470 res = api.GetHostRequest(requests_pb2.GetHostRequestReq(host_request_id=host_request_id))
1471 assert not res.is_archived
1473 api.SetHostRequestArchiveStatus(
1474 requests_pb2.SetHostRequestArchiveStatusReq(host_request_id=host_request_id, is_archived=True)
1475 )
1476 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_archived=True))
1477 assert len(res.host_requests) == 1
1479 # Verify is_archived is True after archiving
1480 res = api.GetHostRequest(requests_pb2.GetHostRequestReq(host_request_id=host_request_id))
1481 assert res.is_archived
1484def test_mark_last_seen(db, moderator):
1485 user1, token1 = generate_user()
1486 user2, token2 = generate_user()
1487 user3, token3 = generate_user()
1488 today_plus_2 = today() + timedelta(days=2)
1489 today_plus_3 = today() + timedelta(days=3)
1490 with requests_session(token1) as api:
1491 host_request_id = api.CreateHostRequest(
1492 requests_pb2.CreateHostRequestReq(
1493 host_user_id=user2.id,
1494 from_date=today_plus_2.isoformat(),
1495 to_date=today_plus_3.isoformat(),
1496 text=valid_request_text("Test message 0"),
1497 )
1498 ).host_request_id
1499 backdate_conversations()
1501 host_request_id_2 = api.CreateHostRequest(
1502 requests_pb2.CreateHostRequestReq(
1503 host_user_id=user2.id,
1504 from_date=today_plus_2.isoformat(),
1505 to_date=today_plus_3.isoformat(),
1506 text=valid_request_text("Test message 0a"),
1507 )
1508 ).host_request_id
1510 moderator.approve_host_request(host_request_id)
1511 moderator.approve_host_request(host_request_id_2)
1513 with requests_session(token1) as api:
1514 api.SendHostRequestMessage(
1515 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 1")
1516 )
1517 api.SendHostRequestMessage(
1518 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 2")
1519 )
1520 api.RespondHostRequest(
1521 requests_pb2.RespondHostRequestReq(
1522 host_request_id=host_request_id,
1523 status=messages_pb2.HOST_REQUEST_STATUS_CANCELLED,
1524 text="Test message 3",
1525 )
1526 )
1528 moderator.approve_host_request(host_request_id)
1529 moderator.approve_host_request(host_request_id_2)
1531 # test Ping unseen host request count, should be automarked after sending
1532 with api_session(token1) as api:
1533 assert api.Ping(api_pb2.PingReq()).unseen_received_host_request_count == 0
1534 assert api.Ping(api_pb2.PingReq()).unseen_sent_host_request_count == 0
1536 with api_session(token2) as api:
1537 assert api.Ping(api_pb2.PingReq()).unseen_received_host_request_count == 2
1538 assert api.Ping(api_pb2.PingReq()).unseen_sent_host_request_count == 0
1540 with requests_session(token2) as api:
1541 assert api.ListHostRequests(requests_pb2.ListHostRequestsReq()).host_requests[0].last_seen_message_id == 0
1543 api.MarkLastSeenHostRequest(
1544 requests_pb2.MarkLastSeenHostRequestReq(host_request_id=host_request_id, last_seen_message_id=3)
1545 )
1547 assert api.ListHostRequests(requests_pb2.ListHostRequestsReq()).host_requests[0].last_seen_message_id == 3
1549 with pytest.raises(grpc.RpcError) as e:
1550 api.MarkLastSeenHostRequest(
1551 requests_pb2.MarkLastSeenHostRequestReq(host_request_id=host_request_id, last_seen_message_id=1)
1552 )
1553 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
1554 assert e.value.details() == "You can't unsee messages."
1556 # this will be used to test sent request notifications
1557 host_request_id_3 = api.CreateHostRequest(
1558 requests_pb2.CreateHostRequestReq(
1559 host_user_id=user1.id,
1560 from_date=today_plus_2.isoformat(),
1561 to_date=today_plus_3.isoformat(),
1562 text=valid_request_text("Another test request"),
1563 )
1564 ).host_request_id
1566 moderator.approve_host_request(host_request_id_3)
1568 with requests_session(token2) as api:
1569 # this should make id_2 all read
1570 api.SendHostRequestMessage(
1571 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id_2, text="Test")
1572 )
1574 with api_session(token2) as api:
1575 assert api.Ping(api_pb2.PingReq()).unseen_received_host_request_count == 1
1576 assert api.Ping(api_pb2.PingReq()).unseen_sent_host_request_count == 0
1578 # make sure sent and received count for unseen notifications
1579 with requests_session(token1) as api:
1580 api.SendHostRequestMessage(
1581 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id_3, text="Test message")
1582 )
1584 with api_session(token2) as api:
1585 assert api.Ping(api_pb2.PingReq()).unseen_received_host_request_count == 1
1586 assert api.Ping(api_pb2.PingReq()).unseen_sent_host_request_count == 1
1589def test_mark_last_seen_clears_notifications(db, moderator):
1590 user1, token1 = generate_user()
1591 user2, token2 = generate_user()
1592 today_plus_2 = today() + timedelta(days=2)
1593 today_plus_3 = today() + timedelta(days=3)
1595 with requests_session(token1) as api:
1596 host_request_id = api.CreateHostRequest(
1597 requests_pb2.CreateHostRequestReq(
1598 host_user_id=user2.id,
1599 from_date=today_plus_2.isoformat(),
1600 to_date=today_plus_3.isoformat(),
1601 text=valid_request_text("Test message"),
1602 )
1603 ).host_request_id
1605 moderator.approve_host_request(host_request_id)
1607 def unseen_notification_count(user_id):
1608 with session_scope() as session:
1609 return session.execute(
1610 select(func.count())
1611 .select_from(Notification)
1612 .where(Notification.user_id == user_id)
1613 .where(Notification.key == str(host_request_id))
1614 .where(Notification.is_seen == False)
1615 ).scalar_one()
1617 assert unseen_notification_count(user2.id) > 0
1619 with requests_session(token2) as api:
1620 api.MarkLastSeenHostRequest(
1621 requests_pb2.MarkLastSeenHostRequestReq(host_request_id=host_request_id, last_seen_message_id=1)
1622 )
1624 assert unseen_notification_count(user2.id) == 0
1627def test_response_rate(db, moderator):
1628 user1, token1 = generate_user()
1629 user2, token2 = generate_user()
1630 user3, token3 = generate_user(delete_user=True)
1632 today_plus_2 = today() + timedelta(days=2)
1633 today_plus_3 = today() + timedelta(days=3)
1635 with session_scope() as session:
1636 refresh_materialized_view(session, "user_response_rates")
1638 with requests_session(token1) as api:
1639 # deleted: not found
1640 with pytest.raises(grpc.RpcError) as e:
1641 api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user3.id))
1642 assert e.value.code() == grpc.StatusCode.NOT_FOUND
1643 assert e.value.details() == "Couldn't find that user."
1645 # no requests: insufficient
1646 res = api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user2.id))
1647 assert res.HasField("insufficient_data")
1649 # send a request and back date it by 36 hours
1650 host_request_1 = api.CreateHostRequest(
1651 requests_pb2.CreateHostRequestReq(
1652 host_user_id=user2.id,
1653 from_date=today_plus_2.isoformat(),
1654 to_date=today_plus_3.isoformat(),
1655 text=valid_request_text("Test request"),
1656 )
1657 ).host_request_id
1658 moderator.approve_host_request(host_request_1)
1659 backdate_conversations()
1660 with session_scope() as session:
1661 session.execute(
1662 select(Message)
1663 .where(Message.conversation_id == host_request_1)
1664 .where(Message.message_type == MessageType.chat_created)
1665 ).scalar_one().time = now() - timedelta(hours=36)
1666 refresh_materialized_view(session, "user_response_rates")
1668 # still insufficient
1669 res = api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user2.id))
1670 assert res.HasField("insufficient_data")
1672 # send a request and back date it by 35 hours
1673 host_request_2 = api.CreateHostRequest(
1674 requests_pb2.CreateHostRequestReq(
1675 host_user_id=user2.id,
1676 from_date=today_plus_2.isoformat(),
1677 to_date=today_plus_3.isoformat(),
1678 text=valid_request_text("Test request"),
1679 )
1680 ).host_request_id
1681 moderator.approve_host_request(host_request_2)
1682 backdate_conversations()
1683 with session_scope() as session:
1684 session.execute(
1685 select(Message)
1686 .where(Message.conversation_id == host_request_2)
1687 .where(Message.message_type == MessageType.chat_created)
1688 ).scalar_one().time = now() - timedelta(hours=35)
1689 refresh_materialized_view(session, "user_response_rates")
1691 # still insufficient
1692 res = api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user2.id))
1693 assert res.HasField("insufficient_data")
1695 # send a request and back date it by 34 hours
1696 host_request_3 = api.CreateHostRequest(
1697 requests_pb2.CreateHostRequestReq(
1698 host_user_id=user2.id,
1699 from_date=today_plus_2.isoformat(),
1700 to_date=today_plus_3.isoformat(),
1701 text=valid_request_text("Test request"),
1702 )
1703 ).host_request_id
1704 moderator.approve_host_request(host_request_3)
1705 backdate_conversations()
1706 with session_scope() as session:
1707 session.execute(
1708 select(Message)
1709 .where(Message.conversation_id == host_request_3)
1710 .where(Message.message_type == MessageType.chat_created)
1711 ).scalar_one().time = now() - timedelta(hours=34)
1712 refresh_materialized_view(session, "user_response_rates")
1714 # now low
1715 res = api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user2.id))
1716 assert res.HasField("low")
1718 with requests_session(token2) as api:
1719 # accept a host req
1720 api.RespondHostRequest(
1721 requests_pb2.RespondHostRequestReq(
1722 host_request_id=host_request_2,
1723 status=messages_pb2.HOST_REQUEST_STATUS_ACCEPTED,
1724 text="Accepting host request",
1725 )
1726 )
1728 with session_scope() as session:
1729 refresh_materialized_view(session, "user_response_rates")
1731 with requests_session(token1) as api:
1732 # now some w p33 = 35h
1733 res = api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user2.id))
1734 assert res.HasField("some")
1735 assert res.some.response_time_p33.ToTimedelta() == timedelta(hours=35)
1737 with requests_session(token2) as api:
1738 # accept another host req
1739 api.RespondHostRequest(
1740 requests_pb2.RespondHostRequestReq(
1741 host_request_id=host_request_3,
1742 status=messages_pb2.HOST_REQUEST_STATUS_ACCEPTED,
1743 text="Accepting host request",
1744 )
1745 )
1747 with session_scope() as session:
1748 refresh_materialized_view(session, "user_response_rates")
1750 with requests_session(token1) as api:
1751 # now most w p33 = 34h, p66 = 35h
1752 res = api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user2.id))
1753 assert res.HasField("most")
1754 assert res.most.response_time_p33.ToTimedelta() == timedelta(hours=34)
1755 assert res.most.response_time_p66.ToTimedelta() == timedelta(hours=35)
1757 with requests_session(token2) as api:
1758 # accept last host req
1759 api.RespondHostRequest(
1760 requests_pb2.RespondHostRequestReq(
1761 host_request_id=host_request_1,
1762 status=messages_pb2.HOST_REQUEST_STATUS_ACCEPTED,
1763 text="Accepting host request",
1764 )
1765 )
1767 with session_scope() as session:
1768 refresh_materialized_view(session, "user_response_rates")
1770 with requests_session(token1) as api:
1771 # now all w p33 = 34h, p66 = 35h
1772 res = api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user2.id))
1773 assert res.HasField("almost_all")
1774 assert res.almost_all.response_time_p33.ToTimedelta() == timedelta(hours=34)
1775 assert res.almost_all.response_time_p66.ToTimedelta() == timedelta(hours=35)
1777 # send a request and back date it by 2 hours
1778 host_request_4 = api.CreateHostRequest(
1779 requests_pb2.CreateHostRequestReq(
1780 host_user_id=user2.id,
1781 from_date=today_plus_2.isoformat(),
1782 to_date=today_plus_3.isoformat(),
1783 text=valid_request_text("Test request"),
1784 )
1785 ).host_request_id
1786 moderator.approve_host_request(host_request_4)
1787 backdate_conversations()
1788 with session_scope() as session:
1789 session.execute(
1790 select(Message)
1791 .where(Message.conversation_id == host_request_4)
1792 .where(Message.message_type == MessageType.chat_created)
1793 ).scalar_one().time = now() - timedelta(hours=2)
1794 refresh_materialized_view(session, "user_response_rates")
1796 # send a request and back date it by 4 hours
1797 host_request_5 = api.CreateHostRequest(
1798 requests_pb2.CreateHostRequestReq(
1799 host_user_id=user2.id,
1800 from_date=today_plus_2.isoformat(),
1801 to_date=today_plus_3.isoformat(),
1802 text=valid_request_text("Test request"),
1803 )
1804 ).host_request_id
1805 moderator.approve_host_request(host_request_5)
1806 backdate_conversations()
1807 with session_scope() as session:
1808 session.execute(
1809 select(Message)
1810 .where(Message.conversation_id == host_request_5)
1811 .where(Message.message_type == MessageType.chat_created)
1812 ).scalar_one().time = now() - timedelta(hours=4)
1813 refresh_materialized_view(session, "user_response_rates")
1815 # now some w p33 = 35h
1816 res = api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user2.id))
1817 assert res.HasField("some")
1818 assert res.some.response_time_p33.ToTimedelta() == timedelta(hours=35)
1820 with requests_session(token2) as api:
1821 # accept host req
1822 api.RespondHostRequest(
1823 requests_pb2.RespondHostRequestReq(
1824 host_request_id=host_request_5,
1825 status=messages_pb2.HOST_REQUEST_STATUS_ACCEPTED,
1826 text="Accepting host request",
1827 )
1828 )
1830 with session_scope() as session:
1831 refresh_materialized_view(session, "user_response_rates")
1833 with requests_session(token1) as api:
1834 # now most w p33 = 34h, p66 = 36h
1835 res = api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user2.id))
1836 assert res.HasField("most")
1837 assert res.most.response_time_p33.ToTimedelta() == timedelta(hours=34)
1838 assert res.most.response_time_p66.ToTimedelta() == timedelta(hours=36)
1840 with requests_session(token2) as api:
1841 # accept host req
1842 api.RespondHostRequest(
1843 requests_pb2.RespondHostRequestReq(
1844 host_request_id=host_request_4,
1845 status=messages_pb2.HOST_REQUEST_STATUS_ACCEPTED,
1846 text="Accepting host request",
1847 )
1848 )
1850 with session_scope() as session:
1851 refresh_materialized_view(session, "user_response_rates")
1853 with requests_session(token1) as api:
1854 # now most w p33 = 4h, p66 = 35h
1855 res = api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user2.id))
1856 assert res.HasField("almost_all")
1857 assert res.almost_all.response_time_p33.ToTimedelta() == timedelta(hours=4)
1858 assert res.almost_all.response_time_p66.ToTimedelta() == timedelta(hours=35)
1861def test_request_notifications(db, email_collector: EmailCollector, push_collector: PushCollector, moderator):
1862 host, host_token = generate_user(complete_profile=True)
1863 surfer, surfer_token = generate_user(complete_profile=True)
1865 host_loc_context = LocalizationContext.from_user(host)
1866 surfer_loc_context = LocalizationContext.from_user(surfer)
1868 today_plus_2 = today() + timedelta(days=2)
1869 today_plus_3 = today() + timedelta(days=3)
1871 with requests_session(surfer_token) as api:
1872 hr_id = api.CreateHostRequest(
1873 requests_pb2.CreateHostRequestReq(
1874 host_user_id=host.id,
1875 from_date=today_plus_2.isoformat(),
1876 to_date=today_plus_3.isoformat(),
1877 text=valid_request_text("can i stay plz"),
1878 )
1879 ).host_request_id
1881 moderator.approve_host_request(hr_id)
1883 email = email_collector.pop_for_recipient(host.email, last=True)
1884 assert email.recipient == host.email
1885 assert "host request" in email.subject.lower()
1886 assert host.name in email.plain
1887 assert host.name in email.html
1888 assert "quick decline" in email.plain.lower(), email.plain
1889 assert "quick decline" in email.html.lower()
1890 assert surfer.name in email.plain
1891 assert surfer.name in email.html
1892 assert host_loc_context.localize_date(today_plus_2, with_year=False) in email.plain
1893 assert host_loc_context.localize_date(today_plus_2, with_year=False) in email.html
1894 assert host_loc_context.localize_date(today_plus_3, with_year=False) in email.plain
1895 assert host_loc_context.localize_date(today_plus_3, with_year=False) in email.html
1896 assert "http://localhost:5001/img/thumbnail/" not in email.plain
1897 assert "http://localhost:5001/img/thumbnail/" in email.html
1898 assert f"http://localhost:3000/messages/request/{hr_id}" in email.plain
1899 assert f"http://localhost:3000/messages/request/{hr_id}" in email.html
1900 assert not email.attachments
1902 assert push_collector.pop_for_user(host.id, last=True).content.title == f"New host request from {surfer.name}"
1904 with requests_session(host_token) as api:
1905 api.RespondHostRequest(
1906 requests_pb2.RespondHostRequestReq(
1907 host_request_id=hr_id,
1908 status=messages_pb2.HOST_REQUEST_STATUS_ACCEPTED,
1909 text="Accepting host request",
1910 )
1911 )
1913 email = email_collector.pop_for_recipient(surfer.email, last=True)
1914 assert email.recipient == surfer.email
1915 assert "host request" in email.subject.lower()
1916 assert host.name in email.plain
1917 assert host.name in email.html
1918 assert surfer.name in email.plain
1919 assert surfer.name in email.html
1920 assert surfer_loc_context.localize_date(today_plus_2, with_year=False) in email.plain
1921 assert surfer_loc_context.localize_date(today_plus_2, with_year=False) in email.html
1922 assert surfer_loc_context.localize_date(today_plus_3, with_year=False) in email.plain
1923 assert surfer_loc_context.localize_date(today_plus_3, with_year=False) in email.html
1924 assert "http://localhost:5001/img/thumbnail/" not in email.plain
1925 assert "http://localhost:5001/img/thumbnail/" in email.html
1926 assert f"http://localhost:3000/messages/request/{hr_id}" in email.plain
1927 assert f"http://localhost:3000/messages/request/{hr_id}" in email.html
1928 assert len(email.attachments or []) == 1
1930 assert push_collector.pop_for_user(surfer.id, last=True).content.title == f"{host.name} accepted your host request"
1933def test_quick_decline(db, email_collector: EmailCollector, push_collector: PushCollector, moderator):
1934 host, host_token = generate_user(complete_profile=True)
1935 surfer, surfer_token = generate_user(complete_profile=True)
1937 host_loc_context = LocalizationContext.from_user(host)
1939 today_plus_2 = today() + timedelta(days=2)
1940 today_plus_3 = today() + timedelta(days=3)
1942 with requests_session(surfer_token) as api:
1943 hr_id = api.CreateHostRequest(
1944 requests_pb2.CreateHostRequestReq(
1945 host_user_id=host.id,
1946 from_date=today_plus_2.isoformat(),
1947 to_date=today_plus_3.isoformat(),
1948 text=valid_request_text("can i stay plz"),
1949 )
1950 ).host_request_id
1952 moderator.approve_host_request(hr_id)
1954 email = email_collector.pop_for_recipient(host.email, last=True)
1955 assert email.recipient == host.email
1956 assert "host request" in email.subject.lower()
1957 assert host.name in email.plain
1958 assert host.name in email.html
1959 assert "quick decline" in email.plain.lower(), email.plain
1960 assert "quick decline" in email.html.lower()
1961 assert surfer.name in email.plain
1962 assert surfer.name in email.html
1963 assert host_loc_context.localize_date(today_plus_2, with_year=False) in email.plain
1964 assert host_loc_context.localize_date(today_plus_2, with_year=False) in email.html
1965 assert host_loc_context.localize_date(today_plus_3, with_year=False) in email.plain
1966 assert host_loc_context.localize_date(today_plus_3, with_year=False) in email.html
1967 assert "http://localhost:5001/img/thumbnail/" not in email.plain
1968 assert "http://localhost:5001/img/thumbnail/" in email.html
1969 assert f"http://localhost:3000/messages/request/{hr_id}" in email.plain
1970 assert f"http://localhost:3000/messages/request/{hr_id}" in email.html
1972 assert push_collector.pop_for_user(host.id, last=True).content.title == f"New host request from {surfer.name}"
1974 # very ugly
1975 # http://localhost:3000/quick-link?payload=CAEiGAoOZnJpZW5kX3JlcXVlc3QSBmFjY2VwdA==&sig=BQdk024NTATm8zlR0krSXTBhP5U9TlFv7VhJeIHZtUg=
1976 for link in re.findall(r'<a href="(.*?)"', email.html): 1976 ↛ 1995line 1976 didn't jump to line 1995 because the loop on line 1976 didn't complete
1977 if "payload" not in link:
1978 continue
1979 print(link)
1980 url_parts = urlparse(html.unescape(link))
1981 params = parse_qs(url_parts.query)
1982 print(params["payload"][0])
1983 payload = unsubscribe_pb2.UnsubscribePayload.FromString(b64decode(params["payload"][0]))
1984 if payload.HasField("host_request_quick_decline"): 1984 ↛ 1976line 1984 didn't jump to line 1976 because the condition on line 1984 was always true
1985 with auth_api_session() as (auth_api, metadata_interceptor):
1986 res = auth_api.Unsubscribe(
1987 auth_pb2.UnsubscribeReq(
1988 payload=b64decode(params["payload"][0]),
1989 sig=b64decode(params["sig"][0]),
1990 )
1991 )
1992 assert res.response == "Thank you for responding to the host request!"
1993 break
1994 else:
1995 raise Exception("Didn't find link")
1997 with requests_session(surfer_token) as api:
1998 res = api.GetHostRequest(requests_pb2.GetHostRequestReq(host_request_id=hr_id))
1999 assert res.status == messages_pb2.HOST_REQUEST_STATUS_REJECTED
2002def test_host_req_feedback(db, moderator):
2003 host, host_token = generate_user(complete_profile=True)
2004 host2, host2_token = generate_user(complete_profile=True)
2005 host3, host3_token = generate_user(complete_profile=True)
2006 surfer, surfer_token = generate_user(complete_profile=True)
2008 today_plus_2 = today() + timedelta(days=2)
2009 today_plus_3 = today() + timedelta(days=3)
2011 with requests_session(surfer_token) as api:
2012 hr_id = api.CreateHostRequest(
2013 requests_pb2.CreateHostRequestReq(
2014 host_user_id=host.id,
2015 from_date=today_plus_2.isoformat(),
2016 to_date=today_plus_3.isoformat(),
2017 text=valid_request_text("can i stay plz"),
2018 )
2019 ).host_request_id
2020 hr2_id = api.CreateHostRequest(
2021 requests_pb2.CreateHostRequestReq(
2022 host_user_id=host2.id,
2023 from_date=today_plus_2.isoformat(),
2024 to_date=today_plus_3.isoformat(),
2025 text=valid_request_text("can i stay plz"),
2026 )
2027 ).host_request_id
2028 hr3_id = api.CreateHostRequest(
2029 requests_pb2.CreateHostRequestReq(
2030 host_user_id=host3.id,
2031 from_date=today_plus_2.isoformat(),
2032 to_date=today_plus_3.isoformat(),
2033 text=valid_request_text("can i stay plz"),
2034 )
2035 ).host_request_id
2037 moderator.approve_host_request(hr_id)
2038 moderator.approve_host_request(hr2_id)
2039 moderator.approve_host_request(hr3_id)
2041 with requests_session(host_token) as api:
2042 res = api.GetHostRequest(requests_pb2.GetHostRequestReq(host_request_id=hr_id))
2043 assert not res.need_host_request_feedback
2045 api.RespondHostRequest(
2046 requests_pb2.RespondHostRequestReq(
2047 host_request_id=hr_id,
2048 status=messages_pb2.HOST_REQUEST_STATUS_REJECTED,
2049 )
2050 )
2052 res = api.GetHostRequest(requests_pb2.GetHostRequestReq(host_request_id=hr_id))
2053 assert res.need_host_request_feedback
2055 # surfer can't leave feedback
2056 with requests_session(surfer_token) as api:
2057 with pytest.raises(grpc.RpcError) as e:
2058 api.SendHostRequestFeedback(
2059 requests_pb2.SendHostRequestFeedbackReq(
2060 host_request_id=hr_id,
2061 )
2062 )
2063 assert e.value.code() == grpc.StatusCode.NOT_FOUND
2064 assert e.value.details() == "Couldn't find that host request."
2066 with requests_session(host_token) as api:
2067 api.SendHostRequestFeedback(
2068 requests_pb2.SendHostRequestFeedbackReq(
2069 host_request_id=hr_id,
2070 host_request_quality=requests_pb2.HOST_REQUEST_QUALITY_LOW,
2071 )
2072 )
2073 res = api.GetHostRequest(requests_pb2.GetHostRequestReq(host_request_id=hr_id))
2074 assert not res.need_host_request_feedback
2076 # can't leave it twice
2077 with requests_session(host_token) as api:
2078 with pytest.raises(grpc.RpcError) as e:
2079 api.SendHostRequestFeedback(
2080 requests_pb2.SendHostRequestFeedbackReq(
2081 host_request_id=hr_id,
2082 )
2083 )
2084 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
2085 assert e.value.details() == "You have already left feedback for this host request!"
2087 res = api.GetHostRequest(requests_pb2.GetHostRequestReq(host_request_id=hr_id))
2088 assert not res.need_host_request_feedback
2090 with requests_session(host2_token) as api:
2091 api.RespondHostRequest(
2092 requests_pb2.RespondHostRequestReq(host_request_id=hr2_id, status=messages_pb2.HOST_REQUEST_STATUS_REJECTED)
2093 )
2094 # can't leave feedback on the wrong one
2095 with pytest.raises(grpc.RpcError) as e:
2096 api.SendHostRequestFeedback(
2097 requests_pb2.SendHostRequestFeedbackReq(
2098 host_request_id=hr_id,
2099 )
2100 )
2101 assert e.value.code() == grpc.StatusCode.NOT_FOUND
2102 assert e.value.details() == "Couldn't find that host request."
2104 # null feedback is still feedback
2105 api.SendHostRequestFeedback(requests_pb2.SendHostRequestFeedbackReq(host_request_id=hr2_id))
2107 with requests_session(host3_token) as api:
2108 api.RespondHostRequest(
2109 requests_pb2.RespondHostRequestReq(host_request_id=hr3_id, status=messages_pb2.HOST_REQUEST_STATUS_REJECTED)
2110 )
2112 api.SendHostRequestFeedback(
2113 requests_pb2.SendHostRequestFeedbackReq(host_request_id=hr3_id, decline_reason="bad req")
2114 )
2117def _make_trip_node_admin(user_id: int, trip_id: int):
2118 with session_scope() as session:
2119 node_id = session.execute(select(PublicTrip.node_id).where(PublicTrip.id == trip_id)).scalar_one()
2120 cluster = session.execute(
2121 select(Cluster).where(Cluster.parent_node_id == node_id).where(Cluster.is_official_cluster)
2122 ).scalar_one_or_none()
2123 if cluster is None: 2123 ↛ 2132line 2123 didn't jump to line 2132 because the condition on line 2123 was always true
2124 cluster = Cluster(
2125 name="Test community",
2126 description="Test",
2127 parent_node_id=node_id,
2128 is_official_cluster=True,
2129 )
2130 session.add(cluster)
2131 session.flush()
2132 session.add(ClusterSubscription(cluster_id=cluster.id, user_id=user_id, role=ClusterRole.admin))
2135def _create_public_trip(
2136 user_id: int,
2137 from_date,
2138 to_date,
2139 *,
2140 status=None,
2141 same_gender_only: bool = False,
2142 visibility: ModerationVisibility = ModerationVisibility.visible,
2143):
2144 with session_scope() as session:
2145 node = session.execute(select(Node).limit(1)).scalar_one_or_none()
2146 if node is None:
2147 node = Node(
2148 geom=to_multi(create_polygon_lat_lng([[0, 0], [0, 2], [2, 2], [2, 0], [0, 0]])),
2149 node_type=NodeType.locality,
2150 )
2151 session.add(node)
2152 session.flush()
2153 moderation_state = ModerationState(
2154 object_type=ModerationObjectType.public_trip,
2155 object_id=0, # placeholder, set after PublicTrip flush
2156 visibility=visibility,
2157 )
2158 session.add(moderation_state)
2159 session.flush()
2160 trip = PublicTrip(
2161 user_id=user_id,
2162 node_id=node.id,
2163 from_date=from_date,
2164 to_date=to_date,
2165 description="Looking for a host!",
2166 status=status or PublicTripStatus.searching_for_host,
2167 same_gender_only=same_gender_only,
2168 moderation_state_id=moderation_state.id,
2169 )
2170 session.add(trip)
2171 session.flush()
2172 moderation_state.object_id = trip.id
2173 return trip.id
2176def test_create_request_with_public_trip(db, moderator):
2177 """Hosts can offer to host a public trip; offered dates must be within trip dates."""
2178 surfer, surfer_token = generate_user()
2179 host, host_token = generate_user()
2181 trip_from = today() + timedelta(days=10)
2182 trip_to = today() + timedelta(days=20)
2183 trip_id = _create_public_trip(surfer.id, trip_from, trip_to)
2185 with requests_session(host_token) as api:
2186 # Happy path: dates within trip window
2187 res = api.CreateHostRequest(
2188 requests_pb2.CreateHostRequestReq(
2189 host_user_id=surfer.id,
2190 from_date=(trip_from + timedelta(days=1)).isoformat(),
2191 to_date=(trip_to - timedelta(days=1)).isoformat(),
2192 text=valid_request_text(),
2193 public_trip_id=trip_id,
2194 )
2195 )
2196 host_request_id = res.host_request_id
2198 moderator.approve_host_request(host_request_id)
2200 with requests_session(host_token) as api:
2201 hr = api.GetHostRequest(requests_pb2.GetHostRequestReq(host_request_id=host_request_id))
2202 assert hr.public_trip_id == trip_id
2205def test_create_request_with_public_trip_dates_out_of_range(db):
2206 """Offered dates outside the trip window are rejected."""
2207 surfer, _ = generate_user()
2208 host, host_token = generate_user()
2210 trip_from = today() + timedelta(days=10)
2211 trip_to = today() + timedelta(days=20)
2212 trip_id = _create_public_trip(surfer.id, trip_from, trip_to)
2214 with requests_session(host_token) as api:
2215 # from_date before trip starts
2216 with pytest.raises(grpc.RpcError) as e:
2217 api.CreateHostRequest(
2218 requests_pb2.CreateHostRequestReq(
2219 host_user_id=surfer.id,
2220 from_date=(trip_from - timedelta(days=1)).isoformat(),
2221 to_date=(trip_from + timedelta(days=1)).isoformat(),
2222 text=valid_request_text(),
2223 public_trip_id=trip_id,
2224 )
2225 )
2226 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
2228 # to_date after trip ends
2229 with pytest.raises(grpc.RpcError) as e:
2230 api.CreateHostRequest(
2231 requests_pb2.CreateHostRequestReq(
2232 host_user_id=surfer.id,
2233 from_date=(trip_to - timedelta(days=1)).isoformat(),
2234 to_date=(trip_to + timedelta(days=1)).isoformat(),
2235 text=valid_request_text(),
2236 public_trip_id=trip_id,
2237 )
2238 )
2239 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
2242def test_create_request_with_public_trip_user_mismatch(db):
2243 """The host_user_id must match the public trip's traveler."""
2244 trip_owner, _ = generate_user()
2245 other_user, _ = generate_user()
2246 host, host_token = generate_user()
2248 trip_from = today() + timedelta(days=10)
2249 trip_to = today() + timedelta(days=20)
2250 trip_id = _create_public_trip(trip_owner.id, trip_from, trip_to)
2252 with requests_session(host_token) as api:
2253 with pytest.raises(grpc.RpcError) as e:
2254 api.CreateHostRequest(
2255 requests_pb2.CreateHostRequestReq(
2256 host_user_id=other_user.id, # not the trip owner
2257 from_date=(trip_from + timedelta(days=1)).isoformat(),
2258 to_date=(trip_to - timedelta(days=1)).isoformat(),
2259 text=valid_request_text(),
2260 public_trip_id=trip_id,
2261 )
2262 )
2263 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
2266def test_create_request_with_closed_public_trip(db):
2267 """Cannot offer to host a trip that's been closed."""
2268 surfer, _ = generate_user()
2269 host, host_token = generate_user()
2271 trip_from = today() + timedelta(days=10)
2272 trip_to = today() + timedelta(days=20)
2273 trip_id = _create_public_trip(surfer.id, trip_from, trip_to, status=PublicTripStatus.closed)
2275 with requests_session(host_token) as api:
2276 with pytest.raises(grpc.RpcError) as e:
2277 api.CreateHostRequest(
2278 requests_pb2.CreateHostRequestReq(
2279 host_user_id=surfer.id,
2280 from_date=(trip_from + timedelta(days=1)).isoformat(),
2281 to_date=(trip_to - timedelta(days=1)).isoformat(),
2282 text=valid_request_text(),
2283 public_trip_id=trip_id,
2284 )
2285 )
2286 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
2289def test_create_request_with_nonexistent_public_trip(db):
2290 """Nonexistent public trip ID returns NOT_FOUND."""
2291 surfer, _ = generate_user()
2292 host, host_token = generate_user()
2294 with requests_session(host_token) as api:
2295 with pytest.raises(grpc.RpcError) as e:
2296 api.CreateHostRequest(
2297 requests_pb2.CreateHostRequestReq(
2298 host_user_id=surfer.id,
2299 from_date=(today() + timedelta(days=2)).isoformat(),
2300 to_date=(today() + timedelta(days=3)).isoformat(),
2301 text=valid_request_text(),
2302 public_trip_id=999999,
2303 )
2304 )
2305 assert e.value.code() == grpc.StatusCode.NOT_FOUND
2308def test_create_request_with_shadowed_public_trip(db, moderator):
2309 """A trip awaiting moderation can't be offered on until it's approved."""
2310 surfer, _ = generate_user()
2311 host, host_token = generate_user()
2313 trip_from = today() + timedelta(days=10)
2314 trip_to = today() + timedelta(days=20)
2315 trip_id = _create_public_trip(surfer.id, trip_from, trip_to, visibility=ModerationVisibility.shadowed)
2317 with requests_session(host_token) as api:
2318 with pytest.raises(grpc.RpcError) as e:
2319 api.CreateHostRequest(
2320 requests_pb2.CreateHostRequestReq(
2321 host_user_id=surfer.id,
2322 from_date=(trip_from + timedelta(days=1)).isoformat(),
2323 to_date=(trip_to - timedelta(days=1)).isoformat(),
2324 text=valid_request_text(),
2325 public_trip_id=trip_id,
2326 )
2327 )
2328 assert e.value.code() == grpc.StatusCode.NOT_FOUND
2329 assert e.value.details() == "Couldn't find that public trip."
2331 moderator.approve_public_trip(trip_id)
2333 with requests_session(host_token) as api:
2334 res = api.CreateHostRequest(
2335 requests_pb2.CreateHostRequestReq(
2336 host_user_id=surfer.id,
2337 from_date=(trip_from + timedelta(days=1)).isoformat(),
2338 to_date=(trip_to - timedelta(days=1)).isoformat(),
2339 text=valid_request_text(),
2340 public_trip_id=trip_id,
2341 )
2342 )
2343 assert res.host_request_id
2346def test_create_request_with_hidden_public_trip(db):
2347 """A hidden trip can't be offered on."""
2348 surfer, _ = generate_user()
2349 host, host_token = generate_user()
2351 trip_from = today() + timedelta(days=10)
2352 trip_to = today() + timedelta(days=20)
2353 trip_id = _create_public_trip(surfer.id, trip_from, trip_to, visibility=ModerationVisibility.hidden)
2355 with requests_session(host_token) as api:
2356 with pytest.raises(grpc.RpcError) as e:
2357 api.CreateHostRequest(
2358 requests_pb2.CreateHostRequestReq(
2359 host_user_id=surfer.id,
2360 from_date=(trip_from + timedelta(days=1)).isoformat(),
2361 to_date=(trip_to - timedelta(days=1)).isoformat(),
2362 text=valid_request_text(),
2363 public_trip_id=trip_id,
2364 )
2365 )
2366 assert e.value.code() == grpc.StatusCode.NOT_FOUND
2367 assert e.value.details() == "Couldn't find that public trip."
2370def test_create_request_without_public_trip_id_unchanged(db, moderator):
2371 """Existing flow without public_trip_id still works (backwards compatibility)."""
2372 surfer, _ = generate_user()
2373 host, host_token = generate_user()
2375 with requests_session(host_token) as api:
2376 res = api.CreateHostRequest(
2377 requests_pb2.CreateHostRequestReq(
2378 host_user_id=surfer.id,
2379 from_date=(today() + timedelta(days=2)).isoformat(),
2380 to_date=(today() + timedelta(days=3)).isoformat(),
2381 text=valid_request_text(),
2382 )
2383 )
2384 host_request_id = res.host_request_id
2386 moderator.approve_host_request(host_request_id)
2388 with requests_session(host_token) as api:
2389 hr = api.GetHostRequest(requests_pb2.GetHostRequestReq(host_request_id=host_request_id))
2390 assert not hr.HasField("public_trip_id")
2393def test_create_request_same_gender_only_wrong_gender_rejected(db):
2394 surfer, _ = generate_user(gender="Woman")
2395 _, host_token = generate_user(gender="Man")
2397 trip_from = today() + timedelta(days=10)
2398 trip_to = today() + timedelta(days=20)
2399 trip_id = _create_public_trip(surfer.id, trip_from, trip_to, same_gender_only=True)
2401 with requests_session(host_token) as api:
2402 with pytest.raises(grpc.RpcError) as e:
2403 api.CreateHostRequest(
2404 requests_pb2.CreateHostRequestReq(
2405 host_user_id=surfer.id,
2406 from_date=trip_from.isoformat(),
2407 to_date=trip_to.isoformat(),
2408 text=valid_request_text(),
2409 public_trip_id=trip_id,
2410 )
2411 )
2412 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
2415def test_create_request_same_gender_only_same_gender_allowed(db, moderator):
2416 surfer, _ = generate_user(gender="Woman")
2417 _, host_token = generate_user(gender="Woman")
2419 trip_from = today() + timedelta(days=10)
2420 trip_to = today() + timedelta(days=20)
2421 trip_id = _create_public_trip(surfer.id, trip_from, trip_to, same_gender_only=True)
2423 with requests_session(host_token) as api:
2424 res = api.CreateHostRequest(
2425 requests_pb2.CreateHostRequestReq(
2426 host_user_id=surfer.id,
2427 from_date=trip_from.isoformat(),
2428 to_date=trip_to.isoformat(),
2429 text=valid_request_text(),
2430 public_trip_id=trip_id,
2431 )
2432 )
2433 assert res.host_request_id > 0
2436def test_create_request_same_gender_only_moderator_bypass(db, moderator):
2437 surfer, _ = generate_user(gender="Woman")
2438 host, host_token = generate_user(gender="Man")
2440 trip_from = today() + timedelta(days=10)
2441 trip_to = today() + timedelta(days=20)
2442 trip_id = _create_public_trip(surfer.id, trip_from, trip_to, same_gender_only=True)
2443 _make_trip_node_admin(host.id, trip_id)
2445 with requests_session(host_token) as api:
2446 res = api.CreateHostRequest(
2447 requests_pb2.CreateHostRequestReq(
2448 host_user_id=surfer.id,
2449 from_date=trip_from.isoformat(),
2450 to_date=trip_to.isoformat(),
2451 text=valid_request_text(),
2452 public_trip_id=trip_id,
2453 )
2454 )
2455 assert res.host_request_id > 0
2458def test_create_request_duplicate_offer_rejected(db):
2459 surfer, _ = generate_user()
2460 _, host_token = generate_user()
2462 trip_from = today() + timedelta(days=10)
2463 trip_to = today() + timedelta(days=20)
2464 trip_id = _create_public_trip(surfer.id, trip_from, trip_to)
2466 with requests_session(host_token) as api:
2467 api.CreateHostRequest(
2468 requests_pb2.CreateHostRequestReq(
2469 host_user_id=surfer.id,
2470 from_date=trip_from.isoformat(),
2471 to_date=trip_to.isoformat(),
2472 text=valid_request_text(),
2473 public_trip_id=trip_id,
2474 )
2475 )
2476 with pytest.raises(grpc.RpcError) as e:
2477 api.CreateHostRequest(
2478 requests_pb2.CreateHostRequestReq(
2479 host_user_id=surfer.id,
2480 from_date=trip_from.isoformat(),
2481 to_date=trip_to.isoformat(),
2482 text=valid_request_text(),
2483 public_trip_id=trip_id,
2484 )
2485 )
2486 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION