Coverage for app/backend/src/tests/test_api.py: 100%
1197 statements
« prev ^ index » next coverage.py v7.14.2, created at 2026-06-21 09:29 +0000
« prev ^ index » next coverage.py v7.14.2, created at 2026-06-21 09:29 +0000
1from datetime import timedelta
3import grpc
4import pytest
5from google.protobuf import empty_pb2, wrappers_pb2
6from sqlalchemy import func, select, update
8from couchers.db import session_scope
9from couchers.helpers.badges import user_add_badge
10from couchers.jobs.handlers import update_badges
11from couchers.materialized_views import refresh_materialized_views_rapid
12from couchers.models import (
13 FriendRelationship,
14 FriendStatus,
15 LanguageFluency,
16 ModerationObjectType,
17 ModerationState,
18 ModerationVisibility,
19 NonvisibleUserAccess,
20 NonvisibleUserAccessType,
21 NonvisibleUserState,
22 RateLimitAction,
23 User,
24 UserBadge,
25)
26from couchers.models.notifications import Notification
27from couchers.proto import admin_pb2, api_pb2, blocking_pb2, jail_pb2, notifications_pb2
28from couchers.rate_limits.definitions import RATE_LIMIT_DEFINITIONS, RATE_LIMIT_HOURS
29from couchers.resources import get_badge_dict
30from couchers.utils import create_coordinate, now, to_aware_datetime
31from tests.fixtures.db import generate_user, make_friends, make_user_block
32from tests.fixtures.misc import EmailCollector, PushCollector
33from tests.fixtures.sessions import (
34 api_session,
35 blocking_session,
36 notifications_session,
37 real_api_session,
38 real_jail_session,
39)
40from tests.fixtures.sessions import (
41 real_admin_session as admin_session,
42)
45@pytest.fixture(autouse=True)
46def _(testconfig):
47 pass
50def test_ping(db):
51 user, token = generate_user(
52 regions_lived=["ESP", "FRA", "EST"],
53 regions_visited=["CHE", "REU", "FIN"],
54 language_abilities=[
55 ("fin", LanguageFluency.fluent),
56 ("fra", LanguageFluency.beginner),
57 ],
58 )
60 with real_api_session(token) as api:
61 res = api.Ping(api_pb2.PingReq())
63 assert res.user.user_id == user.id
64 assert res.user.username == user.username
65 assert res.user.name == user.name
66 assert res.user.city == user.city
67 assert res.user.hometown == user.hometown
68 assert res.user.verification == 0.0
69 assert res.user.community_standing == user.community_standing
70 assert res.user.num_references == 0
71 assert res.user.gender == user.gender
72 assert res.user.pronouns == user.pronouns
73 assert res.user.age == user.age
75 assert (res.user.lat, res.user.lng) == user.coordinates
77 # the joined time is fuzzed
78 # but shouldn't be before actual joined time, or more than one hour behind
79 assert user.joined - timedelta(hours=1) <= to_aware_datetime(res.user.joined) <= user.joined
80 # same for last_active
81 assert user.last_active - timedelta(hours=1) <= to_aware_datetime(res.user.last_active) <= user.last_active
83 assert res.user.hosting_status == api_pb2.HOSTING_STATUS_CANT_HOST
84 assert res.user.meetup_status == api_pb2.MEETUP_STATUS_OPEN_TO_MEETUP
86 assert res.user.occupation == user.occupation
87 assert res.user.education == user.education
88 assert res.user.about_me == user.about_me
89 assert res.user.things_i_like == user.things_i_like
90 assert {language_ability.code for language_ability in res.user.language_abilities} == {"fin", "fra"}
91 assert res.user.about_place == user.about_place
92 assert res.user.regions_visited == ["FIN", "REU", "CHE"] # Tests alphabetization by region name
93 assert res.user.regions_lived == ["EST", "FRA", "ESP"] # Ditto
94 assert res.user.additional_information == user.additional_information
96 assert res.user.friends == api_pb2.User.FriendshipStatus.NA
97 assert not res.user.HasField("pending_friend_request")
100def test_coords(db):
101 # make them need to update location
102 user1, token1 = generate_user(geom=create_coordinate(1, 0), geom_radius=2000, needs_to_update_location=True)
103 user2, token2 = generate_user()
105 with api_session(token2) as api:
106 res = api.Ping(api_pb2.PingReq())
107 assert res.user.city == user2.city
108 lat, lng = user2.coordinates
109 assert res.user.lat == lat
110 assert res.user.lng == lng
111 assert res.user.radius == user2.geom_radius
113 with api_session(token2) as api:
114 res = api.GetUser(api_pb2.GetUserReq(user=user1.username))
115 assert res.city == user1.city
116 assert res.lat == 1.0
117 assert res.lng == 0.0
118 assert res.radius == 2000.0
120 # Check coordinate wrapping
121 user3, token3 = generate_user(geom=create_coordinate(40.0, -180.5))
122 user4, token4 = generate_user(geom=create_coordinate(40.0, 20.0))
123 user5, token5 = generate_user(geom=create_coordinate(90.5, 20.0))
125 with api_session(token3) as api:
126 res = api.GetUser(api_pb2.GetUserReq(user=user3.username))
127 assert res.lat == 40.0
128 assert res.lng == 179.5
130 with api_session(token4) as api:
131 res = api.GetUser(api_pb2.GetUserReq(user=user4.username))
132 assert res.lat == 40.0
133 assert res.lng == 20.0
135 # PostGIS does not wrap longitude for latitude overflow
136 with api_session(token5) as api:
137 res = api.GetUser(api_pb2.GetUserReq(user=user5.username))
138 assert res.lat == 89.5
139 assert res.lng == 20.0
141 with real_jail_session(token1) as jail:
142 res = jail.JailInfo(empty_pb2.Empty())
143 assert res.jailed
144 assert res.needs_to_update_location
146 res = jail.SetLocation(
147 jail_pb2.SetLocationReq(
148 city="New York City",
149 lat=40.7812,
150 lng=-73.9647,
151 radius=250,
152 )
153 )
155 assert not res.jailed
156 assert not res.needs_to_update_location
158 res = jail.JailInfo(empty_pb2.Empty())
159 assert not res.jailed
160 assert not res.needs_to_update_location
162 with api_session(token2) as api:
163 res = api.GetUser(api_pb2.GetUserReq(user=user1.username))
164 assert res.city == "New York City"
165 assert res.lat == 40.7812
166 assert res.lng == -73.9647
167 assert res.radius == 250
170def test_get_user(db):
171 user1, token1 = generate_user()
172 user2, token2 = generate_user()
174 with api_session(token1) as api:
175 res = api.GetUser(api_pb2.GetUserReq(user=user2.username))
176 assert res.user_id == user2.id
177 assert res.username == user2.username
178 assert res.name == user2.name
180 with api_session(token1) as api:
181 res = api.GetUser(api_pb2.GetUserReq(user=str(user2.id)))
182 assert res.user_id == user2.id
183 assert res.username == user2.username
184 assert res.name == user2.name
187@pytest.mark.parametrize("flag", ["deleted_at", "banned_at"])
188def test_user_model_to_pb_ghost_user(db, flag):
189 user1, token1 = generate_user()
190 user2, _ = generate_user()
192 with session_scope() as session:
193 session.execute(update(User).where(User.id == user2.id).values(**{flag: func.now()}))
195 refresh_materialized_views_rapid(empty_pb2.Empty())
197 with api_session(token1) as api:
198 user_pb = api.GetUser(api_pb2.GetUserReq(user=user2.username))
200 assert user_pb.user_id == user2.id
201 assert user_pb.is_ghost
202 assert user_pb.username == "ghost"
203 assert user_pb.name == "Deactivated Account"
204 assert (
205 user_pb.about_me
206 == "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."
207 )
209 assert user_pb.lat == 0
210 assert user_pb.lng == 0
211 assert user_pb.radius == 0
212 assert user_pb.verification == 0.0
213 assert user_pb.community_standing == 0.0
214 assert user_pb.num_references == 0
215 assert user_pb.age == 0
216 assert user_pb.hosting_status == 0
217 assert user_pb.meetup_status == 0
218 assert user_pb.city == ""
219 assert user_pb.hometown == ""
220 assert user_pb.timezone == ""
221 assert user_pb.gender == ""
222 assert user_pb.pronouns == ""
223 assert user_pb.occupation == ""
224 assert user_pb.education == ""
225 assert user_pb.things_i_like == ""
226 assert user_pb.about_place == ""
227 assert user_pb.additional_information == ""
228 assert list(user_pb.language_abilities) == []
229 assert list(user_pb.regions_visited) == []
230 assert list(user_pb.regions_lived) == []
231 assert list(user_pb.badges) == []
232 assert user_pb.friends == api_pb2.User.FriendshipStatus.NOT_FRIENDS
233 assert user_pb.avatar_url == ""
234 assert user_pb.avatar_thumbnail_url == ""
235 assert not user_pb.has_strong_verification
237 with api_session(token1) as api:
238 lite_user_pb = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user2.username))
240 assert lite_user_pb.user_id == user2.id
241 assert lite_user_pb.is_ghost
242 assert lite_user_pb.username == "ghost"
243 assert lite_user_pb.name == "Deactivated Account"
244 assert lite_user_pb.city == ""
245 assert lite_user_pb.age == 0
246 assert lite_user_pb.avatar_url == ""
247 assert lite_user_pb.avatar_thumbnail_url == ""
248 assert lite_user_pb.lat == 0
249 assert lite_user_pb.lng == 0
250 assert lite_user_pb.radius == 0
251 assert not lite_user_pb.has_strong_verification
254def test_user_model_to_pb_ghost_user_blocked(db):
255 user1, token1 = generate_user()
256 user2, _ = generate_user()
258 with blocking_session(token1) as user_blocks:
259 user_blocks.BlockUser(blocking_pb2.BlockUserReq(username=user2.username))
261 refresh_materialized_views_rapid(empty_pb2.Empty())
263 with api_session(token1) as api:
264 user_pb = api.GetUser(api_pb2.GetUserReq(user=user2.username))
266 assert user_pb.user_id == user2.id
267 assert user_pb.is_ghost
268 assert user_pb.username == "ghost"
269 assert user_pb.name == "Deactivated Account"
270 assert (
271 user_pb.about_me
272 == "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."
273 )
275 assert user_pb.lat == 0
276 assert user_pb.lng == 0
277 assert user_pb.radius == 0
278 assert user_pb.verification == 0.0
279 assert user_pb.community_standing == 0.0
280 assert user_pb.num_references == 0
281 assert user_pb.age == 0
282 assert user_pb.hosting_status == 0
283 assert user_pb.meetup_status == 0
284 assert user_pb.city == ""
285 assert user_pb.hometown == ""
286 assert user_pb.timezone == ""
287 assert user_pb.gender == ""
288 assert user_pb.pronouns == ""
289 assert user_pb.occupation == ""
290 assert user_pb.education == ""
291 assert user_pb.things_i_like == ""
292 assert user_pb.about_place == ""
293 assert user_pb.additional_information == ""
294 assert list(user_pb.language_abilities) == []
295 assert list(user_pb.regions_visited) == []
296 assert list(user_pb.regions_lived) == []
297 assert list(user_pb.badges) == []
298 assert user_pb.friends == api_pb2.User.FriendshipStatus.NOT_FRIENDS
299 assert user_pb.avatar_url == ""
300 assert user_pb.avatar_thumbnail_url == ""
301 assert not user_pb.has_strong_verification
303 with api_session(token1) as api:
304 lite_user_pb = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user2.username))
306 assert lite_user_pb.user_id == user2.id
307 assert lite_user_pb.is_ghost
308 assert lite_user_pb.username == "ghost"
309 assert lite_user_pb.name == "Deactivated Account"
310 assert lite_user_pb.city == ""
311 assert lite_user_pb.age == 0
312 assert lite_user_pb.avatar_url == ""
313 assert lite_user_pb.avatar_thumbnail_url == ""
314 assert lite_user_pb.lat == 0
315 assert lite_user_pb.lng == 0
316 assert lite_user_pb.radius == 0
317 assert not lite_user_pb.has_strong_verification
320@pytest.mark.parametrize(
321 "flag,state",
322 [
323 ("banned_at", NonvisibleUserState.banned),
324 ("shadowed_at", NonvisibleUserState.shadowed),
325 ("deleted_at", NonvisibleUserState.deleted),
326 ],
327)
328def test_viewing_nonvisible_user_profile_is_logged(db, flag, state):
329 user1, token1 = generate_user()
330 user2, _ = generate_user()
332 with session_scope() as session:
333 session.execute(update(User).where(User.id == user2.id).values(**{flag: func.now()}))
335 refresh_materialized_views_rapid(empty_pb2.Empty())
337 with api_session(token1) as api:
338 user_pb = api.GetUser(api_pb2.GetUserReq(user=user2.username))
339 assert user_pb.is_ghost
341 with session_scope() as session:
342 access = session.execute(select(NonvisibleUserAccess)).scalar_one()
343 assert access.access_type == NonvisibleUserAccessType.ghost_served
344 assert access.target_state == state
345 assert access.target_user_id == user2.id
346 assert access.actor_user_id == user1.id
347 assert access.ip_address is None
348 assert access.user_agent is None
349 assert access.sofa is None
352def test_viewing_blocked_user_profile_is_not_logged(db):
353 user1, token1 = generate_user()
354 user2, _ = generate_user()
356 with blocking_session(token1) as user_blocks:
357 user_blocks.BlockUser(blocking_pb2.BlockUserReq(username=user2.username))
359 refresh_materialized_views_rapid(empty_pb2.Empty())
361 with api_session(token1) as api:
362 user_pb = api.GetUser(api_pb2.GetUserReq(user=user2.username))
363 assert user_pb.is_ghost
365 with session_scope() as session:
366 assert session.execute(select(func.count()).select_from(NonvisibleUserAccess)).scalar_one() == 0
369@pytest.mark.parametrize("flag", ["deleted_at", "banned_at"])
370def test_admin_viewing_ghost_users_sees_full_profile(db, flag):
371 admin, token_admin = generate_user(is_superuser=True)
372 user, _ = generate_user()
374 with session_scope() as session:
375 session.execute(update(User).where(User.id == user.id).values(**{flag: func.now()}))
377 with admin_session(token_admin) as api:
378 user_pb = api.GetUser(admin_pb2.GetUserReq(user=user.username))
380 assert user_pb.user_id == user.id
381 assert user_pb.username == user.username
382 assert user_pb.name == user.name
383 assert user_pb.city == user.city
384 assert user_pb.name != "Deactivated Account"
385 assert user_pb.username != "ghost"
386 assert user_pb.hosting_status in (
387 api_pb2.HOSTING_STATUS_UNKNOWN,
388 api_pb2.HOSTING_STATUS_CAN_HOST,
389 api_pb2.HOSTING_STATUS_MAYBE,
390 api_pb2.HOSTING_STATUS_CANT_HOST,
391 )
394def test_lite_coords(db):
395 # make them need to update location
396 user1, token1 = generate_user(geom=create_coordinate(0, 0), geom_radius=0, needs_to_update_location=True)
397 user2, token2 = generate_user()
399 refresh_materialized_views_rapid(empty_pb2.Empty())
401 with api_session(token2) as api:
402 res = api.Ping(api_pb2.PingReq())
403 assert res.user.city == user2.city
404 lat, lng = user2.coordinates
405 assert res.user.lat == lat
406 assert res.user.lng == lng
407 assert res.user.radius == user2.geom_radius
409 with api_session(token2) as api:
410 res = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user1.username))
411 assert res.city == user1.city
412 assert res.lat == 0.0
413 assert res.lng == 0.0
414 assert res.radius == 0.0
416 # Check coordinate wrapping
417 user3, token3 = generate_user(geom=create_coordinate(40.0, -180.5))
418 user4, token4 = generate_user(geom=create_coordinate(40.0, 20.0))
419 user5, token5 = generate_user(geom=create_coordinate(90.5, 20.0))
421 refresh_materialized_views_rapid(empty_pb2.Empty())
423 with api_session(token3) as api:
424 res = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user3.username))
425 assert res.lat == 40.0
426 assert res.lng == 179.5
428 with api_session(token4) as api:
429 res = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user4.username))
430 assert res.lat == 40.0
431 assert res.lng == 20.0
433 # PostGIS does not wrap longitude for latitude overflow
434 with api_session(token5) as api:
435 res = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user5.username))
436 assert res.lat == 89.5
437 assert res.lng == 20.0
439 with real_jail_session(token1) as jail:
440 res = jail.JailInfo(empty_pb2.Empty())
441 assert res.jailed
442 assert res.needs_to_update_location
444 res = jail.SetLocation(
445 jail_pb2.SetLocationReq(
446 city="New York City",
447 lat=40.7812,
448 lng=-73.9647,
449 radius=250,
450 )
451 )
453 assert not res.jailed
454 assert not res.needs_to_update_location
456 res = jail.JailInfo(empty_pb2.Empty())
457 assert not res.jailed
458 assert not res.needs_to_update_location
460 refresh_materialized_views_rapid(empty_pb2.Empty())
462 with api_session(token2) as api:
463 res = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user1.username))
464 assert res.city == "New York City"
465 assert res.lat == 40.7812
466 assert res.lng == -73.9647
467 assert res.radius == 250
470def test_lite_get_user(db):
471 user1, token1 = generate_user()
472 user2, token2 = generate_user()
474 refresh_materialized_views_rapid(empty_pb2.Empty())
476 with api_session(token1) as api:
477 res = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user2.username))
478 assert res.user_id == user2.id
479 assert res.username == user2.username
480 assert res.name == user2.name
482 with api_session(token1) as api:
483 res = api.GetLiteUser(api_pb2.GetLiteUserReq(user=str(user2.id)))
484 assert res.user_id == user2.id
485 assert res.username == user2.username
486 assert res.name == user2.name
489def test_GetLiteUsers(db):
490 user1, token1 = generate_user()
491 user2, _ = generate_user()
492 user3, _ = generate_user()
493 user4, _ = generate_user()
494 user5, _ = generate_user()
495 user6, _ = generate_user()
497 make_user_block(user4, user1)
499 refresh_materialized_views_rapid(empty_pb2.Empty())
501 with api_session(token1) as api:
502 res = api.GetLiteUsers(
503 api_pb2.GetLiteUsersReq(
504 users=[
505 user1.username,
506 str(user1.id),
507 "nonexistent",
508 str(user2.id),
509 "9994",
510 user6.username,
511 str(user5.id),
512 "notreal",
513 user4.username,
514 ]
515 )
516 )
518 assert len(res.responses) == 9
519 assert res.responses[0].query == user1.username
520 assert res.responses[0].user.user_id == user1.id
522 assert res.responses[1].query == str(user1.id)
523 assert res.responses[1].user.user_id == user1.id
525 assert res.responses[2].query == "nonexistent"
526 assert res.responses[2].not_found
528 assert res.responses[3].query == str(user2.id)
529 assert res.responses[3].user.user_id == user2.id
531 assert res.responses[4].query == "9994"
532 assert res.responses[4].not_found
534 assert res.responses[5].query == user6.username
535 assert res.responses[5].user.user_id == user6.id
537 assert res.responses[6].query == str(user5.id)
538 assert res.responses[6].user.user_id == user5.id
540 assert res.responses[7].query == "notreal"
541 assert res.responses[7].not_found
543 # blocked - should return ghost profile
544 assert res.responses[8].query == user4.username
545 assert not res.responses[8].not_found
546 assert res.responses[8].user.user_id == user4.id
547 assert res.responses[8].user.is_ghost
548 assert res.responses[8].user.username == "ghost"
549 assert res.responses[8].user.name == "Deactivated Account"
551 with api_session(token1) as api:
552 with pytest.raises(grpc.RpcError) as e:
553 api.GetLiteUsers(api_pb2.GetLiteUsersReq(users=201 * [user1.username]))
554 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
555 assert e.value.details() == "You can't request that many users at a time."
558def test_update_profile(db):
559 user, token = generate_user()
561 with api_session(token) as api:
562 with pytest.raises(grpc.RpcError) as e:
563 api.UpdateProfile(api_pb2.UpdateProfileReq(name=wrappers_pb2.StringValue(value=" ")))
564 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
565 assert e.value.details() == "Name not supported."
567 with pytest.raises(grpc.RpcError) as e:
568 api.UpdateProfile(
569 api_pb2.UpdateProfileReq(lat=wrappers_pb2.DoubleValue(value=0), lng=wrappers_pb2.DoubleValue(value=0))
570 )
571 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
572 assert e.value.details() == "Invalid coordinate."
574 with pytest.raises(grpc.RpcError) as e:
575 api.UpdateProfile(
576 api_pb2.UpdateProfileReq(regions_visited=api_pb2.RepeatedStringValue(value=["United States"]))
577 )
578 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
579 assert e.value.details() == "Invalid region."
581 with pytest.raises(grpc.RpcError) as e:
582 api.UpdateProfile(
583 api_pb2.UpdateProfileReq(regions_lived=api_pb2.RepeatedStringValue(value=["United Kingdom"]))
584 )
585 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
586 assert e.value.details() == "Invalid region."
588 api.UpdateProfile(
589 api_pb2.UpdateProfileReq(
590 name=wrappers_pb2.StringValue(value="New name"),
591 city=wrappers_pb2.StringValue(value="Timbuktu"),
592 hometown=api_pb2.NullableStringValue(value="Walla Walla"),
593 lat=wrappers_pb2.DoubleValue(value=0.01),
594 lng=wrappers_pb2.DoubleValue(value=-2),
595 radius=wrappers_pb2.DoubleValue(value=321),
596 pronouns=api_pb2.NullableStringValue(value="Ro, Robo, Robots"),
597 occupation=api_pb2.NullableStringValue(value="Testing"),
598 education=api_pb2.NullableStringValue(value="Couchers U"),
599 about_me=api_pb2.NullableStringValue(value="I rule"),
600 things_i_like=api_pb2.NullableStringValue(value="Couchers"),
601 about_place=api_pb2.NullableStringValue(value="My place"),
602 hosting_status=api_pb2.HOSTING_STATUS_CAN_HOST,
603 meetup_status=api_pb2.MEETUP_STATUS_WANTS_TO_MEETUP,
604 language_abilities=api_pb2.RepeatedLanguageAbilityValue(
605 value=[
606 api_pb2.LanguageAbility(
607 code="eng",
608 fluency=api_pb2.LanguageAbility.Fluency.FLUENCY_FLUENT,
609 )
610 ],
611 ),
612 regions_visited=api_pb2.RepeatedStringValue(value=["CXR", "FIN"]),
613 regions_lived=api_pb2.RepeatedStringValue(value=["USA", "EST"]),
614 additional_information=api_pb2.NullableStringValue(value="I <3 Couchers"),
615 )
616 )
618 user_details = api.GetUser(api_pb2.GetUserReq(user=user.username))
619 assert user_details.name == "New name"
620 assert user_details.city == "Timbuktu"
621 assert user_details.hometown == "Walla Walla"
622 assert user_details.pronouns == "Ro, Robo, Robots"
623 assert user_details.education == "Couchers U"
624 assert user_details.things_i_like == "Couchers"
625 assert user_details.lat == 0.01
626 assert user_details.lng == -2
627 assert user_details.radius == 321
628 assert user_details.occupation == "Testing"
629 assert user_details.about_me == "I rule"
630 assert user_details.about_place == "My place"
631 assert user_details.hosting_status == api_pb2.HOSTING_STATUS_CAN_HOST
632 assert user_details.meetup_status == api_pb2.MEETUP_STATUS_WANTS_TO_MEETUP
633 assert user_details.language_abilities[0].code == "eng"
634 assert user_details.language_abilities[0].fluency == api_pb2.LanguageAbility.Fluency.FLUENCY_FLUENT
635 assert user_details.additional_information == "I <3 Couchers"
636 assert user_details.regions_visited == ["CXR", "FIN"]
637 assert user_details.regions_lived == ["EST", "USA"]
639 # Test unset values
640 api.UpdateProfile(
641 api_pb2.UpdateProfileReq(
642 hometown=api_pb2.NullableStringValue(is_null=True),
643 radius=wrappers_pb2.DoubleValue(value=0),
644 pronouns=api_pb2.NullableStringValue(is_null=True),
645 occupation=api_pb2.NullableStringValue(is_null=True),
646 education=api_pb2.NullableStringValue(is_null=True),
647 about_me=api_pb2.NullableStringValue(is_null=True),
648 things_i_like=api_pb2.NullableStringValue(is_null=True),
649 about_place=api_pb2.NullableStringValue(is_null=True),
650 hosting_status=api_pb2.HOSTING_STATUS_CAN_HOST,
651 meetup_status=api_pb2.MEETUP_STATUS_WANTS_TO_MEETUP,
652 language_abilities=api_pb2.RepeatedLanguageAbilityValue(value=[]),
653 regions_visited=api_pb2.RepeatedStringValue(value=[]),
654 regions_lived=api_pb2.RepeatedStringValue(value=[]),
655 additional_information=api_pb2.NullableStringValue(is_null=True),
656 )
657 )
659 user_details = api.GetUser(api_pb2.GetUserReq(user=user.username))
660 assert not user_details.hometown
661 assert not user_details.radius
662 assert not user_details.pronouns
663 assert not user_details.occupation
664 assert not user_details.education
665 assert not user_details.about_me
666 assert not user_details.things_i_like
667 assert not user_details.about_place
668 assert user_details.hosting_status == api_pb2.HOSTING_STATUS_CAN_HOST
669 assert user_details.meetup_status == api_pb2.MEETUP_STATUS_WANTS_TO_MEETUP
670 assert not user_details.language_abilities
671 assert not user_details.regions_visited
672 assert not user_details.regions_lived
673 assert not user_details.additional_information
676def test_update_profile_do_not_email(db):
677 user, token = generate_user()
679 with notifications_session(token) as notifications:
680 notifications.SetNotificationSettings(notifications_pb2.SetNotificationSettingsReq(enable_do_not_email=True))
682 with api_session(token) as api:
683 with pytest.raises(grpc.RpcError) as e:
684 api.UpdateProfile(api_pb2.UpdateProfileReq(hosting_status=api_pb2.HOSTING_STATUS_CAN_HOST))
685 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
686 assert e.value.details() == "You cannot enable hosting while you have emails turned off in your settings."
688 with pytest.raises(grpc.RpcError) as e:
689 api.UpdateProfile(api_pb2.UpdateProfileReq(meetup_status=api_pb2.MEETUP_STATUS_OPEN_TO_MEETUP))
690 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
691 assert e.value.details() == "You cannot enable meeting up while you have emails turned off in your settings."
694def test_language_abilities(db):
695 user, token = generate_user(
696 language_abilities=[
697 ("fin", LanguageFluency.fluent),
698 ("fra", LanguageFluency.beginner),
699 ],
700 )
702 with api_session(token) as api:
703 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
704 assert len(res.language_abilities) == 2
706 # can't add non-existent languages
707 with pytest.raises(grpc.RpcError) as err:
708 api.UpdateProfile(
709 api_pb2.UpdateProfileReq(
710 language_abilities=api_pb2.RepeatedLanguageAbilityValue(
711 value=[
712 api_pb2.LanguageAbility(
713 code="QQQ",
714 fluency=api_pb2.LanguageAbility.Fluency.FLUENCY_FLUENT,
715 )
716 ],
717 ),
718 )
719 )
720 assert err.value.code() == grpc.StatusCode.INVALID_ARGUMENT
721 assert err.value.details() == "Invalid language."
723 # can't have multiple languages of the same type
724 with pytest.raises(Exception) as err2:
725 api.UpdateProfile(
726 api_pb2.UpdateProfileReq(
727 language_abilities=api_pb2.RepeatedLanguageAbilityValue(
728 value=[
729 api_pb2.LanguageAbility(
730 code="eng",
731 fluency=api_pb2.LanguageAbility.Fluency.FLUENCY_FLUENT,
732 ),
733 api_pb2.LanguageAbility(
734 code="eng",
735 fluency=api_pb2.LanguageAbility.Fluency.FLUENCY_FLUENT,
736 ),
737 ],
738 ),
739 )
740 )
741 assert "violates unique constraint" in str(err2.value)
743 # nothing changed
744 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
745 assert len(res.language_abilities) == 2
747 # now actually add a value
748 api.UpdateProfile(
749 api_pb2.UpdateProfileReq(
750 language_abilities=api_pb2.RepeatedLanguageAbilityValue(
751 value=[
752 api_pb2.LanguageAbility(
753 code="eng",
754 fluency=api_pb2.LanguageAbility.Fluency.FLUENCY_FLUENT,
755 )
756 ],
757 ),
758 )
759 )
761 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
762 assert len(res.language_abilities) == 1
763 assert res.language_abilities[0].code == "eng"
764 assert res.language_abilities[0].fluency == api_pb2.LanguageAbility.Fluency.FLUENCY_FLUENT
766 # change the value to a new one
767 api.UpdateProfile(
768 api_pb2.UpdateProfileReq(
769 language_abilities=api_pb2.RepeatedLanguageAbilityValue(
770 value=[
771 api_pb2.LanguageAbility(
772 code="fin",
773 fluency=api_pb2.LanguageAbility.Fluency.FLUENCY_BEGINNER,
774 )
775 ],
776 ),
777 )
778 )
780 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
781 assert len(res.language_abilities) == 1
782 assert res.language_abilities[0].code == "fin"
783 assert res.language_abilities[0].fluency == api_pb2.LanguageAbility.Fluency.FLUENCY_BEGINNER
785 # should be able to set to same value still
786 api.UpdateProfile(
787 api_pb2.UpdateProfileReq(
788 language_abilities=api_pb2.RepeatedLanguageAbilityValue(
789 value=[
790 api_pb2.LanguageAbility(
791 code="fin",
792 fluency=api_pb2.LanguageAbility.Fluency.FLUENCY_BEGINNER,
793 )
794 ],
795 ),
796 )
797 )
799 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
800 assert len(res.language_abilities) == 1
801 assert res.language_abilities[0].code == "fin"
802 assert res.language_abilities[0].fluency == api_pb2.LanguageAbility.Fluency.FLUENCY_BEGINNER
804 # don't change it
805 api.UpdateProfile(api_pb2.UpdateProfileReq())
807 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
808 assert len(res.language_abilities) == 1
809 assert res.language_abilities[0].code == "fin"
810 assert res.language_abilities[0].fluency == api_pb2.LanguageAbility.Fluency.FLUENCY_BEGINNER
812 # remove value
813 api.UpdateProfile(
814 api_pb2.UpdateProfileReq(
815 language_abilities=api_pb2.RepeatedLanguageAbilityValue(
816 value=[],
817 ),
818 )
819 )
821 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
822 assert len(res.language_abilities) == 0
825def test_pending_friend_request_count(db, moderator):
826 user1, token1 = generate_user()
827 user2, token2 = generate_user()
828 user3, token3 = generate_user()
830 with api_session(token2) as api:
831 res = api.Ping(api_pb2.PingReq())
832 assert res.pending_friend_request_count == 0
834 with api_session(token1) as api:
835 res = api.Ping(api_pb2.PingReq())
836 assert res.pending_friend_request_count == 0
837 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
838 # Sender can still see their own sent requests (even while SHADOWED)
839 res = api.Ping(api_pb2.PingReq())
840 assert res.pending_friend_request_count == 0
842 # Get friend request ID from sender's view (author can see SHADOWED)
843 with api_session(token1) as api:
844 res = api.ListFriendRequests(empty_pb2.Empty())
845 assert len(res.sent) == 1
846 fr_id = res.sent[0].friend_request_id
848 # Recipient cannot see SHADOWED friend requests before mod approval
849 with api_session(token2) as api:
850 res = api.Ping(api_pb2.PingReq())
851 assert res.pending_friend_request_count == 0
853 # Moderator approves the friend request
854 moderator.approve_friend_request(fr_id)
856 # Now recipient can see the approved friend request
857 with api_session(token2) as api:
858 res = api.Ping(api_pb2.PingReq())
859 assert res.pending_friend_request_count == 1
861 with api_session(token2) as api:
862 # check it's there
863 res = api.ListFriendRequests(empty_pb2.Empty())
864 assert len(res.sent) == 0
865 assert len(res.received) == 1
867 assert res.received[0].state == api_pb2.FriendRequest.FriendRequestStatus.PENDING
868 assert res.received[0].user_id == user1.id
870 fr_id = res.received[0].friend_request_id
872 # accept it
873 api.RespondFriendRequest(api_pb2.RespondFriendRequestReq(friend_request_id=fr_id, accept=True))
875 res = api.Ping(api_pb2.PingReq())
876 assert res.pending_friend_request_count == 0
879def test_friend_request_flow(db, email_collector: EmailCollector, push_collector: PushCollector, moderator):
880 user1, token1 = generate_user(complete_profile=True)
881 user2, token2 = generate_user(complete_profile=True)
882 user3, token3 = generate_user()
884 # send a friend request from user1 to user2
885 with api_session(token1) as api:
886 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
888 with session_scope() as session:
889 friend_request = session.execute(
890 select(FriendRelationship).where(
891 FriendRelationship.from_user_id == user1.id, FriendRelationship.to_user_id == user2.id
892 )
893 ).scalar_one()
894 friend_request_id = friend_request.id
896 # Notification is deferred while content is SHADOWED
897 # No push notification sent yet
898 assert push_collector.count_for_user(user2.id) == 0
900 with api_session(token1) as api:
901 # Sender can see their own sent requests (even while SHADOWED)
902 res = api.ListFriendRequests(empty_pb2.Empty())
903 assert len(res.sent) == 1
904 assert len(res.received) == 0
906 assert res.sent[0].state == api_pb2.FriendRequest.FriendRequestStatus.PENDING
907 assert res.sent[0].user_id == user2.id
908 assert res.sent[0].friend_request_id == friend_request_id
910 # Recipient cannot see SHADOWED friend requests
911 with api_session(token2) as api:
912 res = api.ListFriendRequests(empty_pb2.Empty())
913 assert len(res.sent) == 0
914 assert len(res.received) == 0
916 # Moderator approves the friend request - this triggers the notification
917 moderator.approve_friend_request(friend_request_id)
919 push = push_collector.pop_for_user(user2.id, last=True)
920 assert push.content.title == f"Friend request from {user1.name}"
921 assert push.content.body == f"{user1.name} wants to be your friend."
922 assert push.content.action_url == f"http://localhost:3000/connections/friends/?from={user1.id}"
924 email = email_collector.pop_for_recipient(user2.email, last=True)
925 assert email.recipient == user2.email
926 assert email.subject == f"[TEST] {user1.name} wants to be your friend on Couchers.org!"
927 assert user2.name in email.plain
928 assert user2.name in email.html
929 assert user1.name in email.plain
930 assert user1.name in email.html
931 assert "http://localhost:5001/img/thumbnail/" not in email.plain
932 assert "http://localhost:5001/img/thumbnail/" in email.html
933 assert "http://localhost:3000/connections/friends/" in email.plain
934 assert "http://localhost:3000/connections/friends/" in email.html
936 # Now recipient can see the approved friend request
937 with api_session(token2) as api:
938 # check it's there
939 res = api.ListFriendRequests(empty_pb2.Empty())
940 assert len(res.sent) == 0
941 assert len(res.received) == 1
943 assert res.received[0].state == api_pb2.FriendRequest.FriendRequestStatus.PENDING
944 assert res.received[0].user_id == user1.id
946 fr_id = res.received[0].friend_request_id
948 # accept it
949 api.RespondFriendRequest(api_pb2.RespondFriendRequestReq(friend_request_id=fr_id, accept=True))
951 # check it's gone
952 res = api.ListFriendRequests(empty_pb2.Empty())
953 assert len(res.sent) == 0
954 assert len(res.received) == 0
956 # check we're friends now
957 res = api.ListFriends(empty_pb2.Empty())
958 assert len(res.user_ids) == 1
959 assert res.user_ids[0] == user1.id
961 # user2 got one push (from the friend request creation)
962 # user1 should now have one push (from the friend request acceptance)
963 push = push_collector.pop_for_user(user1.id, last=True)
964 assert push.content.title == f"{user2.name} accepted your friend request"
965 assert push.content.body == f"You are now friends with {user2.name}."
967 email = email_collector.pop_for_recipient(user1.email, last=True)
968 assert email.recipient == user1.email
969 assert email.subject == f"[TEST] {user2.name} accepted your friend request!"
970 assert user1.name in email.plain
971 assert user1.name in email.html
972 assert user2.name in email.plain
973 assert user2.name in email.html
974 assert "http://localhost:5001/img/thumbnail/" not in email.plain
975 assert "http://localhost:5001/img/thumbnail/" in email.html
976 assert f"http://localhost:3000/user/{user2.username}" in email.plain
977 assert f"http://localhost:3000/user/{user2.username}" in email.html
979 with api_session(token1) as api:
980 # check it's gone
981 res = api.ListFriendRequests(empty_pb2.Empty())
982 assert len(res.sent) == 0
983 assert len(res.received) == 0
985 # check we're friends now
986 res = api.ListFriends(empty_pb2.Empty())
987 assert len(res.user_ids) == 1
988 assert res.user_ids[0] == user2.id
990 with api_session(token1) as api:
991 # we can't unfriend if we aren't friends
992 with pytest.raises(grpc.RpcError) as err:
993 api.RemoveFriend(api_pb2.RemoveFriendReq(user_id=user3.id))
994 assert err.value.code() == grpc.StatusCode.FAILED_PRECONDITION
995 assert err.value.details() == "You aren't friends with that user!"
997 # we can unfriend
998 res = api.RemoveFriend(api_pb2.RemoveFriendReq(user_id=user2.id))
1000 res = api.ListFriends(empty_pb2.Empty())
1001 assert len(res.user_ids) == 0
1004def test_RemoveFriend_regression(db, push_collector: PushCollector, moderator):
1005 user1, token1 = generate_user(complete_profile=True)
1006 user2, token2 = generate_user(complete_profile=True)
1007 user3, token3 = generate_user()
1008 user4, token4 = generate_user()
1009 user5, token5 = generate_user()
1010 user6, token6 = generate_user()
1012 # Send friend requests
1013 with api_session(token4) as api:
1014 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user1.id))
1015 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
1017 with api_session(token5) as api:
1018 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user1.id))
1020 with api_session(token1) as api:
1021 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
1022 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user3.id))
1024 # Approve all friend requests via moderation
1025 with session_scope() as session:
1026 friend_requests = session.execute(select(FriendRelationship)).scalars().all()
1027 for fr in friend_requests:
1028 moderator.approve_friend_request(fr.id)
1030 # Now recipients can respond
1031 with api_session(token1) as api:
1032 api.RespondFriendRequest(
1033 api_pb2.RespondFriendRequestReq(
1034 friend_request_id=api.ListFriendRequests(empty_pb2.Empty()).received[0].friend_request_id, accept=True
1035 )
1036 )
1038 with api_session(token2) as api:
1039 for fr in api.ListFriendRequests(empty_pb2.Empty()).received:
1040 api.RespondFriendRequest(
1041 api_pb2.RespondFriendRequestReq(friend_request_id=fr.friend_request_id, accept=True)
1042 )
1044 with api_session(token1) as api:
1045 res = api.ListFriends(empty_pb2.Empty())
1046 assert sorted(res.user_ids) == sorted([user2.id, user4.id])
1048 api.RemoveFriend(api_pb2.RemoveFriendReq(user_id=user2.id))
1050 res = api.ListFriends(empty_pb2.Empty())
1051 assert sorted(res.user_ids) == [user4.id]
1053 api.RemoveFriend(api_pb2.RemoveFriendReq(user_id=user4.id))
1055 res = api.ListFriends(empty_pb2.Empty())
1056 assert not res.user_ids
1059def test_cant_friend_request_twice(db):
1060 user1, token1 = generate_user()
1061 user2, token2 = generate_user()
1063 # send friend request from user1 to user2
1064 with api_session(token1) as api:
1065 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
1067 with pytest.raises(grpc.RpcError) as e:
1068 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
1069 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
1070 assert e.value.details() == "You are already friends with or have sent a friend request to that user."
1073def test_cant_friend_request_pending(db):
1074 user1, token1 = generate_user()
1075 user2, token2 = generate_user()
1077 # send friend request from user1 to user2
1078 with api_session(token1) as api:
1079 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
1081 with api_session(token2) as api:
1082 with pytest.raises(grpc.RpcError) as e:
1083 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user1.id))
1084 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
1085 assert e.value.details() == "You are already friends with or have sent a friend request to that user."
1088def test_cant_friend_request_already_friends(db):
1089 user1, token1 = generate_user()
1090 user2, token2 = generate_user()
1091 make_friends(user1, user2)
1093 with api_session(token1) as api:
1094 with pytest.raises(grpc.RpcError) as e:
1095 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
1096 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
1097 assert e.value.details() == "You are already friends with or have sent a friend request to that user."
1099 with api_session(token2) as api:
1100 with pytest.raises(grpc.RpcError) as e:
1101 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user1.id))
1102 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
1103 assert e.value.details() == "You are already friends with or have sent a friend request to that user."
1106def test_cant_friend_request_incomplete_profile(db):
1107 user1, token1 = generate_user(complete_profile=False)
1108 user2, token2 = generate_user(complete_profile=True)
1110 with api_session(token1) as api:
1111 with pytest.raises(grpc.RpcError) as e:
1112 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
1113 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
1114 assert e.value.details() == "You have to complete your profile before you can send a friend request."
1116 # the other direction should still work
1117 with api_session(token2) as api:
1118 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user1.id))
1121def test_excessive_friend_requests_are_reported(db, email_collector: EmailCollector):
1122 """Test that excessive friend requests are first reported in a warning email and finally lead blocking of further requests."""
1123 user, token = generate_user()
1124 rate_limit_definition = RATE_LIMIT_DEFINITIONS[RateLimitAction.friend_request]
1125 with api_session(token) as api:
1126 # Test warning email
1127 for _ in range(rate_limit_definition.warning_limit):
1128 friend_user, _ = generate_user()
1129 _ = api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=friend_user.id))
1131 assert email_collector.count_for_reports() == 0
1132 friend_user, _ = generate_user()
1133 _ = api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=friend_user.id))
1135 email = email_collector.pop_for_reports(last=True)
1136 assert email.plain.startswith(
1137 f"User {user.username} has sent {rate_limit_definition.warning_limit} friend requests in the past {RATE_LIMIT_HOURS} hours."
1138 )
1140 # Test ban after exceeding FRIEND_REQUEST_HARD_LIMIT
1141 for _ in range(rate_limit_definition.hard_limit - rate_limit_definition.warning_limit - 1):
1142 friend_user, _ = generate_user()
1143 _ = api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=friend_user.id))
1145 assert email_collector.count_for_reports() == 0
1146 friend_user, _ = generate_user()
1147 with pytest.raises(grpc.RpcError) as exc_info:
1148 _ = api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=friend_user.id))
1149 assert exc_info.value.code() == grpc.StatusCode.RESOURCE_EXHAUSTED
1150 assert (
1151 exc_info.value.details()
1152 == "You have sent a lot of friend requests in the past 24 hours. To avoid spam, you can't send any more for now."
1153 )
1155 email = email_collector.pop_for_reports(last=True)
1156 assert email.plain.startswith(
1157 f"User {user.username} has sent {rate_limit_definition.hard_limit} friend requests in the past {RATE_LIMIT_HOURS} hours."
1158 )
1159 assert "The user has been blocked from sending further friend requests for now." in email.plain
1162def test_ListFriends(db, moderator):
1163 user1, token1 = generate_user()
1164 user2, token2 = generate_user()
1165 user3, token3 = generate_user()
1167 # send friend request from user1 to user2 and user3
1168 with api_session(token1) as api:
1169 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
1170 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user3.id))
1171 # sender can see their sent requests (they are the author)
1172 res = api.ListFriendRequests(empty_pb2.Empty())
1173 assert len(res.sent) == 2
1174 user1_to_user2_id = [req for req in res.sent if req.user_id == user2.id][0].friend_request_id
1175 user1_to_user3_id = [req for req in res.sent if req.user_id == user3.id][0].friend_request_id
1177 with api_session(token3) as api:
1178 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
1179 res = api.ListFriendRequests(empty_pb2.Empty())
1180 user3_to_user2_id = res.sent[0].friend_request_id
1182 # Moderator approves the friend requests so recipients can see them
1183 moderator.approve_friend_request(user1_to_user2_id)
1184 moderator.approve_friend_request(user3_to_user2_id)
1186 with api_session(token2) as api:
1187 res = api.ListFriendRequests(empty_pb2.Empty())
1188 assert len(res.received) == 2
1190 # order is an implementation detail
1191 user1_req = [req for req in res.received if req.user_id == user1.id][0]
1192 user3_req = [req for req in res.received if req.user_id == user3.id][0]
1194 assert user1_req.state == api_pb2.FriendRequest.FriendRequestStatus.PENDING
1195 assert user1_req.user_id == user1.id
1196 api.RespondFriendRequest(
1197 api_pb2.RespondFriendRequestReq(friend_request_id=user1_req.friend_request_id, accept=True)
1198 )
1200 assert user3_req.state == api_pb2.FriendRequest.FriendRequestStatus.PENDING
1201 assert user3_req.user_id == user3.id
1202 api.RespondFriendRequest(
1203 api_pb2.RespondFriendRequestReq(friend_request_id=user3_req.friend_request_id, accept=True)
1204 )
1206 # check we now have two friends
1207 res = api.ListFriends(empty_pb2.Empty())
1208 assert len(res.user_ids) == 2
1209 assert user1.id in res.user_ids
1210 assert user3.id in res.user_ids
1212 # Moderator approves user1's friend request to user3 so user3 can see it
1213 moderator.approve_friend_request(user1_to_user3_id)
1215 with api_session(token3) as api:
1216 res = api.ListFriends(empty_pb2.Empty())
1217 assert len(res.user_ids) == 1
1218 assert user2.id in res.user_ids
1220 res = api.ListFriendRequests(empty_pb2.Empty())
1221 assert len(res.received) == 1
1222 assert res.received[0].state == api_pb2.FriendRequest.FriendRequestStatus.PENDING
1223 assert res.received[0].user_id == user1.id
1224 fr_id = res.received[0].friend_request_id
1225 api.RespondFriendRequest(api_pb2.RespondFriendRequestReq(friend_request_id=fr_id, accept=True))
1227 res = api.ListFriends(empty_pb2.Empty())
1228 assert len(res.user_ids) == 2
1229 assert user1.id in res.user_ids
1230 assert user2.id in res.user_ids
1232 with api_session(token1) as api:
1233 res = api.ListFriends(empty_pb2.Empty())
1234 assert len(res.user_ids) == 2
1235 assert user2.id in res.user_ids
1236 assert user3.id in res.user_ids
1239def test_ListMutualFriends(db):
1240 user1, token1 = generate_user()
1241 user2, token2 = generate_user()
1242 user3, token3 = generate_user()
1243 user4, token4 = generate_user()
1244 user5, token5 = generate_user()
1246 # arrange friends like this: 1<->2, 1<->3, 1<->4, 1<->5, 3<->2, 3<->4,
1247 # so 1 and 2 should have mutual friend 3 only
1248 make_friends(user1, user2)
1249 make_friends(user1, user3)
1250 make_friends(user1, user4)
1251 make_friends(user1, user5)
1252 make_friends(user3, user2)
1253 make_friends(user3, user4)
1255 with api_session(token1) as api:
1256 mutual_friends = api.ListMutualFriends(api_pb2.ListMutualFriendsReq(user_id=user2.id)).mutual_friends
1257 assert len(mutual_friends) == 1
1258 assert mutual_friends[0].user_id == user3.id
1260 # and other way around same
1261 with api_session(token2) as api:
1262 mutual_friends = api.ListMutualFriends(api_pb2.ListMutualFriendsReq(user_id=user1.id)).mutual_friends
1263 assert len(mutual_friends) == 1
1264 assert mutual_friends[0].user_id == user3.id
1266 # Check pending request doesn't have effect
1267 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user5.id))
1269 mutual_friends = api.ListMutualFriends(api_pb2.ListMutualFriendsReq(user_id=user1.id)).mutual_friends
1270 assert len(mutual_friends) == 1
1271 assert mutual_friends[0].user_id == user3.id
1273 # both ways
1274 with api_session(token1) as api:
1275 mutual_friends = api.ListMutualFriends(api_pb2.ListMutualFriendsReq(user_id=user2.id)).mutual_friends
1276 assert len(mutual_friends) == 1
1277 assert mutual_friends[0].user_id == user3.id
1280def test_mutual_friends_self(db):
1281 user1, token1 = generate_user()
1282 user2, token2 = generate_user()
1283 user3, token3 = generate_user()
1284 user4, token4 = generate_user()
1286 make_friends(user1, user2)
1287 make_friends(user2, user3)
1288 make_friends(user1, user4)
1290 with api_session(token1) as api:
1291 res = api.ListMutualFriends(api_pb2.ListMutualFriendsReq(user_id=user1.id))
1292 assert len(res.mutual_friends) == 0
1294 with api_session(token2) as api:
1295 res = api.ListMutualFriends(api_pb2.ListMutualFriendsReq(user_id=user2.id))
1296 assert len(res.mutual_friends) == 0
1298 with api_session(token3) as api:
1299 res = api.ListMutualFriends(api_pb2.ListMutualFriendsReq(user_id=user3.id))
1300 assert len(res.mutual_friends) == 0
1302 with api_session(token4) as api:
1303 res = api.ListMutualFriends(api_pb2.ListMutualFriendsReq(user_id=user4.id))
1304 assert len(res.mutual_friends) == 0
1307def test_CancelFriendRequest(db):
1308 user1, token1 = generate_user()
1309 user2, token2 = generate_user()
1311 with api_session(token1) as api:
1312 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
1314 res = api.ListFriendRequests(empty_pb2.Empty())
1315 assert res.sent[0].user_id == user2.id
1316 fr_id = res.sent[0].friend_request_id
1318 api.CancelFriendRequest(api_pb2.CancelFriendRequestReq(friend_request_id=fr_id))
1320 # check it's gone
1321 res = api.ListFriendRequests(empty_pb2.Empty())
1322 assert len(res.sent) == 0
1323 assert len(res.received) == 0
1325 # check not friends
1326 res = api.ListFriends(empty_pb2.Empty())
1327 assert len(res.user_ids) == 0
1329 with api_session(token2) as api:
1330 # check it's gone
1331 res = api.ListFriendRequests(empty_pb2.Empty())
1332 assert len(res.sent) == 0
1333 assert len(res.received) == 0
1335 # check we're not friends
1336 res = api.ListFriends(empty_pb2.Empty())
1337 assert len(res.user_ids) == 0
1339 with api_session(token1) as api:
1340 # check we can send another friend req
1341 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
1343 res = api.ListFriendRequests(empty_pb2.Empty())
1344 assert res.sent[0].state == api_pb2.FriendRequest.FriendRequestStatus.PENDING
1345 assert res.sent[0].user_id == user2.id
1348def test_accept_friend_request(db, moderator):
1349 user1, token1 = generate_user()
1350 user2, token2 = generate_user()
1352 with session_scope() as session:
1353 moderation_state = ModerationState(
1354 object_type=ModerationObjectType.friend_request,
1355 object_id=0,
1356 visibility=ModerationVisibility.visible,
1357 )
1358 session.add(moderation_state)
1359 session.flush()
1360 friend_request = FriendRelationship(
1361 from_user_id=user1.id,
1362 to_user_id=user2.id,
1363 status=FriendStatus.pending,
1364 moderation_state_id=moderation_state.id,
1365 )
1366 session.add(friend_request)
1367 session.flush()
1368 moderation_state.object_id = friend_request.id
1369 session.commit()
1370 friend_request_id = friend_request.id
1372 with api_session(token2) as api:
1373 # check request pending
1374 res = api.ListFriendRequests(empty_pb2.Empty())
1375 assert len(res.received) == 1
1376 assert res.received[0].user_id == user1.id
1378 api.RespondFriendRequest(api_pb2.RespondFriendRequestReq(friend_request_id=friend_request_id, accept=True))
1380 # check request is gone
1381 res = api.ListFriendRequests(empty_pb2.Empty())
1382 assert len(res.sent) == 0
1383 assert len(res.received) == 0
1385 # check now friends
1386 res = api.ListFriends(empty_pb2.Empty())
1387 assert len(res.user_ids) == 1
1388 assert res.user_ids[0] == user1.id
1390 with api_session(token1) as api:
1391 # check request gone
1392 res = api.ListFriendRequests(empty_pb2.Empty())
1393 assert len(res.sent) == 0
1394 assert len(res.received) == 0
1396 # check now friends
1397 res = api.ListFriends(empty_pb2.Empty())
1398 assert len(res.user_ids) == 1
1399 assert res.user_ids[0] == user2.id
1402def test_reject_friend_request(db, moderator):
1403 user1, token1 = generate_user()
1404 user2, token2 = generate_user()
1406 with api_session(token1) as api:
1407 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
1409 res = api.ListFriendRequests(empty_pb2.Empty())
1410 assert res.sent[0].state == api_pb2.FriendRequest.FriendRequestStatus.PENDING
1411 assert res.sent[0].user_id == user2.id
1412 fr_id = res.sent[0].friend_request_id
1414 # Moderator approves the friend request so recipient can see it
1415 moderator.approve_friend_request(fr_id)
1417 with api_session(token2) as api:
1418 res = api.ListFriendRequests(empty_pb2.Empty())
1419 assert res.received[0].state == api_pb2.FriendRequest.FriendRequestStatus.PENDING
1420 assert res.received[0].user_id == user1.id
1422 fr_id = res.received[0].friend_request_id
1424 # reject it
1425 api.RespondFriendRequest(api_pb2.RespondFriendRequestReq(friend_request_id=fr_id, accept=False))
1427 # check it's gone
1428 res = api.ListFriendRequests(empty_pb2.Empty())
1429 assert len(res.sent) == 0
1430 assert len(res.received) == 0
1432 # check not friends
1433 res = api.ListFriends(empty_pb2.Empty())
1434 assert len(res.user_ids) == 0
1436 with api_session(token1) as api:
1437 # check it's gone
1438 res = api.ListFriendRequests(empty_pb2.Empty())
1439 assert len(res.sent) == 0
1440 assert len(res.received) == 0
1442 # check we're not friends
1443 res = api.ListFriends(empty_pb2.Empty())
1444 assert len(res.user_ids) == 0
1446 # check we can send another friend req
1447 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
1449 res = api.ListFriendRequests(empty_pb2.Empty())
1450 assert res.sent[0].state == api_pb2.FriendRequest.FriendRequestStatus.PENDING
1451 assert res.sent[0].user_id == user2.id
1454def test_hosting_preferences(db):
1455 user1, token1 = generate_user()
1456 user2, token2 = generate_user()
1458 with api_session(token1) as api:
1459 res = api.GetUser(api_pb2.GetUserReq(user=user1.username))
1460 assert not res.HasField("max_guests")
1461 assert not res.HasField("last_minute")
1462 assert not res.HasField("has_pets")
1463 assert not res.HasField("accepts_pets")
1464 assert not res.HasField("pet_details")
1465 assert not res.HasField("has_kids")
1466 assert not res.HasField("accepts_kids")
1467 assert not res.HasField("kid_details")
1468 assert not res.HasField("has_housemates")
1469 assert not res.HasField("housemate_details")
1470 assert not res.HasField("wheelchair_accessible")
1471 assert res.smoking_allowed == api_pb2.SMOKING_LOCATION_UNKNOWN
1472 assert not res.HasField("smokes_at_home")
1473 assert not res.HasField("drinking_allowed")
1474 assert not res.HasField("drinks_at_home")
1475 assert not res.HasField("other_host_info")
1476 assert res.sleeping_arrangement == api_pb2.SLEEPING_ARRANGEMENT_UNKNOWN
1477 assert not res.HasField("sleeping_details")
1478 assert not res.HasField("area")
1479 assert not res.HasField("house_rules")
1480 assert not res.HasField("parking")
1481 assert res.parking_details == api_pb2.PARKING_DETAILS_UNKNOWN
1482 assert not res.HasField("camping_ok")
1484 api.UpdateProfile(
1485 api_pb2.UpdateProfileReq(
1486 max_guests=api_pb2.NullableUInt32Value(value=3),
1487 last_minute=api_pb2.NullableBoolValue(value=True),
1488 has_pets=api_pb2.NullableBoolValue(value=False),
1489 accepts_pets=api_pb2.NullableBoolValue(value=True),
1490 pet_details=api_pb2.NullableStringValue(value="I love dogs"),
1491 has_kids=api_pb2.NullableBoolValue(value=False),
1492 accepts_kids=api_pb2.NullableBoolValue(value=True),
1493 kid_details=api_pb2.NullableStringValue(value="I hate kids"),
1494 has_housemates=api_pb2.NullableBoolValue(value=False),
1495 housemate_details=api_pb2.NullableStringValue(value="I have 7 housemates"),
1496 wheelchair_accessible=api_pb2.NullableBoolValue(value=True),
1497 smoking_allowed=api_pb2.SMOKING_LOCATION_WINDOW,
1498 area=api_pb2.NullableStringValue(value="area!"),
1499 smokes_at_home=api_pb2.NullableBoolValue(value=False),
1500 drinking_allowed=api_pb2.NullableBoolValue(value=True),
1501 drinks_at_home=api_pb2.NullableBoolValue(value=False),
1502 other_host_info=api_pb2.NullableStringValue(value="I'm pretty swell"),
1503 sleeping_arrangement=api_pb2.SLEEPING_ARRANGEMENT_COMMON,
1504 sleeping_details=api_pb2.NullableStringValue(value="Couch in living room"),
1505 house_rules=api_pb2.NullableStringValue(value="RULES!"),
1506 parking=api_pb2.NullableBoolValue(value=True),
1507 parking_details=api_pb2.PARKING_DETAILS_PAID_ONSITE,
1508 camping_ok=api_pb2.NullableBoolValue(value=False),
1509 )
1510 )
1512 # Use a second user to view the hosting preferences just to check
1513 # that it is public information.
1514 with api_session(token2) as api:
1515 res = api.GetUser(api_pb2.GetUserReq(user=user1.username))
1516 assert res.max_guests.value == 3
1517 assert res.last_minute.value
1518 assert not res.has_pets.value
1519 assert res.accepts_pets.value
1520 assert res.pet_details.value == "I love dogs"
1521 assert not res.has_kids.value
1522 assert res.accepts_kids.value
1523 assert res.kid_details.value == "I hate kids"
1524 assert not res.has_housemates.value
1525 assert res.housemate_details.value == "I have 7 housemates"
1526 assert res.wheelchair_accessible.value
1527 assert res.smoking_allowed == api_pb2.SMOKING_LOCATION_WINDOW
1528 assert not res.smokes_at_home.value
1529 assert res.drinking_allowed.value
1530 assert not res.drinks_at_home.value
1531 assert res.other_host_info.value == "I'm pretty swell"
1532 assert res.sleeping_arrangement == api_pb2.SLEEPING_ARRANGEMENT_COMMON
1533 assert res.sleeping_details.value == "Couch in living room"
1534 assert res.area.value == "area!"
1535 assert res.house_rules.value == "RULES!"
1536 assert res.parking.value
1537 assert res.parking_details == api_pb2.PARKING_DETAILS_PAID_ONSITE
1538 assert not res.camping_ok.value
1540 # test unsetting
1541 with api_session(token1) as api:
1542 api.UpdateProfile(
1543 api_pb2.UpdateProfileReq(
1544 max_guests=api_pb2.NullableUInt32Value(is_null=True),
1545 last_minute=api_pb2.NullableBoolValue(is_null=True),
1546 has_pets=api_pb2.NullableBoolValue(is_null=True),
1547 accepts_pets=api_pb2.NullableBoolValue(is_null=True),
1548 pet_details=api_pb2.NullableStringValue(is_null=True),
1549 has_kids=api_pb2.NullableBoolValue(is_null=True),
1550 accepts_kids=api_pb2.NullableBoolValue(is_null=True),
1551 kid_details=api_pb2.NullableStringValue(is_null=True),
1552 has_housemates=api_pb2.NullableBoolValue(is_null=True),
1553 housemate_details=api_pb2.NullableStringValue(is_null=True),
1554 wheelchair_accessible=api_pb2.NullableBoolValue(is_null=True),
1555 smoking_allowed=api_pb2.SMOKING_LOCATION_UNKNOWN,
1556 area=api_pb2.NullableStringValue(is_null=True),
1557 smokes_at_home=api_pb2.NullableBoolValue(is_null=True),
1558 drinking_allowed=api_pb2.NullableBoolValue(is_null=True),
1559 drinks_at_home=api_pb2.NullableBoolValue(is_null=True),
1560 other_host_info=api_pb2.NullableStringValue(is_null=True),
1561 sleeping_arrangement=api_pb2.SLEEPING_ARRANGEMENT_UNKNOWN,
1562 sleeping_details=api_pb2.NullableStringValue(is_null=True),
1563 house_rules=api_pb2.NullableStringValue(is_null=True),
1564 parking=api_pb2.NullableBoolValue(is_null=True),
1565 parking_details=api_pb2.PARKING_DETAILS_UNKNOWN,
1566 camping_ok=api_pb2.NullableBoolValue(is_null=True),
1567 )
1568 )
1570 res = api.GetUser(api_pb2.GetUserReq(user=user1.username))
1571 assert not res.HasField("max_guests")
1572 assert not res.HasField("last_minute")
1573 assert not res.HasField("has_pets")
1574 assert not res.HasField("accepts_pets")
1575 assert not res.HasField("pet_details")
1576 assert not res.HasField("has_kids")
1577 assert not res.HasField("accepts_kids")
1578 assert not res.HasField("kid_details")
1579 assert not res.HasField("has_housemates")
1580 assert not res.HasField("housemate_details")
1581 assert not res.HasField("wheelchair_accessible")
1582 assert res.smoking_allowed == api_pb2.SMOKING_LOCATION_UNKNOWN
1583 assert not res.HasField("smokes_at_home")
1584 assert not res.HasField("drinking_allowed")
1585 assert not res.HasField("drinks_at_home")
1586 assert not res.HasField("other_host_info")
1587 assert res.sleeping_arrangement == api_pb2.SLEEPING_ARRANGEMENT_UNKNOWN
1588 assert not res.HasField("sleeping_details")
1589 assert not res.HasField("area")
1590 assert not res.HasField("house_rules")
1591 assert not res.HasField("parking")
1592 assert res.parking_details == api_pb2.PARKING_DETAILS_UNKNOWN
1593 assert not res.HasField("camping_ok")
1596def test_badges(db):
1597 user1, _ = generate_user(last_donated=None)
1598 user2, _ = generate_user(last_donated=None)
1599 user3, _ = generate_user(last_donated=None)
1600 user4, token = generate_user(last_donated=None)
1602 update_badges(empty_pb2.Empty())
1604 founder_badge = get_badge_dict()["founder"]
1605 board_member_badge = get_badge_dict()["board_member"]
1607 with api_session(token) as api:
1608 assert api.GetUser(api_pb2.GetUserReq(user=user1.username)).badges == ["founder", "board_member"]
1609 assert api.GetUser(api_pb2.GetUserReq(user=user2.username)).badges == ["founder", "board_member"]
1610 assert api.GetUser(api_pb2.GetUserReq(user=user3.username)).badges == []
1612 assert api.ListBadgeUsers(api_pb2.ListBadgeUsersReq(badge_id=founder_badge.id)).user_ids == [1, 2]
1613 res = api.ListBadgeUsers(api_pb2.ListBadgeUsersReq(badge_id=board_member_badge.id, page_size=1))
1614 assert res.user_ids == [1]
1615 res2 = api.ListBadgeUsers(
1616 api_pb2.ListBadgeUsersReq(badge_id=board_member_badge.id, page_token=res.next_page_token)
1617 )
1618 assert res2.user_ids == [2]
1621def test_user_add_badge_is_idempotent(db):
1622 """Test that adding a badge a user already has is a no-op and doesn't send a duplicate notification."""
1623 user, _ = generate_user()
1625 with session_scope() as session:
1626 user_add_badge(session, user.id, "volunteer")
1628 # one badge row, one notification
1629 with session_scope() as session:
1630 badge_count = session.execute(
1631 select(func.count())
1632 .select_from(UserBadge)
1633 .where(UserBadge.user_id == user.id, UserBadge.badge_id == "volunteer")
1634 ).scalar()
1635 assert badge_count == 1
1636 notification_count = session.execute(
1637 select(func.count()).select_from(Notification).where(Notification.user_id == user.id)
1638 ).scalar()
1639 assert notification_count == 1
1641 # add the same badge again
1642 with session_scope() as session:
1643 user_add_badge(session, user.id, "volunteer")
1645 # still one badge row, no new notification
1646 with session_scope() as session:
1647 badge_count = session.execute(
1648 select(func.count())
1649 .select_from(UserBadge)
1650 .where(UserBadge.user_id == user.id, UserBadge.badge_id == "volunteer")
1651 ).scalar()
1652 assert badge_count == 1
1653 notification_count = session.execute(
1654 select(func.count()).select_from(Notification).where(Notification.user_id == user.id)
1655 ).scalar()
1656 assert notification_count == 1
1659@pytest.mark.parametrize("flag", ["deleted_at", "banned_at"])
1660def test_ListBadgeUsers_excludes_ghost_users(db, flag):
1661 """Test that ListBadgeUsers does not return deleted/banned users."""
1662 user1, token1 = generate_user()
1663 user2, _ = generate_user()
1664 user3, _ = generate_user()
1666 volunteer_badge = get_badge_dict()["volunteer"]
1668 # Give all three users the volunteer badge
1669 with session_scope() as session:
1670 user_add_badge(session, user1.id, "volunteer", do_notify=False)
1671 user_add_badge(session, user2.id, "volunteer", do_notify=False)
1672 user_add_badge(session, user3.id, "volunteer", do_notify=False)
1674 # Verify all three users appear in the badge list
1675 with api_session(token1) as api:
1676 res = api.ListBadgeUsers(api_pb2.ListBadgeUsersReq(badge_id=volunteer_badge.id))
1677 assert set(res.user_ids) == {user1.id, user2.id, user3.id}
1679 # Make user2 invisible (deleted or banned)
1680 with session_scope() as session:
1681 db_user2 = session.execute(select(User).where(User.id == user2.id)).scalar_one()
1682 setattr(db_user2, flag, now())
1684 # Now user2 should not appear in the badge list
1685 with api_session(token1) as api:
1686 res = api.ListBadgeUsers(api_pb2.ListBadgeUsersReq(badge_id=volunteer_badge.id))
1687 assert set(res.user_ids) == {user1.id, user3.id}
1690@pytest.mark.parametrize("flag", ["deleted_at", "banned_at"])
1691def test_GetLiteUser_ghost_user_by_username(db, flag):
1692 """Test that GetLiteUser returns a ghost profile for deleted/banned users when querying by username."""
1693 user1, token1 = generate_user()
1694 user2, _ = generate_user()
1696 # Make user2 invisible
1697 with session_scope() as session:
1698 db_user2 = session.merge(user2)
1699 setattr(db_user2, flag, now())
1700 session.commit()
1702 # Refresh the materialized view
1703 refresh_materialized_views_rapid(empty_pb2.Empty())
1705 with api_session(token1) as api:
1706 # Query by username
1707 lite_user = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user2.username))
1709 assert lite_user.user_id == user2.id
1710 assert lite_user.username == "ghost"
1711 assert lite_user.name == "Deactivated Account"
1712 assert lite_user.lat == 0
1713 assert lite_user.lng == 0
1714 assert lite_user.radius == 0
1715 assert lite_user.city == ""
1716 assert lite_user.age == 0
1717 assert lite_user.avatar_url == ""
1718 assert lite_user.avatar_thumbnail_url == ""
1719 assert not lite_user.has_strong_verification
1722@pytest.mark.parametrize("flag", ["deleted_at", "banned_at"])
1723def test_GetLiteUser_ghost_user_by_id(db, flag):
1724 """Test that GetLiteUser returns a ghost profile for deleted/banned users when querying by ID."""
1725 user1, token1 = generate_user()
1726 user2, _ = generate_user()
1728 # Make user2 invisible
1729 with session_scope() as session:
1730 db_user2 = session.merge(user2)
1731 setattr(db_user2, flag, now())
1732 session.commit()
1734 # Refresh the materialized view
1735 refresh_materialized_views_rapid(empty_pb2.Empty())
1737 with api_session(token1) as api:
1738 # Query by ID
1739 lite_user = api.GetLiteUser(api_pb2.GetLiteUserReq(user=str(user2.id)))
1741 assert lite_user.user_id == user2.id
1742 assert lite_user.username == "ghost"
1743 assert lite_user.name == "Deactivated Account"
1744 assert lite_user.lat == 0
1745 assert lite_user.lng == 0
1746 assert lite_user.radius == 0
1747 assert lite_user.city == ""
1748 assert lite_user.age == 0
1749 assert lite_user.avatar_url == ""
1750 assert lite_user.avatar_thumbnail_url == ""
1751 assert not lite_user.has_strong_verification
1754def test_GetLiteUser_blocked_user(db):
1755 """Test that GetLiteUser returns a ghost profile for blocked users."""
1756 user1, token1 = generate_user()
1757 user2, _ = generate_user()
1759 # User1 blocks user2
1760 make_user_block(user1, user2)
1762 # Refresh the materialized view
1763 refresh_materialized_views_rapid(empty_pb2.Empty())
1765 with api_session(token1) as api:
1766 # Query by username
1767 lite_user = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user2.username))
1769 assert lite_user.user_id == user2.id
1770 assert lite_user.is_ghost
1771 assert lite_user.username == "ghost"
1772 assert lite_user.name == "Deactivated Account"
1774 # Query by ID
1775 lite_user = api.GetLiteUser(api_pb2.GetLiteUserReq(user=str(user2.id)))
1777 assert lite_user.user_id == user2.id
1778 assert lite_user.is_ghost
1779 assert lite_user.username == "ghost"
1780 assert lite_user.name == "Deactivated Account"
1783def test_GetLiteUser_blocking_user(db):
1784 """Test that GetLiteUser returns a ghost profile when the target user has blocked the requester."""
1785 user1, token1 = generate_user()
1786 user2, _ = generate_user()
1788 # User2 blocks user1
1789 make_user_block(user2, user1)
1791 # Refresh the materialized view
1792 refresh_materialized_views_rapid(empty_pb2.Empty())
1794 with api_session(token1) as api:
1795 # Query by username
1796 lite_user = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user2.username))
1798 assert lite_user.user_id == user2.id
1799 assert lite_user.is_ghost
1800 assert lite_user.username == "ghost"
1801 assert lite_user.name == "Deactivated Account"
1803 # Query by ID
1804 lite_user = api.GetLiteUser(api_pb2.GetLiteUserReq(user=str(user2.id)))
1806 assert lite_user.user_id == user2.id
1807 assert lite_user.is_ghost
1808 assert lite_user.username == "ghost"
1809 assert lite_user.name == "Deactivated Account"
1812@pytest.mark.parametrize("flag", ["deleted_at", "banned_at"])
1813def test_GetLiteUsers_ghost_users(db, flag):
1814 """Test that GetLiteUsers returns ghost profiles for deleted/banned users."""
1815 user1, token1 = generate_user()
1816 user2, _ = generate_user()
1817 user3, _ = generate_user()
1818 user4, _ = generate_user()
1820 # Make user2 and user4 invisible
1821 with session_scope() as session:
1822 db_user2 = session.merge(user2)
1823 setattr(db_user2, flag, now())
1824 db_user4 = session.merge(user4)
1825 setattr(db_user4, flag, now())
1826 session.commit()
1828 # Refresh the materialized view
1829 refresh_materialized_views_rapid(empty_pb2.Empty())
1831 with api_session(token1) as api:
1832 res = api.GetLiteUsers(
1833 api_pb2.GetLiteUsersReq(
1834 users=[
1835 user1.username, # visible
1836 user2.username, # ghost
1837 str(user3.id), # visible
1838 str(user4.id), # ghost
1839 ]
1840 )
1841 )
1843 assert len(res.responses) == 4
1845 # user1 - visible, normal profile
1846 assert res.responses[0].query == user1.username
1847 assert not res.responses[0].not_found
1848 assert res.responses[0].user.user_id == user1.id
1849 assert res.responses[0].user.username == user1.username
1850 assert res.responses[0].user.name == user1.name
1852 # user2 - ghost by username
1853 assert res.responses[1].query == user2.username
1854 assert not res.responses[1].not_found
1855 assert res.responses[1].user.user_id == user2.id
1856 assert res.responses[1].user.is_ghost
1857 assert res.responses[1].user.username == "ghost"
1858 assert res.responses[1].user.name == "Deactivated Account"
1860 # user3 - visible, normal profile
1861 assert res.responses[2].query == str(user3.id)
1862 assert not res.responses[2].not_found
1863 assert res.responses[2].user.user_id == user3.id
1864 assert res.responses[2].user.username == user3.username
1865 assert res.responses[2].user.name == user3.name
1867 # user4 - ghost by ID
1868 assert res.responses[3].query == str(user4.id)
1869 assert not res.responses[3].not_found
1870 assert res.responses[3].user.user_id == user4.id
1871 assert res.responses[3].user.is_ghost
1872 assert res.responses[3].user.username == "ghost"
1873 assert res.responses[3].user.name == "Deactivated Account"
1876def test_GetLiteUsers_blocked_users(db):
1877 """Test that GetLiteUsers returns ghost profiles for blocked users."""
1878 user1, token1 = generate_user()
1879 user2, _ = generate_user()
1880 user3, _ = generate_user()
1881 user4, _ = generate_user()
1882 user5, _ = generate_user()
1884 # User1 blocks user2
1885 make_user_block(user1, user2)
1886 # User4 blocks user1
1887 make_user_block(user4, user1)
1889 # Refresh the materialized view
1890 refresh_materialized_views_rapid(empty_pb2.Empty())
1892 with api_session(token1) as api:
1893 res = api.GetLiteUsers(
1894 api_pb2.GetLiteUsersReq(
1895 users=[
1896 user2.username, # user1 blocked user2
1897 str(user3.id), # visible
1898 user4.username, # user4 blocked user1
1899 str(user5.id), # visible
1900 ]
1901 )
1902 )
1904 assert len(res.responses) == 4
1906 # user2 - blocked by user1, should be ghost
1907 assert res.responses[0].query == user2.username
1908 assert not res.responses[0].not_found
1909 assert res.responses[0].user.user_id == user2.id
1910 assert res.responses[0].user.is_ghost
1911 assert res.responses[0].user.username == "ghost"
1912 assert res.responses[0].user.name == "Deactivated Account"
1914 # user3 - visible
1915 assert res.responses[1].query == str(user3.id)
1916 assert not res.responses[1].not_found
1917 assert res.responses[1].user.user_id == user3.id
1918 assert res.responses[1].user.username == user3.username
1920 # user4 - user4 blocked user1, should be ghost
1921 assert res.responses[2].query == user4.username
1922 assert not res.responses[2].not_found
1923 assert res.responses[2].user.user_id == user4.id
1924 assert res.responses[2].user.is_ghost
1925 assert res.responses[2].user.username == "ghost"
1926 assert res.responses[2].user.name == "Deactivated Account"
1928 # user5 - visible
1929 assert res.responses[3].query == str(user5.id)
1930 assert not res.responses[3].not_found
1931 assert res.responses[3].user.user_id == user5.id
1932 assert res.responses[3].user.username == user5.username
1935@pytest.mark.parametrize("flag", ["deleted_at", "banned_at"])
1936def test_GetUser_ghost_user_by_id(db, flag):
1937 """Test that GetUser returns a ghost profile for deleted/banned users when querying by ID."""
1938 user1, token1 = generate_user()
1939 user2, _ = generate_user()
1941 # Make user2 invisible
1942 with session_scope() as session:
1943 db_user2 = session.merge(user2)
1944 setattr(db_user2, flag, now())
1945 session.commit()
1947 with api_session(token1) as api:
1948 # Query by ID
1949 user_pb = api.GetUser(api_pb2.GetUserReq(user=str(user2.id)))
1951 assert user_pb.user_id == user2.id
1952 assert user_pb.username == "ghost"
1953 assert user_pb.name == "Deactivated Account"
1954 assert user_pb.city == ""
1955 assert user_pb.hosting_status == 0
1956 assert user_pb.meetup_status == 0
1959def test_GetUser_blocked_user(db):
1960 """Test that GetUser returns a ghost profile for blocked users."""
1961 user1, token1 = generate_user()
1962 user2, _ = generate_user()
1964 # User1 blocks user2
1965 make_user_block(user1, user2)
1967 with api_session(token1) as api:
1968 # Query by username
1969 user_pb = api.GetUser(api_pb2.GetUserReq(user=user2.username))
1971 assert user_pb.user_id == user2.id
1972 assert user_pb.username == "ghost"
1973 assert user_pb.name == "Deactivated Account"
1975 # Query by ID
1976 user_pb = api.GetUser(api_pb2.GetUserReq(user=str(user2.id)))
1978 assert user_pb.user_id == user2.id
1979 assert user_pb.username == "ghost"
1980 assert user_pb.name == "Deactivated Account"
1983def test_GetUser_blocking_user(db):
1984 """Test that GetUser returns a ghost profile when the target user has blocked the requester."""
1985 user1, token1 = generate_user()
1986 user2, _ = generate_user()
1988 # User2 blocks user1
1989 make_user_block(user2, user1)
1991 with api_session(token1) as api:
1992 # Query by username
1993 user_pb = api.GetUser(api_pb2.GetUserReq(user=user2.username))
1995 assert user_pb.user_id == user2.id
1996 assert user_pb.username == "ghost"
1997 assert user_pb.name == "Deactivated Account"
1999 # Query by ID
2000 user_pb = api.GetUser(api_pb2.GetUserReq(user=str(user2.id)))
2002 assert user_pb.user_id == user2.id
2003 assert user_pb.username == "ghost"
2004 assert user_pb.name == "Deactivated Account"