Coverage for app / backend / src / tests / test_api.py: 100%
1147 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-19 14:14 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-19 14:14 +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.jobs.handlers import update_badges
10from couchers.materialized_views import refresh_materialized_views_rapid
11from couchers.models import (
12 FriendRelationship,
13 FriendStatus,
14 LanguageFluency,
15 ModerationObjectType,
16 ModerationState,
17 ModerationVisibility,
18 RateLimitAction,
19 User,
20)
21from couchers.proto import admin_pb2, api_pb2, blocking_pb2, jail_pb2, notifications_pb2
22from couchers.rate_limits.definitions import RATE_LIMIT_DEFINITIONS, RATE_LIMIT_HOURS
23from couchers.resources import get_badge_dict
24from couchers.utils import create_coordinate, now, to_aware_datetime
25from tests.fixtures.db import generate_user, make_friends, make_user_block
26from tests.fixtures.misc import PushCollector, email_fields, mock_notification_email
27from tests.fixtures.sessions import (
28 api_session,
29 blocking_session,
30 notifications_session,
31 real_api_session,
32 real_jail_session,
33)
34from tests.fixtures.sessions import (
35 real_admin_session as admin_session,
36)
39@pytest.fixture(autouse=True)
40def _(testconfig):
41 pass
44def test_ping(db):
45 user, token = generate_user(
46 regions_lived=["ESP", "FRA", "EST"],
47 regions_visited=["CHE", "REU", "FIN"],
48 language_abilities=[
49 ("fin", LanguageFluency.fluent),
50 ("fra", LanguageFluency.beginner),
51 ],
52 )
54 with real_api_session(token) as api:
55 res = api.Ping(api_pb2.PingReq())
57 assert res.user.user_id == user.id
58 assert res.user.username == user.username
59 assert res.user.name == user.name
60 assert res.user.city == user.city
61 assert res.user.hometown == user.hometown
62 assert res.user.verification == 0.0
63 assert res.user.community_standing == user.community_standing
64 assert res.user.num_references == 0
65 assert res.user.gender == user.gender
66 assert res.user.pronouns == user.pronouns
67 assert res.user.age == user.age
69 assert (res.user.lat, res.user.lng) == user.coordinates
71 # the joined time is fuzzed
72 # but shouldn't be before actual joined time, or more than one hour behind
73 assert user.joined - timedelta(hours=1) <= to_aware_datetime(res.user.joined) <= user.joined
74 # same for last_active
75 assert user.last_active - timedelta(hours=1) <= to_aware_datetime(res.user.last_active) <= user.last_active
77 assert res.user.hosting_status == api_pb2.HOSTING_STATUS_CANT_HOST
78 assert res.user.meetup_status == api_pb2.MEETUP_STATUS_OPEN_TO_MEETUP
80 assert res.user.occupation == user.occupation
81 assert res.user.education == user.education
82 assert res.user.about_me == user.about_me
83 assert res.user.things_i_like == user.things_i_like
84 assert {language_ability.code for language_ability in res.user.language_abilities} == {"fin", "fra"}
85 assert res.user.about_place == user.about_place
86 assert res.user.regions_visited == ["FIN", "REU", "CHE"] # Tests alphabetization by region name
87 assert res.user.regions_lived == ["EST", "FRA", "ESP"] # Ditto
88 assert res.user.additional_information == user.additional_information
90 assert res.user.friends == api_pb2.User.FriendshipStatus.NA
91 assert not res.user.HasField("pending_friend_request")
94def test_coords(db):
95 # make them need to update location
96 user1, token1 = generate_user(geom=create_coordinate(1, 0), geom_radius=2000, needs_to_update_location=True)
97 user2, token2 = generate_user()
99 with api_session(token2) as api:
100 res = api.Ping(api_pb2.PingReq())
101 assert res.user.city == user2.city
102 lat, lng = user2.coordinates
103 assert res.user.lat == lat
104 assert res.user.lng == lng
105 assert res.user.radius == user2.geom_radius
107 with api_session(token2) as api:
108 res = api.GetUser(api_pb2.GetUserReq(user=user1.username))
109 assert res.city == user1.city
110 assert res.lat == 1.0
111 assert res.lng == 0.0
112 assert res.radius == 2000.0
114 # Check coordinate wrapping
115 user3, token3 = generate_user(geom=create_coordinate(40.0, -180.5))
116 user4, token4 = generate_user(geom=create_coordinate(40.0, 20.0))
117 user5, token5 = generate_user(geom=create_coordinate(90.5, 20.0))
119 with api_session(token3) as api:
120 res = api.GetUser(api_pb2.GetUserReq(user=user3.username))
121 assert res.lat == 40.0
122 assert res.lng == 179.5
124 with api_session(token4) as api:
125 res = api.GetUser(api_pb2.GetUserReq(user=user4.username))
126 assert res.lat == 40.0
127 assert res.lng == 20.0
129 # PostGIS does not wrap longitude for latitude overflow
130 with api_session(token5) as api:
131 res = api.GetUser(api_pb2.GetUserReq(user=user5.username))
132 assert res.lat == 89.5
133 assert res.lng == 20.0
135 with real_jail_session(token1) as jail:
136 res = jail.JailInfo(empty_pb2.Empty())
137 assert res.jailed
138 assert res.needs_to_update_location
140 res = jail.SetLocation(
141 jail_pb2.SetLocationReq(
142 city="New York City",
143 lat=40.7812,
144 lng=-73.9647,
145 radius=250,
146 )
147 )
149 assert not res.jailed
150 assert not res.needs_to_update_location
152 res = jail.JailInfo(empty_pb2.Empty())
153 assert not res.jailed
154 assert not res.needs_to_update_location
156 with api_session(token2) as api:
157 res = api.GetUser(api_pb2.GetUserReq(user=user1.username))
158 assert res.city == "New York City"
159 assert res.lat == 40.7812
160 assert res.lng == -73.9647
161 assert res.radius == 250
164def test_get_user(db):
165 user1, token1 = generate_user()
166 user2, token2 = generate_user()
168 with api_session(token1) as api:
169 res = api.GetUser(api_pb2.GetUserReq(user=user2.username))
170 assert res.user_id == user2.id
171 assert res.username == user2.username
172 assert res.name == user2.name
174 with api_session(token1) as api:
175 res = api.GetUser(api_pb2.GetUserReq(user=str(user2.id)))
176 assert res.user_id == user2.id
177 assert res.username == user2.username
178 assert res.name == user2.name
181@pytest.mark.parametrize("flag", ["deleted_at", "banned_at"])
182def test_user_model_to_pb_ghost_user(db, flag):
183 user1, token1 = generate_user()
184 user2, _ = generate_user()
186 with session_scope() as session:
187 session.execute(update(User).where(User.id == user2.id).values(**{flag: func.now()}))
189 refresh_materialized_views_rapid(empty_pb2.Empty())
191 with api_session(token1) as api:
192 user_pb = api.GetUser(api_pb2.GetUserReq(user=user2.username))
194 assert user_pb.user_id == user2.id
195 assert user_pb.is_ghost
196 assert user_pb.username == "ghost"
197 assert user_pb.name == "Deactivated Account"
198 assert (
199 user_pb.about_me
200 == "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."
201 )
203 assert user_pb.lat == 0
204 assert user_pb.lng == 0
205 assert user_pb.radius == 0
206 assert user_pb.verification == 0.0
207 assert user_pb.community_standing == 0.0
208 assert user_pb.num_references == 0
209 assert user_pb.age == 0
210 assert user_pb.hosting_status == 0
211 assert user_pb.meetup_status == 0
212 assert user_pb.city == ""
213 assert user_pb.hometown == ""
214 assert user_pb.timezone == ""
215 assert user_pb.gender == ""
216 assert user_pb.pronouns == ""
217 assert user_pb.occupation == ""
218 assert user_pb.education == ""
219 assert user_pb.things_i_like == ""
220 assert user_pb.about_place == ""
221 assert user_pb.additional_information == ""
222 assert list(user_pb.language_abilities) == []
223 assert list(user_pb.regions_visited) == []
224 assert list(user_pb.regions_lived) == []
225 assert list(user_pb.badges) == []
226 assert user_pb.friends == api_pb2.User.FriendshipStatus.NOT_FRIENDS
227 assert user_pb.avatar_url == ""
228 assert user_pb.avatar_thumbnail_url == ""
229 assert not user_pb.has_strong_verification
231 with api_session(token1) as api:
232 lite_user_pb = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user2.username))
234 assert lite_user_pb.user_id == user2.id
235 assert lite_user_pb.is_ghost
236 assert lite_user_pb.username == "ghost"
237 assert lite_user_pb.name == "Deactivated Account"
238 assert lite_user_pb.city == ""
239 assert lite_user_pb.age == 0
240 assert lite_user_pb.avatar_url == ""
241 assert lite_user_pb.avatar_thumbnail_url == ""
242 assert lite_user_pb.lat == 0
243 assert lite_user_pb.lng == 0
244 assert lite_user_pb.radius == 0
245 assert not lite_user_pb.has_strong_verification
248def test_user_model_to_pb_ghost_user_blocked(db):
249 user1, token1 = generate_user()
250 user2, _ = generate_user()
252 with blocking_session(token1) as user_blocks:
253 user_blocks.BlockUser(blocking_pb2.BlockUserReq(username=user2.username))
255 refresh_materialized_views_rapid(empty_pb2.Empty())
257 with api_session(token1) as api:
258 user_pb = api.GetUser(api_pb2.GetUserReq(user=user2.username))
260 assert user_pb.user_id == user2.id
261 assert user_pb.is_ghost
262 assert user_pb.username == "ghost"
263 assert user_pb.name == "Deactivated Account"
264 assert (
265 user_pb.about_me
266 == "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."
267 )
269 assert user_pb.lat == 0
270 assert user_pb.lng == 0
271 assert user_pb.radius == 0
272 assert user_pb.verification == 0.0
273 assert user_pb.community_standing == 0.0
274 assert user_pb.num_references == 0
275 assert user_pb.age == 0
276 assert user_pb.hosting_status == 0
277 assert user_pb.meetup_status == 0
278 assert user_pb.city == ""
279 assert user_pb.hometown == ""
280 assert user_pb.timezone == ""
281 assert user_pb.gender == ""
282 assert user_pb.pronouns == ""
283 assert user_pb.occupation == ""
284 assert user_pb.education == ""
285 assert user_pb.things_i_like == ""
286 assert user_pb.about_place == ""
287 assert user_pb.additional_information == ""
288 assert list(user_pb.language_abilities) == []
289 assert list(user_pb.regions_visited) == []
290 assert list(user_pb.regions_lived) == []
291 assert list(user_pb.badges) == []
292 assert user_pb.friends == api_pb2.User.FriendshipStatus.NOT_FRIENDS
293 assert user_pb.avatar_url == ""
294 assert user_pb.avatar_thumbnail_url == ""
295 assert not user_pb.has_strong_verification
297 with api_session(token1) as api:
298 lite_user_pb = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user2.username))
300 assert lite_user_pb.user_id == user2.id
301 assert lite_user_pb.is_ghost
302 assert lite_user_pb.username == "ghost"
303 assert lite_user_pb.name == "Deactivated Account"
304 assert lite_user_pb.city == ""
305 assert lite_user_pb.age == 0
306 assert lite_user_pb.avatar_url == ""
307 assert lite_user_pb.avatar_thumbnail_url == ""
308 assert lite_user_pb.lat == 0
309 assert lite_user_pb.lng == 0
310 assert lite_user_pb.radius == 0
311 assert not lite_user_pb.has_strong_verification
314@pytest.mark.parametrize("flag", ["deleted_at", "banned_at"])
315def test_admin_viewing_ghost_users_sees_full_profile(db, flag):
316 admin, token_admin = generate_user(is_superuser=True)
317 user, _ = generate_user()
319 with session_scope() as session:
320 session.execute(update(User).where(User.id == user.id).values(**{flag: func.now()}))
322 with admin_session(token_admin) as api:
323 user_pb = api.GetUser(admin_pb2.GetUserReq(user=user.username))
325 assert user_pb.user_id == user.id
326 assert user_pb.username == user.username
327 assert user_pb.name == user.name
328 assert user_pb.city == user.city
329 assert user_pb.name != "Deactivated Account"
330 assert user_pb.username != "ghost"
331 assert user_pb.hosting_status in (
332 api_pb2.HOSTING_STATUS_UNKNOWN,
333 api_pb2.HOSTING_STATUS_CAN_HOST,
334 api_pb2.HOSTING_STATUS_MAYBE,
335 api_pb2.HOSTING_STATUS_CANT_HOST,
336 )
339def test_lite_coords(db):
340 # make them need to update location
341 user1, token1 = generate_user(geom=create_coordinate(0, 0), geom_radius=0, needs_to_update_location=True)
342 user2, token2 = generate_user()
344 refresh_materialized_views_rapid(empty_pb2.Empty())
346 with api_session(token2) as api:
347 res = api.Ping(api_pb2.PingReq())
348 assert res.user.city == user2.city
349 lat, lng = user2.coordinates
350 assert res.user.lat == lat
351 assert res.user.lng == lng
352 assert res.user.radius == user2.geom_radius
354 with api_session(token2) as api:
355 res = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user1.username))
356 assert res.city == user1.city
357 assert res.lat == 0.0
358 assert res.lng == 0.0
359 assert res.radius == 0.0
361 # Check coordinate wrapping
362 user3, token3 = generate_user(geom=create_coordinate(40.0, -180.5))
363 user4, token4 = generate_user(geom=create_coordinate(40.0, 20.0))
364 user5, token5 = generate_user(geom=create_coordinate(90.5, 20.0))
366 refresh_materialized_views_rapid(empty_pb2.Empty())
368 with api_session(token3) as api:
369 res = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user3.username))
370 assert res.lat == 40.0
371 assert res.lng == 179.5
373 with api_session(token4) as api:
374 res = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user4.username))
375 assert res.lat == 40.0
376 assert res.lng == 20.0
378 # PostGIS does not wrap longitude for latitude overflow
379 with api_session(token5) as api:
380 res = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user5.username))
381 assert res.lat == 89.5
382 assert res.lng == 20.0
384 with real_jail_session(token1) as jail:
385 res = jail.JailInfo(empty_pb2.Empty())
386 assert res.jailed
387 assert res.needs_to_update_location
389 res = jail.SetLocation(
390 jail_pb2.SetLocationReq(
391 city="New York City",
392 lat=40.7812,
393 lng=-73.9647,
394 radius=250,
395 )
396 )
398 assert not res.jailed
399 assert not res.needs_to_update_location
401 res = jail.JailInfo(empty_pb2.Empty())
402 assert not res.jailed
403 assert not res.needs_to_update_location
405 refresh_materialized_views_rapid(empty_pb2.Empty())
407 with api_session(token2) as api:
408 res = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user1.username))
409 assert res.city == "New York City"
410 assert res.lat == 40.7812
411 assert res.lng == -73.9647
412 assert res.radius == 250
415def test_lite_get_user(db):
416 user1, token1 = generate_user()
417 user2, token2 = generate_user()
419 refresh_materialized_views_rapid(empty_pb2.Empty())
421 with api_session(token1) as api:
422 res = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user2.username))
423 assert res.user_id == user2.id
424 assert res.username == user2.username
425 assert res.name == user2.name
427 with api_session(token1) as api:
428 res = api.GetLiteUser(api_pb2.GetLiteUserReq(user=str(user2.id)))
429 assert res.user_id == user2.id
430 assert res.username == user2.username
431 assert res.name == user2.name
434def test_GetLiteUsers(db):
435 user1, token1 = generate_user()
436 user2, _ = generate_user()
437 user3, _ = generate_user()
438 user4, _ = generate_user()
439 user5, _ = generate_user()
440 user6, _ = generate_user()
442 make_user_block(user4, user1)
444 refresh_materialized_views_rapid(empty_pb2.Empty())
446 with api_session(token1) as api:
447 res = api.GetLiteUsers(
448 api_pb2.GetLiteUsersReq(
449 users=[
450 user1.username,
451 str(user1.id),
452 "nonexistent",
453 str(user2.id),
454 "9994",
455 user6.username,
456 str(user5.id),
457 "notreal",
458 user4.username,
459 ]
460 )
461 )
463 assert len(res.responses) == 9
464 assert res.responses[0].query == user1.username
465 assert res.responses[0].user.user_id == user1.id
467 assert res.responses[1].query == str(user1.id)
468 assert res.responses[1].user.user_id == user1.id
470 assert res.responses[2].query == "nonexistent"
471 assert res.responses[2].not_found
473 assert res.responses[3].query == str(user2.id)
474 assert res.responses[3].user.user_id == user2.id
476 assert res.responses[4].query == "9994"
477 assert res.responses[4].not_found
479 assert res.responses[5].query == user6.username
480 assert res.responses[5].user.user_id == user6.id
482 assert res.responses[6].query == str(user5.id)
483 assert res.responses[6].user.user_id == user5.id
485 assert res.responses[7].query == "notreal"
486 assert res.responses[7].not_found
488 # blocked - should return ghost profile
489 assert res.responses[8].query == user4.username
490 assert not res.responses[8].not_found
491 assert res.responses[8].user.user_id == user4.id
492 assert res.responses[8].user.is_ghost
493 assert res.responses[8].user.username == "ghost"
494 assert res.responses[8].user.name == "Deactivated Account"
496 with api_session(token1) as api:
497 with pytest.raises(grpc.RpcError) as e:
498 api.GetLiteUsers(api_pb2.GetLiteUsersReq(users=201 * [user1.username]))
499 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
500 assert e.value.details() == "You can't request that many users at a time."
503def test_update_profile(db):
504 user, token = generate_user()
506 with api_session(token) as api:
507 with pytest.raises(grpc.RpcError) as e:
508 api.UpdateProfile(api_pb2.UpdateProfileReq(name=wrappers_pb2.StringValue(value=" ")))
509 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
510 assert e.value.details() == "Name not supported."
512 with pytest.raises(grpc.RpcError) as e:
513 api.UpdateProfile(
514 api_pb2.UpdateProfileReq(lat=wrappers_pb2.DoubleValue(value=0), lng=wrappers_pb2.DoubleValue(value=0))
515 )
516 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
517 assert e.value.details() == "Invalid coordinate."
519 with pytest.raises(grpc.RpcError) as e:
520 api.UpdateProfile(
521 api_pb2.UpdateProfileReq(regions_visited=api_pb2.RepeatedStringValue(value=["United States"]))
522 )
523 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
524 assert e.value.details() == "Invalid region."
526 with pytest.raises(grpc.RpcError) as e:
527 api.UpdateProfile(
528 api_pb2.UpdateProfileReq(regions_lived=api_pb2.RepeatedStringValue(value=["United Kingdom"]))
529 )
530 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
531 assert e.value.details() == "Invalid region."
533 api.UpdateProfile(
534 api_pb2.UpdateProfileReq(
535 name=wrappers_pb2.StringValue(value="New name"),
536 city=wrappers_pb2.StringValue(value="Timbuktu"),
537 hometown=api_pb2.NullableStringValue(value="Walla Walla"),
538 lat=wrappers_pb2.DoubleValue(value=0.01),
539 lng=wrappers_pb2.DoubleValue(value=-2),
540 radius=wrappers_pb2.DoubleValue(value=321),
541 pronouns=api_pb2.NullableStringValue(value="Ro, Robo, Robots"),
542 occupation=api_pb2.NullableStringValue(value="Testing"),
543 education=api_pb2.NullableStringValue(value="Couchers U"),
544 about_me=api_pb2.NullableStringValue(value="I rule"),
545 things_i_like=api_pb2.NullableStringValue(value="Couchers"),
546 about_place=api_pb2.NullableStringValue(value="My place"),
547 hosting_status=api_pb2.HOSTING_STATUS_CAN_HOST,
548 meetup_status=api_pb2.MEETUP_STATUS_WANTS_TO_MEETUP,
549 language_abilities=api_pb2.RepeatedLanguageAbilityValue(
550 value=[
551 api_pb2.LanguageAbility(
552 code="eng",
553 fluency=api_pb2.LanguageAbility.Fluency.FLUENCY_FLUENT,
554 )
555 ],
556 ),
557 regions_visited=api_pb2.RepeatedStringValue(value=["CXR", "FIN"]),
558 regions_lived=api_pb2.RepeatedStringValue(value=["USA", "EST"]),
559 additional_information=api_pb2.NullableStringValue(value="I <3 Couchers"),
560 )
561 )
563 user_details = api.GetUser(api_pb2.GetUserReq(user=user.username))
564 assert user_details.name == "New name"
565 assert user_details.city == "Timbuktu"
566 assert user_details.hometown == "Walla Walla"
567 assert user_details.pronouns == "Ro, Robo, Robots"
568 assert user_details.education == "Couchers U"
569 assert user_details.things_i_like == "Couchers"
570 assert user_details.lat == 0.01
571 assert user_details.lng == -2
572 assert user_details.radius == 321
573 assert user_details.occupation == "Testing"
574 assert user_details.about_me == "I rule"
575 assert user_details.about_place == "My place"
576 assert user_details.hosting_status == api_pb2.HOSTING_STATUS_CAN_HOST
577 assert user_details.meetup_status == api_pb2.MEETUP_STATUS_WANTS_TO_MEETUP
578 assert user_details.language_abilities[0].code == "eng"
579 assert user_details.language_abilities[0].fluency == api_pb2.LanguageAbility.Fluency.FLUENCY_FLUENT
580 assert user_details.additional_information == "I <3 Couchers"
581 assert user_details.regions_visited == ["CXR", "FIN"]
582 assert user_details.regions_lived == ["EST", "USA"]
584 # Test unset values
585 api.UpdateProfile(
586 api_pb2.UpdateProfileReq(
587 hometown=api_pb2.NullableStringValue(is_null=True),
588 radius=wrappers_pb2.DoubleValue(value=0),
589 pronouns=api_pb2.NullableStringValue(is_null=True),
590 occupation=api_pb2.NullableStringValue(is_null=True),
591 education=api_pb2.NullableStringValue(is_null=True),
592 about_me=api_pb2.NullableStringValue(is_null=True),
593 things_i_like=api_pb2.NullableStringValue(is_null=True),
594 about_place=api_pb2.NullableStringValue(is_null=True),
595 hosting_status=api_pb2.HOSTING_STATUS_CAN_HOST,
596 meetup_status=api_pb2.MEETUP_STATUS_WANTS_TO_MEETUP,
597 language_abilities=api_pb2.RepeatedLanguageAbilityValue(value=[]),
598 regions_visited=api_pb2.RepeatedStringValue(value=[]),
599 regions_lived=api_pb2.RepeatedStringValue(value=[]),
600 additional_information=api_pb2.NullableStringValue(is_null=True),
601 )
602 )
604 user_details = api.GetUser(api_pb2.GetUserReq(user=user.username))
605 assert not user_details.hometown
606 assert not user_details.radius
607 assert not user_details.pronouns
608 assert not user_details.occupation
609 assert not user_details.education
610 assert not user_details.about_me
611 assert not user_details.things_i_like
612 assert not user_details.about_place
613 assert user_details.hosting_status == api_pb2.HOSTING_STATUS_CAN_HOST
614 assert user_details.meetup_status == api_pb2.MEETUP_STATUS_WANTS_TO_MEETUP
615 assert not user_details.language_abilities
616 assert not user_details.regions_visited
617 assert not user_details.regions_lived
618 assert not user_details.additional_information
621def test_update_profile_do_not_email(db):
622 user, token = generate_user()
624 with notifications_session(token) as notifications:
625 notifications.SetNotificationSettings(notifications_pb2.SetNotificationSettingsReq(enable_do_not_email=True))
627 with api_session(token) as api:
628 with pytest.raises(grpc.RpcError) as e:
629 api.UpdateProfile(api_pb2.UpdateProfileReq(hosting_status=api_pb2.HOSTING_STATUS_CAN_HOST))
630 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
631 assert e.value.details() == "You cannot enable hosting while you have emails turned off in your settings."
633 with pytest.raises(grpc.RpcError) as e:
634 api.UpdateProfile(api_pb2.UpdateProfileReq(meetup_status=api_pb2.MEETUP_STATUS_OPEN_TO_MEETUP))
635 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
636 assert e.value.details() == "You cannot enable meeting up while you have emails turned off in your settings."
639def test_language_abilities(db):
640 user, token = generate_user(
641 language_abilities=[
642 ("fin", LanguageFluency.fluent),
643 ("fra", LanguageFluency.beginner),
644 ],
645 )
647 with api_session(token) as api:
648 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
649 assert len(res.language_abilities) == 2
651 # can't add non-existent languages
652 with pytest.raises(grpc.RpcError) as err:
653 api.UpdateProfile(
654 api_pb2.UpdateProfileReq(
655 language_abilities=api_pb2.RepeatedLanguageAbilityValue(
656 value=[
657 api_pb2.LanguageAbility(
658 code="QQQ",
659 fluency=api_pb2.LanguageAbility.Fluency.FLUENCY_FLUENT,
660 )
661 ],
662 ),
663 )
664 )
665 assert err.value.code() == grpc.StatusCode.INVALID_ARGUMENT
666 assert err.value.details() == "Invalid language."
668 # can't have multiple languages of the same type
669 with pytest.raises(Exception) as err2:
670 api.UpdateProfile(
671 api_pb2.UpdateProfileReq(
672 language_abilities=api_pb2.RepeatedLanguageAbilityValue(
673 value=[
674 api_pb2.LanguageAbility(
675 code="eng",
676 fluency=api_pb2.LanguageAbility.Fluency.FLUENCY_FLUENT,
677 ),
678 api_pb2.LanguageAbility(
679 code="eng",
680 fluency=api_pb2.LanguageAbility.Fluency.FLUENCY_FLUENT,
681 ),
682 ],
683 ),
684 )
685 )
686 assert "violates unique constraint" in str(err2.value)
688 # nothing changed
689 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
690 assert len(res.language_abilities) == 2
692 # now actually add a value
693 api.UpdateProfile(
694 api_pb2.UpdateProfileReq(
695 language_abilities=api_pb2.RepeatedLanguageAbilityValue(
696 value=[
697 api_pb2.LanguageAbility(
698 code="eng",
699 fluency=api_pb2.LanguageAbility.Fluency.FLUENCY_FLUENT,
700 )
701 ],
702 ),
703 )
704 )
706 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
707 assert len(res.language_abilities) == 1
708 assert res.language_abilities[0].code == "eng"
709 assert res.language_abilities[0].fluency == api_pb2.LanguageAbility.Fluency.FLUENCY_FLUENT
711 # change the value to a new one
712 api.UpdateProfile(
713 api_pb2.UpdateProfileReq(
714 language_abilities=api_pb2.RepeatedLanguageAbilityValue(
715 value=[
716 api_pb2.LanguageAbility(
717 code="fin",
718 fluency=api_pb2.LanguageAbility.Fluency.FLUENCY_BEGINNER,
719 )
720 ],
721 ),
722 )
723 )
725 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
726 assert len(res.language_abilities) == 1
727 assert res.language_abilities[0].code == "fin"
728 assert res.language_abilities[0].fluency == api_pb2.LanguageAbility.Fluency.FLUENCY_BEGINNER
730 # should be able to set to same value still
731 api.UpdateProfile(
732 api_pb2.UpdateProfileReq(
733 language_abilities=api_pb2.RepeatedLanguageAbilityValue(
734 value=[
735 api_pb2.LanguageAbility(
736 code="fin",
737 fluency=api_pb2.LanguageAbility.Fluency.FLUENCY_BEGINNER,
738 )
739 ],
740 ),
741 )
742 )
744 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
745 assert len(res.language_abilities) == 1
746 assert res.language_abilities[0].code == "fin"
747 assert res.language_abilities[0].fluency == api_pb2.LanguageAbility.Fluency.FLUENCY_BEGINNER
749 # don't change it
750 api.UpdateProfile(api_pb2.UpdateProfileReq())
752 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
753 assert len(res.language_abilities) == 1
754 assert res.language_abilities[0].code == "fin"
755 assert res.language_abilities[0].fluency == api_pb2.LanguageAbility.Fluency.FLUENCY_BEGINNER
757 # remove value
758 api.UpdateProfile(
759 api_pb2.UpdateProfileReq(
760 language_abilities=api_pb2.RepeatedLanguageAbilityValue(
761 value=[],
762 ),
763 )
764 )
766 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
767 assert len(res.language_abilities) == 0
770def test_pending_friend_request_count(db, moderator):
771 user1, token1 = generate_user()
772 user2, token2 = generate_user()
773 user3, token3 = generate_user()
775 with api_session(token2) as api:
776 res = api.Ping(api_pb2.PingReq())
777 assert res.pending_friend_request_count == 0
779 with api_session(token1) as api:
780 res = api.Ping(api_pb2.PingReq())
781 assert res.pending_friend_request_count == 0
782 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
783 # Sender can still see their own sent requests (even while SHADOWED)
784 res = api.Ping(api_pb2.PingReq())
785 assert res.pending_friend_request_count == 0
787 # Get friend request ID from sender's view (author can see SHADOWED)
788 with api_session(token1) as api:
789 res = api.ListFriendRequests(empty_pb2.Empty())
790 assert len(res.sent) == 1
791 fr_id = res.sent[0].friend_request_id
793 # Recipient cannot see SHADOWED friend requests before mod approval
794 with api_session(token2) as api:
795 res = api.Ping(api_pb2.PingReq())
796 assert res.pending_friend_request_count == 0
798 # Moderator approves the friend request
799 moderator.approve_friend_request(fr_id)
801 # Now recipient can see the approved friend request
802 with api_session(token2) as api:
803 res = api.Ping(api_pb2.PingReq())
804 assert res.pending_friend_request_count == 1
806 with api_session(token2) as api:
807 # check it's there
808 res = api.ListFriendRequests(empty_pb2.Empty())
809 assert len(res.sent) == 0
810 assert len(res.received) == 1
812 assert res.received[0].state == api_pb2.FriendRequest.FriendRequestStatus.PENDING
813 assert res.received[0].user_id == user1.id
815 fr_id = res.received[0].friend_request_id
817 # accept it
818 api.RespondFriendRequest(api_pb2.RespondFriendRequestReq(friend_request_id=fr_id, accept=True))
820 res = api.Ping(api_pb2.PingReq())
821 assert res.pending_friend_request_count == 0
824def test_friend_request_flow(db, push_collector: PushCollector, moderator):
825 user1, token1 = generate_user(complete_profile=True)
826 user2, token2 = generate_user(complete_profile=True)
827 user3, token3 = generate_user()
829 # send a friend request from user1 to user2
830 with api_session(token1) as api:
831 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
833 with session_scope() as session:
834 friend_request = session.execute(
835 select(FriendRelationship).where(
836 FriendRelationship.from_user_id == user1.id, FriendRelationship.to_user_id == user2.id
837 )
838 ).scalar_one()
839 friend_request_id = friend_request.id
841 # Notification is deferred while content is SHADOWED
842 # No push notification sent yet
843 assert push_collector.count_for_user(user2.id) == 0
845 with api_session(token1) as api:
846 # Sender can see their own sent requests (even while SHADOWED)
847 res = api.ListFriendRequests(empty_pb2.Empty())
848 assert len(res.sent) == 1
849 assert len(res.received) == 0
851 assert res.sent[0].state == api_pb2.FriendRequest.FriendRequestStatus.PENDING
852 assert res.sent[0].user_id == user2.id
853 assert res.sent[0].friend_request_id == friend_request_id
855 # Recipient cannot see SHADOWED friend requests
856 with api_session(token2) as api:
857 res = api.ListFriendRequests(empty_pb2.Empty())
858 assert len(res.sent) == 0
859 assert len(res.received) == 0
861 # Moderator approves the friend request - this triggers the notification
862 with mock_notification_email() as mock:
863 moderator.approve_friend_request(friend_request_id)
865 push = push_collector.pop_for_user(user2.id, last=True)
866 assert push.content.title == f"Friend request from {user1.name}"
867 assert push.content.body == f"{user1.name} wants to be your friend."
869 mock.assert_called_once()
870 e = email_fields(mock)
871 assert e.recipient == user2.email
872 assert e.subject == f"[TEST] {user1.name} wants to be your friend on Couchers.org!"
873 assert user2.name in e.plain
874 assert user2.name in e.html
875 assert user1.name in e.plain
876 assert user1.name in e.html
877 assert "http://localhost:5001/img/thumbnail/" not in e.plain
878 assert "http://localhost:5001/img/thumbnail/" in e.html
879 assert "http://localhost:3000/connections/friends/" in e.plain
880 assert "http://localhost:3000/connections/friends/" in e.html
882 # Now recipient can see the approved friend request
883 with api_session(token2) as api:
884 # check it's there
885 res = api.ListFriendRequests(empty_pb2.Empty())
886 assert len(res.sent) == 0
887 assert len(res.received) == 1
889 assert res.received[0].state == api_pb2.FriendRequest.FriendRequestStatus.PENDING
890 assert res.received[0].user_id == user1.id
892 fr_id = res.received[0].friend_request_id
894 # accept it
895 with mock_notification_email() as mock:
896 api.RespondFriendRequest(api_pb2.RespondFriendRequestReq(friend_request_id=fr_id, accept=True))
898 # check it's gone
899 res = api.ListFriendRequests(empty_pb2.Empty())
900 assert len(res.sent) == 0
901 assert len(res.received) == 0
903 # check we're friends now
904 res = api.ListFriends(empty_pb2.Empty())
905 assert len(res.user_ids) == 1
906 assert res.user_ids[0] == user1.id
908 # user2 got one push (from the friend request creation)
909 # user1 should now have one push (from the friend request acceptance)
910 push = push_collector.pop_for_user(user1.id, last=True)
911 assert push.content.title == f"{user2.name} accepted your friend request"
912 assert push.content.body == f"You are now friends with {user2.name}."
914 mock.assert_called_once()
915 e = email_fields(mock)
916 assert e.recipient == user1.email
917 assert e.subject == f"[TEST] {user2.name} accepted your friend request!"
918 assert user1.name in e.plain
919 assert user1.name in e.html
920 assert user2.name in e.plain
921 assert user2.name in e.html
922 assert "http://localhost:5001/img/thumbnail/" not in e.plain
923 assert "http://localhost:5001/img/thumbnail/" in e.html
924 assert f"http://localhost:3000/user/{user2.username}" in e.plain
925 assert f"http://localhost:3000/user/{user2.username}" in e.html
927 with api_session(token1) as api:
928 # check it's gone
929 res = api.ListFriendRequests(empty_pb2.Empty())
930 assert len(res.sent) == 0
931 assert len(res.received) == 0
933 # check we're friends now
934 res = api.ListFriends(empty_pb2.Empty())
935 assert len(res.user_ids) == 1
936 assert res.user_ids[0] == user2.id
938 with api_session(token1) as api:
939 # we can't unfriend if we aren't friends
940 with pytest.raises(grpc.RpcError) as err:
941 api.RemoveFriend(api_pb2.RemoveFriendReq(user_id=user3.id))
942 assert err.value.code() == grpc.StatusCode.FAILED_PRECONDITION
943 assert err.value.details() == "You aren't friends with that user!"
945 # we can unfriend
946 res = api.RemoveFriend(api_pb2.RemoveFriendReq(user_id=user2.id))
948 res = api.ListFriends(empty_pb2.Empty())
949 assert len(res.user_ids) == 0
952def test_RemoveFriend_regression(db, push_collector: PushCollector, moderator):
953 user1, token1 = generate_user(complete_profile=True)
954 user2, token2 = generate_user(complete_profile=True)
955 user3, token3 = generate_user()
956 user4, token4 = generate_user()
957 user5, token5 = generate_user()
958 user6, token6 = generate_user()
960 # Send friend requests
961 with api_session(token4) as api:
962 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user1.id))
963 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
965 with api_session(token5) as api:
966 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user1.id))
968 with api_session(token1) as api:
969 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
970 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user3.id))
972 # Approve all friend requests via moderation
973 with session_scope() as session:
974 friend_requests = session.execute(select(FriendRelationship)).scalars().all()
975 for fr in friend_requests:
976 moderator.approve_friend_request(fr.id)
978 # Now recipients can respond
979 with api_session(token1) as api:
980 api.RespondFriendRequest(
981 api_pb2.RespondFriendRequestReq(
982 friend_request_id=api.ListFriendRequests(empty_pb2.Empty()).received[0].friend_request_id, accept=True
983 )
984 )
986 with api_session(token2) as api:
987 for fr in api.ListFriendRequests(empty_pb2.Empty()).received:
988 api.RespondFriendRequest(
989 api_pb2.RespondFriendRequestReq(friend_request_id=fr.friend_request_id, accept=True)
990 )
992 with api_session(token1) as api:
993 res = api.ListFriends(empty_pb2.Empty())
994 assert sorted(res.user_ids) == sorted([user2.id, user4.id])
996 api.RemoveFriend(api_pb2.RemoveFriendReq(user_id=user2.id))
998 res = api.ListFriends(empty_pb2.Empty())
999 assert sorted(res.user_ids) == [user4.id]
1001 api.RemoveFriend(api_pb2.RemoveFriendReq(user_id=user4.id))
1003 res = api.ListFriends(empty_pb2.Empty())
1004 assert not res.user_ids
1007def test_cant_friend_request_twice(db):
1008 user1, token1 = generate_user()
1009 user2, token2 = generate_user()
1011 # send friend request from user1 to user2
1012 with api_session(token1) as api:
1013 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
1015 with pytest.raises(grpc.RpcError) as e:
1016 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
1017 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
1018 assert e.value.details() == "You are already friends with or have sent a friend request to that user."
1021def test_cant_friend_request_pending(db):
1022 user1, token1 = generate_user()
1023 user2, token2 = generate_user()
1025 # send friend request from user1 to user2
1026 with api_session(token1) as api:
1027 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
1029 with api_session(token2) as api:
1030 with pytest.raises(grpc.RpcError) as e:
1031 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user1.id))
1032 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
1033 assert e.value.details() == "You are already friends with or have sent a friend request to that user."
1036def test_cant_friend_request_already_friends(db):
1037 user1, token1 = generate_user()
1038 user2, token2 = generate_user()
1039 make_friends(user1, user2)
1041 with api_session(token1) as api:
1042 with pytest.raises(grpc.RpcError) as e:
1043 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
1044 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
1045 assert e.value.details() == "You are already friends with or have sent a friend request to that user."
1047 with api_session(token2) as api:
1048 with pytest.raises(grpc.RpcError) as e:
1049 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user1.id))
1050 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
1051 assert e.value.details() == "You are already friends with or have sent a friend request to that user."
1054def test_excessive_friend_requests_are_reported(db):
1055 """Test that excessive friend requests are first reported in a warning email and finally lead blocking of further requests."""
1056 user, token = generate_user()
1057 rate_limit_definition = RATE_LIMIT_DEFINITIONS[RateLimitAction.friend_request]
1058 with api_session(token) as api:
1059 # Test warning email
1060 with mock_notification_email() as mock_email:
1061 for _ in range(rate_limit_definition.warning_limit):
1062 friend_user, _ = generate_user()
1063 _ = api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=friend_user.id))
1065 assert mock_email.call_count == 0
1066 friend_user, _ = generate_user()
1067 _ = api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=friend_user.id))
1069 assert mock_email.call_count == 1
1070 email = email_fields(mock_email).plain
1071 assert email.startswith(
1072 f"User {user.username} has sent {rate_limit_definition.warning_limit} friend requests in the past {RATE_LIMIT_HOURS} hours."
1073 )
1075 # Test ban after exceeding FRIEND_REQUEST_HARD_LIMIT
1076 with mock_notification_email() as mock_email:
1077 for _ in range(rate_limit_definition.hard_limit - rate_limit_definition.warning_limit - 1):
1078 friend_user, _ = generate_user()
1079 _ = api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=friend_user.id))
1081 assert mock_email.call_count == 0
1082 friend_user, _ = generate_user()
1083 with pytest.raises(grpc.RpcError) as exc_info:
1084 _ = api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=friend_user.id))
1085 assert exc_info.value.code() == grpc.StatusCode.RESOURCE_EXHAUSTED
1086 assert (
1087 exc_info.value.details()
1088 == "You have sent a lot of friend requests in the past 24 hours. To avoid spam, you can't send any more for now."
1089 )
1091 assert mock_email.call_count == 1
1092 email = email_fields(mock_email).plain
1093 assert email.startswith(
1094 f"User {user.username} has sent {rate_limit_definition.hard_limit} friend requests in the past {RATE_LIMIT_HOURS} hours."
1095 )
1096 assert "The user has been blocked from sending further friend requests for now." in email
1099def test_ListFriends(db, moderator):
1100 user1, token1 = generate_user()
1101 user2, token2 = generate_user()
1102 user3, token3 = generate_user()
1104 # send friend request from user1 to user2 and user3
1105 with api_session(token1) as api:
1106 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
1107 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user3.id))
1108 # sender can see their sent requests (they are the author)
1109 res = api.ListFriendRequests(empty_pb2.Empty())
1110 assert len(res.sent) == 2
1111 user1_to_user2_id = [req for req in res.sent if req.user_id == user2.id][0].friend_request_id
1112 user1_to_user3_id = [req for req in res.sent if req.user_id == user3.id][0].friend_request_id
1114 with api_session(token3) as api:
1115 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
1116 res = api.ListFriendRequests(empty_pb2.Empty())
1117 user3_to_user2_id = res.sent[0].friend_request_id
1119 # Moderator approves the friend requests so recipients can see them
1120 moderator.approve_friend_request(user1_to_user2_id)
1121 moderator.approve_friend_request(user3_to_user2_id)
1123 with api_session(token2) as api:
1124 res = api.ListFriendRequests(empty_pb2.Empty())
1125 assert len(res.received) == 2
1127 # order is an implementation detail
1128 user1_req = [req for req in res.received if req.user_id == user1.id][0]
1129 user3_req = [req for req in res.received if req.user_id == user3.id][0]
1131 assert user1_req.state == api_pb2.FriendRequest.FriendRequestStatus.PENDING
1132 assert user1_req.user_id == user1.id
1133 api.RespondFriendRequest(
1134 api_pb2.RespondFriendRequestReq(friend_request_id=user1_req.friend_request_id, accept=True)
1135 )
1137 assert user3_req.state == api_pb2.FriendRequest.FriendRequestStatus.PENDING
1138 assert user3_req.user_id == user3.id
1139 api.RespondFriendRequest(
1140 api_pb2.RespondFriendRequestReq(friend_request_id=user3_req.friend_request_id, accept=True)
1141 )
1143 # check we now have two friends
1144 res = api.ListFriends(empty_pb2.Empty())
1145 assert len(res.user_ids) == 2
1146 assert user1.id in res.user_ids
1147 assert user3.id in res.user_ids
1149 # Moderator approves user1's friend request to user3 so user3 can see it
1150 moderator.approve_friend_request(user1_to_user3_id)
1152 with api_session(token3) as api:
1153 res = api.ListFriends(empty_pb2.Empty())
1154 assert len(res.user_ids) == 1
1155 assert user2.id in res.user_ids
1157 res = api.ListFriendRequests(empty_pb2.Empty())
1158 assert len(res.received) == 1
1159 assert res.received[0].state == api_pb2.FriendRequest.FriendRequestStatus.PENDING
1160 assert res.received[0].user_id == user1.id
1161 fr_id = res.received[0].friend_request_id
1162 api.RespondFriendRequest(api_pb2.RespondFriendRequestReq(friend_request_id=fr_id, accept=True))
1164 res = api.ListFriends(empty_pb2.Empty())
1165 assert len(res.user_ids) == 2
1166 assert user1.id in res.user_ids
1167 assert user2.id in res.user_ids
1169 with api_session(token1) as api:
1170 res = api.ListFriends(empty_pb2.Empty())
1171 assert len(res.user_ids) == 2
1172 assert user2.id in res.user_ids
1173 assert user3.id in res.user_ids
1176def test_ListMutualFriends(db):
1177 user1, token1 = generate_user()
1178 user2, token2 = generate_user()
1179 user3, token3 = generate_user()
1180 user4, token4 = generate_user()
1181 user5, token5 = generate_user()
1183 # arrange friends like this: 1<->2, 1<->3, 1<->4, 1<->5, 3<->2, 3<->4,
1184 # so 1 and 2 should have mutual friend 3 only
1185 make_friends(user1, user2)
1186 make_friends(user1, user3)
1187 make_friends(user1, user4)
1188 make_friends(user1, user5)
1189 make_friends(user3, user2)
1190 make_friends(user3, user4)
1192 with api_session(token1) as api:
1193 mutual_friends = api.ListMutualFriends(api_pb2.ListMutualFriendsReq(user_id=user2.id)).mutual_friends
1194 assert len(mutual_friends) == 1
1195 assert mutual_friends[0].user_id == user3.id
1197 # and other way around same
1198 with api_session(token2) as api:
1199 mutual_friends = api.ListMutualFriends(api_pb2.ListMutualFriendsReq(user_id=user1.id)).mutual_friends
1200 assert len(mutual_friends) == 1
1201 assert mutual_friends[0].user_id == user3.id
1203 # Check pending request doesn't have effect
1204 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user5.id))
1206 mutual_friends = api.ListMutualFriends(api_pb2.ListMutualFriendsReq(user_id=user1.id)).mutual_friends
1207 assert len(mutual_friends) == 1
1208 assert mutual_friends[0].user_id == user3.id
1210 # both ways
1211 with api_session(token1) as api:
1212 mutual_friends = api.ListMutualFriends(api_pb2.ListMutualFriendsReq(user_id=user2.id)).mutual_friends
1213 assert len(mutual_friends) == 1
1214 assert mutual_friends[0].user_id == user3.id
1217def test_mutual_friends_self(db):
1218 user1, token1 = generate_user()
1219 user2, token2 = generate_user()
1220 user3, token3 = generate_user()
1221 user4, token4 = generate_user()
1223 make_friends(user1, user2)
1224 make_friends(user2, user3)
1225 make_friends(user1, user4)
1227 with api_session(token1) as api:
1228 res = api.ListMutualFriends(api_pb2.ListMutualFriendsReq(user_id=user1.id))
1229 assert len(res.mutual_friends) == 0
1231 with api_session(token2) as api:
1232 res = api.ListMutualFriends(api_pb2.ListMutualFriendsReq(user_id=user2.id))
1233 assert len(res.mutual_friends) == 0
1235 with api_session(token3) as api:
1236 res = api.ListMutualFriends(api_pb2.ListMutualFriendsReq(user_id=user3.id))
1237 assert len(res.mutual_friends) == 0
1239 with api_session(token4) as api:
1240 res = api.ListMutualFriends(api_pb2.ListMutualFriendsReq(user_id=user4.id))
1241 assert len(res.mutual_friends) == 0
1244def test_CancelFriendRequest(db):
1245 user1, token1 = generate_user()
1246 user2, token2 = generate_user()
1248 with api_session(token1) as api:
1249 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
1251 res = api.ListFriendRequests(empty_pb2.Empty())
1252 assert res.sent[0].user_id == user2.id
1253 fr_id = res.sent[0].friend_request_id
1255 api.CancelFriendRequest(api_pb2.CancelFriendRequestReq(friend_request_id=fr_id))
1257 # check it's gone
1258 res = api.ListFriendRequests(empty_pb2.Empty())
1259 assert len(res.sent) == 0
1260 assert len(res.received) == 0
1262 # check not friends
1263 res = api.ListFriends(empty_pb2.Empty())
1264 assert len(res.user_ids) == 0
1266 with api_session(token2) as api:
1267 # check it's gone
1268 res = api.ListFriendRequests(empty_pb2.Empty())
1269 assert len(res.sent) == 0
1270 assert len(res.received) == 0
1272 # check we're not friends
1273 res = api.ListFriends(empty_pb2.Empty())
1274 assert len(res.user_ids) == 0
1276 with api_session(token1) as api:
1277 # check we can send another friend req
1278 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
1280 res = api.ListFriendRequests(empty_pb2.Empty())
1281 assert res.sent[0].state == api_pb2.FriendRequest.FriendRequestStatus.PENDING
1282 assert res.sent[0].user_id == user2.id
1285def test_accept_friend_request(db, moderator):
1286 user1, token1 = generate_user()
1287 user2, token2 = generate_user()
1289 with session_scope() as session:
1290 moderation_state = ModerationState(
1291 object_type=ModerationObjectType.friend_request,
1292 object_id=0,
1293 visibility=ModerationVisibility.visible,
1294 )
1295 session.add(moderation_state)
1296 session.flush()
1297 friend_request = FriendRelationship(
1298 from_user_id=user1.id,
1299 to_user_id=user2.id,
1300 status=FriendStatus.pending,
1301 moderation_state_id=moderation_state.id,
1302 )
1303 session.add(friend_request)
1304 session.flush()
1305 moderation_state.object_id = friend_request.id
1306 session.commit()
1307 friend_request_id = friend_request.id
1309 with api_session(token2) as api:
1310 # check request pending
1311 res = api.ListFriendRequests(empty_pb2.Empty())
1312 assert len(res.received) == 1
1313 assert res.received[0].user_id == user1.id
1315 api.RespondFriendRequest(api_pb2.RespondFriendRequestReq(friend_request_id=friend_request_id, accept=True))
1317 # check request is gone
1318 res = api.ListFriendRequests(empty_pb2.Empty())
1319 assert len(res.sent) == 0
1320 assert len(res.received) == 0
1322 # check now friends
1323 res = api.ListFriends(empty_pb2.Empty())
1324 assert len(res.user_ids) == 1
1325 assert res.user_ids[0] == user1.id
1327 with api_session(token1) as api:
1328 # check request gone
1329 res = api.ListFriendRequests(empty_pb2.Empty())
1330 assert len(res.sent) == 0
1331 assert len(res.received) == 0
1333 # check now friends
1334 res = api.ListFriends(empty_pb2.Empty())
1335 assert len(res.user_ids) == 1
1336 assert res.user_ids[0] == user2.id
1339def test_reject_friend_request(db, moderator):
1340 user1, token1 = generate_user()
1341 user2, token2 = generate_user()
1343 with api_session(token1) as api:
1344 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
1346 res = api.ListFriendRequests(empty_pb2.Empty())
1347 assert res.sent[0].state == api_pb2.FriendRequest.FriendRequestStatus.PENDING
1348 assert res.sent[0].user_id == user2.id
1349 fr_id = res.sent[0].friend_request_id
1351 # Moderator approves the friend request so recipient can see it
1352 moderator.approve_friend_request(fr_id)
1354 with api_session(token2) as api:
1355 res = api.ListFriendRequests(empty_pb2.Empty())
1356 assert res.received[0].state == api_pb2.FriendRequest.FriendRequestStatus.PENDING
1357 assert res.received[0].user_id == user1.id
1359 fr_id = res.received[0].friend_request_id
1361 # reject it
1362 api.RespondFriendRequest(api_pb2.RespondFriendRequestReq(friend_request_id=fr_id, accept=False))
1364 # check it's gone
1365 res = api.ListFriendRequests(empty_pb2.Empty())
1366 assert len(res.sent) == 0
1367 assert len(res.received) == 0
1369 # check not friends
1370 res = api.ListFriends(empty_pb2.Empty())
1371 assert len(res.user_ids) == 0
1373 with api_session(token1) as api:
1374 # check it's gone
1375 res = api.ListFriendRequests(empty_pb2.Empty())
1376 assert len(res.sent) == 0
1377 assert len(res.received) == 0
1379 # check we're not friends
1380 res = api.ListFriends(empty_pb2.Empty())
1381 assert len(res.user_ids) == 0
1383 # check we can send another friend req
1384 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
1386 res = api.ListFriendRequests(empty_pb2.Empty())
1387 assert res.sent[0].state == api_pb2.FriendRequest.FriendRequestStatus.PENDING
1388 assert res.sent[0].user_id == user2.id
1391def test_hosting_preferences(db):
1392 user1, token1 = generate_user()
1393 user2, token2 = generate_user()
1395 with api_session(token1) as api:
1396 res = api.GetUser(api_pb2.GetUserReq(user=user1.username))
1397 assert not res.HasField("max_guests")
1398 assert not res.HasField("last_minute")
1399 assert not res.HasField("has_pets")
1400 assert not res.HasField("accepts_pets")
1401 assert not res.HasField("pet_details")
1402 assert not res.HasField("has_kids")
1403 assert not res.HasField("accepts_kids")
1404 assert not res.HasField("kid_details")
1405 assert not res.HasField("has_housemates")
1406 assert not res.HasField("housemate_details")
1407 assert not res.HasField("wheelchair_accessible")
1408 assert res.smoking_allowed == api_pb2.SMOKING_LOCATION_UNKNOWN
1409 assert not res.HasField("smokes_at_home")
1410 assert not res.HasField("drinking_allowed")
1411 assert not res.HasField("drinks_at_home")
1412 assert not res.HasField("other_host_info")
1413 assert res.sleeping_arrangement == api_pb2.SLEEPING_ARRANGEMENT_UNKNOWN
1414 assert not res.HasField("sleeping_details")
1415 assert not res.HasField("area")
1416 assert not res.HasField("house_rules")
1417 assert not res.HasField("parking")
1418 assert res.parking_details == api_pb2.PARKING_DETAILS_UNKNOWN
1419 assert not res.HasField("camping_ok")
1421 api.UpdateProfile(
1422 api_pb2.UpdateProfileReq(
1423 max_guests=api_pb2.NullableUInt32Value(value=3),
1424 last_minute=api_pb2.NullableBoolValue(value=True),
1425 has_pets=api_pb2.NullableBoolValue(value=False),
1426 accepts_pets=api_pb2.NullableBoolValue(value=True),
1427 pet_details=api_pb2.NullableStringValue(value="I love dogs"),
1428 has_kids=api_pb2.NullableBoolValue(value=False),
1429 accepts_kids=api_pb2.NullableBoolValue(value=True),
1430 kid_details=api_pb2.NullableStringValue(value="I hate kids"),
1431 has_housemates=api_pb2.NullableBoolValue(value=False),
1432 housemate_details=api_pb2.NullableStringValue(value="I have 7 housemates"),
1433 wheelchair_accessible=api_pb2.NullableBoolValue(value=True),
1434 smoking_allowed=api_pb2.SMOKING_LOCATION_WINDOW,
1435 area=api_pb2.NullableStringValue(value="area!"),
1436 smokes_at_home=api_pb2.NullableBoolValue(value=False),
1437 drinking_allowed=api_pb2.NullableBoolValue(value=True),
1438 drinks_at_home=api_pb2.NullableBoolValue(value=False),
1439 other_host_info=api_pb2.NullableStringValue(value="I'm pretty swell"),
1440 sleeping_arrangement=api_pb2.SLEEPING_ARRANGEMENT_COMMON,
1441 sleeping_details=api_pb2.NullableStringValue(value="Couch in living room"),
1442 house_rules=api_pb2.NullableStringValue(value="RULES!"),
1443 parking=api_pb2.NullableBoolValue(value=True),
1444 parking_details=api_pb2.PARKING_DETAILS_PAID_ONSITE,
1445 camping_ok=api_pb2.NullableBoolValue(value=False),
1446 )
1447 )
1449 # Use a second user to view the hosting preferences just to check
1450 # that it is public information.
1451 with api_session(token2) as api:
1452 res = api.GetUser(api_pb2.GetUserReq(user=user1.username))
1453 assert res.max_guests.value == 3
1454 assert res.last_minute.value
1455 assert not res.has_pets.value
1456 assert res.accepts_pets.value
1457 assert res.pet_details.value == "I love dogs"
1458 assert not res.has_kids.value
1459 assert res.accepts_kids.value
1460 assert res.kid_details.value == "I hate kids"
1461 assert not res.has_housemates.value
1462 assert res.housemate_details.value == "I have 7 housemates"
1463 assert res.wheelchair_accessible.value
1464 assert res.smoking_allowed == api_pb2.SMOKING_LOCATION_WINDOW
1465 assert not res.smokes_at_home.value
1466 assert res.drinking_allowed.value
1467 assert not res.drinks_at_home.value
1468 assert res.other_host_info.value == "I'm pretty swell"
1469 assert res.sleeping_arrangement == api_pb2.SLEEPING_ARRANGEMENT_COMMON
1470 assert res.sleeping_details.value == "Couch in living room"
1471 assert res.area.value == "area!"
1472 assert res.house_rules.value == "RULES!"
1473 assert res.parking.value
1474 assert res.parking_details == api_pb2.PARKING_DETAILS_PAID_ONSITE
1475 assert not res.camping_ok.value
1477 # test unsetting
1478 with api_session(token1) as api:
1479 api.UpdateProfile(
1480 api_pb2.UpdateProfileReq(
1481 max_guests=api_pb2.NullableUInt32Value(is_null=True),
1482 last_minute=api_pb2.NullableBoolValue(is_null=True),
1483 has_pets=api_pb2.NullableBoolValue(is_null=True),
1484 accepts_pets=api_pb2.NullableBoolValue(is_null=True),
1485 pet_details=api_pb2.NullableStringValue(is_null=True),
1486 has_kids=api_pb2.NullableBoolValue(is_null=True),
1487 accepts_kids=api_pb2.NullableBoolValue(is_null=True),
1488 kid_details=api_pb2.NullableStringValue(is_null=True),
1489 has_housemates=api_pb2.NullableBoolValue(is_null=True),
1490 housemate_details=api_pb2.NullableStringValue(is_null=True),
1491 wheelchair_accessible=api_pb2.NullableBoolValue(is_null=True),
1492 smoking_allowed=api_pb2.SMOKING_LOCATION_UNKNOWN,
1493 area=api_pb2.NullableStringValue(is_null=True),
1494 smokes_at_home=api_pb2.NullableBoolValue(is_null=True),
1495 drinking_allowed=api_pb2.NullableBoolValue(is_null=True),
1496 drinks_at_home=api_pb2.NullableBoolValue(is_null=True),
1497 other_host_info=api_pb2.NullableStringValue(is_null=True),
1498 sleeping_arrangement=api_pb2.SLEEPING_ARRANGEMENT_UNKNOWN,
1499 sleeping_details=api_pb2.NullableStringValue(is_null=True),
1500 house_rules=api_pb2.NullableStringValue(is_null=True),
1501 parking=api_pb2.NullableBoolValue(is_null=True),
1502 parking_details=api_pb2.PARKING_DETAILS_UNKNOWN,
1503 camping_ok=api_pb2.NullableBoolValue(is_null=True),
1504 )
1505 )
1507 res = api.GetUser(api_pb2.GetUserReq(user=user1.username))
1508 assert not res.HasField("max_guests")
1509 assert not res.HasField("last_minute")
1510 assert not res.HasField("has_pets")
1511 assert not res.HasField("accepts_pets")
1512 assert not res.HasField("pet_details")
1513 assert not res.HasField("has_kids")
1514 assert not res.HasField("accepts_kids")
1515 assert not res.HasField("kid_details")
1516 assert not res.HasField("has_housemates")
1517 assert not res.HasField("housemate_details")
1518 assert not res.HasField("wheelchair_accessible")
1519 assert res.smoking_allowed == api_pb2.SMOKING_LOCATION_UNKNOWN
1520 assert not res.HasField("smokes_at_home")
1521 assert not res.HasField("drinking_allowed")
1522 assert not res.HasField("drinks_at_home")
1523 assert not res.HasField("other_host_info")
1524 assert res.sleeping_arrangement == api_pb2.SLEEPING_ARRANGEMENT_UNKNOWN
1525 assert not res.HasField("sleeping_details")
1526 assert not res.HasField("area")
1527 assert not res.HasField("house_rules")
1528 assert not res.HasField("parking")
1529 assert res.parking_details == api_pb2.PARKING_DETAILS_UNKNOWN
1530 assert not res.HasField("camping_ok")
1533def test_badges(db):
1534 user1, _ = generate_user(last_donated=None)
1535 user2, _ = generate_user(last_donated=None)
1536 user3, _ = generate_user(last_donated=None)
1537 user4, token = generate_user(last_donated=None)
1539 update_badges(empty_pb2.Empty())
1541 founder_badge = get_badge_dict()["founder"]
1542 board_member_badge = get_badge_dict()["board_member"]
1544 with api_session(token) as api:
1545 assert api.GetUser(api_pb2.GetUserReq(user=user1.username)).badges == ["founder", "board_member"]
1546 assert api.GetUser(api_pb2.GetUserReq(user=user2.username)).badges == ["founder", "board_member"]
1547 assert api.GetUser(api_pb2.GetUserReq(user=user3.username)).badges == []
1549 assert api.ListBadgeUsers(api_pb2.ListBadgeUsersReq(badge_id=founder_badge.id)).user_ids == [1, 2]
1550 res = api.ListBadgeUsers(api_pb2.ListBadgeUsersReq(badge_id=board_member_badge.id, page_size=1))
1551 assert res.user_ids == [1]
1552 res2 = api.ListBadgeUsers(
1553 api_pb2.ListBadgeUsersReq(badge_id=board_member_badge.id, page_token=res.next_page_token)
1554 )
1555 assert res2.user_ids == [2]
1558@pytest.mark.parametrize("flag", ["deleted_at", "banned_at"])
1559def test_ListBadgeUsers_excludes_ghost_users(db, flag):
1560 """Test that ListBadgeUsers does not return deleted/banned users."""
1561 from couchers.helpers.badges import user_add_badge
1563 user1, token1 = generate_user()
1564 user2, _ = generate_user()
1565 user3, _ = generate_user()
1567 volunteer_badge = get_badge_dict()["volunteer"]
1569 # Give all three users the volunteer badge
1570 with session_scope() as session:
1571 user_add_badge(session, user1.id, "volunteer", do_notify=False)
1572 user_add_badge(session, user2.id, "volunteer", do_notify=False)
1573 user_add_badge(session, user3.id, "volunteer", do_notify=False)
1575 # Verify all three users appear in the badge list
1576 with api_session(token1) as api:
1577 res = api.ListBadgeUsers(api_pb2.ListBadgeUsersReq(badge_id=volunteer_badge.id))
1578 assert set(res.user_ids) == {user1.id, user2.id, user3.id}
1580 # Make user2 invisible (deleted or banned)
1581 with session_scope() as session:
1582 db_user2 = session.execute(select(User).where(User.id == user2.id)).scalar_one()
1583 setattr(db_user2, flag, now())
1585 # Now user2 should not appear in the badge list
1586 with api_session(token1) as api:
1587 res = api.ListBadgeUsers(api_pb2.ListBadgeUsersReq(badge_id=volunteer_badge.id))
1588 assert set(res.user_ids) == {user1.id, user3.id}
1591@pytest.mark.parametrize("flag", ["deleted_at", "banned_at"])
1592def test_GetLiteUser_ghost_user_by_username(db, flag):
1593 """Test that GetLiteUser returns a ghost profile for deleted/banned users when querying by username."""
1594 user1, token1 = generate_user()
1595 user2, _ = generate_user()
1597 # Make user2 invisible
1598 with session_scope() as session:
1599 db_user2 = session.merge(user2)
1600 setattr(db_user2, flag, now())
1601 session.commit()
1603 # Refresh the materialized view
1604 refresh_materialized_views_rapid(empty_pb2.Empty())
1606 with api_session(token1) as api:
1607 # Query by username
1608 lite_user = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user2.username))
1610 assert lite_user.user_id == user2.id
1611 assert lite_user.username == "ghost"
1612 assert lite_user.name == "Deactivated Account"
1613 assert lite_user.lat == 0
1614 assert lite_user.lng == 0
1615 assert lite_user.radius == 0
1616 assert lite_user.city == ""
1617 assert lite_user.age == 0
1618 assert lite_user.avatar_url == ""
1619 assert lite_user.avatar_thumbnail_url == ""
1620 assert not lite_user.has_strong_verification
1623@pytest.mark.parametrize("flag", ["deleted_at", "banned_at"])
1624def test_GetLiteUser_ghost_user_by_id(db, flag):
1625 """Test that GetLiteUser returns a ghost profile for deleted/banned users when querying by ID."""
1626 user1, token1 = generate_user()
1627 user2, _ = generate_user()
1629 # Make user2 invisible
1630 with session_scope() as session:
1631 db_user2 = session.merge(user2)
1632 setattr(db_user2, flag, now())
1633 session.commit()
1635 # Refresh the materialized view
1636 refresh_materialized_views_rapid(empty_pb2.Empty())
1638 with api_session(token1) as api:
1639 # Query by ID
1640 lite_user = api.GetLiteUser(api_pb2.GetLiteUserReq(user=str(user2.id)))
1642 assert lite_user.user_id == user2.id
1643 assert lite_user.username == "ghost"
1644 assert lite_user.name == "Deactivated Account"
1645 assert lite_user.lat == 0
1646 assert lite_user.lng == 0
1647 assert lite_user.radius == 0
1648 assert lite_user.city == ""
1649 assert lite_user.age == 0
1650 assert lite_user.avatar_url == ""
1651 assert lite_user.avatar_thumbnail_url == ""
1652 assert not lite_user.has_strong_verification
1655def test_GetLiteUser_blocked_user(db):
1656 """Test that GetLiteUser returns a ghost profile for blocked users."""
1657 user1, token1 = generate_user()
1658 user2, _ = generate_user()
1660 # User1 blocks user2
1661 make_user_block(user1, user2)
1663 # Refresh the materialized view
1664 refresh_materialized_views_rapid(empty_pb2.Empty())
1666 with api_session(token1) as api:
1667 # Query by username
1668 lite_user = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user2.username))
1670 assert lite_user.user_id == user2.id
1671 assert lite_user.is_ghost
1672 assert lite_user.username == "ghost"
1673 assert lite_user.name == "Deactivated Account"
1675 # Query by ID
1676 lite_user = api.GetLiteUser(api_pb2.GetLiteUserReq(user=str(user2.id)))
1678 assert lite_user.user_id == user2.id
1679 assert lite_user.is_ghost
1680 assert lite_user.username == "ghost"
1681 assert lite_user.name == "Deactivated Account"
1684def test_GetLiteUser_blocking_user(db):
1685 """Test that GetLiteUser returns a ghost profile when the target user has blocked the requester."""
1686 user1, token1 = generate_user()
1687 user2, _ = generate_user()
1689 # User2 blocks user1
1690 make_user_block(user2, user1)
1692 # Refresh the materialized view
1693 refresh_materialized_views_rapid(empty_pb2.Empty())
1695 with api_session(token1) as api:
1696 # Query by username
1697 lite_user = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user2.username))
1699 assert lite_user.user_id == user2.id
1700 assert lite_user.is_ghost
1701 assert lite_user.username == "ghost"
1702 assert lite_user.name == "Deactivated Account"
1704 # Query by ID
1705 lite_user = api.GetLiteUser(api_pb2.GetLiteUserReq(user=str(user2.id)))
1707 assert lite_user.user_id == user2.id
1708 assert lite_user.is_ghost
1709 assert lite_user.username == "ghost"
1710 assert lite_user.name == "Deactivated Account"
1713@pytest.mark.parametrize("flag", ["deleted_at", "banned_at"])
1714def test_GetLiteUsers_ghost_users(db, flag):
1715 """Test that GetLiteUsers returns ghost profiles for deleted/banned users."""
1716 user1, token1 = generate_user()
1717 user2, _ = generate_user()
1718 user3, _ = generate_user()
1719 user4, _ = generate_user()
1721 # Make user2 and user4 invisible
1722 with session_scope() as session:
1723 db_user2 = session.merge(user2)
1724 setattr(db_user2, flag, now())
1725 db_user4 = session.merge(user4)
1726 setattr(db_user4, 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 res = api.GetLiteUsers(
1734 api_pb2.GetLiteUsersReq(
1735 users=[
1736 user1.username, # visible
1737 user2.username, # ghost
1738 str(user3.id), # visible
1739 str(user4.id), # ghost
1740 ]
1741 )
1742 )
1744 assert len(res.responses) == 4
1746 # user1 - visible, normal profile
1747 assert res.responses[0].query == user1.username
1748 assert not res.responses[0].not_found
1749 assert res.responses[0].user.user_id == user1.id
1750 assert res.responses[0].user.username == user1.username
1751 assert res.responses[0].user.name == user1.name
1753 # user2 - ghost by username
1754 assert res.responses[1].query == user2.username
1755 assert not res.responses[1].not_found
1756 assert res.responses[1].user.user_id == user2.id
1757 assert res.responses[1].user.is_ghost
1758 assert res.responses[1].user.username == "ghost"
1759 assert res.responses[1].user.name == "Deactivated Account"
1761 # user3 - visible, normal profile
1762 assert res.responses[2].query == str(user3.id)
1763 assert not res.responses[2].not_found
1764 assert res.responses[2].user.user_id == user3.id
1765 assert res.responses[2].user.username == user3.username
1766 assert res.responses[2].user.name == user3.name
1768 # user4 - ghost by ID
1769 assert res.responses[3].query == str(user4.id)
1770 assert not res.responses[3].not_found
1771 assert res.responses[3].user.user_id == user4.id
1772 assert res.responses[3].user.is_ghost
1773 assert res.responses[3].user.username == "ghost"
1774 assert res.responses[3].user.name == "Deactivated Account"
1777def test_GetLiteUsers_blocked_users(db):
1778 """Test that GetLiteUsers returns ghost profiles for blocked users."""
1779 user1, token1 = generate_user()
1780 user2, _ = generate_user()
1781 user3, _ = generate_user()
1782 user4, _ = generate_user()
1783 user5, _ = generate_user()
1785 # User1 blocks user2
1786 make_user_block(user1, user2)
1787 # User4 blocks user1
1788 make_user_block(user4, user1)
1790 # Refresh the materialized view
1791 refresh_materialized_views_rapid(empty_pb2.Empty())
1793 with api_session(token1) as api:
1794 res = api.GetLiteUsers(
1795 api_pb2.GetLiteUsersReq(
1796 users=[
1797 user2.username, # user1 blocked user2
1798 str(user3.id), # visible
1799 user4.username, # user4 blocked user1
1800 str(user5.id), # visible
1801 ]
1802 )
1803 )
1805 assert len(res.responses) == 4
1807 # user2 - blocked by user1, should be ghost
1808 assert res.responses[0].query == user2.username
1809 assert not res.responses[0].not_found
1810 assert res.responses[0].user.user_id == user2.id
1811 assert res.responses[0].user.is_ghost
1812 assert res.responses[0].user.username == "ghost"
1813 assert res.responses[0].user.name == "Deactivated Account"
1815 # user3 - visible
1816 assert res.responses[1].query == str(user3.id)
1817 assert not res.responses[1].not_found
1818 assert res.responses[1].user.user_id == user3.id
1819 assert res.responses[1].user.username == user3.username
1821 # user4 - user4 blocked user1, should be ghost
1822 assert res.responses[2].query == user4.username
1823 assert not res.responses[2].not_found
1824 assert res.responses[2].user.user_id == user4.id
1825 assert res.responses[2].user.is_ghost
1826 assert res.responses[2].user.username == "ghost"
1827 assert res.responses[2].user.name == "Deactivated Account"
1829 # user5 - visible
1830 assert res.responses[3].query == str(user5.id)
1831 assert not res.responses[3].not_found
1832 assert res.responses[3].user.user_id == user5.id
1833 assert res.responses[3].user.username == user5.username
1836@pytest.mark.parametrize("flag", ["deleted_at", "banned_at"])
1837def test_GetUser_ghost_user_by_id(db, flag):
1838 """Test that GetUser returns a ghost profile for deleted/banned users when querying by ID."""
1839 user1, token1 = generate_user()
1840 user2, _ = generate_user()
1842 # Make user2 invisible
1843 with session_scope() as session:
1844 db_user2 = session.merge(user2)
1845 setattr(db_user2, flag, now())
1846 session.commit()
1848 with api_session(token1) as api:
1849 # Query by ID
1850 user_pb = api.GetUser(api_pb2.GetUserReq(user=str(user2.id)))
1852 assert user_pb.user_id == user2.id
1853 assert user_pb.username == "ghost"
1854 assert user_pb.name == "Deactivated Account"
1855 assert user_pb.city == ""
1856 assert user_pb.hosting_status == 0
1857 assert user_pb.meetup_status == 0
1860def test_GetUser_blocked_user(db):
1861 """Test that GetUser returns a ghost profile for blocked users."""
1862 user1, token1 = generate_user()
1863 user2, _ = generate_user()
1865 # User1 blocks user2
1866 make_user_block(user1, user2)
1868 with api_session(token1) as api:
1869 # Query by username
1870 user_pb = api.GetUser(api_pb2.GetUserReq(user=user2.username))
1872 assert user_pb.user_id == user2.id
1873 assert user_pb.username == "ghost"
1874 assert user_pb.name == "Deactivated Account"
1876 # Query by ID
1877 user_pb = api.GetUser(api_pb2.GetUserReq(user=str(user2.id)))
1879 assert user_pb.user_id == user2.id
1880 assert user_pb.username == "ghost"
1881 assert user_pb.name == "Deactivated Account"
1884def test_GetUser_blocking_user(db):
1885 """Test that GetUser returns a ghost profile when the target user has blocked the requester."""
1886 user1, token1 = generate_user()
1887 user2, _ = generate_user()
1889 # User2 blocks user1
1890 make_user_block(user2, user1)
1892 with api_session(token1) as api:
1893 # Query by username
1894 user_pb = api.GetUser(api_pb2.GetUserReq(user=user2.username))
1896 assert user_pb.user_id == user2.id
1897 assert user_pb.username == "ghost"
1898 assert user_pb.name == "Deactivated Account"
1900 # Query by ID
1901 user_pb = api.GetUser(api_pb2.GetUserReq(user=str(user2.id)))
1903 assert user_pb.user_id == user2.id
1904 assert user_pb.username == "ghost"
1905 assert user_pb.name == "Deactivated Account"