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