Coverage for app / backend / src / tests / test_requests.py: 99%
857 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-05-05 09:44 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-05-05 09:44 +0000
1import html
2import re
3from datetime import timedelta
4from urllib.parse import parse_qs, urlparse
6import grpc
7import pytest
8from sqlalchemy import select
9from sqlalchemy_utils import refresh_materialized_view
11from couchers.constants import HOST_REQUEST_MIN_LENGTH_UTF16
12from couchers.crypto import b64decode
13from couchers.db import session_scope
14from couchers.i18n import LocalizationContext
15from couchers.models import (
16 Cluster,
17 ClusterRole,
18 ClusterSubscription,
19 Message,
20 MessageType,
21 Node,
22 NodeType,
23 RateLimitAction,
24)
25from couchers.models.public_trips import PublicTrip, PublicTripStatus
26from couchers.proto import (
27 api_pb2,
28 auth_pb2,
29 conversations_pb2,
30 requests_pb2,
31)
32from couchers.proto.internal import unsubscribe_pb2
33from couchers.rate_limits.definitions import RATE_LIMIT_DEFINITIONS, RATE_LIMIT_HOURS
34from couchers.utils import create_coordinate, create_polygon_lat_lng, now, to_multi, today
35from tests.fixtures.db import generate_user
36from tests.fixtures.misc import PushCollector, email_fields, mock_notification_email
37from tests.fixtures.sessions import api_session, auth_api_session, requests_session
40@pytest.fixture(autouse=True)
41def _(testconfig):
42 pass
45def valid_request_text(text: str = "Test request") -> str:
46 """Pads a request text to a valid length."""
47 # Request lengths are measured in utf-16 code units to match the frontend.
48 utf16_length = len(text.encode("utf-16-le")) // 2
49 if utf16_length >= HOST_REQUEST_MIN_LENGTH_UTF16: 49 ↛ 50line 49 didn't jump to line 50 because the condition on line 49 was never true
50 return text
51 padding_length = HOST_REQUEST_MIN_LENGTH_UTF16 - utf16_length
52 return text + ("_" * padding_length) # Each "_" adds one utf16 code unit.
55def test_create_request(db, moderator):
56 user1, token1 = generate_user()
57 hosting_city = "Morningside Heights, New York City"
58 hosting_lat = 40.8086
59 hosting_lng = -73.9616
60 hosting_radius = 500
61 user2, token2 = generate_user(
62 city=hosting_city,
63 geom=create_coordinate(hosting_lat, hosting_lng),
64 geom_radius=hosting_radius,
65 )
67 today_plus_2 = today() + timedelta(days=2)
68 today_plus_3 = today() + timedelta(days=3)
69 today_minus_2 = today() - timedelta(days=2)
70 today_minus_3 = today() - timedelta(days=3)
72 with requests_session(token1) as api:
73 with pytest.raises(grpc.RpcError) as e:
74 api.CreateHostRequest(
75 requests_pb2.CreateHostRequestReq(
76 host_user_id=user1.id,
77 from_date=today_plus_2.isoformat(),
78 to_date=today_plus_3.isoformat(),
79 text=valid_request_text(),
80 )
81 )
82 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
83 assert e.value.details() == "Can't request hosting from yourself."
85 with pytest.raises(grpc.RpcError) as e:
86 api.CreateHostRequest(
87 requests_pb2.CreateHostRequestReq(
88 host_user_id=999,
89 from_date=today_plus_2.isoformat(),
90 to_date=today_plus_3.isoformat(),
91 text=valid_request_text(),
92 )
93 )
94 assert e.value.code() == grpc.StatusCode.NOT_FOUND
95 assert e.value.details() == "Couldn't find that user."
97 with pytest.raises(grpc.RpcError) as e:
98 api.CreateHostRequest(
99 requests_pb2.CreateHostRequestReq(
100 host_user_id=user2.id,
101 from_date=today_plus_3.isoformat(),
102 to_date=today_plus_2.isoformat(),
103 text=valid_request_text(),
104 )
105 )
106 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
107 assert e.value.details() == "From date can't be after to date."
109 with pytest.raises(grpc.RpcError) as e:
110 api.CreateHostRequest(
111 requests_pb2.CreateHostRequestReq(
112 host_user_id=user2.id,
113 from_date=today_minus_3.isoformat(),
114 to_date=today_plus_2.isoformat(),
115 text=valid_request_text(),
116 )
117 )
118 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
119 assert e.value.details() == "From date must be today or later."
121 with pytest.raises(grpc.RpcError) as e:
122 api.CreateHostRequest(
123 requests_pb2.CreateHostRequestReq(
124 host_user_id=user2.id,
125 from_date=today_plus_2.isoformat(),
126 to_date=today_minus_2.isoformat(),
127 text=valid_request_text(),
128 )
129 )
130 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
131 assert e.value.details() == "From date can't be after to date."
133 with pytest.raises(grpc.RpcError) as e:
134 api.CreateHostRequest(
135 requests_pb2.CreateHostRequestReq(
136 host_user_id=user2.id,
137 from_date="2020-00-06",
138 to_date=today_minus_2.isoformat(),
139 text=valid_request_text(),
140 )
141 )
142 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
143 assert e.value.details() == "Invalid date."
145 with pytest.raises(grpc.RpcError) as e:
146 api.CreateHostRequest(
147 requests_pb2.CreateHostRequestReq(
148 host_user_id=user2.id,
149 from_date=today_plus_2.isoformat(),
150 to_date=today_plus_3.isoformat(),
151 text="Too short.",
152 )
153 )
154 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
155 assert e.value.details() == "Host request cannot be shorter than 250 characters."
157 res = api.CreateHostRequest(
158 requests_pb2.CreateHostRequestReq(
159 host_user_id=user2.id,
160 from_date=today_plus_2.isoformat(),
161 to_date=today_plus_3.isoformat(),
162 text=valid_request_text(),
163 )
164 )
165 host_request_id = res.host_request_id
167 moderator.approve_host_request(host_request_id)
169 with requests_session(token1) as api:
170 host_requests = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_sent=True)).host_requests
172 assert len(host_requests) == 1
173 hr = host_requests[0]
175 assert hr.latest_message.text.text == valid_request_text()
177 assert hr.hosting_city == hosting_city
178 assert round(hr.hosting_lat, 4) == hosting_lat
179 assert round(hr.hosting_lng, 4) == hosting_lng
180 assert hr.hosting_radius == hosting_radius
182 today_ = today()
183 today_plus_one_year = today_ + timedelta(days=365)
184 today_plus_one_year_plus_2 = today_plus_one_year + timedelta(days=2)
185 today_plus_one_year_plus_3 = today_plus_one_year + timedelta(days=3)
186 with pytest.raises(grpc.RpcError) as e:
187 api.CreateHostRequest(
188 requests_pb2.CreateHostRequestReq(
189 host_user_id=user2.id,
190 from_date=today_plus_one_year_plus_2.isoformat(),
191 to_date=today_plus_one_year_plus_3.isoformat(),
192 text=valid_request_text("Test from date after one year"),
193 )
194 )
195 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
196 assert e.value.details() == "The start date must be within one year from today."
198 with pytest.raises(grpc.RpcError) as e:
199 api.CreateHostRequest(
200 requests_pb2.CreateHostRequestReq(
201 host_user_id=user2.id,
202 from_date=today_plus_2.isoformat(),
203 to_date=today_plus_one_year_plus_3.isoformat(),
204 text=valid_request_text("Test to date one year after from date"),
205 )
206 )
207 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
208 assert e.value.details() == "You cannot request to stay with someone for longer than one year."
211def test_create_request_incomplete_profile(db):
212 user1, token1 = generate_user(complete_profile=False)
213 user2, _ = generate_user()
214 today_plus_2 = today() + timedelta(days=2)
215 today_plus_3 = today() + timedelta(days=3)
216 with requests_session(token1) as api:
217 with pytest.raises(grpc.RpcError) as e:
218 api.CreateHostRequest(
219 requests_pb2.CreateHostRequestReq(
220 host_user_id=user2.id,
221 from_date=today_plus_2.isoformat(),
222 to_date=today_plus_3.isoformat(),
223 text=valid_request_text(),
224 )
225 )
226 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
227 assert e.value.details() == "You have to complete your profile before you can send a request."
230def test_excessive_requests_are_reported(db):
231 """Test that excessive host requests are first reported in a warning email and finally lead blocking of further requests."""
232 user, token = generate_user()
233 today_plus_2 = today() + timedelta(days=2)
234 today_plus_3 = today() + timedelta(days=3)
235 rate_limit_definition = RATE_LIMIT_DEFINITIONS[RateLimitAction.host_request]
236 with requests_session(token) as api:
237 # Test warning email
238 with mock_notification_email() as mock_email:
239 for _ in range(rate_limit_definition.warning_limit):
240 host_user, _ = generate_user()
241 _ = api.CreateHostRequest(
242 requests_pb2.CreateHostRequestReq(
243 host_user_id=host_user.id,
244 from_date=today_plus_2.isoformat(),
245 to_date=today_plus_3.isoformat(),
246 text=valid_request_text(),
247 )
248 )
250 assert mock_email.call_count == 0
251 host_user, _ = generate_user()
252 _ = api.CreateHostRequest(
253 requests_pb2.CreateHostRequestReq(
254 host_user_id=host_user.id,
255 from_date=today_plus_2.isoformat(),
256 to_date=today_plus_3.isoformat(),
257 text=valid_request_text("Excessive test request"),
258 )
259 )
260 assert mock_email.call_count == 1
261 email = email_fields(mock_email).plain
262 assert email.startswith(
263 f"User {user.username} has sent {rate_limit_definition.warning_limit} host requests in the past {RATE_LIMIT_HOURS} hours."
264 )
266 # Test ban after exceeding HOST_REQUEST_HARD_LIMIT
267 with mock_notification_email() as mock_email:
268 for _ in range(rate_limit_definition.hard_limit - rate_limit_definition.warning_limit - 1):
269 host_user, _ = generate_user()
270 _ = api.CreateHostRequest(
271 requests_pb2.CreateHostRequestReq(
272 host_user_id=host_user.id,
273 from_date=today_plus_2.isoformat(),
274 to_date=today_plus_3.isoformat(),
275 text=valid_request_text(),
276 )
277 )
279 assert mock_email.call_count == 0
280 host_user, _ = generate_user()
281 with pytest.raises(grpc.RpcError) as exc_info:
282 _ = api.CreateHostRequest(
283 requests_pb2.CreateHostRequestReq(
284 host_user_id=host_user.id,
285 from_date=today_plus_2.isoformat(),
286 to_date=today_plus_3.isoformat(),
287 text=valid_request_text("Excessive test request"),
288 )
289 )
290 assert exc_info.value.code() == grpc.StatusCode.RESOURCE_EXHAUSTED
291 assert (
292 exc_info.value.details()
293 == "You have sent a lot of host requests in the past 24 hours. To avoid spam, you can't send any more for now."
294 )
296 assert mock_email.call_count == 1
297 email = email_fields(mock_email).plain
298 assert email.startswith(
299 f"User {user.username} has sent {rate_limit_definition.hard_limit} host requests in the past {RATE_LIMIT_HOURS} hours."
300 )
301 assert "The user has been blocked from sending further host requests for now." in email
304def add_message(db, text, author_id, conversation_id):
305 with session_scope() as session:
306 message = Message(
307 conversation_id=conversation_id, author_id=author_id, text=text, message_type=MessageType.text
308 )
310 session.add(message)
313def test_GetHostRequest(db):
314 user1, token1 = generate_user()
315 user2, token2 = generate_user()
316 user3, token3 = generate_user()
317 today_plus_2 = today() + timedelta(days=2)
318 today_plus_3 = today() + timedelta(days=3)
319 with requests_session(token1) as api:
320 host_request_id = api.CreateHostRequest(
321 requests_pb2.CreateHostRequestReq(
322 host_user_id=user2.id,
323 from_date=today_plus_2.isoformat(),
324 to_date=today_plus_3.isoformat(),
325 text=valid_request_text("Test request 1"),
326 )
327 ).host_request_id
329 with pytest.raises(grpc.RpcError) as e:
330 api.GetHostRequest(requests_pb2.GetHostRequestReq(host_request_id=999))
331 assert e.value.code() == grpc.StatusCode.NOT_FOUND
332 assert e.value.details() == "Couldn't find that host request."
334 api.SendHostRequestMessage(
335 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 1")
336 )
338 res = api.GetHostRequest(requests_pb2.GetHostRequestReq(host_request_id=host_request_id))
339 assert res.latest_message.text.text == "Test message 1"
342def test_ListHostRequests(db, moderator):
343 user1, token1 = generate_user()
344 user2, token2 = generate_user()
345 user3, token3 = generate_user()
346 today_plus_2 = today() + timedelta(days=2)
347 today_plus_3 = today() + timedelta(days=3)
348 with requests_session(token1) as api:
349 host_request_1 = api.CreateHostRequest(
350 requests_pb2.CreateHostRequestReq(
351 host_user_id=user2.id,
352 from_date=today_plus_2.isoformat(),
353 to_date=today_plus_3.isoformat(),
354 text=valid_request_text("Test request 1"),
355 )
356 ).host_request_id
358 host_request_2 = api.CreateHostRequest(
359 requests_pb2.CreateHostRequestReq(
360 host_user_id=user3.id,
361 from_date=today_plus_2.isoformat(),
362 to_date=today_plus_3.isoformat(),
363 text=valid_request_text("Test request 2"),
364 )
365 ).host_request_id
367 moderator.approve_host_request(host_request_1)
368 moderator.approve_host_request(host_request_2)
370 with requests_session(token1) as api:
371 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_sent=True))
372 assert res.no_more
373 assert len(res.host_requests) == 2
375 with requests_session(token2) as api:
376 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_received=True))
377 assert res.no_more
378 assert len(res.host_requests) == 1
379 assert res.host_requests[0].latest_message.text.text == valid_request_text("Test request 1")
380 assert res.host_requests[0].surfer_user_id == user1.id
381 assert res.host_requests[0].host_user_id == user2.id
382 assert res.host_requests[0].status == conversations_pb2.HOST_REQUEST_STATUS_PENDING
384 add_message(db, "Test request 1 message 1", user2.id, host_request_1)
385 add_message(db, "Test request 1 message 2", user2.id, host_request_1)
386 add_message(db, "Test request 1 message 3", user2.id, host_request_1)
388 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_received=True))
389 assert res.host_requests[0].latest_message.text.text == "Test request 1 message 3"
391 host_request_3 = api.CreateHostRequest(
392 requests_pb2.CreateHostRequestReq(
393 host_user_id=user1.id,
394 from_date=today_plus_2.isoformat(),
395 to_date=today_plus_3.isoformat(),
396 text=valid_request_text("Test request 3"),
397 )
398 ).host_request_id
400 moderator.approve_host_request(host_request_3)
402 add_message(db, "Test request 2 message 1", user1.id, host_request_2)
403 add_message(db, "Test request 2 message 2", user3.id, host_request_2)
405 with requests_session(token3) as api:
406 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_received=True))
407 assert res.no_more
408 assert len(res.host_requests) == 1
409 assert res.host_requests[0].latest_message.text.text == "Test request 2 message 2"
411 with requests_session(token1) as api:
412 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_received=True))
413 assert len(res.host_requests) == 1
415 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq())
416 assert len(res.host_requests) == 3
419def test_ListHostRequests_pagination_regression(db, moderator):
420 """
421 ListHostRequests was skipping a request when getting multiple pages
422 """
423 user1, token1 = generate_user()
424 user2, token2 = generate_user()
425 today_plus_2 = today() + timedelta(days=2)
426 today_plus_3 = today() + timedelta(days=3)
427 with requests_session(token1) as api:
428 host_request_1 = api.CreateHostRequest(
429 requests_pb2.CreateHostRequestReq(
430 host_user_id=user2.id,
431 from_date=today_plus_2.isoformat(),
432 to_date=today_plus_3.isoformat(),
433 text=valid_request_text("Test request 1"),
434 )
435 ).host_request_id
437 host_request_2 = api.CreateHostRequest(
438 requests_pb2.CreateHostRequestReq(
439 host_user_id=user2.id,
440 from_date=today_plus_2.isoformat(),
441 to_date=today_plus_3.isoformat(),
442 text=valid_request_text("Test request 2"),
443 )
444 ).host_request_id
446 host_request_3 = api.CreateHostRequest(
447 requests_pb2.CreateHostRequestReq(
448 host_user_id=user2.id,
449 from_date=today_plus_2.isoformat(),
450 to_date=today_plus_3.isoformat(),
451 text=valid_request_text("Test request 3"),
452 )
453 ).host_request_id
455 moderator.approve_host_request(host_request_1)
456 moderator.approve_host_request(host_request_2)
457 moderator.approve_host_request(host_request_3)
459 with requests_session(token2) as api:
460 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_received=True))
461 assert res.no_more
462 assert len(res.host_requests) == 3
463 assert res.host_requests[0].latest_message.text.text == valid_request_text("Test request 3")
464 assert res.host_requests[1].latest_message.text.text == valid_request_text("Test request 2")
465 assert res.host_requests[2].latest_message.text.text == valid_request_text("Test request 1")
467 with requests_session(token2) as api:
468 api.RespondHostRequest(
469 requests_pb2.RespondHostRequestReq(
470 host_request_id=host_request_2,
471 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED,
472 text="Accepting host request 2",
473 )
474 )
475 api.RespondHostRequest(
476 requests_pb2.RespondHostRequestReq(
477 host_request_id=host_request_1,
478 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED,
479 text="Accepting host request 1",
480 )
481 )
482 api.RespondHostRequest(
483 requests_pb2.RespondHostRequestReq(
484 host_request_id=host_request_3,
485 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED,
486 text="Accepting host request 3",
487 )
488 )
490 with requests_session(token2) as api:
491 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_received=True))
492 assert res.no_more
493 assert len(res.host_requests) == 3
494 assert res.host_requests[0].latest_message.text.text == "Accepting host request 3"
495 assert res.host_requests[1].latest_message.text.text == "Accepting host request 1"
496 assert res.host_requests[2].latest_message.text.text == "Accepting host request 2"
498 with requests_session(token2) as api:
499 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_received=True, number=1))
500 assert not res.no_more
501 assert len(res.host_requests) == 1
502 assert res.host_requests[0].latest_message.text.text == "Accepting host request 3"
503 res = api.ListHostRequests(
504 requests_pb2.ListHostRequestsReq(only_received=True, number=1, last_request_id=res.last_request_id)
505 )
506 assert not res.no_more
507 assert len(res.host_requests) == 1
508 assert res.host_requests[0].latest_message.text.text == "Accepting host request 1"
509 res = api.ListHostRequests(
510 requests_pb2.ListHostRequestsReq(only_received=True, number=1, last_request_id=res.last_request_id)
511 )
512 assert res.no_more
513 assert len(res.host_requests) == 1
514 assert res.host_requests[0].latest_message.text.text == "Accepting host request 2"
517def test_ListHostRequests_active_filter(db, moderator):
518 user1, token1 = generate_user()
519 user2, token2 = generate_user()
520 today_plus_2 = today() + timedelta(days=2)
521 today_plus_3 = today() + timedelta(days=3)
523 with requests_session(token1) as api:
524 request_id = api.CreateHostRequest(
525 requests_pb2.CreateHostRequestReq(
526 host_user_id=user2.id,
527 from_date=today_plus_2.isoformat(),
528 to_date=today_plus_3.isoformat(),
529 text=valid_request_text("Test request 1"),
530 )
531 ).host_request_id
533 moderator.approve_host_request(request_id)
535 with requests_session(token1) as api:
536 api.RespondHostRequest(
537 requests_pb2.RespondHostRequestReq(
538 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED
539 )
540 )
542 with requests_session(token2) as api:
543 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_received=True))
544 assert len(res.host_requests) == 1
545 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_active=True))
546 assert len(res.host_requests) == 0
549def test_RespondHostRequests(db, moderator):
550 user1, token1 = generate_user()
551 user2, token2 = generate_user()
552 user3, token3 = generate_user()
553 today_plus_2 = today() + timedelta(days=2)
554 today_plus_3 = today() + timedelta(days=3)
556 with requests_session(token1) as api:
557 request_id = api.CreateHostRequest(
558 requests_pb2.CreateHostRequestReq(
559 host_user_id=user2.id,
560 from_date=today_plus_2.isoformat(),
561 to_date=today_plus_3.isoformat(),
562 text=valid_request_text("Test request 1"),
563 )
564 ).host_request_id
566 moderator.approve_host_request(request_id)
568 # another user can't access
569 with requests_session(token3) as api:
570 with pytest.raises(grpc.RpcError) as e:
571 api.RespondHostRequest(
572 requests_pb2.RespondHostRequestReq(
573 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED
574 )
575 )
576 assert e.value.code() == grpc.StatusCode.NOT_FOUND
577 assert e.value.details() == "Couldn't find that host request."
579 with requests_session(token1) as api:
580 with pytest.raises(grpc.RpcError) as e:
581 api.RespondHostRequest(
582 requests_pb2.RespondHostRequestReq(
583 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED
584 )
585 )
586 assert e.value.code() == grpc.StatusCode.PERMISSION_DENIED
587 assert e.value.details() == "You are not the host of this request."
589 with requests_session(token2) as api:
590 # non existing id
591 with pytest.raises(grpc.RpcError) as e:
592 api.RespondHostRequest(
593 requests_pb2.RespondHostRequestReq(
594 host_request_id=9999, status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED
595 )
596 )
597 assert e.value.code() == grpc.StatusCode.NOT_FOUND
599 # host can't confirm or cancel (host should accept/reject)
600 with pytest.raises(grpc.RpcError) as e:
601 api.RespondHostRequest(
602 requests_pb2.RespondHostRequestReq(
603 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CONFIRMED
604 )
605 )
606 assert e.value.code() == grpc.StatusCode.PERMISSION_DENIED
607 assert e.value.details() == "You can't set the host request status to that."
608 with pytest.raises(grpc.RpcError) as e:
609 api.RespondHostRequest(
610 requests_pb2.RespondHostRequestReq(
611 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED
612 )
613 )
614 assert e.value.code() == grpc.StatusCode.PERMISSION_DENIED
615 assert e.value.details() == "You can't set the host request status to that."
617 api.RespondHostRequest(
618 requests_pb2.RespondHostRequestReq(
619 host_request_id=request_id,
620 status=conversations_pb2.HOST_REQUEST_STATUS_REJECTED,
621 text="Test rejection message",
622 )
623 )
624 res = api.GetHostRequestMessages(requests_pb2.GetHostRequestMessagesReq(host_request_id=request_id))
625 assert res.messages[0].text.text == "Test rejection message"
626 assert res.messages[1].WhichOneof("content") == "host_request_status_changed"
627 assert res.messages[1].host_request_status_changed.status == conversations_pb2.HOST_REQUEST_STATUS_REJECTED
628 # should be able to move from rejected -> accepted
629 api.RespondHostRequest(
630 requests_pb2.RespondHostRequestReq(
631 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED
632 )
633 )
635 with requests_session(token1) as api:
636 # can't make pending
637 with pytest.raises(grpc.RpcError) as e:
638 api.RespondHostRequest(
639 requests_pb2.RespondHostRequestReq(
640 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_PENDING
641 )
642 )
643 assert e.value.code() == grpc.StatusCode.PERMISSION_DENIED
644 assert e.value.details() == "You can't set the host request status to that."
646 # can confirm then cancel
647 api.RespondHostRequest(
648 requests_pb2.RespondHostRequestReq(
649 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CONFIRMED
650 )
651 )
653 api.RespondHostRequest(
654 requests_pb2.RespondHostRequestReq(
655 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED
656 )
657 )
659 # can't confirm after having cancelled
660 with pytest.raises(grpc.RpcError) as e:
661 api.RespondHostRequest(
662 requests_pb2.RespondHostRequestReq(
663 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CONFIRMED
664 )
665 )
666 assert e.value.code() == grpc.StatusCode.PERMISSION_DENIED
667 assert e.value.details() == "You can't set the host request status to that."
669 # at this point there should be 7 messages
670 # 2 for creation, 2 for the status change with message, 3 for the other status changed
671 with requests_session(token1) as api:
672 res = api.GetHostRequestMessages(requests_pb2.GetHostRequestMessagesReq(host_request_id=request_id))
673 assert len(res.messages) == 7
674 assert res.messages[0].host_request_status_changed.status == conversations_pb2.HOST_REQUEST_STATUS_CANCELLED
675 assert res.messages[1].host_request_status_changed.status == conversations_pb2.HOST_REQUEST_STATUS_CONFIRMED
676 assert res.messages[2].host_request_status_changed.status == conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED
677 assert res.messages[4].host_request_status_changed.status == conversations_pb2.HOST_REQUEST_STATUS_REJECTED
678 assert res.messages[6].WhichOneof("content") == "chat_created"
681def test_get_host_request_messages(db, moderator):
682 user1, token1 = generate_user()
683 user2, token2 = generate_user()
684 today_plus_2 = today() + timedelta(days=2)
685 today_plus_3 = today() + timedelta(days=3)
686 with requests_session(token1) as api:
687 res = api.CreateHostRequest(
688 requests_pb2.CreateHostRequestReq(
689 host_user_id=user2.id,
690 from_date=today_plus_2.isoformat(),
691 to_date=today_plus_3.isoformat(),
692 text=valid_request_text("Test request 1"),
693 )
694 )
695 conversation_id = res.host_request_id
697 moderator.approve_host_request(conversation_id)
699 add_message(db, "Test request 1 message 1", user1.id, conversation_id)
700 add_message(db, "Test request 1 message 2", user1.id, conversation_id)
701 add_message(db, "Test request 1 message 3", user1.id, conversation_id)
703 with requests_session(token2) as api:
704 api.RespondHostRequest(
705 requests_pb2.RespondHostRequestReq(
706 host_request_id=conversation_id, status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED
707 )
708 )
710 add_message(db, "Test request 1 message 4", user2.id, conversation_id)
711 add_message(db, "Test request 1 message 5", user2.id, conversation_id)
713 api.RespondHostRequest(
714 requests_pb2.RespondHostRequestReq(
715 host_request_id=conversation_id, status=conversations_pb2.HOST_REQUEST_STATUS_REJECTED
716 )
717 )
719 with requests_session(token1) as api:
720 # 9 including initial message
721 res = api.GetHostRequestMessages(requests_pb2.GetHostRequestMessagesReq(host_request_id=conversation_id))
722 assert len(res.messages) == 9
723 assert res.no_more
725 res = api.GetHostRequestMessages(
726 requests_pb2.GetHostRequestMessagesReq(host_request_id=conversation_id, number=3)
727 )
728 assert not res.no_more
729 assert len(res.messages) == 3
730 assert res.messages[0].host_request_status_changed.status == conversations_pb2.HOST_REQUEST_STATUS_REJECTED
731 assert res.messages[0].WhichOneof("content") == "host_request_status_changed"
732 assert res.messages[1].text.text == "Test request 1 message 5"
733 assert res.messages[2].text.text == "Test request 1 message 4"
735 res = api.GetHostRequestMessages(
736 requests_pb2.GetHostRequestMessagesReq(
737 host_request_id=conversation_id,
738 last_message_id=res.messages[2].message_id,
739 number=6,
740 )
741 )
742 assert res.no_more
743 assert len(res.messages) == 6
744 assert res.messages[0].host_request_status_changed.status == conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED
745 assert res.messages[0].WhichOneof("content") == "host_request_status_changed"
746 assert res.messages[1].text.text == "Test request 1 message 3"
747 assert res.messages[2].text.text == "Test request 1 message 2"
748 assert res.messages[3].text.text == "Test request 1 message 1"
749 assert res.messages[4].text.text == valid_request_text("Test request 1")
750 assert res.messages[5].WhichOneof("content") == "chat_created"
753def test_SendHostRequestMessage(db, moderator):
754 user1, token1 = generate_user()
755 user2, token2 = generate_user()
756 user3, token3 = generate_user()
757 today_plus_2 = today() + timedelta(days=2)
758 today_plus_3 = today() + timedelta(days=3)
759 with requests_session(token1) as api:
760 host_request_id = api.CreateHostRequest(
761 requests_pb2.CreateHostRequestReq(
762 host_user_id=user2.id,
763 from_date=today_plus_2.isoformat(),
764 to_date=today_plus_3.isoformat(),
765 text=valid_request_text("Test request 1"),
766 )
767 ).host_request_id
769 moderator.approve_host_request(host_request_id)
771 with requests_session(token1) as api:
772 with pytest.raises(grpc.RpcError) as e:
773 api.SendHostRequestMessage(
774 requests_pb2.SendHostRequestMessageReq(host_request_id=999, text="Test message 1")
775 )
776 assert e.value.code() == grpc.StatusCode.NOT_FOUND
778 with pytest.raises(grpc.RpcError) as e:
779 api.SendHostRequestMessage(requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text=""))
780 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
781 assert e.value.details() == "Invalid message."
783 api.SendHostRequestMessage(
784 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 1")
785 )
786 res = api.GetHostRequestMessages(requests_pb2.GetHostRequestMessagesReq(host_request_id=host_request_id))
787 assert res.messages[0].text.text == "Test message 1"
788 assert res.messages[0].author_user_id == user1.id
790 with requests_session(token3) as api:
791 # other user can't send
792 with pytest.raises(grpc.RpcError) as e:
793 api.SendHostRequestMessage(
794 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 2")
795 )
796 assert e.value.code() == grpc.StatusCode.NOT_FOUND
797 assert e.value.details() == "Couldn't find that host request."
799 with requests_session(token2) as api:
800 api.SendHostRequestMessage(
801 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 2")
802 )
803 res = api.GetHostRequestMessages(requests_pb2.GetHostRequestMessagesReq(host_request_id=host_request_id))
804 # including 2 for creation control message and message
805 assert len(res.messages) == 4
806 assert res.messages[0].text.text == "Test message 2"
807 assert res.messages[0].author_user_id == user2.id
809 # CAN send messages to a rejected, confirmed or cancelled request, and for accepted
810 api.RespondHostRequest(
811 requests_pb2.RespondHostRequestReq(
812 host_request_id=host_request_id, status=conversations_pb2.HOST_REQUEST_STATUS_REJECTED
813 )
814 )
815 api.SendHostRequestMessage(
816 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 3")
817 )
819 api.RespondHostRequest(
820 requests_pb2.RespondHostRequestReq(
821 host_request_id=host_request_id, status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED
822 )
823 )
825 with requests_session(token1) as api:
826 api.RespondHostRequest(
827 requests_pb2.RespondHostRequestReq(
828 host_request_id=host_request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CONFIRMED
829 )
830 )
831 api.SendHostRequestMessage(
832 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 3")
833 )
835 api.RespondHostRequest(
836 requests_pb2.RespondHostRequestReq(
837 host_request_id=host_request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED
838 )
839 )
840 api.SendHostRequestMessage(
841 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 3")
842 )
845def test_get_updates(db, moderator):
846 user1, token1 = generate_user()
847 user2, token2 = generate_user()
848 user3, token3 = generate_user()
849 today_plus_2 = today() + timedelta(days=2)
850 today_plus_3 = today() + timedelta(days=3)
851 with requests_session(token1) as api:
852 host_request_id = api.CreateHostRequest(
853 requests_pb2.CreateHostRequestReq(
854 host_user_id=user2.id,
855 from_date=today_plus_2.isoformat(),
856 to_date=today_plus_3.isoformat(),
857 text=valid_request_text("Test message 0"),
858 )
859 ).host_request_id
861 moderator.approve_host_request(host_request_id)
863 with requests_session(token1) as api:
864 api.SendHostRequestMessage(
865 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 1")
866 )
867 api.SendHostRequestMessage(
868 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 2")
869 )
870 api.RespondHostRequest(
871 requests_pb2.RespondHostRequestReq(
872 host_request_id=host_request_id,
873 status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED,
874 text="Test message 3",
875 )
876 )
878 api.CreateHostRequest(
879 requests_pb2.CreateHostRequestReq(
880 host_user_id=user2.id,
881 from_date=today_plus_2.isoformat(),
882 to_date=today_plus_3.isoformat(),
883 text=valid_request_text("Test message 4"),
884 )
885 )
887 res = api.GetHostRequestMessages(requests_pb2.GetHostRequestMessagesReq(host_request_id=host_request_id))
888 assert len(res.messages) == 6
889 assert res.messages[0].text.text == "Test message 3"
890 assert res.messages[1].host_request_status_changed.status == conversations_pb2.HOST_REQUEST_STATUS_CANCELLED
891 assert res.messages[2].text.text == "Test message 2"
892 assert res.messages[3].text.text == "Test message 1"
893 assert res.messages[4].text.text == valid_request_text("Test message 0")
894 message_id_3 = res.messages[0].message_id
895 message_id_cancel = res.messages[1].message_id
896 message_id_2 = res.messages[2].message_id
897 message_id_1 = res.messages[3].message_id
898 message_id_0 = res.messages[4].message_id
900 with pytest.raises(grpc.RpcError) as e:
901 api.GetHostRequestUpdates(requests_pb2.GetHostRequestUpdatesReq(newest_message_id=0))
902 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
904 res = api.GetHostRequestUpdates(requests_pb2.GetHostRequestUpdatesReq(newest_message_id=message_id_1))
905 assert res.no_more
906 assert len(res.updates) == 5
907 assert res.updates[0].message.text.text == "Test message 2"
908 assert (
909 res.updates[1].message.host_request_status_changed.status == conversations_pb2.HOST_REQUEST_STATUS_CANCELLED
910 )
911 assert res.updates[1].status == conversations_pb2.HOST_REQUEST_STATUS_CANCELLED
912 assert res.updates[2].message.text.text == "Test message 3"
913 assert res.updates[3].message.WhichOneof("content") == "chat_created"
914 assert res.updates[3].status == conversations_pb2.HOST_REQUEST_STATUS_PENDING
915 assert res.updates[4].message.text.text == valid_request_text("Test message 4")
917 res = api.GetHostRequestUpdates(requests_pb2.GetHostRequestUpdatesReq(newest_message_id=message_id_1, number=1))
918 assert not res.no_more
919 assert len(res.updates) == 1
920 assert res.updates[0].message.text.text == "Test message 2"
921 assert res.updates[0].status == conversations_pb2.HOST_REQUEST_STATUS_CANCELLED
923 with requests_session(token3) as api:
924 # other user can't access
925 res = api.GetHostRequestUpdates(requests_pb2.GetHostRequestUpdatesReq(newest_message_id=message_id_1))
926 assert len(res.updates) == 0
929def test_archive_host_request(db, moderator):
930 user1, token1 = generate_user()
931 user2, token2 = generate_user()
933 today_plus_2 = today() + timedelta(days=2)
934 today_plus_3 = today() + timedelta(days=3)
936 with requests_session(token1) as api:
937 host_request_id = api.CreateHostRequest(
938 requests_pb2.CreateHostRequestReq(
939 host_user_id=user2.id,
940 from_date=today_plus_2.isoformat(),
941 to_date=today_plus_3.isoformat(),
942 text=valid_request_text("Test message 0"),
943 )
944 ).host_request_id
946 api.SendHostRequestMessage(
947 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 1")
948 )
949 api.SendHostRequestMessage(
950 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 2")
951 )
953 moderator.approve_host_request(host_request_id)
955 # happy path archiving host request
956 with requests_session(token1) as api:
957 api.RespondHostRequest(
958 requests_pb2.RespondHostRequestReq(
959 host_request_id=host_request_id,
960 status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED,
961 text="Test message 3",
962 )
963 )
964 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_sent=True))
965 assert len(res.host_requests) == 1
966 assert res.host_requests[0].status == conversations_pb2.HOST_REQUEST_STATUS_CANCELLED
968 # Verify is_archived is False before archiving
969 res = api.GetHostRequest(requests_pb2.GetHostRequestReq(host_request_id=host_request_id))
970 assert not res.is_archived
972 api.SetHostRequestArchiveStatus(
973 requests_pb2.SetHostRequestArchiveStatusReq(host_request_id=host_request_id, is_archived=True)
974 )
975 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_archived=True))
976 assert len(res.host_requests) == 1
978 # Verify is_archived is True after archiving
979 res = api.GetHostRequest(requests_pb2.GetHostRequestReq(host_request_id=host_request_id))
980 assert res.is_archived
983def test_mark_last_seen(db, moderator):
984 user1, token1 = generate_user()
985 user2, token2 = generate_user()
986 user3, token3 = generate_user()
987 today_plus_2 = today() + timedelta(days=2)
988 today_plus_3 = today() + timedelta(days=3)
989 with requests_session(token1) as api:
990 host_request_id = api.CreateHostRequest(
991 requests_pb2.CreateHostRequestReq(
992 host_user_id=user2.id,
993 from_date=today_plus_2.isoformat(),
994 to_date=today_plus_3.isoformat(),
995 text=valid_request_text("Test message 0"),
996 )
997 ).host_request_id
999 host_request_id_2 = api.CreateHostRequest(
1000 requests_pb2.CreateHostRequestReq(
1001 host_user_id=user2.id,
1002 from_date=today_plus_2.isoformat(),
1003 to_date=today_plus_3.isoformat(),
1004 text=valid_request_text("Test message 0a"),
1005 )
1006 ).host_request_id
1008 moderator.approve_host_request(host_request_id)
1009 moderator.approve_host_request(host_request_id_2)
1011 with requests_session(token1) as api:
1012 api.SendHostRequestMessage(
1013 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 1")
1014 )
1015 api.SendHostRequestMessage(
1016 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 2")
1017 )
1018 api.RespondHostRequest(
1019 requests_pb2.RespondHostRequestReq(
1020 host_request_id=host_request_id,
1021 status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED,
1022 text="Test message 3",
1023 )
1024 )
1026 moderator.approve_host_request(host_request_id)
1027 moderator.approve_host_request(host_request_id_2)
1029 # test Ping unseen host request count, should be automarked after sending
1030 with api_session(token1) as api:
1031 assert api.Ping(api_pb2.PingReq()).unseen_received_host_request_count == 0
1032 assert api.Ping(api_pb2.PingReq()).unseen_sent_host_request_count == 0
1034 with api_session(token2) as api:
1035 assert api.Ping(api_pb2.PingReq()).unseen_received_host_request_count == 2
1036 assert api.Ping(api_pb2.PingReq()).unseen_sent_host_request_count == 0
1038 with requests_session(token2) as api:
1039 assert api.ListHostRequests(requests_pb2.ListHostRequestsReq()).host_requests[0].last_seen_message_id == 0
1041 api.MarkLastSeenHostRequest(
1042 requests_pb2.MarkLastSeenHostRequestReq(host_request_id=host_request_id, last_seen_message_id=3)
1043 )
1045 assert api.ListHostRequests(requests_pb2.ListHostRequestsReq()).host_requests[0].last_seen_message_id == 3
1047 with pytest.raises(grpc.RpcError) as e:
1048 api.MarkLastSeenHostRequest(
1049 requests_pb2.MarkLastSeenHostRequestReq(host_request_id=host_request_id, last_seen_message_id=1)
1050 )
1051 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
1052 assert e.value.details() == "You can't unsee messages."
1054 # this will be used to test sent request notifications
1055 host_request_id_3 = api.CreateHostRequest(
1056 requests_pb2.CreateHostRequestReq(
1057 host_user_id=user1.id,
1058 from_date=today_plus_2.isoformat(),
1059 to_date=today_plus_3.isoformat(),
1060 text=valid_request_text("Another test request"),
1061 )
1062 ).host_request_id
1064 moderator.approve_host_request(host_request_id_3)
1066 with requests_session(token2) as api:
1067 # this should make id_2 all read
1068 api.SendHostRequestMessage(
1069 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id_2, text="Test")
1070 )
1072 with api_session(token2) as api:
1073 assert api.Ping(api_pb2.PingReq()).unseen_received_host_request_count == 1
1074 assert api.Ping(api_pb2.PingReq()).unseen_sent_host_request_count == 0
1076 # make sure sent and received count for unseen notifications
1077 with requests_session(token1) as api:
1078 api.SendHostRequestMessage(
1079 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id_3, text="Test message")
1080 )
1082 with api_session(token2) as api:
1083 assert api.Ping(api_pb2.PingReq()).unseen_received_host_request_count == 1
1084 assert api.Ping(api_pb2.PingReq()).unseen_sent_host_request_count == 1
1087def test_response_rate(db, moderator):
1088 user1, token1 = generate_user()
1089 user2, token2 = generate_user()
1090 user3, token3 = generate_user(delete_user=True)
1092 today_plus_2 = today() + timedelta(days=2)
1093 today_plus_3 = today() + timedelta(days=3)
1095 with session_scope() as session:
1096 refresh_materialized_view(session, "user_response_rates")
1098 with requests_session(token1) as api:
1099 # deleted: not found
1100 with pytest.raises(grpc.RpcError) as e:
1101 api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user3.id))
1102 assert e.value.code() == grpc.StatusCode.NOT_FOUND
1103 assert e.value.details() == "Couldn't find that user."
1105 # no requests: insufficient
1106 res = api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user2.id))
1107 assert res.HasField("insufficient_data")
1109 # send a request and back date it by 36 hours
1110 host_request_1 = api.CreateHostRequest(
1111 requests_pb2.CreateHostRequestReq(
1112 host_user_id=user2.id,
1113 from_date=today_plus_2.isoformat(),
1114 to_date=today_plus_3.isoformat(),
1115 text=valid_request_text("Test request"),
1116 )
1117 ).host_request_id
1118 moderator.approve_host_request(host_request_1)
1119 with session_scope() as session:
1120 session.execute(
1121 select(Message)
1122 .where(Message.conversation_id == host_request_1)
1123 .where(Message.message_type == MessageType.chat_created)
1124 ).scalar_one().time = now() - timedelta(hours=36)
1125 refresh_materialized_view(session, "user_response_rates")
1127 # still insufficient
1128 res = api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user2.id))
1129 assert res.HasField("insufficient_data")
1131 # send a request and back date it by 35 hours
1132 host_request_2 = api.CreateHostRequest(
1133 requests_pb2.CreateHostRequestReq(
1134 host_user_id=user2.id,
1135 from_date=today_plus_2.isoformat(),
1136 to_date=today_plus_3.isoformat(),
1137 text=valid_request_text("Test request"),
1138 )
1139 ).host_request_id
1140 moderator.approve_host_request(host_request_2)
1141 with session_scope() as session:
1142 session.execute(
1143 select(Message)
1144 .where(Message.conversation_id == host_request_2)
1145 .where(Message.message_type == MessageType.chat_created)
1146 ).scalar_one().time = now() - timedelta(hours=35)
1147 refresh_materialized_view(session, "user_response_rates")
1149 # still insufficient
1150 res = api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user2.id))
1151 assert res.HasField("insufficient_data")
1153 # send a request and back date it by 34 hours
1154 host_request_3 = api.CreateHostRequest(
1155 requests_pb2.CreateHostRequestReq(
1156 host_user_id=user2.id,
1157 from_date=today_plus_2.isoformat(),
1158 to_date=today_plus_3.isoformat(),
1159 text=valid_request_text("Test request"),
1160 )
1161 ).host_request_id
1162 moderator.approve_host_request(host_request_3)
1163 with session_scope() as session:
1164 session.execute(
1165 select(Message)
1166 .where(Message.conversation_id == host_request_3)
1167 .where(Message.message_type == MessageType.chat_created)
1168 ).scalar_one().time = now() - timedelta(hours=34)
1169 refresh_materialized_view(session, "user_response_rates")
1171 # now low
1172 res = api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user2.id))
1173 assert res.HasField("low")
1175 with requests_session(token2) as api:
1176 # accept a host req
1177 api.RespondHostRequest(
1178 requests_pb2.RespondHostRequestReq(
1179 host_request_id=host_request_2,
1180 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED,
1181 text="Accepting host request",
1182 )
1183 )
1185 with session_scope() as session:
1186 refresh_materialized_view(session, "user_response_rates")
1188 with requests_session(token1) as api:
1189 # now some w p33 = 35h
1190 res = api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user2.id))
1191 assert res.HasField("some")
1192 assert res.some.response_time_p33.ToTimedelta() == timedelta(hours=35)
1194 with requests_session(token2) as api:
1195 # accept another host req
1196 api.RespondHostRequest(
1197 requests_pb2.RespondHostRequestReq(
1198 host_request_id=host_request_3,
1199 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED,
1200 text="Accepting host request",
1201 )
1202 )
1204 with session_scope() as session:
1205 refresh_materialized_view(session, "user_response_rates")
1207 with requests_session(token1) as api:
1208 # now most w p33 = 34h, p66 = 35h
1209 res = api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user2.id))
1210 assert res.HasField("most")
1211 assert res.most.response_time_p33.ToTimedelta() == timedelta(hours=34)
1212 assert res.most.response_time_p66.ToTimedelta() == timedelta(hours=35)
1214 with requests_session(token2) as api:
1215 # accept last host req
1216 api.RespondHostRequest(
1217 requests_pb2.RespondHostRequestReq(
1218 host_request_id=host_request_1,
1219 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED,
1220 text="Accepting host request",
1221 )
1222 )
1224 with session_scope() as session:
1225 refresh_materialized_view(session, "user_response_rates")
1227 with requests_session(token1) as api:
1228 # now all w p33 = 34h, p66 = 35h
1229 res = api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user2.id))
1230 assert res.HasField("almost_all")
1231 assert res.almost_all.response_time_p33.ToTimedelta() == timedelta(hours=34)
1232 assert res.almost_all.response_time_p66.ToTimedelta() == timedelta(hours=35)
1234 # send a request and back date it by 2 hours
1235 host_request_4 = api.CreateHostRequest(
1236 requests_pb2.CreateHostRequestReq(
1237 host_user_id=user2.id,
1238 from_date=today_plus_2.isoformat(),
1239 to_date=today_plus_3.isoformat(),
1240 text=valid_request_text("Test request"),
1241 )
1242 ).host_request_id
1243 moderator.approve_host_request(host_request_4)
1244 with session_scope() as session:
1245 session.execute(
1246 select(Message)
1247 .where(Message.conversation_id == host_request_4)
1248 .where(Message.message_type == MessageType.chat_created)
1249 ).scalar_one().time = now() - timedelta(hours=2)
1250 refresh_materialized_view(session, "user_response_rates")
1252 # send a request and back date it by 4 hours
1253 host_request_5 = api.CreateHostRequest(
1254 requests_pb2.CreateHostRequestReq(
1255 host_user_id=user2.id,
1256 from_date=today_plus_2.isoformat(),
1257 to_date=today_plus_3.isoformat(),
1258 text=valid_request_text("Test request"),
1259 )
1260 ).host_request_id
1261 moderator.approve_host_request(host_request_5)
1262 with session_scope() as session:
1263 session.execute(
1264 select(Message)
1265 .where(Message.conversation_id == host_request_5)
1266 .where(Message.message_type == MessageType.chat_created)
1267 ).scalar_one().time = now() - timedelta(hours=4)
1268 refresh_materialized_view(session, "user_response_rates")
1270 # now some w p33 = 35h
1271 res = api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user2.id))
1272 assert res.HasField("some")
1273 assert res.some.response_time_p33.ToTimedelta() == timedelta(hours=35)
1275 with requests_session(token2) as api:
1276 # accept host req
1277 api.RespondHostRequest(
1278 requests_pb2.RespondHostRequestReq(
1279 host_request_id=host_request_5,
1280 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED,
1281 text="Accepting host request",
1282 )
1283 )
1285 with session_scope() as session:
1286 refresh_materialized_view(session, "user_response_rates")
1288 with requests_session(token1) as api:
1289 # now most w p33 = 34h, p66 = 36h
1290 res = api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user2.id))
1291 assert res.HasField("most")
1292 assert res.most.response_time_p33.ToTimedelta() == timedelta(hours=34)
1293 assert res.most.response_time_p66.ToTimedelta() == timedelta(hours=36)
1295 with requests_session(token2) as api:
1296 # accept host req
1297 api.RespondHostRequest(
1298 requests_pb2.RespondHostRequestReq(
1299 host_request_id=host_request_4,
1300 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED,
1301 text="Accepting host request",
1302 )
1303 )
1305 with session_scope() as session:
1306 refresh_materialized_view(session, "user_response_rates")
1308 with requests_session(token1) as api:
1309 # now most w p33 = 4h, p66 = 35h
1310 res = api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user2.id))
1311 assert res.HasField("almost_all")
1312 assert res.almost_all.response_time_p33.ToTimedelta() == timedelta(hours=4)
1313 assert res.almost_all.response_time_p66.ToTimedelta() == timedelta(hours=35)
1316def test_request_notifications(db, push_collector: PushCollector, moderator):
1317 host, host_token = generate_user(complete_profile=True)
1318 surfer, surfer_token = generate_user(complete_profile=True)
1320 host_loc_context = LocalizationContext.from_user(host)
1321 surfer_loc_context = LocalizationContext.from_user(surfer)
1323 today_plus_2 = today() + timedelta(days=2)
1324 today_plus_3 = today() + timedelta(days=3)
1326 with requests_session(surfer_token) as api:
1327 hr_id = api.CreateHostRequest(
1328 requests_pb2.CreateHostRequestReq(
1329 host_user_id=host.id,
1330 from_date=today_plus_2.isoformat(),
1331 to_date=today_plus_3.isoformat(),
1332 text=valid_request_text("can i stay plz"),
1333 )
1334 ).host_request_id
1336 with mock_notification_email() as mock:
1337 moderator.approve_host_request(hr_id)
1339 mock.assert_called_once()
1340 e = email_fields(mock)
1341 assert e.recipient == host.email
1342 assert "host request" in e.subject.lower()
1343 assert host.name in e.plain
1344 assert host.name in e.html
1345 assert "quick decline" in e.plain.lower(), e.plain
1346 assert "quick decline" in e.html.lower()
1347 assert surfer.name in e.plain
1348 assert surfer.name in e.html
1349 assert host_loc_context.localize_date(today_plus_2) in e.plain
1350 assert host_loc_context.localize_date(today_plus_2) in e.html
1351 assert host_loc_context.localize_date(today_plus_3) in e.plain
1352 assert host_loc_context.localize_date(today_plus_3) in e.html
1353 assert "http://localhost:5001/img/thumbnail/" not in e.plain
1354 assert "http://localhost:5001/img/thumbnail/" in e.html
1355 assert f"http://localhost:3000/messages/request/{hr_id}" in e.plain
1356 assert f"http://localhost:3000/messages/request/{hr_id}" in e.html
1358 assert push_collector.pop_for_user(host.id, last=True).content.title == f"New host request from {surfer.name}"
1360 with requests_session(host_token) as api:
1361 with mock_notification_email() as mock:
1362 api.RespondHostRequest(
1363 requests_pb2.RespondHostRequestReq(
1364 host_request_id=hr_id,
1365 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED,
1366 text="Accepting host request",
1367 )
1368 )
1370 e = email_fields(mock)
1371 assert e.recipient == surfer.email
1372 assert "host request" in e.subject.lower()
1373 assert host.name in e.plain
1374 assert host.name in e.html
1375 assert surfer.name in e.plain
1376 assert surfer.name in e.html
1377 assert surfer_loc_context.localize_date(today_plus_2) in e.plain
1378 assert surfer_loc_context.localize_date(today_plus_2) in e.html
1379 assert surfer_loc_context.localize_date(today_plus_3) in e.plain
1380 assert surfer_loc_context.localize_date(today_plus_3) in e.html
1381 assert "http://localhost:5001/img/thumbnail/" not in e.plain
1382 assert "http://localhost:5001/img/thumbnail/" in e.html
1383 assert f"http://localhost:3000/messages/request/{hr_id}" in e.plain
1384 assert f"http://localhost:3000/messages/request/{hr_id}" in e.html
1386 assert push_collector.pop_for_user(surfer.id, last=True).content.title == f"{host.name} accepted your host request"
1389def test_quick_decline(db, push_collector: PushCollector, moderator):
1390 host, host_token = generate_user(complete_profile=True)
1391 surfer, surfer_token = generate_user(complete_profile=True)
1393 host_loc_context = LocalizationContext.from_user(host)
1395 today_plus_2 = today() + timedelta(days=2)
1396 today_plus_3 = today() + timedelta(days=3)
1398 with requests_session(surfer_token) as api:
1399 hr_id = api.CreateHostRequest(
1400 requests_pb2.CreateHostRequestReq(
1401 host_user_id=host.id,
1402 from_date=today_plus_2.isoformat(),
1403 to_date=today_plus_3.isoformat(),
1404 text=valid_request_text("can i stay plz"),
1405 )
1406 ).host_request_id
1408 with mock_notification_email() as mock:
1409 moderator.approve_host_request(hr_id)
1411 mock.assert_called_once()
1412 e = email_fields(mock)
1413 assert e.recipient == host.email
1414 assert "host request" in e.subject.lower()
1415 assert host.name in e.plain
1416 assert host.name in e.html
1417 assert "quick decline" in e.plain.lower(), e.plain
1418 assert "quick decline" in e.html.lower()
1419 assert surfer.name in e.plain
1420 assert surfer.name in e.html
1421 assert host_loc_context.localize_date(today_plus_2) in e.plain
1422 assert host_loc_context.localize_date(today_plus_2) in e.html
1423 assert host_loc_context.localize_date(today_plus_3) in e.plain
1424 assert host_loc_context.localize_date(today_plus_3) in e.html
1425 assert "http://localhost:5001/img/thumbnail/" not in e.plain
1426 assert "http://localhost:5001/img/thumbnail/" in e.html
1427 assert f"http://localhost:3000/messages/request/{hr_id}" in e.plain
1428 assert f"http://localhost:3000/messages/request/{hr_id}" in e.html
1430 assert push_collector.pop_for_user(host.id, last=True).content.title == f"New host request from {surfer.name}"
1432 # very ugly
1433 # http://localhost:3000/quick-link?payload=CAEiGAoOZnJpZW5kX3JlcXVlc3QSBmFjY2VwdA==&sig=BQdk024NTATm8zlR0krSXTBhP5U9TlFv7VhJeIHZtUg=
1434 for link in re.findall(r'<a href="(.*?)"', email_fields(mock).html): 1434 ↛ 1453line 1434 didn't jump to line 1453 because the loop on line 1434 didn't complete
1435 if "payload" not in link:
1436 continue
1437 print(link)
1438 url_parts = urlparse(html.unescape(link))
1439 params = parse_qs(url_parts.query)
1440 print(params["payload"][0])
1441 payload = unsubscribe_pb2.UnsubscribePayload.FromString(b64decode(params["payload"][0]))
1442 if payload.HasField("host_request_quick_decline"): 1442 ↛ 1434line 1442 didn't jump to line 1434 because the condition on line 1442 was always true
1443 with auth_api_session() as (auth_api, metadata_interceptor):
1444 res = auth_api.Unsubscribe(
1445 auth_pb2.UnsubscribeReq(
1446 payload=b64decode(params["payload"][0]),
1447 sig=b64decode(params["sig"][0]),
1448 )
1449 )
1450 assert res.response == "Thank you for responding to the host request!"
1451 break
1452 else:
1453 raise Exception("Didn't find link")
1455 with requests_session(surfer_token) as api:
1456 res = api.GetHostRequest(requests_pb2.GetHostRequestReq(host_request_id=hr_id))
1457 assert res.status == conversations_pb2.HOST_REQUEST_STATUS_REJECTED
1460def test_host_req_feedback(db, moderator):
1461 host, host_token = generate_user(complete_profile=True)
1462 host2, host2_token = generate_user(complete_profile=True)
1463 host3, host3_token = generate_user(complete_profile=True)
1464 surfer, surfer_token = generate_user(complete_profile=True)
1466 today_plus_2 = today() + timedelta(days=2)
1467 today_plus_3 = today() + timedelta(days=3)
1469 with requests_session(surfer_token) as api:
1470 hr_id = api.CreateHostRequest(
1471 requests_pb2.CreateHostRequestReq(
1472 host_user_id=host.id,
1473 from_date=today_plus_2.isoformat(),
1474 to_date=today_plus_3.isoformat(),
1475 text=valid_request_text("can i stay plz"),
1476 )
1477 ).host_request_id
1478 hr2_id = api.CreateHostRequest(
1479 requests_pb2.CreateHostRequestReq(
1480 host_user_id=host2.id,
1481 from_date=today_plus_2.isoformat(),
1482 to_date=today_plus_3.isoformat(),
1483 text=valid_request_text("can i stay plz"),
1484 )
1485 ).host_request_id
1486 hr3_id = api.CreateHostRequest(
1487 requests_pb2.CreateHostRequestReq(
1488 host_user_id=host3.id,
1489 from_date=today_plus_2.isoformat(),
1490 to_date=today_plus_3.isoformat(),
1491 text=valid_request_text("can i stay plz"),
1492 )
1493 ).host_request_id
1495 moderator.approve_host_request(hr_id)
1496 moderator.approve_host_request(hr2_id)
1497 moderator.approve_host_request(hr3_id)
1499 with requests_session(host_token) as api:
1500 res = api.GetHostRequest(requests_pb2.GetHostRequestReq(host_request_id=hr_id))
1501 assert not res.need_host_request_feedback
1503 api.RespondHostRequest(
1504 requests_pb2.RespondHostRequestReq(
1505 host_request_id=hr_id,
1506 status=conversations_pb2.HOST_REQUEST_STATUS_REJECTED,
1507 )
1508 )
1510 res = api.GetHostRequest(requests_pb2.GetHostRequestReq(host_request_id=hr_id))
1511 assert res.need_host_request_feedback
1513 # surfer can't leave feedback
1514 with requests_session(surfer_token) as api:
1515 with pytest.raises(grpc.RpcError) as e:
1516 api.SendHostRequestFeedback(
1517 requests_pb2.SendHostRequestFeedbackReq(
1518 host_request_id=hr_id,
1519 )
1520 )
1521 assert e.value.code() == grpc.StatusCode.NOT_FOUND
1522 assert e.value.details() == "Couldn't find that host request."
1524 with requests_session(host_token) as api:
1525 api.SendHostRequestFeedback(
1526 requests_pb2.SendHostRequestFeedbackReq(
1527 host_request_id=hr_id,
1528 host_request_quality=requests_pb2.HOST_REQUEST_QUALITY_LOW,
1529 )
1530 )
1531 res = api.GetHostRequest(requests_pb2.GetHostRequestReq(host_request_id=hr_id))
1532 assert not res.need_host_request_feedback
1534 # can't leave it twice
1535 with requests_session(host_token) as api:
1536 with pytest.raises(grpc.RpcError) as e:
1537 api.SendHostRequestFeedback(
1538 requests_pb2.SendHostRequestFeedbackReq(
1539 host_request_id=hr_id,
1540 )
1541 )
1542 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
1543 assert e.value.details() == "You have already left feedback for this host request!"
1545 res = api.GetHostRequest(requests_pb2.GetHostRequestReq(host_request_id=hr_id))
1546 assert not res.need_host_request_feedback
1548 with requests_session(host2_token) as api:
1549 api.RespondHostRequest(
1550 requests_pb2.RespondHostRequestReq(
1551 host_request_id=hr2_id, status=conversations_pb2.HOST_REQUEST_STATUS_REJECTED
1552 )
1553 )
1554 # can't leave feedback on the wrong one
1555 with pytest.raises(grpc.RpcError) as e:
1556 api.SendHostRequestFeedback(
1557 requests_pb2.SendHostRequestFeedbackReq(
1558 host_request_id=hr_id,
1559 )
1560 )
1561 assert e.value.code() == grpc.StatusCode.NOT_FOUND
1562 assert e.value.details() == "Couldn't find that host request."
1564 # null feedback is still feedback
1565 api.SendHostRequestFeedback(requests_pb2.SendHostRequestFeedbackReq(host_request_id=hr2_id))
1567 with requests_session(host3_token) as api:
1568 api.RespondHostRequest(
1569 requests_pb2.RespondHostRequestReq(
1570 host_request_id=hr3_id, status=conversations_pb2.HOST_REQUEST_STATUS_REJECTED
1571 )
1572 )
1574 api.SendHostRequestFeedback(
1575 requests_pb2.SendHostRequestFeedbackReq(host_request_id=hr3_id, decline_reason="bad req")
1576 )
1579def _make_trip_node_admin(user_id: int, trip_id: int):
1580 with session_scope() as session:
1581 node_id = session.execute(select(PublicTrip.node_id).where(PublicTrip.id == trip_id)).scalar_one()
1582 cluster = session.execute(
1583 select(Cluster).where(Cluster.parent_node_id == node_id).where(Cluster.is_official_cluster)
1584 ).scalar_one_or_none()
1585 if cluster is None: 1585 ↛ 1594line 1585 didn't jump to line 1594 because the condition on line 1585 was always true
1586 cluster = Cluster(
1587 name="Test community",
1588 description="Test",
1589 parent_node_id=node_id,
1590 is_official_cluster=True,
1591 )
1592 session.add(cluster)
1593 session.flush()
1594 session.add(ClusterSubscription(cluster_id=cluster.id, user_id=user_id, role=ClusterRole.admin))
1597def _create_public_trip(user_id: int, from_date, to_date, *, status=None, same_gender_only: bool = False):
1598 with session_scope() as session:
1599 node = session.execute(select(Node).limit(1)).scalar_one_or_none()
1600 if node is None: 1600 ↛ 1607line 1600 didn't jump to line 1607 because the condition on line 1600 was always true
1601 node = Node(
1602 geom=to_multi(create_polygon_lat_lng([[0, 0], [0, 2], [2, 2], [2, 0], [0, 0]])),
1603 node_type=NodeType.locality,
1604 )
1605 session.add(node)
1606 session.flush()
1607 trip = PublicTrip(
1608 user_id=user_id,
1609 node_id=node.id,
1610 from_date=from_date,
1611 to_date=to_date,
1612 description="Looking for a host!",
1613 status=status or PublicTripStatus.searching_for_host,
1614 same_gender_only=same_gender_only,
1615 )
1616 session.add(trip)
1617 session.flush()
1618 return trip.id
1621def test_create_request_with_public_trip(db, moderator):
1622 """Hosts can offer to host a public trip; offered dates must be within trip dates."""
1623 surfer, surfer_token = generate_user()
1624 host, host_token = generate_user()
1626 trip_from = today() + timedelta(days=10)
1627 trip_to = today() + timedelta(days=20)
1628 trip_id = _create_public_trip(surfer.id, trip_from, trip_to)
1630 with requests_session(host_token) as api:
1631 # Happy path: dates within trip window
1632 res = api.CreateHostRequest(
1633 requests_pb2.CreateHostRequestReq(
1634 host_user_id=surfer.id,
1635 from_date=(trip_from + timedelta(days=1)).isoformat(),
1636 to_date=(trip_to - timedelta(days=1)).isoformat(),
1637 text=valid_request_text(),
1638 public_trip_id=trip_id,
1639 )
1640 )
1641 host_request_id = res.host_request_id
1643 moderator.approve_host_request(host_request_id)
1645 with requests_session(host_token) as api:
1646 hr = api.GetHostRequest(requests_pb2.GetHostRequestReq(host_request_id=host_request_id))
1647 assert hr.public_trip_id == trip_id
1650def test_create_request_with_public_trip_dates_out_of_range(db):
1651 """Offered dates outside the trip window are rejected."""
1652 surfer, _ = generate_user()
1653 host, host_token = generate_user()
1655 trip_from = today() + timedelta(days=10)
1656 trip_to = today() + timedelta(days=20)
1657 trip_id = _create_public_trip(surfer.id, trip_from, trip_to)
1659 with requests_session(host_token) as api:
1660 # from_date before trip starts
1661 with pytest.raises(grpc.RpcError) as e:
1662 api.CreateHostRequest(
1663 requests_pb2.CreateHostRequestReq(
1664 host_user_id=surfer.id,
1665 from_date=(trip_from - timedelta(days=1)).isoformat(),
1666 to_date=(trip_from + timedelta(days=1)).isoformat(),
1667 text=valid_request_text(),
1668 public_trip_id=trip_id,
1669 )
1670 )
1671 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
1673 # to_date after trip ends
1674 with pytest.raises(grpc.RpcError) as e:
1675 api.CreateHostRequest(
1676 requests_pb2.CreateHostRequestReq(
1677 host_user_id=surfer.id,
1678 from_date=(trip_to - timedelta(days=1)).isoformat(),
1679 to_date=(trip_to + timedelta(days=1)).isoformat(),
1680 text=valid_request_text(),
1681 public_trip_id=trip_id,
1682 )
1683 )
1684 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
1687def test_create_request_with_public_trip_user_mismatch(db):
1688 """The host_user_id must match the public trip's traveler."""
1689 trip_owner, _ = generate_user()
1690 other_user, _ = generate_user()
1691 host, host_token = generate_user()
1693 trip_from = today() + timedelta(days=10)
1694 trip_to = today() + timedelta(days=20)
1695 trip_id = _create_public_trip(trip_owner.id, trip_from, trip_to)
1697 with requests_session(host_token) as api:
1698 with pytest.raises(grpc.RpcError) as e:
1699 api.CreateHostRequest(
1700 requests_pb2.CreateHostRequestReq(
1701 host_user_id=other_user.id, # not the trip owner
1702 from_date=(trip_from + timedelta(days=1)).isoformat(),
1703 to_date=(trip_to - timedelta(days=1)).isoformat(),
1704 text=valid_request_text(),
1705 public_trip_id=trip_id,
1706 )
1707 )
1708 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
1711def test_create_request_with_closed_public_trip(db):
1712 """Cannot offer to host a trip that's been closed."""
1713 surfer, _ = generate_user()
1714 host, host_token = generate_user()
1716 trip_from = today() + timedelta(days=10)
1717 trip_to = today() + timedelta(days=20)
1718 trip_id = _create_public_trip(surfer.id, trip_from, trip_to, status=PublicTripStatus.closed)
1720 with requests_session(host_token) as api:
1721 with pytest.raises(grpc.RpcError) as e:
1722 api.CreateHostRequest(
1723 requests_pb2.CreateHostRequestReq(
1724 host_user_id=surfer.id,
1725 from_date=(trip_from + timedelta(days=1)).isoformat(),
1726 to_date=(trip_to - timedelta(days=1)).isoformat(),
1727 text=valid_request_text(),
1728 public_trip_id=trip_id,
1729 )
1730 )
1731 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
1734def test_create_request_with_nonexistent_public_trip(db):
1735 """Nonexistent public trip ID returns NOT_FOUND."""
1736 surfer, _ = generate_user()
1737 host, host_token = generate_user()
1739 with requests_session(host_token) as api:
1740 with pytest.raises(grpc.RpcError) as e:
1741 api.CreateHostRequest(
1742 requests_pb2.CreateHostRequestReq(
1743 host_user_id=surfer.id,
1744 from_date=(today() + timedelta(days=2)).isoformat(),
1745 to_date=(today() + timedelta(days=3)).isoformat(),
1746 text=valid_request_text(),
1747 public_trip_id=999999,
1748 )
1749 )
1750 assert e.value.code() == grpc.StatusCode.NOT_FOUND
1753def test_create_request_without_public_trip_id_unchanged(db, moderator):
1754 """Existing flow without public_trip_id still works (backwards compatibility)."""
1755 surfer, _ = generate_user()
1756 host, host_token = generate_user()
1758 with requests_session(host_token) as api:
1759 res = api.CreateHostRequest(
1760 requests_pb2.CreateHostRequestReq(
1761 host_user_id=surfer.id,
1762 from_date=(today() + timedelta(days=2)).isoformat(),
1763 to_date=(today() + timedelta(days=3)).isoformat(),
1764 text=valid_request_text(),
1765 )
1766 )
1767 host_request_id = res.host_request_id
1769 moderator.approve_host_request(host_request_id)
1771 with requests_session(host_token) as api:
1772 hr = api.GetHostRequest(requests_pb2.GetHostRequestReq(host_request_id=host_request_id))
1773 assert not hr.HasField("public_trip_id")
1776def test_create_request_same_gender_only_wrong_gender_rejected(db):
1777 surfer, _ = generate_user(gender="Woman")
1778 _, host_token = generate_user(gender="Man")
1780 trip_from = today() + timedelta(days=10)
1781 trip_to = today() + timedelta(days=20)
1782 trip_id = _create_public_trip(surfer.id, trip_from, trip_to, same_gender_only=True)
1784 with requests_session(host_token) as api:
1785 with pytest.raises(grpc.RpcError) as e:
1786 api.CreateHostRequest(
1787 requests_pb2.CreateHostRequestReq(
1788 host_user_id=surfer.id,
1789 from_date=trip_from.isoformat(),
1790 to_date=trip_to.isoformat(),
1791 text=valid_request_text(),
1792 public_trip_id=trip_id,
1793 )
1794 )
1795 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
1798def test_create_request_same_gender_only_same_gender_allowed(db, moderator):
1799 surfer, _ = generate_user(gender="Woman")
1800 _, host_token = generate_user(gender="Woman")
1802 trip_from = today() + timedelta(days=10)
1803 trip_to = today() + timedelta(days=20)
1804 trip_id = _create_public_trip(surfer.id, trip_from, trip_to, same_gender_only=True)
1806 with requests_session(host_token) as api:
1807 res = api.CreateHostRequest(
1808 requests_pb2.CreateHostRequestReq(
1809 host_user_id=surfer.id,
1810 from_date=trip_from.isoformat(),
1811 to_date=trip_to.isoformat(),
1812 text=valid_request_text(),
1813 public_trip_id=trip_id,
1814 )
1815 )
1816 assert res.host_request_id > 0
1819def test_create_request_same_gender_only_moderator_bypass(db, moderator):
1820 surfer, _ = generate_user(gender="Woman")
1821 host, host_token = generate_user(gender="Man")
1823 trip_from = today() + timedelta(days=10)
1824 trip_to = today() + timedelta(days=20)
1825 trip_id = _create_public_trip(surfer.id, trip_from, trip_to, same_gender_only=True)
1826 _make_trip_node_admin(host.id, trip_id)
1828 with requests_session(host_token) as api:
1829 res = api.CreateHostRequest(
1830 requests_pb2.CreateHostRequestReq(
1831 host_user_id=surfer.id,
1832 from_date=trip_from.isoformat(),
1833 to_date=trip_to.isoformat(),
1834 text=valid_request_text(),
1835 public_trip_id=trip_id,
1836 )
1837 )
1838 assert res.host_request_id > 0
1841def test_create_request_duplicate_offer_rejected(db):
1842 surfer, _ = generate_user()
1843 _, host_token = generate_user()
1845 trip_from = today() + timedelta(days=10)
1846 trip_to = today() + timedelta(days=20)
1847 trip_id = _create_public_trip(surfer.id, trip_from, trip_to)
1849 with requests_session(host_token) as api:
1850 api.CreateHostRequest(
1851 requests_pb2.CreateHostRequestReq(
1852 host_user_id=surfer.id,
1853 from_date=trip_from.isoformat(),
1854 to_date=trip_to.isoformat(),
1855 text=valid_request_text(),
1856 public_trip_id=trip_id,
1857 )
1858 )
1859 with pytest.raises(grpc.RpcError) as e:
1860 api.CreateHostRequest(
1861 requests_pb2.CreateHostRequestReq(
1862 host_user_id=surfer.id,
1863 from_date=trip_from.isoformat(),
1864 to_date=trip_to.isoformat(),
1865 text=valid_request_text(),
1866 public_trip_id=trip_id,
1867 )
1868 )
1869 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION