Coverage for app/backend/src/tests/test_api.py: 100%
1201 statements
« prev ^ index » next coverage.py v7.17.0, created at 2026-11-03 11:56 +0000
« prev ^ index » next coverage.py v7.17.0, created at 2026-11-03 11:56 +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 RateLimitViolation,
24 User,
25 UserBadge,
26)
27from couchers.models.notifications import Notification
28from couchers.proto import admin_pb2, api_pb2, blocking_pb2, jail_pb2, notifications_pb2
29from couchers.rate_limits.definitions import RATE_LIMIT_DEFINITIONS, RATE_LIMIT_HOURS
30from couchers.resources import get_badge_dict
31from couchers.utils import create_coordinate, now, to_aware_datetime
32from tests.fixtures.db import generate_user, make_friends, make_user_block
33from tests.fixtures.misc import EmailCollector, PushCollector
34from tests.fixtures.sessions import (
35 api_session,
36 blocking_session,
37 notifications_session,
38 real_api_session,
39 real_jail_session,
40)
41from tests.fixtures.sessions import (
42 real_admin_session as admin_session,
43)
46def test_ping(db):
47 user, token = generate_user(
48 regions_lived=["ESP", "FRA", "EST"],
49 regions_visited=["CHE", "REU", "FIN"],
50 language_abilities=[
51 ("fin", LanguageFluency.fluent),
52 ("fra", LanguageFluency.beginner),
53 ],
54 )
56 with real_api_session(token) as api:
57 res = api.Ping(api_pb2.PingReq())
59 assert res.user.user_id == user.id
60 assert res.user.username == user.username
61 assert res.user.name == user.name
62 assert res.user.city == user.city
63 assert res.user.hometown == user.hometown
64 assert res.user.verification == 0.0
65 assert res.user.community_standing == user.community_standing
66 assert res.user.num_references == 0
67 assert res.user.gender == user.gender
68 assert res.user.pronouns == user.pronouns
69 assert res.user.age == user.age
71 assert (res.user.lat, res.user.lng) == user.coordinates
73 # the joined time is fuzzed
74 # but shouldn't be before actual joined time, or more than one hour behind
75 assert user.joined - timedelta(hours=1) <= to_aware_datetime(res.user.joined) <= user.joined
76 # same for last_active
77 assert user.last_active - timedelta(hours=1) <= to_aware_datetime(res.user.last_active) <= user.last_active
79 assert res.user.hosting_status == api_pb2.HOSTING_STATUS_CANT_HOST
80 assert res.user.meetup_status == api_pb2.MEETUP_STATUS_OPEN_TO_MEETUP
82 assert res.user.occupation == user.occupation
83 assert res.user.education == user.education
84 assert res.user.about_me == user.about_me
85 assert res.user.things_i_like == user.things_i_like
86 assert {language_ability.code for language_ability in res.user.language_abilities} == {"fin", "fra"}
87 assert res.user.about_place == user.about_place
88 assert res.user.regions_visited == ["FIN", "REU", "CHE"] # Tests alphabetization by region name
89 assert res.user.regions_lived == ["EST", "FRA", "ESP"] # Ditto
90 assert res.user.additional_information == user.additional_information
92 assert res.user.friends == api_pb2.User.FriendshipStatus.NA
93 assert not res.user.HasField("pending_friend_request")
96def test_coords(db):
97 # make them need to update location
98 user1, token1 = generate_user(geom=create_coordinate(1, 0), geom_radius=2000, needs_to_update_location=True)
99 user2, token2 = generate_user()
101 with api_session(token2) as api:
102 res = api.Ping(api_pb2.PingReq())
103 assert res.user.city == user2.city
104 lat, lng = user2.coordinates
105 assert res.user.lat == lat
106 assert res.user.lng == lng
107 assert res.user.radius == user2.geom_radius
109 with api_session(token2) as api:
110 res = api.GetUser(api_pb2.GetUserReq(user=user1.username))
111 assert res.city == user1.city
112 assert res.lat == 1.0
113 assert res.lng == 0.0
114 assert res.radius == 2000.0
116 # Check coordinate wrapping
117 user3, token3 = generate_user(geom=create_coordinate(40.0, -180.5))
118 user4, token4 = generate_user(geom=create_coordinate(40.0, 20.0))
119 user5, token5 = generate_user(geom=create_coordinate(90.5, 20.0))
121 with api_session(token3) as api:
122 res = api.GetUser(api_pb2.GetUserReq(user=user3.username))
123 assert res.lat == 40.0
124 assert res.lng == 179.5
126 with api_session(token4) as api:
127 res = api.GetUser(api_pb2.GetUserReq(user=user4.username))
128 assert res.lat == 40.0
129 assert res.lng == 20.0
131 # PostGIS does not wrap longitude for latitude overflow
132 with api_session(token5) as api:
133 res = api.GetUser(api_pb2.GetUserReq(user=user5.username))
134 assert res.lat == 89.5
135 assert res.lng == 20.0
137 with real_jail_session(token1) as jail:
138 res = jail.JailInfo(empty_pb2.Empty())
139 assert res.jailed
140 assert res.needs_to_update_location
142 res = jail.SetLocation(
143 jail_pb2.SetLocationReq(
144 city="New York City",
145 lat=40.7812,
146 lng=-73.9647,
147 radius=250,
148 )
149 )
151 assert not res.jailed
152 assert not res.needs_to_update_location
154 res = jail.JailInfo(empty_pb2.Empty())
155 assert not res.jailed
156 assert not res.needs_to_update_location
158 with api_session(token2) as api:
159 res = api.GetUser(api_pb2.GetUserReq(user=user1.username))
160 assert res.city == "New York City"
161 assert res.lat == 40.7812
162 assert res.lng == -73.9647
163 assert res.radius == 250
166def test_get_user(db):
167 user1, token1 = generate_user()
168 user2, token2 = generate_user()
170 with api_session(token1) as api:
171 res = api.GetUser(api_pb2.GetUserReq(user=user2.username))
172 assert res.user_id == user2.id
173 assert res.username == user2.username
174 assert res.name == user2.name
176 with api_session(token1) as api:
177 res = api.GetUser(api_pb2.GetUserReq(user=str(user2.id)))
178 assert res.user_id == user2.id
179 assert res.username == user2.username
180 assert res.name == user2.name
183@pytest.mark.parametrize("flag", ["deleted_at", "banned_at"])
184def test_user_model_to_pb_ghost_user(db, flag):
185 user1, token1 = generate_user()
186 user2, _ = generate_user()
188 with session_scope() as session:
189 session.execute(update(User).where(User.id == user2.id).values(**{flag: func.now()}))
191 refresh_materialized_views_rapid(empty_pb2.Empty())
193 with api_session(token1) as api:
194 user_pb = api.GetUser(api_pb2.GetUserReq(user=user2.username))
196 assert user_pb.user_id == user2.id
197 assert user_pb.is_ghost
198 assert user_pb.username == "ghost"
199 assert user_pb.name == "Deactivated Account"
200 assert (
201 user_pb.about_me
202 == "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."
203 )
205 assert user_pb.lat == 0
206 assert user_pb.lng == 0
207 assert user_pb.radius == 0
208 assert user_pb.verification == 0.0
209 assert user_pb.community_standing == 0.0
210 assert user_pb.num_references == 0
211 assert user_pb.age == 0
212 assert user_pb.hosting_status == 0
213 assert user_pb.meetup_status == 0
214 assert user_pb.city == ""
215 assert user_pb.hometown == ""
216 assert user_pb.timezone == ""
217 assert user_pb.gender == ""
218 assert user_pb.pronouns == ""
219 assert user_pb.occupation == ""
220 assert user_pb.education == ""
221 assert user_pb.things_i_like == ""
222 assert user_pb.about_place == ""
223 assert user_pb.additional_information == ""
224 assert list(user_pb.language_abilities) == []
225 assert list(user_pb.regions_visited) == []
226 assert list(user_pb.regions_lived) == []
227 assert list(user_pb.badges) == []
228 assert user_pb.friends == api_pb2.User.FriendshipStatus.NOT_FRIENDS
229 assert user_pb.avatar_url == ""
230 assert user_pb.avatar_thumbnail_url == ""
231 assert not user_pb.has_strong_verification
233 with api_session(token1) as api:
234 lite_user_pb = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user2.username))
236 assert lite_user_pb.user_id == user2.id
237 assert lite_user_pb.is_ghost
238 assert lite_user_pb.username == "ghost"
239 assert lite_user_pb.name == "Deactivated Account"
240 assert lite_user_pb.city == ""
241 assert lite_user_pb.age == 0
242 assert lite_user_pb.avatar_url == ""
243 assert lite_user_pb.avatar_thumbnail_url == ""
244 assert lite_user_pb.lat == 0
245 assert lite_user_pb.lng == 0
246 assert lite_user_pb.radius == 0
247 assert not lite_user_pb.has_strong_verification
250def test_user_model_to_pb_ghost_user_blocked(db):
251 user1, token1 = generate_user()
252 user2, _ = generate_user()
254 with blocking_session(token1) as user_blocks:
255 user_blocks.BlockUser(blocking_pb2.BlockUserReq(username=user2.username))
257 refresh_materialized_views_rapid(empty_pb2.Empty())
259 with api_session(token1) as api:
260 user_pb = api.GetUser(api_pb2.GetUserReq(user=user2.username))
262 assert user_pb.user_id == user2.id
263 assert user_pb.is_ghost
264 assert user_pb.username == "ghost"
265 assert user_pb.name == "Deactivated Account"
266 assert (
267 user_pb.about_me
268 == "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."
269 )
271 assert user_pb.lat == 0
272 assert user_pb.lng == 0
273 assert user_pb.radius == 0
274 assert user_pb.verification == 0.0
275 assert user_pb.community_standing == 0.0
276 assert user_pb.num_references == 0
277 assert user_pb.age == 0
278 assert user_pb.hosting_status == 0
279 assert user_pb.meetup_status == 0
280 assert user_pb.city == ""
281 assert user_pb.hometown == ""
282 assert user_pb.timezone == ""
283 assert user_pb.gender == ""
284 assert user_pb.pronouns == ""
285 assert user_pb.occupation == ""
286 assert user_pb.education == ""
287 assert user_pb.things_i_like == ""
288 assert user_pb.about_place == ""
289 assert user_pb.additional_information == ""
290 assert list(user_pb.language_abilities) == []
291 assert list(user_pb.regions_visited) == []
292 assert list(user_pb.regions_lived) == []
293 assert list(user_pb.badges) == []
294 assert user_pb.friends == api_pb2.User.FriendshipStatus.NOT_FRIENDS
295 assert user_pb.avatar_url == ""
296 assert user_pb.avatar_thumbnail_url == ""
297 assert not user_pb.has_strong_verification
299 with api_session(token1) as api:
300 lite_user_pb = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user2.username))
302 assert lite_user_pb.user_id == user2.id
303 assert lite_user_pb.is_ghost
304 assert lite_user_pb.username == "ghost"
305 assert lite_user_pb.name == "Deactivated Account"
306 assert lite_user_pb.city == ""
307 assert lite_user_pb.age == 0
308 assert lite_user_pb.avatar_url == ""
309 assert lite_user_pb.avatar_thumbnail_url == ""
310 assert lite_user_pb.lat == 0
311 assert lite_user_pb.lng == 0
312 assert lite_user_pb.radius == 0
313 assert not lite_user_pb.has_strong_verification
316@pytest.mark.parametrize(
317 "flag,state",
318 [
319 ("banned_at", NonvisibleUserState.banned),
320 ("shadowed_at", NonvisibleUserState.shadowed),
321 ("deleted_at", NonvisibleUserState.deleted),
322 ],
323)
324def test_viewing_nonvisible_user_profile_is_logged(db, flag, state):
325 user1, token1 = generate_user()
326 user2, _ = generate_user()
328 with session_scope() as session:
329 session.execute(update(User).where(User.id == user2.id).values(**{flag: func.now()}))
331 refresh_materialized_views_rapid(empty_pb2.Empty())
333 with api_session(token1) as api:
334 user_pb = api.GetUser(api_pb2.GetUserReq(user=user2.username))
335 assert user_pb.is_ghost
337 with session_scope() as session:
338 access = session.execute(select(NonvisibleUserAccess)).scalar_one()
339 assert access.access_type == NonvisibleUserAccessType.ghost_served
340 assert access.target_state == state
341 assert access.target_user_id == user2.id
342 assert access.actor_user_id == user1.id
343 assert access.ip_address is None
344 assert access.user_agent is None
345 assert access.sofa is None
348def test_viewing_blocked_user_profile_is_not_logged(db):
349 user1, token1 = generate_user()
350 user2, _ = generate_user()
352 with blocking_session(token1) as user_blocks:
353 user_blocks.BlockUser(blocking_pb2.BlockUserReq(username=user2.username))
355 refresh_materialized_views_rapid(empty_pb2.Empty())
357 with api_session(token1) as api:
358 user_pb = api.GetUser(api_pb2.GetUserReq(user=user2.username))
359 assert user_pb.is_ghost
361 with session_scope() as session:
362 assert session.execute(select(func.count()).select_from(NonvisibleUserAccess)).scalar_one() == 0
365@pytest.mark.parametrize("flag", ["deleted_at", "banned_at"])
366def test_admin_viewing_ghost_users_sees_full_profile(db, flag):
367 admin, token_admin = generate_user(is_superuser=True)
368 user, _ = generate_user()
370 with session_scope() as session:
371 session.execute(update(User).where(User.id == user.id).values(**{flag: func.now()}))
373 with admin_session(token_admin) as api:
374 user_pb = api.GetUser(admin_pb2.GetUserReq(user=user.username))
376 assert user_pb.user_id == user.id
377 assert user_pb.username == user.username
378 assert user_pb.name == user.name
379 assert user_pb.city == user.city
380 assert user_pb.name != "Deactivated Account"
381 assert user_pb.username != "ghost"
382 assert user_pb.hosting_status in (
383 api_pb2.HOSTING_STATUS_UNKNOWN,
384 api_pb2.HOSTING_STATUS_CAN_HOST,
385 api_pb2.HOSTING_STATUS_MAYBE,
386 api_pb2.HOSTING_STATUS_CANT_HOST,
387 )
390def test_lite_coords(db):
391 # make them need to update location
392 user1, token1 = generate_user(geom=create_coordinate(0, 0), geom_radius=0, needs_to_update_location=True)
393 user2, token2 = generate_user()
395 refresh_materialized_views_rapid(empty_pb2.Empty())
397 with api_session(token2) as api:
398 res = api.Ping(api_pb2.PingReq())
399 assert res.user.city == user2.city
400 lat, lng = user2.coordinates
401 assert res.user.lat == lat
402 assert res.user.lng == lng
403 assert res.user.radius == user2.geom_radius
405 with api_session(token2) as api:
406 res = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user1.username))
407 assert res.city == user1.city
408 assert res.lat == 0.0
409 assert res.lng == 0.0
410 assert res.radius == 0.0
412 # Check coordinate wrapping
413 user3, token3 = generate_user(geom=create_coordinate(40.0, -180.5))
414 user4, token4 = generate_user(geom=create_coordinate(40.0, 20.0))
415 user5, token5 = generate_user(geom=create_coordinate(90.5, 20.0))
417 refresh_materialized_views_rapid(empty_pb2.Empty())
419 with api_session(token3) as api:
420 res = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user3.username))
421 assert res.lat == 40.0
422 assert res.lng == 179.5
424 with api_session(token4) as api:
425 res = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user4.username))
426 assert res.lat == 40.0
427 assert res.lng == 20.0
429 # PostGIS does not wrap longitude for latitude overflow
430 with api_session(token5) as api:
431 res = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user5.username))
432 assert res.lat == 89.5
433 assert res.lng == 20.0
435 with real_jail_session(token1) as jail:
436 res = jail.JailInfo(empty_pb2.Empty())
437 assert res.jailed
438 assert res.needs_to_update_location
440 res = jail.SetLocation(
441 jail_pb2.SetLocationReq(
442 city="New York City",
443 lat=40.7812,
444 lng=-73.9647,
445 radius=250,
446 )
447 )
449 assert not res.jailed
450 assert not res.needs_to_update_location
452 res = jail.JailInfo(empty_pb2.Empty())
453 assert not res.jailed
454 assert not res.needs_to_update_location
456 refresh_materialized_views_rapid(empty_pb2.Empty())
458 with api_session(token2) as api:
459 res = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user1.username))
460 assert res.city == "New York City"
461 assert res.lat == 40.7812
462 assert res.lng == -73.9647
463 assert res.radius == 250
466def test_lite_get_user(db):
467 user1, token1 = generate_user()
468 user2, token2 = generate_user()
470 refresh_materialized_views_rapid(empty_pb2.Empty())
472 with api_session(token1) as api:
473 res = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user2.username))
474 assert res.user_id == user2.id
475 assert res.username == user2.username
476 assert res.name == user2.name
478 with api_session(token1) as api:
479 res = api.GetLiteUser(api_pb2.GetLiteUserReq(user=str(user2.id)))
480 assert res.user_id == user2.id
481 assert res.username == user2.username
482 assert res.name == user2.name
485def test_GetLiteUsers(db):
486 user1, token1 = generate_user()
487 user2, _ = generate_user()
488 user3, _ = generate_user()
489 user4, _ = generate_user()
490 user5, _ = generate_user()
491 user6, _ = generate_user()
493 make_user_block(user4, user1)
495 refresh_materialized_views_rapid(empty_pb2.Empty())
497 with api_session(token1) as api:
498 res = api.GetLiteUsers(
499 api_pb2.GetLiteUsersReq(
500 users=[
501 user1.username,
502 str(user1.id),
503 "nonexistent",
504 str(user2.id),
505 "9994",
506 user6.username,
507 str(user5.id),
508 "notreal",
509 user4.username,
510 ]
511 )
512 )
514 assert len(res.responses) == 9
515 assert res.responses[0].query == user1.username
516 assert res.responses[0].user.user_id == user1.id
518 assert res.responses[1].query == str(user1.id)
519 assert res.responses[1].user.user_id == user1.id
521 assert res.responses[2].query == "nonexistent"
522 assert res.responses[2].not_found
524 assert res.responses[3].query == str(user2.id)
525 assert res.responses[3].user.user_id == user2.id
527 assert res.responses[4].query == "9994"
528 assert res.responses[4].not_found
530 assert res.responses[5].query == user6.username
531 assert res.responses[5].user.user_id == user6.id
533 assert res.responses[6].query == str(user5.id)
534 assert res.responses[6].user.user_id == user5.id
536 assert res.responses[7].query == "notreal"
537 assert res.responses[7].not_found
539 # blocked - should return ghost profile
540 assert res.responses[8].query == user4.username
541 assert not res.responses[8].not_found
542 assert res.responses[8].user.user_id == user4.id
543 assert res.responses[8].user.is_ghost
544 assert res.responses[8].user.username == "ghost"
545 assert res.responses[8].user.name == "Deactivated Account"
547 with api_session(token1) as api:
548 with pytest.raises(grpc.RpcError) as e:
549 api.GetLiteUsers(api_pb2.GetLiteUsersReq(users=201 * [user1.username]))
550 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
551 assert e.value.details() == "You can't request that many users at a time."
554def test_update_profile(db):
555 user, token = generate_user()
557 with api_session(token) as api:
558 with pytest.raises(grpc.RpcError) as e:
559 api.UpdateProfile(api_pb2.UpdateProfileReq(name=wrappers_pb2.StringValue(value=" ")))
560 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
561 assert e.value.details() == "Name not supported."
563 with pytest.raises(grpc.RpcError) as e:
564 api.UpdateProfile(
565 api_pb2.UpdateProfileReq(lat=wrappers_pb2.DoubleValue(value=0), lng=wrappers_pb2.DoubleValue(value=0))
566 )
567 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
568 assert e.value.details() == "Invalid coordinate."
570 with pytest.raises(grpc.RpcError) as e:
571 api.UpdateProfile(
572 api_pb2.UpdateProfileReq(regions_visited=api_pb2.RepeatedStringValue(value=["United States"]))
573 )
574 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
575 assert e.value.details() == "Invalid region."
577 with pytest.raises(grpc.RpcError) as e:
578 api.UpdateProfile(
579 api_pb2.UpdateProfileReq(regions_lived=api_pb2.RepeatedStringValue(value=["United Kingdom"]))
580 )
581 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
582 assert e.value.details() == "Invalid region."
584 api.UpdateProfile(
585 api_pb2.UpdateProfileReq(
586 name=wrappers_pb2.StringValue(value="New name"),
587 city=wrappers_pb2.StringValue(value="Timbuktu"),
588 hometown=api_pb2.NullableStringValue(value="Walla Walla"),
589 lat=wrappers_pb2.DoubleValue(value=0.01),
590 lng=wrappers_pb2.DoubleValue(value=-2),
591 radius=wrappers_pb2.DoubleValue(value=321),
592 pronouns=api_pb2.NullableStringValue(value="Ro, Robo, Robots"),
593 occupation=api_pb2.NullableStringValue(value="Testing"),
594 education=api_pb2.NullableStringValue(value="Couchers U"),
595 about_me=api_pb2.NullableStringValue(value="I rule"),
596 things_i_like=api_pb2.NullableStringValue(value="Couchers"),
597 about_place=api_pb2.NullableStringValue(value="My place"),
598 hosting_status=api_pb2.HOSTING_STATUS_CAN_HOST,
599 meetup_status=api_pb2.MEETUP_STATUS_WANTS_TO_MEETUP,
600 language_abilities=api_pb2.RepeatedLanguageAbilityValue(
601 value=[
602 api_pb2.LanguageAbility(
603 code="eng",
604 fluency=api_pb2.LanguageAbility.Fluency.FLUENCY_FLUENT,
605 )
606 ],
607 ),
608 regions_visited=api_pb2.RepeatedStringValue(value=["CXR", "FIN"]),
609 regions_lived=api_pb2.RepeatedStringValue(value=["USA", "EST"]),
610 additional_information=api_pb2.NullableStringValue(value="I <3 Couchers"),
611 )
612 )
614 user_details = api.GetUser(api_pb2.GetUserReq(user=user.username))
615 assert user_details.name == "New name"
616 assert user_details.city == "Timbuktu"
617 assert user_details.hometown == "Walla Walla"
618 assert user_details.pronouns == "Ro, Robo, Robots"
619 assert user_details.education == "Couchers U"
620 assert user_details.things_i_like == "Couchers"
621 assert user_details.lat == 0.01
622 assert user_details.lng == -2
623 assert user_details.radius == 321
624 assert user_details.occupation == "Testing"
625 assert user_details.about_me == "I rule"
626 assert user_details.about_place == "My place"
627 assert user_details.hosting_status == api_pb2.HOSTING_STATUS_CAN_HOST
628 assert user_details.meetup_status == api_pb2.MEETUP_STATUS_WANTS_TO_MEETUP
629 assert user_details.language_abilities[0].code == "eng"
630 assert user_details.language_abilities[0].fluency == api_pb2.LanguageAbility.Fluency.FLUENCY_FLUENT
631 assert user_details.additional_information == "I <3 Couchers"
632 assert user_details.regions_visited == ["CXR", "FIN"]
633 assert user_details.regions_lived == ["EST", "USA"]
635 # Test unset values
636 api.UpdateProfile(
637 api_pb2.UpdateProfileReq(
638 hometown=api_pb2.NullableStringValue(is_null=True),
639 radius=wrappers_pb2.DoubleValue(value=0),
640 pronouns=api_pb2.NullableStringValue(is_null=True),
641 occupation=api_pb2.NullableStringValue(is_null=True),
642 education=api_pb2.NullableStringValue(is_null=True),
643 about_me=api_pb2.NullableStringValue(is_null=True),
644 things_i_like=api_pb2.NullableStringValue(is_null=True),
645 about_place=api_pb2.NullableStringValue(is_null=True),
646 hosting_status=api_pb2.HOSTING_STATUS_CAN_HOST,
647 meetup_status=api_pb2.MEETUP_STATUS_WANTS_TO_MEETUP,
648 language_abilities=api_pb2.RepeatedLanguageAbilityValue(value=[]),
649 regions_visited=api_pb2.RepeatedStringValue(value=[]),
650 regions_lived=api_pb2.RepeatedStringValue(value=[]),
651 additional_information=api_pb2.NullableStringValue(is_null=True),
652 )
653 )
655 user_details = api.GetUser(api_pb2.GetUserReq(user=user.username))
656 assert not user_details.hometown
657 assert not user_details.radius
658 assert not user_details.pronouns
659 assert not user_details.occupation
660 assert not user_details.education
661 assert not user_details.about_me
662 assert not user_details.things_i_like
663 assert not user_details.about_place
664 assert user_details.hosting_status == api_pb2.HOSTING_STATUS_CAN_HOST
665 assert user_details.meetup_status == api_pb2.MEETUP_STATUS_WANTS_TO_MEETUP
666 assert not user_details.language_abilities
667 assert not user_details.regions_visited
668 assert not user_details.regions_lived
669 assert not user_details.additional_information
672def test_update_profile_do_not_email(db):
673 user, token = generate_user()
675 with notifications_session(token) as notifications:
676 notifications.SetNotificationSettings(notifications_pb2.SetNotificationSettingsReq(enable_do_not_email=True))
678 with api_session(token) as api:
679 with pytest.raises(grpc.RpcError) as e:
680 api.UpdateProfile(api_pb2.UpdateProfileReq(hosting_status=api_pb2.HOSTING_STATUS_CAN_HOST))
681 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
682 assert e.value.details() == "You cannot enable hosting while you have emails turned off in your settings."
684 with pytest.raises(grpc.RpcError) as e:
685 api.UpdateProfile(api_pb2.UpdateProfileReq(meetup_status=api_pb2.MEETUP_STATUS_OPEN_TO_MEETUP))
686 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
687 assert e.value.details() == "You cannot enable meeting up while you have emails turned off in your settings."
690def test_language_abilities(db):
691 user, token = generate_user(
692 language_abilities=[
693 ("fin", LanguageFluency.fluent),
694 ("fra", LanguageFluency.beginner),
695 ],
696 )
698 with api_session(token) as api:
699 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
700 assert len(res.language_abilities) == 2
702 # can't add non-existent languages
703 with pytest.raises(grpc.RpcError) as err:
704 api.UpdateProfile(
705 api_pb2.UpdateProfileReq(
706 language_abilities=api_pb2.RepeatedLanguageAbilityValue(
707 value=[
708 api_pb2.LanguageAbility(
709 code="QQQ",
710 fluency=api_pb2.LanguageAbility.Fluency.FLUENCY_FLUENT,
711 )
712 ],
713 ),
714 )
715 )
716 assert err.value.code() == grpc.StatusCode.INVALID_ARGUMENT
717 assert err.value.details() == "Invalid language."
719 # can't have multiple languages of the same type
720 with pytest.raises(Exception) as err2:
721 api.UpdateProfile(
722 api_pb2.UpdateProfileReq(
723 language_abilities=api_pb2.RepeatedLanguageAbilityValue(
724 value=[
725 api_pb2.LanguageAbility(
726 code="eng",
727 fluency=api_pb2.LanguageAbility.Fluency.FLUENCY_FLUENT,
728 ),
729 api_pb2.LanguageAbility(
730 code="eng",
731 fluency=api_pb2.LanguageAbility.Fluency.FLUENCY_FLUENT,
732 ),
733 ],
734 ),
735 )
736 )
737 assert "violates unique constraint" in str(err2.value)
739 # nothing changed
740 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
741 assert len(res.language_abilities) == 2
743 # now actually add a value
744 api.UpdateProfile(
745 api_pb2.UpdateProfileReq(
746 language_abilities=api_pb2.RepeatedLanguageAbilityValue(
747 value=[
748 api_pb2.LanguageAbility(
749 code="eng",
750 fluency=api_pb2.LanguageAbility.Fluency.FLUENCY_FLUENT,
751 )
752 ],
753 ),
754 )
755 )
757 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
758 assert len(res.language_abilities) == 1
759 assert res.language_abilities[0].code == "eng"
760 assert res.language_abilities[0].fluency == api_pb2.LanguageAbility.Fluency.FLUENCY_FLUENT
762 # change the value to a new one
763 api.UpdateProfile(
764 api_pb2.UpdateProfileReq(
765 language_abilities=api_pb2.RepeatedLanguageAbilityValue(
766 value=[
767 api_pb2.LanguageAbility(
768 code="fin",
769 fluency=api_pb2.LanguageAbility.Fluency.FLUENCY_BEGINNER,
770 )
771 ],
772 ),
773 )
774 )
776 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
777 assert len(res.language_abilities) == 1
778 assert res.language_abilities[0].code == "fin"
779 assert res.language_abilities[0].fluency == api_pb2.LanguageAbility.Fluency.FLUENCY_BEGINNER
781 # should be able to set to same value still
782 api.UpdateProfile(
783 api_pb2.UpdateProfileReq(
784 language_abilities=api_pb2.RepeatedLanguageAbilityValue(
785 value=[
786 api_pb2.LanguageAbility(
787 code="fin",
788 fluency=api_pb2.LanguageAbility.Fluency.FLUENCY_BEGINNER,
789 )
790 ],
791 ),
792 )
793 )
795 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
796 assert len(res.language_abilities) == 1
797 assert res.language_abilities[0].code == "fin"
798 assert res.language_abilities[0].fluency == api_pb2.LanguageAbility.Fluency.FLUENCY_BEGINNER
800 # don't change it
801 api.UpdateProfile(api_pb2.UpdateProfileReq())
803 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
804 assert len(res.language_abilities) == 1
805 assert res.language_abilities[0].code == "fin"
806 assert res.language_abilities[0].fluency == api_pb2.LanguageAbility.Fluency.FLUENCY_BEGINNER
808 # remove value
809 api.UpdateProfile(
810 api_pb2.UpdateProfileReq(
811 language_abilities=api_pb2.RepeatedLanguageAbilityValue(
812 value=[],
813 ),
814 )
815 )
817 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
818 assert len(res.language_abilities) == 0
821def test_pending_friend_request_count(db, moderator):
822 user1, token1 = generate_user()
823 user2, token2 = generate_user()
824 user3, token3 = generate_user()
826 with api_session(token2) as api:
827 res = api.Ping(api_pb2.PingReq())
828 assert res.pending_friend_request_count == 0
830 with api_session(token1) as api:
831 res = api.Ping(api_pb2.PingReq())
832 assert res.pending_friend_request_count == 0
833 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
834 # Sender can still see their own sent requests (even while SHADOWED)
835 res = api.Ping(api_pb2.PingReq())
836 assert res.pending_friend_request_count == 0
838 # Get friend request ID from sender's view (author can see SHADOWED)
839 with api_session(token1) as api:
840 res = api.ListFriendRequests(empty_pb2.Empty())
841 assert len(res.sent) == 1
842 fr_id = res.sent[0].friend_request_id
844 # Recipient cannot see SHADOWED friend requests before mod approval
845 with api_session(token2) as api:
846 res = api.Ping(api_pb2.PingReq())
847 assert res.pending_friend_request_count == 0
849 # Moderator approves the friend request
850 moderator.approve_friend_request(fr_id)
852 # Now recipient can see the approved friend request
853 with api_session(token2) as api:
854 res = api.Ping(api_pb2.PingReq())
855 assert res.pending_friend_request_count == 1
857 with api_session(token2) as api:
858 # check it's there
859 res = api.ListFriendRequests(empty_pb2.Empty())
860 assert len(res.sent) == 0
861 assert len(res.received) == 1
863 assert res.received[0].state == api_pb2.FriendRequest.FriendRequestStatus.PENDING
864 assert res.received[0].user_id == user1.id
866 fr_id = res.received[0].friend_request_id
868 # accept it
869 api.RespondFriendRequest(api_pb2.RespondFriendRequestReq(friend_request_id=fr_id, accept=True))
871 res = api.Ping(api_pb2.PingReq())
872 assert res.pending_friend_request_count == 0
875def test_friend_request_flow(db, email_collector: EmailCollector, push_collector: PushCollector, moderator):
876 user1, token1 = generate_user(complete_profile=True)
877 user2, token2 = generate_user(complete_profile=True)
878 user3, token3 = generate_user()
880 # send a friend request from user1 to user2
881 with api_session(token1) as api:
882 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
884 with session_scope() as session:
885 friend_request = session.execute(
886 select(FriendRelationship).where(
887 FriendRelationship.from_user_id == user1.id, FriendRelationship.to_user_id == user2.id
888 )
889 ).scalar_one()
890 friend_request_id = friend_request.id
892 # Notification is deferred while content is SHADOWED
893 # No push notification sent yet
894 assert push_collector.count_for_user(user2.id) == 0
896 with api_session(token1) as api:
897 # Sender can see their own sent requests (even while SHADOWED)
898 res = api.ListFriendRequests(empty_pb2.Empty())
899 assert len(res.sent) == 1
900 assert len(res.received) == 0
902 assert res.sent[0].state == api_pb2.FriendRequest.FriendRequestStatus.PENDING
903 assert res.sent[0].user_id == user2.id
904 assert res.sent[0].friend_request_id == friend_request_id
906 # Recipient cannot see SHADOWED friend requests
907 with api_session(token2) as api:
908 res = api.ListFriendRequests(empty_pb2.Empty())
909 assert len(res.sent) == 0
910 assert len(res.received) == 0
912 # Moderator approves the friend request - this triggers the notification
913 moderator.approve_friend_request(friend_request_id)
915 push = push_collector.pop_for_user(user2.id, last=True)
916 assert push.content.title == f"Friend request from {user1.name}"
917 assert push.content.body == f"{user1.name} wants to be your friend."
918 assert push.content.action_url == f"http://localhost:3000/connections/friends/?from={user1.id}"
920 email = email_collector.pop_for_recipient(user2.email, last=True)
921 assert email.recipient == user2.email
922 assert email.subject == f"[TEST] {user1.name} wants to be your friend"
923 assert user2.name in email.plain
924 assert user2.name in email.html
925 assert user1.name in email.plain
926 assert user1.name in email.html
927 assert "http://localhost:5001/img/thumbnail/" not in email.plain
928 assert "http://localhost:5001/img/thumbnail/" in email.html
929 assert "http://localhost:3000/connections/friends/" in email.plain
930 assert "http://localhost:3000/connections/friends/" in email.html
932 # Now recipient can see the approved friend request
933 with api_session(token2) as api:
934 # check it's there
935 res = api.ListFriendRequests(empty_pb2.Empty())
936 assert len(res.sent) == 0
937 assert len(res.received) == 1
939 assert res.received[0].state == api_pb2.FriendRequest.FriendRequestStatus.PENDING
940 assert res.received[0].user_id == user1.id
942 fr_id = res.received[0].friend_request_id
944 # accept it
945 api.RespondFriendRequest(api_pb2.RespondFriendRequestReq(friend_request_id=fr_id, accept=True))
947 # check it's gone
948 res = api.ListFriendRequests(empty_pb2.Empty())
949 assert len(res.sent) == 0
950 assert len(res.received) == 0
952 # check we're friends now
953 res = api.ListFriends(empty_pb2.Empty())
954 assert len(res.user_ids) == 1
955 assert res.user_ids[0] == user1.id
957 # user2 got one push (from the friend request creation)
958 # user1 should now have one push (from the friend request acceptance)
959 push = push_collector.pop_for_user(user1.id, last=True)
960 assert push.content.title == f"{user2.name} accepted your friend request"
961 assert push.content.body == f"You are now friends with {user2.name}."
963 email = email_collector.pop_for_recipient(user1.email, last=True)
964 assert email.recipient == user1.email
965 assert email.subject == f"[TEST] {user2.name} accepted your friend request"
966 assert user1.name in email.plain
967 assert user1.name in email.html
968 assert user2.name in email.plain
969 assert user2.name in email.html
970 assert "http://localhost:5001/img/thumbnail/" not in email.plain
971 assert "http://localhost:5001/img/thumbnail/" in email.html
972 assert f"http://localhost:3000/user/{user2.username}" in email.plain
973 assert f"http://localhost:3000/user/{user2.username}" in email.html
975 with api_session(token1) as api:
976 # check it's gone
977 res = api.ListFriendRequests(empty_pb2.Empty())
978 assert len(res.sent) == 0
979 assert len(res.received) == 0
981 # check we're friends now
982 res = api.ListFriends(empty_pb2.Empty())
983 assert len(res.user_ids) == 1
984 assert res.user_ids[0] == user2.id
986 with api_session(token1) as api:
987 # we can't unfriend if we aren't friends
988 with pytest.raises(grpc.RpcError) as err:
989 api.RemoveFriend(api_pb2.RemoveFriendReq(user_id=user3.id))
990 assert err.value.code() == grpc.StatusCode.FAILED_PRECONDITION
991 assert err.value.details() == "You aren't friends with that user!"
993 # we can unfriend
994 res = api.RemoveFriend(api_pb2.RemoveFriendReq(user_id=user2.id))
996 res = api.ListFriends(empty_pb2.Empty())
997 assert len(res.user_ids) == 0
1000def test_RemoveFriend_regression(db, push_collector: PushCollector, moderator):
1001 user1, token1 = generate_user(complete_profile=True)
1002 user2, token2 = generate_user(complete_profile=True)
1003 user3, token3 = generate_user()
1004 user4, token4 = generate_user()
1005 user5, token5 = generate_user()
1006 user6, token6 = generate_user()
1008 # Send friend requests
1009 with api_session(token4) as api:
1010 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user1.id))
1011 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
1013 with api_session(token5) as api:
1014 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user1.id))
1016 with api_session(token1) as api:
1017 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
1018 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user3.id))
1020 # Approve all friend requests via moderation
1021 with session_scope() as session:
1022 friend_requests = session.execute(select(FriendRelationship)).scalars().all()
1023 for fr in friend_requests:
1024 moderator.approve_friend_request(fr.id)
1026 # Now recipients can respond
1027 with api_session(token1) as api:
1028 api.RespondFriendRequest(
1029 api_pb2.RespondFriendRequestReq(
1030 friend_request_id=api.ListFriendRequests(empty_pb2.Empty()).received[0].friend_request_id, accept=True
1031 )
1032 )
1034 with api_session(token2) as api:
1035 for fr in api.ListFriendRequests(empty_pb2.Empty()).received:
1036 api.RespondFriendRequest(
1037 api_pb2.RespondFriendRequestReq(friend_request_id=fr.friend_request_id, accept=True)
1038 )
1040 with api_session(token1) as api:
1041 res = api.ListFriends(empty_pb2.Empty())
1042 assert sorted(res.user_ids) == sorted([user2.id, user4.id])
1044 api.RemoveFriend(api_pb2.RemoveFriendReq(user_id=user2.id))
1046 res = api.ListFriends(empty_pb2.Empty())
1047 assert sorted(res.user_ids) == [user4.id]
1049 api.RemoveFriend(api_pb2.RemoveFriendReq(user_id=user4.id))
1051 res = api.ListFriends(empty_pb2.Empty())
1052 assert not res.user_ids
1055def test_cant_friend_request_twice(db):
1056 user1, token1 = generate_user()
1057 user2, token2 = generate_user()
1059 # send friend request from user1 to user2
1060 with api_session(token1) as api:
1061 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
1063 with pytest.raises(grpc.RpcError) as e:
1064 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
1065 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
1066 assert e.value.details() == "You are already friends with or have sent a friend request to that user."
1069def test_cant_friend_request_pending(db):
1070 user1, token1 = generate_user()
1071 user2, token2 = generate_user()
1073 # send friend request from user1 to user2
1074 with api_session(token1) as api:
1075 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
1077 with api_session(token2) as api:
1078 with pytest.raises(grpc.RpcError) as e:
1079 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user1.id))
1080 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
1081 assert e.value.details() == "You are already friends with or have sent a friend request to that user."
1084def test_cant_friend_request_already_friends(db):
1085 user1, token1 = generate_user()
1086 user2, token2 = generate_user()
1087 make_friends(user1, user2)
1089 with api_session(token1) as api:
1090 with pytest.raises(grpc.RpcError) as e:
1091 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
1092 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
1093 assert e.value.details() == "You are already friends with or have sent a friend request to that user."
1095 with api_session(token2) as api:
1096 with pytest.raises(grpc.RpcError) as e:
1097 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user1.id))
1098 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
1099 assert e.value.details() == "You are already friends with or have sent a friend request to that user."
1102def test_cant_friend_request_incomplete_profile(db):
1103 user1, token1 = generate_user(complete_profile=False)
1104 user2, token2 = generate_user(complete_profile=True)
1106 with api_session(token1) as api:
1107 with pytest.raises(grpc.RpcError) as e:
1108 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
1109 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
1110 assert e.value.details() == "You have to complete your profile before you can send a friend request."
1112 # the other direction should still work
1113 with api_session(token2) as api:
1114 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user1.id))
1117def test_excessive_friend_requests_are_reported(db, low_rate_limits, email_collector: EmailCollector):
1118 """Test that excessive friend requests are first reported in a warning email and finally lead blocking of further requests."""
1119 user, token = generate_user()
1120 rate_limit_definition = RATE_LIMIT_DEFINITIONS[RateLimitAction.friend_request]
1121 with api_session(token) as api:
1122 # Test warning email
1123 for _ in range(rate_limit_definition.warning_limit):
1124 friend_user, _ = generate_user()
1125 _ = api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=friend_user.id))
1127 assert email_collector.count_for_reports() == 0
1128 friend_user, _ = generate_user()
1129 _ = api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=friend_user.id))
1131 email = email_collector.pop_for_reports(last=True)
1132 assert email.plain.startswith(
1133 f"User {user.username} has sent {rate_limit_definition.warning_limit} friend requests in the past {RATE_LIMIT_HOURS} hours."
1134 )
1136 # Test ban after exceeding FRIEND_REQUEST_HARD_LIMIT
1137 for _ in range(rate_limit_definition.hard_limit - rate_limit_definition.warning_limit - 1):
1138 friend_user, _ = generate_user()
1139 _ = api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=friend_user.id))
1141 assert email_collector.count_for_reports() == 0
1142 friend_user, _ = generate_user()
1143 with pytest.raises(grpc.RpcError) as exc_info:
1144 _ = api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=friend_user.id))
1145 assert exc_info.value.code() == grpc.StatusCode.RESOURCE_EXHAUSTED
1146 assert (
1147 exc_info.value.details()
1148 == "You have sent a lot of friend requests in the past 24 hours. To avoid spam, you can't send any more for now."
1149 )
1151 email = email_collector.pop_for_reports(last=True)
1152 assert email.plain.startswith(
1153 f"User {user.username} has sent {rate_limit_definition.hard_limit} friend requests in the past {RATE_LIMIT_HOURS} hours."
1154 )
1155 assert "The user has been blocked from sending further friend requests for now." in email.plain
1157 with session_scope() as session:
1158 assert session.execute(
1159 select(RateLimitViolation.is_hard_limit)
1160 .where(RateLimitViolation.user_id == user.id)
1161 .order_by(RateLimitViolation.id)
1162 ).scalars().all() == [False, True]
1164 # still blocked once the hard violation is on record, without reporting again
1165 friend_user, _ = generate_user()
1166 with pytest.raises(grpc.RpcError) as exc_info:
1167 _ = api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=friend_user.id))
1168 assert exc_info.value.code() == grpc.StatusCode.RESOURCE_EXHAUSTED
1169 assert email_collector.count_for_reports() == 0
1172def test_ListFriends(db, moderator):
1173 user1, token1 = generate_user()
1174 user2, token2 = generate_user()
1175 user3, token3 = generate_user()
1177 # send friend request from user1 to user2 and user3
1178 with api_session(token1) as api:
1179 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
1180 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user3.id))
1181 # sender can see their sent requests (they are the author)
1182 res = api.ListFriendRequests(empty_pb2.Empty())
1183 assert len(res.sent) == 2
1184 user1_to_user2_id = [req for req in res.sent if req.user_id == user2.id][0].friend_request_id
1185 user1_to_user3_id = [req for req in res.sent if req.user_id == user3.id][0].friend_request_id
1187 with api_session(token3) as api:
1188 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
1189 res = api.ListFriendRequests(empty_pb2.Empty())
1190 user3_to_user2_id = res.sent[0].friend_request_id
1192 # Moderator approves the friend requests so recipients can see them
1193 moderator.approve_friend_request(user1_to_user2_id)
1194 moderator.approve_friend_request(user3_to_user2_id)
1196 with api_session(token2) as api:
1197 res = api.ListFriendRequests(empty_pb2.Empty())
1198 assert len(res.received) == 2
1200 # order is an implementation detail
1201 user1_req = [req for req in res.received if req.user_id == user1.id][0]
1202 user3_req = [req for req in res.received if req.user_id == user3.id][0]
1204 assert user1_req.state == api_pb2.FriendRequest.FriendRequestStatus.PENDING
1205 assert user1_req.user_id == user1.id
1206 api.RespondFriendRequest(
1207 api_pb2.RespondFriendRequestReq(friend_request_id=user1_req.friend_request_id, accept=True)
1208 )
1210 assert user3_req.state == api_pb2.FriendRequest.FriendRequestStatus.PENDING
1211 assert user3_req.user_id == user3.id
1212 api.RespondFriendRequest(
1213 api_pb2.RespondFriendRequestReq(friend_request_id=user3_req.friend_request_id, accept=True)
1214 )
1216 # check we now have two friends
1217 res = api.ListFriends(empty_pb2.Empty())
1218 assert len(res.user_ids) == 2
1219 assert user1.id in res.user_ids
1220 assert user3.id in res.user_ids
1222 # Moderator approves user1's friend request to user3 so user3 can see it
1223 moderator.approve_friend_request(user1_to_user3_id)
1225 with api_session(token3) as api:
1226 res = api.ListFriends(empty_pb2.Empty())
1227 assert len(res.user_ids) == 1
1228 assert user2.id in res.user_ids
1230 res = api.ListFriendRequests(empty_pb2.Empty())
1231 assert len(res.received) == 1
1232 assert res.received[0].state == api_pb2.FriendRequest.FriendRequestStatus.PENDING
1233 assert res.received[0].user_id == user1.id
1234 fr_id = res.received[0].friend_request_id
1235 api.RespondFriendRequest(api_pb2.RespondFriendRequestReq(friend_request_id=fr_id, accept=True))
1237 res = api.ListFriends(empty_pb2.Empty())
1238 assert len(res.user_ids) == 2
1239 assert user1.id in res.user_ids
1240 assert user2.id in res.user_ids
1242 with api_session(token1) as api:
1243 res = api.ListFriends(empty_pb2.Empty())
1244 assert len(res.user_ids) == 2
1245 assert user2.id in res.user_ids
1246 assert user3.id in res.user_ids
1249def test_ListMutualFriends(db):
1250 user1, token1 = generate_user()
1251 user2, token2 = generate_user()
1252 user3, token3 = generate_user()
1253 user4, token4 = generate_user()
1254 user5, token5 = generate_user()
1256 # arrange friends like this: 1<->2, 1<->3, 1<->4, 1<->5, 3<->2, 3<->4,
1257 # so 1 and 2 should have mutual friend 3 only
1258 make_friends(user1, user2)
1259 make_friends(user1, user3)
1260 make_friends(user1, user4)
1261 make_friends(user1, user5)
1262 make_friends(user3, user2)
1263 make_friends(user3, user4)
1265 with api_session(token1) as api:
1266 mutual_friends = api.ListMutualFriends(api_pb2.ListMutualFriendsReq(user_id=user2.id)).mutual_friends
1267 assert len(mutual_friends) == 1
1268 assert mutual_friends[0].user_id == user3.id
1270 # and other way around same
1271 with api_session(token2) as api:
1272 mutual_friends = api.ListMutualFriends(api_pb2.ListMutualFriendsReq(user_id=user1.id)).mutual_friends
1273 assert len(mutual_friends) == 1
1274 assert mutual_friends[0].user_id == user3.id
1276 # Check pending request doesn't have effect
1277 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user5.id))
1279 mutual_friends = api.ListMutualFriends(api_pb2.ListMutualFriendsReq(user_id=user1.id)).mutual_friends
1280 assert len(mutual_friends) == 1
1281 assert mutual_friends[0].user_id == user3.id
1283 # both ways
1284 with api_session(token1) as api:
1285 mutual_friends = api.ListMutualFriends(api_pb2.ListMutualFriendsReq(user_id=user2.id)).mutual_friends
1286 assert len(mutual_friends) == 1
1287 assert mutual_friends[0].user_id == user3.id
1290def test_mutual_friends_self(db):
1291 user1, token1 = generate_user()
1292 user2, token2 = generate_user()
1293 user3, token3 = generate_user()
1294 user4, token4 = generate_user()
1296 make_friends(user1, user2)
1297 make_friends(user2, user3)
1298 make_friends(user1, user4)
1300 with api_session(token1) as api:
1301 res = api.ListMutualFriends(api_pb2.ListMutualFriendsReq(user_id=user1.id))
1302 assert len(res.mutual_friends) == 0
1304 with api_session(token2) as api:
1305 res = api.ListMutualFriends(api_pb2.ListMutualFriendsReq(user_id=user2.id))
1306 assert len(res.mutual_friends) == 0
1308 with api_session(token3) as api:
1309 res = api.ListMutualFriends(api_pb2.ListMutualFriendsReq(user_id=user3.id))
1310 assert len(res.mutual_friends) == 0
1312 with api_session(token4) as api:
1313 res = api.ListMutualFriends(api_pb2.ListMutualFriendsReq(user_id=user4.id))
1314 assert len(res.mutual_friends) == 0
1317def test_CancelFriendRequest(db):
1318 user1, token1 = generate_user()
1319 user2, token2 = generate_user()
1321 with api_session(token1) as api:
1322 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
1324 res = api.ListFriendRequests(empty_pb2.Empty())
1325 assert res.sent[0].user_id == user2.id
1326 fr_id = res.sent[0].friend_request_id
1328 api.CancelFriendRequest(api_pb2.CancelFriendRequestReq(friend_request_id=fr_id))
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 not friends
1336 res = api.ListFriends(empty_pb2.Empty())
1337 assert len(res.user_ids) == 0
1339 with api_session(token2) as api:
1340 # check it's gone
1341 res = api.ListFriendRequests(empty_pb2.Empty())
1342 assert len(res.sent) == 0
1343 assert len(res.received) == 0
1345 # check we're not friends
1346 res = api.ListFriends(empty_pb2.Empty())
1347 assert len(res.user_ids) == 0
1349 with api_session(token1) as api:
1350 # check we can send another friend req
1351 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
1353 res = api.ListFriendRequests(empty_pb2.Empty())
1354 assert res.sent[0].state == api_pb2.FriendRequest.FriendRequestStatus.PENDING
1355 assert res.sent[0].user_id == user2.id
1358def test_accept_friend_request(db, moderator):
1359 user1, token1 = generate_user()
1360 user2, token2 = generate_user()
1362 with session_scope() as session:
1363 moderation_state = ModerationState(
1364 object_type=ModerationObjectType.friend_request,
1365 object_id=0,
1366 visibility=ModerationVisibility.visible,
1367 )
1368 session.add(moderation_state)
1369 session.flush()
1370 friend_request = FriendRelationship(
1371 from_user_id=user1.id,
1372 to_user_id=user2.id,
1373 status=FriendStatus.pending,
1374 moderation_state_id=moderation_state.id,
1375 )
1376 session.add(friend_request)
1377 session.flush()
1378 moderation_state.object_id = friend_request.id
1379 session.commit()
1380 friend_request_id = friend_request.id
1382 with api_session(token2) as api:
1383 # check request pending
1384 res = api.ListFriendRequests(empty_pb2.Empty())
1385 assert len(res.received) == 1
1386 assert res.received[0].user_id == user1.id
1388 api.RespondFriendRequest(api_pb2.RespondFriendRequestReq(friend_request_id=friend_request_id, accept=True))
1390 # check request is gone
1391 res = api.ListFriendRequests(empty_pb2.Empty())
1392 assert len(res.sent) == 0
1393 assert len(res.received) == 0
1395 # check now friends
1396 res = api.ListFriends(empty_pb2.Empty())
1397 assert len(res.user_ids) == 1
1398 assert res.user_ids[0] == user1.id
1400 with api_session(token1) as api:
1401 # check request gone
1402 res = api.ListFriendRequests(empty_pb2.Empty())
1403 assert len(res.sent) == 0
1404 assert len(res.received) == 0
1406 # check now friends
1407 res = api.ListFriends(empty_pb2.Empty())
1408 assert len(res.user_ids) == 1
1409 assert res.user_ids[0] == user2.id
1412def test_reject_friend_request(db, moderator):
1413 user1, token1 = generate_user()
1414 user2, token2 = generate_user()
1416 with api_session(token1) as api:
1417 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
1419 res = api.ListFriendRequests(empty_pb2.Empty())
1420 assert res.sent[0].state == api_pb2.FriendRequest.FriendRequestStatus.PENDING
1421 assert res.sent[0].user_id == user2.id
1422 fr_id = res.sent[0].friend_request_id
1424 # Moderator approves the friend request so recipient can see it
1425 moderator.approve_friend_request(fr_id)
1427 with api_session(token2) as api:
1428 res = api.ListFriendRequests(empty_pb2.Empty())
1429 assert res.received[0].state == api_pb2.FriendRequest.FriendRequestStatus.PENDING
1430 assert res.received[0].user_id == user1.id
1432 fr_id = res.received[0].friend_request_id
1434 # reject it
1435 api.RespondFriendRequest(api_pb2.RespondFriendRequestReq(friend_request_id=fr_id, accept=False))
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 not friends
1443 res = api.ListFriends(empty_pb2.Empty())
1444 assert len(res.user_ids) == 0
1446 with api_session(token1) as api:
1447 # check it's gone
1448 res = api.ListFriendRequests(empty_pb2.Empty())
1449 assert len(res.sent) == 0
1450 assert len(res.received) == 0
1452 # check we're not friends
1453 res = api.ListFriends(empty_pb2.Empty())
1454 assert len(res.user_ids) == 0
1456 # check we can send another friend req
1457 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
1459 res = api.ListFriendRequests(empty_pb2.Empty())
1460 assert res.sent[0].state == api_pb2.FriendRequest.FriendRequestStatus.PENDING
1461 assert res.sent[0].user_id == user2.id
1464def test_hosting_preferences(db):
1465 user1, token1 = generate_user()
1466 user2, token2 = generate_user()
1468 with api_session(token1) as api:
1469 res = api.GetUser(api_pb2.GetUserReq(user=user1.username))
1470 assert not res.HasField("max_guests")
1471 assert not res.HasField("last_minute")
1472 assert not res.HasField("has_pets")
1473 assert not res.HasField("accepts_pets")
1474 assert not res.HasField("pet_details")
1475 assert not res.HasField("has_kids")
1476 assert not res.HasField("accepts_kids")
1477 assert not res.HasField("kid_details")
1478 assert not res.HasField("has_housemates")
1479 assert not res.HasField("housemate_details")
1480 assert not res.HasField("wheelchair_accessible")
1481 assert res.smoking_allowed == api_pb2.SMOKING_LOCATION_UNKNOWN
1482 assert not res.HasField("smokes_at_home")
1483 assert not res.HasField("drinking_allowed")
1484 assert not res.HasField("drinks_at_home")
1485 assert not res.HasField("other_host_info")
1486 assert res.sleeping_arrangement == api_pb2.SLEEPING_ARRANGEMENT_UNKNOWN
1487 assert not res.HasField("sleeping_details")
1488 assert not res.HasField("area")
1489 assert not res.HasField("house_rules")
1490 assert not res.HasField("parking")
1491 assert res.parking_details == api_pb2.PARKING_DETAILS_UNKNOWN
1492 assert not res.HasField("camping_ok")
1494 api.UpdateProfile(
1495 api_pb2.UpdateProfileReq(
1496 max_guests=api_pb2.NullableUInt32Value(value=3),
1497 last_minute=api_pb2.NullableBoolValue(value=True),
1498 has_pets=api_pb2.NullableBoolValue(value=False),
1499 accepts_pets=api_pb2.NullableBoolValue(value=True),
1500 pet_details=api_pb2.NullableStringValue(value="I love dogs"),
1501 has_kids=api_pb2.NullableBoolValue(value=False),
1502 accepts_kids=api_pb2.NullableBoolValue(value=True),
1503 kid_details=api_pb2.NullableStringValue(value="I hate kids"),
1504 has_housemates=api_pb2.NullableBoolValue(value=False),
1505 housemate_details=api_pb2.NullableStringValue(value="I have 7 housemates"),
1506 wheelchair_accessible=api_pb2.NullableBoolValue(value=True),
1507 smoking_allowed=api_pb2.SMOKING_LOCATION_WINDOW,
1508 area=api_pb2.NullableStringValue(value="area!"),
1509 smokes_at_home=api_pb2.NullableBoolValue(value=False),
1510 drinking_allowed=api_pb2.NullableBoolValue(value=True),
1511 drinks_at_home=api_pb2.NullableBoolValue(value=False),
1512 other_host_info=api_pb2.NullableStringValue(value="I'm pretty swell"),
1513 sleeping_arrangement=api_pb2.SLEEPING_ARRANGEMENT_COMMON,
1514 sleeping_details=api_pb2.NullableStringValue(value="Couch in living room"),
1515 house_rules=api_pb2.NullableStringValue(value="RULES!"),
1516 parking=api_pb2.NullableBoolValue(value=True),
1517 parking_details=api_pb2.PARKING_DETAILS_PAID_ONSITE,
1518 camping_ok=api_pb2.NullableBoolValue(value=False),
1519 )
1520 )
1522 # Use a second user to view the hosting preferences just to check
1523 # that it is public information.
1524 with api_session(token2) as api:
1525 res = api.GetUser(api_pb2.GetUserReq(user=user1.username))
1526 assert res.max_guests.value == 3
1527 assert res.last_minute.value
1528 assert not res.has_pets.value
1529 assert res.accepts_pets.value
1530 assert res.pet_details.value == "I love dogs"
1531 assert not res.has_kids.value
1532 assert res.accepts_kids.value
1533 assert res.kid_details.value == "I hate kids"
1534 assert not res.has_housemates.value
1535 assert res.housemate_details.value == "I have 7 housemates"
1536 assert res.wheelchair_accessible.value
1537 assert res.smoking_allowed == api_pb2.SMOKING_LOCATION_WINDOW
1538 assert not res.smokes_at_home.value
1539 assert res.drinking_allowed.value
1540 assert not res.drinks_at_home.value
1541 assert res.other_host_info.value == "I'm pretty swell"
1542 assert res.sleeping_arrangement == api_pb2.SLEEPING_ARRANGEMENT_COMMON
1543 assert res.sleeping_details.value == "Couch in living room"
1544 assert res.area.value == "area!"
1545 assert res.house_rules.value == "RULES!"
1546 assert res.parking.value
1547 assert res.parking_details == api_pb2.PARKING_DETAILS_PAID_ONSITE
1548 assert not res.camping_ok.value
1550 # test unsetting
1551 with api_session(token1) as api:
1552 api.UpdateProfile(
1553 api_pb2.UpdateProfileReq(
1554 max_guests=api_pb2.NullableUInt32Value(is_null=True),
1555 last_minute=api_pb2.NullableBoolValue(is_null=True),
1556 has_pets=api_pb2.NullableBoolValue(is_null=True),
1557 accepts_pets=api_pb2.NullableBoolValue(is_null=True),
1558 pet_details=api_pb2.NullableStringValue(is_null=True),
1559 has_kids=api_pb2.NullableBoolValue(is_null=True),
1560 accepts_kids=api_pb2.NullableBoolValue(is_null=True),
1561 kid_details=api_pb2.NullableStringValue(is_null=True),
1562 has_housemates=api_pb2.NullableBoolValue(is_null=True),
1563 housemate_details=api_pb2.NullableStringValue(is_null=True),
1564 wheelchair_accessible=api_pb2.NullableBoolValue(is_null=True),
1565 smoking_allowed=api_pb2.SMOKING_LOCATION_UNKNOWN,
1566 area=api_pb2.NullableStringValue(is_null=True),
1567 smokes_at_home=api_pb2.NullableBoolValue(is_null=True),
1568 drinking_allowed=api_pb2.NullableBoolValue(is_null=True),
1569 drinks_at_home=api_pb2.NullableBoolValue(is_null=True),
1570 other_host_info=api_pb2.NullableStringValue(is_null=True),
1571 sleeping_arrangement=api_pb2.SLEEPING_ARRANGEMENT_UNKNOWN,
1572 sleeping_details=api_pb2.NullableStringValue(is_null=True),
1573 house_rules=api_pb2.NullableStringValue(is_null=True),
1574 parking=api_pb2.NullableBoolValue(is_null=True),
1575 parking_details=api_pb2.PARKING_DETAILS_UNKNOWN,
1576 camping_ok=api_pb2.NullableBoolValue(is_null=True),
1577 )
1578 )
1580 res = api.GetUser(api_pb2.GetUserReq(user=user1.username))
1581 assert not res.HasField("max_guests")
1582 assert not res.HasField("last_minute")
1583 assert not res.HasField("has_pets")
1584 assert not res.HasField("accepts_pets")
1585 assert not res.HasField("pet_details")
1586 assert not res.HasField("has_kids")
1587 assert not res.HasField("accepts_kids")
1588 assert not res.HasField("kid_details")
1589 assert not res.HasField("has_housemates")
1590 assert not res.HasField("housemate_details")
1591 assert not res.HasField("wheelchair_accessible")
1592 assert res.smoking_allowed == api_pb2.SMOKING_LOCATION_UNKNOWN
1593 assert not res.HasField("smokes_at_home")
1594 assert not res.HasField("drinking_allowed")
1595 assert not res.HasField("drinks_at_home")
1596 assert not res.HasField("other_host_info")
1597 assert res.sleeping_arrangement == api_pb2.SLEEPING_ARRANGEMENT_UNKNOWN
1598 assert not res.HasField("sleeping_details")
1599 assert not res.HasField("area")
1600 assert not res.HasField("house_rules")
1601 assert not res.HasField("parking")
1602 assert res.parking_details == api_pb2.PARKING_DETAILS_UNKNOWN
1603 assert not res.HasField("camping_ok")
1606def test_badges(db):
1607 user1, _ = generate_user(last_donated=None)
1608 user2, _ = generate_user(last_donated=None)
1609 user3, _ = generate_user(last_donated=None)
1610 user4, token = generate_user(last_donated=None)
1612 update_badges(empty_pb2.Empty())
1614 founder_badge = get_badge_dict()["founder"]
1615 board_member_badge = get_badge_dict()["board_member"]
1617 with api_session(token) as api:
1618 assert api.GetUser(api_pb2.GetUserReq(user=user1.username)).badges == ["founder", "board_member"]
1619 assert api.GetUser(api_pb2.GetUserReq(user=user2.username)).badges == ["founder", "board_member"]
1620 assert api.GetUser(api_pb2.GetUserReq(user=user3.username)).badges == []
1622 assert api.ListBadgeUsers(api_pb2.ListBadgeUsersReq(badge_id=founder_badge.id)).user_ids == [1, 2]
1623 res = api.ListBadgeUsers(api_pb2.ListBadgeUsersReq(badge_id=board_member_badge.id, page_size=1))
1624 assert res.user_ids == [1]
1625 res2 = api.ListBadgeUsers(
1626 api_pb2.ListBadgeUsersReq(badge_id=board_member_badge.id, page_token=res.next_page_token)
1627 )
1628 assert res2.user_ids == [2]
1631def test_user_add_badge_is_idempotent(db):
1632 """Test that adding a badge a user already has is a no-op and doesn't send a duplicate notification."""
1633 user, _ = generate_user()
1635 with session_scope() as session:
1636 user_add_badge(session, user.id, "volunteer")
1638 # one badge row, one notification
1639 with session_scope() as session:
1640 badge_count = session.execute(
1641 select(func.count())
1642 .select_from(UserBadge)
1643 .where(UserBadge.user_id == user.id, UserBadge.badge_id == "volunteer")
1644 ).scalar()
1645 assert badge_count == 1
1646 notification_count = session.execute(
1647 select(func.count()).select_from(Notification).where(Notification.user_id == user.id)
1648 ).scalar()
1649 assert notification_count == 1
1651 # add the same badge again
1652 with session_scope() as session:
1653 user_add_badge(session, user.id, "volunteer")
1655 # still one badge row, no new notification
1656 with session_scope() as session:
1657 badge_count = session.execute(
1658 select(func.count())
1659 .select_from(UserBadge)
1660 .where(UserBadge.user_id == user.id, UserBadge.badge_id == "volunteer")
1661 ).scalar()
1662 assert badge_count == 1
1663 notification_count = session.execute(
1664 select(func.count()).select_from(Notification).where(Notification.user_id == user.id)
1665 ).scalar()
1666 assert notification_count == 1
1669@pytest.mark.parametrize("flag", ["deleted_at", "banned_at"])
1670def test_ListBadgeUsers_excludes_ghost_users(db, flag):
1671 """Test that ListBadgeUsers does not return deleted/banned users."""
1672 user1, token1 = generate_user()
1673 user2, _ = generate_user()
1674 user3, _ = generate_user()
1676 volunteer_badge = get_badge_dict()["volunteer"]
1678 # Give all three users the volunteer badge
1679 with session_scope() as session:
1680 user_add_badge(session, user1.id, "volunteer", do_notify=False)
1681 user_add_badge(session, user2.id, "volunteer", do_notify=False)
1682 user_add_badge(session, user3.id, "volunteer", do_notify=False)
1684 # Verify all three users 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, user2.id, user3.id}
1689 # Make user2 invisible (deleted or banned)
1690 with session_scope() as session:
1691 db_user2 = session.execute(select(User).where(User.id == user2.id)).scalar_one()
1692 setattr(db_user2, flag, now())
1694 # Now user2 should not appear in the badge list
1695 with api_session(token1) as api:
1696 res = api.ListBadgeUsers(api_pb2.ListBadgeUsersReq(badge_id=volunteer_badge.id))
1697 assert set(res.user_ids) == {user1.id, user3.id}
1700@pytest.mark.parametrize("flag", ["deleted_at", "banned_at"])
1701def test_GetLiteUser_ghost_user_by_username(db, flag):
1702 """Test that GetLiteUser returns a ghost profile for deleted/banned users when querying by username."""
1703 user1, token1 = generate_user()
1704 user2, _ = generate_user()
1706 # Make user2 invisible
1707 with session_scope() as session:
1708 db_user2 = session.merge(user2)
1709 setattr(db_user2, flag, now())
1710 session.commit()
1712 # Refresh the materialized view
1713 refresh_materialized_views_rapid(empty_pb2.Empty())
1715 with api_session(token1) as api:
1716 # Query by username
1717 lite_user = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user2.username))
1719 assert lite_user.user_id == user2.id
1720 assert lite_user.username == "ghost"
1721 assert lite_user.name == "Deactivated Account"
1722 assert lite_user.lat == 0
1723 assert lite_user.lng == 0
1724 assert lite_user.radius == 0
1725 assert lite_user.city == ""
1726 assert lite_user.age == 0
1727 assert lite_user.avatar_url == ""
1728 assert lite_user.avatar_thumbnail_url == ""
1729 assert not lite_user.has_strong_verification
1732@pytest.mark.parametrize("flag", ["deleted_at", "banned_at"])
1733def test_GetLiteUser_ghost_user_by_id(db, flag):
1734 """Test that GetLiteUser returns a ghost profile for deleted/banned users when querying by ID."""
1735 user1, token1 = generate_user()
1736 user2, _ = generate_user()
1738 # Make user2 invisible
1739 with session_scope() as session:
1740 db_user2 = session.merge(user2)
1741 setattr(db_user2, flag, now())
1742 session.commit()
1744 # Refresh the materialized view
1745 refresh_materialized_views_rapid(empty_pb2.Empty())
1747 with api_session(token1) as api:
1748 # Query by ID
1749 lite_user = api.GetLiteUser(api_pb2.GetLiteUserReq(user=str(user2.id)))
1751 assert lite_user.user_id == user2.id
1752 assert lite_user.username == "ghost"
1753 assert lite_user.name == "Deactivated Account"
1754 assert lite_user.lat == 0
1755 assert lite_user.lng == 0
1756 assert lite_user.radius == 0
1757 assert lite_user.city == ""
1758 assert lite_user.age == 0
1759 assert lite_user.avatar_url == ""
1760 assert lite_user.avatar_thumbnail_url == ""
1761 assert not lite_user.has_strong_verification
1764def test_GetLiteUser_blocked_user(db):
1765 """Test that GetLiteUser returns a ghost profile for blocked users."""
1766 user1, token1 = generate_user()
1767 user2, _ = generate_user()
1769 # User1 blocks user2
1770 make_user_block(user1, user2)
1772 # Refresh the materialized view
1773 refresh_materialized_views_rapid(empty_pb2.Empty())
1775 with api_session(token1) as api:
1776 # Query by username
1777 lite_user = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user2.username))
1779 assert lite_user.user_id == user2.id
1780 assert lite_user.is_ghost
1781 assert lite_user.username == "ghost"
1782 assert lite_user.name == "Deactivated Account"
1784 # Query by ID
1785 lite_user = api.GetLiteUser(api_pb2.GetLiteUserReq(user=str(user2.id)))
1787 assert lite_user.user_id == user2.id
1788 assert lite_user.is_ghost
1789 assert lite_user.username == "ghost"
1790 assert lite_user.name == "Deactivated Account"
1793def test_GetLiteUser_blocking_user(db):
1794 """Test that GetLiteUser returns a ghost profile when the target user has blocked the requester."""
1795 user1, token1 = generate_user()
1796 user2, _ = generate_user()
1798 # User2 blocks user1
1799 make_user_block(user2, user1)
1801 # Refresh the materialized view
1802 refresh_materialized_views_rapid(empty_pb2.Empty())
1804 with api_session(token1) as api:
1805 # Query by username
1806 lite_user = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user2.username))
1808 assert lite_user.user_id == user2.id
1809 assert lite_user.is_ghost
1810 assert lite_user.username == "ghost"
1811 assert lite_user.name == "Deactivated Account"
1813 # Query by ID
1814 lite_user = api.GetLiteUser(api_pb2.GetLiteUserReq(user=str(user2.id)))
1816 assert lite_user.user_id == user2.id
1817 assert lite_user.is_ghost
1818 assert lite_user.username == "ghost"
1819 assert lite_user.name == "Deactivated Account"
1822@pytest.mark.parametrize("flag", ["deleted_at", "banned_at"])
1823def test_GetLiteUsers_ghost_users(db, flag):
1824 """Test that GetLiteUsers returns ghost profiles for deleted/banned users."""
1825 user1, token1 = generate_user()
1826 user2, _ = generate_user()
1827 user3, _ = generate_user()
1828 user4, _ = generate_user()
1830 # Make user2 and user4 invisible
1831 with session_scope() as session:
1832 db_user2 = session.merge(user2)
1833 setattr(db_user2, flag, now())
1834 db_user4 = session.merge(user4)
1835 setattr(db_user4, flag, now())
1836 session.commit()
1838 # Refresh the materialized view
1839 refresh_materialized_views_rapid(empty_pb2.Empty())
1841 with api_session(token1) as api:
1842 res = api.GetLiteUsers(
1843 api_pb2.GetLiteUsersReq(
1844 users=[
1845 user1.username, # visible
1846 user2.username, # ghost
1847 str(user3.id), # visible
1848 str(user4.id), # ghost
1849 ]
1850 )
1851 )
1853 assert len(res.responses) == 4
1855 # user1 - visible, normal profile
1856 assert res.responses[0].query == user1.username
1857 assert not res.responses[0].not_found
1858 assert res.responses[0].user.user_id == user1.id
1859 assert res.responses[0].user.username == user1.username
1860 assert res.responses[0].user.name == user1.name
1862 # user2 - ghost by username
1863 assert res.responses[1].query == user2.username
1864 assert not res.responses[1].not_found
1865 assert res.responses[1].user.user_id == user2.id
1866 assert res.responses[1].user.is_ghost
1867 assert res.responses[1].user.username == "ghost"
1868 assert res.responses[1].user.name == "Deactivated Account"
1870 # user3 - visible, normal profile
1871 assert res.responses[2].query == str(user3.id)
1872 assert not res.responses[2].not_found
1873 assert res.responses[2].user.user_id == user3.id
1874 assert res.responses[2].user.username == user3.username
1875 assert res.responses[2].user.name == user3.name
1877 # user4 - ghost by ID
1878 assert res.responses[3].query == str(user4.id)
1879 assert not res.responses[3].not_found
1880 assert res.responses[3].user.user_id == user4.id
1881 assert res.responses[3].user.is_ghost
1882 assert res.responses[3].user.username == "ghost"
1883 assert res.responses[3].user.name == "Deactivated Account"
1886def test_GetLiteUsers_blocked_users(db):
1887 """Test that GetLiteUsers returns ghost profiles for blocked users."""
1888 user1, token1 = generate_user()
1889 user2, _ = generate_user()
1890 user3, _ = generate_user()
1891 user4, _ = generate_user()
1892 user5, _ = generate_user()
1894 # User1 blocks user2
1895 make_user_block(user1, user2)
1896 # User4 blocks user1
1897 make_user_block(user4, user1)
1899 # Refresh the materialized view
1900 refresh_materialized_views_rapid(empty_pb2.Empty())
1902 with api_session(token1) as api:
1903 res = api.GetLiteUsers(
1904 api_pb2.GetLiteUsersReq(
1905 users=[
1906 user2.username, # user1 blocked user2
1907 str(user3.id), # visible
1908 user4.username, # user4 blocked user1
1909 str(user5.id), # visible
1910 ]
1911 )
1912 )
1914 assert len(res.responses) == 4
1916 # user2 - blocked by user1, should be ghost
1917 assert res.responses[0].query == user2.username
1918 assert not res.responses[0].not_found
1919 assert res.responses[0].user.user_id == user2.id
1920 assert res.responses[0].user.is_ghost
1921 assert res.responses[0].user.username == "ghost"
1922 assert res.responses[0].user.name == "Deactivated Account"
1924 # user3 - visible
1925 assert res.responses[1].query == str(user3.id)
1926 assert not res.responses[1].not_found
1927 assert res.responses[1].user.user_id == user3.id
1928 assert res.responses[1].user.username == user3.username
1930 # user4 - user4 blocked user1, should be ghost
1931 assert res.responses[2].query == user4.username
1932 assert not res.responses[2].not_found
1933 assert res.responses[2].user.user_id == user4.id
1934 assert res.responses[2].user.is_ghost
1935 assert res.responses[2].user.username == "ghost"
1936 assert res.responses[2].user.name == "Deactivated Account"
1938 # user5 - visible
1939 assert res.responses[3].query == str(user5.id)
1940 assert not res.responses[3].not_found
1941 assert res.responses[3].user.user_id == user5.id
1942 assert res.responses[3].user.username == user5.username
1945@pytest.mark.parametrize("flag", ["deleted_at", "banned_at"])
1946def test_GetUser_ghost_user_by_id(db, flag):
1947 """Test that GetUser returns a ghost profile for deleted/banned users when querying by ID."""
1948 user1, token1 = generate_user()
1949 user2, _ = generate_user()
1951 # Make user2 invisible
1952 with session_scope() as session:
1953 db_user2 = session.merge(user2)
1954 setattr(db_user2, flag, now())
1955 session.commit()
1957 with api_session(token1) as api:
1958 # Query by ID
1959 user_pb = api.GetUser(api_pb2.GetUserReq(user=str(user2.id)))
1961 assert user_pb.user_id == user2.id
1962 assert user_pb.username == "ghost"
1963 assert user_pb.name == "Deactivated Account"
1964 assert user_pb.city == ""
1965 assert user_pb.hosting_status == 0
1966 assert user_pb.meetup_status == 0
1969def test_GetUser_blocked_user(db):
1970 """Test that GetUser returns a ghost profile for blocked users."""
1971 user1, token1 = generate_user()
1972 user2, _ = generate_user()
1974 # User1 blocks user2
1975 make_user_block(user1, user2)
1977 with api_session(token1) as api:
1978 # Query by username
1979 user_pb = api.GetUser(api_pb2.GetUserReq(user=user2.username))
1981 assert user_pb.user_id == user2.id
1982 assert user_pb.username == "ghost"
1983 assert user_pb.name == "Deactivated Account"
1985 # Query by ID
1986 user_pb = api.GetUser(api_pb2.GetUserReq(user=str(user2.id)))
1988 assert user_pb.user_id == user2.id
1989 assert user_pb.username == "ghost"
1990 assert user_pb.name == "Deactivated Account"
1993def test_GetUser_blocking_user(db):
1994 """Test that GetUser returns a ghost profile when the target user has blocked the requester."""
1995 user1, token1 = generate_user()
1996 user2, _ = generate_user()
1998 # User2 blocks user1
1999 make_user_block(user2, user1)
2001 with api_session(token1) as api:
2002 # Query by username
2003 user_pb = api.GetUser(api_pb2.GetUserReq(user=user2.username))
2005 assert user_pb.user_id == user2.id
2006 assert user_pb.username == "ghost"
2007 assert user_pb.name == "Deactivated Account"
2009 # Query by ID
2010 user_pb = api.GetUser(api_pb2.GetUserReq(user=str(user2.id)))
2012 assert user_pb.user_id == user2.id
2013 assert user_pb.username == "ghost"
2014 assert user_pb.name == "Deactivated Account"