Coverage for app/backend/src/tests/test_api.py: 100%
1194 statements
« prev ^ index » next coverage.py v7.16.1, created at 2026-09-19 15:47 +0000
« prev ^ index » next coverage.py v7.16.1, created at 2026-09-19 15:47 +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)
45def test_ping(db):
46 user, token = generate_user(
47 regions_lived=["ESP", "FRA", "EST"],
48 regions_visited=["CHE", "REU", "FIN"],
49 language_abilities=[
50 ("fin", LanguageFluency.fluent),
51 ("fra", LanguageFluency.beginner),
52 ],
53 )
55 with real_api_session(token) as api:
56 res = api.Ping(api_pb2.PingReq())
58 assert res.user.user_id == user.id
59 assert res.user.username == user.username
60 assert res.user.name == user.name
61 assert res.user.city == user.city
62 assert res.user.hometown == user.hometown
63 assert res.user.verification == 0.0
64 assert res.user.community_standing == user.community_standing
65 assert res.user.num_references == 0
66 assert res.user.gender == user.gender
67 assert res.user.pronouns == user.pronouns
68 assert res.user.age == user.age
70 assert (res.user.lat, res.user.lng) == user.coordinates
72 # the joined time is fuzzed
73 # but shouldn't be before actual joined time, or more than one hour behind
74 assert user.joined - timedelta(hours=1) <= to_aware_datetime(res.user.joined) <= user.joined
75 # same for last_active
76 assert user.last_active - timedelta(hours=1) <= to_aware_datetime(res.user.last_active) <= user.last_active
78 assert res.user.hosting_status == api_pb2.HOSTING_STATUS_CANT_HOST
79 assert res.user.meetup_status == api_pb2.MEETUP_STATUS_OPEN_TO_MEETUP
81 assert res.user.occupation == user.occupation
82 assert res.user.education == user.education
83 assert res.user.about_me == user.about_me
84 assert res.user.things_i_like == user.things_i_like
85 assert {language_ability.code for language_ability in res.user.language_abilities} == {"fin", "fra"}
86 assert res.user.about_place == user.about_place
87 assert res.user.regions_visited == ["FIN", "REU", "CHE"] # Tests alphabetization by region name
88 assert res.user.regions_lived == ["EST", "FRA", "ESP"] # Ditto
89 assert res.user.additional_information == user.additional_information
91 assert res.user.friends == api_pb2.User.FriendshipStatus.NA
92 assert not res.user.HasField("pending_friend_request")
95def test_coords(db):
96 # make them need to update location
97 user1, token1 = generate_user(geom=create_coordinate(1, 0), geom_radius=2000, needs_to_update_location=True)
98 user2, token2 = generate_user()
100 with api_session(token2) as api:
101 res = api.Ping(api_pb2.PingReq())
102 assert res.user.city == user2.city
103 lat, lng = user2.coordinates
104 assert res.user.lat == lat
105 assert res.user.lng == lng
106 assert res.user.radius == user2.geom_radius
108 with api_session(token2) as api:
109 res = api.GetUser(api_pb2.GetUserReq(user=user1.username))
110 assert res.city == user1.city
111 assert res.lat == 1.0
112 assert res.lng == 0.0
113 assert res.radius == 2000.0
115 # Check coordinate wrapping
116 user3, token3 = generate_user(geom=create_coordinate(40.0, -180.5))
117 user4, token4 = generate_user(geom=create_coordinate(40.0, 20.0))
118 user5, token5 = generate_user(geom=create_coordinate(90.5, 20.0))
120 with api_session(token3) as api:
121 res = api.GetUser(api_pb2.GetUserReq(user=user3.username))
122 assert res.lat == 40.0
123 assert res.lng == 179.5
125 with api_session(token4) as api:
126 res = api.GetUser(api_pb2.GetUserReq(user=user4.username))
127 assert res.lat == 40.0
128 assert res.lng == 20.0
130 # PostGIS does not wrap longitude for latitude overflow
131 with api_session(token5) as api:
132 res = api.GetUser(api_pb2.GetUserReq(user=user5.username))
133 assert res.lat == 89.5
134 assert res.lng == 20.0
136 with real_jail_session(token1) as jail:
137 res = jail.JailInfo(empty_pb2.Empty())
138 assert res.jailed
139 assert res.needs_to_update_location
141 res = jail.SetLocation(
142 jail_pb2.SetLocationReq(
143 city="New York City",
144 lat=40.7812,
145 lng=-73.9647,
146 radius=250,
147 )
148 )
150 assert not res.jailed
151 assert not res.needs_to_update_location
153 res = jail.JailInfo(empty_pb2.Empty())
154 assert not res.jailed
155 assert not res.needs_to_update_location
157 with api_session(token2) as api:
158 res = api.GetUser(api_pb2.GetUserReq(user=user1.username))
159 assert res.city == "New York City"
160 assert res.lat == 40.7812
161 assert res.lng == -73.9647
162 assert res.radius == 250
165def test_get_user(db):
166 user1, token1 = generate_user()
167 user2, token2 = generate_user()
169 with api_session(token1) as api:
170 res = api.GetUser(api_pb2.GetUserReq(user=user2.username))
171 assert res.user_id == user2.id
172 assert res.username == user2.username
173 assert res.name == user2.name
175 with api_session(token1) as api:
176 res = api.GetUser(api_pb2.GetUserReq(user=str(user2.id)))
177 assert res.user_id == user2.id
178 assert res.username == user2.username
179 assert res.name == user2.name
182@pytest.mark.parametrize("flag", ["deleted_at", "banned_at"])
183def test_user_model_to_pb_ghost_user(db, flag):
184 user1, token1 = generate_user()
185 user2, _ = generate_user()
187 with session_scope() as session:
188 session.execute(update(User).where(User.id == user2.id).values(**{flag: func.now()}))
190 refresh_materialized_views_rapid(empty_pb2.Empty())
192 with api_session(token1) as api:
193 user_pb = api.GetUser(api_pb2.GetUserReq(user=user2.username))
195 assert user_pb.user_id == user2.id
196 assert user_pb.is_ghost
197 assert user_pb.username == "ghost"
198 assert user_pb.name == "Deactivated Account"
199 assert (
200 user_pb.about_me
201 == "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."
202 )
204 assert user_pb.lat == 0
205 assert user_pb.lng == 0
206 assert user_pb.radius == 0
207 assert user_pb.verification == 0.0
208 assert user_pb.community_standing == 0.0
209 assert user_pb.num_references == 0
210 assert user_pb.age == 0
211 assert user_pb.hosting_status == 0
212 assert user_pb.meetup_status == 0
213 assert user_pb.city == ""
214 assert user_pb.hometown == ""
215 assert user_pb.timezone == ""
216 assert user_pb.gender == ""
217 assert user_pb.pronouns == ""
218 assert user_pb.occupation == ""
219 assert user_pb.education == ""
220 assert user_pb.things_i_like == ""
221 assert user_pb.about_place == ""
222 assert user_pb.additional_information == ""
223 assert list(user_pb.language_abilities) == []
224 assert list(user_pb.regions_visited) == []
225 assert list(user_pb.regions_lived) == []
226 assert list(user_pb.badges) == []
227 assert user_pb.friends == api_pb2.User.FriendshipStatus.NOT_FRIENDS
228 assert user_pb.avatar_url == ""
229 assert user_pb.avatar_thumbnail_url == ""
230 assert not user_pb.has_strong_verification
232 with api_session(token1) as api:
233 lite_user_pb = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user2.username))
235 assert lite_user_pb.user_id == user2.id
236 assert lite_user_pb.is_ghost
237 assert lite_user_pb.username == "ghost"
238 assert lite_user_pb.name == "Deactivated Account"
239 assert lite_user_pb.city == ""
240 assert lite_user_pb.age == 0
241 assert lite_user_pb.avatar_url == ""
242 assert lite_user_pb.avatar_thumbnail_url == ""
243 assert lite_user_pb.lat == 0
244 assert lite_user_pb.lng == 0
245 assert lite_user_pb.radius == 0
246 assert not lite_user_pb.has_strong_verification
249def test_user_model_to_pb_ghost_user_blocked(db):
250 user1, token1 = generate_user()
251 user2, _ = generate_user()
253 with blocking_session(token1) as user_blocks:
254 user_blocks.BlockUser(blocking_pb2.BlockUserReq(username=user2.username))
256 refresh_materialized_views_rapid(empty_pb2.Empty())
258 with api_session(token1) as api:
259 user_pb = api.GetUser(api_pb2.GetUserReq(user=user2.username))
261 assert user_pb.user_id == user2.id
262 assert user_pb.is_ghost
263 assert user_pb.username == "ghost"
264 assert user_pb.name == "Deactivated Account"
265 assert (
266 user_pb.about_me
267 == "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."
268 )
270 assert user_pb.lat == 0
271 assert user_pb.lng == 0
272 assert user_pb.radius == 0
273 assert user_pb.verification == 0.0
274 assert user_pb.community_standing == 0.0
275 assert user_pb.num_references == 0
276 assert user_pb.age == 0
277 assert user_pb.hosting_status == 0
278 assert user_pb.meetup_status == 0
279 assert user_pb.city == ""
280 assert user_pb.hometown == ""
281 assert user_pb.timezone == ""
282 assert user_pb.gender == ""
283 assert user_pb.pronouns == ""
284 assert user_pb.occupation == ""
285 assert user_pb.education == ""
286 assert user_pb.things_i_like == ""
287 assert user_pb.about_place == ""
288 assert user_pb.additional_information == ""
289 assert list(user_pb.language_abilities) == []
290 assert list(user_pb.regions_visited) == []
291 assert list(user_pb.regions_lived) == []
292 assert list(user_pb.badges) == []
293 assert user_pb.friends == api_pb2.User.FriendshipStatus.NOT_FRIENDS
294 assert user_pb.avatar_url == ""
295 assert user_pb.avatar_thumbnail_url == ""
296 assert not user_pb.has_strong_verification
298 with api_session(token1) as api:
299 lite_user_pb = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user2.username))
301 assert lite_user_pb.user_id == user2.id
302 assert lite_user_pb.is_ghost
303 assert lite_user_pb.username == "ghost"
304 assert lite_user_pb.name == "Deactivated Account"
305 assert lite_user_pb.city == ""
306 assert lite_user_pb.age == 0
307 assert lite_user_pb.avatar_url == ""
308 assert lite_user_pb.avatar_thumbnail_url == ""
309 assert lite_user_pb.lat == 0
310 assert lite_user_pb.lng == 0
311 assert lite_user_pb.radius == 0
312 assert not lite_user_pb.has_strong_verification
315@pytest.mark.parametrize(
316 "flag,state",
317 [
318 ("banned_at", NonvisibleUserState.banned),
319 ("shadowed_at", NonvisibleUserState.shadowed),
320 ("deleted_at", NonvisibleUserState.deleted),
321 ],
322)
323def test_viewing_nonvisible_user_profile_is_logged(db, flag, state):
324 user1, token1 = generate_user()
325 user2, _ = generate_user()
327 with session_scope() as session:
328 session.execute(update(User).where(User.id == user2.id).values(**{flag: func.now()}))
330 refresh_materialized_views_rapid(empty_pb2.Empty())
332 with api_session(token1) as api:
333 user_pb = api.GetUser(api_pb2.GetUserReq(user=user2.username))
334 assert user_pb.is_ghost
336 with session_scope() as session:
337 access = session.execute(select(NonvisibleUserAccess)).scalar_one()
338 assert access.access_type == NonvisibleUserAccessType.ghost_served
339 assert access.target_state == state
340 assert access.target_user_id == user2.id
341 assert access.actor_user_id == user1.id
342 assert access.ip_address is None
343 assert access.user_agent is None
344 assert access.sofa is None
347def test_viewing_blocked_user_profile_is_not_logged(db):
348 user1, token1 = generate_user()
349 user2, _ = generate_user()
351 with blocking_session(token1) as user_blocks:
352 user_blocks.BlockUser(blocking_pb2.BlockUserReq(username=user2.username))
354 refresh_materialized_views_rapid(empty_pb2.Empty())
356 with api_session(token1) as api:
357 user_pb = api.GetUser(api_pb2.GetUserReq(user=user2.username))
358 assert user_pb.is_ghost
360 with session_scope() as session:
361 assert session.execute(select(func.count()).select_from(NonvisibleUserAccess)).scalar_one() == 0
364@pytest.mark.parametrize("flag", ["deleted_at", "banned_at"])
365def test_admin_viewing_ghost_users_sees_full_profile(db, flag):
366 admin, token_admin = generate_user(is_superuser=True)
367 user, _ = generate_user()
369 with session_scope() as session:
370 session.execute(update(User).where(User.id == user.id).values(**{flag: func.now()}))
372 with admin_session(token_admin) as api:
373 user_pb = api.GetUser(admin_pb2.GetUserReq(user=user.username))
375 assert user_pb.user_id == user.id
376 assert user_pb.username == user.username
377 assert user_pb.name == user.name
378 assert user_pb.city == user.city
379 assert user_pb.name != "Deactivated Account"
380 assert user_pb.username != "ghost"
381 assert user_pb.hosting_status in (
382 api_pb2.HOSTING_STATUS_UNKNOWN,
383 api_pb2.HOSTING_STATUS_CAN_HOST,
384 api_pb2.HOSTING_STATUS_MAYBE,
385 api_pb2.HOSTING_STATUS_CANT_HOST,
386 )
389def test_lite_coords(db):
390 # make them need to update location
391 user1, token1 = generate_user(geom=create_coordinate(0, 0), geom_radius=0, needs_to_update_location=True)
392 user2, token2 = generate_user()
394 refresh_materialized_views_rapid(empty_pb2.Empty())
396 with api_session(token2) as api:
397 res = api.Ping(api_pb2.PingReq())
398 assert res.user.city == user2.city
399 lat, lng = user2.coordinates
400 assert res.user.lat == lat
401 assert res.user.lng == lng
402 assert res.user.radius == user2.geom_radius
404 with api_session(token2) as api:
405 res = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user1.username))
406 assert res.city == user1.city
407 assert res.lat == 0.0
408 assert res.lng == 0.0
409 assert res.radius == 0.0
411 # Check coordinate wrapping
412 user3, token3 = generate_user(geom=create_coordinate(40.0, -180.5))
413 user4, token4 = generate_user(geom=create_coordinate(40.0, 20.0))
414 user5, token5 = generate_user(geom=create_coordinate(90.5, 20.0))
416 refresh_materialized_views_rapid(empty_pb2.Empty())
418 with api_session(token3) as api:
419 res = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user3.username))
420 assert res.lat == 40.0
421 assert res.lng == 179.5
423 with api_session(token4) as api:
424 res = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user4.username))
425 assert res.lat == 40.0
426 assert res.lng == 20.0
428 # PostGIS does not wrap longitude for latitude overflow
429 with api_session(token5) as api:
430 res = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user5.username))
431 assert res.lat == 89.5
432 assert res.lng == 20.0
434 with real_jail_session(token1) as jail:
435 res = jail.JailInfo(empty_pb2.Empty())
436 assert res.jailed
437 assert res.needs_to_update_location
439 res = jail.SetLocation(
440 jail_pb2.SetLocationReq(
441 city="New York City",
442 lat=40.7812,
443 lng=-73.9647,
444 radius=250,
445 )
446 )
448 assert not res.jailed
449 assert not res.needs_to_update_location
451 res = jail.JailInfo(empty_pb2.Empty())
452 assert not res.jailed
453 assert not res.needs_to_update_location
455 refresh_materialized_views_rapid(empty_pb2.Empty())
457 with api_session(token2) as api:
458 res = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user1.username))
459 assert res.city == "New York City"
460 assert res.lat == 40.7812
461 assert res.lng == -73.9647
462 assert res.radius == 250
465def test_lite_get_user(db):
466 user1, token1 = generate_user()
467 user2, token2 = generate_user()
469 refresh_materialized_views_rapid(empty_pb2.Empty())
471 with api_session(token1) as api:
472 res = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user2.username))
473 assert res.user_id == user2.id
474 assert res.username == user2.username
475 assert res.name == user2.name
477 with api_session(token1) as api:
478 res = api.GetLiteUser(api_pb2.GetLiteUserReq(user=str(user2.id)))
479 assert res.user_id == user2.id
480 assert res.username == user2.username
481 assert res.name == user2.name
484def test_GetLiteUsers(db):
485 user1, token1 = generate_user()
486 user2, _ = generate_user()
487 user3, _ = generate_user()
488 user4, _ = generate_user()
489 user5, _ = generate_user()
490 user6, _ = generate_user()
492 make_user_block(user4, user1)
494 refresh_materialized_views_rapid(empty_pb2.Empty())
496 with api_session(token1) as api:
497 res = api.GetLiteUsers(
498 api_pb2.GetLiteUsersReq(
499 users=[
500 user1.username,
501 str(user1.id),
502 "nonexistent",
503 str(user2.id),
504 "9994",
505 user6.username,
506 str(user5.id),
507 "notreal",
508 user4.username,
509 ]
510 )
511 )
513 assert len(res.responses) == 9
514 assert res.responses[0].query == user1.username
515 assert res.responses[0].user.user_id == user1.id
517 assert res.responses[1].query == str(user1.id)
518 assert res.responses[1].user.user_id == user1.id
520 assert res.responses[2].query == "nonexistent"
521 assert res.responses[2].not_found
523 assert res.responses[3].query == str(user2.id)
524 assert res.responses[3].user.user_id == user2.id
526 assert res.responses[4].query == "9994"
527 assert res.responses[4].not_found
529 assert res.responses[5].query == user6.username
530 assert res.responses[5].user.user_id == user6.id
532 assert res.responses[6].query == str(user5.id)
533 assert res.responses[6].user.user_id == user5.id
535 assert res.responses[7].query == "notreal"
536 assert res.responses[7].not_found
538 # blocked - should return ghost profile
539 assert res.responses[8].query == user4.username
540 assert not res.responses[8].not_found
541 assert res.responses[8].user.user_id == user4.id
542 assert res.responses[8].user.is_ghost
543 assert res.responses[8].user.username == "ghost"
544 assert res.responses[8].user.name == "Deactivated Account"
546 with api_session(token1) as api:
547 with pytest.raises(grpc.RpcError) as e:
548 api.GetLiteUsers(api_pb2.GetLiteUsersReq(users=201 * [user1.username]))
549 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
550 assert e.value.details() == "You can't request that many users at a time."
553def test_update_profile(db):
554 user, token = generate_user()
556 with api_session(token) as api:
557 with pytest.raises(grpc.RpcError) as e:
558 api.UpdateProfile(api_pb2.UpdateProfileReq(name=wrappers_pb2.StringValue(value=" ")))
559 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
560 assert e.value.details() == "Name not supported."
562 with pytest.raises(grpc.RpcError) as e:
563 api.UpdateProfile(
564 api_pb2.UpdateProfileReq(lat=wrappers_pb2.DoubleValue(value=0), lng=wrappers_pb2.DoubleValue(value=0))
565 )
566 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
567 assert e.value.details() == "Invalid coordinate."
569 with pytest.raises(grpc.RpcError) as e:
570 api.UpdateProfile(
571 api_pb2.UpdateProfileReq(regions_visited=api_pb2.RepeatedStringValue(value=["United States"]))
572 )
573 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
574 assert e.value.details() == "Invalid region."
576 with pytest.raises(grpc.RpcError) as e:
577 api.UpdateProfile(
578 api_pb2.UpdateProfileReq(regions_lived=api_pb2.RepeatedStringValue(value=["United Kingdom"]))
579 )
580 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
581 assert e.value.details() == "Invalid region."
583 api.UpdateProfile(
584 api_pb2.UpdateProfileReq(
585 name=wrappers_pb2.StringValue(value="New name"),
586 city=wrappers_pb2.StringValue(value="Timbuktu"),
587 hometown=api_pb2.NullableStringValue(value="Walla Walla"),
588 lat=wrappers_pb2.DoubleValue(value=0.01),
589 lng=wrappers_pb2.DoubleValue(value=-2),
590 radius=wrappers_pb2.DoubleValue(value=321),
591 pronouns=api_pb2.NullableStringValue(value="Ro, Robo, Robots"),
592 occupation=api_pb2.NullableStringValue(value="Testing"),
593 education=api_pb2.NullableStringValue(value="Couchers U"),
594 about_me=api_pb2.NullableStringValue(value="I rule"),
595 things_i_like=api_pb2.NullableStringValue(value="Couchers"),
596 about_place=api_pb2.NullableStringValue(value="My place"),
597 hosting_status=api_pb2.HOSTING_STATUS_CAN_HOST,
598 meetup_status=api_pb2.MEETUP_STATUS_WANTS_TO_MEETUP,
599 language_abilities=api_pb2.RepeatedLanguageAbilityValue(
600 value=[
601 api_pb2.LanguageAbility(
602 code="eng",
603 fluency=api_pb2.LanguageAbility.Fluency.FLUENCY_FLUENT,
604 )
605 ],
606 ),
607 regions_visited=api_pb2.RepeatedStringValue(value=["CXR", "FIN"]),
608 regions_lived=api_pb2.RepeatedStringValue(value=["USA", "EST"]),
609 additional_information=api_pb2.NullableStringValue(value="I <3 Couchers"),
610 )
611 )
613 user_details = api.GetUser(api_pb2.GetUserReq(user=user.username))
614 assert user_details.name == "New name"
615 assert user_details.city == "Timbuktu"
616 assert user_details.hometown == "Walla Walla"
617 assert user_details.pronouns == "Ro, Robo, Robots"
618 assert user_details.education == "Couchers U"
619 assert user_details.things_i_like == "Couchers"
620 assert user_details.lat == 0.01
621 assert user_details.lng == -2
622 assert user_details.radius == 321
623 assert user_details.occupation == "Testing"
624 assert user_details.about_me == "I rule"
625 assert user_details.about_place == "My place"
626 assert user_details.hosting_status == api_pb2.HOSTING_STATUS_CAN_HOST
627 assert user_details.meetup_status == api_pb2.MEETUP_STATUS_WANTS_TO_MEETUP
628 assert user_details.language_abilities[0].code == "eng"
629 assert user_details.language_abilities[0].fluency == api_pb2.LanguageAbility.Fluency.FLUENCY_FLUENT
630 assert user_details.additional_information == "I <3 Couchers"
631 assert user_details.regions_visited == ["CXR", "FIN"]
632 assert user_details.regions_lived == ["EST", "USA"]
634 # Test unset values
635 api.UpdateProfile(
636 api_pb2.UpdateProfileReq(
637 hometown=api_pb2.NullableStringValue(is_null=True),
638 radius=wrappers_pb2.DoubleValue(value=0),
639 pronouns=api_pb2.NullableStringValue(is_null=True),
640 occupation=api_pb2.NullableStringValue(is_null=True),
641 education=api_pb2.NullableStringValue(is_null=True),
642 about_me=api_pb2.NullableStringValue(is_null=True),
643 things_i_like=api_pb2.NullableStringValue(is_null=True),
644 about_place=api_pb2.NullableStringValue(is_null=True),
645 hosting_status=api_pb2.HOSTING_STATUS_CAN_HOST,
646 meetup_status=api_pb2.MEETUP_STATUS_WANTS_TO_MEETUP,
647 language_abilities=api_pb2.RepeatedLanguageAbilityValue(value=[]),
648 regions_visited=api_pb2.RepeatedStringValue(value=[]),
649 regions_lived=api_pb2.RepeatedStringValue(value=[]),
650 additional_information=api_pb2.NullableStringValue(is_null=True),
651 )
652 )
654 user_details = api.GetUser(api_pb2.GetUserReq(user=user.username))
655 assert not user_details.hometown
656 assert not user_details.radius
657 assert not user_details.pronouns
658 assert not user_details.occupation
659 assert not user_details.education
660 assert not user_details.about_me
661 assert not user_details.things_i_like
662 assert not user_details.about_place
663 assert user_details.hosting_status == api_pb2.HOSTING_STATUS_CAN_HOST
664 assert user_details.meetup_status == api_pb2.MEETUP_STATUS_WANTS_TO_MEETUP
665 assert not user_details.language_abilities
666 assert not user_details.regions_visited
667 assert not user_details.regions_lived
668 assert not user_details.additional_information
671def test_update_profile_do_not_email(db):
672 user, token = generate_user()
674 with notifications_session(token) as notifications:
675 notifications.SetNotificationSettings(notifications_pb2.SetNotificationSettingsReq(enable_do_not_email=True))
677 with api_session(token) as api:
678 with pytest.raises(grpc.RpcError) as e:
679 api.UpdateProfile(api_pb2.UpdateProfileReq(hosting_status=api_pb2.HOSTING_STATUS_CAN_HOST))
680 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
681 assert e.value.details() == "You cannot enable hosting while you have emails turned off in your settings."
683 with pytest.raises(grpc.RpcError) as e:
684 api.UpdateProfile(api_pb2.UpdateProfileReq(meetup_status=api_pb2.MEETUP_STATUS_OPEN_TO_MEETUP))
685 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
686 assert e.value.details() == "You cannot enable meeting up while you have emails turned off in your settings."
689def test_language_abilities(db):
690 user, token = generate_user(
691 language_abilities=[
692 ("fin", LanguageFluency.fluent),
693 ("fra", LanguageFluency.beginner),
694 ],
695 )
697 with api_session(token) as api:
698 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
699 assert len(res.language_abilities) == 2
701 # can't add non-existent languages
702 with pytest.raises(grpc.RpcError) as err:
703 api.UpdateProfile(
704 api_pb2.UpdateProfileReq(
705 language_abilities=api_pb2.RepeatedLanguageAbilityValue(
706 value=[
707 api_pb2.LanguageAbility(
708 code="QQQ",
709 fluency=api_pb2.LanguageAbility.Fluency.FLUENCY_FLUENT,
710 )
711 ],
712 ),
713 )
714 )
715 assert err.value.code() == grpc.StatusCode.INVALID_ARGUMENT
716 assert err.value.details() == "Invalid language."
718 # can't have multiple languages of the same type
719 with pytest.raises(Exception) as err2:
720 api.UpdateProfile(
721 api_pb2.UpdateProfileReq(
722 language_abilities=api_pb2.RepeatedLanguageAbilityValue(
723 value=[
724 api_pb2.LanguageAbility(
725 code="eng",
726 fluency=api_pb2.LanguageAbility.Fluency.FLUENCY_FLUENT,
727 ),
728 api_pb2.LanguageAbility(
729 code="eng",
730 fluency=api_pb2.LanguageAbility.Fluency.FLUENCY_FLUENT,
731 ),
732 ],
733 ),
734 )
735 )
736 assert "violates unique constraint" in str(err2.value)
738 # nothing changed
739 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
740 assert len(res.language_abilities) == 2
742 # now actually add a value
743 api.UpdateProfile(
744 api_pb2.UpdateProfileReq(
745 language_abilities=api_pb2.RepeatedLanguageAbilityValue(
746 value=[
747 api_pb2.LanguageAbility(
748 code="eng",
749 fluency=api_pb2.LanguageAbility.Fluency.FLUENCY_FLUENT,
750 )
751 ],
752 ),
753 )
754 )
756 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
757 assert len(res.language_abilities) == 1
758 assert res.language_abilities[0].code == "eng"
759 assert res.language_abilities[0].fluency == api_pb2.LanguageAbility.Fluency.FLUENCY_FLUENT
761 # change the value to a new one
762 api.UpdateProfile(
763 api_pb2.UpdateProfileReq(
764 language_abilities=api_pb2.RepeatedLanguageAbilityValue(
765 value=[
766 api_pb2.LanguageAbility(
767 code="fin",
768 fluency=api_pb2.LanguageAbility.Fluency.FLUENCY_BEGINNER,
769 )
770 ],
771 ),
772 )
773 )
775 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
776 assert len(res.language_abilities) == 1
777 assert res.language_abilities[0].code == "fin"
778 assert res.language_abilities[0].fluency == api_pb2.LanguageAbility.Fluency.FLUENCY_BEGINNER
780 # should be able to set to same value still
781 api.UpdateProfile(
782 api_pb2.UpdateProfileReq(
783 language_abilities=api_pb2.RepeatedLanguageAbilityValue(
784 value=[
785 api_pb2.LanguageAbility(
786 code="fin",
787 fluency=api_pb2.LanguageAbility.Fluency.FLUENCY_BEGINNER,
788 )
789 ],
790 ),
791 )
792 )
794 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
795 assert len(res.language_abilities) == 1
796 assert res.language_abilities[0].code == "fin"
797 assert res.language_abilities[0].fluency == api_pb2.LanguageAbility.Fluency.FLUENCY_BEGINNER
799 # don't change it
800 api.UpdateProfile(api_pb2.UpdateProfileReq())
802 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
803 assert len(res.language_abilities) == 1
804 assert res.language_abilities[0].code == "fin"
805 assert res.language_abilities[0].fluency == api_pb2.LanguageAbility.Fluency.FLUENCY_BEGINNER
807 # remove value
808 api.UpdateProfile(
809 api_pb2.UpdateProfileReq(
810 language_abilities=api_pb2.RepeatedLanguageAbilityValue(
811 value=[],
812 ),
813 )
814 )
816 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
817 assert len(res.language_abilities) == 0
820def test_pending_friend_request_count(db, moderator):
821 user1, token1 = generate_user()
822 user2, token2 = generate_user()
823 user3, token3 = generate_user()
825 with api_session(token2) as api:
826 res = api.Ping(api_pb2.PingReq())
827 assert res.pending_friend_request_count == 0
829 with api_session(token1) as api:
830 res = api.Ping(api_pb2.PingReq())
831 assert res.pending_friend_request_count == 0
832 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
833 # Sender can still see their own sent requests (even while SHADOWED)
834 res = api.Ping(api_pb2.PingReq())
835 assert res.pending_friend_request_count == 0
837 # Get friend request ID from sender's view (author can see SHADOWED)
838 with api_session(token1) as api:
839 res = api.ListFriendRequests(empty_pb2.Empty())
840 assert len(res.sent) == 1
841 fr_id = res.sent[0].friend_request_id
843 # Recipient cannot see SHADOWED friend requests before mod approval
844 with api_session(token2) as api:
845 res = api.Ping(api_pb2.PingReq())
846 assert res.pending_friend_request_count == 0
848 # Moderator approves the friend request
849 moderator.approve_friend_request(fr_id)
851 # Now recipient can see the approved friend request
852 with api_session(token2) as api:
853 res = api.Ping(api_pb2.PingReq())
854 assert res.pending_friend_request_count == 1
856 with api_session(token2) as api:
857 # check it's there
858 res = api.ListFriendRequests(empty_pb2.Empty())
859 assert len(res.sent) == 0
860 assert len(res.received) == 1
862 assert res.received[0].state == api_pb2.FriendRequest.FriendRequestStatus.PENDING
863 assert res.received[0].user_id == user1.id
865 fr_id = res.received[0].friend_request_id
867 # accept it
868 api.RespondFriendRequest(api_pb2.RespondFriendRequestReq(friend_request_id=fr_id, accept=True))
870 res = api.Ping(api_pb2.PingReq())
871 assert res.pending_friend_request_count == 0
874def test_friend_request_flow(db, email_collector: EmailCollector, push_collector: PushCollector, moderator):
875 user1, token1 = generate_user(complete_profile=True)
876 user2, token2 = generate_user(complete_profile=True)
877 user3, token3 = generate_user()
879 # send a friend request from user1 to user2
880 with api_session(token1) as api:
881 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
883 with session_scope() as session:
884 friend_request = session.execute(
885 select(FriendRelationship).where(
886 FriendRelationship.from_user_id == user1.id, FriendRelationship.to_user_id == user2.id
887 )
888 ).scalar_one()
889 friend_request_id = friend_request.id
891 # Notification is deferred while content is SHADOWED
892 # No push notification sent yet
893 assert push_collector.count_for_user(user2.id) == 0
895 with api_session(token1) as api:
896 # Sender can see their own sent requests (even while SHADOWED)
897 res = api.ListFriendRequests(empty_pb2.Empty())
898 assert len(res.sent) == 1
899 assert len(res.received) == 0
901 assert res.sent[0].state == api_pb2.FriendRequest.FriendRequestStatus.PENDING
902 assert res.sent[0].user_id == user2.id
903 assert res.sent[0].friend_request_id == friend_request_id
905 # Recipient cannot see SHADOWED friend requests
906 with api_session(token2) as api:
907 res = api.ListFriendRequests(empty_pb2.Empty())
908 assert len(res.sent) == 0
909 assert len(res.received) == 0
911 # Moderator approves the friend request - this triggers the notification
912 moderator.approve_friend_request(friend_request_id)
914 push = push_collector.pop_for_user(user2.id, last=True)
915 assert push.content.title == f"Friend request from {user1.name}"
916 assert push.content.body == f"{user1.name} wants to be your friend."
917 assert push.content.action_url == f"http://localhost:3000/connections/friends/?from={user1.id}"
919 email = email_collector.pop_for_recipient(user2.email, last=True)
920 assert email.recipient == user2.email
921 assert email.subject == f"[TEST] {user1.name} wants to be your friend"
922 assert user2.name in email.plain
923 assert user2.name in email.html
924 assert user1.name in email.plain
925 assert user1.name in email.html
926 assert "http://localhost:5001/img/thumbnail/" not in email.plain
927 assert "http://localhost:5001/img/thumbnail/" in email.html
928 assert "http://localhost:3000/connections/friends/" in email.plain
929 assert "http://localhost:3000/connections/friends/" in email.html
931 # Now recipient can see the approved friend request
932 with api_session(token2) as api:
933 # check it's there
934 res = api.ListFriendRequests(empty_pb2.Empty())
935 assert len(res.sent) == 0
936 assert len(res.received) == 1
938 assert res.received[0].state == api_pb2.FriendRequest.FriendRequestStatus.PENDING
939 assert res.received[0].user_id == user1.id
941 fr_id = res.received[0].friend_request_id
943 # accept it
944 api.RespondFriendRequest(api_pb2.RespondFriendRequestReq(friend_request_id=fr_id, accept=True))
946 # check it's gone
947 res = api.ListFriendRequests(empty_pb2.Empty())
948 assert len(res.sent) == 0
949 assert len(res.received) == 0
951 # check we're friends now
952 res = api.ListFriends(empty_pb2.Empty())
953 assert len(res.user_ids) == 1
954 assert res.user_ids[0] == user1.id
956 # user2 got one push (from the friend request creation)
957 # user1 should now have one push (from the friend request acceptance)
958 push = push_collector.pop_for_user(user1.id, last=True)
959 assert push.content.title == f"{user2.name} accepted your friend request"
960 assert push.content.body == f"You are now friends with {user2.name}."
962 email = email_collector.pop_for_recipient(user1.email, last=True)
963 assert email.recipient == user1.email
964 assert email.subject == f"[TEST] {user2.name} accepted your friend request"
965 assert user1.name in email.plain
966 assert user1.name in email.html
967 assert user2.name in email.plain
968 assert user2.name in email.html
969 assert "http://localhost:5001/img/thumbnail/" not in email.plain
970 assert "http://localhost:5001/img/thumbnail/" in email.html
971 assert f"http://localhost:3000/user/{user2.username}" in email.plain
972 assert f"http://localhost:3000/user/{user2.username}" in email.html
974 with api_session(token1) as api:
975 # check it's gone
976 res = api.ListFriendRequests(empty_pb2.Empty())
977 assert len(res.sent) == 0
978 assert len(res.received) == 0
980 # check we're friends now
981 res = api.ListFriends(empty_pb2.Empty())
982 assert len(res.user_ids) == 1
983 assert res.user_ids[0] == user2.id
985 with api_session(token1) as api:
986 # we can't unfriend if we aren't friends
987 with pytest.raises(grpc.RpcError) as err:
988 api.RemoveFriend(api_pb2.RemoveFriendReq(user_id=user3.id))
989 assert err.value.code() == grpc.StatusCode.FAILED_PRECONDITION
990 assert err.value.details() == "You aren't friends with that user!"
992 # we can unfriend
993 res = api.RemoveFriend(api_pb2.RemoveFriendReq(user_id=user2.id))
995 res = api.ListFriends(empty_pb2.Empty())
996 assert len(res.user_ids) == 0
999def test_RemoveFriend_regression(db, push_collector: PushCollector, moderator):
1000 user1, token1 = generate_user(complete_profile=True)
1001 user2, token2 = generate_user(complete_profile=True)
1002 user3, token3 = generate_user()
1003 user4, token4 = generate_user()
1004 user5, token5 = generate_user()
1005 user6, token6 = generate_user()
1007 # Send friend requests
1008 with api_session(token4) as api:
1009 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user1.id))
1010 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
1012 with api_session(token5) as api:
1013 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user1.id))
1015 with api_session(token1) as api:
1016 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
1017 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user3.id))
1019 # Approve all friend requests via moderation
1020 with session_scope() as session:
1021 friend_requests = session.execute(select(FriendRelationship)).scalars().all()
1022 for fr in friend_requests:
1023 moderator.approve_friend_request(fr.id)
1025 # Now recipients can respond
1026 with api_session(token1) as api:
1027 api.RespondFriendRequest(
1028 api_pb2.RespondFriendRequestReq(
1029 friend_request_id=api.ListFriendRequests(empty_pb2.Empty()).received[0].friend_request_id, accept=True
1030 )
1031 )
1033 with api_session(token2) as api:
1034 for fr in api.ListFriendRequests(empty_pb2.Empty()).received:
1035 api.RespondFriendRequest(
1036 api_pb2.RespondFriendRequestReq(friend_request_id=fr.friend_request_id, accept=True)
1037 )
1039 with api_session(token1) as api:
1040 res = api.ListFriends(empty_pb2.Empty())
1041 assert sorted(res.user_ids) == sorted([user2.id, user4.id])
1043 api.RemoveFriend(api_pb2.RemoveFriendReq(user_id=user2.id))
1045 res = api.ListFriends(empty_pb2.Empty())
1046 assert sorted(res.user_ids) == [user4.id]
1048 api.RemoveFriend(api_pb2.RemoveFriendReq(user_id=user4.id))
1050 res = api.ListFriends(empty_pb2.Empty())
1051 assert not res.user_ids
1054def test_cant_friend_request_twice(db):
1055 user1, token1 = generate_user()
1056 user2, token2 = generate_user()
1058 # send friend request from user1 to user2
1059 with api_session(token1) as api:
1060 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
1062 with pytest.raises(grpc.RpcError) as e:
1063 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
1064 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
1065 assert e.value.details() == "You are already friends with or have sent a friend request to that user."
1068def test_cant_friend_request_pending(db):
1069 user1, token1 = generate_user()
1070 user2, token2 = generate_user()
1072 # send friend request from user1 to user2
1073 with api_session(token1) as api:
1074 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
1076 with api_session(token2) as api:
1077 with pytest.raises(grpc.RpcError) as e:
1078 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user1.id))
1079 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
1080 assert e.value.details() == "You are already friends with or have sent a friend request to that user."
1083def test_cant_friend_request_already_friends(db):
1084 user1, token1 = generate_user()
1085 user2, token2 = generate_user()
1086 make_friends(user1, user2)
1088 with api_session(token1) as api:
1089 with pytest.raises(grpc.RpcError) as e:
1090 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
1091 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
1092 assert e.value.details() == "You are already friends with or have sent a friend request to that user."
1094 with api_session(token2) as api:
1095 with pytest.raises(grpc.RpcError) as e:
1096 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user1.id))
1097 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
1098 assert e.value.details() == "You are already friends with or have sent a friend request to that user."
1101def test_cant_friend_request_incomplete_profile(db):
1102 user1, token1 = generate_user(complete_profile=False)
1103 user2, token2 = generate_user(complete_profile=True)
1105 with api_session(token1) as api:
1106 with pytest.raises(grpc.RpcError) as e:
1107 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
1108 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
1109 assert e.value.details() == "You have to complete your profile before you can send a friend request."
1111 # the other direction should still work
1112 with api_session(token2) as api:
1113 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user1.id))
1116def test_excessive_friend_requests_are_reported(db, low_rate_limits, email_collector: EmailCollector):
1117 """Test that excessive friend requests are first reported in a warning email and finally lead blocking of further requests."""
1118 user, token = generate_user()
1119 rate_limit_definition = RATE_LIMIT_DEFINITIONS[RateLimitAction.friend_request]
1120 with api_session(token) as api:
1121 # Test warning email
1122 for _ in range(rate_limit_definition.warning_limit):
1123 friend_user, _ = generate_user()
1124 _ = api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=friend_user.id))
1126 assert email_collector.count_for_reports() == 0
1127 friend_user, _ = generate_user()
1128 _ = api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=friend_user.id))
1130 email = email_collector.pop_for_reports(last=True)
1131 assert email.plain.startswith(
1132 f"User {user.username} has sent {rate_limit_definition.warning_limit} friend requests in the past {RATE_LIMIT_HOURS} hours."
1133 )
1135 # Test ban after exceeding FRIEND_REQUEST_HARD_LIMIT
1136 for _ in range(rate_limit_definition.hard_limit - rate_limit_definition.warning_limit - 1):
1137 friend_user, _ = generate_user()
1138 _ = api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=friend_user.id))
1140 assert email_collector.count_for_reports() == 0
1141 friend_user, _ = generate_user()
1142 with pytest.raises(grpc.RpcError) as exc_info:
1143 _ = api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=friend_user.id))
1144 assert exc_info.value.code() == grpc.StatusCode.RESOURCE_EXHAUSTED
1145 assert (
1146 exc_info.value.details()
1147 == "You have sent a lot of friend requests in the past 24 hours. To avoid spam, you can't send any more for now."
1148 )
1150 email = email_collector.pop_for_reports(last=True)
1151 assert email.plain.startswith(
1152 f"User {user.username} has sent {rate_limit_definition.hard_limit} friend requests in the past {RATE_LIMIT_HOURS} hours."
1153 )
1154 assert "The user has been blocked from sending further friend requests for now." in email.plain
1157def test_ListFriends(db, moderator):
1158 user1, token1 = generate_user()
1159 user2, token2 = generate_user()
1160 user3, token3 = generate_user()
1162 # send friend request from user1 to user2 and user3
1163 with api_session(token1) as api:
1164 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
1165 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user3.id))
1166 # sender can see their sent requests (they are the author)
1167 res = api.ListFriendRequests(empty_pb2.Empty())
1168 assert len(res.sent) == 2
1169 user1_to_user2_id = [req for req in res.sent if req.user_id == user2.id][0].friend_request_id
1170 user1_to_user3_id = [req for req in res.sent if req.user_id == user3.id][0].friend_request_id
1172 with api_session(token3) as api:
1173 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
1174 res = api.ListFriendRequests(empty_pb2.Empty())
1175 user3_to_user2_id = res.sent[0].friend_request_id
1177 # Moderator approves the friend requests so recipients can see them
1178 moderator.approve_friend_request(user1_to_user2_id)
1179 moderator.approve_friend_request(user3_to_user2_id)
1181 with api_session(token2) as api:
1182 res = api.ListFriendRequests(empty_pb2.Empty())
1183 assert len(res.received) == 2
1185 # order is an implementation detail
1186 user1_req = [req for req in res.received if req.user_id == user1.id][0]
1187 user3_req = [req for req in res.received if req.user_id == user3.id][0]
1189 assert user1_req.state == api_pb2.FriendRequest.FriendRequestStatus.PENDING
1190 assert user1_req.user_id == user1.id
1191 api.RespondFriendRequest(
1192 api_pb2.RespondFriendRequestReq(friend_request_id=user1_req.friend_request_id, accept=True)
1193 )
1195 assert user3_req.state == api_pb2.FriendRequest.FriendRequestStatus.PENDING
1196 assert user3_req.user_id == user3.id
1197 api.RespondFriendRequest(
1198 api_pb2.RespondFriendRequestReq(friend_request_id=user3_req.friend_request_id, accept=True)
1199 )
1201 # check we now have two friends
1202 res = api.ListFriends(empty_pb2.Empty())
1203 assert len(res.user_ids) == 2
1204 assert user1.id in res.user_ids
1205 assert user3.id in res.user_ids
1207 # Moderator approves user1's friend request to user3 so user3 can see it
1208 moderator.approve_friend_request(user1_to_user3_id)
1210 with api_session(token3) as api:
1211 res = api.ListFriends(empty_pb2.Empty())
1212 assert len(res.user_ids) == 1
1213 assert user2.id in res.user_ids
1215 res = api.ListFriendRequests(empty_pb2.Empty())
1216 assert len(res.received) == 1
1217 assert res.received[0].state == api_pb2.FriendRequest.FriendRequestStatus.PENDING
1218 assert res.received[0].user_id == user1.id
1219 fr_id = res.received[0].friend_request_id
1220 api.RespondFriendRequest(api_pb2.RespondFriendRequestReq(friend_request_id=fr_id, accept=True))
1222 res = api.ListFriends(empty_pb2.Empty())
1223 assert len(res.user_ids) == 2
1224 assert user1.id in res.user_ids
1225 assert user2.id in res.user_ids
1227 with api_session(token1) as api:
1228 res = api.ListFriends(empty_pb2.Empty())
1229 assert len(res.user_ids) == 2
1230 assert user2.id in res.user_ids
1231 assert user3.id in res.user_ids
1234def test_ListMutualFriends(db):
1235 user1, token1 = generate_user()
1236 user2, token2 = generate_user()
1237 user3, token3 = generate_user()
1238 user4, token4 = generate_user()
1239 user5, token5 = generate_user()
1241 # arrange friends like this: 1<->2, 1<->3, 1<->4, 1<->5, 3<->2, 3<->4,
1242 # so 1 and 2 should have mutual friend 3 only
1243 make_friends(user1, user2)
1244 make_friends(user1, user3)
1245 make_friends(user1, user4)
1246 make_friends(user1, user5)
1247 make_friends(user3, user2)
1248 make_friends(user3, user4)
1250 with api_session(token1) as api:
1251 mutual_friends = api.ListMutualFriends(api_pb2.ListMutualFriendsReq(user_id=user2.id)).mutual_friends
1252 assert len(mutual_friends) == 1
1253 assert mutual_friends[0].user_id == user3.id
1255 # and other way around same
1256 with api_session(token2) as api:
1257 mutual_friends = api.ListMutualFriends(api_pb2.ListMutualFriendsReq(user_id=user1.id)).mutual_friends
1258 assert len(mutual_friends) == 1
1259 assert mutual_friends[0].user_id == user3.id
1261 # Check pending request doesn't have effect
1262 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user5.id))
1264 mutual_friends = api.ListMutualFriends(api_pb2.ListMutualFriendsReq(user_id=user1.id)).mutual_friends
1265 assert len(mutual_friends) == 1
1266 assert mutual_friends[0].user_id == user3.id
1268 # both ways
1269 with api_session(token1) as api:
1270 mutual_friends = api.ListMutualFriends(api_pb2.ListMutualFriendsReq(user_id=user2.id)).mutual_friends
1271 assert len(mutual_friends) == 1
1272 assert mutual_friends[0].user_id == user3.id
1275def test_mutual_friends_self(db):
1276 user1, token1 = generate_user()
1277 user2, token2 = generate_user()
1278 user3, token3 = generate_user()
1279 user4, token4 = generate_user()
1281 make_friends(user1, user2)
1282 make_friends(user2, user3)
1283 make_friends(user1, user4)
1285 with api_session(token1) as api:
1286 res = api.ListMutualFriends(api_pb2.ListMutualFriendsReq(user_id=user1.id))
1287 assert len(res.mutual_friends) == 0
1289 with api_session(token2) as api:
1290 res = api.ListMutualFriends(api_pb2.ListMutualFriendsReq(user_id=user2.id))
1291 assert len(res.mutual_friends) == 0
1293 with api_session(token3) as api:
1294 res = api.ListMutualFriends(api_pb2.ListMutualFriendsReq(user_id=user3.id))
1295 assert len(res.mutual_friends) == 0
1297 with api_session(token4) as api:
1298 res = api.ListMutualFriends(api_pb2.ListMutualFriendsReq(user_id=user4.id))
1299 assert len(res.mutual_friends) == 0
1302def test_CancelFriendRequest(db):
1303 user1, token1 = generate_user()
1304 user2, token2 = generate_user()
1306 with api_session(token1) as api:
1307 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
1309 res = api.ListFriendRequests(empty_pb2.Empty())
1310 assert res.sent[0].user_id == user2.id
1311 fr_id = res.sent[0].friend_request_id
1313 api.CancelFriendRequest(api_pb2.CancelFriendRequestReq(friend_request_id=fr_id))
1315 # check it's gone
1316 res = api.ListFriendRequests(empty_pb2.Empty())
1317 assert len(res.sent) == 0
1318 assert len(res.received) == 0
1320 # check not friends
1321 res = api.ListFriends(empty_pb2.Empty())
1322 assert len(res.user_ids) == 0
1324 with api_session(token2) as api:
1325 # check it's gone
1326 res = api.ListFriendRequests(empty_pb2.Empty())
1327 assert len(res.sent) == 0
1328 assert len(res.received) == 0
1330 # check we're not friends
1331 res = api.ListFriends(empty_pb2.Empty())
1332 assert len(res.user_ids) == 0
1334 with api_session(token1) as api:
1335 # check we can send another friend req
1336 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
1338 res = api.ListFriendRequests(empty_pb2.Empty())
1339 assert res.sent[0].state == api_pb2.FriendRequest.FriendRequestStatus.PENDING
1340 assert res.sent[0].user_id == user2.id
1343def test_accept_friend_request(db, moderator):
1344 user1, token1 = generate_user()
1345 user2, token2 = generate_user()
1347 with session_scope() as session:
1348 moderation_state = ModerationState(
1349 object_type=ModerationObjectType.friend_request,
1350 object_id=0,
1351 visibility=ModerationVisibility.visible,
1352 )
1353 session.add(moderation_state)
1354 session.flush()
1355 friend_request = FriendRelationship(
1356 from_user_id=user1.id,
1357 to_user_id=user2.id,
1358 status=FriendStatus.pending,
1359 moderation_state_id=moderation_state.id,
1360 )
1361 session.add(friend_request)
1362 session.flush()
1363 moderation_state.object_id = friend_request.id
1364 session.commit()
1365 friend_request_id = friend_request.id
1367 with api_session(token2) as api:
1368 # check request pending
1369 res = api.ListFriendRequests(empty_pb2.Empty())
1370 assert len(res.received) == 1
1371 assert res.received[0].user_id == user1.id
1373 api.RespondFriendRequest(api_pb2.RespondFriendRequestReq(friend_request_id=friend_request_id, accept=True))
1375 # check request is gone
1376 res = api.ListFriendRequests(empty_pb2.Empty())
1377 assert len(res.sent) == 0
1378 assert len(res.received) == 0
1380 # check now friends
1381 res = api.ListFriends(empty_pb2.Empty())
1382 assert len(res.user_ids) == 1
1383 assert res.user_ids[0] == user1.id
1385 with api_session(token1) as api:
1386 # check request gone
1387 res = api.ListFriendRequests(empty_pb2.Empty())
1388 assert len(res.sent) == 0
1389 assert len(res.received) == 0
1391 # check now friends
1392 res = api.ListFriends(empty_pb2.Empty())
1393 assert len(res.user_ids) == 1
1394 assert res.user_ids[0] == user2.id
1397def test_reject_friend_request(db, moderator):
1398 user1, token1 = generate_user()
1399 user2, token2 = generate_user()
1401 with api_session(token1) as api:
1402 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
1404 res = api.ListFriendRequests(empty_pb2.Empty())
1405 assert res.sent[0].state == api_pb2.FriendRequest.FriendRequestStatus.PENDING
1406 assert res.sent[0].user_id == user2.id
1407 fr_id = res.sent[0].friend_request_id
1409 # Moderator approves the friend request so recipient can see it
1410 moderator.approve_friend_request(fr_id)
1412 with api_session(token2) as api:
1413 res = api.ListFriendRequests(empty_pb2.Empty())
1414 assert res.received[0].state == api_pb2.FriendRequest.FriendRequestStatus.PENDING
1415 assert res.received[0].user_id == user1.id
1417 fr_id = res.received[0].friend_request_id
1419 # reject it
1420 api.RespondFriendRequest(api_pb2.RespondFriendRequestReq(friend_request_id=fr_id, accept=False))
1422 # check it's gone
1423 res = api.ListFriendRequests(empty_pb2.Empty())
1424 assert len(res.sent) == 0
1425 assert len(res.received) == 0
1427 # check not friends
1428 res = api.ListFriends(empty_pb2.Empty())
1429 assert len(res.user_ids) == 0
1431 with api_session(token1) as api:
1432 # check it's gone
1433 res = api.ListFriendRequests(empty_pb2.Empty())
1434 assert len(res.sent) == 0
1435 assert len(res.received) == 0
1437 # check we're not friends
1438 res = api.ListFriends(empty_pb2.Empty())
1439 assert len(res.user_ids) == 0
1441 # check we can send another friend req
1442 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
1444 res = api.ListFriendRequests(empty_pb2.Empty())
1445 assert res.sent[0].state == api_pb2.FriendRequest.FriendRequestStatus.PENDING
1446 assert res.sent[0].user_id == user2.id
1449def test_hosting_preferences(db):
1450 user1, token1 = generate_user()
1451 user2, token2 = generate_user()
1453 with api_session(token1) as api:
1454 res = api.GetUser(api_pb2.GetUserReq(user=user1.username))
1455 assert not res.HasField("max_guests")
1456 assert not res.HasField("last_minute")
1457 assert not res.HasField("has_pets")
1458 assert not res.HasField("accepts_pets")
1459 assert not res.HasField("pet_details")
1460 assert not res.HasField("has_kids")
1461 assert not res.HasField("accepts_kids")
1462 assert not res.HasField("kid_details")
1463 assert not res.HasField("has_housemates")
1464 assert not res.HasField("housemate_details")
1465 assert not res.HasField("wheelchair_accessible")
1466 assert res.smoking_allowed == api_pb2.SMOKING_LOCATION_UNKNOWN
1467 assert not res.HasField("smokes_at_home")
1468 assert not res.HasField("drinking_allowed")
1469 assert not res.HasField("drinks_at_home")
1470 assert not res.HasField("other_host_info")
1471 assert res.sleeping_arrangement == api_pb2.SLEEPING_ARRANGEMENT_UNKNOWN
1472 assert not res.HasField("sleeping_details")
1473 assert not res.HasField("area")
1474 assert not res.HasField("house_rules")
1475 assert not res.HasField("parking")
1476 assert res.parking_details == api_pb2.PARKING_DETAILS_UNKNOWN
1477 assert not res.HasField("camping_ok")
1479 api.UpdateProfile(
1480 api_pb2.UpdateProfileReq(
1481 max_guests=api_pb2.NullableUInt32Value(value=3),
1482 last_minute=api_pb2.NullableBoolValue(value=True),
1483 has_pets=api_pb2.NullableBoolValue(value=False),
1484 accepts_pets=api_pb2.NullableBoolValue(value=True),
1485 pet_details=api_pb2.NullableStringValue(value="I love dogs"),
1486 has_kids=api_pb2.NullableBoolValue(value=False),
1487 accepts_kids=api_pb2.NullableBoolValue(value=True),
1488 kid_details=api_pb2.NullableStringValue(value="I hate kids"),
1489 has_housemates=api_pb2.NullableBoolValue(value=False),
1490 housemate_details=api_pb2.NullableStringValue(value="I have 7 housemates"),
1491 wheelchair_accessible=api_pb2.NullableBoolValue(value=True),
1492 smoking_allowed=api_pb2.SMOKING_LOCATION_WINDOW,
1493 area=api_pb2.NullableStringValue(value="area!"),
1494 smokes_at_home=api_pb2.NullableBoolValue(value=False),
1495 drinking_allowed=api_pb2.NullableBoolValue(value=True),
1496 drinks_at_home=api_pb2.NullableBoolValue(value=False),
1497 other_host_info=api_pb2.NullableStringValue(value="I'm pretty swell"),
1498 sleeping_arrangement=api_pb2.SLEEPING_ARRANGEMENT_COMMON,
1499 sleeping_details=api_pb2.NullableStringValue(value="Couch in living room"),
1500 house_rules=api_pb2.NullableStringValue(value="RULES!"),
1501 parking=api_pb2.NullableBoolValue(value=True),
1502 parking_details=api_pb2.PARKING_DETAILS_PAID_ONSITE,
1503 camping_ok=api_pb2.NullableBoolValue(value=False),
1504 )
1505 )
1507 # Use a second user to view the hosting preferences just to check
1508 # that it is public information.
1509 with api_session(token2) as api:
1510 res = api.GetUser(api_pb2.GetUserReq(user=user1.username))
1511 assert res.max_guests.value == 3
1512 assert res.last_minute.value
1513 assert not res.has_pets.value
1514 assert res.accepts_pets.value
1515 assert res.pet_details.value == "I love dogs"
1516 assert not res.has_kids.value
1517 assert res.accepts_kids.value
1518 assert res.kid_details.value == "I hate kids"
1519 assert not res.has_housemates.value
1520 assert res.housemate_details.value == "I have 7 housemates"
1521 assert res.wheelchair_accessible.value
1522 assert res.smoking_allowed == api_pb2.SMOKING_LOCATION_WINDOW
1523 assert not res.smokes_at_home.value
1524 assert res.drinking_allowed.value
1525 assert not res.drinks_at_home.value
1526 assert res.other_host_info.value == "I'm pretty swell"
1527 assert res.sleeping_arrangement == api_pb2.SLEEPING_ARRANGEMENT_COMMON
1528 assert res.sleeping_details.value == "Couch in living room"
1529 assert res.area.value == "area!"
1530 assert res.house_rules.value == "RULES!"
1531 assert res.parking.value
1532 assert res.parking_details == api_pb2.PARKING_DETAILS_PAID_ONSITE
1533 assert not res.camping_ok.value
1535 # test unsetting
1536 with api_session(token1) as api:
1537 api.UpdateProfile(
1538 api_pb2.UpdateProfileReq(
1539 max_guests=api_pb2.NullableUInt32Value(is_null=True),
1540 last_minute=api_pb2.NullableBoolValue(is_null=True),
1541 has_pets=api_pb2.NullableBoolValue(is_null=True),
1542 accepts_pets=api_pb2.NullableBoolValue(is_null=True),
1543 pet_details=api_pb2.NullableStringValue(is_null=True),
1544 has_kids=api_pb2.NullableBoolValue(is_null=True),
1545 accepts_kids=api_pb2.NullableBoolValue(is_null=True),
1546 kid_details=api_pb2.NullableStringValue(is_null=True),
1547 has_housemates=api_pb2.NullableBoolValue(is_null=True),
1548 housemate_details=api_pb2.NullableStringValue(is_null=True),
1549 wheelchair_accessible=api_pb2.NullableBoolValue(is_null=True),
1550 smoking_allowed=api_pb2.SMOKING_LOCATION_UNKNOWN,
1551 area=api_pb2.NullableStringValue(is_null=True),
1552 smokes_at_home=api_pb2.NullableBoolValue(is_null=True),
1553 drinking_allowed=api_pb2.NullableBoolValue(is_null=True),
1554 drinks_at_home=api_pb2.NullableBoolValue(is_null=True),
1555 other_host_info=api_pb2.NullableStringValue(is_null=True),
1556 sleeping_arrangement=api_pb2.SLEEPING_ARRANGEMENT_UNKNOWN,
1557 sleeping_details=api_pb2.NullableStringValue(is_null=True),
1558 house_rules=api_pb2.NullableStringValue(is_null=True),
1559 parking=api_pb2.NullableBoolValue(is_null=True),
1560 parking_details=api_pb2.PARKING_DETAILS_UNKNOWN,
1561 camping_ok=api_pb2.NullableBoolValue(is_null=True),
1562 )
1563 )
1565 res = api.GetUser(api_pb2.GetUserReq(user=user1.username))
1566 assert not res.HasField("max_guests")
1567 assert not res.HasField("last_minute")
1568 assert not res.HasField("has_pets")
1569 assert not res.HasField("accepts_pets")
1570 assert not res.HasField("pet_details")
1571 assert not res.HasField("has_kids")
1572 assert not res.HasField("accepts_kids")
1573 assert not res.HasField("kid_details")
1574 assert not res.HasField("has_housemates")
1575 assert not res.HasField("housemate_details")
1576 assert not res.HasField("wheelchair_accessible")
1577 assert res.smoking_allowed == api_pb2.SMOKING_LOCATION_UNKNOWN
1578 assert not res.HasField("smokes_at_home")
1579 assert not res.HasField("drinking_allowed")
1580 assert not res.HasField("drinks_at_home")
1581 assert not res.HasField("other_host_info")
1582 assert res.sleeping_arrangement == api_pb2.SLEEPING_ARRANGEMENT_UNKNOWN
1583 assert not res.HasField("sleeping_details")
1584 assert not res.HasField("area")
1585 assert not res.HasField("house_rules")
1586 assert not res.HasField("parking")
1587 assert res.parking_details == api_pb2.PARKING_DETAILS_UNKNOWN
1588 assert not res.HasField("camping_ok")
1591def test_badges(db):
1592 user1, _ = generate_user(last_donated=None)
1593 user2, _ = generate_user(last_donated=None)
1594 user3, _ = generate_user(last_donated=None)
1595 user4, token = generate_user(last_donated=None)
1597 update_badges(empty_pb2.Empty())
1599 founder_badge = get_badge_dict()["founder"]
1600 board_member_badge = get_badge_dict()["board_member"]
1602 with api_session(token) as api:
1603 assert api.GetUser(api_pb2.GetUserReq(user=user1.username)).badges == ["founder", "board_member"]
1604 assert api.GetUser(api_pb2.GetUserReq(user=user2.username)).badges == ["founder", "board_member"]
1605 assert api.GetUser(api_pb2.GetUserReq(user=user3.username)).badges == []
1607 assert api.ListBadgeUsers(api_pb2.ListBadgeUsersReq(badge_id=founder_badge.id)).user_ids == [1, 2]
1608 res = api.ListBadgeUsers(api_pb2.ListBadgeUsersReq(badge_id=board_member_badge.id, page_size=1))
1609 assert res.user_ids == [1]
1610 res2 = api.ListBadgeUsers(
1611 api_pb2.ListBadgeUsersReq(badge_id=board_member_badge.id, page_token=res.next_page_token)
1612 )
1613 assert res2.user_ids == [2]
1616def test_user_add_badge_is_idempotent(db):
1617 """Test that adding a badge a user already has is a no-op and doesn't send a duplicate notification."""
1618 user, _ = generate_user()
1620 with session_scope() as session:
1621 user_add_badge(session, user.id, "volunteer")
1623 # one badge row, one notification
1624 with session_scope() as session:
1625 badge_count = session.execute(
1626 select(func.count())
1627 .select_from(UserBadge)
1628 .where(UserBadge.user_id == user.id, UserBadge.badge_id == "volunteer")
1629 ).scalar()
1630 assert badge_count == 1
1631 notification_count = session.execute(
1632 select(func.count()).select_from(Notification).where(Notification.user_id == user.id)
1633 ).scalar()
1634 assert notification_count == 1
1636 # add the same badge again
1637 with session_scope() as session:
1638 user_add_badge(session, user.id, "volunteer")
1640 # still one badge row, no new notification
1641 with session_scope() as session:
1642 badge_count = session.execute(
1643 select(func.count())
1644 .select_from(UserBadge)
1645 .where(UserBadge.user_id == user.id, UserBadge.badge_id == "volunteer")
1646 ).scalar()
1647 assert badge_count == 1
1648 notification_count = session.execute(
1649 select(func.count()).select_from(Notification).where(Notification.user_id == user.id)
1650 ).scalar()
1651 assert notification_count == 1
1654@pytest.mark.parametrize("flag", ["deleted_at", "banned_at"])
1655def test_ListBadgeUsers_excludes_ghost_users(db, flag):
1656 """Test that ListBadgeUsers does not return deleted/banned users."""
1657 user1, token1 = generate_user()
1658 user2, _ = generate_user()
1659 user3, _ = generate_user()
1661 volunteer_badge = get_badge_dict()["volunteer"]
1663 # Give all three users the volunteer badge
1664 with session_scope() as session:
1665 user_add_badge(session, user1.id, "volunteer", do_notify=False)
1666 user_add_badge(session, user2.id, "volunteer", do_notify=False)
1667 user_add_badge(session, user3.id, "volunteer", do_notify=False)
1669 # Verify all three users appear in the badge list
1670 with api_session(token1) as api:
1671 res = api.ListBadgeUsers(api_pb2.ListBadgeUsersReq(badge_id=volunteer_badge.id))
1672 assert set(res.user_ids) == {user1.id, user2.id, user3.id}
1674 # Make user2 invisible (deleted or banned)
1675 with session_scope() as session:
1676 db_user2 = session.execute(select(User).where(User.id == user2.id)).scalar_one()
1677 setattr(db_user2, flag, now())
1679 # Now user2 should not appear in the badge list
1680 with api_session(token1) as api:
1681 res = api.ListBadgeUsers(api_pb2.ListBadgeUsersReq(badge_id=volunteer_badge.id))
1682 assert set(res.user_ids) == {user1.id, user3.id}
1685@pytest.mark.parametrize("flag", ["deleted_at", "banned_at"])
1686def test_GetLiteUser_ghost_user_by_username(db, flag):
1687 """Test that GetLiteUser returns a ghost profile for deleted/banned users when querying by username."""
1688 user1, token1 = generate_user()
1689 user2, _ = generate_user()
1691 # Make user2 invisible
1692 with session_scope() as session:
1693 db_user2 = session.merge(user2)
1694 setattr(db_user2, flag, now())
1695 session.commit()
1697 # Refresh the materialized view
1698 refresh_materialized_views_rapid(empty_pb2.Empty())
1700 with api_session(token1) as api:
1701 # Query by username
1702 lite_user = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user2.username))
1704 assert lite_user.user_id == user2.id
1705 assert lite_user.username == "ghost"
1706 assert lite_user.name == "Deactivated Account"
1707 assert lite_user.lat == 0
1708 assert lite_user.lng == 0
1709 assert lite_user.radius == 0
1710 assert lite_user.city == ""
1711 assert lite_user.age == 0
1712 assert lite_user.avatar_url == ""
1713 assert lite_user.avatar_thumbnail_url == ""
1714 assert not lite_user.has_strong_verification
1717@pytest.mark.parametrize("flag", ["deleted_at", "banned_at"])
1718def test_GetLiteUser_ghost_user_by_id(db, flag):
1719 """Test that GetLiteUser returns a ghost profile for deleted/banned users when querying by ID."""
1720 user1, token1 = generate_user()
1721 user2, _ = generate_user()
1723 # Make user2 invisible
1724 with session_scope() as session:
1725 db_user2 = session.merge(user2)
1726 setattr(db_user2, flag, now())
1727 session.commit()
1729 # Refresh the materialized view
1730 refresh_materialized_views_rapid(empty_pb2.Empty())
1732 with api_session(token1) as api:
1733 # Query by ID
1734 lite_user = api.GetLiteUser(api_pb2.GetLiteUserReq(user=str(user2.id)))
1736 assert lite_user.user_id == user2.id
1737 assert lite_user.username == "ghost"
1738 assert lite_user.name == "Deactivated Account"
1739 assert lite_user.lat == 0
1740 assert lite_user.lng == 0
1741 assert lite_user.radius == 0
1742 assert lite_user.city == ""
1743 assert lite_user.age == 0
1744 assert lite_user.avatar_url == ""
1745 assert lite_user.avatar_thumbnail_url == ""
1746 assert not lite_user.has_strong_verification
1749def test_GetLiteUser_blocked_user(db):
1750 """Test that GetLiteUser returns a ghost profile for blocked users."""
1751 user1, token1 = generate_user()
1752 user2, _ = generate_user()
1754 # User1 blocks user2
1755 make_user_block(user1, user2)
1757 # Refresh the materialized view
1758 refresh_materialized_views_rapid(empty_pb2.Empty())
1760 with api_session(token1) as api:
1761 # Query by username
1762 lite_user = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user2.username))
1764 assert lite_user.user_id == user2.id
1765 assert lite_user.is_ghost
1766 assert lite_user.username == "ghost"
1767 assert lite_user.name == "Deactivated Account"
1769 # Query by ID
1770 lite_user = api.GetLiteUser(api_pb2.GetLiteUserReq(user=str(user2.id)))
1772 assert lite_user.user_id == user2.id
1773 assert lite_user.is_ghost
1774 assert lite_user.username == "ghost"
1775 assert lite_user.name == "Deactivated Account"
1778def test_GetLiteUser_blocking_user(db):
1779 """Test that GetLiteUser returns a ghost profile when the target user has blocked the requester."""
1780 user1, token1 = generate_user()
1781 user2, _ = generate_user()
1783 # User2 blocks user1
1784 make_user_block(user2, user1)
1786 # Refresh the materialized view
1787 refresh_materialized_views_rapid(empty_pb2.Empty())
1789 with api_session(token1) as api:
1790 # Query by username
1791 lite_user = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user2.username))
1793 assert lite_user.user_id == user2.id
1794 assert lite_user.is_ghost
1795 assert lite_user.username == "ghost"
1796 assert lite_user.name == "Deactivated Account"
1798 # Query by ID
1799 lite_user = api.GetLiteUser(api_pb2.GetLiteUserReq(user=str(user2.id)))
1801 assert lite_user.user_id == user2.id
1802 assert lite_user.is_ghost
1803 assert lite_user.username == "ghost"
1804 assert lite_user.name == "Deactivated Account"
1807@pytest.mark.parametrize("flag", ["deleted_at", "banned_at"])
1808def test_GetLiteUsers_ghost_users(db, flag):
1809 """Test that GetLiteUsers returns ghost profiles for deleted/banned users."""
1810 user1, token1 = generate_user()
1811 user2, _ = generate_user()
1812 user3, _ = generate_user()
1813 user4, _ = generate_user()
1815 # Make user2 and user4 invisible
1816 with session_scope() as session:
1817 db_user2 = session.merge(user2)
1818 setattr(db_user2, flag, now())
1819 db_user4 = session.merge(user4)
1820 setattr(db_user4, flag, now())
1821 session.commit()
1823 # Refresh the materialized view
1824 refresh_materialized_views_rapid(empty_pb2.Empty())
1826 with api_session(token1) as api:
1827 res = api.GetLiteUsers(
1828 api_pb2.GetLiteUsersReq(
1829 users=[
1830 user1.username, # visible
1831 user2.username, # ghost
1832 str(user3.id), # visible
1833 str(user4.id), # ghost
1834 ]
1835 )
1836 )
1838 assert len(res.responses) == 4
1840 # user1 - visible, normal profile
1841 assert res.responses[0].query == user1.username
1842 assert not res.responses[0].not_found
1843 assert res.responses[0].user.user_id == user1.id
1844 assert res.responses[0].user.username == user1.username
1845 assert res.responses[0].user.name == user1.name
1847 # user2 - ghost by username
1848 assert res.responses[1].query == user2.username
1849 assert not res.responses[1].not_found
1850 assert res.responses[1].user.user_id == user2.id
1851 assert res.responses[1].user.is_ghost
1852 assert res.responses[1].user.username == "ghost"
1853 assert res.responses[1].user.name == "Deactivated Account"
1855 # user3 - visible, normal profile
1856 assert res.responses[2].query == str(user3.id)
1857 assert not res.responses[2].not_found
1858 assert res.responses[2].user.user_id == user3.id
1859 assert res.responses[2].user.username == user3.username
1860 assert res.responses[2].user.name == user3.name
1862 # user4 - ghost by ID
1863 assert res.responses[3].query == str(user4.id)
1864 assert not res.responses[3].not_found
1865 assert res.responses[3].user.user_id == user4.id
1866 assert res.responses[3].user.is_ghost
1867 assert res.responses[3].user.username == "ghost"
1868 assert res.responses[3].user.name == "Deactivated Account"
1871def test_GetLiteUsers_blocked_users(db):
1872 """Test that GetLiteUsers returns ghost profiles for blocked users."""
1873 user1, token1 = generate_user()
1874 user2, _ = generate_user()
1875 user3, _ = generate_user()
1876 user4, _ = generate_user()
1877 user5, _ = generate_user()
1879 # User1 blocks user2
1880 make_user_block(user1, user2)
1881 # User4 blocks user1
1882 make_user_block(user4, user1)
1884 # Refresh the materialized view
1885 refresh_materialized_views_rapid(empty_pb2.Empty())
1887 with api_session(token1) as api:
1888 res = api.GetLiteUsers(
1889 api_pb2.GetLiteUsersReq(
1890 users=[
1891 user2.username, # user1 blocked user2
1892 str(user3.id), # visible
1893 user4.username, # user4 blocked user1
1894 str(user5.id), # visible
1895 ]
1896 )
1897 )
1899 assert len(res.responses) == 4
1901 # user2 - blocked by user1, should be ghost
1902 assert res.responses[0].query == user2.username
1903 assert not res.responses[0].not_found
1904 assert res.responses[0].user.user_id == user2.id
1905 assert res.responses[0].user.is_ghost
1906 assert res.responses[0].user.username == "ghost"
1907 assert res.responses[0].user.name == "Deactivated Account"
1909 # user3 - visible
1910 assert res.responses[1].query == str(user3.id)
1911 assert not res.responses[1].not_found
1912 assert res.responses[1].user.user_id == user3.id
1913 assert res.responses[1].user.username == user3.username
1915 # user4 - user4 blocked user1, should be ghost
1916 assert res.responses[2].query == user4.username
1917 assert not res.responses[2].not_found
1918 assert res.responses[2].user.user_id == user4.id
1919 assert res.responses[2].user.is_ghost
1920 assert res.responses[2].user.username == "ghost"
1921 assert res.responses[2].user.name == "Deactivated Account"
1923 # user5 - visible
1924 assert res.responses[3].query == str(user5.id)
1925 assert not res.responses[3].not_found
1926 assert res.responses[3].user.user_id == user5.id
1927 assert res.responses[3].user.username == user5.username
1930@pytest.mark.parametrize("flag", ["deleted_at", "banned_at"])
1931def test_GetUser_ghost_user_by_id(db, flag):
1932 """Test that GetUser returns a ghost profile for deleted/banned users when querying by ID."""
1933 user1, token1 = generate_user()
1934 user2, _ = generate_user()
1936 # Make user2 invisible
1937 with session_scope() as session:
1938 db_user2 = session.merge(user2)
1939 setattr(db_user2, flag, now())
1940 session.commit()
1942 with api_session(token1) as api:
1943 # Query by ID
1944 user_pb = api.GetUser(api_pb2.GetUserReq(user=str(user2.id)))
1946 assert user_pb.user_id == user2.id
1947 assert user_pb.username == "ghost"
1948 assert user_pb.name == "Deactivated Account"
1949 assert user_pb.city == ""
1950 assert user_pb.hosting_status == 0
1951 assert user_pb.meetup_status == 0
1954def test_GetUser_blocked_user(db):
1955 """Test that GetUser returns a ghost profile for blocked users."""
1956 user1, token1 = generate_user()
1957 user2, _ = generate_user()
1959 # User1 blocks user2
1960 make_user_block(user1, user2)
1962 with api_session(token1) as api:
1963 # Query by username
1964 user_pb = api.GetUser(api_pb2.GetUserReq(user=user2.username))
1966 assert user_pb.user_id == user2.id
1967 assert user_pb.username == "ghost"
1968 assert user_pb.name == "Deactivated Account"
1970 # Query by ID
1971 user_pb = api.GetUser(api_pb2.GetUserReq(user=str(user2.id)))
1973 assert user_pb.user_id == user2.id
1974 assert user_pb.username == "ghost"
1975 assert user_pb.name == "Deactivated Account"
1978def test_GetUser_blocking_user(db):
1979 """Test that GetUser returns a ghost profile when the target user has blocked the requester."""
1980 user1, token1 = generate_user()
1981 user2, _ = generate_user()
1983 # User2 blocks user1
1984 make_user_block(user2, user1)
1986 with api_session(token1) as api:
1987 # Query by username
1988 user_pb = api.GetUser(api_pb2.GetUserReq(user=user2.username))
1990 assert user_pb.user_id == user2.id
1991 assert user_pb.username == "ghost"
1992 assert user_pb.name == "Deactivated Account"
1994 # Query by ID
1995 user_pb = api.GetUser(api_pb2.GetUserReq(user=str(user2.id)))
1997 assert user_pb.user_id == user2.id
1998 assert user_pb.username == "ghost"
1999 assert user_pb.name == "Deactivated Account"