Coverage for src/tests/test_requests.py: 99%
722 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-12-09 11:32 +0000
« prev ^ index » next coverage.py v7.11.0, created at 2025-12-09 11:32 +0000
1import re
2from datetime import timedelta
3from urllib.parse import parse_qs, urlparse
5import grpc
6import pytest
8from couchers.constants import HOST_REQUEST_MIN_LENGTH_UTF16
9from couchers.crypto import b64decode
10from couchers.db import session_scope
11from couchers.materialized_views import refresh_materialized_view
12from couchers.models import (
13 Message,
14 MessageType,
15 RateLimitAction,
16)
17from couchers.proto import (
18 api_pb2,
19 auth_pb2,
20 conversations_pb2,
21 requests_pb2,
22)
23from couchers.proto.internal import unsubscribe_pb2
24from couchers.rate_limits.definitions import RATE_LIMIT_DEFINITIONS, RATE_LIMIT_HOURS
25from couchers.sql import couchers_select as select
26from couchers.templates.v2 import v2date
27from couchers.utils import create_coordinate, now, today
28from tests.test_fixtures import ( # noqa
29 api_session,
30 auth_api_session,
31 db,
32 email_fields,
33 generate_user,
34 mock_notification_email,
35 moderator,
36 push_collector,
37 requests_session,
38 testconfig,
39)
42@pytest.fixture(autouse=True)
43def _(testconfig):
44 pass
47def valid_request_text(text: str = "Test request") -> str:
48 """Pads a request text to a valid length."""
49 # Request lengths are measured in utf-16 code units to match the frontend.
50 utf16_length = len(text.encode("utf-16-le")) // 2
51 if utf16_length >= HOST_REQUEST_MIN_LENGTH_UTF16:
52 return text
53 padding_length = HOST_REQUEST_MIN_LENGTH_UTF16 - utf16_length
54 return text + ("_" * padding_length) # Each "_" adds one utf16 code unit.
57def test_create_request(db, moderator):
58 user1, token1 = generate_user()
59 hosting_city = "Morningside Heights, New York City"
60 hosting_lat = 40.8086
61 hosting_lng = -73.9616
62 hosting_radius = 500
63 user2, token2 = generate_user(
64 city=hosting_city,
65 geom=create_coordinate(hosting_lat, hosting_lng),
66 geom_radius=hosting_radius,
67 )
69 today_plus_2 = (today() + timedelta(days=2)).isoformat()
70 today_plus_3 = (today() + timedelta(days=3)).isoformat()
71 today_minus_2 = (today() - timedelta(days=2)).isoformat()
72 today_minus_3 = (today() - timedelta(days=3)).isoformat()
74 with requests_session(token1) as api:
75 with pytest.raises(grpc.RpcError) as e:
76 api.CreateHostRequest(
77 requests_pb2.CreateHostRequestReq(
78 host_user_id=user1.id, from_date=today_plus_2, to_date=today_plus_3, text=valid_request_text()
79 )
80 )
81 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
82 assert e.value.details() == "Can't request hosting from yourself."
84 with pytest.raises(grpc.RpcError) as e:
85 api.CreateHostRequest(
86 requests_pb2.CreateHostRequestReq(
87 host_user_id=999, from_date=today_plus_2, to_date=today_plus_3, text=valid_request_text()
88 )
89 )
90 assert e.value.code() == grpc.StatusCode.NOT_FOUND
91 assert e.value.details() == "Couldn't find that user."
93 with pytest.raises(grpc.RpcError) as e:
94 api.CreateHostRequest(
95 requests_pb2.CreateHostRequestReq(
96 host_user_id=user2.id, from_date=today_plus_3, to_date=today_plus_2, 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, from_date=today_minus_3, to_date=today_plus_2, text=valid_request_text()
106 )
107 )
108 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
109 assert e.value.details() == "From date must be today or later."
111 with pytest.raises(grpc.RpcError) as e:
112 api.CreateHostRequest(
113 requests_pb2.CreateHostRequestReq(
114 host_user_id=user2.id, from_date=today_plus_2, to_date=today_minus_2, text=valid_request_text()
115 )
116 )
117 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
118 assert e.value.details() == "From date can't be after to date."
120 with pytest.raises(grpc.RpcError) as e:
121 api.CreateHostRequest(
122 requests_pb2.CreateHostRequestReq(
123 host_user_id=user2.id, from_date="2020-00-06", to_date=today_minus_2, text=valid_request_text()
124 )
125 )
126 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
127 assert e.value.details() == "Invalid date."
129 with pytest.raises(grpc.RpcError) as e:
130 api.CreateHostRequest(
131 requests_pb2.CreateHostRequestReq(
132 host_user_id=user2.id, from_date=today_plus_2, to_date=today_plus_3, text="Too short."
133 )
134 )
135 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
136 assert e.value.details() == "Host request cannot be shorter than 250 characters."
138 res = api.CreateHostRequest(
139 requests_pb2.CreateHostRequestReq(
140 host_user_id=user2.id,
141 from_date=today_plus_2,
142 to_date=today_plus_3,
143 text=valid_request_text(),
144 )
145 )
146 host_request_id = res.host_request_id
148 moderator.approve_host_request(host_request_id)
150 with requests_session(token1) as api:
151 host_requests = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_sent=True)).host_requests
153 assert len(host_requests) == 1
154 hr = host_requests[0]
156 assert hr.latest_message.text.text == valid_request_text()
158 assert hr.hosting_city == hosting_city
159 assert round(hr.hosting_lat, 4) == hosting_lat
160 assert round(hr.hosting_lng, 4) == hosting_lng
161 assert hr.hosting_radius == hosting_radius
163 today_ = today()
164 today_plus_one_year = today_ + timedelta(days=365)
165 today_plus_one_year_plus_2 = (today_plus_one_year + timedelta(days=2)).isoformat()
166 today_plus_one_year_plus_3 = (today_plus_one_year + timedelta(days=3)).isoformat()
167 with pytest.raises(grpc.RpcError) as e:
168 api.CreateHostRequest(
169 requests_pb2.CreateHostRequestReq(
170 host_user_id=user2.id,
171 from_date=today_plus_one_year_plus_2,
172 to_date=today_plus_one_year_plus_3,
173 text=valid_request_text("Test from date after one year"),
174 )
175 )
176 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
177 assert e.value.details() == "The start date must be within one year from today."
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_2,
184 to_date=today_plus_one_year_plus_3,
185 text=valid_request_text("Test to date one year after from date"),
186 )
187 )
188 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
189 assert e.value.details() == "You cannot request to stay with someone for longer than one year."
192def test_create_request_incomplete_profile(db):
193 user1, token1 = generate_user(complete_profile=False)
194 user2, _ = generate_user()
195 today_plus_2 = (today() + timedelta(days=2)).isoformat()
196 today_plus_3 = (today() + timedelta(days=3)).isoformat()
197 with requests_session(token1) as api:
198 with pytest.raises(grpc.RpcError) as e:
199 api.CreateHostRequest(
200 requests_pb2.CreateHostRequestReq(
201 host_user_id=user2.id, from_date=today_plus_2, to_date=today_plus_3, text=valid_request_text()
202 )
203 )
204 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
205 assert e.value.details() == "You have to complete your profile before you can send a request."
208def test_excessive_requests_are_reported(db):
209 """Test that excessive host requests are first reported in a warning email and finally lead blocking of further requests."""
210 user, token = generate_user()
211 today_plus_2 = (today() + timedelta(days=2)).isoformat()
212 today_plus_3 = (today() + timedelta(days=3)).isoformat()
213 rate_limit_definition = RATE_LIMIT_DEFINITIONS[RateLimitAction.host_request]
214 with requests_session(token) as api:
215 # Test warning email
216 with mock_notification_email() as mock_email:
217 for _ in range(rate_limit_definition.warning_limit):
218 host_user, _ = generate_user()
219 _ = api.CreateHostRequest(
220 requests_pb2.CreateHostRequestReq(
221 host_user_id=host_user.id,
222 from_date=today_plus_2,
223 to_date=today_plus_3,
224 text=valid_request_text(),
225 )
226 )
228 assert mock_email.call_count == 0
229 host_user, _ = generate_user()
230 _ = api.CreateHostRequest(
231 requests_pb2.CreateHostRequestReq(
232 host_user_id=host_user.id,
233 from_date=today_plus_2,
234 to_date=today_plus_3,
235 text=valid_request_text("Excessive test request"),
236 )
237 )
238 assert mock_email.call_count == 1
239 email = mock_email.mock_calls[0].kwargs["plain"]
240 assert email.startswith(
241 f"User {user.username} has sent {rate_limit_definition.warning_limit} host requests in the past {RATE_LIMIT_HOURS} hours."
242 )
244 # Test ban after exceeding HOST_REQUEST_HARD_LIMIT
245 with mock_notification_email() as mock_email:
246 for _ in range(rate_limit_definition.hard_limit - rate_limit_definition.warning_limit - 1):
247 host_user, _ = generate_user()
248 _ = api.CreateHostRequest(
249 requests_pb2.CreateHostRequestReq(
250 host_user_id=host_user.id,
251 from_date=today_plus_2,
252 to_date=today_plus_3,
253 text=valid_request_text(),
254 )
255 )
257 assert mock_email.call_count == 0
258 host_user, _ = generate_user()
259 with pytest.raises(grpc.RpcError) as exc_info:
260 _ = api.CreateHostRequest(
261 requests_pb2.CreateHostRequestReq(
262 host_user_id=host_user.id,
263 from_date=today_plus_2,
264 to_date=today_plus_3,
265 text=valid_request_text("Excessive test request"),
266 )
267 )
268 assert exc_info.value.code() == grpc.StatusCode.RESOURCE_EXHAUSTED
269 assert (
270 exc_info.value.details()
271 == "You have sent a lot of host requests in the past 24 hours. To avoid spam, you can't send any more for now."
272 )
274 assert mock_email.call_count == 1
275 email = mock_email.mock_calls[0].kwargs["plain"]
276 assert email.startswith(
277 f"User {user.username} has sent {rate_limit_definition.hard_limit} host requests in the past {RATE_LIMIT_HOURS} hours."
278 )
279 assert "The user has been blocked from sending further host requests for now." in email
282def add_message(db, text, author_id, conversation_id):
283 with session_scope() as session:
284 message = Message(
285 conversation_id=conversation_id, author_id=author_id, text=text, message_type=MessageType.text
286 )
288 session.add(message)
291def test_GetHostRequest(db):
292 user1, token1 = generate_user()
293 user2, token2 = generate_user()
294 user3, token3 = generate_user()
295 today_plus_2 = (today() + timedelta(days=2)).isoformat()
296 today_plus_3 = (today() + timedelta(days=3)).isoformat()
297 with requests_session(token1) as api:
298 host_request_id = api.CreateHostRequest(
299 requests_pb2.CreateHostRequestReq(
300 host_user_id=user2.id,
301 from_date=today_plus_2,
302 to_date=today_plus_3,
303 text=valid_request_text("Test request 1"),
304 )
305 ).host_request_id
307 with pytest.raises(grpc.RpcError) as e:
308 api.GetHostRequest(requests_pb2.GetHostRequestReq(host_request_id=999))
309 assert e.value.code() == grpc.StatusCode.NOT_FOUND
310 assert e.value.details() == "Couldn't find that host request."
312 api.SendHostRequestMessage(
313 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 1")
314 )
316 res = api.GetHostRequest(requests_pb2.GetHostRequestReq(host_request_id=host_request_id))
317 assert res.latest_message.text.text == "Test message 1"
320def test_ListHostRequests(db, moderator):
321 user1, token1 = generate_user()
322 user2, token2 = generate_user()
323 user3, token3 = generate_user()
324 today_plus_2 = (today() + timedelta(days=2)).isoformat()
325 today_plus_3 = (today() + timedelta(days=3)).isoformat()
326 with requests_session(token1) as api:
327 host_request_1 = api.CreateHostRequest(
328 requests_pb2.CreateHostRequestReq(
329 host_user_id=user2.id,
330 from_date=today_plus_2,
331 to_date=today_plus_3,
332 text=valid_request_text("Test request 1"),
333 )
334 ).host_request_id
336 host_request_2 = api.CreateHostRequest(
337 requests_pb2.CreateHostRequestReq(
338 host_user_id=user3.id,
339 from_date=today_plus_2,
340 to_date=today_plus_3,
341 text=valid_request_text("Test request 2"),
342 )
343 ).host_request_id
345 moderator.approve_host_request(host_request_1)
346 moderator.approve_host_request(host_request_2)
348 with requests_session(token1) as api:
349 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_sent=True))
350 assert res.no_more
351 assert len(res.host_requests) == 2
353 with requests_session(token2) as api:
354 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_received=True))
355 assert res.no_more
356 assert len(res.host_requests) == 1
357 assert res.host_requests[0].latest_message.text.text == valid_request_text("Test request 1")
358 assert res.host_requests[0].surfer_user_id == user1.id
359 assert res.host_requests[0].host_user_id == user2.id
360 assert res.host_requests[0].status == conversations_pb2.HOST_REQUEST_STATUS_PENDING
362 add_message(db, "Test request 1 message 1", user2.id, host_request_1)
363 add_message(db, "Test request 1 message 2", user2.id, host_request_1)
364 add_message(db, "Test request 1 message 3", user2.id, host_request_1)
366 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_received=True))
367 assert res.host_requests[0].latest_message.text.text == "Test request 1 message 3"
369 host_request_3 = api.CreateHostRequest(
370 requests_pb2.CreateHostRequestReq(
371 host_user_id=user1.id,
372 from_date=today_plus_2,
373 to_date=today_plus_3,
374 text=valid_request_text("Test request 3"),
375 )
376 ).host_request_id
378 moderator.approve_host_request(host_request_3)
380 add_message(db, "Test request 2 message 1", user1.id, host_request_2)
381 add_message(db, "Test request 2 message 2", user3.id, host_request_2)
383 with requests_session(token3) as api:
384 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_received=True))
385 assert res.no_more
386 assert len(res.host_requests) == 1
387 assert res.host_requests[0].latest_message.text.text == "Test request 2 message 2"
389 with requests_session(token1) as api:
390 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_received=True))
391 assert len(res.host_requests) == 1
393 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq())
394 assert len(res.host_requests) == 3
397def test_ListHostRequests_pagination_regression(db, moderator):
398 """
399 ListHostRequests was skipping a request when getting multiple pages
400 """
401 user1, token1 = generate_user()
402 user2, token2 = generate_user()
403 today_plus_2 = (today() + timedelta(days=2)).isoformat()
404 today_plus_3 = (today() + timedelta(days=3)).isoformat()
405 with requests_session(token1) as api:
406 host_request_1 = api.CreateHostRequest(
407 requests_pb2.CreateHostRequestReq(
408 host_user_id=user2.id,
409 from_date=today_plus_2,
410 to_date=today_plus_3,
411 text=valid_request_text("Test request 1"),
412 )
413 ).host_request_id
415 host_request_2 = api.CreateHostRequest(
416 requests_pb2.CreateHostRequestReq(
417 host_user_id=user2.id,
418 from_date=today_plus_2,
419 to_date=today_plus_3,
420 text=valid_request_text("Test request 2"),
421 )
422 ).host_request_id
424 host_request_3 = api.CreateHostRequest(
425 requests_pb2.CreateHostRequestReq(
426 host_user_id=user2.id,
427 from_date=today_plus_2,
428 to_date=today_plus_3,
429 text=valid_request_text("Test request 3"),
430 )
431 ).host_request_id
433 moderator.approve_host_request(host_request_1)
434 moderator.approve_host_request(host_request_2)
435 moderator.approve_host_request(host_request_3)
437 with requests_session(token2) as api:
438 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_received=True))
439 assert res.no_more
440 assert len(res.host_requests) == 3
441 assert res.host_requests[0].latest_message.text.text == valid_request_text("Test request 3")
442 assert res.host_requests[1].latest_message.text.text == valid_request_text("Test request 2")
443 assert res.host_requests[2].latest_message.text.text == valid_request_text("Test request 1")
445 with requests_session(token2) as api:
446 api.RespondHostRequest(
447 requests_pb2.RespondHostRequestReq(
448 host_request_id=host_request_2,
449 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED,
450 text="Accepting host request 2",
451 )
452 )
453 api.RespondHostRequest(
454 requests_pb2.RespondHostRequestReq(
455 host_request_id=host_request_1,
456 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED,
457 text="Accepting host request 1",
458 )
459 )
460 api.RespondHostRequest(
461 requests_pb2.RespondHostRequestReq(
462 host_request_id=host_request_3,
463 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED,
464 text="Accepting host request 3",
465 )
466 )
468 with requests_session(token2) as api:
469 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_received=True))
470 assert res.no_more
471 assert len(res.host_requests) == 3
472 assert res.host_requests[0].latest_message.text.text == "Accepting host request 3"
473 assert res.host_requests[1].latest_message.text.text == "Accepting host request 1"
474 assert res.host_requests[2].latest_message.text.text == "Accepting host request 2"
476 with requests_session(token2) as api:
477 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_received=True, number=1))
478 assert not res.no_more
479 assert len(res.host_requests) == 1
480 assert res.host_requests[0].latest_message.text.text == "Accepting host request 3"
481 res = api.ListHostRequests(
482 requests_pb2.ListHostRequestsReq(only_received=True, number=1, last_request_id=res.last_request_id)
483 )
484 assert not res.no_more
485 assert len(res.host_requests) == 1
486 assert res.host_requests[0].latest_message.text.text == "Accepting host request 1"
487 res = api.ListHostRequests(
488 requests_pb2.ListHostRequestsReq(only_received=True, number=1, last_request_id=res.last_request_id)
489 )
490 assert res.no_more
491 assert len(res.host_requests) == 1
492 assert res.host_requests[0].latest_message.text.text == "Accepting host request 2"
495def test_ListHostRequests_active_filter(db, moderator):
496 user1, token1 = generate_user()
497 user2, token2 = generate_user()
498 today_plus_2 = (today() + timedelta(days=2)).isoformat()
499 today_plus_3 = (today() + timedelta(days=3)).isoformat()
501 with requests_session(token1) as api:
502 request_id = api.CreateHostRequest(
503 requests_pb2.CreateHostRequestReq(
504 host_user_id=user2.id,
505 from_date=today_plus_2,
506 to_date=today_plus_3,
507 text=valid_request_text("Test request 1"),
508 )
509 ).host_request_id
511 moderator.approve_host_request(request_id)
513 with requests_session(token1) as api:
514 api.RespondHostRequest(
515 requests_pb2.RespondHostRequestReq(
516 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED
517 )
518 )
520 with requests_session(token2) as api:
521 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_received=True))
522 assert len(res.host_requests) == 1
523 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_active=True))
524 assert len(res.host_requests) == 0
527def test_RespondHostRequests(db, moderator):
528 user1, token1 = generate_user()
529 user2, token2 = generate_user()
530 user3, token3 = generate_user()
531 today_plus_2 = (today() + timedelta(days=2)).isoformat()
532 today_plus_3 = (today() + timedelta(days=3)).isoformat()
534 with requests_session(token1) as api:
535 request_id = api.CreateHostRequest(
536 requests_pb2.CreateHostRequestReq(
537 host_user_id=user2.id,
538 from_date=today_plus_2,
539 to_date=today_plus_3,
540 text=valid_request_text("Test request 1"),
541 )
542 ).host_request_id
544 moderator.approve_host_request(request_id)
546 # another user can't access
547 with requests_session(token3) as api:
548 with pytest.raises(grpc.RpcError) as e:
549 api.RespondHostRequest(
550 requests_pb2.RespondHostRequestReq(
551 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED
552 )
553 )
554 assert e.value.code() == grpc.StatusCode.NOT_FOUND
555 assert e.value.details() == "Couldn't find that host request."
557 with requests_session(token1) as api:
558 with pytest.raises(grpc.RpcError) as e:
559 api.RespondHostRequest(
560 requests_pb2.RespondHostRequestReq(
561 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED
562 )
563 )
564 assert e.value.code() == grpc.StatusCode.PERMISSION_DENIED
565 assert e.value.details() == "You are not the host of this request."
567 with requests_session(token2) as api:
568 # non existing id
569 with pytest.raises(grpc.RpcError) as e:
570 api.RespondHostRequest(
571 requests_pb2.RespondHostRequestReq(
572 host_request_id=9999, status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED
573 )
574 )
575 assert e.value.code() == grpc.StatusCode.NOT_FOUND
577 # host can't confirm or cancel (host should accept/reject)
578 with pytest.raises(grpc.RpcError) as e:
579 api.RespondHostRequest(
580 requests_pb2.RespondHostRequestReq(
581 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CONFIRMED
582 )
583 )
584 assert e.value.code() == grpc.StatusCode.PERMISSION_DENIED
585 assert e.value.details() == "You can't set the host request status to that."
586 with pytest.raises(grpc.RpcError) as e:
587 api.RespondHostRequest(
588 requests_pb2.RespondHostRequestReq(
589 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED
590 )
591 )
592 assert e.value.code() == grpc.StatusCode.PERMISSION_DENIED
593 assert e.value.details() == "You can't set the host request status to that."
595 api.RespondHostRequest(
596 requests_pb2.RespondHostRequestReq(
597 host_request_id=request_id,
598 status=conversations_pb2.HOST_REQUEST_STATUS_REJECTED,
599 text="Test rejection message",
600 )
601 )
602 res = api.GetHostRequestMessages(requests_pb2.GetHostRequestMessagesReq(host_request_id=request_id))
603 assert res.messages[0].text.text == "Test rejection message"
604 assert res.messages[1].WhichOneof("content") == "host_request_status_changed"
605 assert res.messages[1].host_request_status_changed.status == conversations_pb2.HOST_REQUEST_STATUS_REJECTED
606 # should be able to move from rejected -> accepted
607 api.RespondHostRequest(
608 requests_pb2.RespondHostRequestReq(
609 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED
610 )
611 )
613 with requests_session(token1) as api:
614 # can't make pending
615 with pytest.raises(grpc.RpcError) as e:
616 api.RespondHostRequest(
617 requests_pb2.RespondHostRequestReq(
618 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_PENDING
619 )
620 )
621 assert e.value.code() == grpc.StatusCode.PERMISSION_DENIED
622 assert e.value.details() == "You can't set the host request status to that."
624 # can confirm then cancel
625 api.RespondHostRequest(
626 requests_pb2.RespondHostRequestReq(
627 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CONFIRMED
628 )
629 )
631 api.RespondHostRequest(
632 requests_pb2.RespondHostRequestReq(
633 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED
634 )
635 )
637 # can't confirm after having cancelled
638 with pytest.raises(grpc.RpcError) as e:
639 api.RespondHostRequest(
640 requests_pb2.RespondHostRequestReq(
641 host_request_id=request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CONFIRMED
642 )
643 )
644 assert e.value.code() == grpc.StatusCode.PERMISSION_DENIED
645 assert e.value.details() == "You can't set the host request status to that."
647 # at this point there should be 7 messages
648 # 2 for creation, 2 for the status change with message, 3 for the other status changed
649 with requests_session(token1) as api:
650 res = api.GetHostRequestMessages(requests_pb2.GetHostRequestMessagesReq(host_request_id=request_id))
651 assert len(res.messages) == 7
652 assert res.messages[0].host_request_status_changed.status == conversations_pb2.HOST_REQUEST_STATUS_CANCELLED
653 assert res.messages[1].host_request_status_changed.status == conversations_pb2.HOST_REQUEST_STATUS_CONFIRMED
654 assert res.messages[2].host_request_status_changed.status == conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED
655 assert res.messages[4].host_request_status_changed.status == conversations_pb2.HOST_REQUEST_STATUS_REJECTED
656 assert res.messages[6].WhichOneof("content") == "chat_created"
659def test_get_host_request_messages(db, moderator):
660 user1, token1 = generate_user()
661 user2, token2 = generate_user()
662 today_plus_2 = (today() + timedelta(days=2)).isoformat()
663 today_plus_3 = (today() + timedelta(days=3)).isoformat()
664 with requests_session(token1) as api:
665 res = api.CreateHostRequest(
666 requests_pb2.CreateHostRequestReq(
667 host_user_id=user2.id,
668 from_date=today_plus_2,
669 to_date=today_plus_3,
670 text=valid_request_text("Test request 1"),
671 )
672 )
673 conversation_id = res.host_request_id
675 moderator.approve_host_request(conversation_id)
677 add_message(db, "Test request 1 message 1", user1.id, conversation_id)
678 add_message(db, "Test request 1 message 2", user1.id, conversation_id)
679 add_message(db, "Test request 1 message 3", user1.id, conversation_id)
681 with requests_session(token2) as api:
682 api.RespondHostRequest(
683 requests_pb2.RespondHostRequestReq(
684 host_request_id=conversation_id, status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED
685 )
686 )
688 add_message(db, "Test request 1 message 4", user2.id, conversation_id)
689 add_message(db, "Test request 1 message 5", user2.id, conversation_id)
691 api.RespondHostRequest(
692 requests_pb2.RespondHostRequestReq(
693 host_request_id=conversation_id, status=conversations_pb2.HOST_REQUEST_STATUS_REJECTED
694 )
695 )
697 with requests_session(token1) as api:
698 # 9 including initial message
699 res = api.GetHostRequestMessages(requests_pb2.GetHostRequestMessagesReq(host_request_id=conversation_id))
700 assert len(res.messages) == 9
701 assert res.no_more
703 res = api.GetHostRequestMessages(
704 requests_pb2.GetHostRequestMessagesReq(host_request_id=conversation_id, number=3)
705 )
706 assert not res.no_more
707 assert len(res.messages) == 3
708 assert res.messages[0].host_request_status_changed.status == conversations_pb2.HOST_REQUEST_STATUS_REJECTED
709 assert res.messages[0].WhichOneof("content") == "host_request_status_changed"
710 assert res.messages[1].text.text == "Test request 1 message 5"
711 assert res.messages[2].text.text == "Test request 1 message 4"
713 res = api.GetHostRequestMessages(
714 requests_pb2.GetHostRequestMessagesReq(
715 host_request_id=conversation_id,
716 last_message_id=res.messages[2].message_id,
717 number=6,
718 )
719 )
720 assert res.no_more
721 assert len(res.messages) == 6
722 assert res.messages[0].host_request_status_changed.status == conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED
723 assert res.messages[0].WhichOneof("content") == "host_request_status_changed"
724 assert res.messages[1].text.text == "Test request 1 message 3"
725 assert res.messages[2].text.text == "Test request 1 message 2"
726 assert res.messages[3].text.text == "Test request 1 message 1"
727 assert res.messages[4].text.text == valid_request_text("Test request 1")
728 assert res.messages[5].WhichOneof("content") == "chat_created"
731def test_SendHostRequestMessage(db, moderator):
732 user1, token1 = generate_user()
733 user2, token2 = generate_user()
734 user3, token3 = generate_user()
735 today_plus_2 = (today() + timedelta(days=2)).isoformat()
736 today_plus_3 = (today() + timedelta(days=3)).isoformat()
737 with requests_session(token1) as api:
738 host_request_id = api.CreateHostRequest(
739 requests_pb2.CreateHostRequestReq(
740 host_user_id=user2.id,
741 from_date=today_plus_2,
742 to_date=today_plus_3,
743 text=valid_request_text("Test request 1"),
744 )
745 ).host_request_id
747 moderator.approve_host_request(host_request_id)
749 with requests_session(token1) as api:
750 with pytest.raises(grpc.RpcError) as e:
751 api.SendHostRequestMessage(
752 requests_pb2.SendHostRequestMessageReq(host_request_id=999, text="Test message 1")
753 )
754 assert e.value.code() == grpc.StatusCode.NOT_FOUND
756 with pytest.raises(grpc.RpcError) as e:
757 api.SendHostRequestMessage(requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text=""))
758 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
759 assert e.value.details() == "Invalid message."
761 api.SendHostRequestMessage(
762 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 1")
763 )
764 res = api.GetHostRequestMessages(requests_pb2.GetHostRequestMessagesReq(host_request_id=host_request_id))
765 assert res.messages[0].text.text == "Test message 1"
766 assert res.messages[0].author_user_id == user1.id
768 with requests_session(token3) as api:
769 # other user can't send
770 with pytest.raises(grpc.RpcError) as e:
771 api.SendHostRequestMessage(
772 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 2")
773 )
774 assert e.value.code() == grpc.StatusCode.NOT_FOUND
775 assert e.value.details() == "Couldn't find that host request."
777 with requests_session(token2) as api:
778 api.SendHostRequestMessage(
779 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 2")
780 )
781 res = api.GetHostRequestMessages(requests_pb2.GetHostRequestMessagesReq(host_request_id=host_request_id))
782 # including 2 for creation control message and message
783 assert len(res.messages) == 4
784 assert res.messages[0].text.text == "Test message 2"
785 assert res.messages[0].author_user_id == user2.id
787 # CAN send messages to a rejected, confirmed or cancelled request, and for accepted
788 api.RespondHostRequest(
789 requests_pb2.RespondHostRequestReq(
790 host_request_id=host_request_id, status=conversations_pb2.HOST_REQUEST_STATUS_REJECTED
791 )
792 )
793 api.SendHostRequestMessage(
794 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 3")
795 )
797 api.RespondHostRequest(
798 requests_pb2.RespondHostRequestReq(
799 host_request_id=host_request_id, status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED
800 )
801 )
803 with requests_session(token1) as api:
804 api.RespondHostRequest(
805 requests_pb2.RespondHostRequestReq(
806 host_request_id=host_request_id, status=conversations_pb2.HOST_REQUEST_STATUS_CONFIRMED
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_CANCELLED
816 )
817 )
818 api.SendHostRequestMessage(
819 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 3")
820 )
823def test_get_updates(db, moderator):
824 user1, token1 = generate_user()
825 user2, token2 = generate_user()
826 user3, token3 = generate_user()
827 today_plus_2 = (today() + timedelta(days=2)).isoformat()
828 today_plus_3 = (today() + timedelta(days=3)).isoformat()
829 with requests_session(token1) as api:
830 host_request_id = api.CreateHostRequest(
831 requests_pb2.CreateHostRequestReq(
832 host_user_id=user2.id,
833 from_date=today_plus_2,
834 to_date=today_plus_3,
835 text=valid_request_text("Test message 0"),
836 )
837 ).host_request_id
839 moderator.approve_host_request(host_request_id)
841 with requests_session(token1) as api:
842 api.SendHostRequestMessage(
843 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 1")
844 )
845 api.SendHostRequestMessage(
846 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 2")
847 )
848 api.RespondHostRequest(
849 requests_pb2.RespondHostRequestReq(
850 host_request_id=host_request_id,
851 status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED,
852 text="Test message 3",
853 )
854 )
856 api.CreateHostRequest(
857 requests_pb2.CreateHostRequestReq(
858 host_user_id=user2.id,
859 from_date=today_plus_2,
860 to_date=today_plus_3,
861 text=valid_request_text("Test message 4"),
862 )
863 )
865 res = api.GetHostRequestMessages(requests_pb2.GetHostRequestMessagesReq(host_request_id=host_request_id))
866 assert len(res.messages) == 6
867 assert res.messages[0].text.text == "Test message 3"
868 assert res.messages[1].host_request_status_changed.status == conversations_pb2.HOST_REQUEST_STATUS_CANCELLED
869 assert res.messages[2].text.text == "Test message 2"
870 assert res.messages[3].text.text == "Test message 1"
871 assert res.messages[4].text.text == valid_request_text("Test message 0")
872 message_id_3 = res.messages[0].message_id
873 message_id_cancel = res.messages[1].message_id
874 message_id_2 = res.messages[2].message_id
875 message_id_1 = res.messages[3].message_id
876 message_id_0 = res.messages[4].message_id
878 with pytest.raises(grpc.RpcError) as e:
879 api.GetHostRequestUpdates(requests_pb2.GetHostRequestUpdatesReq(newest_message_id=0))
880 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
882 res = api.GetHostRequestUpdates(requests_pb2.GetHostRequestUpdatesReq(newest_message_id=message_id_1))
883 assert res.no_more
884 assert len(res.updates) == 5
885 assert res.updates[0].message.text.text == "Test message 2"
886 assert (
887 res.updates[1].message.host_request_status_changed.status == conversations_pb2.HOST_REQUEST_STATUS_CANCELLED
888 )
889 assert res.updates[1].status == conversations_pb2.HOST_REQUEST_STATUS_CANCELLED
890 assert res.updates[2].message.text.text == "Test message 3"
891 assert res.updates[3].message.WhichOneof("content") == "chat_created"
892 assert res.updates[3].status == conversations_pb2.HOST_REQUEST_STATUS_PENDING
893 assert res.updates[4].message.text.text == valid_request_text("Test message 4")
895 res = api.GetHostRequestUpdates(requests_pb2.GetHostRequestUpdatesReq(newest_message_id=message_id_1, number=1))
896 assert not res.no_more
897 assert len(res.updates) == 1
898 assert res.updates[0].message.text.text == "Test message 2"
899 assert res.updates[0].status == conversations_pb2.HOST_REQUEST_STATUS_CANCELLED
901 with requests_session(token3) as api:
902 # other user can't access
903 res = api.GetHostRequestUpdates(requests_pb2.GetHostRequestUpdatesReq(newest_message_id=message_id_1))
904 assert len(res.updates) == 0
907def test_archive_host_request(db, moderator):
908 user1, token1 = generate_user()
909 user2, token2 = generate_user()
911 today_plus_2 = (today() + timedelta(days=2)).isoformat()
912 today_plus_3 = (today() + timedelta(days=3)).isoformat()
914 with requests_session(token1) as api:
915 host_request_id = api.CreateHostRequest(
916 requests_pb2.CreateHostRequestReq(
917 host_user_id=user2.id,
918 from_date=today_plus_2,
919 to_date=today_plus_3,
920 text=valid_request_text("Test message 0"),
921 )
922 ).host_request_id
924 api.SendHostRequestMessage(
925 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 1")
926 )
927 api.SendHostRequestMessage(
928 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 2")
929 )
931 moderator.approve_host_request(host_request_id)
933 # happy path archiving host request
934 with requests_session(token1) as api:
935 api.RespondHostRequest(
936 requests_pb2.RespondHostRequestReq(
937 host_request_id=host_request_id,
938 status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED,
939 text="Test message 3",
940 )
941 )
942 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_sent=True))
943 assert len(res.host_requests) == 1
944 assert res.host_requests[0].status == conversations_pb2.HOST_REQUEST_STATUS_CANCELLED
945 api.SetHostRequestArchiveStatus(
946 requests_pb2.SetHostRequestArchiveStatusReq(host_request_id=host_request_id, is_archived=True)
947 )
948 res = api.ListHostRequests(requests_pb2.ListHostRequestsReq(only_archived=True))
949 assert len(res.host_requests) == 1
952def test_mark_last_seen(db, moderator):
953 user1, token1 = generate_user()
954 user2, token2 = generate_user()
955 user3, token3 = generate_user()
956 today_plus_2 = (today() + timedelta(days=2)).isoformat()
957 today_plus_3 = (today() + timedelta(days=3)).isoformat()
958 with requests_session(token1) as api:
959 host_request_id = api.CreateHostRequest(
960 requests_pb2.CreateHostRequestReq(
961 host_user_id=user2.id,
962 from_date=today_plus_2,
963 to_date=today_plus_3,
964 text=valid_request_text("Test message 0"),
965 )
966 ).host_request_id
968 host_request_id_2 = api.CreateHostRequest(
969 requests_pb2.CreateHostRequestReq(
970 host_user_id=user2.id,
971 from_date=today_plus_2,
972 to_date=today_plus_3,
973 text=valid_request_text("Test message 0a"),
974 )
975 ).host_request_id
977 moderator.approve_host_request(host_request_id)
978 moderator.approve_host_request(host_request_id_2)
980 with requests_session(token1) as api:
981 api.SendHostRequestMessage(
982 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 1")
983 )
984 api.SendHostRequestMessage(
985 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id, text="Test message 2")
986 )
987 api.RespondHostRequest(
988 requests_pb2.RespondHostRequestReq(
989 host_request_id=host_request_id,
990 status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED,
991 text="Test message 3",
992 )
993 )
995 moderator.approve_host_request(host_request_id)
996 moderator.approve_host_request(host_request_id_2)
998 # test Ping unseen host request count, should be automarked after sending
999 with api_session(token1) as api:
1000 assert api.Ping(api_pb2.PingReq()).unseen_received_host_request_count == 0
1001 assert api.Ping(api_pb2.PingReq()).unseen_sent_host_request_count == 0
1003 with api_session(token2) as api:
1004 assert api.Ping(api_pb2.PingReq()).unseen_received_host_request_count == 2
1005 assert api.Ping(api_pb2.PingReq()).unseen_sent_host_request_count == 0
1007 with requests_session(token2) as api:
1008 assert api.ListHostRequests(requests_pb2.ListHostRequestsReq()).host_requests[0].last_seen_message_id == 0
1010 api.MarkLastSeenHostRequest(
1011 requests_pb2.MarkLastSeenHostRequestReq(host_request_id=host_request_id, last_seen_message_id=3)
1012 )
1014 assert api.ListHostRequests(requests_pb2.ListHostRequestsReq()).host_requests[0].last_seen_message_id == 3
1016 with pytest.raises(grpc.RpcError) as e:
1017 api.MarkLastSeenHostRequest(
1018 requests_pb2.MarkLastSeenHostRequestReq(host_request_id=host_request_id, last_seen_message_id=1)
1019 )
1020 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
1021 assert e.value.details() == "You can't unsee messages."
1023 # this will be used to test sent request notifications
1024 host_request_id_3 = api.CreateHostRequest(
1025 requests_pb2.CreateHostRequestReq(
1026 host_user_id=user1.id,
1027 from_date=today_plus_2,
1028 to_date=today_plus_3,
1029 text=valid_request_text("Another test request"),
1030 )
1031 ).host_request_id
1033 moderator.approve_host_request(host_request_id_3)
1035 with requests_session(token2) as api:
1036 # this should make id_2 all read
1037 api.SendHostRequestMessage(
1038 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id_2, text="Test")
1039 )
1041 with api_session(token2) as api:
1042 assert api.Ping(api_pb2.PingReq()).unseen_received_host_request_count == 1
1043 assert api.Ping(api_pb2.PingReq()).unseen_sent_host_request_count == 0
1045 # make sure sent and received count for unseen notifications
1046 with requests_session(token1) as api:
1047 api.SendHostRequestMessage(
1048 requests_pb2.SendHostRequestMessageReq(host_request_id=host_request_id_3, text="Test message")
1049 )
1051 with api_session(token2) as api:
1052 assert api.Ping(api_pb2.PingReq()).unseen_received_host_request_count == 1
1053 assert api.Ping(api_pb2.PingReq()).unseen_sent_host_request_count == 1
1056def test_response_rate(db, moderator):
1057 user1, token1 = generate_user()
1058 user2, token2 = generate_user()
1059 user3, token3 = generate_user(delete_user=True)
1061 today_plus_2 = (today() + timedelta(days=2)).isoformat()
1062 today_plus_3 = (today() + timedelta(days=3)).isoformat()
1064 with session_scope() as session:
1065 refresh_materialized_view(session, "user_response_rates")
1067 with requests_session(token1) as api:
1068 # deleted: not found
1069 with pytest.raises(grpc.RpcError) as e:
1070 api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user3.id))
1071 assert e.value.code() == grpc.StatusCode.NOT_FOUND
1072 assert e.value.details() == "Couldn't find that user."
1074 # no requests: insufficient
1075 res = api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user2.id))
1076 assert res.HasField("insufficient_data")
1078 # send a request and back date it by 36 hours
1079 host_request_1 = api.CreateHostRequest(
1080 requests_pb2.CreateHostRequestReq(
1081 host_user_id=user2.id,
1082 from_date=today_plus_2,
1083 to_date=today_plus_3,
1084 text=valid_request_text("Test request"),
1085 )
1086 ).host_request_id
1087 moderator.approve_host_request(host_request_1)
1088 with session_scope() as session:
1089 session.execute(
1090 select(Message)
1091 .where(Message.conversation_id == host_request_1)
1092 .where(Message.message_type == MessageType.chat_created)
1093 ).scalar_one().time = now() - timedelta(hours=36)
1094 refresh_materialized_view(session, "user_response_rates")
1096 # still insufficient
1097 res = api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user2.id))
1098 assert res.HasField("insufficient_data")
1100 # send a request and back date it by 35 hours
1101 host_request_2 = api.CreateHostRequest(
1102 requests_pb2.CreateHostRequestReq(
1103 host_user_id=user2.id,
1104 from_date=today_plus_2,
1105 to_date=today_plus_3,
1106 text=valid_request_text("Test request"),
1107 )
1108 ).host_request_id
1109 moderator.approve_host_request(host_request_2)
1110 with session_scope() as session:
1111 session.execute(
1112 select(Message)
1113 .where(Message.conversation_id == host_request_2)
1114 .where(Message.message_type == MessageType.chat_created)
1115 ).scalar_one().time = now() - timedelta(hours=35)
1116 refresh_materialized_view(session, "user_response_rates")
1118 # still insufficient
1119 res = api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user2.id))
1120 assert res.HasField("insufficient_data")
1122 # send a request and back date it by 34 hours
1123 host_request_3 = api.CreateHostRequest(
1124 requests_pb2.CreateHostRequestReq(
1125 host_user_id=user2.id,
1126 from_date=today_plus_2,
1127 to_date=today_plus_3,
1128 text=valid_request_text("Test request"),
1129 )
1130 ).host_request_id
1131 moderator.approve_host_request(host_request_3)
1132 with session_scope() as session:
1133 session.execute(
1134 select(Message)
1135 .where(Message.conversation_id == host_request_3)
1136 .where(Message.message_type == MessageType.chat_created)
1137 ).scalar_one().time = now() - timedelta(hours=34)
1138 refresh_materialized_view(session, "user_response_rates")
1140 # now low
1141 res = api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user2.id))
1142 assert res.HasField("low")
1144 with requests_session(token2) as api:
1145 # accept a host req
1146 api.RespondHostRequest(
1147 requests_pb2.RespondHostRequestReq(
1148 host_request_id=host_request_2,
1149 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED,
1150 text="Accepting host request",
1151 )
1152 )
1154 with session_scope() as session:
1155 refresh_materialized_view(session, "user_response_rates")
1157 with requests_session(token1) as api:
1158 # now some w p33 = 35h
1159 res = api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user2.id))
1160 assert res.HasField("some")
1161 assert res.some.response_time_p33.ToTimedelta() == timedelta(hours=35)
1163 with requests_session(token2) as api:
1164 # accept another host req
1165 api.RespondHostRequest(
1166 requests_pb2.RespondHostRequestReq(
1167 host_request_id=host_request_3,
1168 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED,
1169 text="Accepting host request",
1170 )
1171 )
1173 with session_scope() as session:
1174 refresh_materialized_view(session, "user_response_rates")
1176 with requests_session(token1) as api:
1177 # now most w p33 = 34h, p66 = 35h
1178 res = api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user2.id))
1179 assert res.HasField("most")
1180 assert res.most.response_time_p33.ToTimedelta() == timedelta(hours=34)
1181 assert res.most.response_time_p66.ToTimedelta() == timedelta(hours=35)
1183 with requests_session(token2) as api:
1184 # accept last host req
1185 api.RespondHostRequest(
1186 requests_pb2.RespondHostRequestReq(
1187 host_request_id=host_request_1,
1188 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED,
1189 text="Accepting host request",
1190 )
1191 )
1193 with session_scope() as session:
1194 refresh_materialized_view(session, "user_response_rates")
1196 with requests_session(token1) as api:
1197 # now all w p33 = 34h, p66 = 35h
1198 res = api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user2.id))
1199 assert res.HasField("almost_all")
1200 assert res.almost_all.response_time_p33.ToTimedelta() == timedelta(hours=34)
1201 assert res.almost_all.response_time_p66.ToTimedelta() == timedelta(hours=35)
1203 # send a request and back date it by 2 hours
1204 host_request_4 = api.CreateHostRequest(
1205 requests_pb2.CreateHostRequestReq(
1206 host_user_id=user2.id,
1207 from_date=today_plus_2,
1208 to_date=today_plus_3,
1209 text=valid_request_text("Test request"),
1210 )
1211 ).host_request_id
1212 moderator.approve_host_request(host_request_4)
1213 with session_scope() as session:
1214 session.execute(
1215 select(Message)
1216 .where(Message.conversation_id == host_request_4)
1217 .where(Message.message_type == MessageType.chat_created)
1218 ).scalar_one().time = now() - timedelta(hours=2)
1219 refresh_materialized_view(session, "user_response_rates")
1221 # send a request and back date it by 4 hours
1222 host_request_5 = api.CreateHostRequest(
1223 requests_pb2.CreateHostRequestReq(
1224 host_user_id=user2.id,
1225 from_date=today_plus_2,
1226 to_date=today_plus_3,
1227 text=valid_request_text("Test request"),
1228 )
1229 ).host_request_id
1230 moderator.approve_host_request(host_request_5)
1231 with session_scope() as session:
1232 session.execute(
1233 select(Message)
1234 .where(Message.conversation_id == host_request_5)
1235 .where(Message.message_type == MessageType.chat_created)
1236 ).scalar_one().time = now() - timedelta(hours=4)
1237 refresh_materialized_view(session, "user_response_rates")
1239 # now some w p33 = 35h
1240 res = api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user2.id))
1241 assert res.HasField("some")
1242 assert res.some.response_time_p33.ToTimedelta() == timedelta(hours=35)
1244 with requests_session(token2) as api:
1245 # accept host req
1246 api.RespondHostRequest(
1247 requests_pb2.RespondHostRequestReq(
1248 host_request_id=host_request_5,
1249 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED,
1250 text="Accepting host request",
1251 )
1252 )
1254 with session_scope() as session:
1255 refresh_materialized_view(session, "user_response_rates")
1257 with requests_session(token1) as api:
1258 # now most w p33 = 34h, p66 = 36h
1259 res = api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user2.id))
1260 assert res.HasField("most")
1261 assert res.most.response_time_p33.ToTimedelta() == timedelta(hours=34)
1262 assert res.most.response_time_p66.ToTimedelta() == timedelta(hours=36)
1264 with requests_session(token2) as api:
1265 # accept host req
1266 api.RespondHostRequest(
1267 requests_pb2.RespondHostRequestReq(
1268 host_request_id=host_request_4,
1269 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED,
1270 text="Accepting host request",
1271 )
1272 )
1274 with session_scope() as session:
1275 refresh_materialized_view(session, "user_response_rates")
1277 with requests_session(token1) as api:
1278 # now most w p33 = 4h, p66 = 35h
1279 res = api.GetResponseRate(requests_pb2.GetResponseRateReq(user_id=user2.id))
1280 assert res.HasField("almost_all")
1281 assert res.almost_all.response_time_p33.ToTimedelta() == timedelta(hours=4)
1282 assert res.almost_all.response_time_p66.ToTimedelta() == timedelta(hours=35)
1285def test_request_notifications(db, push_collector, moderator):
1286 host, host_token = generate_user(complete_profile=True)
1287 surfer, surfer_token = generate_user(complete_profile=True)
1289 today_plus_2 = (today() + timedelta(days=2)).isoformat()
1290 today_plus_3 = (today() + timedelta(days=3)).isoformat()
1292 with requests_session(surfer_token) as api:
1293 hr_id = api.CreateHostRequest(
1294 requests_pb2.CreateHostRequestReq(
1295 host_user_id=host.id,
1296 from_date=today_plus_2,
1297 to_date=today_plus_3,
1298 text=valid_request_text("can i stay plz"),
1299 )
1300 ).host_request_id
1302 with mock_notification_email() as mock:
1303 moderator.approve_host_request(hr_id)
1305 mock.assert_called_once()
1306 e = email_fields(mock)
1307 assert e.recipient == host.email
1308 assert "host request" in e.subject.lower()
1309 assert host.name in e.plain
1310 assert host.name in e.html
1311 assert "quick decline" in e.plain.lower(), e.plain
1312 assert "quick decline" in e.html.lower()
1313 assert surfer.name in e.plain
1314 assert surfer.name in e.html
1315 assert v2date(today_plus_2, host) in e.plain
1316 assert v2date(today_plus_2, host) in e.html
1317 assert v2date(today_plus_3, host) in e.plain
1318 assert v2date(today_plus_3, host) in e.html
1319 assert "http://localhost:5001/img/thumbnail/" not in e.plain
1320 assert "http://localhost:5001/img/thumbnail/" in e.html
1321 assert f"http://localhost:3000/messages/request/{hr_id}" in e.plain
1322 assert f"http://localhost:3000/messages/request/{hr_id}" in e.html
1324 push_collector.assert_user_has_single_matching(
1325 host.id,
1326 title=f"{surfer.name} sent you a host request",
1327 )
1329 with requests_session(host_token) as api:
1330 with mock_notification_email() as mock:
1331 api.RespondHostRequest(
1332 requests_pb2.RespondHostRequestReq(
1333 host_request_id=hr_id,
1334 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED,
1335 text="Accepting host request",
1336 )
1337 )
1339 e = email_fields(mock)
1340 assert e.recipient == surfer.email
1341 assert "host request" in e.subject.lower()
1342 assert host.name in e.plain
1343 assert host.name in e.html
1344 assert surfer.name in e.plain
1345 assert surfer.name in e.html
1346 assert v2date(today_plus_2, surfer) in e.plain
1347 assert v2date(today_plus_2, surfer) in e.html
1348 assert v2date(today_plus_3, surfer) in e.plain
1349 assert v2date(today_plus_3, surfer) in e.html
1350 assert "http://localhost:5001/img/thumbnail/" not in e.plain
1351 assert "http://localhost:5001/img/thumbnail/" in e.html
1352 assert f"http://localhost:3000/messages/request/{hr_id}" in e.plain
1353 assert f"http://localhost:3000/messages/request/{hr_id}" in e.html
1355 push_collector.assert_user_has_single_matching(
1356 surfer.id,
1357 title=f"{host.name} accepted your host request",
1358 )
1361def test_quick_decline(db, push_collector, moderator):
1362 host, host_token = generate_user(complete_profile=True)
1363 surfer, surfer_token = generate_user(complete_profile=True)
1365 today_plus_2 = (today() + timedelta(days=2)).isoformat()
1366 today_plus_3 = (today() + timedelta(days=3)).isoformat()
1368 with requests_session(surfer_token) as api:
1369 hr_id = api.CreateHostRequest(
1370 requests_pb2.CreateHostRequestReq(
1371 host_user_id=host.id,
1372 from_date=today_plus_2,
1373 to_date=today_plus_3,
1374 text=valid_request_text("can i stay plz"),
1375 )
1376 ).host_request_id
1378 with mock_notification_email() as mock:
1379 moderator.approve_host_request(hr_id)
1381 mock.assert_called_once()
1382 e = email_fields(mock)
1383 assert e.recipient == host.email
1384 assert "host request" in e.subject.lower()
1385 assert host.name in e.plain
1386 assert host.name in e.html
1387 assert "quick decline" in e.plain.lower(), e.plain
1388 assert "quick decline" in e.html.lower()
1389 assert surfer.name in e.plain
1390 assert surfer.name in e.html
1391 assert v2date(today_plus_2, host) in e.plain
1392 assert v2date(today_plus_2, host) in e.html
1393 assert v2date(today_plus_3, host) in e.plain
1394 assert v2date(today_plus_3, host) in e.html
1395 assert "http://localhost:5001/img/thumbnail/" not in e.plain
1396 assert "http://localhost:5001/img/thumbnail/" in e.html
1397 assert f"http://localhost:3000/messages/request/{hr_id}" in e.plain
1398 assert f"http://localhost:3000/messages/request/{hr_id}" in e.html
1400 push_collector.assert_user_has_single_matching(
1401 host.id,
1402 title=f"{surfer.name} sent you a host request",
1403 )
1405 # very ugly
1406 # http://localhost:3000/quick-link?payload=CAEiGAoOZnJpZW5kX3JlcXVlc3QSBmFjY2VwdA==&sig=BQdk024NTATm8zlR0krSXTBhP5U9TlFv7VhJeIHZtUg=
1407 for link in re.findall(r'<a href="(.*?)"', email_fields(mock).html):
1408 if "payload" not in link:
1409 continue
1410 print(link)
1411 url_parts = urlparse(link)
1412 params = parse_qs(url_parts.query)
1413 print(params["payload"][0])
1414 payload = unsubscribe_pb2.UnsubscribePayload.FromString(b64decode(params["payload"][0]))
1415 if payload.HasField("host_request_quick_decline"):
1416 with auth_api_session() as (auth_api, metadata_interceptor):
1417 res = auth_api.Unsubscribe(
1418 auth_pb2.UnsubscribeReq(
1419 payload=b64decode(params["payload"][0]),
1420 sig=b64decode(params["sig"][0]),
1421 )
1422 )
1423 assert res.response == "Thank you for responding to the host request!"
1424 break
1425 else:
1426 raise Exception("Didn't find link")
1428 with requests_session(surfer_token) as api:
1429 res = api.GetHostRequest(requests_pb2.GetHostRequestReq(host_request_id=hr_id))
1430 assert res.status == conversations_pb2.HOST_REQUEST_STATUS_REJECTED
1433def test_host_req_feedback(db, moderator):
1434 host, host_token = generate_user(complete_profile=True)
1435 host2, host2_token = generate_user(complete_profile=True)
1436 host3, host3_token = generate_user(complete_profile=True)
1437 surfer, surfer_token = generate_user(complete_profile=True)
1439 today_plus_2 = (today() + timedelta(days=2)).isoformat()
1440 today_plus_3 = (today() + timedelta(days=3)).isoformat()
1442 with requests_session(surfer_token) as api:
1443 hr_id = api.CreateHostRequest(
1444 requests_pb2.CreateHostRequestReq(
1445 host_user_id=host.id,
1446 from_date=today_plus_2,
1447 to_date=today_plus_3,
1448 text=valid_request_text("can i stay plz"),
1449 )
1450 ).host_request_id
1451 hr2_id = api.CreateHostRequest(
1452 requests_pb2.CreateHostRequestReq(
1453 host_user_id=host2.id,
1454 from_date=today_plus_2,
1455 to_date=today_plus_3,
1456 text=valid_request_text("can i stay plz"),
1457 )
1458 ).host_request_id
1459 hr3_id = api.CreateHostRequest(
1460 requests_pb2.CreateHostRequestReq(
1461 host_user_id=host3.id,
1462 from_date=today_plus_2,
1463 to_date=today_plus_3,
1464 text=valid_request_text("can i stay plz"),
1465 )
1466 ).host_request_id
1468 moderator.approve_host_request(hr_id)
1469 moderator.approve_host_request(hr2_id)
1470 moderator.approve_host_request(hr3_id)
1472 with requests_session(host_token) as api:
1473 res = api.GetHostRequest(requests_pb2.GetHostRequestReq(host_request_id=hr_id))
1474 assert not res.need_host_request_feedback
1476 api.RespondHostRequest(
1477 requests_pb2.RespondHostRequestReq(
1478 host_request_id=hr_id,
1479 status=conversations_pb2.HOST_REQUEST_STATUS_REJECTED,
1480 )
1481 )
1483 res = api.GetHostRequest(requests_pb2.GetHostRequestReq(host_request_id=hr_id))
1484 assert res.need_host_request_feedback
1486 # surfer can't leave feedback
1487 with requests_session(surfer_token) as api:
1488 with pytest.raises(grpc.RpcError) as e:
1489 api.SendHostRequestFeedback(
1490 requests_pb2.SendHostRequestFeedbackReq(
1491 host_request_id=hr_id,
1492 )
1493 )
1494 assert e.value.code() == grpc.StatusCode.NOT_FOUND
1495 assert e.value.details() == "Couldn't find that host request."
1497 with requests_session(host_token) as api:
1498 api.SendHostRequestFeedback(
1499 requests_pb2.SendHostRequestFeedbackReq(
1500 host_request_id=hr_id,
1501 host_request_quality=requests_pb2.HOST_REQUEST_QUALITY_LOW,
1502 )
1503 )
1504 res = api.GetHostRequest(requests_pb2.GetHostRequestReq(host_request_id=hr_id))
1505 assert not res.need_host_request_feedback
1507 # can't leave it twice
1508 with requests_session(host_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.FAILED_PRECONDITION
1516 assert e.value.details() == "You have already left feedback for this host request!"
1518 res = api.GetHostRequest(requests_pb2.GetHostRequestReq(host_request_id=hr_id))
1519 assert not res.need_host_request_feedback
1521 with requests_session(host2_token) as api:
1522 api.RespondHostRequest(
1523 requests_pb2.RespondHostRequestReq(
1524 host_request_id=hr2_id, status=conversations_pb2.HOST_REQUEST_STATUS_REJECTED
1525 )
1526 )
1527 # can't leave feedback on the wrong one
1528 with pytest.raises(grpc.RpcError) as e:
1529 api.SendHostRequestFeedback(
1530 requests_pb2.SendHostRequestFeedbackReq(
1531 host_request_id=hr_id,
1532 )
1533 )
1534 assert e.value.code() == grpc.StatusCode.NOT_FOUND
1535 assert e.value.details() == "Couldn't find that host request."
1537 # null feedback is still feedback
1538 api.SendHostRequestFeedback(requests_pb2.SendHostRequestFeedbackReq(host_request_id=hr2_id))
1540 with requests_session(host3_token) as api:
1541 api.RespondHostRequest(
1542 requests_pb2.RespondHostRequestReq(
1543 host_request_id=hr3_id, status=conversations_pb2.HOST_REQUEST_STATUS_REJECTED
1544 )
1545 )
1547 api.SendHostRequestFeedback(
1548 requests_pb2.SendHostRequestFeedbackReq(host_request_id=hr3_id, decline_reason="bad req")
1549 )