Coverage for src / tests / test_requests.py: 99%
724 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-14 09:03 +0000
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-14 09:03 +0000
1import re
2from datetime import timedelta
3from urllib.parse import parse_qs, urlparse
5import grpc
6import pytest
7from sqlalchemy import select
8from sqlalchemy_utils import refresh_materialized_view
10from couchers.constants import HOST_REQUEST_MIN_LENGTH_UTF16
11from couchers.crypto import b64decode
12from couchers.db import session_scope
13from couchers.i18n.localize import localize_date
14from couchers.models import (
15 Message,
16 MessageType,
17 RateLimitAction,
18)
19from couchers.proto import (
20 api_pb2,
21 auth_pb2,
22 conversations_pb2,
23 requests_pb2,
24)
25from couchers.proto.internal import unsubscribe_pb2
26from couchers.rate_limits.definitions import RATE_LIMIT_DEFINITIONS, RATE_LIMIT_HOURS
27from couchers.utils import create_coordinate, now, today
28from tests.fixtures.db import generate_user
29from tests.fixtures.misc import PushCollector, email_fields, mock_notification_email
30from tests.fixtures.sessions import api_session, auth_api_session, requests_session
33@pytest.fixture(autouse=True)
34def _(testconfig):
35 pass
38def valid_request_text(text: str = "Test request") -> str:
39 """Pads a request text to a valid length."""
40 # Request lengths are measured in utf-16 code units to match the frontend.
41 utf16_length = len(text.encode("utf-16-le")) // 2
42 if utf16_length >= HOST_REQUEST_MIN_LENGTH_UTF16: 42 ↛ 43line 42 didn't jump to line 43 because the condition on line 42 was never true
43 return text
44 padding_length = HOST_REQUEST_MIN_LENGTH_UTF16 - utf16_length
45 return text + ("_" * padding_length) # Each "_" adds one utf16 code unit.
48def test_create_request(db, moderator):
49 user1, token1 = generate_user()
50 hosting_city = "Morningside Heights, New York City"
51 hosting_lat = 40.8086
52 hosting_lng = -73.9616
53 hosting_radius = 500
54 user2, token2 = generate_user(
55 city=hosting_city,
56 geom=create_coordinate(hosting_lat, hosting_lng),
57 geom_radius=hosting_radius,
58 )
60 today_plus_2 = today() + timedelta(days=2)
61 today_plus_3 = today() + timedelta(days=3)
62 today_minus_2 = today() - timedelta(days=2)
63 today_minus_3 = today() - timedelta(days=3)
65 with requests_session(token1) as api:
66 with pytest.raises(grpc.RpcError) as e:
67 api.CreateHostRequest(
68 requests_pb2.CreateHostRequestReq(
69 host_user_id=user1.id,
70 from_date=today_plus_2.isoformat(),
71 to_date=today_plus_3.isoformat(),
72 text=valid_request_text(),
73 )
74 )
75 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
76 assert e.value.details() == "Can't request hosting from yourself."
78 with pytest.raises(grpc.RpcError) as e:
79 api.CreateHostRequest(
80 requests_pb2.CreateHostRequestReq(
81 host_user_id=999,
82 from_date=today_plus_2.isoformat(),
83 to_date=today_plus_3.isoformat(),
84 text=valid_request_text(),
85 )
86 )
87 assert e.value.code() == grpc.StatusCode.NOT_FOUND
88 assert e.value.details() == "Couldn't find that user."
90 with pytest.raises(grpc.RpcError) as e:
91 api.CreateHostRequest(
92 requests_pb2.CreateHostRequestReq(
93 host_user_id=user2.id,
94 from_date=today_plus_3.isoformat(),
95 to_date=today_plus_2.isoformat(),
96 text=valid_request_text(),
97 )
98 )
99 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
100 assert e.value.details() == "From date can't be after to date."
102 with pytest.raises(grpc.RpcError) as e:
103 api.CreateHostRequest(
104 requests_pb2.CreateHostRequestReq(
105 host_user_id=user2.id,
106 from_date=today_minus_3.isoformat(),
107 to_date=today_plus_2.isoformat(),
108 text=valid_request_text(),
109 )
110 )
111 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
112 assert e.value.details() == "From date must be today or later."
114 with pytest.raises(grpc.RpcError) as e:
115 api.CreateHostRequest(
116 requests_pb2.CreateHostRequestReq(
117 host_user_id=user2.id,
118 from_date=today_plus_2.isoformat(),
119 to_date=today_minus_2.isoformat(),
120 text=valid_request_text(),
121 )
122 )
123 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
124 assert e.value.details() == "From date can't be after to date."
126 with pytest.raises(grpc.RpcError) as e:
127 api.CreateHostRequest(
128 requests_pb2.CreateHostRequestReq(
129 host_user_id=user2.id,
130 from_date="2020-00-06",
131 to_date=today_minus_2.isoformat(),
132 text=valid_request_text(),
133 )
134 )
135 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
136 assert e.value.details() == "Invalid date."
138 with pytest.raises(grpc.RpcError) as e:
139 api.CreateHostRequest(
140 requests_pb2.CreateHostRequestReq(
141 host_user_id=user2.id,
142 from_date=today_plus_2.isoformat(),
143 to_date=today_plus_3.isoformat(),
144 text="Too short.",
145 )
146 )
147 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
148 assert e.value.details() == "Host request cannot be shorter than 250 characters."
150 res = api.CreateHostRequest(
151 requests_pb2.CreateHostRequestReq(
152 host_user_id=user2.id,
153 from_date=today_plus_2.isoformat(),
154 to_date=today_plus_3.isoformat(),
155 text=valid_request_text(),
156 )
157 )
158 host_request_id = res.host_request_id
160 moderator.approve_host_request(host_request_id)
162 with requests_session(token1) as api:
163 host_requests = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_sent=True)).host_requests
165 assert len(host_requests) == 1
166 hr = host_requests[0]
168 assert hr.latest_message.text.text == valid_request_text()
170 assert hr.hosting_city == hosting_city
171 assert round(hr.hosting_lat, 4) == hosting_lat
172 assert round(hr.hosting_lng, 4) == hosting_lng
173 assert hr.hosting_radius == hosting_radius
175 today_ = today()
176 today_plus_one_year = today_ + timedelta(days=365)
177 today_plus_one_year_plus_2 = today_plus_one_year + timedelta(days=2)
178 today_plus_one_year_plus_3 = today_plus_one_year + timedelta(days=3)
179 with pytest.raises(grpc.RpcError) as e:
180 api.CreateHostRequest(
181 requests_pb2.CreateHostRequestReq(
182 host_user_id=user2.id,
183 from_date=today_plus_one_year_plus_2.isoformat(),
184 to_date=today_plus_one_year_plus_3.isoformat(),
185 text=valid_request_text("Test from date after one year"),
186 )
187 )
188 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
189 assert e.value.details() == "The start date must be within one year from today."
191 with pytest.raises(grpc.RpcError) as e:
192 api.CreateHostRequest(
193 requests_pb2.CreateHostRequestReq(
194 host_user_id=user2.id,
195 from_date=today_plus_2.isoformat(),
196 to_date=today_plus_one_year_plus_3.isoformat(),
197 text=valid_request_text("Test to date one year after from date"),
198 )
199 )
200 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
201 assert e.value.details() == "You cannot request to stay with someone for longer than one year."
204def test_create_request_incomplete_profile(db):
205 user1, token1 = generate_user(complete_profile=False)
206 user2, _ = generate_user()
207 today_plus_2 = today() + timedelta(days=2)
208 today_plus_3 = today() + timedelta(days=3)
209 with requests_session(token1) as api:
210 with pytest.raises(grpc.RpcError) as e:
211 api.CreateHostRequest(
212 requests_pb2.CreateHostRequestReq(
213 host_user_id=user2.id,
214 from_date=today_plus_2.isoformat(),
215 to_date=today_plus_3.isoformat(),
216 text=valid_request_text(),
217 )
218 )
219 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
220 assert e.value.details() == "You have to complete your profile before you can send a request."
223def test_excessive_requests_are_reported(db):
224 """Test that excessive host requests are first reported in a warning email and finally lead blocking of further requests."""
225 user, token = generate_user()
226 today_plus_2 = today() + timedelta(days=2)
227 today_plus_3 = today() + timedelta(days=3)
228 rate_limit_definition = RATE_LIMIT_DEFINITIONS[RateLimitAction.host_request]
229 with requests_session(token) as api:
230 # Test warning email
231 with mock_notification_email() as mock_email:
232 for _ in range(rate_limit_definition.warning_limit):
233 host_user, _ = generate_user()
234 _ = api.CreateHostRequest(
235 requests_pb2.CreateHostRequestReq(
236 host_user_id=host_user.id,
237 from_date=today_plus_2.isoformat(),
238 to_date=today_plus_3.isoformat(),
239 text=valid_request_text(),
240 )
241 )
243 assert mock_email.call_count == 0
244 host_user, _ = generate_user()
245 _ = api.CreateHostRequest(
246 requests_pb2.CreateHostRequestReq(
247 host_user_id=host_user.id,
248 from_date=today_plus_2.isoformat(),
249 to_date=today_plus_3.isoformat(),
250 text=valid_request_text("Excessive test request"),
251 )
252 )
253 assert mock_email.call_count == 1
254 email = mock_email.mock_calls[0].kwargs["plain"]
255 assert email.startswith(
256 f"User {user.username} has sent {rate_limit_definition.warning_limit} host requests in the past {RATE_LIMIT_HOURS} hours."
257 )
259 # Test ban after exceeding HOST_REQUEST_HARD_LIMIT
260 with mock_notification_email() as mock_email:
261 for _ in range(rate_limit_definition.hard_limit - rate_limit_definition.warning_limit - 1):
262 host_user, _ = generate_user()
263 _ = api.CreateHostRequest(
264 requests_pb2.CreateHostRequestReq(
265 host_user_id=host_user.id,
266 from_date=today_plus_2.isoformat(),
267 to_date=today_plus_3.isoformat(),
268 text=valid_request_text(),
269 )
270 )
272 assert mock_email.call_count == 0
273 host_user, _ = generate_user()
274 with pytest.raises(grpc.RpcError) as exc_info:
275 _ = api.CreateHostRequest(
276 requests_pb2.CreateHostRequestReq(
277 host_user_id=host_user.id,
278 from_date=today_plus_2.isoformat(),
279 to_date=today_plus_3.isoformat(),
280 text=valid_request_text("Excessive test request"),
281 )
282 )
283 assert exc_info.value.code() == grpc.StatusCode.RESOURCE_EXHAUSTED
284 assert (
285 exc_info.value.details()
286 == "You have sent a lot of host requests in the past 24 hours. To avoid spam, you can't send any more for now."
287 )
289 assert mock_email.call_count == 1
290 email = mock_email.mock_calls[0].kwargs["plain"]
291 assert email.startswith(
292 f"User {user.username} has sent {rate_limit_definition.hard_limit} host requests in the past {RATE_LIMIT_HOURS} hours."
293 )
294 assert "The user has been blocked from sending further host requests for now." in email
297def add_message(db, text, author_id, conversation_id):
298 with session_scope() as session:
299 message = Message(
300 conversation_id=conversation_id, author_id=author_id, text=text, message_type=MessageType.text
301 )
303 session.add(message)
306def test_GetHostRequest(db):
307 user1, token1 = generate_user()
308 user2, token2 = generate_user()
309 user3, token3 = generate_user()
310 today_plus_2 = today() + timedelta(days=2)
311 today_plus_3 = today() + timedelta(days=3)
312 with requests_session(token1) as api:
313 host_request_id = api.CreateHostRequest(
314 requests_pb2.CreateHostRequestReq(
315 host_user_id=user2.id,
316 from_date=today_plus_2.isoformat(),
317 to_date=today_plus_3.isoformat(),
318 text=valid_request_text("Test request 1"),
319 )
320 ).host_request_id
322 with pytest.raises(grpc.RpcError) as e:
323 api.GetHostRequest(requests_pb2.GetHostRequestReq(host_request_id=999))
324 assert e.value.code() == grpc.StatusCode.NOT_FOUND
325 assert e.value.details() == "Couldn't find that host request."
327 api.SendHostRequestMessage(
328 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 1")
329 )
331 res = api.GetHostRequest(requests_pb2.GetHostRequestReq(host_request_id=host_request_id))
332 assert res.latest_message.text.text == "Test message 1"
335def test_ListHostRequests(db, moderator):
336 user1, token1 = generate_user()
337 user2, token2 = generate_user()
338 user3, token3 = generate_user()
339 today_plus_2 = today() + timedelta(days=2)
340 today_plus_3 = today() + timedelta(days=3)
341 with requests_session(token1) as api:
342 host_request_1 = api.CreateHostRequest(
343 requests_pb2.CreateHostRequestReq(
344 host_user_id=user2.id,
345 from_date=today_plus_2.isoformat(),
346 to_date=today_plus_3.isoformat(),
347 text=valid_request_text("Test request 1"),
348 )
349 ).host_request_id
351 host_request_2 = api.CreateHostRequest(
352 requests_pb2.CreateHostRequestReq(
353 host_user_id=user3.id,
354 from_date=today_plus_2.isoformat(),
355 to_date=today_plus_3.isoformat(),
356 text=valid_request_text("Test request 2"),
357 )
358 ).host_request_id
360 moderator.approve_host_request(host_request_1)
361 moderator.approve_host_request(host_request_2)
363 with requests_session(token1) as api:
364 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_sent=True))
365 assert res.no_more
366 assert len(res.host_requests) == 2
368 with requests_session(token2) as api:
369 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_received=True))
370 assert res.no_more
371 assert len(res.host_requests) == 1
372 assert res.host_requests[0].latest_message.text.text == valid_request_text("Test request 1")
373 assert res.host_requests[0].surfer_user_id == user1.id
374 assert res.host_requests[0].host_user_id == user2.id
375 assert res.host_requests[0].status == conversations_pb2.HOST_REQUEST_STATUS_PENDING
377 add_message(db, "Test request 1 message 1", user2.id, host_request_1)
378 add_message(db, "Test request 1 message 2", user2.id, host_request_1)
379 add_message(db, "Test request 1 message 3", user2.id, host_request_1)
381 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_received=True))
382 assert res.host_requests[0].latest_message.text.text == "Test request 1 message 3"
384 host_request_3 = api.CreateHostRequest(
385 requests_pb2.CreateHostRequestReq(
386 host_user_id=user1.id,
387 from_date=today_plus_2.isoformat(),
388 to_date=today_plus_3.isoformat(),
389 text=valid_request_text("Test request 3"),
390 )
391 ).host_request_id
393 moderator.approve_host_request(host_request_3)
395 add_message(db, "Test request 2 message 1", user1.id, host_request_2)
396 add_message(db, "Test request 2 message 2", user3.id, host_request_2)
398 with requests_session(token3) as api:
399 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_received=True))
400 assert res.no_more
401 assert len(res.host_requests) == 1
402 assert res.host_requests[0].latest_message.text.text == "Test request 2 message 2"
404 with requests_session(token1) as api:
405 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_received=True))
406 assert len(res.host_requests) == 1
408 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq())
409 assert len(res.host_requests) == 3
412def test_ListHostRequests_pagination_regression(db, moderator):
413 """
414 ListHostRequests was skipping a request when getting multiple pages
415 """
416 user1, token1 = generate_user()
417 user2, token2 = generate_user()
418 today_plus_2 = today() + timedelta(days=2)
419 today_plus_3 = today() + timedelta(days=3)
420 with requests_session(token1) as api:
421 host_request_1 = api.CreateHostRequest(
422 requests_pb2.CreateHostRequestReq(
423 host_user_id=user2.id,
424 from_date=today_plus_2.isoformat(),
425 to_date=today_plus_3.isoformat(),
426 text=valid_request_text("Test request 1"),
427 )
428 ).host_request_id
430 host_request_2 = api.CreateHostRequest(
431 requests_pb2.CreateHostRequestReq(
432 host_user_id=user2.id,
433 from_date=today_plus_2.isoformat(),
434 to_date=today_plus_3.isoformat(),
435 text=valid_request_text("Test request 2"),
436 )
437 ).host_request_id
439 host_request_3 = api.CreateHostRequest(
440 requests_pb2.CreateHostRequestReq(
441 host_user_id=user2.id,
442 from_date=today_plus_2.isoformat(),
443 to_date=today_plus_3.isoformat(),
444 text=valid_request_text("Test request 3"),
445 )
446 ).host_request_id
448 moderator.approve_host_request(host_request_1)
449 moderator.approve_host_request(host_request_2)
450 moderator.approve_host_request(host_request_3)
452 with requests_session(token2) as api:
453 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_received=True))
454 assert res.no_more
455 assert len(res.host_requests) == 3
456 assert res.host_requests[0].latest_message.text.text == valid_request_text("Test request 3")
457 assert res.host_requests[1].latest_message.text.text == valid_request_text("Test request 2")
458 assert res.host_requests[2].latest_message.text.text == valid_request_text("Test request 1")
460 with requests_session(token2) as api:
461 api.RespondHostRequest(
462 requests_pb2.RespondHostRequestReq(
463 host_request_id=host_request_2,
464 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED,
465 text="Accepting host request 2",
466 )
467 )
468 api.RespondHostRequest(
469 requests_pb2.RespondHostRequestReq(
470 host_request_id=host_request_1,
471 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED,
472 text="Accepting host request 1",
473 )
474 )
475 api.RespondHostRequest(
476 requests_pb2.RespondHostRequestReq(
477 host_request_id=host_request_3,
478 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED,
479 text="Accepting host request 3",
480 )
481 )
483 with requests_session(token2) as api:
484 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_received=True))
485 assert res.no_more
486 assert len(res.host_requests) == 3
487 assert res.host_requests[0].latest_message.text.text == "Accepting host request 3"
488 assert res.host_requests[1].latest_message.text.text == "Accepting host request 1"
489 assert res.host_requests[2].latest_message.text.text == "Accepting host request 2"
491 with requests_session(token2) as api:
492 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_received=True, number=1))
493 assert not res.no_more
494 assert len(res.host_requests) == 1
495 assert res.host_requests[0].latest_message.text.text == "Accepting host request 3"
496 res = api.ListHostRequests(
497 requests_pb2.ListHostRequestsReq(only_received=True, number=1, last_request_id=res.last_request_id)
498 )
499 assert not res.no_more
500 assert len(res.host_requests) == 1
501 assert res.host_requests[0].latest_message.text.text == "Accepting host request 1"
502 res = api.ListHostRequests(
503 requests_pb2.ListHostRequestsReq(only_received=True, number=1, last_request_id=res.last_request_id)
504 )
505 assert res.no_more
506 assert len(res.host_requests) == 1
507 assert res.host_requests[0].latest_message.text.text == "Accepting host request 2"
510def test_ListHostRequests_active_filter(db, moderator):
511 user1, token1 = generate_user()
512 user2, token2 = generate_user()
513 today_plus_2 = today() + timedelta(days=2)
514 today_plus_3 = today() + timedelta(days=3)
516 with requests_session(token1) as api:
517 request_id = api.CreateHostRequest(
518 requests_pb2.CreateHostRequestReq(
519 host_user_id=user2.id,
520 from_date=today_plus_2.isoformat(),
521 to_date=today_plus_3.isoformat(),
522 text=valid_request_text("Test request 1"),
523 )
524 ).host_request_id
526 moderator.approve_host_request(request_id)
528 with requests_session(token1) as api:
529 api.RespondHostRequest(
530 requests_pb2.RespondHostRequestReq(
531 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED
532 )
533 )
535 with requests_session(token2) as api:
536 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_received=True))
537 assert len(res.host_requests) == 1
538 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_active=True))
539 assert len(res.host_requests) == 0
542def test_RespondHostRequests(db, moderator):
543 user1, token1 = generate_user()
544 user2, token2 = generate_user()
545 user3, token3 = generate_user()
546 today_plus_2 = today() + timedelta(days=2)
547 today_plus_3 = today() + timedelta(days=3)
549 with requests_session(token1) as api:
550 request_id = api.CreateHostRequest(
551 requests_pb2.CreateHostRequestReq(
552 host_user_id=user2.id,
553 from_date=today_plus_2.isoformat(),
554 to_date=today_plus_3.isoformat(),
555 text=valid_request_text("Test request 1"),
556 )
557 ).host_request_id
559 moderator.approve_host_request(request_id)
561 # another user can't access
562 with requests_session(token3) as api:
563 with pytest.raises(grpc.RpcError) as e:
564 api.RespondHostRequest(
565 requests_pb2.RespondHostRequestReq(
566 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED
567 )
568 )
569 assert e.value.code() == grpc.StatusCode.NOT_FOUND
570 assert e.value.details() == "Couldn't find that host request."
572 with requests_session(token1) as api:
573 with pytest.raises(grpc.RpcError) as e:
574 api.RespondHostRequest(
575 requests_pb2.RespondHostRequestReq(
576 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED
577 )
578 )
579 assert e.value.code() == grpc.StatusCode.PERMISSION_DENIED
580 assert e.value.details() == "You are not the host of this request."
582 with requests_session(token2) as api:
583 # non existing id
584 with pytest.raises(grpc.RpcError) as e:
585 api.RespondHostRequest(
586 requests_pb2.RespondHostRequestReq(
587 host_request_id=9999, status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED
588 )
589 )
590 assert e.value.code() == grpc.StatusCode.NOT_FOUND
592 # host can't confirm or cancel (host should accept/reject)
593 with pytest.raises(grpc.RpcError) as e:
594 api.RespondHostRequest(
595 requests_pb2.RespondHostRequestReq(
596 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CONFIRMED
597 )
598 )
599 assert e.value.code() == grpc.StatusCode.PERMISSION_DENIED
600 assert e.value.details() == "You can't set the host request status to that."
601 with pytest.raises(grpc.RpcError) as e:
602 api.RespondHostRequest(
603 requests_pb2.RespondHostRequestReq(
604 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED
605 )
606 )
607 assert e.value.code() == grpc.StatusCode.PERMISSION_DENIED
608 assert e.value.details() == "You can't set the host request status to that."
610 api.RespondHostRequest(
611 requests_pb2.RespondHostRequestReq(
612 host_request_id=request_id,
613 status=conversations_pb2.HOST_REQUEST_STATUS_REJECTED,
614 text="Test rejection message",
615 )
616 )
617 res = api.GetHostRequestMessages(requests_pb2.GetHostRequestMessagesReq(host_request_id=request_id))
618 assert res.messages[0].text.text == "Test rejection message"
619 assert res.messages[1].WhichOneof("content") == "host_request_status_changed"
620 assert res.messages[1].host_request_status_changed.status == conversations_pb2.HOST_REQUEST_STATUS_REJECTED
621 # should be able to move from rejected -> accepted
622 api.RespondHostRequest(
623 requests_pb2.RespondHostRequestReq(
624 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED
625 )
626 )
628 with requests_session(token1) as api:
629 # can't make pending
630 with pytest.raises(grpc.RpcError) as e:
631 api.RespondHostRequest(
632 requests_pb2.RespondHostRequestReq(
633 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_PENDING
634 )
635 )
636 assert e.value.code() == grpc.StatusCode.PERMISSION_DENIED
637 assert e.value.details() == "You can't set the host request status to that."
639 # can confirm then cancel
640 api.RespondHostRequest(
641 requests_pb2.RespondHostRequestReq(
642 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CONFIRMED
643 )
644 )
646 api.RespondHostRequest(
647 requests_pb2.RespondHostRequestReq(
648 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED
649 )
650 )
652 # can't confirm after having cancelled
653 with pytest.raises(grpc.RpcError) as e:
654 api.RespondHostRequest(
655 requests_pb2.RespondHostRequestReq(
656 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CONFIRMED
657 )
658 )
659 assert e.value.code() == grpc.StatusCode.PERMISSION_DENIED
660 assert e.value.details() == "You can't set the host request status to that."
662 # at this point there should be 7 messages
663 # 2 for creation, 2 for the status change with message, 3 for the other status changed
664 with requests_session(token1) as api:
665 res = api.GetHostRequestMessages(requests_pb2.GetHostRequestMessagesReq(host_request_id=request_id))
666 assert len(res.messages) == 7
667 assert res.messages[0].host_request_status_changed.status == conversations_pb2.HOST_REQUEST_STATUS_CANCELLED
668 assert res.messages[1].host_request_status_changed.status == conversations_pb2.HOST_REQUEST_STATUS_CONFIRMED
669 assert res.messages[2].host_request_status_changed.status == conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED
670 assert res.messages[4].host_request_status_changed.status == conversations_pb2.HOST_REQUEST_STATUS_REJECTED
671 assert res.messages[6].WhichOneof("content") == "chat_created"
674def test_get_host_request_messages(db, moderator):
675 user1, token1 = generate_user()
676 user2, token2 = generate_user()
677 today_plus_2 = today() + timedelta(days=2)
678 today_plus_3 = today() + timedelta(days=3)
679 with requests_session(token1) as api:
680 res = api.CreateHostRequest(
681 requests_pb2.CreateHostRequestReq(
682 host_user_id=user2.id,
683 from_date=today_plus_2.isoformat(),
684 to_date=today_plus_3.isoformat(),
685 text=valid_request_text("Test request 1"),
686 )
687 )
688 conversation_id = res.host_request_id
690 moderator.approve_host_request(conversation_id)
692 add_message(db, "Test request 1 message 1", user1.id, conversation_id)
693 add_message(db, "Test request 1 message 2", user1.id, conversation_id)
694 add_message(db, "Test request 1 message 3", user1.id, conversation_id)
696 with requests_session(token2) as api:
697 api.RespondHostRequest(
698 requests_pb2.RespondHostRequestReq(
699 host_request_id=conversation_id, status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED
700 )
701 )
703 add_message(db, "Test request 1 message 4", user2.id, conversation_id)
704 add_message(db, "Test request 1 message 5", user2.id, conversation_id)
706 api.RespondHostRequest(
707 requests_pb2.RespondHostRequestReq(
708 host_request_id=conversation_id, status=conversations_pb2.HOST_REQUEST_STATUS_REJECTED
709 )
710 )
712 with requests_session(token1) as api:
713 # 9 including initial message
714 res = api.GetHostRequestMessages(requests_pb2.GetHostRequestMessagesReq(host_request_id=conversation_id))
715 assert len(res.messages) == 9
716 assert res.no_more
718 res = api.GetHostRequestMessages(
719 requests_pb2.GetHostRequestMessagesReq(host_request_id=conversation_id, number=3)
720 )
721 assert not res.no_more
722 assert len(res.messages) == 3
723 assert res.messages[0].host_request_status_changed.status == conversations_pb2.HOST_REQUEST_STATUS_REJECTED
724 assert res.messages[0].WhichOneof("content") == "host_request_status_changed"
725 assert res.messages[1].text.text == "Test request 1 message 5"
726 assert res.messages[2].text.text == "Test request 1 message 4"
728 res = api.GetHostRequestMessages(
729 requests_pb2.GetHostRequestMessagesReq(
730 host_request_id=conversation_id,
731 last_message_id=res.messages[2].message_id,
732 number=6,
733 )
734 )
735 assert res.no_more
736 assert len(res.messages) == 6
737 assert res.messages[0].host_request_status_changed.status == conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED
738 assert res.messages[0].WhichOneof("content") == "host_request_status_changed"
739 assert res.messages[1].text.text == "Test request 1 message 3"
740 assert res.messages[2].text.text == "Test request 1 message 2"
741 assert res.messages[3].text.text == "Test request 1 message 1"
742 assert res.messages[4].text.text == valid_request_text("Test request 1")
743 assert res.messages[5].WhichOneof("content") == "chat_created"
746def test_SendHostRequestMessage(db, moderator):
747 user1, token1 = generate_user()
748 user2, token2 = generate_user()
749 user3, token3 = generate_user()
750 today_plus_2 = today() + timedelta(days=2)
751 today_plus_3 = today() + timedelta(days=3)
752 with requests_session(token1) as api:
753 host_request_id = api.CreateHostRequest(
754 requests_pb2.CreateHostRequestReq(
755 host_user_id=user2.id,
756 from_date=today_plus_2.isoformat(),
757 to_date=today_plus_3.isoformat(),
758 text=valid_request_text("Test request 1"),
759 )
760 ).host_request_id
762 moderator.approve_host_request(host_request_id)
764 with requests_session(token1) as api:
765 with pytest.raises(grpc.RpcError) as e:
766 api.SendHostRequestMessage(
767 requests_pb2.SendHostRequestMessageReq(host_request_id=999, text="Test message 1")
768 )
769 assert e.value.code() == grpc.StatusCode.NOT_FOUND
771 with pytest.raises(grpc.RpcError) as e:
772 api.SendHostRequestMessage(requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text=""))
773 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
774 assert e.value.details() == "Invalid message."
776 api.SendHostRequestMessage(
777 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 1")
778 )
779 res = api.GetHostRequestMessages(requests_pb2.GetHostRequestMessagesReq(host_request_id=host_request_id))
780 assert res.messages[0].text.text == "Test message 1"
781 assert res.messages[0].author_user_id == user1.id
783 with requests_session(token3) as api:
784 # other user can't send
785 with pytest.raises(grpc.RpcError) as e:
786 api.SendHostRequestMessage(
787 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 2")
788 )
789 assert e.value.code() == grpc.StatusCode.NOT_FOUND
790 assert e.value.details() == "Couldn't find that host request."
792 with requests_session(token2) as api:
793 api.SendHostRequestMessage(
794 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 2")
795 )
796 res = api.GetHostRequestMessages(requests_pb2.GetHostRequestMessagesReq(host_request_id=host_request_id))
797 # including 2 for creation control message and message
798 assert len(res.messages) == 4
799 assert res.messages[0].text.text == "Test message 2"
800 assert res.messages[0].author_user_id == user2.id
802 # CAN send messages to a rejected, confirmed or cancelled request, and for accepted
803 api.RespondHostRequest(
804 requests_pb2.RespondHostRequestReq(
805 host_request_id=host_request_id, status=conversations_pb2.HOST_REQUEST_STATUS_REJECTED
806 )
807 )
808 api.SendHostRequestMessage(
809 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 3")
810 )
812 api.RespondHostRequest(
813 requests_pb2.RespondHostRequestReq(
814 host_request_id=host_request_id, status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED
815 )
816 )
818 with requests_session(token1) as api:
819 api.RespondHostRequest(
820 requests_pb2.RespondHostRequestReq(
821 host_request_id=host_request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CONFIRMED
822 )
823 )
824 api.SendHostRequestMessage(
825 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 3")
826 )
828 api.RespondHostRequest(
829 requests_pb2.RespondHostRequestReq(
830 host_request_id=host_request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED
831 )
832 )
833 api.SendHostRequestMessage(
834 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 3")
835 )
838def test_get_updates(db, moderator):
839 user1, token1 = generate_user()
840 user2, token2 = generate_user()
841 user3, token3 = generate_user()
842 today_plus_2 = today() + timedelta(days=2)
843 today_plus_3 = today() + timedelta(days=3)
844 with requests_session(token1) as api:
845 host_request_id = api.CreateHostRequest(
846 requests_pb2.CreateHostRequestReq(
847 host_user_id=user2.id,
848 from_date=today_plus_2.isoformat(),
849 to_date=today_plus_3.isoformat(),
850 text=valid_request_text("Test message 0"),
851 )
852 ).host_request_id
854 moderator.approve_host_request(host_request_id)
856 with requests_session(token1) as api:
857 api.SendHostRequestMessage(
858 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 1")
859 )
860 api.SendHostRequestMessage(
861 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 2")
862 )
863 api.RespondHostRequest(
864 requests_pb2.RespondHostRequestReq(
865 host_request_id=host_request_id,
866 status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED,
867 text="Test message 3",
868 )
869 )
871 api.CreateHostRequest(
872 requests_pb2.CreateHostRequestReq(
873 host_user_id=user2.id,
874 from_date=today_plus_2.isoformat(),
875 to_date=today_plus_3.isoformat(),
876 text=valid_request_text("Test message 4"),
877 )
878 )
880 res = api.GetHostRequestMessages(requests_pb2.GetHostRequestMessagesReq(host_request_id=host_request_id))
881 assert len(res.messages) == 6
882 assert res.messages[0].text.text == "Test message 3"
883 assert res.messages[1].host_request_status_changed.status == conversations_pb2.HOST_REQUEST_STATUS_CANCELLED
884 assert res.messages[2].text.text == "Test message 2"
885 assert res.messages[3].text.text == "Test message 1"
886 assert res.messages[4].text.text == valid_request_text("Test message 0")
887 message_id_3 = res.messages[0].message_id
888 message_id_cancel = res.messages[1].message_id
889 message_id_2 = res.messages[2].message_id
890 message_id_1 = res.messages[3].message_id
891 message_id_0 = res.messages[4].message_id
893 with pytest.raises(grpc.RpcError) as e:
894 api.GetHostRequestUpdates(requests_pb2.GetHostRequestUpdatesReq(newest_message_id=0))
895 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
897 res = api.GetHostRequestUpdates(requests_pb2.GetHostRequestUpdatesReq(newest_message_id=message_id_1))
898 assert res.no_more
899 assert len(res.updates) == 5
900 assert res.updates[0].message.text.text == "Test message 2"
901 assert (
902 res.updates[1].message.host_request_status_changed.status == conversations_pb2.HOST_REQUEST_STATUS_CANCELLED
903 )
904 assert res.updates[1].status == conversations_pb2.HOST_REQUEST_STATUS_CANCELLED
905 assert res.updates[2].message.text.text == "Test message 3"
906 assert res.updates[3].message.WhichOneof("content") == "chat_created"
907 assert res.updates[3].status == conversations_pb2.HOST_REQUEST_STATUS_PENDING
908 assert res.updates[4].message.text.text == valid_request_text("Test message 4")
910 res = api.GetHostRequestUpdates(requests_pb2.GetHostRequestUpdatesReq(newest_message_id=message_id_1, number=1))
911 assert not res.no_more
912 assert len(res.updates) == 1
913 assert res.updates[0].message.text.text == "Test message 2"
914 assert res.updates[0].status == conversations_pb2.HOST_REQUEST_STATUS_CANCELLED
916 with requests_session(token3) as api:
917 # other user can't access
918 res = api.GetHostRequestUpdates(requests_pb2.GetHostRequestUpdatesReq(newest_message_id=message_id_1))
919 assert len(res.updates) == 0
922def test_archive_host_request(db, moderator):
923 user1, token1 = generate_user()
924 user2, token2 = generate_user()
926 today_plus_2 = today() + timedelta(days=2)
927 today_plus_3 = today() + timedelta(days=3)
929 with requests_session(token1) as api:
930 host_request_id = api.CreateHostRequest(
931 requests_pb2.CreateHostRequestReq(
932 host_user_id=user2.id,
933 from_date=today_plus_2.isoformat(),
934 to_date=today_plus_3.isoformat(),
935 text=valid_request_text("Test message 0"),
936 )
937 ).host_request_id
939 api.SendHostRequestMessage(
940 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 1")
941 )
942 api.SendHostRequestMessage(
943 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 2")
944 )
946 moderator.approve_host_request(host_request_id)
948 # happy path archiving host request
949 with requests_session(token1) as api:
950 api.RespondHostRequest(
951 requests_pb2.RespondHostRequestReq(
952 host_request_id=host_request_id,
953 status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED,
954 text="Test message 3",
955 )
956 )
957 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_sent=True))
958 assert len(res.host_requests) == 1
959 assert res.host_requests[0].status == conversations_pb2.HOST_REQUEST_STATUS_CANCELLED
960 api.SetHostRequestArchiveStatus(
961 requests_pb2.SetHostRequestArchiveStatusReq(host_request_id=host_request_id, is_archived=True)
962 )
963 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_archived=True))
964 assert len(res.host_requests) == 1
967def test_mark_last_seen(db, moderator):
968 user1, token1 = generate_user()
969 user2, token2 = generate_user()
970 user3, token3 = generate_user()
971 today_plus_2 = today() + timedelta(days=2)
972 today_plus_3 = today() + timedelta(days=3)
973 with requests_session(token1) as api:
974 host_request_id = api.CreateHostRequest(
975 requests_pb2.CreateHostRequestReq(
976 host_user_id=user2.id,
977 from_date=today_plus_2.isoformat(),
978 to_date=today_plus_3.isoformat(),
979 text=valid_request_text("Test message 0"),
980 )
981 ).host_request_id
983 host_request_id_2 = api.CreateHostRequest(
984 requests_pb2.CreateHostRequestReq(
985 host_user_id=user2.id,
986 from_date=today_plus_2.isoformat(),
987 to_date=today_plus_3.isoformat(),
988 text=valid_request_text("Test message 0a"),
989 )
990 ).host_request_id
992 moderator.approve_host_request(host_request_id)
993 moderator.approve_host_request(host_request_id_2)
995 with requests_session(token1) as api:
996 api.SendHostRequestMessage(
997 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 1")
998 )
999 api.SendHostRequestMessage(
1000 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 2")
1001 )
1002 api.RespondHostRequest(
1003 requests_pb2.RespondHostRequestReq(
1004 host_request_id=host_request_id,
1005 status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED,
1006 text="Test message 3",
1007 )
1008 )
1010 moderator.approve_host_request(host_request_id)
1011 moderator.approve_host_request(host_request_id_2)
1013 # test Ping unseen host request count, should be automarked after sending
1014 with api_session(token1) as api:
1015 assert api.Ping(api_pb2.PingReq()).unseen_received_host_request_count == 0
1016 assert api.Ping(api_pb2.PingReq()).unseen_sent_host_request_count == 0
1018 with api_session(token2) as api:
1019 assert api.Ping(api_pb2.PingReq()).unseen_received_host_request_count == 2
1020 assert api.Ping(api_pb2.PingReq()).unseen_sent_host_request_count == 0
1022 with requests_session(token2) as api:
1023 assert api.ListHostRequests(requests_pb2.ListHostRequestsReq()).host_requests[0].last_seen_message_id == 0
1025 api.MarkLastSeenHostRequest(
1026 requests_pb2.MarkLastSeenHostRequestReq(host_request_id=host_request_id, last_seen_message_id=3)
1027 )
1029 assert api.ListHostRequests(requests_pb2.ListHostRequestsReq()).host_requests[0].last_seen_message_id == 3
1031 with pytest.raises(grpc.RpcError) as e:
1032 api.MarkLastSeenHostRequest(
1033 requests_pb2.MarkLastSeenHostRequestReq(host_request_id=host_request_id, last_seen_message_id=1)
1034 )
1035 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
1036 assert e.value.details() == "You can't unsee messages."
1038 # this will be used to test sent request notifications
1039 host_request_id_3 = api.CreateHostRequest(
1040 requests_pb2.CreateHostRequestReq(
1041 host_user_id=user1.id,
1042 from_date=today_plus_2.isoformat(),
1043 to_date=today_plus_3.isoformat(),
1044 text=valid_request_text("Another test request"),
1045 )
1046 ).host_request_id
1048 moderator.approve_host_request(host_request_id_3)
1050 with requests_session(token2) as api:
1051 # this should make id_2 all read
1052 api.SendHostRequestMessage(
1053 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id_2, text="Test")
1054 )
1056 with api_session(token2) as api:
1057 assert api.Ping(api_pb2.PingReq()).unseen_received_host_request_count == 1
1058 assert api.Ping(api_pb2.PingReq()).unseen_sent_host_request_count == 0
1060 # make sure sent and received count for unseen notifications
1061 with requests_session(token1) as api:
1062 api.SendHostRequestMessage(
1063 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id_3, text="Test message")
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 == 1
1071def test_response_rate(db, moderator):
1072 user1, token1 = generate_user()
1073 user2, token2 = generate_user()
1074 user3, token3 = generate_user(delete_user=True)
1076 today_plus_2 = today() + timedelta(days=2)
1077 today_plus_3 = today() + timedelta(days=3)
1079 with session_scope() as session:
1080 refresh_materialized_view(session, "user_response_rates")
1082 with requests_session(token1) as api:
1083 # deleted: not found
1084 with pytest.raises(grpc.RpcError) as e:
1085 api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user3.id))
1086 assert e.value.code() == grpc.StatusCode.NOT_FOUND
1087 assert e.value.details() == "Couldn't find that user."
1089 # no requests: insufficient
1090 res = api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user2.id))
1091 assert res.HasField("insufficient_data")
1093 # send a request and back date it by 36 hours
1094 host_request_1 = api.CreateHostRequest(
1095 requests_pb2.CreateHostRequestReq(
1096 host_user_id=user2.id,
1097 from_date=today_plus_2.isoformat(),
1098 to_date=today_plus_3.isoformat(),
1099 text=valid_request_text("Test request"),
1100 )
1101 ).host_request_id
1102 moderator.approve_host_request(host_request_1)
1103 with session_scope() as session:
1104 session.execute(
1105 select(Message)
1106 .where(Message.conversation_id == host_request_1)
1107 .where(Message.message_type == MessageType.chat_created)
1108 ).scalar_one().time = now() - timedelta(hours=36)
1109 refresh_materialized_view(session, "user_response_rates")
1111 # still insufficient
1112 res = api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user2.id))
1113 assert res.HasField("insufficient_data")
1115 # send a request and back date it by 35 hours
1116 host_request_2 = api.CreateHostRequest(
1117 requests_pb2.CreateHostRequestReq(
1118 host_user_id=user2.id,
1119 from_date=today_plus_2.isoformat(),
1120 to_date=today_plus_3.isoformat(),
1121 text=valid_request_text("Test request"),
1122 )
1123 ).host_request_id
1124 moderator.approve_host_request(host_request_2)
1125 with session_scope() as session:
1126 session.execute(
1127 select(Message)
1128 .where(Message.conversation_id == host_request_2)
1129 .where(Message.message_type == MessageType.chat_created)
1130 ).scalar_one().time = now() - timedelta(hours=35)
1131 refresh_materialized_view(session, "user_response_rates")
1133 # still insufficient
1134 res = api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user2.id))
1135 assert res.HasField("insufficient_data")
1137 # send a request and back date it by 34 hours
1138 host_request_3 = api.CreateHostRequest(
1139 requests_pb2.CreateHostRequestReq(
1140 host_user_id=user2.id,
1141 from_date=today_plus_2.isoformat(),
1142 to_date=today_plus_3.isoformat(),
1143 text=valid_request_text("Test request"),
1144 )
1145 ).host_request_id
1146 moderator.approve_host_request(host_request_3)
1147 with session_scope() as session:
1148 session.execute(
1149 select(Message)
1150 .where(Message.conversation_id == host_request_3)
1151 .where(Message.message_type == MessageType.chat_created)
1152 ).scalar_one().time = now() - timedelta(hours=34)
1153 refresh_materialized_view(session, "user_response_rates")
1155 # now low
1156 res = api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user2.id))
1157 assert res.HasField("low")
1159 with requests_session(token2) as api:
1160 # accept a host req
1161 api.RespondHostRequest(
1162 requests_pb2.RespondHostRequestReq(
1163 host_request_id=host_request_2,
1164 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED,
1165 text="Accepting host request",
1166 )
1167 )
1169 with session_scope() as session:
1170 refresh_materialized_view(session, "user_response_rates")
1172 with requests_session(token1) as api:
1173 # now some w p33 = 35h
1174 res = api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user2.id))
1175 assert res.HasField("some")
1176 assert res.some.response_time_p33.ToTimedelta() == timedelta(hours=35)
1178 with requests_session(token2) as api:
1179 # accept another host req
1180 api.RespondHostRequest(
1181 requests_pb2.RespondHostRequestReq(
1182 host_request_id=host_request_3,
1183 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED,
1184 text="Accepting host request",
1185 )
1186 )
1188 with session_scope() as session:
1189 refresh_materialized_view(session, "user_response_rates")
1191 with requests_session(token1) as api:
1192 # now most w p33 = 34h, p66 = 35h
1193 res = api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user2.id))
1194 assert res.HasField("most")
1195 assert res.most.response_time_p33.ToTimedelta() == timedelta(hours=34)
1196 assert res.most.response_time_p66.ToTimedelta() == timedelta(hours=35)
1198 with requests_session(token2) as api:
1199 # accept last host req
1200 api.RespondHostRequest(
1201 requests_pb2.RespondHostRequestReq(
1202 host_request_id=host_request_1,
1203 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED,
1204 text="Accepting host request",
1205 )
1206 )
1208 with session_scope() as session:
1209 refresh_materialized_view(session, "user_response_rates")
1211 with requests_session(token1) as api:
1212 # now all w p33 = 34h, p66 = 35h
1213 res = api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user2.id))
1214 assert res.HasField("almost_all")
1215 assert res.almost_all.response_time_p33.ToTimedelta() == timedelta(hours=34)
1216 assert res.almost_all.response_time_p66.ToTimedelta() == timedelta(hours=35)
1218 # send a request and back date it by 2 hours
1219 host_request_4 = api.CreateHostRequest(
1220 requests_pb2.CreateHostRequestReq(
1221 host_user_id=user2.id,
1222 from_date=today_plus_2.isoformat(),
1223 to_date=today_plus_3.isoformat(),
1224 text=valid_request_text("Test request"),
1225 )
1226 ).host_request_id
1227 moderator.approve_host_request(host_request_4)
1228 with session_scope() as session:
1229 session.execute(
1230 select(Message)
1231 .where(Message.conversation_id == host_request_4)
1232 .where(Message.message_type == MessageType.chat_created)
1233 ).scalar_one().time = now() - timedelta(hours=2)
1234 refresh_materialized_view(session, "user_response_rates")
1236 # send a request and back date it by 4 hours
1237 host_request_5 = api.CreateHostRequest(
1238 requests_pb2.CreateHostRequestReq(
1239 host_user_id=user2.id,
1240 from_date=today_plus_2.isoformat(),
1241 to_date=today_plus_3.isoformat(),
1242 text=valid_request_text("Test request"),
1243 )
1244 ).host_request_id
1245 moderator.approve_host_request(host_request_5)
1246 with session_scope() as session:
1247 session.execute(
1248 select(Message)
1249 .where(Message.conversation_id == host_request_5)
1250 .where(Message.message_type == MessageType.chat_created)
1251 ).scalar_one().time = now() - timedelta(hours=4)
1252 refresh_materialized_view(session, "user_response_rates")
1254 # now some w p33 = 35h
1255 res = api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user2.id))
1256 assert res.HasField("some")
1257 assert res.some.response_time_p33.ToTimedelta() == timedelta(hours=35)
1259 with requests_session(token2) as api:
1260 # accept host req
1261 api.RespondHostRequest(
1262 requests_pb2.RespondHostRequestReq(
1263 host_request_id=host_request_5,
1264 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED,
1265 text="Accepting host request",
1266 )
1267 )
1269 with session_scope() as session:
1270 refresh_materialized_view(session, "user_response_rates")
1272 with requests_session(token1) as api:
1273 # now most w p33 = 34h, p66 = 36h
1274 res = api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user2.id))
1275 assert res.HasField("most")
1276 assert res.most.response_time_p33.ToTimedelta() == timedelta(hours=34)
1277 assert res.most.response_time_p66.ToTimedelta() == timedelta(hours=36)
1279 with requests_session(token2) as api:
1280 # accept host req
1281 api.RespondHostRequest(
1282 requests_pb2.RespondHostRequestReq(
1283 host_request_id=host_request_4,
1284 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED,
1285 text="Accepting host request",
1286 )
1287 )
1289 with session_scope() as session:
1290 refresh_materialized_view(session, "user_response_rates")
1292 with requests_session(token1) as api:
1293 # now most w p33 = 4h, p66 = 35h
1294 res = api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user2.id))
1295 assert res.HasField("almost_all")
1296 assert res.almost_all.response_time_p33.ToTimedelta() == timedelta(hours=4)
1297 assert res.almost_all.response_time_p66.ToTimedelta() == timedelta(hours=35)
1300def test_request_notifications(db, push_collector: PushCollector, moderator):
1301 host, host_token = generate_user(complete_profile=True)
1302 surfer, surfer_token = generate_user(complete_profile=True)
1304 today_plus_2 = today() + timedelta(days=2)
1305 today_plus_3 = today() + timedelta(days=3)
1307 with requests_session(surfer_token) as api:
1308 hr_id = api.CreateHostRequest(
1309 requests_pb2.CreateHostRequestReq(
1310 host_user_id=host.id,
1311 from_date=today_plus_2.isoformat(),
1312 to_date=today_plus_3.isoformat(),
1313 text=valid_request_text("can i stay plz"),
1314 )
1315 ).host_request_id
1317 with mock_notification_email() as mock:
1318 moderator.approve_host_request(hr_id)
1320 mock.assert_called_once()
1321 e = email_fields(mock)
1322 assert e.recipient == host.email
1323 assert "host request" in e.subject.lower()
1324 assert host.name in e.plain
1325 assert host.name in e.html
1326 assert "quick decline" in e.plain.lower(), e.plain
1327 assert "quick decline" in e.html.lower()
1328 assert surfer.name in e.plain
1329 assert surfer.name in e.html
1330 assert localize_date(today_plus_2, host.ui_language_preference or "en") in e.plain
1331 assert localize_date(today_plus_2, host.ui_language_preference or "en") in e.html
1332 assert localize_date(today_plus_3, host.ui_language_preference or "en") in e.plain
1333 assert localize_date(today_plus_3, host.ui_language_preference or "en") in e.html
1334 assert "http://localhost:5001/img/thumbnail/" not in e.plain
1335 assert "http://localhost:5001/img/thumbnail/" in e.html
1336 assert f"http://localhost:3000/messages/request/{hr_id}" in e.plain
1337 assert f"http://localhost:3000/messages/request/{hr_id}" in e.html
1339 assert push_collector.get_for_user(host.id).content.title == f"New host request from {surfer.name}"
1341 with requests_session(host_token) as api:
1342 with mock_notification_email() as mock:
1343 api.RespondHostRequest(
1344 requests_pb2.RespondHostRequestReq(
1345 host_request_id=hr_id,
1346 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED,
1347 text="Accepting host request",
1348 )
1349 )
1351 e = email_fields(mock)
1352 assert e.recipient == surfer.email
1353 assert "host request" in e.subject.lower()
1354 assert host.name in e.plain
1355 assert host.name in e.html
1356 assert surfer.name in e.plain
1357 assert surfer.name in e.html
1358 assert localize_date(today_plus_2, surfer.ui_language_preference or "en") in e.plain
1359 assert localize_date(today_plus_2, surfer.ui_language_preference or "en") in e.html
1360 assert localize_date(today_plus_3, surfer.ui_language_preference or "en") in e.plain
1361 assert localize_date(today_plus_3, surfer.ui_language_preference or "en") in e.html
1362 assert "http://localhost:5001/img/thumbnail/" not in e.plain
1363 assert "http://localhost:5001/img/thumbnail/" in e.html
1364 assert f"http://localhost:3000/messages/request/{hr_id}" in e.plain
1365 assert f"http://localhost:3000/messages/request/{hr_id}" in e.html
1367 assert push_collector.get_for_user(surfer.id).content.title == f"{host.name} accepted your request"
1370def test_quick_decline(db, push_collector: PushCollector, moderator):
1371 host, host_token = generate_user(complete_profile=True)
1372 surfer, surfer_token = generate_user(complete_profile=True)
1374 today_plus_2 = today() + timedelta(days=2)
1375 today_plus_3 = today() + timedelta(days=3)
1377 with requests_session(surfer_token) as api:
1378 hr_id = api.CreateHostRequest(
1379 requests_pb2.CreateHostRequestReq(
1380 host_user_id=host.id,
1381 from_date=today_plus_2.isoformat(),
1382 to_date=today_plus_3.isoformat(),
1383 text=valid_request_text("can i stay plz"),
1384 )
1385 ).host_request_id
1387 with mock_notification_email() as mock:
1388 moderator.approve_host_request(hr_id)
1390 mock.assert_called_once()
1391 e = email_fields(mock)
1392 assert e.recipient == host.email
1393 assert "host request" in e.subject.lower()
1394 assert host.name in e.plain
1395 assert host.name in e.html
1396 assert "quick decline" in e.plain.lower(), e.plain
1397 assert "quick decline" in e.html.lower()
1398 assert surfer.name in e.plain
1399 assert surfer.name in e.html
1400 assert localize_date(today_plus_2, host.ui_language_preference or "en") in e.plain
1401 assert localize_date(today_plus_2, host.ui_language_preference or "en") in e.html
1402 assert localize_date(today_plus_3, host.ui_language_preference or "en") in e.plain
1403 assert localize_date(today_plus_3, host.ui_language_preference or "en") in e.html
1404 assert "http://localhost:5001/img/thumbnail/" not in e.plain
1405 assert "http://localhost:5001/img/thumbnail/" in e.html
1406 assert f"http://localhost:3000/messages/request/{hr_id}" in e.plain
1407 assert f"http://localhost:3000/messages/request/{hr_id}" in e.html
1409 assert push_collector.get_for_user(host.id).content.title == f"New host request from {surfer.name}"
1411 # very ugly
1412 # http://localhost:3000/quick-link?payload=CAEiGAoOZnJpZW5kX3JlcXVlc3QSBmFjY2VwdA==&sig=BQdk024NTATm8zlR0krSXTBhP5U9TlFv7VhJeIHZtUg=
1413 for link in re.findall(r'<a href="(.*?)"', email_fields(mock).html): 1413 ↛ 1432line 1413 didn't jump to line 1432 because the loop on line 1413 didn't complete
1414 if "payload" not in link:
1415 continue
1416 print(link)
1417 url_parts = urlparse(link)
1418 params = parse_qs(url_parts.query)
1419 print(params["payload"][0])
1420 payload = unsubscribe_pb2.UnsubscribePayload.FromString(b64decode(params["payload"][0]))
1421 if payload.HasField("host_request_quick_decline"): 1421 ↛ 1413line 1421 didn't jump to line 1413 because the condition on line 1421 was always true
1422 with auth_api_session() as (auth_api, metadata_interceptor):
1423 res = auth_api.Unsubscribe(
1424 auth_pb2.UnsubscribeReq(
1425 payload=b64decode(params["payload"][0]),
1426 sig=b64decode(params["sig"][0]),
1427 )
1428 )
1429 assert res.response == "Thank you for responding to the host request!"
1430 break
1431 else:
1432 raise Exception("Didn't find link")
1434 with requests_session(surfer_token) as api:
1435 res = api.GetHostRequest(requests_pb2.GetHostRequestReq(host_request_id=hr_id))
1436 assert res.status == conversations_pb2.HOST_REQUEST_STATUS_REJECTED
1439def test_host_req_feedback(db, moderator):
1440 host, host_token = generate_user(complete_profile=True)
1441 host2, host2_token = generate_user(complete_profile=True)
1442 host3, host3_token = generate_user(complete_profile=True)
1443 surfer, surfer_token = generate_user(complete_profile=True)
1445 today_plus_2 = today() + timedelta(days=2)
1446 today_plus_3 = today() + timedelta(days=3)
1448 with requests_session(surfer_token) as api:
1449 hr_id = api.CreateHostRequest(
1450 requests_pb2.CreateHostRequestReq(
1451 host_user_id=host.id,
1452 from_date=today_plus_2.isoformat(),
1453 to_date=today_plus_3.isoformat(),
1454 text=valid_request_text("can i stay plz"),
1455 )
1456 ).host_request_id
1457 hr2_id = api.CreateHostRequest(
1458 requests_pb2.CreateHostRequestReq(
1459 host_user_id=host2.id,
1460 from_date=today_plus_2.isoformat(),
1461 to_date=today_plus_3.isoformat(),
1462 text=valid_request_text("can i stay plz"),
1463 )
1464 ).host_request_id
1465 hr3_id = api.CreateHostRequest(
1466 requests_pb2.CreateHostRequestReq(
1467 host_user_id=host3.id,
1468 from_date=today_plus_2.isoformat(),
1469 to_date=today_plus_3.isoformat(),
1470 text=valid_request_text("can i stay plz"),
1471 )
1472 ).host_request_id
1474 moderator.approve_host_request(hr_id)
1475 moderator.approve_host_request(hr2_id)
1476 moderator.approve_host_request(hr3_id)
1478 with requests_session(host_token) as api:
1479 res = api.GetHostRequest(requests_pb2.GetHostRequestReq(host_request_id=hr_id))
1480 assert not res.need_host_request_feedback
1482 api.RespondHostRequest(
1483 requests_pb2.RespondHostRequestReq(
1484 host_request_id=hr_id,
1485 status=conversations_pb2.HOST_REQUEST_STATUS_REJECTED,
1486 )
1487 )
1489 res = api.GetHostRequest(requests_pb2.GetHostRequestReq(host_request_id=hr_id))
1490 assert res.need_host_request_feedback
1492 # surfer can't leave feedback
1493 with requests_session(surfer_token) as api:
1494 with pytest.raises(grpc.RpcError) as e:
1495 api.SendHostRequestFeedback(
1496 requests_pb2.SendHostRequestFeedbackReq(
1497 host_request_id=hr_id,
1498 )
1499 )
1500 assert e.value.code() == grpc.StatusCode.NOT_FOUND
1501 assert e.value.details() == "Couldn't find that host request."
1503 with requests_session(host_token) as api:
1504 api.SendHostRequestFeedback(
1505 requests_pb2.SendHostRequestFeedbackReq(
1506 host_request_id=hr_id,
1507 host_request_quality=requests_pb2.HOST_REQUEST_QUALITY_LOW,
1508 )
1509 )
1510 res = api.GetHostRequest(requests_pb2.GetHostRequestReq(host_request_id=hr_id))
1511 assert not res.need_host_request_feedback
1513 # can't leave it twice
1514 with requests_session(host_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.FAILED_PRECONDITION
1522 assert e.value.details() == "You have already left feedback for this host request!"
1524 res = api.GetHostRequest(requests_pb2.GetHostRequestReq(host_request_id=hr_id))
1525 assert not res.need_host_request_feedback
1527 with requests_session(host2_token) as api:
1528 api.RespondHostRequest(
1529 requests_pb2.RespondHostRequestReq(
1530 host_request_id=hr2_id, status=conversations_pb2.HOST_REQUEST_STATUS_REJECTED
1531 )
1532 )
1533 # can't leave feedback on the wrong one
1534 with pytest.raises(grpc.RpcError) as e:
1535 api.SendHostRequestFeedback(
1536 requests_pb2.SendHostRequestFeedbackReq(
1537 host_request_id=hr_id,
1538 )
1539 )
1540 assert e.value.code() == grpc.StatusCode.NOT_FOUND
1541 assert e.value.details() == "Couldn't find that host request."
1543 # null feedback is still feedback
1544 api.SendHostRequestFeedback(requests_pb2.SendHostRequestFeedbackReq(host_request_id=hr2_id))
1546 with requests_session(host3_token) as api:
1547 api.RespondHostRequest(
1548 requests_pb2.RespondHostRequestReq(
1549 host_request_id=hr3_id, status=conversations_pb2.HOST_REQUEST_STATUS_REJECTED
1550 )
1551 )
1553 api.SendHostRequestFeedback(
1554 requests_pb2.SendHostRequestFeedbackReq(host_request_id=hr3_id, decline_reason="bad req")
1555 )