Coverage for app / backend / src / tests / test_api.py: 100%
1112 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
1from datetime import timedelta
3import grpc
4import pytest
5from google.protobuf import empty_pb2, wrappers_pb2
6from sqlalchemy import select, update
8from couchers.db import session_scope
9from couchers.jobs.handlers import update_badges
10from couchers.materialized_views import refresh_materialized_views_rapid
11from couchers.models import FriendRelationship, FriendStatus, LanguageFluency, RateLimitAction, User
12from couchers.proto import admin_pb2, api_pb2, blocking_pb2, jail_pb2, notifications_pb2
13from couchers.rate_limits.definitions import RATE_LIMIT_DEFINITIONS, RATE_LIMIT_HOURS
14from couchers.resources import get_badge_dict
15from couchers.utils import create_coordinate, to_aware_datetime
16from tests.fixtures.db import generate_user, make_friends, make_user_block
17from tests.fixtures.misc import PushCollector, email_fields, mock_notification_email
18from tests.fixtures.sessions import (
19 api_session,
20 blocking_session,
21 notifications_session,
22 real_api_session,
23 real_jail_session,
24)
25from tests.fixtures.sessions import (
26 real_admin_session as admin_session,
27)
30@pytest.fixture(autouse=True)
31def _(testconfig):
32 pass
35def test_ping(db):
36 user, token = generate_user(
37 regions_lived=["ESP", "FRA", "EST"],
38 regions_visited=["CHE", "REU", "FIN"],
39 language_abilities=[
40 ("fin", LanguageFluency.fluent),
41 ("fra", LanguageFluency.beginner),
42 ],
43 )
45 with real_api_session(token) as api:
46 res = api.Ping(api_pb2.PingReq())
48 assert res.user.user_id == user.id
49 assert res.user.username == user.username
50 assert res.user.name == user.name
51 assert res.user.city == user.city
52 assert res.user.hometown == user.hometown
53 assert res.user.verification == 0.0
54 assert res.user.community_standing == user.community_standing
55 assert res.user.num_references == 0
56 assert res.user.gender == user.gender
57 assert res.user.pronouns == user.pronouns
58 assert res.user.age == user.age
60 assert (res.user.lat, res.user.lng) == user.coordinates
62 # the joined time is fuzzed
63 # but shouldn't be before actual joined time, or more than one hour behind
64 assert user.joined - timedelta(hours=1) <= to_aware_datetime(res.user.joined) <= user.joined
65 # same for last_active
66 assert user.last_active - timedelta(hours=1) <= to_aware_datetime(res.user.last_active) <= user.last_active
68 assert res.user.hosting_status == api_pb2.HOSTING_STATUS_CANT_HOST
69 assert res.user.meetup_status == api_pb2.MEETUP_STATUS_OPEN_TO_MEETUP
71 assert res.user.occupation == user.occupation
72 assert res.user.education == user.education
73 assert res.user.about_me == user.about_me
74 assert res.user.things_i_like == user.things_i_like
75 assert {language_ability.code for language_ability in res.user.language_abilities} == {"fin", "fra"}
76 assert res.user.about_place == user.about_place
77 assert res.user.regions_visited == ["FIN", "REU", "CHE"] # Tests alphabetization by region name
78 assert res.user.regions_lived == ["EST", "FRA", "ESP"] # Ditto
79 assert res.user.additional_information == user.additional_information
81 assert res.user.friends == api_pb2.User.FriendshipStatus.NA
82 assert not res.user.HasField("pending_friend_request")
85def test_coords(db):
86 # make them need to update location
87 user1, token1 = generate_user(geom=create_coordinate(1, 0), geom_radius=2000, needs_to_update_location=True)
88 user2, token2 = generate_user()
90 with api_session(token2) as api:
91 res = api.Ping(api_pb2.PingReq())
92 assert res.user.city == user2.city
93 lat, lng = user2.coordinates
94 assert res.user.lat == lat
95 assert res.user.lng == lng
96 assert res.user.radius == user2.geom_radius
98 with api_session(token2) as api:
99 res = api.GetUser(api_pb2.GetUserReq(user=user1.username))
100 assert res.city == user1.city
101 assert res.lat == 1.0
102 assert res.lng == 0.0
103 assert res.radius == 2000.0
105 # Check coordinate wrapping
106 user3, token3 = generate_user(geom=create_coordinate(40.0, -180.5))
107 user4, token4 = generate_user(geom=create_coordinate(40.0, 20.0))
108 user5, token5 = generate_user(geom=create_coordinate(90.5, 20.0))
110 with api_session(token3) as api:
111 res = api.GetUser(api_pb2.GetUserReq(user=user3.username))
112 assert res.lat == 40.0
113 assert res.lng == 179.5
115 with api_session(token4) as api:
116 res = api.GetUser(api_pb2.GetUserReq(user=user4.username))
117 assert res.lat == 40.0
118 assert res.lng == 20.0
120 # PostGIS does not wrap longitude for latitude overflow
121 with api_session(token5) as api:
122 res = api.GetUser(api_pb2.GetUserReq(user=user5.username))
123 assert res.lat == 89.5
124 assert res.lng == 20.0
126 with real_jail_session(token1) as jail:
127 res = jail.JailInfo(empty_pb2.Empty())
128 assert res.jailed
129 assert res.needs_to_update_location
131 res = jail.SetLocation(
132 jail_pb2.SetLocationReq(
133 city="New York City",
134 lat=40.7812,
135 lng=-73.9647,
136 radius=250,
137 )
138 )
140 assert not res.jailed
141 assert not res.needs_to_update_location
143 res = jail.JailInfo(empty_pb2.Empty())
144 assert not res.jailed
145 assert not res.needs_to_update_location
147 with api_session(token2) as api:
148 res = api.GetUser(api_pb2.GetUserReq(user=user1.username))
149 assert res.city == "New York City"
150 assert res.lat == 40.7812
151 assert res.lng == -73.9647
152 assert res.radius == 250
155def test_get_user(db):
156 user1, token1 = generate_user()
157 user2, token2 = generate_user()
159 with api_session(token1) as api:
160 res = api.GetUser(api_pb2.GetUserReq(user=user2.username))
161 assert res.user_id == user2.id
162 assert res.username == user2.username
163 assert res.name == user2.name
165 with api_session(token1) as api:
166 res = api.GetUser(api_pb2.GetUserReq(user=str(user2.id)))
167 assert res.user_id == user2.id
168 assert res.username == user2.username
169 assert res.name == user2.name
172@pytest.mark.parametrize("flag", ["is_deleted", "is_banned"])
173def test_user_model_to_pb_ghost_user(db, flag):
174 user1, token1 = generate_user()
175 user2, _ = generate_user()
177 with session_scope() as session:
178 session.execute(update(User).where(User.id == user2.id).values(**{flag: True}))
180 refresh_materialized_views_rapid(empty_pb2.Empty())
182 with api_session(token1) as api:
183 user_pb = api.GetUser(api_pb2.GetUserReq(user=user2.username))
185 assert user_pb.user_id == user2.id
186 assert user_pb.is_ghost
187 assert user_pb.username == "ghost"
188 assert user_pb.name == "Deactivated Account"
189 assert (
190 user_pb.about_me
191 == "This user is no longer on the platform. They may have deleted their account, been blocked, or been banned. We recommend exercising caution with any further interaction with this user off the platform. You can always reach out to support if you need any help."
192 )
194 assert user_pb.lat == 0
195 assert user_pb.lng == 0
196 assert user_pb.radius == 0
197 assert user_pb.verification == 0.0
198 assert user_pb.community_standing == 0.0
199 assert user_pb.num_references == 0
200 assert user_pb.age == 0
201 assert user_pb.hosting_status == 0
202 assert user_pb.meetup_status == 0
203 assert user_pb.city == ""
204 assert user_pb.hometown == ""
205 assert user_pb.timezone == ""
206 assert user_pb.gender == ""
207 assert user_pb.pronouns == ""
208 assert user_pb.occupation == ""
209 assert user_pb.education == ""
210 assert user_pb.things_i_like == ""
211 assert user_pb.about_place == ""
212 assert user_pb.additional_information == ""
213 assert list(user_pb.language_abilities) == []
214 assert list(user_pb.regions_visited) == []
215 assert list(user_pb.regions_lived) == []
216 assert list(user_pb.badges) == []
217 assert user_pb.friends == api_pb2.User.FriendshipStatus.NOT_FRIENDS
218 assert user_pb.avatar_url == ""
219 assert user_pb.avatar_thumbnail_url == ""
220 assert not user_pb.has_strong_verification
222 with api_session(token1) as api:
223 lite_user_pb = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user2.username))
225 assert lite_user_pb.user_id == user2.id
226 assert lite_user_pb.is_ghost
227 assert lite_user_pb.username == "ghost"
228 assert lite_user_pb.name == "Deactivated Account"
229 assert lite_user_pb.city == ""
230 assert lite_user_pb.age == 0
231 assert lite_user_pb.avatar_url == ""
232 assert lite_user_pb.avatar_thumbnail_url == ""
233 assert lite_user_pb.lat == 0
234 assert lite_user_pb.lng == 0
235 assert lite_user_pb.radius == 0
236 assert not lite_user_pb.has_strong_verification
239def test_user_model_to_pb_ghost_user_blocked(db):
240 user1, token1 = generate_user()
241 user2, _ = generate_user()
243 with blocking_session(token1) as user_blocks:
244 user_blocks.BlockUser(blocking_pb2.BlockUserReq(username=user2.username))
246 refresh_materialized_views_rapid(empty_pb2.Empty())
248 with api_session(token1) as api:
249 user_pb = api.GetUser(api_pb2.GetUserReq(user=user2.username))
251 assert user_pb.user_id == user2.id
252 assert user_pb.is_ghost
253 assert user_pb.username == "ghost"
254 assert user_pb.name == "Deactivated Account"
255 assert (
256 user_pb.about_me
257 == "This user is no longer on the platform. They may have deleted their account, been blocked, or been banned. We recommend exercising caution with any further interaction with this user off the platform. You can always reach out to support if you need any help."
258 )
260 assert user_pb.lat == 0
261 assert user_pb.lng == 0
262 assert user_pb.radius == 0
263 assert user_pb.verification == 0.0
264 assert user_pb.community_standing == 0.0
265 assert user_pb.num_references == 0
266 assert user_pb.age == 0
267 assert user_pb.hosting_status == 0
268 assert user_pb.meetup_status == 0
269 assert user_pb.city == ""
270 assert user_pb.hometown == ""
271 assert user_pb.timezone == ""
272 assert user_pb.gender == ""
273 assert user_pb.pronouns == ""
274 assert user_pb.occupation == ""
275 assert user_pb.education == ""
276 assert user_pb.things_i_like == ""
277 assert user_pb.about_place == ""
278 assert user_pb.additional_information == ""
279 assert list(user_pb.language_abilities) == []
280 assert list(user_pb.regions_visited) == []
281 assert list(user_pb.regions_lived) == []
282 assert list(user_pb.badges) == []
283 assert user_pb.friends == api_pb2.User.FriendshipStatus.NOT_FRIENDS
284 assert user_pb.avatar_url == ""
285 assert user_pb.avatar_thumbnail_url == ""
286 assert not user_pb.has_strong_verification
288 with api_session(token1) as api:
289 lite_user_pb = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user2.username))
291 assert lite_user_pb.user_id == user2.id
292 assert lite_user_pb.is_ghost
293 assert lite_user_pb.username == "ghost"
294 assert lite_user_pb.name == "Deactivated Account"
295 assert lite_user_pb.city == ""
296 assert lite_user_pb.age == 0
297 assert lite_user_pb.avatar_url == ""
298 assert lite_user_pb.avatar_thumbnail_url == ""
299 assert lite_user_pb.lat == 0
300 assert lite_user_pb.lng == 0
301 assert lite_user_pb.radius == 0
302 assert not lite_user_pb.has_strong_verification
305@pytest.mark.parametrize("flag", ["is_deleted", "is_banned"])
306def test_admin_viewing_ghost_users_sees_full_profile(db, flag):
307 admin, token_admin = generate_user(is_superuser=True)
308 user, _ = generate_user()
310 with session_scope() as session:
311 session.execute(update(User).where(User.id == user.id).values(**{flag: True}))
313 with admin_session(token_admin) as api:
314 user_pb = api.GetUser(admin_pb2.GetUserReq(user=user.username))
316 assert user_pb.user_id == user.id
317 assert user_pb.username == user.username
318 assert user_pb.name == user.name
319 assert user_pb.city == user.city
320 assert user_pb.name != "Deactivated Account"
321 assert user_pb.username != "ghost"
322 assert user_pb.hosting_status in (
323 api_pb2.HOSTING_STATUS_UNKNOWN,
324 api_pb2.HOSTING_STATUS_CAN_HOST,
325 api_pb2.HOSTING_STATUS_MAYBE,
326 api_pb2.HOSTING_STATUS_CANT_HOST,
327 )
330def test_lite_coords(db):
331 # make them need to update location
332 user1, token1 = generate_user(geom=create_coordinate(0, 0), geom_radius=0, needs_to_update_location=True)
333 user2, token2 = generate_user()
335 refresh_materialized_views_rapid(empty_pb2.Empty())
337 with api_session(token2) as api:
338 res = api.Ping(api_pb2.PingReq())
339 assert res.user.city == user2.city
340 lat, lng = user2.coordinates
341 assert res.user.lat == lat
342 assert res.user.lng == lng
343 assert res.user.radius == user2.geom_radius
345 with api_session(token2) as api:
346 res = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user1.username))
347 assert res.city == user1.city
348 assert res.lat == 0.0
349 assert res.lng == 0.0
350 assert res.radius == 0.0
352 # Check coordinate wrapping
353 user3, token3 = generate_user(geom=create_coordinate(40.0, -180.5))
354 user4, token4 = generate_user(geom=create_coordinate(40.0, 20.0))
355 user5, token5 = generate_user(geom=create_coordinate(90.5, 20.0))
357 refresh_materialized_views_rapid(empty_pb2.Empty())
359 with api_session(token3) as api:
360 res = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user3.username))
361 assert res.lat == 40.0
362 assert res.lng == 179.5
364 with api_session(token4) as api:
365 res = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user4.username))
366 assert res.lat == 40.0
367 assert res.lng == 20.0
369 # PostGIS does not wrap longitude for latitude overflow
370 with api_session(token5) as api:
371 res = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user5.username))
372 assert res.lat == 89.5
373 assert res.lng == 20.0
375 with real_jail_session(token1) as jail:
376 res = jail.JailInfo(empty_pb2.Empty())
377 assert res.jailed
378 assert res.needs_to_update_location
380 res = jail.SetLocation(
381 jail_pb2.SetLocationReq(
382 city="New York City",
383 lat=40.7812,
384 lng=-73.9647,
385 radius=250,
386 )
387 )
389 assert not res.jailed
390 assert not res.needs_to_update_location
392 res = jail.JailInfo(empty_pb2.Empty())
393 assert not res.jailed
394 assert not res.needs_to_update_location
396 refresh_materialized_views_rapid(empty_pb2.Empty())
398 with api_session(token2) as api:
399 res = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user1.username))
400 assert res.city == "New York City"
401 assert res.lat == 40.7812
402 assert res.lng == -73.9647
403 assert res.radius == 250
406def test_lite_get_user(db):
407 user1, token1 = generate_user()
408 user2, token2 = generate_user()
410 refresh_materialized_views_rapid(empty_pb2.Empty())
412 with api_session(token1) as api:
413 res = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user2.username))
414 assert res.user_id == user2.id
415 assert res.username == user2.username
416 assert res.name == user2.name
418 with api_session(token1) as api:
419 res = api.GetLiteUser(api_pb2.GetLiteUserReq(user=str(user2.id)))
420 assert res.user_id == user2.id
421 assert res.username == user2.username
422 assert res.name == user2.name
425def test_GetLiteUsers(db):
426 user1, token1 = generate_user()
427 user2, _ = generate_user()
428 user3, _ = generate_user()
429 user4, _ = generate_user()
430 user5, _ = generate_user()
431 user6, _ = generate_user()
433 make_user_block(user4, user1)
435 refresh_materialized_views_rapid(empty_pb2.Empty())
437 with api_session(token1) as api:
438 res = api.GetLiteUsers(
439 api_pb2.GetLiteUsersReq(
440 users=[
441 user1.username,
442 str(user1.id),
443 "nonexistent",
444 str(user2.id),
445 "9994",
446 user6.username,
447 str(user5.id),
448 "notreal",
449 user4.username,
450 ]
451 )
452 )
454 assert len(res.responses) == 9
455 assert res.responses[0].query == user1.username
456 assert res.responses[0].user.user_id == user1.id
458 assert res.responses[1].query == str(user1.id)
459 assert res.responses[1].user.user_id == user1.id
461 assert res.responses[2].query == "nonexistent"
462 assert res.responses[2].not_found
464 assert res.responses[3].query == str(user2.id)
465 assert res.responses[3].user.user_id == user2.id
467 assert res.responses[4].query == "9994"
468 assert res.responses[4].not_found
470 assert res.responses[5].query == user6.username
471 assert res.responses[5].user.user_id == user6.id
473 assert res.responses[6].query == str(user5.id)
474 assert res.responses[6].user.user_id == user5.id
476 assert res.responses[7].query == "notreal"
477 assert res.responses[7].not_found
479 # blocked - should return ghost profile
480 assert res.responses[8].query == user4.username
481 assert not res.responses[8].not_found
482 assert res.responses[8].user.user_id == user4.id
483 assert res.responses[8].user.is_ghost
484 assert res.responses[8].user.username == "ghost"
485 assert res.responses[8].user.name == "Deactivated Account"
487 with api_session(token1) as api:
488 with pytest.raises(grpc.RpcError) as e:
489 api.GetLiteUsers(api_pb2.GetLiteUsersReq(users=201 * [user1.username]))
490 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
491 assert e.value.details() == "You can't request that many users at a time."
494def test_update_profile(db):
495 user, token = generate_user()
497 with api_session(token) as api:
498 with pytest.raises(grpc.RpcError) as e:
499 api.UpdateProfile(api_pb2.UpdateProfileReq(name=wrappers_pb2.StringValue(value=" ")))
500 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
501 assert e.value.details() == "Name not supported."
503 with pytest.raises(grpc.RpcError) as e:
504 api.UpdateProfile(
505 api_pb2.UpdateProfileReq(lat=wrappers_pb2.DoubleValue(value=0), lng=wrappers_pb2.DoubleValue(value=0))
506 )
507 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
508 assert e.value.details() == "Invalid coordinate."
510 with pytest.raises(grpc.RpcError) as e:
511 api.UpdateProfile(
512 api_pb2.UpdateProfileReq(regions_visited=api_pb2.RepeatedStringValue(value=["United States"]))
513 )
514 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
515 assert e.value.details() == "Invalid region."
517 with pytest.raises(grpc.RpcError) as e:
518 api.UpdateProfile(
519 api_pb2.UpdateProfileReq(regions_lived=api_pb2.RepeatedStringValue(value=["United Kingdom"]))
520 )
521 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
522 assert e.value.details() == "Invalid region."
524 api.UpdateProfile(
525 api_pb2.UpdateProfileReq(
526 name=wrappers_pb2.StringValue(value="New name"),
527 city=wrappers_pb2.StringValue(value="Timbuktu"),
528 hometown=api_pb2.NullableStringValue(value="Walla Walla"),
529 lat=wrappers_pb2.DoubleValue(value=0.01),
530 lng=wrappers_pb2.DoubleValue(value=-2),
531 radius=wrappers_pb2.DoubleValue(value=321),
532 pronouns=api_pb2.NullableStringValue(value="Ro, Robo, Robots"),
533 occupation=api_pb2.NullableStringValue(value="Testing"),
534 education=api_pb2.NullableStringValue(value="Couchers U"),
535 about_me=api_pb2.NullableStringValue(value="I rule"),
536 things_i_like=api_pb2.NullableStringValue(value="Couchers"),
537 about_place=api_pb2.NullableStringValue(value="My place"),
538 hosting_status=api_pb2.HOSTING_STATUS_CAN_HOST,
539 meetup_status=api_pb2.MEETUP_STATUS_WANTS_TO_MEETUP,
540 language_abilities=api_pb2.RepeatedLanguageAbilityValue(
541 value=[
542 api_pb2.LanguageAbility(
543 code="eng",
544 fluency=api_pb2.LanguageAbility.Fluency.FLUENCY_FLUENT,
545 )
546 ],
547 ),
548 regions_visited=api_pb2.RepeatedStringValue(value=["CXR", "FIN"]),
549 regions_lived=api_pb2.RepeatedStringValue(value=["USA", "EST"]),
550 additional_information=api_pb2.NullableStringValue(value="I <3 Couchers"),
551 )
552 )
554 user_details = api.GetUser(api_pb2.GetUserReq(user=user.username))
555 assert user_details.name == "New name"
556 assert user_details.city == "Timbuktu"
557 assert user_details.hometown == "Walla Walla"
558 assert user_details.pronouns == "Ro, Robo, Robots"
559 assert user_details.education == "Couchers U"
560 assert user_details.things_i_like == "Couchers"
561 assert user_details.lat == 0.01
562 assert user_details.lng == -2
563 assert user_details.radius == 321
564 assert user_details.occupation == "Testing"
565 assert user_details.about_me == "I rule"
566 assert user_details.about_place == "My place"
567 assert user_details.hosting_status == api_pb2.HOSTING_STATUS_CAN_HOST
568 assert user_details.meetup_status == api_pb2.MEETUP_STATUS_WANTS_TO_MEETUP
569 assert user_details.language_abilities[0].code == "eng"
570 assert user_details.language_abilities[0].fluency == api_pb2.LanguageAbility.Fluency.FLUENCY_FLUENT
571 assert user_details.additional_information == "I <3 Couchers"
572 assert user_details.regions_visited == ["CXR", "FIN"]
573 assert user_details.regions_lived == ["EST", "USA"]
575 # Test unset values
576 api.UpdateProfile(
577 api_pb2.UpdateProfileReq(
578 hometown=api_pb2.NullableStringValue(is_null=True),
579 radius=wrappers_pb2.DoubleValue(value=0),
580 pronouns=api_pb2.NullableStringValue(is_null=True),
581 occupation=api_pb2.NullableStringValue(is_null=True),
582 education=api_pb2.NullableStringValue(is_null=True),
583 about_me=api_pb2.NullableStringValue(is_null=True),
584 things_i_like=api_pb2.NullableStringValue(is_null=True),
585 about_place=api_pb2.NullableStringValue(is_null=True),
586 hosting_status=api_pb2.HOSTING_STATUS_CAN_HOST,
587 meetup_status=api_pb2.MEETUP_STATUS_WANTS_TO_MEETUP,
588 language_abilities=api_pb2.RepeatedLanguageAbilityValue(value=[]),
589 regions_visited=api_pb2.RepeatedStringValue(value=[]),
590 regions_lived=api_pb2.RepeatedStringValue(value=[]),
591 additional_information=api_pb2.NullableStringValue(is_null=True),
592 )
593 )
595 user_details = api.GetUser(api_pb2.GetUserReq(user=user.username))
596 assert not user_details.hometown
597 assert not user_details.radius
598 assert not user_details.pronouns
599 assert not user_details.occupation
600 assert not user_details.education
601 assert not user_details.about_me
602 assert not user_details.things_i_like
603 assert not user_details.about_place
604 assert user_details.hosting_status == api_pb2.HOSTING_STATUS_CAN_HOST
605 assert user_details.meetup_status == api_pb2.MEETUP_STATUS_WANTS_TO_MEETUP
606 assert not user_details.language_abilities
607 assert not user_details.regions_visited
608 assert not user_details.regions_lived
609 assert not user_details.additional_information
612def test_update_profile_do_not_email(db):
613 user, token = generate_user()
615 with notifications_session(token) as notifications:
616 notifications.SetNotificationSettings(notifications_pb2.SetNotificationSettingsReq(enable_do_not_email=True))
618 with api_session(token) as api:
619 with pytest.raises(grpc.RpcError) as e:
620 api.UpdateProfile(api_pb2.UpdateProfileReq(hosting_status=api_pb2.HOSTING_STATUS_CAN_HOST))
621 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
622 assert e.value.details() == "You cannot enable hosting while you have emails turned off in your settings."
624 with pytest.raises(grpc.RpcError) as e:
625 api.UpdateProfile(api_pb2.UpdateProfileReq(meetup_status=api_pb2.MEETUP_STATUS_OPEN_TO_MEETUP))
626 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
627 assert e.value.details() == "You cannot enable meeting up while you have emails turned off in your settings."
630def test_language_abilities(db):
631 user, token = generate_user(
632 language_abilities=[
633 ("fin", LanguageFluency.fluent),
634 ("fra", LanguageFluency.beginner),
635 ],
636 )
638 with api_session(token) as api:
639 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
640 assert len(res.language_abilities) == 2
642 # can't add non-existent languages
643 with pytest.raises(grpc.RpcError) as err:
644 api.UpdateProfile(
645 api_pb2.UpdateProfileReq(
646 language_abilities=api_pb2.RepeatedLanguageAbilityValue(
647 value=[
648 api_pb2.LanguageAbility(
649 code="QQQ",
650 fluency=api_pb2.LanguageAbility.Fluency.FLUENCY_FLUENT,
651 )
652 ],
653 ),
654 )
655 )
656 assert err.value.code() == grpc.StatusCode.INVALID_ARGUMENT
657 assert err.value.details() == "Invalid language."
659 # can't have multiple languages of the same type
660 with pytest.raises(Exception) as err2:
661 api.UpdateProfile(
662 api_pb2.UpdateProfileReq(
663 language_abilities=api_pb2.RepeatedLanguageAbilityValue(
664 value=[
665 api_pb2.LanguageAbility(
666 code="eng",
667 fluency=api_pb2.LanguageAbility.Fluency.FLUENCY_FLUENT,
668 ),
669 api_pb2.LanguageAbility(
670 code="eng",
671 fluency=api_pb2.LanguageAbility.Fluency.FLUENCY_FLUENT,
672 ),
673 ],
674 ),
675 )
676 )
677 assert "violates unique constraint" in str(err2.value)
679 # nothing changed
680 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
681 assert len(res.language_abilities) == 2
683 # now actually add a value
684 api.UpdateProfile(
685 api_pb2.UpdateProfileReq(
686 language_abilities=api_pb2.RepeatedLanguageAbilityValue(
687 value=[
688 api_pb2.LanguageAbility(
689 code="eng",
690 fluency=api_pb2.LanguageAbility.Fluency.FLUENCY_FLUENT,
691 )
692 ],
693 ),
694 )
695 )
697 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
698 assert len(res.language_abilities) == 1
699 assert res.language_abilities[0].code == "eng"
700 assert res.language_abilities[0].fluency == api_pb2.LanguageAbility.Fluency.FLUENCY_FLUENT
702 # change the value to a new one
703 api.UpdateProfile(
704 api_pb2.UpdateProfileReq(
705 language_abilities=api_pb2.RepeatedLanguageAbilityValue(
706 value=[
707 api_pb2.LanguageAbility(
708 code="fin",
709 fluency=api_pb2.LanguageAbility.Fluency.FLUENCY_BEGINNER,
710 )
711 ],
712 ),
713 )
714 )
716 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
717 assert len(res.language_abilities) == 1
718 assert res.language_abilities[0].code == "fin"
719 assert res.language_abilities[0].fluency == api_pb2.LanguageAbility.Fluency.FLUENCY_BEGINNER
721 # should be able to set to same value still
722 api.UpdateProfile(
723 api_pb2.UpdateProfileReq(
724 language_abilities=api_pb2.RepeatedLanguageAbilityValue(
725 value=[
726 api_pb2.LanguageAbility(
727 code="fin",
728 fluency=api_pb2.LanguageAbility.Fluency.FLUENCY_BEGINNER,
729 )
730 ],
731 ),
732 )
733 )
735 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
736 assert len(res.language_abilities) == 1
737 assert res.language_abilities[0].code == "fin"
738 assert res.language_abilities[0].fluency == api_pb2.LanguageAbility.Fluency.FLUENCY_BEGINNER
740 # don't change it
741 api.UpdateProfile(api_pb2.UpdateProfileReq())
743 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
744 assert len(res.language_abilities) == 1
745 assert res.language_abilities[0].code == "fin"
746 assert res.language_abilities[0].fluency == api_pb2.LanguageAbility.Fluency.FLUENCY_BEGINNER
748 # remove value
749 api.UpdateProfile(
750 api_pb2.UpdateProfileReq(
751 language_abilities=api_pb2.RepeatedLanguageAbilityValue(
752 value=[],
753 ),
754 )
755 )
757 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
758 assert len(res.language_abilities) == 0
761def test_pending_friend_request_count(db):
762 user1, token1 = generate_user()
763 user2, token2 = generate_user()
764 user3, token3 = generate_user()
766 with api_session(token2) as api:
767 res = api.Ping(api_pb2.PingReq())
768 assert res.pending_friend_request_count == 0
770 with api_session(token1) as api:
771 res = api.Ping(api_pb2.PingReq())
772 assert res.pending_friend_request_count == 0
773 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
774 res = api.Ping(api_pb2.PingReq())
775 assert res.pending_friend_request_count == 0
777 with api_session(token2) as api:
778 res = api.Ping(api_pb2.PingReq())
779 assert res.pending_friend_request_count == 1
781 with api_session(token2) as api:
782 # check it's there
783 res = api.ListFriendRequests(empty_pb2.Empty())
784 assert len(res.sent) == 0
785 assert len(res.received) == 1
787 assert res.received[0].state == api_pb2.FriendRequest.FriendRequestStatus.PENDING
788 assert res.received[0].user_id == user1.id
790 fr_id = res.received[0].friend_request_id
792 # accept it
793 api.RespondFriendRequest(api_pb2.RespondFriendRequestReq(friend_request_id=fr_id, accept=True))
795 res = api.Ping(api_pb2.PingReq())
796 assert res.pending_friend_request_count == 0
799def test_friend_request_flow(db, push_collector: PushCollector):
800 user1, token1 = generate_user(complete_profile=True)
801 user2, token2 = generate_user(complete_profile=True)
802 user3, token3 = generate_user()
804 # send a friend request from user1 to user2
805 with mock_notification_email() as mock:
806 with api_session(token1) as api:
807 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
809 push = push_collector.pop_for_user(user2.id, last=True)
810 assert push.content.title == f"Friend request from {user1.name}"
811 assert push.content.body == f"{user1.name} wants to be your friend."
813 mock.assert_called_once()
814 e = email_fields(mock)
815 assert e.recipient == user2.email
816 assert e.subject == f"[TEST] {user1.name} wants to be your friend on Couchers.org!"
817 assert user2.name in e.plain
818 assert user2.name in e.html
819 assert user1.name in e.plain
820 assert user1.name in e.html
821 assert "http://localhost:5001/img/thumbnail/" not in e.plain
822 assert "http://localhost:5001/img/thumbnail/" in e.html
823 assert "http://localhost:3000/connections/friends/" in e.plain
824 assert "http://localhost:3000/connections/friends/" in e.html
826 with session_scope() as session:
827 friend_request_id = session.execute(
828 select(FriendRelationship.id).where(
829 FriendRelationship.from_user_id == user1.id and FriendRelationship.to_user_id == user2.id
830 )
831 ).scalar_one_or_none()
833 with api_session(token1) as api:
834 # check it went through
835 res = api.ListFriendRequests(empty_pb2.Empty())
836 assert len(res.sent) == 1
837 assert len(res.received) == 0
839 assert res.sent[0].state == api_pb2.FriendRequest.FriendRequestStatus.PENDING
840 assert res.sent[0].user_id == user2.id
841 assert res.sent[0].friend_request_id == friend_request_id
843 with api_session(token2) as api:
844 # check it's there
845 res = api.ListFriendRequests(empty_pb2.Empty())
846 assert len(res.sent) == 0
847 assert len(res.received) == 1
849 assert res.received[0].state == api_pb2.FriendRequest.FriendRequestStatus.PENDING
850 assert res.received[0].user_id == user1.id
852 fr_id = res.received[0].friend_request_id
854 # accept it
855 with mock_notification_email() as mock:
856 api.RespondFriendRequest(api_pb2.RespondFriendRequestReq(friend_request_id=fr_id, accept=True))
858 # check it's gone
859 res = api.ListFriendRequests(empty_pb2.Empty())
860 assert len(res.sent) == 0
861 assert len(res.received) == 0
863 # check we're friends now
864 res = api.ListFriends(empty_pb2.Empty())
865 assert len(res.user_ids) == 1
866 assert res.user_ids[0] == user1.id
868 assert push_collector.count_for_user(user2.id) == 0
869 push = push_collector.pop_for_user(user1.id, last=True)
870 assert push.content.title == f"{user2.name} accepted your friend request"
871 assert push.content.body == f"You are now friends with {user2.name}."
873 mock.assert_called_once()
874 e = email_fields(mock)
875 assert e.recipient == user1.email
876 assert e.subject == f"[TEST] {user2.name} accepted your friend request!"
877 assert user1.name in e.plain
878 assert user1.name in e.html
879 assert user2.name in e.plain
880 assert user2.name in e.html
881 assert "http://localhost:5001/img/thumbnail/" not in e.plain
882 assert "http://localhost:5001/img/thumbnail/" in e.html
883 assert f"http://localhost:3000/user/{user2.username}" in e.plain
884 assert f"http://localhost:3000/user/{user2.username}" in e.html
886 with api_session(token1) as api:
887 # check it's gone
888 res = api.ListFriendRequests(empty_pb2.Empty())
889 assert len(res.sent) == 0
890 assert len(res.received) == 0
892 # check we're friends now
893 res = api.ListFriends(empty_pb2.Empty())
894 assert len(res.user_ids) == 1
895 assert res.user_ids[0] == user2.id
897 with api_session(token1) as api:
898 # we can't unfriend if we aren't friends
899 with pytest.raises(grpc.RpcError) as err:
900 api.RemoveFriend(api_pb2.RemoveFriendReq(user_id=user3.id))
901 assert err.value.code() == grpc.StatusCode.FAILED_PRECONDITION
902 assert err.value.details() == "You aren't friends with that user!"
904 # we can unfriend
905 res = api.RemoveFriend(api_pb2.RemoveFriendReq(user_id=user2.id))
907 res = api.ListFriends(empty_pb2.Empty())
908 assert len(res.user_ids) == 0
911def test_RemoveFriend_regression(db, push_collector: PushCollector):
912 user1, token1 = generate_user(complete_profile=True)
913 user2, token2 = generate_user(complete_profile=True)
914 user3, token3 = generate_user()
915 user4, token4 = generate_user()
916 user5, token5 = generate_user()
917 user6, token6 = generate_user()
919 with api_session(token4) as api:
920 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user1.id))
921 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
923 with api_session(token5) as api:
924 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user1.id))
926 with api_session(token1) as api:
927 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
928 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user3.id))
930 api.RespondFriendRequest(
931 api_pb2.RespondFriendRequestReq(
932 friend_request_id=api.ListFriendRequests(empty_pb2.Empty()).received[0].friend_request_id, accept=True
933 )
934 )
936 with api_session(token2) as api:
937 for fr in api.ListFriendRequests(empty_pb2.Empty()).received:
938 api.RespondFriendRequest(
939 api_pb2.RespondFriendRequestReq(friend_request_id=fr.friend_request_id, accept=True)
940 )
942 with api_session(token1) as api:
943 res = api.ListFriends(empty_pb2.Empty())
944 assert sorted(res.user_ids) == [2, 4]
946 api.RemoveFriend(api_pb2.RemoveFriendReq(user_id=user2.id))
948 res = api.ListFriends(empty_pb2.Empty())
949 assert sorted(res.user_ids) == [4]
951 api.RemoveFriend(api_pb2.RemoveFriendReq(user_id=user4.id))
953 res = api.ListFriends(empty_pb2.Empty())
954 assert not res.user_ids
957def test_cant_friend_request_twice(db):
958 user1, token1 = generate_user()
959 user2, token2 = generate_user()
961 # send friend request from user1 to user2
962 with api_session(token1) as api:
963 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
965 with pytest.raises(grpc.RpcError) as e:
966 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
967 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
968 assert e.value.details() == "You are already friends with or have sent a friend request to that user."
971def test_cant_friend_request_pending(db):
972 user1, token1 = generate_user()
973 user2, token2 = generate_user()
975 # send friend request from user1 to user2
976 with api_session(token1) as api:
977 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
979 with api_session(token2) as api:
980 with pytest.raises(grpc.RpcError) as e:
981 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user1.id))
982 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
983 assert e.value.details() == "You are already friends with or have sent a friend request to that user."
986def test_cant_friend_request_already_friends(db):
987 user1, token1 = generate_user()
988 user2, token2 = generate_user()
989 make_friends(user1, user2)
991 with api_session(token1) as api:
992 with pytest.raises(grpc.RpcError) as e:
993 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
994 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
995 assert e.value.details() == "You are already friends with or have sent a friend request to that user."
997 with api_session(token2) as api:
998 with pytest.raises(grpc.RpcError) as e:
999 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user1.id))
1000 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
1001 assert e.value.details() == "You are already friends with or have sent a friend request to that user."
1004def test_excessive_friend_requests_are_reported(db):
1005 """Test that excessive friend requests are first reported in a warning email and finally lead blocking of further requests."""
1006 user, token = generate_user()
1007 rate_limit_definition = RATE_LIMIT_DEFINITIONS[RateLimitAction.friend_request]
1008 with api_session(token) as api:
1009 # Test warning email
1010 with mock_notification_email() as mock_email:
1011 for _ in range(rate_limit_definition.warning_limit):
1012 friend_user, _ = generate_user()
1013 _ = api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=friend_user.id))
1015 assert mock_email.call_count == 0
1016 friend_user, _ = generate_user()
1017 _ = api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=friend_user.id))
1019 assert mock_email.call_count == 1
1020 email = email_fields(mock_email).plain
1021 assert email.startswith(
1022 f"User {user.username} has sent {rate_limit_definition.warning_limit} friend requests in the past {RATE_LIMIT_HOURS} hours."
1023 )
1025 # Test ban after exceeding FRIEND_REQUEST_HARD_LIMIT
1026 with mock_notification_email() as mock_email:
1027 for _ in range(rate_limit_definition.hard_limit - rate_limit_definition.warning_limit - 1):
1028 friend_user, _ = generate_user()
1029 _ = api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=friend_user.id))
1031 assert mock_email.call_count == 0
1032 friend_user, _ = generate_user()
1033 with pytest.raises(grpc.RpcError) as exc_info:
1034 _ = api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=friend_user.id))
1035 assert exc_info.value.code() == grpc.StatusCode.RESOURCE_EXHAUSTED
1036 assert (
1037 exc_info.value.details()
1038 == "You have sent a lot of friend requests in the past 24 hours. To avoid spam, you can't send any more for now."
1039 )
1041 assert mock_email.call_count == 1
1042 email = email_fields(mock_email).plain
1043 assert email.startswith(
1044 f"User {user.username} has sent {rate_limit_definition.hard_limit} friend requests in the past {RATE_LIMIT_HOURS} hours."
1045 )
1046 assert "The user has been blocked from sending further friend requests for now." in email
1049def test_ListFriends(db):
1050 user1, token1 = generate_user()
1051 user2, token2 = generate_user()
1052 user3, token3 = generate_user()
1054 # send friend request from user1 to user2 and user3
1055 with api_session(token1) as api:
1056 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
1057 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user3.id))
1059 with api_session(token3) as api:
1060 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
1062 with api_session(token2) as api:
1063 res = api.ListFriendRequests(empty_pb2.Empty())
1064 assert len(res.received) == 2
1066 # order is an implementation detail
1067 user1_req = [req for req in res.received if req.user_id == user1.id][0]
1068 user3_req = [req for req in res.received if req.user_id == user3.id][0]
1070 assert user1_req.state == api_pb2.FriendRequest.FriendRequestStatus.PENDING
1071 assert user1_req.user_id == user1.id
1072 api.RespondFriendRequest(
1073 api_pb2.RespondFriendRequestReq(friend_request_id=user1_req.friend_request_id, accept=True)
1074 )
1076 assert user3_req.state == api_pb2.FriendRequest.FriendRequestStatus.PENDING
1077 assert user3_req.user_id == user3.id
1078 api.RespondFriendRequest(
1079 api_pb2.RespondFriendRequestReq(friend_request_id=user3_req.friend_request_id, accept=True)
1080 )
1082 # check we now have two friends
1083 res = api.ListFriends(empty_pb2.Empty())
1084 assert len(res.user_ids) == 2
1085 assert user1.id in res.user_ids
1086 assert user3.id in res.user_ids
1088 with api_session(token3) as api:
1089 res = api.ListFriends(empty_pb2.Empty())
1090 assert len(res.user_ids) == 1
1091 assert user2.id in res.user_ids
1093 res = api.ListFriendRequests(empty_pb2.Empty())
1094 assert len(res.received) == 1
1095 assert res.received[0].state == api_pb2.FriendRequest.FriendRequestStatus.PENDING
1096 assert res.received[0].user_id == user1.id
1097 fr_id = res.received[0].friend_request_id
1098 api.RespondFriendRequest(api_pb2.RespondFriendRequestReq(friend_request_id=fr_id, accept=True))
1100 res = api.ListFriends(empty_pb2.Empty())
1101 assert len(res.user_ids) == 2
1102 assert user1.id in res.user_ids
1103 assert user2.id in res.user_ids
1105 with api_session(token1) as api:
1106 res = api.ListFriends(empty_pb2.Empty())
1107 assert len(res.user_ids) == 2
1108 assert user2.id in res.user_ids
1109 assert user3.id in res.user_ids
1112def test_ListMutualFriends(db):
1113 user1, token1 = generate_user()
1114 user2, token2 = generate_user()
1115 user3, token3 = generate_user()
1116 user4, token4 = generate_user()
1117 user5, token5 = generate_user()
1119 # arrange friends like this: 1<->2, 1<->3, 1<->4, 1<->5, 3<->2, 3<->4,
1120 # so 1 and 2 should have mutual friend 3 only
1121 make_friends(user1, user2)
1122 make_friends(user1, user3)
1123 make_friends(user1, user4)
1124 make_friends(user1, user5)
1125 make_friends(user3, user2)
1126 make_friends(user3, user4)
1128 with api_session(token1) as api:
1129 mutual_friends = api.ListMutualFriends(api_pb2.ListMutualFriendsReq(user_id=user2.id)).mutual_friends
1130 assert len(mutual_friends) == 1
1131 assert mutual_friends[0].user_id == user3.id
1133 # and other way around same
1134 with api_session(token2) as api:
1135 mutual_friends = api.ListMutualFriends(api_pb2.ListMutualFriendsReq(user_id=user1.id)).mutual_friends
1136 assert len(mutual_friends) == 1
1137 assert mutual_friends[0].user_id == user3.id
1139 # Check pending request doesn't have effect
1140 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user5.id))
1142 mutual_friends = api.ListMutualFriends(api_pb2.ListMutualFriendsReq(user_id=user1.id)).mutual_friends
1143 assert len(mutual_friends) == 1
1144 assert mutual_friends[0].user_id == user3.id
1146 # both ways
1147 with api_session(token1) as api:
1148 mutual_friends = api.ListMutualFriends(api_pb2.ListMutualFriendsReq(user_id=user2.id)).mutual_friends
1149 assert len(mutual_friends) == 1
1150 assert mutual_friends[0].user_id == user3.id
1153def test_mutual_friends_self(db):
1154 user1, token1 = generate_user()
1155 user2, token2 = generate_user()
1156 user3, token3 = generate_user()
1157 user4, token4 = generate_user()
1159 make_friends(user1, user2)
1160 make_friends(user2, user3)
1161 make_friends(user1, user4)
1163 with api_session(token1) as api:
1164 res = api.ListMutualFriends(api_pb2.ListMutualFriendsReq(user_id=user1.id))
1165 assert len(res.mutual_friends) == 0
1167 with api_session(token2) as api:
1168 res = api.ListMutualFriends(api_pb2.ListMutualFriendsReq(user_id=user2.id))
1169 assert len(res.mutual_friends) == 0
1171 with api_session(token3) as api:
1172 res = api.ListMutualFriends(api_pb2.ListMutualFriendsReq(user_id=user3.id))
1173 assert len(res.mutual_friends) == 0
1175 with api_session(token4) as api:
1176 res = api.ListMutualFriends(api_pb2.ListMutualFriendsReq(user_id=user4.id))
1177 assert len(res.mutual_friends) == 0
1180def test_CancelFriendRequest(db):
1181 user1, token1 = generate_user()
1182 user2, token2 = generate_user()
1184 with api_session(token1) as api:
1185 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
1187 res = api.ListFriendRequests(empty_pb2.Empty())
1188 assert res.sent[0].user_id == user2.id
1189 fr_id = res.sent[0].friend_request_id
1191 api.CancelFriendRequest(api_pb2.CancelFriendRequestReq(friend_request_id=fr_id))
1193 # check it's gone
1194 res = api.ListFriendRequests(empty_pb2.Empty())
1195 assert len(res.sent) == 0
1196 assert len(res.received) == 0
1198 # check not friends
1199 res = api.ListFriends(empty_pb2.Empty())
1200 assert len(res.user_ids) == 0
1202 with api_session(token2) as api:
1203 # check it's gone
1204 res = api.ListFriendRequests(empty_pb2.Empty())
1205 assert len(res.sent) == 0
1206 assert len(res.received) == 0
1208 # check we're not friends
1209 res = api.ListFriends(empty_pb2.Empty())
1210 assert len(res.user_ids) == 0
1212 with api_session(token1) as api:
1213 # check we can send another friend req
1214 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
1216 res = api.ListFriendRequests(empty_pb2.Empty())
1217 assert res.sent[0].state == api_pb2.FriendRequest.FriendRequestStatus.PENDING
1218 assert res.sent[0].user_id == user2.id
1221def test_accept_friend_request(db):
1222 user1, token1 = generate_user()
1223 user2, token2 = generate_user()
1225 with session_scope() as session:
1226 friend_request = FriendRelationship(from_user_id=user1.id, to_user_id=user2.id, status=FriendStatus.pending)
1227 session.add(friend_request)
1228 session.commit()
1229 friend_request_id = friend_request.id
1231 with api_session(token2) as api:
1232 # check request pending
1233 res = api.ListFriendRequests(empty_pb2.Empty())
1234 assert len(res.received) == 1
1235 assert res.received[0].user_id == user1.id
1237 api.RespondFriendRequest(api_pb2.RespondFriendRequestReq(friend_request_id=friend_request_id, accept=True))
1239 # check request is gone
1240 res = api.ListFriendRequests(empty_pb2.Empty())
1241 assert len(res.sent) == 0
1242 assert len(res.received) == 0
1244 # check now friends
1245 res = api.ListFriends(empty_pb2.Empty())
1246 assert len(res.user_ids) == 1
1247 assert res.user_ids[0] == user1.id
1249 with api_session(token1) as api:
1250 # check request gone
1251 res = api.ListFriendRequests(empty_pb2.Empty())
1252 assert len(res.sent) == 0
1253 assert len(res.received) == 0
1255 # check now friends
1256 res = api.ListFriends(empty_pb2.Empty())
1257 assert len(res.user_ids) == 1
1258 assert res.user_ids[0] == user2.id
1261def test_reject_friend_request(db):
1262 user1, token1 = generate_user()
1263 user2, token2 = generate_user()
1265 with api_session(token1) as api:
1266 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
1268 res = api.ListFriendRequests(empty_pb2.Empty())
1269 assert res.sent[0].state == api_pb2.FriendRequest.FriendRequestStatus.PENDING
1270 assert res.sent[0].user_id == user2.id
1272 with api_session(token2) as api:
1273 res = api.ListFriendRequests(empty_pb2.Empty())
1274 assert res.received[0].state == api_pb2.FriendRequest.FriendRequestStatus.PENDING
1275 assert res.received[0].user_id == user1.id
1277 fr_id = res.received[0].friend_request_id
1279 # reject it
1280 api.RespondFriendRequest(api_pb2.RespondFriendRequestReq(friend_request_id=fr_id, accept=False))
1282 # check it's gone
1283 res = api.ListFriendRequests(empty_pb2.Empty())
1284 assert len(res.sent) == 0
1285 assert len(res.received) == 0
1287 # check not friends
1288 res = api.ListFriends(empty_pb2.Empty())
1289 assert len(res.user_ids) == 0
1291 with api_session(token1) as api:
1292 # check it's gone
1293 res = api.ListFriendRequests(empty_pb2.Empty())
1294 assert len(res.sent) == 0
1295 assert len(res.received) == 0
1297 # check we're not friends
1298 res = api.ListFriends(empty_pb2.Empty())
1299 assert len(res.user_ids) == 0
1301 # check we can send another friend req
1302 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
1304 res = api.ListFriendRequests(empty_pb2.Empty())
1305 assert res.sent[0].state == api_pb2.FriendRequest.FriendRequestStatus.PENDING
1306 assert res.sent[0].user_id == user2.id
1309def test_hosting_preferences(db):
1310 user1, token1 = generate_user()
1311 user2, token2 = generate_user()
1313 with api_session(token1) as api:
1314 res = api.GetUser(api_pb2.GetUserReq(user=user1.username))
1315 assert not res.HasField("max_guests")
1316 assert not res.HasField("last_minute")
1317 assert not res.HasField("has_pets")
1318 assert not res.HasField("accepts_pets")
1319 assert not res.HasField("pet_details")
1320 assert not res.HasField("has_kids")
1321 assert not res.HasField("accepts_kids")
1322 assert not res.HasField("kid_details")
1323 assert not res.HasField("has_housemates")
1324 assert not res.HasField("housemate_details")
1325 assert not res.HasField("wheelchair_accessible")
1326 assert res.smoking_allowed == api_pb2.SMOKING_LOCATION_UNKNOWN
1327 assert not res.HasField("smokes_at_home")
1328 assert not res.HasField("drinking_allowed")
1329 assert not res.HasField("drinks_at_home")
1330 assert not res.HasField("other_host_info")
1331 assert res.sleeping_arrangement == api_pb2.SLEEPING_ARRANGEMENT_UNKNOWN
1332 assert not res.HasField("sleeping_details")
1333 assert not res.HasField("area")
1334 assert not res.HasField("house_rules")
1335 assert not res.HasField("parking")
1336 assert res.parking_details == api_pb2.PARKING_DETAILS_UNKNOWN
1337 assert not res.HasField("camping_ok")
1339 api.UpdateProfile(
1340 api_pb2.UpdateProfileReq(
1341 max_guests=api_pb2.NullableUInt32Value(value=3),
1342 last_minute=api_pb2.NullableBoolValue(value=True),
1343 has_pets=api_pb2.NullableBoolValue(value=False),
1344 accepts_pets=api_pb2.NullableBoolValue(value=True),
1345 pet_details=api_pb2.NullableStringValue(value="I love dogs"),
1346 has_kids=api_pb2.NullableBoolValue(value=False),
1347 accepts_kids=api_pb2.NullableBoolValue(value=True),
1348 kid_details=api_pb2.NullableStringValue(value="I hate kids"),
1349 has_housemates=api_pb2.NullableBoolValue(value=False),
1350 housemate_details=api_pb2.NullableStringValue(value="I have 7 housemates"),
1351 wheelchair_accessible=api_pb2.NullableBoolValue(value=True),
1352 smoking_allowed=api_pb2.SMOKING_LOCATION_WINDOW,
1353 area=api_pb2.NullableStringValue(value="area!"),
1354 smokes_at_home=api_pb2.NullableBoolValue(value=False),
1355 drinking_allowed=api_pb2.NullableBoolValue(value=True),
1356 drinks_at_home=api_pb2.NullableBoolValue(value=False),
1357 other_host_info=api_pb2.NullableStringValue(value="I'm pretty swell"),
1358 sleeping_arrangement=api_pb2.SLEEPING_ARRANGEMENT_COMMON,
1359 sleeping_details=api_pb2.NullableStringValue(value="Couch in living room"),
1360 house_rules=api_pb2.NullableStringValue(value="RULES!"),
1361 parking=api_pb2.NullableBoolValue(value=True),
1362 parking_details=api_pb2.PARKING_DETAILS_PAID_ONSITE,
1363 camping_ok=api_pb2.NullableBoolValue(value=False),
1364 )
1365 )
1367 # Use a second user to view the hosting preferences just to check
1368 # that it is public information.
1369 with api_session(token2) as api:
1370 res = api.GetUser(api_pb2.GetUserReq(user=user1.username))
1371 assert res.max_guests.value == 3
1372 assert res.last_minute.value
1373 assert not res.has_pets.value
1374 assert res.accepts_pets.value
1375 assert res.pet_details.value == "I love dogs"
1376 assert not res.has_kids.value
1377 assert res.accepts_kids.value
1378 assert res.kid_details.value == "I hate kids"
1379 assert not res.has_housemates.value
1380 assert res.housemate_details.value == "I have 7 housemates"
1381 assert res.wheelchair_accessible.value
1382 assert res.smoking_allowed == api_pb2.SMOKING_LOCATION_WINDOW
1383 assert not res.smokes_at_home.value
1384 assert res.drinking_allowed.value
1385 assert not res.drinks_at_home.value
1386 assert res.other_host_info.value == "I'm pretty swell"
1387 assert res.sleeping_arrangement == api_pb2.SLEEPING_ARRANGEMENT_COMMON
1388 assert res.sleeping_details.value == "Couch in living room"
1389 assert res.area.value == "area!"
1390 assert res.house_rules.value == "RULES!"
1391 assert res.parking.value
1392 assert res.parking_details == api_pb2.PARKING_DETAILS_PAID_ONSITE
1393 assert not res.camping_ok.value
1395 # test unsetting
1396 with api_session(token1) as api:
1397 api.UpdateProfile(
1398 api_pb2.UpdateProfileReq(
1399 max_guests=api_pb2.NullableUInt32Value(is_null=True),
1400 last_minute=api_pb2.NullableBoolValue(is_null=True),
1401 has_pets=api_pb2.NullableBoolValue(is_null=True),
1402 accepts_pets=api_pb2.NullableBoolValue(is_null=True),
1403 pet_details=api_pb2.NullableStringValue(is_null=True),
1404 has_kids=api_pb2.NullableBoolValue(is_null=True),
1405 accepts_kids=api_pb2.NullableBoolValue(is_null=True),
1406 kid_details=api_pb2.NullableStringValue(is_null=True),
1407 has_housemates=api_pb2.NullableBoolValue(is_null=True),
1408 housemate_details=api_pb2.NullableStringValue(is_null=True),
1409 wheelchair_accessible=api_pb2.NullableBoolValue(is_null=True),
1410 smoking_allowed=api_pb2.SMOKING_LOCATION_UNKNOWN,
1411 area=api_pb2.NullableStringValue(is_null=True),
1412 smokes_at_home=api_pb2.NullableBoolValue(is_null=True),
1413 drinking_allowed=api_pb2.NullableBoolValue(is_null=True),
1414 drinks_at_home=api_pb2.NullableBoolValue(is_null=True),
1415 other_host_info=api_pb2.NullableStringValue(is_null=True),
1416 sleeping_arrangement=api_pb2.SLEEPING_ARRANGEMENT_UNKNOWN,
1417 sleeping_details=api_pb2.NullableStringValue(is_null=True),
1418 house_rules=api_pb2.NullableStringValue(is_null=True),
1419 parking=api_pb2.NullableBoolValue(is_null=True),
1420 parking_details=api_pb2.PARKING_DETAILS_UNKNOWN,
1421 camping_ok=api_pb2.NullableBoolValue(is_null=True),
1422 )
1423 )
1425 res = api.GetUser(api_pb2.GetUserReq(user=user1.username))
1426 assert not res.HasField("max_guests")
1427 assert not res.HasField("last_minute")
1428 assert not res.HasField("has_pets")
1429 assert not res.HasField("accepts_pets")
1430 assert not res.HasField("pet_details")
1431 assert not res.HasField("has_kids")
1432 assert not res.HasField("accepts_kids")
1433 assert not res.HasField("kid_details")
1434 assert not res.HasField("has_housemates")
1435 assert not res.HasField("housemate_details")
1436 assert not res.HasField("wheelchair_accessible")
1437 assert res.smoking_allowed == api_pb2.SMOKING_LOCATION_UNKNOWN
1438 assert not res.HasField("smokes_at_home")
1439 assert not res.HasField("drinking_allowed")
1440 assert not res.HasField("drinks_at_home")
1441 assert not res.HasField("other_host_info")
1442 assert res.sleeping_arrangement == api_pb2.SLEEPING_ARRANGEMENT_UNKNOWN
1443 assert not res.HasField("sleeping_details")
1444 assert not res.HasField("area")
1445 assert not res.HasField("house_rules")
1446 assert not res.HasField("parking")
1447 assert res.parking_details == api_pb2.PARKING_DETAILS_UNKNOWN
1448 assert not res.HasField("camping_ok")
1451def test_badges(db):
1452 user1, _ = generate_user(last_donated=None)
1453 user2, _ = generate_user(last_donated=None)
1454 user3, _ = generate_user(last_donated=None)
1455 user4, token = generate_user(last_donated=None)
1457 update_badges(empty_pb2.Empty())
1459 founder_badge = get_badge_dict()["founder"]
1460 board_member_badge = get_badge_dict()["board_member"]
1462 with api_session(token) as api:
1463 assert api.GetUser(api_pb2.GetUserReq(user=user1.username)).badges == ["founder", "board_member"]
1464 assert api.GetUser(api_pb2.GetUserReq(user=user2.username)).badges == ["founder", "board_member"]
1465 assert api.GetUser(api_pb2.GetUserReq(user=user3.username)).badges == []
1467 assert api.ListBadgeUsers(api_pb2.ListBadgeUsersReq(badge_id=founder_badge.id)).user_ids == [1, 2]
1468 res = api.ListBadgeUsers(api_pb2.ListBadgeUsersReq(badge_id=board_member_badge.id, page_size=1))
1469 assert res.user_ids == [1]
1470 res2 = api.ListBadgeUsers(
1471 api_pb2.ListBadgeUsersReq(badge_id=board_member_badge.id, page_token=res.next_page_token)
1472 )
1473 assert res2.user_ids == [2]
1476@pytest.mark.parametrize("flag", ["is_deleted", "is_banned"])
1477def test_ListBadgeUsers_excludes_ghost_users(db, flag):
1478 """Test that ListBadgeUsers does not return deleted/banned users."""
1479 from couchers.helpers.badges import user_add_badge
1481 user1, token1 = generate_user()
1482 user2, _ = generate_user()
1483 user3, _ = generate_user()
1485 volunteer_badge = get_badge_dict()["volunteer"]
1487 # Give all three users the volunteer badge
1488 with session_scope() as session:
1489 user_add_badge(session, user1.id, "volunteer", do_notify=False)
1490 user_add_badge(session, user2.id, "volunteer", do_notify=False)
1491 user_add_badge(session, user3.id, "volunteer", do_notify=False)
1493 # Verify all three users appear in the badge list
1494 with api_session(token1) as api:
1495 res = api.ListBadgeUsers(api_pb2.ListBadgeUsersReq(badge_id=volunteer_badge.id))
1496 assert set(res.user_ids) == {user1.id, user2.id, user3.id}
1498 # Make user2 invisible (deleted or banned)
1499 with session_scope() as session:
1500 db_user2 = session.execute(select(User).where(User.id == user2.id)).scalar_one()
1501 setattr(db_user2, flag, True)
1503 # Now user2 should not appear in the badge list
1504 with api_session(token1) as api:
1505 res = api.ListBadgeUsers(api_pb2.ListBadgeUsersReq(badge_id=volunteer_badge.id))
1506 assert set(res.user_ids) == {user1.id, user3.id}
1509@pytest.mark.parametrize("flag", ["is_deleted", "is_banned"])
1510def test_GetLiteUser_ghost_user_by_username(db, flag):
1511 """Test that GetLiteUser returns a ghost profile for deleted/banned users when querying by username."""
1512 user1, token1 = generate_user()
1513 user2, _ = generate_user()
1515 # Make user2 invisible
1516 with session_scope() as session:
1517 db_user2 = session.merge(user2)
1518 setattr(db_user2, flag, True)
1519 session.commit()
1521 # Refresh the materialized view
1522 refresh_materialized_views_rapid(empty_pb2.Empty())
1524 with api_session(token1) as api:
1525 # Query by username
1526 lite_user = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user2.username))
1528 assert lite_user.user_id == user2.id
1529 assert lite_user.username == "ghost"
1530 assert lite_user.name == "Deactivated Account"
1531 assert lite_user.lat == 0
1532 assert lite_user.lng == 0
1533 assert lite_user.radius == 0
1534 assert lite_user.city == ""
1535 assert lite_user.age == 0
1536 assert lite_user.avatar_url == ""
1537 assert lite_user.avatar_thumbnail_url == ""
1538 assert not lite_user.has_strong_verification
1541@pytest.mark.parametrize("flag", ["is_deleted", "is_banned"])
1542def test_GetLiteUser_ghost_user_by_id(db, flag):
1543 """Test that GetLiteUser returns a ghost profile for deleted/banned users when querying by ID."""
1544 user1, token1 = generate_user()
1545 user2, _ = generate_user()
1547 # Make user2 invisible
1548 with session_scope() as session:
1549 db_user2 = session.merge(user2)
1550 setattr(db_user2, flag, True)
1551 session.commit()
1553 # Refresh the materialized view
1554 refresh_materialized_views_rapid(empty_pb2.Empty())
1556 with api_session(token1) as api:
1557 # Query by ID
1558 lite_user = api.GetLiteUser(api_pb2.GetLiteUserReq(user=str(user2.id)))
1560 assert lite_user.user_id == user2.id
1561 assert lite_user.username == "ghost"
1562 assert lite_user.name == "Deactivated Account"
1563 assert lite_user.lat == 0
1564 assert lite_user.lng == 0
1565 assert lite_user.radius == 0
1566 assert lite_user.city == ""
1567 assert lite_user.age == 0
1568 assert lite_user.avatar_url == ""
1569 assert lite_user.avatar_thumbnail_url == ""
1570 assert not lite_user.has_strong_verification
1573def test_GetLiteUser_blocked_user(db):
1574 """Test that GetLiteUser returns a ghost profile for blocked users."""
1575 user1, token1 = generate_user()
1576 user2, _ = generate_user()
1578 # User1 blocks user2
1579 make_user_block(user1, user2)
1581 # Refresh the materialized view
1582 refresh_materialized_views_rapid(empty_pb2.Empty())
1584 with api_session(token1) as api:
1585 # Query by username
1586 lite_user = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user2.username))
1588 assert lite_user.user_id == user2.id
1589 assert lite_user.is_ghost
1590 assert lite_user.username == "ghost"
1591 assert lite_user.name == "Deactivated Account"
1593 # Query by ID
1594 lite_user = api.GetLiteUser(api_pb2.GetLiteUserReq(user=str(user2.id)))
1596 assert lite_user.user_id == user2.id
1597 assert lite_user.is_ghost
1598 assert lite_user.username == "ghost"
1599 assert lite_user.name == "Deactivated Account"
1602def test_GetLiteUser_blocking_user(db):
1603 """Test that GetLiteUser returns a ghost profile when the target user has blocked the requester."""
1604 user1, token1 = generate_user()
1605 user2, _ = generate_user()
1607 # User2 blocks user1
1608 make_user_block(user2, user1)
1610 # Refresh the materialized view
1611 refresh_materialized_views_rapid(empty_pb2.Empty())
1613 with api_session(token1) as api:
1614 # Query by username
1615 lite_user = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user2.username))
1617 assert lite_user.user_id == user2.id
1618 assert lite_user.is_ghost
1619 assert lite_user.username == "ghost"
1620 assert lite_user.name == "Deactivated Account"
1622 # Query by ID
1623 lite_user = api.GetLiteUser(api_pb2.GetLiteUserReq(user=str(user2.id)))
1625 assert lite_user.user_id == user2.id
1626 assert lite_user.is_ghost
1627 assert lite_user.username == "ghost"
1628 assert lite_user.name == "Deactivated Account"
1631@pytest.mark.parametrize("flag", ["is_deleted", "is_banned"])
1632def test_GetLiteUsers_ghost_users(db, flag):
1633 """Test that GetLiteUsers returns ghost profiles for deleted/banned users."""
1634 user1, token1 = generate_user()
1635 user2, _ = generate_user()
1636 user3, _ = generate_user()
1637 user4, _ = generate_user()
1639 # Make user2 and user4 invisible
1640 with session_scope() as session:
1641 db_user2 = session.merge(user2)
1642 setattr(db_user2, flag, True)
1643 db_user4 = session.merge(user4)
1644 setattr(db_user4, flag, True)
1645 session.commit()
1647 # Refresh the materialized view
1648 refresh_materialized_views_rapid(empty_pb2.Empty())
1650 with api_session(token1) as api:
1651 res = api.GetLiteUsers(
1652 api_pb2.GetLiteUsersReq(
1653 users=[
1654 user1.username, # visible
1655 user2.username, # ghost
1656 str(user3.id), # visible
1657 str(user4.id), # ghost
1658 ]
1659 )
1660 )
1662 assert len(res.responses) == 4
1664 # user1 - visible, normal profile
1665 assert res.responses[0].query == user1.username
1666 assert not res.responses[0].not_found
1667 assert res.responses[0].user.user_id == user1.id
1668 assert res.responses[0].user.username == user1.username
1669 assert res.responses[0].user.name == user1.name
1671 # user2 - ghost by username
1672 assert res.responses[1].query == user2.username
1673 assert not res.responses[1].not_found
1674 assert res.responses[1].user.user_id == user2.id
1675 assert res.responses[1].user.is_ghost
1676 assert res.responses[1].user.username == "ghost"
1677 assert res.responses[1].user.name == "Deactivated Account"
1679 # user3 - visible, normal profile
1680 assert res.responses[2].query == str(user3.id)
1681 assert not res.responses[2].not_found
1682 assert res.responses[2].user.user_id == user3.id
1683 assert res.responses[2].user.username == user3.username
1684 assert res.responses[2].user.name == user3.name
1686 # user4 - ghost by ID
1687 assert res.responses[3].query == str(user4.id)
1688 assert not res.responses[3].not_found
1689 assert res.responses[3].user.user_id == user4.id
1690 assert res.responses[3].user.is_ghost
1691 assert res.responses[3].user.username == "ghost"
1692 assert res.responses[3].user.name == "Deactivated Account"
1695def test_GetLiteUsers_blocked_users(db):
1696 """Test that GetLiteUsers returns ghost profiles for blocked users."""
1697 user1, token1 = generate_user()
1698 user2, _ = generate_user()
1699 user3, _ = generate_user()
1700 user4, _ = generate_user()
1701 user5, _ = generate_user()
1703 # User1 blocks user2
1704 make_user_block(user1, user2)
1705 # User4 blocks user1
1706 make_user_block(user4, user1)
1708 # Refresh the materialized view
1709 refresh_materialized_views_rapid(empty_pb2.Empty())
1711 with api_session(token1) as api:
1712 res = api.GetLiteUsers(
1713 api_pb2.GetLiteUsersReq(
1714 users=[
1715 user2.username, # user1 blocked user2
1716 str(user3.id), # visible
1717 user4.username, # user4 blocked user1
1718 str(user5.id), # visible
1719 ]
1720 )
1721 )
1723 assert len(res.responses) == 4
1725 # user2 - blocked by user1, should be ghost
1726 assert res.responses[0].query == user2.username
1727 assert not res.responses[0].not_found
1728 assert res.responses[0].user.user_id == user2.id
1729 assert res.responses[0].user.is_ghost
1730 assert res.responses[0].user.username == "ghost"
1731 assert res.responses[0].user.name == "Deactivated Account"
1733 # user3 - visible
1734 assert res.responses[1].query == str(user3.id)
1735 assert not res.responses[1].not_found
1736 assert res.responses[1].user.user_id == user3.id
1737 assert res.responses[1].user.username == user3.username
1739 # user4 - user4 blocked user1, should be ghost
1740 assert res.responses[2].query == user4.username
1741 assert not res.responses[2].not_found
1742 assert res.responses[2].user.user_id == user4.id
1743 assert res.responses[2].user.is_ghost
1744 assert res.responses[2].user.username == "ghost"
1745 assert res.responses[2].user.name == "Deactivated Account"
1747 # user5 - visible
1748 assert res.responses[3].query == str(user5.id)
1749 assert not res.responses[3].not_found
1750 assert res.responses[3].user.user_id == user5.id
1751 assert res.responses[3].user.username == user5.username
1754@pytest.mark.parametrize("flag", ["is_deleted", "is_banned"])
1755def test_GetUser_ghost_user_by_id(db, flag):
1756 """Test that GetUser returns a ghost profile for deleted/banned users when querying by ID."""
1757 user1, token1 = generate_user()
1758 user2, _ = generate_user()
1760 # Make user2 invisible
1761 with session_scope() as session:
1762 db_user2 = session.merge(user2)
1763 setattr(db_user2, flag, True)
1764 session.commit()
1766 with api_session(token1) as api:
1767 # Query by ID
1768 user_pb = api.GetUser(api_pb2.GetUserReq(user=str(user2.id)))
1770 assert user_pb.user_id == user2.id
1771 assert user_pb.username == "ghost"
1772 assert user_pb.name == "Deactivated Account"
1773 assert user_pb.city == ""
1774 assert user_pb.hosting_status == 0
1775 assert user_pb.meetup_status == 0
1778def test_GetUser_blocked_user(db):
1779 """Test that GetUser returns a ghost profile for blocked users."""
1780 user1, token1 = generate_user()
1781 user2, _ = generate_user()
1783 # User1 blocks user2
1784 make_user_block(user1, user2)
1786 with api_session(token1) as api:
1787 # Query by username
1788 user_pb = api.GetUser(api_pb2.GetUserReq(user=user2.username))
1790 assert user_pb.user_id == user2.id
1791 assert user_pb.username == "ghost"
1792 assert user_pb.name == "Deactivated Account"
1794 # Query by ID
1795 user_pb = api.GetUser(api_pb2.GetUserReq(user=str(user2.id)))
1797 assert user_pb.user_id == user2.id
1798 assert user_pb.username == "ghost"
1799 assert user_pb.name == "Deactivated Account"
1802def test_GetUser_blocking_user(db):
1803 """Test that GetUser returns a ghost profile when the target user has blocked the requester."""
1804 user1, token1 = generate_user()
1805 user2, _ = generate_user()
1807 # User2 blocks user1
1808 make_user_block(user2, user1)
1810 with api_session(token1) as api:
1811 # Query by username
1812 user_pb = api.GetUser(api_pb2.GetUserReq(user=user2.username))
1814 assert user_pb.user_id == user2.id
1815 assert user_pb.username == "ghost"
1816 assert user_pb.name == "Deactivated Account"
1818 # Query by ID
1819 user_pb = api.GetUser(api_pb2.GetUserReq(user=str(user2.id)))
1821 assert user_pb.user_id == user2.id
1822 assert user_pb.username == "ghost"
1823 assert user_pb.name == "Deactivated Account"