Coverage for app / backend / src / tests / test_requests.py: 99%
732 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-19 14:14 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-19 14:14 +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 Message,
17 MessageType,
18 RateLimitAction,
19)
20from couchers.proto import (
21 api_pb2,
22 auth_pb2,
23 conversations_pb2,
24 requests_pb2,
25)
26from couchers.proto.internal import unsubscribe_pb2
27from couchers.rate_limits.definitions import RATE_LIMIT_DEFINITIONS, RATE_LIMIT_HOURS
28from couchers.utils import create_coordinate, now, today
29from tests.fixtures.db import generate_user
30from tests.fixtures.misc import PushCollector, email_fields, mock_notification_email
31from tests.fixtures.sessions import api_session, auth_api_session, requests_session
34@pytest.fixture(autouse=True)
35def _(testconfig):
36 pass
39def valid_request_text(text: str = "Test request") -> str:
40 """Pads a request text to a valid length."""
41 # Request lengths are measured in utf-16 code units to match the frontend.
42 utf16_length = len(text.encode("utf-16-le")) // 2
43 if utf16_length >= HOST_REQUEST_MIN_LENGTH_UTF16: 43 ↛ 44line 43 didn't jump to line 44 because the condition on line 43 was never true
44 return text
45 padding_length = HOST_REQUEST_MIN_LENGTH_UTF16 - utf16_length
46 return text + ("_" * padding_length) # Each "_" adds one utf16 code unit.
49def test_create_request(db, moderator):
50 user1, token1 = generate_user()
51 hosting_city = "Morningside Heights, New York City"
52 hosting_lat = 40.8086
53 hosting_lng = -73.9616
54 hosting_radius = 500
55 user2, token2 = generate_user(
56 city=hosting_city,
57 geom=create_coordinate(hosting_lat, hosting_lng),
58 geom_radius=hosting_radius,
59 )
61 today_plus_2 = today() + timedelta(days=2)
62 today_plus_3 = today() + timedelta(days=3)
63 today_minus_2 = today() - timedelta(days=2)
64 today_minus_3 = today() - timedelta(days=3)
66 with requests_session(token1) as api:
67 with pytest.raises(grpc.RpcError) as e:
68 api.CreateHostRequest(
69 requests_pb2.CreateHostRequestReq(
70 host_user_id=user1.id,
71 from_date=today_plus_2.isoformat(),
72 to_date=today_plus_3.isoformat(),
73 text=valid_request_text(),
74 )
75 )
76 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
77 assert e.value.details() == "Can't request hosting from yourself."
79 with pytest.raises(grpc.RpcError) as e:
80 api.CreateHostRequest(
81 requests_pb2.CreateHostRequestReq(
82 host_user_id=999,
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.NOT_FOUND
89 assert e.value.details() == "Couldn't find that user."
91 with pytest.raises(grpc.RpcError) as e:
92 api.CreateHostRequest(
93 requests_pb2.CreateHostRequestReq(
94 host_user_id=user2.id,
95 from_date=today_plus_3.isoformat(),
96 to_date=today_plus_2.isoformat(),
97 text=valid_request_text(),
98 )
99 )
100 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
101 assert e.value.details() == "From date can't be after to date."
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_minus_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 must be today or later."
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_plus_2.isoformat(),
120 to_date=today_minus_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 can't be after to date."
127 with pytest.raises(grpc.RpcError) as e:
128 api.CreateHostRequest(
129 requests_pb2.CreateHostRequestReq(
130 host_user_id=user2.id,
131 from_date="2020-00-06",
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() == "Invalid 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=today_plus_2.isoformat(),
144 to_date=today_plus_3.isoformat(),
145 text="Too short.",
146 )
147 )
148 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
149 assert e.value.details() == "Host request cannot be shorter than 250 characters."
151 res = api.CreateHostRequest(
152 requests_pb2.CreateHostRequestReq(
153 host_user_id=user2.id,
154 from_date=today_plus_2.isoformat(),
155 to_date=today_plus_3.isoformat(),
156 text=valid_request_text(),
157 )
158 )
159 host_request_id = res.host_request_id
161 moderator.approve_host_request(host_request_id)
163 with requests_session(token1) as api:
164 host_requests = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_sent=True)).host_requests
166 assert len(host_requests) == 1
167 hr = host_requests[0]
169 assert hr.latest_message.text.text == valid_request_text()
171 assert hr.hosting_city == hosting_city
172 assert round(hr.hosting_lat, 4) == hosting_lat
173 assert round(hr.hosting_lng, 4) == hosting_lng
174 assert hr.hosting_radius == hosting_radius
176 today_ = today()
177 today_plus_one_year = today_ + timedelta(days=365)
178 today_plus_one_year_plus_2 = today_plus_one_year + timedelta(days=2)
179 today_plus_one_year_plus_3 = today_plus_one_year + timedelta(days=3)
180 with pytest.raises(grpc.RpcError) as e:
181 api.CreateHostRequest(
182 requests_pb2.CreateHostRequestReq(
183 host_user_id=user2.id,
184 from_date=today_plus_one_year_plus_2.isoformat(),
185 to_date=today_plus_one_year_plus_3.isoformat(),
186 text=valid_request_text("Test from date after one year"),
187 )
188 )
189 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
190 assert e.value.details() == "The start date must be within one year from today."
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_2.isoformat(),
197 to_date=today_plus_one_year_plus_3.isoformat(),
198 text=valid_request_text("Test to date one year after from date"),
199 )
200 )
201 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
202 assert e.value.details() == "You cannot request to stay with someone for longer than one year."
205def test_create_request_incomplete_profile(db):
206 user1, token1 = generate_user(complete_profile=False)
207 user2, _ = generate_user()
208 today_plus_2 = today() + timedelta(days=2)
209 today_plus_3 = today() + timedelta(days=3)
210 with requests_session(token1) as api:
211 with pytest.raises(grpc.RpcError) as e:
212 api.CreateHostRequest(
213 requests_pb2.CreateHostRequestReq(
214 host_user_id=user2.id,
215 from_date=today_plus_2.isoformat(),
216 to_date=today_plus_3.isoformat(),
217 text=valid_request_text(),
218 )
219 )
220 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
221 assert e.value.details() == "You have to complete your profile before you can send a request."
224def test_excessive_requests_are_reported(db):
225 """Test that excessive host requests are first reported in a warning email and finally lead blocking of further requests."""
226 user, token = generate_user()
227 today_plus_2 = today() + timedelta(days=2)
228 today_plus_3 = today() + timedelta(days=3)
229 rate_limit_definition = RATE_LIMIT_DEFINITIONS[RateLimitAction.host_request]
230 with requests_session(token) as api:
231 # Test warning email
232 with mock_notification_email() as mock_email:
233 for _ in range(rate_limit_definition.warning_limit):
234 host_user, _ = generate_user()
235 _ = api.CreateHostRequest(
236 requests_pb2.CreateHostRequestReq(
237 host_user_id=host_user.id,
238 from_date=today_plus_2.isoformat(),
239 to_date=today_plus_3.isoformat(),
240 text=valid_request_text(),
241 )
242 )
244 assert mock_email.call_count == 0
245 host_user, _ = generate_user()
246 _ = api.CreateHostRequest(
247 requests_pb2.CreateHostRequestReq(
248 host_user_id=host_user.id,
249 from_date=today_plus_2.isoformat(),
250 to_date=today_plus_3.isoformat(),
251 text=valid_request_text("Excessive test request"),
252 )
253 )
254 assert mock_email.call_count == 1
255 email = email_fields(mock_email).plain
256 assert email.startswith(
257 f"User {user.username} has sent {rate_limit_definition.warning_limit} host requests in the past {RATE_LIMIT_HOURS} hours."
258 )
260 # Test ban after exceeding HOST_REQUEST_HARD_LIMIT
261 with mock_notification_email() as mock_email:
262 for _ in range(rate_limit_definition.hard_limit - rate_limit_definition.warning_limit - 1):
263 host_user, _ = generate_user()
264 _ = api.CreateHostRequest(
265 requests_pb2.CreateHostRequestReq(
266 host_user_id=host_user.id,
267 from_date=today_plus_2.isoformat(),
268 to_date=today_plus_3.isoformat(),
269 text=valid_request_text(),
270 )
271 )
273 assert mock_email.call_count == 0
274 host_user, _ = generate_user()
275 with pytest.raises(grpc.RpcError) as exc_info:
276 _ = api.CreateHostRequest(
277 requests_pb2.CreateHostRequestReq(
278 host_user_id=host_user.id,
279 from_date=today_plus_2.isoformat(),
280 to_date=today_plus_3.isoformat(),
281 text=valid_request_text("Excessive test request"),
282 )
283 )
284 assert exc_info.value.code() == grpc.StatusCode.RESOURCE_EXHAUSTED
285 assert (
286 exc_info.value.details()
287 == "You have sent a lot of host requests in the past 24 hours. To avoid spam, you can't send any more for now."
288 )
290 assert mock_email.call_count == 1
291 email = email_fields(mock_email).plain
292 assert email.startswith(
293 f"User {user.username} has sent {rate_limit_definition.hard_limit} host requests in the past {RATE_LIMIT_HOURS} hours."
294 )
295 assert "The user has been blocked from sending further host requests for now." in email
298def add_message(db, text, author_id, conversation_id):
299 with session_scope() as session:
300 message = Message(
301 conversation_id=conversation_id, author_id=author_id, text=text, message_type=MessageType.text
302 )
304 session.add(message)
307def test_GetHostRequest(db):
308 user1, token1 = generate_user()
309 user2, token2 = generate_user()
310 user3, token3 = generate_user()
311 today_plus_2 = today() + timedelta(days=2)
312 today_plus_3 = today() + timedelta(days=3)
313 with requests_session(token1) as api:
314 host_request_id = api.CreateHostRequest(
315 requests_pb2.CreateHostRequestReq(
316 host_user_id=user2.id,
317 from_date=today_plus_2.isoformat(),
318 to_date=today_plus_3.isoformat(),
319 text=valid_request_text("Test request 1"),
320 )
321 ).host_request_id
323 with pytest.raises(grpc.RpcError) as e:
324 api.GetHostRequest(requests_pb2.GetHostRequestReq(host_request_id=999))
325 assert e.value.code() == grpc.StatusCode.NOT_FOUND
326 assert e.value.details() == "Couldn't find that host request."
328 api.SendHostRequestMessage(
329 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 1")
330 )
332 res = api.GetHostRequest(requests_pb2.GetHostRequestReq(host_request_id=host_request_id))
333 assert res.latest_message.text.text == "Test message 1"
336def test_ListHostRequests(db, moderator):
337 user1, token1 = generate_user()
338 user2, token2 = generate_user()
339 user3, token3 = generate_user()
340 today_plus_2 = today() + timedelta(days=2)
341 today_plus_3 = today() + timedelta(days=3)
342 with requests_session(token1) as api:
343 host_request_1 = api.CreateHostRequest(
344 requests_pb2.CreateHostRequestReq(
345 host_user_id=user2.id,
346 from_date=today_plus_2.isoformat(),
347 to_date=today_plus_3.isoformat(),
348 text=valid_request_text("Test request 1"),
349 )
350 ).host_request_id
352 host_request_2 = api.CreateHostRequest(
353 requests_pb2.CreateHostRequestReq(
354 host_user_id=user3.id,
355 from_date=today_plus_2.isoformat(),
356 to_date=today_plus_3.isoformat(),
357 text=valid_request_text("Test request 2"),
358 )
359 ).host_request_id
361 moderator.approve_host_request(host_request_1)
362 moderator.approve_host_request(host_request_2)
364 with requests_session(token1) as api:
365 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_sent=True))
366 assert res.no_more
367 assert len(res.host_requests) == 2
369 with requests_session(token2) as api:
370 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_received=True))
371 assert res.no_more
372 assert len(res.host_requests) == 1
373 assert res.host_requests[0].latest_message.text.text == valid_request_text("Test request 1")
374 assert res.host_requests[0].surfer_user_id == user1.id
375 assert res.host_requests[0].host_user_id == user2.id
376 assert res.host_requests[0].status == conversations_pb2.HOST_REQUEST_STATUS_PENDING
378 add_message(db, "Test request 1 message 1", user2.id, host_request_1)
379 add_message(db, "Test request 1 message 2", user2.id, host_request_1)
380 add_message(db, "Test request 1 message 3", user2.id, host_request_1)
382 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_received=True))
383 assert res.host_requests[0].latest_message.text.text == "Test request 1 message 3"
385 host_request_3 = api.CreateHostRequest(
386 requests_pb2.CreateHostRequestReq(
387 host_user_id=user1.id,
388 from_date=today_plus_2.isoformat(),
389 to_date=today_plus_3.isoformat(),
390 text=valid_request_text("Test request 3"),
391 )
392 ).host_request_id
394 moderator.approve_host_request(host_request_3)
396 add_message(db, "Test request 2 message 1", user1.id, host_request_2)
397 add_message(db, "Test request 2 message 2", user3.id, host_request_2)
399 with requests_session(token3) as api:
400 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_received=True))
401 assert res.no_more
402 assert len(res.host_requests) == 1
403 assert res.host_requests[0].latest_message.text.text == "Test request 2 message 2"
405 with requests_session(token1) as api:
406 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_received=True))
407 assert len(res.host_requests) == 1
409 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq())
410 assert len(res.host_requests) == 3
413def test_ListHostRequests_pagination_regression(db, moderator):
414 """
415 ListHostRequests was skipping a request when getting multiple pages
416 """
417 user1, token1 = generate_user()
418 user2, token2 = generate_user()
419 today_plus_2 = today() + timedelta(days=2)
420 today_plus_3 = today() + timedelta(days=3)
421 with requests_session(token1) as api:
422 host_request_1 = api.CreateHostRequest(
423 requests_pb2.CreateHostRequestReq(
424 host_user_id=user2.id,
425 from_date=today_plus_2.isoformat(),
426 to_date=today_plus_3.isoformat(),
427 text=valid_request_text("Test request 1"),
428 )
429 ).host_request_id
431 host_request_2 = api.CreateHostRequest(
432 requests_pb2.CreateHostRequestReq(
433 host_user_id=user2.id,
434 from_date=today_plus_2.isoformat(),
435 to_date=today_plus_3.isoformat(),
436 text=valid_request_text("Test request 2"),
437 )
438 ).host_request_id
440 host_request_3 = api.CreateHostRequest(
441 requests_pb2.CreateHostRequestReq(
442 host_user_id=user2.id,
443 from_date=today_plus_2.isoformat(),
444 to_date=today_plus_3.isoformat(),
445 text=valid_request_text("Test request 3"),
446 )
447 ).host_request_id
449 moderator.approve_host_request(host_request_1)
450 moderator.approve_host_request(host_request_2)
451 moderator.approve_host_request(host_request_3)
453 with requests_session(token2) as api:
454 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_received=True))
455 assert res.no_more
456 assert len(res.host_requests) == 3
457 assert res.host_requests[0].latest_message.text.text == valid_request_text("Test request 3")
458 assert res.host_requests[1].latest_message.text.text == valid_request_text("Test request 2")
459 assert res.host_requests[2].latest_message.text.text == valid_request_text("Test request 1")
461 with requests_session(token2) as api:
462 api.RespondHostRequest(
463 requests_pb2.RespondHostRequestReq(
464 host_request_id=host_request_2,
465 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED,
466 text="Accepting host request 2",
467 )
468 )
469 api.RespondHostRequest(
470 requests_pb2.RespondHostRequestReq(
471 host_request_id=host_request_1,
472 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED,
473 text="Accepting host request 1",
474 )
475 )
476 api.RespondHostRequest(
477 requests_pb2.RespondHostRequestReq(
478 host_request_id=host_request_3,
479 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED,
480 text="Accepting host request 3",
481 )
482 )
484 with requests_session(token2) as api:
485 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_received=True))
486 assert res.no_more
487 assert len(res.host_requests) == 3
488 assert res.host_requests[0].latest_message.text.text == "Accepting host request 3"
489 assert res.host_requests[1].latest_message.text.text == "Accepting host request 1"
490 assert res.host_requests[2].latest_message.text.text == "Accepting host request 2"
492 with requests_session(token2) as api:
493 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_received=True, number=1))
494 assert not res.no_more
495 assert len(res.host_requests) == 1
496 assert res.host_requests[0].latest_message.text.text == "Accepting host request 3"
497 res = api.ListHostRequests(
498 requests_pb2.ListHostRequestsReq(only_received=True, number=1, last_request_id=res.last_request_id)
499 )
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 1"
503 res = api.ListHostRequests(
504 requests_pb2.ListHostRequestsReq(only_received=True, number=1, last_request_id=res.last_request_id)
505 )
506 assert res.no_more
507 assert len(res.host_requests) == 1
508 assert res.host_requests[0].latest_message.text.text == "Accepting host request 2"
511def test_ListHostRequests_active_filter(db, moderator):
512 user1, token1 = generate_user()
513 user2, token2 = generate_user()
514 today_plus_2 = today() + timedelta(days=2)
515 today_plus_3 = today() + timedelta(days=3)
517 with requests_session(token1) as api:
518 request_id = api.CreateHostRequest(
519 requests_pb2.CreateHostRequestReq(
520 host_user_id=user2.id,
521 from_date=today_plus_2.isoformat(),
522 to_date=today_plus_3.isoformat(),
523 text=valid_request_text("Test request 1"),
524 )
525 ).host_request_id
527 moderator.approve_host_request(request_id)
529 with requests_session(token1) as api:
530 api.RespondHostRequest(
531 requests_pb2.RespondHostRequestReq(
532 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED
533 )
534 )
536 with requests_session(token2) as api:
537 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_received=True))
538 assert len(res.host_requests) == 1
539 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_active=True))
540 assert len(res.host_requests) == 0
543def test_RespondHostRequests(db, moderator):
544 user1, token1 = generate_user()
545 user2, token2 = generate_user()
546 user3, token3 = generate_user()
547 today_plus_2 = today() + timedelta(days=2)
548 today_plus_3 = today() + timedelta(days=3)
550 with requests_session(token1) as api:
551 request_id = api.CreateHostRequest(
552 requests_pb2.CreateHostRequestReq(
553 host_user_id=user2.id,
554 from_date=today_plus_2.isoformat(),
555 to_date=today_plus_3.isoformat(),
556 text=valid_request_text("Test request 1"),
557 )
558 ).host_request_id
560 moderator.approve_host_request(request_id)
562 # another user can't access
563 with requests_session(token3) as api:
564 with pytest.raises(grpc.RpcError) as e:
565 api.RespondHostRequest(
566 requests_pb2.RespondHostRequestReq(
567 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED
568 )
569 )
570 assert e.value.code() == grpc.StatusCode.NOT_FOUND
571 assert e.value.details() == "Couldn't find that host request."
573 with requests_session(token1) as api:
574 with pytest.raises(grpc.RpcError) as e:
575 api.RespondHostRequest(
576 requests_pb2.RespondHostRequestReq(
577 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED
578 )
579 )
580 assert e.value.code() == grpc.StatusCode.PERMISSION_DENIED
581 assert e.value.details() == "You are not the host of this request."
583 with requests_session(token2) as api:
584 # non existing id
585 with pytest.raises(grpc.RpcError) as e:
586 api.RespondHostRequest(
587 requests_pb2.RespondHostRequestReq(
588 host_request_id=9999, status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED
589 )
590 )
591 assert e.value.code() == grpc.StatusCode.NOT_FOUND
593 # host can't confirm or cancel (host should accept/reject)
594 with pytest.raises(grpc.RpcError) as e:
595 api.RespondHostRequest(
596 requests_pb2.RespondHostRequestReq(
597 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CONFIRMED
598 )
599 )
600 assert e.value.code() == grpc.StatusCode.PERMISSION_DENIED
601 assert e.value.details() == "You can't set the host request status to that."
602 with pytest.raises(grpc.RpcError) as e:
603 api.RespondHostRequest(
604 requests_pb2.RespondHostRequestReq(
605 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED
606 )
607 )
608 assert e.value.code() == grpc.StatusCode.PERMISSION_DENIED
609 assert e.value.details() == "You can't set the host request status to that."
611 api.RespondHostRequest(
612 requests_pb2.RespondHostRequestReq(
613 host_request_id=request_id,
614 status=conversations_pb2.HOST_REQUEST_STATUS_REJECTED,
615 text="Test rejection message",
616 )
617 )
618 res = api.GetHostRequestMessages(requests_pb2.GetHostRequestMessagesReq(host_request_id=request_id))
619 assert res.messages[0].text.text == "Test rejection message"
620 assert res.messages[1].WhichOneof("content") == "host_request_status_changed"
621 assert res.messages[1].host_request_status_changed.status == conversations_pb2.HOST_REQUEST_STATUS_REJECTED
622 # should be able to move from rejected -> accepted
623 api.RespondHostRequest(
624 requests_pb2.RespondHostRequestReq(
625 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED
626 )
627 )
629 with requests_session(token1) as api:
630 # can't make pending
631 with pytest.raises(grpc.RpcError) as e:
632 api.RespondHostRequest(
633 requests_pb2.RespondHostRequestReq(
634 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_PENDING
635 )
636 )
637 assert e.value.code() == grpc.StatusCode.PERMISSION_DENIED
638 assert e.value.details() == "You can't set the host request status to that."
640 # can confirm then cancel
641 api.RespondHostRequest(
642 requests_pb2.RespondHostRequestReq(
643 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CONFIRMED
644 )
645 )
647 api.RespondHostRequest(
648 requests_pb2.RespondHostRequestReq(
649 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED
650 )
651 )
653 # can't confirm after having cancelled
654 with pytest.raises(grpc.RpcError) as e:
655 api.RespondHostRequest(
656 requests_pb2.RespondHostRequestReq(
657 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CONFIRMED
658 )
659 )
660 assert e.value.code() == grpc.StatusCode.PERMISSION_DENIED
661 assert e.value.details() == "You can't set the host request status to that."
663 # at this point there should be 7 messages
664 # 2 for creation, 2 for the status change with message, 3 for the other status changed
665 with requests_session(token1) as api:
666 res = api.GetHostRequestMessages(requests_pb2.GetHostRequestMessagesReq(host_request_id=request_id))
667 assert len(res.messages) == 7
668 assert res.messages[0].host_request_status_changed.status == conversations_pb2.HOST_REQUEST_STATUS_CANCELLED
669 assert res.messages[1].host_request_status_changed.status == conversations_pb2.HOST_REQUEST_STATUS_CONFIRMED
670 assert res.messages[2].host_request_status_changed.status == conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED
671 assert res.messages[4].host_request_status_changed.status == conversations_pb2.HOST_REQUEST_STATUS_REJECTED
672 assert res.messages[6].WhichOneof("content") == "chat_created"
675def test_get_host_request_messages(db, moderator):
676 user1, token1 = generate_user()
677 user2, token2 = generate_user()
678 today_plus_2 = today() + timedelta(days=2)
679 today_plus_3 = today() + timedelta(days=3)
680 with requests_session(token1) as api:
681 res = api.CreateHostRequest(
682 requests_pb2.CreateHostRequestReq(
683 host_user_id=user2.id,
684 from_date=today_plus_2.isoformat(),
685 to_date=today_plus_3.isoformat(),
686 text=valid_request_text("Test request 1"),
687 )
688 )
689 conversation_id = res.host_request_id
691 moderator.approve_host_request(conversation_id)
693 add_message(db, "Test request 1 message 1", user1.id, conversation_id)
694 add_message(db, "Test request 1 message 2", user1.id, conversation_id)
695 add_message(db, "Test request 1 message 3", user1.id, conversation_id)
697 with requests_session(token2) as api:
698 api.RespondHostRequest(
699 requests_pb2.RespondHostRequestReq(
700 host_request_id=conversation_id, status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED
701 )
702 )
704 add_message(db, "Test request 1 message 4", user2.id, conversation_id)
705 add_message(db, "Test request 1 message 5", user2.id, conversation_id)
707 api.RespondHostRequest(
708 requests_pb2.RespondHostRequestReq(
709 host_request_id=conversation_id, status=conversations_pb2.HOST_REQUEST_STATUS_REJECTED
710 )
711 )
713 with requests_session(token1) as api:
714 # 9 including initial message
715 res = api.GetHostRequestMessages(requests_pb2.GetHostRequestMessagesReq(host_request_id=conversation_id))
716 assert len(res.messages) == 9
717 assert res.no_more
719 res = api.GetHostRequestMessages(
720 requests_pb2.GetHostRequestMessagesReq(host_request_id=conversation_id, number=3)
721 )
722 assert not res.no_more
723 assert len(res.messages) == 3
724 assert res.messages[0].host_request_status_changed.status == conversations_pb2.HOST_REQUEST_STATUS_REJECTED
725 assert res.messages[0].WhichOneof("content") == "host_request_status_changed"
726 assert res.messages[1].text.text == "Test request 1 message 5"
727 assert res.messages[2].text.text == "Test request 1 message 4"
729 res = api.GetHostRequestMessages(
730 requests_pb2.GetHostRequestMessagesReq(
731 host_request_id=conversation_id,
732 last_message_id=res.messages[2].message_id,
733 number=6,
734 )
735 )
736 assert res.no_more
737 assert len(res.messages) == 6
738 assert res.messages[0].host_request_status_changed.status == conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED
739 assert res.messages[0].WhichOneof("content") == "host_request_status_changed"
740 assert res.messages[1].text.text == "Test request 1 message 3"
741 assert res.messages[2].text.text == "Test request 1 message 2"
742 assert res.messages[3].text.text == "Test request 1 message 1"
743 assert res.messages[4].text.text == valid_request_text("Test request 1")
744 assert res.messages[5].WhichOneof("content") == "chat_created"
747def test_SendHostRequestMessage(db, moderator):
748 user1, token1 = generate_user()
749 user2, token2 = generate_user()
750 user3, token3 = generate_user()
751 today_plus_2 = today() + timedelta(days=2)
752 today_plus_3 = today() + timedelta(days=3)
753 with requests_session(token1) as api:
754 host_request_id = api.CreateHostRequest(
755 requests_pb2.CreateHostRequestReq(
756 host_user_id=user2.id,
757 from_date=today_plus_2.isoformat(),
758 to_date=today_plus_3.isoformat(),
759 text=valid_request_text("Test request 1"),
760 )
761 ).host_request_id
763 moderator.approve_host_request(host_request_id)
765 with requests_session(token1) as api:
766 with pytest.raises(grpc.RpcError) as e:
767 api.SendHostRequestMessage(
768 requests_pb2.SendHostRequestMessageReq(host_request_id=999, text="Test message 1")
769 )
770 assert e.value.code() == grpc.StatusCode.NOT_FOUND
772 with pytest.raises(grpc.RpcError) as e:
773 api.SendHostRequestMessage(requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text=""))
774 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
775 assert e.value.details() == "Invalid message."
777 api.SendHostRequestMessage(
778 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 1")
779 )
780 res = api.GetHostRequestMessages(requests_pb2.GetHostRequestMessagesReq(host_request_id=host_request_id))
781 assert res.messages[0].text.text == "Test message 1"
782 assert res.messages[0].author_user_id == user1.id
784 with requests_session(token3) as api:
785 # other user can't send
786 with pytest.raises(grpc.RpcError) as e:
787 api.SendHostRequestMessage(
788 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 2")
789 )
790 assert e.value.code() == grpc.StatusCode.NOT_FOUND
791 assert e.value.details() == "Couldn't find that host request."
793 with requests_session(token2) as api:
794 api.SendHostRequestMessage(
795 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 2")
796 )
797 res = api.GetHostRequestMessages(requests_pb2.GetHostRequestMessagesReq(host_request_id=host_request_id))
798 # including 2 for creation control message and message
799 assert len(res.messages) == 4
800 assert res.messages[0].text.text == "Test message 2"
801 assert res.messages[0].author_user_id == user2.id
803 # CAN send messages to a rejected, confirmed or cancelled request, and for accepted
804 api.RespondHostRequest(
805 requests_pb2.RespondHostRequestReq(
806 host_request_id=host_request_id, status=conversations_pb2.HOST_REQUEST_STATUS_REJECTED
807 )
808 )
809 api.SendHostRequestMessage(
810 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 3")
811 )
813 api.RespondHostRequest(
814 requests_pb2.RespondHostRequestReq(
815 host_request_id=host_request_id, status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED
816 )
817 )
819 with requests_session(token1) as api:
820 api.RespondHostRequest(
821 requests_pb2.RespondHostRequestReq(
822 host_request_id=host_request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CONFIRMED
823 )
824 )
825 api.SendHostRequestMessage(
826 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 3")
827 )
829 api.RespondHostRequest(
830 requests_pb2.RespondHostRequestReq(
831 host_request_id=host_request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED
832 )
833 )
834 api.SendHostRequestMessage(
835 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 3")
836 )
839def test_get_updates(db, moderator):
840 user1, token1 = generate_user()
841 user2, token2 = generate_user()
842 user3, token3 = generate_user()
843 today_plus_2 = today() + timedelta(days=2)
844 today_plus_3 = today() + timedelta(days=3)
845 with requests_session(token1) as api:
846 host_request_id = api.CreateHostRequest(
847 requests_pb2.CreateHostRequestReq(
848 host_user_id=user2.id,
849 from_date=today_plus_2.isoformat(),
850 to_date=today_plus_3.isoformat(),
851 text=valid_request_text("Test message 0"),
852 )
853 ).host_request_id
855 moderator.approve_host_request(host_request_id)
857 with requests_session(token1) as api:
858 api.SendHostRequestMessage(
859 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 1")
860 )
861 api.SendHostRequestMessage(
862 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 2")
863 )
864 api.RespondHostRequest(
865 requests_pb2.RespondHostRequestReq(
866 host_request_id=host_request_id,
867 status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED,
868 text="Test message 3",
869 )
870 )
872 api.CreateHostRequest(
873 requests_pb2.CreateHostRequestReq(
874 host_user_id=user2.id,
875 from_date=today_plus_2.isoformat(),
876 to_date=today_plus_3.isoformat(),
877 text=valid_request_text("Test message 4"),
878 )
879 )
881 res = api.GetHostRequestMessages(requests_pb2.GetHostRequestMessagesReq(host_request_id=host_request_id))
882 assert len(res.messages) == 6
883 assert res.messages[0].text.text == "Test message 3"
884 assert res.messages[1].host_request_status_changed.status == conversations_pb2.HOST_REQUEST_STATUS_CANCELLED
885 assert res.messages[2].text.text == "Test message 2"
886 assert res.messages[3].text.text == "Test message 1"
887 assert res.messages[4].text.text == valid_request_text("Test message 0")
888 message_id_3 = res.messages[0].message_id
889 message_id_cancel = res.messages[1].message_id
890 message_id_2 = res.messages[2].message_id
891 message_id_1 = res.messages[3].message_id
892 message_id_0 = res.messages[4].message_id
894 with pytest.raises(grpc.RpcError) as e:
895 api.GetHostRequestUpdates(requests_pb2.GetHostRequestUpdatesReq(newest_message_id=0))
896 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
898 res = api.GetHostRequestUpdates(requests_pb2.GetHostRequestUpdatesReq(newest_message_id=message_id_1))
899 assert res.no_more
900 assert len(res.updates) == 5
901 assert res.updates[0].message.text.text == "Test message 2"
902 assert (
903 res.updates[1].message.host_request_status_changed.status == conversations_pb2.HOST_REQUEST_STATUS_CANCELLED
904 )
905 assert res.updates[1].status == conversations_pb2.HOST_REQUEST_STATUS_CANCELLED
906 assert res.updates[2].message.text.text == "Test message 3"
907 assert res.updates[3].message.WhichOneof("content") == "chat_created"
908 assert res.updates[3].status == conversations_pb2.HOST_REQUEST_STATUS_PENDING
909 assert res.updates[4].message.text.text == valid_request_text("Test message 4")
911 res = api.GetHostRequestUpdates(requests_pb2.GetHostRequestUpdatesReq(newest_message_id=message_id_1, number=1))
912 assert not res.no_more
913 assert len(res.updates) == 1
914 assert res.updates[0].message.text.text == "Test message 2"
915 assert res.updates[0].status == conversations_pb2.HOST_REQUEST_STATUS_CANCELLED
917 with requests_session(token3) as api:
918 # other user can't access
919 res = api.GetHostRequestUpdates(requests_pb2.GetHostRequestUpdatesReq(newest_message_id=message_id_1))
920 assert len(res.updates) == 0
923def test_archive_host_request(db, moderator):
924 user1, token1 = generate_user()
925 user2, token2 = generate_user()
927 today_plus_2 = today() + timedelta(days=2)
928 today_plus_3 = today() + timedelta(days=3)
930 with requests_session(token1) as api:
931 host_request_id = api.CreateHostRequest(
932 requests_pb2.CreateHostRequestReq(
933 host_user_id=user2.id,
934 from_date=today_plus_2.isoformat(),
935 to_date=today_plus_3.isoformat(),
936 text=valid_request_text("Test message 0"),
937 )
938 ).host_request_id
940 api.SendHostRequestMessage(
941 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 1")
942 )
943 api.SendHostRequestMessage(
944 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 2")
945 )
947 moderator.approve_host_request(host_request_id)
949 # happy path archiving host request
950 with requests_session(token1) as api:
951 api.RespondHostRequest(
952 requests_pb2.RespondHostRequestReq(
953 host_request_id=host_request_id,
954 status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED,
955 text="Test message 3",
956 )
957 )
958 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_sent=True))
959 assert len(res.host_requests) == 1
960 assert res.host_requests[0].status == conversations_pb2.HOST_REQUEST_STATUS_CANCELLED
962 # Verify is_archived is False before archiving
963 res = api.GetHostRequest(requests_pb2.GetHostRequestReq(host_request_id=host_request_id))
964 assert not res.is_archived
966 api.SetHostRequestArchiveStatus(
967 requests_pb2.SetHostRequestArchiveStatusReq(host_request_id=host_request_id, is_archived=True)
968 )
969 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_archived=True))
970 assert len(res.host_requests) == 1
972 # Verify is_archived is True after archiving
973 res = api.GetHostRequest(requests_pb2.GetHostRequestReq(host_request_id=host_request_id))
974 assert res.is_archived
977def test_mark_last_seen(db, moderator):
978 user1, token1 = generate_user()
979 user2, token2 = generate_user()
980 user3, token3 = generate_user()
981 today_plus_2 = today() + timedelta(days=2)
982 today_plus_3 = today() + timedelta(days=3)
983 with requests_session(token1) as api:
984 host_request_id = api.CreateHostRequest(
985 requests_pb2.CreateHostRequestReq(
986 host_user_id=user2.id,
987 from_date=today_plus_2.isoformat(),
988 to_date=today_plus_3.isoformat(),
989 text=valid_request_text("Test message 0"),
990 )
991 ).host_request_id
993 host_request_id_2 = api.CreateHostRequest(
994 requests_pb2.CreateHostRequestReq(
995 host_user_id=user2.id,
996 from_date=today_plus_2.isoformat(),
997 to_date=today_plus_3.isoformat(),
998 text=valid_request_text("Test message 0a"),
999 )
1000 ).host_request_id
1002 moderator.approve_host_request(host_request_id)
1003 moderator.approve_host_request(host_request_id_2)
1005 with requests_session(token1) as api:
1006 api.SendHostRequestMessage(
1007 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 1")
1008 )
1009 api.SendHostRequestMessage(
1010 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 2")
1011 )
1012 api.RespondHostRequest(
1013 requests_pb2.RespondHostRequestReq(
1014 host_request_id=host_request_id,
1015 status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED,
1016 text="Test message 3",
1017 )
1018 )
1020 moderator.approve_host_request(host_request_id)
1021 moderator.approve_host_request(host_request_id_2)
1023 # test Ping unseen host request count, should be automarked after sending
1024 with api_session(token1) as api:
1025 assert api.Ping(api_pb2.PingReq()).unseen_received_host_request_count == 0
1026 assert api.Ping(api_pb2.PingReq()).unseen_sent_host_request_count == 0
1028 with api_session(token2) as api:
1029 assert api.Ping(api_pb2.PingReq()).unseen_received_host_request_count == 2
1030 assert api.Ping(api_pb2.PingReq()).unseen_sent_host_request_count == 0
1032 with requests_session(token2) as api:
1033 assert api.ListHostRequests(requests_pb2.ListHostRequestsReq()).host_requests[0].last_seen_message_id == 0
1035 api.MarkLastSeenHostRequest(
1036 requests_pb2.MarkLastSeenHostRequestReq(host_request_id=host_request_id, last_seen_message_id=3)
1037 )
1039 assert api.ListHostRequests(requests_pb2.ListHostRequestsReq()).host_requests[0].last_seen_message_id == 3
1041 with pytest.raises(grpc.RpcError) as e:
1042 api.MarkLastSeenHostRequest(
1043 requests_pb2.MarkLastSeenHostRequestReq(host_request_id=host_request_id, last_seen_message_id=1)
1044 )
1045 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
1046 assert e.value.details() == "You can't unsee messages."
1048 # this will be used to test sent request notifications
1049 host_request_id_3 = api.CreateHostRequest(
1050 requests_pb2.CreateHostRequestReq(
1051 host_user_id=user1.id,
1052 from_date=today_plus_2.isoformat(),
1053 to_date=today_plus_3.isoformat(),
1054 text=valid_request_text("Another test request"),
1055 )
1056 ).host_request_id
1058 moderator.approve_host_request(host_request_id_3)
1060 with requests_session(token2) as api:
1061 # this should make id_2 all read
1062 api.SendHostRequestMessage(
1063 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id_2, text="Test")
1064 )
1066 with api_session(token2) as api:
1067 assert api.Ping(api_pb2.PingReq()).unseen_received_host_request_count == 1
1068 assert api.Ping(api_pb2.PingReq()).unseen_sent_host_request_count == 0
1070 # make sure sent and received count for unseen notifications
1071 with requests_session(token1) as api:
1072 api.SendHostRequestMessage(
1073 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id_3, text="Test message")
1074 )
1076 with api_session(token2) as api:
1077 assert api.Ping(api_pb2.PingReq()).unseen_received_host_request_count == 1
1078 assert api.Ping(api_pb2.PingReq()).unseen_sent_host_request_count == 1
1081def test_response_rate(db, moderator):
1082 user1, token1 = generate_user()
1083 user2, token2 = generate_user()
1084 user3, token3 = generate_user(delete_user=True)
1086 today_plus_2 = today() + timedelta(days=2)
1087 today_plus_3 = today() + timedelta(days=3)
1089 with session_scope() as session:
1090 refresh_materialized_view(session, "user_response_rates")
1092 with requests_session(token1) as api:
1093 # deleted: not found
1094 with pytest.raises(grpc.RpcError) as e:
1095 api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user3.id))
1096 assert e.value.code() == grpc.StatusCode.NOT_FOUND
1097 assert e.value.details() == "Couldn't find that user."
1099 # no requests: insufficient
1100 res = api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user2.id))
1101 assert res.HasField("insufficient_data")
1103 # send a request and back date it by 36 hours
1104 host_request_1 = api.CreateHostRequest(
1105 requests_pb2.CreateHostRequestReq(
1106 host_user_id=user2.id,
1107 from_date=today_plus_2.isoformat(),
1108 to_date=today_plus_3.isoformat(),
1109 text=valid_request_text("Test request"),
1110 )
1111 ).host_request_id
1112 moderator.approve_host_request(host_request_1)
1113 with session_scope() as session:
1114 session.execute(
1115 select(Message)
1116 .where(Message.conversation_id == host_request_1)
1117 .where(Message.message_type == MessageType.chat_created)
1118 ).scalar_one().time = now() - timedelta(hours=36)
1119 refresh_materialized_view(session, "user_response_rates")
1121 # still insufficient
1122 res = api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user2.id))
1123 assert res.HasField("insufficient_data")
1125 # send a request and back date it by 35 hours
1126 host_request_2 = api.CreateHostRequest(
1127 requests_pb2.CreateHostRequestReq(
1128 host_user_id=user2.id,
1129 from_date=today_plus_2.isoformat(),
1130 to_date=today_plus_3.isoformat(),
1131 text=valid_request_text("Test request"),
1132 )
1133 ).host_request_id
1134 moderator.approve_host_request(host_request_2)
1135 with session_scope() as session:
1136 session.execute(
1137 select(Message)
1138 .where(Message.conversation_id == host_request_2)
1139 .where(Message.message_type == MessageType.chat_created)
1140 ).scalar_one().time = now() - timedelta(hours=35)
1141 refresh_materialized_view(session, "user_response_rates")
1143 # still insufficient
1144 res = api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user2.id))
1145 assert res.HasField("insufficient_data")
1147 # send a request and back date it by 34 hours
1148 host_request_3 = api.CreateHostRequest(
1149 requests_pb2.CreateHostRequestReq(
1150 host_user_id=user2.id,
1151 from_date=today_plus_2.isoformat(),
1152 to_date=today_plus_3.isoformat(),
1153 text=valid_request_text("Test request"),
1154 )
1155 ).host_request_id
1156 moderator.approve_host_request(host_request_3)
1157 with session_scope() as session:
1158 session.execute(
1159 select(Message)
1160 .where(Message.conversation_id == host_request_3)
1161 .where(Message.message_type == MessageType.chat_created)
1162 ).scalar_one().time = now() - timedelta(hours=34)
1163 refresh_materialized_view(session, "user_response_rates")
1165 # now low
1166 res = api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user2.id))
1167 assert res.HasField("low")
1169 with requests_session(token2) as api:
1170 # accept a host req
1171 api.RespondHostRequest(
1172 requests_pb2.RespondHostRequestReq(
1173 host_request_id=host_request_2,
1174 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED,
1175 text="Accepting host request",
1176 )
1177 )
1179 with session_scope() as session:
1180 refresh_materialized_view(session, "user_response_rates")
1182 with requests_session(token1) as api:
1183 # now some w p33 = 35h
1184 res = api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user2.id))
1185 assert res.HasField("some")
1186 assert res.some.response_time_p33.ToTimedelta() == timedelta(hours=35)
1188 with requests_session(token2) as api:
1189 # accept another host req
1190 api.RespondHostRequest(
1191 requests_pb2.RespondHostRequestReq(
1192 host_request_id=host_request_3,
1193 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED,
1194 text="Accepting host request",
1195 )
1196 )
1198 with session_scope() as session:
1199 refresh_materialized_view(session, "user_response_rates")
1201 with requests_session(token1) as api:
1202 # now most w p33 = 34h, p66 = 35h
1203 res = api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user2.id))
1204 assert res.HasField("most")
1205 assert res.most.response_time_p33.ToTimedelta() == timedelta(hours=34)
1206 assert res.most.response_time_p66.ToTimedelta() == timedelta(hours=35)
1208 with requests_session(token2) as api:
1209 # accept last host req
1210 api.RespondHostRequest(
1211 requests_pb2.RespondHostRequestReq(
1212 host_request_id=host_request_1,
1213 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED,
1214 text="Accepting host request",
1215 )
1216 )
1218 with session_scope() as session:
1219 refresh_materialized_view(session, "user_response_rates")
1221 with requests_session(token1) as api:
1222 # now all w p33 = 34h, p66 = 35h
1223 res = api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user2.id))
1224 assert res.HasField("almost_all")
1225 assert res.almost_all.response_time_p33.ToTimedelta() == timedelta(hours=34)
1226 assert res.almost_all.response_time_p66.ToTimedelta() == timedelta(hours=35)
1228 # send a request and back date it by 2 hours
1229 host_request_4 = api.CreateHostRequest(
1230 requests_pb2.CreateHostRequestReq(
1231 host_user_id=user2.id,
1232 from_date=today_plus_2.isoformat(),
1233 to_date=today_plus_3.isoformat(),
1234 text=valid_request_text("Test request"),
1235 )
1236 ).host_request_id
1237 moderator.approve_host_request(host_request_4)
1238 with session_scope() as session:
1239 session.execute(
1240 select(Message)
1241 .where(Message.conversation_id == host_request_4)
1242 .where(Message.message_type == MessageType.chat_created)
1243 ).scalar_one().time = now() - timedelta(hours=2)
1244 refresh_materialized_view(session, "user_response_rates")
1246 # send a request and back date it by 4 hours
1247 host_request_5 = api.CreateHostRequest(
1248 requests_pb2.CreateHostRequestReq(
1249 host_user_id=user2.id,
1250 from_date=today_plus_2.isoformat(),
1251 to_date=today_plus_3.isoformat(),
1252 text=valid_request_text("Test request"),
1253 )
1254 ).host_request_id
1255 moderator.approve_host_request(host_request_5)
1256 with session_scope() as session:
1257 session.execute(
1258 select(Message)
1259 .where(Message.conversation_id == host_request_5)
1260 .where(Message.message_type == MessageType.chat_created)
1261 ).scalar_one().time = now() - timedelta(hours=4)
1262 refresh_materialized_view(session, "user_response_rates")
1264 # now some w p33 = 35h
1265 res = api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user2.id))
1266 assert res.HasField("some")
1267 assert res.some.response_time_p33.ToTimedelta() == timedelta(hours=35)
1269 with requests_session(token2) as api:
1270 # accept host req
1271 api.RespondHostRequest(
1272 requests_pb2.RespondHostRequestReq(
1273 host_request_id=host_request_5,
1274 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED,
1275 text="Accepting host request",
1276 )
1277 )
1279 with session_scope() as session:
1280 refresh_materialized_view(session, "user_response_rates")
1282 with requests_session(token1) as api:
1283 # now most w p33 = 34h, p66 = 36h
1284 res = api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user2.id))
1285 assert res.HasField("most")
1286 assert res.most.response_time_p33.ToTimedelta() == timedelta(hours=34)
1287 assert res.most.response_time_p66.ToTimedelta() == timedelta(hours=36)
1289 with requests_session(token2) as api:
1290 # accept host req
1291 api.RespondHostRequest(
1292 requests_pb2.RespondHostRequestReq(
1293 host_request_id=host_request_4,
1294 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED,
1295 text="Accepting host request",
1296 )
1297 )
1299 with session_scope() as session:
1300 refresh_materialized_view(session, "user_response_rates")
1302 with requests_session(token1) as api:
1303 # now most w p33 = 4h, p66 = 35h
1304 res = api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user2.id))
1305 assert res.HasField("almost_all")
1306 assert res.almost_all.response_time_p33.ToTimedelta() == timedelta(hours=4)
1307 assert res.almost_all.response_time_p66.ToTimedelta() == timedelta(hours=35)
1310def test_request_notifications(db, push_collector: PushCollector, moderator):
1311 host, host_token = generate_user(complete_profile=True)
1312 surfer, surfer_token = generate_user(complete_profile=True)
1314 host_loc_context = LocalizationContext.from_user(host)
1315 surfer_loc_context = LocalizationContext.from_user(surfer)
1317 today_plus_2 = today() + timedelta(days=2)
1318 today_plus_3 = today() + timedelta(days=3)
1320 with requests_session(surfer_token) as api:
1321 hr_id = api.CreateHostRequest(
1322 requests_pb2.CreateHostRequestReq(
1323 host_user_id=host.id,
1324 from_date=today_plus_2.isoformat(),
1325 to_date=today_plus_3.isoformat(),
1326 text=valid_request_text("can i stay plz"),
1327 )
1328 ).host_request_id
1330 with mock_notification_email() as mock:
1331 moderator.approve_host_request(hr_id)
1333 mock.assert_called_once()
1334 e = email_fields(mock)
1335 assert e.recipient == host.email
1336 assert "host request" in e.subject.lower()
1337 assert host.name in e.plain
1338 assert host.name in e.html
1339 assert "quick decline" in e.plain.lower(), e.plain
1340 assert "quick decline" in e.html.lower()
1341 assert surfer.name in e.plain
1342 assert surfer.name in e.html
1343 assert host_loc_context.localize_date(today_plus_2) in e.plain
1344 assert host_loc_context.localize_date(today_plus_2) in e.html
1345 assert host_loc_context.localize_date(today_plus_3) in e.plain
1346 assert host_loc_context.localize_date(today_plus_3) in e.html
1347 assert "http://localhost:5001/img/thumbnail/" not in e.plain
1348 assert "http://localhost:5001/img/thumbnail/" in e.html
1349 assert f"http://localhost:3000/messages/request/{hr_id}" in e.plain
1350 assert f"http://localhost:3000/messages/request/{hr_id}" in e.html
1352 assert push_collector.pop_for_user(host.id, last=True).content.title == f"New host request from {surfer.name}"
1354 with requests_session(host_token) as api:
1355 with mock_notification_email() as mock:
1356 api.RespondHostRequest(
1357 requests_pb2.RespondHostRequestReq(
1358 host_request_id=hr_id,
1359 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED,
1360 text="Accepting host request",
1361 )
1362 )
1364 e = email_fields(mock)
1365 assert e.recipient == surfer.email
1366 assert "host request" in e.subject.lower()
1367 assert host.name in e.plain
1368 assert host.name in e.html
1369 assert surfer.name in e.plain
1370 assert surfer.name in e.html
1371 assert surfer_loc_context.localize_date(today_plus_2) in e.plain
1372 assert surfer_loc_context.localize_date(today_plus_2) in e.html
1373 assert surfer_loc_context.localize_date(today_plus_3) in e.plain
1374 assert surfer_loc_context.localize_date(today_plus_3) in e.html
1375 assert "http://localhost:5001/img/thumbnail/" not in e.plain
1376 assert "http://localhost:5001/img/thumbnail/" in e.html
1377 assert f"http://localhost:3000/messages/request/{hr_id}" in e.plain
1378 assert f"http://localhost:3000/messages/request/{hr_id}" in e.html
1380 assert push_collector.pop_for_user(surfer.id, last=True).content.title == f"{host.name} accepted your host request"
1383def test_quick_decline(db, push_collector: PushCollector, moderator):
1384 host, host_token = generate_user(complete_profile=True)
1385 surfer, surfer_token = generate_user(complete_profile=True)
1387 host_loc_context = LocalizationContext.from_user(host)
1389 today_plus_2 = today() + timedelta(days=2)
1390 today_plus_3 = today() + timedelta(days=3)
1392 with requests_session(surfer_token) as api:
1393 hr_id = api.CreateHostRequest(
1394 requests_pb2.CreateHostRequestReq(
1395 host_user_id=host.id,
1396 from_date=today_plus_2.isoformat(),
1397 to_date=today_plus_3.isoformat(),
1398 text=valid_request_text("can i stay plz"),
1399 )
1400 ).host_request_id
1402 with mock_notification_email() as mock:
1403 moderator.approve_host_request(hr_id)
1405 mock.assert_called_once()
1406 e = email_fields(mock)
1407 assert e.recipient == host.email
1408 assert "host request" in e.subject.lower()
1409 assert host.name in e.plain
1410 assert host.name in e.html
1411 assert "quick decline" in e.plain.lower(), e.plain
1412 assert "quick decline" in e.html.lower()
1413 assert surfer.name in e.plain
1414 assert surfer.name in e.html
1415 assert host_loc_context.localize_date(today_plus_2) in e.plain
1416 assert host_loc_context.localize_date(today_plus_2) in e.html
1417 assert host_loc_context.localize_date(today_plus_3) in e.plain
1418 assert host_loc_context.localize_date(today_plus_3) in e.html
1419 assert "http://localhost:5001/img/thumbnail/" not in e.plain
1420 assert "http://localhost:5001/img/thumbnail/" in e.html
1421 assert f"http://localhost:3000/messages/request/{hr_id}" in e.plain
1422 assert f"http://localhost:3000/messages/request/{hr_id}" in e.html
1424 assert push_collector.pop_for_user(host.id, last=True).content.title == f"New host request from {surfer.name}"
1426 # very ugly
1427 # http://localhost:3000/quick-link?payload=CAEiGAoOZnJpZW5kX3JlcXVlc3QSBmFjY2VwdA==&sig=BQdk024NTATm8zlR0krSXTBhP5U9TlFv7VhJeIHZtUg=
1428 for link in re.findall(r'<a href="(.*?)"', email_fields(mock).html): 1428 ↛ 1447line 1428 didn't jump to line 1447 because the loop on line 1428 didn't complete
1429 if "payload" not in link:
1430 continue
1431 print(link)
1432 url_parts = urlparse(html.unescape(link))
1433 params = parse_qs(url_parts.query)
1434 print(params["payload"][0])
1435 payload = unsubscribe_pb2.UnsubscribePayload.FromString(b64decode(params["payload"][0]))
1436 if payload.HasField("host_request_quick_decline"): 1436 ↛ 1428line 1436 didn't jump to line 1428 because the condition on line 1436 was always true
1437 with auth_api_session() as (auth_api, metadata_interceptor):
1438 res = auth_api.Unsubscribe(
1439 auth_pb2.UnsubscribeReq(
1440 payload=b64decode(params["payload"][0]),
1441 sig=b64decode(params["sig"][0]),
1442 )
1443 )
1444 assert res.response == "Thank you for responding to the host request!"
1445 break
1446 else:
1447 raise Exception("Didn't find link")
1449 with requests_session(surfer_token) as api:
1450 res = api.GetHostRequest(requests_pb2.GetHostRequestReq(host_request_id=hr_id))
1451 assert res.status == conversations_pb2.HOST_REQUEST_STATUS_REJECTED
1454def test_host_req_feedback(db, moderator):
1455 host, host_token = generate_user(complete_profile=True)
1456 host2, host2_token = generate_user(complete_profile=True)
1457 host3, host3_token = generate_user(complete_profile=True)
1458 surfer, surfer_token = generate_user(complete_profile=True)
1460 today_plus_2 = today() + timedelta(days=2)
1461 today_plus_3 = today() + timedelta(days=3)
1463 with requests_session(surfer_token) as api:
1464 hr_id = api.CreateHostRequest(
1465 requests_pb2.CreateHostRequestReq(
1466 host_user_id=host.id,
1467 from_date=today_plus_2.isoformat(),
1468 to_date=today_plus_3.isoformat(),
1469 text=valid_request_text("can i stay plz"),
1470 )
1471 ).host_request_id
1472 hr2_id = api.CreateHostRequest(
1473 requests_pb2.CreateHostRequestReq(
1474 host_user_id=host2.id,
1475 from_date=today_plus_2.isoformat(),
1476 to_date=today_plus_3.isoformat(),
1477 text=valid_request_text("can i stay plz"),
1478 )
1479 ).host_request_id
1480 hr3_id = api.CreateHostRequest(
1481 requests_pb2.CreateHostRequestReq(
1482 host_user_id=host3.id,
1483 from_date=today_plus_2.isoformat(),
1484 to_date=today_plus_3.isoformat(),
1485 text=valid_request_text("can i stay plz"),
1486 )
1487 ).host_request_id
1489 moderator.approve_host_request(hr_id)
1490 moderator.approve_host_request(hr2_id)
1491 moderator.approve_host_request(hr3_id)
1493 with requests_session(host_token) as api:
1494 res = api.GetHostRequest(requests_pb2.GetHostRequestReq(host_request_id=hr_id))
1495 assert not res.need_host_request_feedback
1497 api.RespondHostRequest(
1498 requests_pb2.RespondHostRequestReq(
1499 host_request_id=hr_id,
1500 status=conversations_pb2.HOST_REQUEST_STATUS_REJECTED,
1501 )
1502 )
1504 res = api.GetHostRequest(requests_pb2.GetHostRequestReq(host_request_id=hr_id))
1505 assert res.need_host_request_feedback
1507 # surfer can't leave feedback
1508 with requests_session(surfer_token) as api:
1509 with pytest.raises(grpc.RpcError) as e:
1510 api.SendHostRequestFeedback(
1511 requests_pb2.SendHostRequestFeedbackReq(
1512 host_request_id=hr_id,
1513 )
1514 )
1515 assert e.value.code() == grpc.StatusCode.NOT_FOUND
1516 assert e.value.details() == "Couldn't find that host request."
1518 with requests_session(host_token) as api:
1519 api.SendHostRequestFeedback(
1520 requests_pb2.SendHostRequestFeedbackReq(
1521 host_request_id=hr_id,
1522 host_request_quality=requests_pb2.HOST_REQUEST_QUALITY_LOW,
1523 )
1524 )
1525 res = api.GetHostRequest(requests_pb2.GetHostRequestReq(host_request_id=hr_id))
1526 assert not res.need_host_request_feedback
1528 # can't leave it twice
1529 with requests_session(host_token) as api:
1530 with pytest.raises(grpc.RpcError) as e:
1531 api.SendHostRequestFeedback(
1532 requests_pb2.SendHostRequestFeedbackReq(
1533 host_request_id=hr_id,
1534 )
1535 )
1536 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
1537 assert e.value.details() == "You have already left feedback for this host request!"
1539 res = api.GetHostRequest(requests_pb2.GetHostRequestReq(host_request_id=hr_id))
1540 assert not res.need_host_request_feedback
1542 with requests_session(host2_token) as api:
1543 api.RespondHostRequest(
1544 requests_pb2.RespondHostRequestReq(
1545 host_request_id=hr2_id, status=conversations_pb2.HOST_REQUEST_STATUS_REJECTED
1546 )
1547 )
1548 # can't leave feedback on the wrong one
1549 with pytest.raises(grpc.RpcError) as e:
1550 api.SendHostRequestFeedback(
1551 requests_pb2.SendHostRequestFeedbackReq(
1552 host_request_id=hr_id,
1553 )
1554 )
1555 assert e.value.code() == grpc.StatusCode.NOT_FOUND
1556 assert e.value.details() == "Couldn't find that host request."
1558 # null feedback is still feedback
1559 api.SendHostRequestFeedback(requests_pb2.SendHostRequestFeedbackReq(host_request_id=hr2_id))
1561 with requests_session(host3_token) as api:
1562 api.RespondHostRequest(
1563 requests_pb2.RespondHostRequestReq(
1564 host_request_id=hr3_id, status=conversations_pb2.HOST_REQUEST_STATUS_REJECTED
1565 )
1566 )
1568 api.SendHostRequestFeedback(
1569 requests_pb2.SendHostRequestFeedbackReq(host_request_id=hr3_id, decline_reason="bad req")
1570 )