Coverage for src/tests/test_api.py: 100%
1087 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-12-20 11:53 +0000
« prev ^ index » next coverage.py v7.11.0, created at 2025-12-20 11:53 +0000
1from datetime import timedelta
3import grpc
4import pytest
5from google.protobuf import empty_pb2, wrappers_pb2
6from sqlalchemy import update
8from couchers.db import session_scope
9from couchers.jobs.handlers import update_badges
10from couchers.materialized_views import refresh_materialized_views_rapid
11from couchers.models import FriendRelationship, FriendStatus, LanguageFluency, RateLimitAction, User
12from couchers.proto import admin_pb2, api_pb2, blocking_pb2, jail_pb2, notifications_pb2
13from couchers.rate_limits.definitions import RATE_LIMIT_DEFINITIONS, RATE_LIMIT_HOURS
14from couchers.resources import get_badge_dict
15from couchers.sql import couchers_select as select
16from couchers.utils import create_coordinate, to_aware_datetime
17from tests.test_fixtures import ( # noqa
18 api_session,
19 blocking_session,
20 db,
21 email_fields,
22 generate_user,
23 make_friends,
24 make_user_block,
25 make_user_invisible,
26 mock_notification_email,
27 notifications_session,
28 push_collector,
29 real_api_session,
30 real_jail_session,
31 testconfig,
32)
33from tests.test_fixtures import real_admin_session as admin_session
36@pytest.fixture(autouse=True)
37def _(testconfig):
38 pass
41def test_ping(db):
42 user, token = generate_user(
43 regions_lived=["ESP", "FRA", "EST"],
44 regions_visited=["CHE", "REU", "FIN"],
45 language_abilities=[
46 ("fin", LanguageFluency.fluent),
47 ("fra", LanguageFluency.beginner),
48 ],
49 )
51 with real_api_session(token) as api:
52 res = api.Ping(api_pb2.PingReq())
54 assert res.user.user_id == user.id
55 assert res.user.username == user.username
56 assert res.user.name == user.name
57 assert res.user.city == user.city
58 assert res.user.hometown == user.hometown
59 assert res.user.verification == 0.0
60 assert res.user.community_standing == user.community_standing
61 assert res.user.num_references == 0
62 assert res.user.gender == user.gender
63 assert res.user.pronouns == user.pronouns
64 assert res.user.age == user.age
66 assert (res.user.lat, res.user.lng) == (user.coordinates or (0, 0))
68 # the joined time is fuzzed
69 # but shouldn't be before actual joined time, or more than one hour behind
70 assert user.joined - timedelta(hours=1) <= to_aware_datetime(res.user.joined) <= user.joined
71 # same for last_active
72 assert user.last_active - timedelta(hours=1) <= to_aware_datetime(res.user.last_active) <= user.last_active
74 assert res.user.hosting_status == api_pb2.HOSTING_STATUS_CANT_HOST
75 assert res.user.meetup_status == api_pb2.MEETUP_STATUS_OPEN_TO_MEETUP
77 assert res.user.occupation == user.occupation
78 assert res.user.education == user.education
79 assert res.user.about_me == user.about_me
80 assert res.user.things_i_like == user.things_i_like
81 assert {language_ability.code for language_ability in res.user.language_abilities} == {"fin", "fra"}
82 assert res.user.about_place == user.about_place
83 assert res.user.regions_visited == ["FIN", "REU", "CHE"] # Tests alphabetization by region name
84 assert res.user.regions_lived == ["EST", "FRA", "ESP"] # Ditto
85 assert res.user.additional_information == user.additional_information
87 assert res.user.friends == api_pb2.User.FriendshipStatus.NA
88 assert not res.user.HasField("pending_friend_request")
91def test_coords(db):
92 # make them need to update location
93 user1, token1 = generate_user(geom=create_coordinate(1, 0), geom_radius=2000, needs_to_update_location=True)
94 user2, token2 = generate_user()
96 with api_session(token2) as api:
97 res = api.Ping(api_pb2.PingReq())
98 assert res.user.city == user2.city
99 lat, lng = user2.coordinates or (0, 0)
100 assert res.user.lat == lat
101 assert res.user.lng == lng
102 assert res.user.radius == user2.geom_radius
104 with api_session(token2) as api:
105 res = api.GetUser(api_pb2.GetUserReq(user=user1.username))
106 assert res.city == user1.city
107 assert res.lat == 1.0
108 assert res.lng == 0.0
109 assert res.radius == 2000.0
111 # Check coordinate wrapping
112 user3, token3 = generate_user(geom=create_coordinate(40.0, -180.5))
113 user4, token4 = generate_user(geom=create_coordinate(40.0, 20.0))
114 user5, token5 = generate_user(geom=create_coordinate(90.5, 20.0))
116 with api_session(token3) as api:
117 res = api.GetUser(api_pb2.GetUserReq(user=user3.username))
118 assert res.lat == 40.0
119 assert res.lng == 179.5
121 with api_session(token4) as api:
122 res = api.GetUser(api_pb2.GetUserReq(user=user4.username))
123 assert res.lat == 40.0
124 assert res.lng == 20.0
126 # PostGIS does not wrap longitude for latitude overflow
127 with api_session(token5) as api:
128 res = api.GetUser(api_pb2.GetUserReq(user=user5.username))
129 assert res.lat == 89.5
130 assert res.lng == 20.0
132 with real_jail_session(token1) as jail:
133 res = jail.JailInfo(empty_pb2.Empty())
134 assert res.jailed
135 assert res.needs_to_update_location
137 res = jail.SetLocation(
138 jail_pb2.SetLocationReq(
139 city="New York City",
140 lat=40.7812,
141 lng=-73.9647,
142 radius=250,
143 )
144 )
146 assert not res.jailed
147 assert not res.needs_to_update_location
149 res = jail.JailInfo(empty_pb2.Empty())
150 assert not res.jailed
151 assert not res.needs_to_update_location
153 with api_session(token2) as api:
154 res = api.GetUser(api_pb2.GetUserReq(user=user1.username))
155 assert res.city == "New York City"
156 assert res.lat == 40.7812
157 assert res.lng == -73.9647
158 assert res.radius == 250
161def test_get_user(db):
162 user1, token1 = generate_user()
163 user2, token2 = generate_user()
165 with api_session(token1) as api:
166 res = api.GetUser(api_pb2.GetUserReq(user=user2.username))
167 assert res.user_id == user2.id
168 assert res.username == user2.username
169 assert res.name == user2.name
171 with api_session(token1) as api:
172 res = api.GetUser(api_pb2.GetUserReq(user=str(user2.id)))
173 assert res.user_id == user2.id
174 assert res.username == user2.username
175 assert res.name == user2.name
178@pytest.mark.parametrize("flag", ["is_deleted", "is_banned"])
179def test_user_model_to_pb_ghost_user(db, flag):
180 user1, token1 = generate_user()
181 user2, _ = generate_user()
183 with session_scope() as session:
184 session.execute(update(User).where(User.id == user2.id).values(**{flag: True}))
186 refresh_materialized_views_rapid(None)
188 with api_session(token1) as api:
189 user_pb = api.GetUser(api_pb2.GetUserReq(user=user2.username))
191 assert user_pb.user_id == user2.id
192 assert user_pb.is_ghost
193 assert user_pb.username == "ghost"
194 assert user_pb.name == "Ghost"
195 assert (
196 user_pb.about_me
197 == "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."
198 )
200 assert user_pb.lat == 0
201 assert user_pb.lng == 0
202 assert user_pb.radius == 0
203 assert user_pb.verification == 0.0
204 assert user_pb.community_standing == 0.0
205 assert user_pb.num_references == 0
206 assert user_pb.age == 0
207 assert user_pb.hosting_status == 0
208 assert user_pb.meetup_status == 0
209 assert user_pb.city == ""
210 assert user_pb.hometown == ""
211 assert user_pb.timezone == ""
212 assert user_pb.gender == ""
213 assert user_pb.pronouns == ""
214 assert user_pb.occupation == ""
215 assert user_pb.education == ""
216 assert user_pb.things_i_like == ""
217 assert user_pb.about_place == ""
218 assert user_pb.additional_information == ""
219 assert list(user_pb.language_abilities) == []
220 assert list(user_pb.regions_visited) == []
221 assert list(user_pb.regions_lived) == []
222 assert list(user_pb.badges) == []
223 assert user_pb.friends == api_pb2.User.FriendshipStatus.NOT_FRIENDS
224 assert user_pb.avatar_url == ""
225 assert user_pb.avatar_thumbnail_url == ""
226 assert not user_pb.has_strong_verification
228 with api_session(token1) as api:
229 lite_user_pb = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user2.username))
231 assert lite_user_pb.user_id == user2.id
232 assert lite_user_pb.is_ghost
233 assert lite_user_pb.username == "ghost"
234 assert lite_user_pb.name == "Ghost"
235 assert lite_user_pb.city == ""
236 assert lite_user_pb.age == 0
237 assert lite_user_pb.avatar_url == ""
238 assert lite_user_pb.avatar_thumbnail_url == ""
239 assert lite_user_pb.lat == 0
240 assert lite_user_pb.lng == 0
241 assert lite_user_pb.radius == 0
242 assert not lite_user_pb.has_strong_verification
245def test_user_model_to_pb_ghost_user_blocked(db):
246 user1, token1 = generate_user()
247 user2, _ = generate_user()
249 with blocking_session(token1) as user_blocks:
250 user_blocks.BlockUser(blocking_pb2.BlockUserReq(username=user2.username))
252 refresh_materialized_views_rapid(None)
254 with api_session(token1) as api:
255 user_pb = api.GetUser(api_pb2.GetUserReq(user=user2.username))
257 assert user_pb.user_id == user2.id
258 assert user_pb.is_ghost
259 assert user_pb.username == "ghost"
260 assert user_pb.name == "Ghost"
261 assert (
262 user_pb.about_me
263 == "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."
264 )
266 assert user_pb.lat == 0
267 assert user_pb.lng == 0
268 assert user_pb.radius == 0
269 assert user_pb.verification == 0.0
270 assert user_pb.community_standing == 0.0
271 assert user_pb.num_references == 0
272 assert user_pb.age == 0
273 assert user_pb.hosting_status == 0
274 assert user_pb.meetup_status == 0
275 assert user_pb.city == ""
276 assert user_pb.hometown == ""
277 assert user_pb.timezone == ""
278 assert user_pb.gender == ""
279 assert user_pb.pronouns == ""
280 assert user_pb.occupation == ""
281 assert user_pb.education == ""
282 assert user_pb.things_i_like == ""
283 assert user_pb.about_place == ""
284 assert user_pb.additional_information == ""
285 assert list(user_pb.language_abilities) == []
286 assert list(user_pb.regions_visited) == []
287 assert list(user_pb.regions_lived) == []
288 assert list(user_pb.badges) == []
289 assert user_pb.friends == api_pb2.User.FriendshipStatus.NOT_FRIENDS
290 assert user_pb.avatar_url == ""
291 assert user_pb.avatar_thumbnail_url == ""
292 assert not user_pb.has_strong_verification
294 with api_session(token1) as api:
295 lite_user_pb = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user2.username))
297 assert lite_user_pb.user_id == user2.id
298 assert lite_user_pb.is_ghost
299 assert lite_user_pb.username == "ghost"
300 assert lite_user_pb.name == "Ghost"
301 assert lite_user_pb.city == ""
302 assert lite_user_pb.age == 0
303 assert lite_user_pb.avatar_url == ""
304 assert lite_user_pb.avatar_thumbnail_url == ""
305 assert lite_user_pb.lat == 0
306 assert lite_user_pb.lng == 0
307 assert lite_user_pb.radius == 0
308 assert not lite_user_pb.has_strong_verification
311@pytest.mark.parametrize("flag", ["is_deleted", "is_banned"])
312def test_admin_viewing_ghost_users_sees_full_profile(db, flag):
313 admin, token_admin = generate_user(is_superuser=True)
314 user, _ = generate_user()
316 with session_scope() as session:
317 session.execute(update(User).where(User.id == user.id).values(**{flag: True}))
319 with admin_session(token_admin) as api:
320 user_pb = api.GetUser(admin_pb2.GetUserReq(user=user.username))
322 assert user_pb.user_id == user.id
323 assert user_pb.username == user.username
324 assert user_pb.name == user.name
325 assert user_pb.city == user.city
326 assert user_pb.name != "Ghost"
327 assert user_pb.username != "ghost"
328 assert user_pb.hosting_status in (
329 api_pb2.HOSTING_STATUS_UNKNOWN,
330 api_pb2.HOSTING_STATUS_CAN_HOST,
331 api_pb2.HOSTING_STATUS_MAYBE,
332 api_pb2.HOSTING_STATUS_CANT_HOST,
333 )
336def test_lite_coords(db):
337 # make them need to update location
338 user1, token1 = generate_user(geom=create_coordinate(0, 0), geom_radius=0, needs_to_update_location=True)
339 user2, token2 = generate_user()
341 refresh_materialized_views_rapid(None)
343 with api_session(token2) as api:
344 res = api.Ping(api_pb2.PingReq())
345 assert res.user.city == user2.city
346 lat, lng = user2.coordinates or (0, 0)
347 assert res.user.lat == lat
348 assert res.user.lng == lng
349 assert res.user.radius == user2.geom_radius
351 with api_session(token2) as api:
352 res = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user1.username))
353 assert res.city == user1.city
354 assert res.lat == 0.0
355 assert res.lng == 0.0
356 assert res.radius == 0.0
358 # Check coordinate wrapping
359 user3, token3 = generate_user(geom=create_coordinate(40.0, -180.5))
360 user4, token4 = generate_user(geom=create_coordinate(40.0, 20.0))
361 user5, token5 = generate_user(geom=create_coordinate(90.5, 20.0))
363 refresh_materialized_views_rapid(None)
365 with api_session(token3) as api:
366 res = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user3.username))
367 assert res.lat == 40.0
368 assert res.lng == 179.5
370 with api_session(token4) as api:
371 res = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user4.username))
372 assert res.lat == 40.0
373 assert res.lng == 20.0
375 # PostGIS does not wrap longitude for latitude overflow
376 with api_session(token5) as api:
377 res = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user5.username))
378 assert res.lat == 89.5
379 assert res.lng == 20.0
381 with real_jail_session(token1) as jail:
382 res = jail.JailInfo(empty_pb2.Empty())
383 assert res.jailed
384 assert res.needs_to_update_location
386 res = jail.SetLocation(
387 jail_pb2.SetLocationReq(
388 city="New York City",
389 lat=40.7812,
390 lng=-73.9647,
391 radius=250,
392 )
393 )
395 assert not res.jailed
396 assert not res.needs_to_update_location
398 res = jail.JailInfo(empty_pb2.Empty())
399 assert not res.jailed
400 assert not res.needs_to_update_location
402 refresh_materialized_views_rapid(None)
404 with api_session(token2) as api:
405 res = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user1.username))
406 assert res.city == "New York City"
407 assert res.lat == 40.7812
408 assert res.lng == -73.9647
409 assert res.radius == 250
412def test_lite_get_user(db):
413 user1, token1 = generate_user()
414 user2, token2 = generate_user()
416 refresh_materialized_views_rapid(None)
418 with api_session(token1) as api:
419 res = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user2.username))
420 assert res.user_id == user2.id
421 assert res.username == user2.username
422 assert res.name == user2.name
424 with api_session(token1) as api:
425 res = api.GetLiteUser(api_pb2.GetLiteUserReq(user=str(user2.id)))
426 assert res.user_id == user2.id
427 assert res.username == user2.username
428 assert res.name == user2.name
431def test_GetLiteUsers(db):
432 user1, token1 = generate_user()
433 user2, _ = generate_user()
434 user3, _ = generate_user()
435 user4, _ = generate_user()
436 user5, _ = generate_user()
437 user6, _ = generate_user()
439 make_user_block(user4, user1)
441 refresh_materialized_views_rapid(None)
443 with api_session(token1) as api:
444 res = api.GetLiteUsers(
445 api_pb2.GetLiteUsersReq(
446 users=[
447 user1.username,
448 str(user1.id),
449 "nonexistent",
450 str(user2.id),
451 "9994",
452 user6.username,
453 str(user5.id),
454 "notreal",
455 user4.username,
456 ]
457 )
458 )
460 assert len(res.responses) == 9
461 assert res.responses[0].query == user1.username
462 assert res.responses[0].user.user_id == user1.id
464 assert res.responses[1].query == str(user1.id)
465 assert res.responses[1].user.user_id == user1.id
467 assert res.responses[2].query == "nonexistent"
468 assert res.responses[2].not_found
470 assert res.responses[3].query == str(user2.id)
471 assert res.responses[3].user.user_id == user2.id
473 assert res.responses[4].query == "9994"
474 assert res.responses[4].not_found
476 assert res.responses[5].query == user6.username
477 assert res.responses[5].user.user_id == user6.id
479 assert res.responses[6].query == str(user5.id)
480 assert res.responses[6].user.user_id == user5.id
482 assert res.responses[7].query == "notreal"
483 assert res.responses[7].not_found
485 # blocked - should return ghost profile
486 assert res.responses[8].query == user4.username
487 assert not res.responses[8].not_found
488 assert res.responses[8].user.user_id == user4.id
489 assert res.responses[8].user.is_ghost
490 assert res.responses[8].user.username == "ghost"
491 assert res.responses[8].user.name == "Ghost"
493 with api_session(token1) as api:
494 with pytest.raises(grpc.RpcError) as e:
495 api.GetLiteUsers(api_pb2.GetLiteUsersReq(users=201 * [user1.username]))
496 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
497 assert e.value.details() == "You can't request that many users at a time."
500def test_update_profile(db):
501 user, token = generate_user()
503 with api_session(token) as api:
504 with pytest.raises(grpc.RpcError) as e:
505 api.UpdateProfile(api_pb2.UpdateProfileReq(name=wrappers_pb2.StringValue(value=" ")))
506 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
507 assert e.value.details() == "Name not supported."
509 with pytest.raises(grpc.RpcError) as e:
510 api.UpdateProfile(
511 api_pb2.UpdateProfileReq(lat=wrappers_pb2.DoubleValue(value=0), lng=wrappers_pb2.DoubleValue(value=0))
512 )
513 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
514 assert e.value.details() == "Invalid coordinate."
516 with pytest.raises(grpc.RpcError) as e:
517 api.UpdateProfile(
518 api_pb2.UpdateProfileReq(regions_visited=api_pb2.RepeatedStringValue(value=["United States"]))
519 )
520 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
521 assert e.value.details() == "Invalid region."
523 with pytest.raises(grpc.RpcError) as e:
524 api.UpdateProfile(
525 api_pb2.UpdateProfileReq(regions_lived=api_pb2.RepeatedStringValue(value=["United Kingdom"]))
526 )
527 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
528 assert e.value.details() == "Invalid region."
530 api.UpdateProfile(
531 api_pb2.UpdateProfileReq(
532 name=wrappers_pb2.StringValue(value="New name"),
533 city=wrappers_pb2.StringValue(value="Timbuktu"),
534 hometown=api_pb2.NullableStringValue(value="Walla Walla"),
535 lat=wrappers_pb2.DoubleValue(value=0.01),
536 lng=wrappers_pb2.DoubleValue(value=-2),
537 radius=wrappers_pb2.DoubleValue(value=321),
538 pronouns=api_pb2.NullableStringValue(value="Ro, Robo, Robots"),
539 occupation=api_pb2.NullableStringValue(value="Testing"),
540 education=api_pb2.NullableStringValue(value="Couchers U"),
541 about_me=api_pb2.NullableStringValue(value="I rule"),
542 things_i_like=api_pb2.NullableStringValue(value="Couchers"),
543 about_place=api_pb2.NullableStringValue(value="My place"),
544 hosting_status=api_pb2.HOSTING_STATUS_CAN_HOST,
545 meetup_status=api_pb2.MEETUP_STATUS_WANTS_TO_MEETUP,
546 language_abilities=api_pb2.RepeatedLanguageAbilityValue(
547 value=[
548 api_pb2.LanguageAbility(
549 code="eng",
550 fluency=api_pb2.LanguageAbility.Fluency.FLUENCY_FLUENT,
551 )
552 ],
553 ),
554 regions_visited=api_pb2.RepeatedStringValue(value=["CXR", "FIN"]),
555 regions_lived=api_pb2.RepeatedStringValue(value=["USA", "EST"]),
556 additional_information=api_pb2.NullableStringValue(value="I <3 Couchers"),
557 )
558 )
560 user_details = api.GetUser(api_pb2.GetUserReq(user=user.username))
561 assert user_details.name == "New name"
562 assert user_details.city == "Timbuktu"
563 assert user_details.hometown == "Walla Walla"
564 assert user_details.pronouns == "Ro, Robo, Robots"
565 assert user_details.education == "Couchers U"
566 assert user_details.things_i_like == "Couchers"
567 assert user_details.lat == 0.01
568 assert user_details.lng == -2
569 assert user_details.radius == 321
570 assert user_details.occupation == "Testing"
571 assert user_details.about_me == "I rule"
572 assert user_details.about_place == "My place"
573 assert user_details.hosting_status == api_pb2.HOSTING_STATUS_CAN_HOST
574 assert user_details.meetup_status == api_pb2.MEETUP_STATUS_WANTS_TO_MEETUP
575 assert user_details.language_abilities[0].code == "eng"
576 assert user_details.language_abilities[0].fluency == api_pb2.LanguageAbility.Fluency.FLUENCY_FLUENT
577 assert user_details.additional_information == "I <3 Couchers"
578 assert user_details.regions_visited == ["CXR", "FIN"]
579 assert user_details.regions_lived == ["EST", "USA"]
581 # Test unset values
582 api.UpdateProfile(
583 api_pb2.UpdateProfileReq(
584 hometown=api_pb2.NullableStringValue(is_null=True),
585 radius=wrappers_pb2.DoubleValue(value=0),
586 pronouns=api_pb2.NullableStringValue(is_null=True),
587 occupation=api_pb2.NullableStringValue(is_null=True),
588 education=api_pb2.NullableStringValue(is_null=True),
589 about_me=api_pb2.NullableStringValue(is_null=True),
590 things_i_like=api_pb2.NullableStringValue(is_null=True),
591 about_place=api_pb2.NullableStringValue(is_null=True),
592 hosting_status=api_pb2.HOSTING_STATUS_CAN_HOST,
593 meetup_status=api_pb2.MEETUP_STATUS_WANTS_TO_MEETUP,
594 language_abilities=api_pb2.RepeatedLanguageAbilityValue(value=[]),
595 regions_visited=api_pb2.RepeatedStringValue(value=[]),
596 regions_lived=api_pb2.RepeatedStringValue(value=[]),
597 additional_information=api_pb2.NullableStringValue(is_null=True),
598 )
599 )
601 user_details = api.GetUser(api_pb2.GetUserReq(user=user.username))
602 assert not user_details.hometown
603 assert not user_details.radius
604 assert not user_details.pronouns
605 assert not user_details.occupation
606 assert not user_details.education
607 assert not user_details.about_me
608 assert not user_details.things_i_like
609 assert not user_details.about_place
610 assert user_details.hosting_status == api_pb2.HOSTING_STATUS_CAN_HOST
611 assert user_details.meetup_status == api_pb2.MEETUP_STATUS_WANTS_TO_MEETUP
612 assert not user_details.language_abilities
613 assert not user_details.regions_visited
614 assert not user_details.regions_lived
615 assert not user_details.additional_information
618def test_update_profile_do_not_email(db):
619 user, token = generate_user()
621 with notifications_session(token) as notifications:
622 notifications.SetNotificationSettings(notifications_pb2.SetNotificationSettingsReq(enable_do_not_email=True))
624 with api_session(token) as api:
625 with pytest.raises(grpc.RpcError) as e:
626 api.UpdateProfile(api_pb2.UpdateProfileReq(hosting_status=api_pb2.HOSTING_STATUS_CAN_HOST))
627 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
628 assert e.value.details() == "You cannot enable hosting while you have emails turned off in your settings."
630 with pytest.raises(grpc.RpcError) as e:
631 api.UpdateProfile(api_pb2.UpdateProfileReq(meetup_status=api_pb2.MEETUP_STATUS_OPEN_TO_MEETUP))
632 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
633 assert e.value.details() == "You cannot enable meeting up while you have emails turned off in your settings."
636def test_language_abilities(db):
637 user, token = generate_user(
638 language_abilities=[
639 ("fin", LanguageFluency.fluent),
640 ("fra", LanguageFluency.beginner),
641 ],
642 )
644 with api_session(token) as api:
645 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
646 assert len(res.language_abilities) == 2
648 # can't add non-existent languages
649 with pytest.raises(grpc.RpcError) as e:
650 res = api.UpdateProfile(
651 api_pb2.UpdateProfileReq(
652 language_abilities=api_pb2.RepeatedLanguageAbilityValue(
653 value=[
654 api_pb2.LanguageAbility(
655 code="QQQ",
656 fluency=api_pb2.LanguageAbility.Fluency.FLUENCY_FLUENT,
657 )
658 ],
659 ),
660 )
661 )
662 assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
663 assert e.value.details() == "Invalid language."
665 # can't have multiple languages of the same type
666 with pytest.raises(Exception) as e:
667 res = api.UpdateProfile(
668 api_pb2.UpdateProfileReq(
669 language_abilities=api_pb2.RepeatedLanguageAbilityValue(
670 value=[
671 api_pb2.LanguageAbility(
672 code="eng",
673 fluency=api_pb2.LanguageAbility.Fluency.FLUENCY_FLUENT,
674 ),
675 api_pb2.LanguageAbility(
676 code="eng",
677 fluency=api_pb2.LanguageAbility.Fluency.FLUENCY_FLUENT,
678 ),
679 ],
680 ),
681 )
682 )
683 assert "violates unique constraint" in str(e.value)
685 # nothing changed
686 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
687 assert len(res.language_abilities) == 2
689 # now actually add a value
690 api.UpdateProfile(
691 api_pb2.UpdateProfileReq(
692 language_abilities=api_pb2.RepeatedLanguageAbilityValue(
693 value=[
694 api_pb2.LanguageAbility(
695 code="eng",
696 fluency=api_pb2.LanguageAbility.Fluency.FLUENCY_FLUENT,
697 )
698 ],
699 ),
700 )
701 )
703 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
704 assert len(res.language_abilities) == 1
705 assert res.language_abilities[0].code == "eng"
706 assert res.language_abilities[0].fluency == api_pb2.LanguageAbility.Fluency.FLUENCY_FLUENT
708 # change the value to a new one
709 api.UpdateProfile(
710 api_pb2.UpdateProfileReq(
711 language_abilities=api_pb2.RepeatedLanguageAbilityValue(
712 value=[
713 api_pb2.LanguageAbility(
714 code="fin",
715 fluency=api_pb2.LanguageAbility.Fluency.FLUENCY_BEGINNER,
716 )
717 ],
718 ),
719 )
720 )
722 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
723 assert len(res.language_abilities) == 1
724 assert res.language_abilities[0].code == "fin"
725 assert res.language_abilities[0].fluency == api_pb2.LanguageAbility.Fluency.FLUENCY_BEGINNER
727 # should be able to set to same value still
728 api.UpdateProfile(
729 api_pb2.UpdateProfileReq(
730 language_abilities=api_pb2.RepeatedLanguageAbilityValue(
731 value=[
732 api_pb2.LanguageAbility(
733 code="fin",
734 fluency=api_pb2.LanguageAbility.Fluency.FLUENCY_BEGINNER,
735 )
736 ],
737 ),
738 )
739 )
741 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
742 assert len(res.language_abilities) == 1
743 assert res.language_abilities[0].code == "fin"
744 assert res.language_abilities[0].fluency == api_pb2.LanguageAbility.Fluency.FLUENCY_BEGINNER
746 # don't change it
747 api.UpdateProfile(api_pb2.UpdateProfileReq())
749 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
750 assert len(res.language_abilities) == 1
751 assert res.language_abilities[0].code == "fin"
752 assert res.language_abilities[0].fluency == api_pb2.LanguageAbility.Fluency.FLUENCY_BEGINNER
754 # remove value
755 api.UpdateProfile(
756 api_pb2.UpdateProfileReq(
757 language_abilities=api_pb2.RepeatedLanguageAbilityValue(
758 value=[],
759 ),
760 )
761 )
763 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
764 assert len(res.language_abilities) == 0
767def test_pending_friend_request_count(db):
768 user1, token1 = generate_user()
769 user2, token2 = generate_user()
770 user3, token3 = generate_user()
772 with api_session(token2) as api:
773 res = api.Ping(api_pb2.PingReq())
774 assert res.pending_friend_request_count == 0
776 with api_session(token1) as api:
777 res = api.Ping(api_pb2.PingReq())
778 assert res.pending_friend_request_count == 0
779 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
780 res = api.Ping(api_pb2.PingReq())
781 assert res.pending_friend_request_count == 0
783 with api_session(token2) as api:
784 res = api.Ping(api_pb2.PingReq())
785 assert res.pending_friend_request_count == 1
787 with api_session(token2) as api:
788 # check it's there
789 res = api.ListFriendRequests(empty_pb2.Empty())
790 assert len(res.sent) == 0
791 assert len(res.received) == 1
793 assert res.received[0].state == api_pb2.FriendRequest.FriendRequestStatus.PENDING
794 assert res.received[0].user_id == user1.id
796 fr_id = res.received[0].friend_request_id
798 # accept it
799 api.RespondFriendRequest(api_pb2.RespondFriendRequestReq(friend_request_id=fr_id, accept=True))
801 res = api.Ping(api_pb2.PingReq())
802 assert res.pending_friend_request_count == 0
805def test_friend_request_flow(db, push_collector):
806 user1, token1 = generate_user(complete_profile=True)
807 user2, token2 = generate_user(complete_profile=True)
808 user3, token3 = generate_user()
810 # send a friend request from user1 to user2
811 with mock_notification_email() as mock:
812 with api_session(token1) as api:
813 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
815 push_collector.assert_user_has_single_matching(
816 user2.id,
817 title=f"{user1.name} wants to be your friend",
818 body=f"You've received a friend request from {user1.name}",
819 )
821 mock.assert_called_once()
822 e = email_fields(mock)
823 assert e.recipient == user2.email
824 assert e.subject == f"[TEST] {user1.name} wants to be your friend on Couchers.org!"
825 assert user2.name in e.plain
826 assert user2.name in e.html
827 assert user1.name in e.plain
828 assert user1.name in e.html
829 assert "http://localhost:5001/img/thumbnail/" not in e.plain
830 assert "http://localhost:5001/img/thumbnail/" in e.html
831 assert "http://localhost:3000/connections/friends/" in e.plain
832 assert "http://localhost:3000/connections/friends/" in e.html
834 with session_scope() as session:
835 friend_request_id = (
836 session.execute(
837 select(FriendRelationship).where(
838 FriendRelationship.from_user_id == user1.id and FriendRelationship.to_user_id == user2.id
839 )
840 ).scalar_one_or_none()
841 ).id
843 with api_session(token1) as api:
844 # check it went through
845 res = api.ListFriendRequests(empty_pb2.Empty())
846 assert len(res.sent) == 1
847 assert len(res.received) == 0
849 assert res.sent[0].state == api_pb2.FriendRequest.FriendRequestStatus.PENDING
850 assert res.sent[0].user_id == user2.id
851 assert res.sent[0].friend_request_id == friend_request_id
853 with api_session(token2) as api:
854 # check it's there
855 res = api.ListFriendRequests(empty_pb2.Empty())
856 assert len(res.sent) == 0
857 assert len(res.received) == 1
859 assert res.received[0].state == api_pb2.FriendRequest.FriendRequestStatus.PENDING
860 assert res.received[0].user_id == user1.id
862 fr_id = res.received[0].friend_request_id
864 # accept it
865 with mock_notification_email() as mock:
866 api.RespondFriendRequest(api_pb2.RespondFriendRequestReq(friend_request_id=fr_id, accept=True))
868 # check it's gone
869 res = api.ListFriendRequests(empty_pb2.Empty())
870 assert len(res.sent) == 0
871 assert len(res.received) == 0
873 # check we're friends now
874 res = api.ListFriends(empty_pb2.Empty())
875 assert len(res.user_ids) == 1
876 assert res.user_ids[0] == user1.id
878 push_collector.assert_user_has_count(user2.id, 1)
879 push_collector.assert_user_push_matches_fields(
880 user1.id,
881 title=f"{user2.name} accepted your friend request!",
882 body=f"{user2.name} has accepted your friend request",
883 )
885 mock.assert_called_once()
886 e = email_fields(mock)
887 assert e.recipient == user1.email
888 assert e.subject == f"[TEST] {user2.name} accepted your friend request!"
889 assert user1.name in e.plain
890 assert user1.name in e.html
891 assert user2.name in e.plain
892 assert user2.name in e.html
893 assert "http://localhost:5001/img/thumbnail/" not in e.plain
894 assert "http://localhost:5001/img/thumbnail/" in e.html
895 assert f"http://localhost:3000/user/{user2.username}" in e.plain
896 assert f"http://localhost:3000/user/{user2.username}" in e.html
898 with api_session(token1) as api:
899 # check it's gone
900 res = api.ListFriendRequests(empty_pb2.Empty())
901 assert len(res.sent) == 0
902 assert len(res.received) == 0
904 # check we're friends now
905 res = api.ListFriends(empty_pb2.Empty())
906 assert len(res.user_ids) == 1
907 assert res.user_ids[0] == user2.id
909 with api_session(token1) as api:
910 # we can't unfriend if we aren't friends
911 with pytest.raises(grpc.RpcError) as e:
912 api.RemoveFriend(api_pb2.RemoveFriendReq(user_id=user3.id))
913 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
914 assert e.value.details() == "You aren't friends with that user!"
916 # we can unfriend
917 res = api.RemoveFriend(api_pb2.RemoveFriendReq(user_id=user2.id))
919 res = api.ListFriends(empty_pb2.Empty())
920 assert len(res.user_ids) == 0
923def test_RemoveFriend_regression(db, push_collector):
924 user1, token1 = generate_user(complete_profile=True)
925 user2, token2 = generate_user(complete_profile=True)
926 user3, token3 = generate_user()
927 user4, token4 = generate_user()
928 user5, token5 = generate_user()
929 user6, token6 = generate_user()
931 with api_session(token4) as api:
932 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user1.id))
933 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
935 with api_session(token5) as api:
936 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user1.id))
938 with api_session(token1) as api:
939 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
940 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user3.id))
942 api.RespondFriendRequest(
943 api_pb2.RespondFriendRequestReq(
944 friend_request_id=api.ListFriendRequests(empty_pb2.Empty()).received[0].friend_request_id, accept=True
945 )
946 )
948 with api_session(token2) as api:
949 for fr in api.ListFriendRequests(empty_pb2.Empty()).received:
950 api.RespondFriendRequest(
951 api_pb2.RespondFriendRequestReq(friend_request_id=fr.friend_request_id, accept=True)
952 )
954 with api_session(token1) as api:
955 res = api.ListFriends(empty_pb2.Empty())
956 assert sorted(res.user_ids) == [2, 4]
958 api.RemoveFriend(api_pb2.RemoveFriendReq(user_id=user2.id))
960 res = api.ListFriends(empty_pb2.Empty())
961 assert sorted(res.user_ids) == [4]
963 api.RemoveFriend(api_pb2.RemoveFriendReq(user_id=user4.id))
965 res = api.ListFriends(empty_pb2.Empty())
966 assert not res.user_ids
969def test_cant_friend_request_twice(db):
970 user1, token1 = generate_user()
971 user2, token2 = generate_user()
973 # send friend request from user1 to user2
974 with api_session(token1) as api:
975 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
977 with pytest.raises(grpc.RpcError) as e:
978 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
979 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
980 assert e.value.details() == "You are already friends with or have sent a friend request to that user."
983def test_cant_friend_request_pending(db):
984 user1, token1 = generate_user()
985 user2, token2 = generate_user()
987 # send friend request from user1 to user2
988 with api_session(token1) as api:
989 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
991 with api_session(token2) as api:
992 with pytest.raises(grpc.RpcError) as e:
993 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user1.id))
994 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
995 assert e.value.details() == "You are already friends with or have sent a friend request to that user."
998def test_cant_friend_request_already_friends(db):
999 user1, token1 = generate_user()
1000 user2, token2 = generate_user()
1001 make_friends(user1, user2)
1003 with api_session(token1) as api:
1004 with pytest.raises(grpc.RpcError) as e:
1005 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
1006 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
1007 assert e.value.details() == "You are already friends with or have sent a friend request to that user."
1009 with api_session(token2) as api:
1010 with pytest.raises(grpc.RpcError) as e:
1011 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user1.id))
1012 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
1013 assert e.value.details() == "You are already friends with or have sent a friend request to that user."
1016def test_excessive_friend_requests_are_reported(db):
1017 """Test that excessive friend requests are first reported in a warning email and finally lead blocking of further requests."""
1018 user, token = generate_user()
1019 rate_limit_definition = RATE_LIMIT_DEFINITIONS[RateLimitAction.friend_request]
1020 with api_session(token) as api:
1021 # Test warning email
1022 with mock_notification_email() as mock_email:
1023 for _ in range(rate_limit_definition.warning_limit):
1024 friend_user, _ = generate_user()
1025 _ = api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=friend_user.id))
1027 assert mock_email.call_count == 0
1028 friend_user, _ = generate_user()
1029 _ = api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=friend_user.id))
1031 assert mock_email.call_count == 1
1032 email = mock_email.mock_calls[0].kwargs["plain"]
1033 assert email.startswith(
1034 f"User {user.username} has sent {rate_limit_definition.warning_limit} friend requests in the past {RATE_LIMIT_HOURS} hours."
1035 )
1037 # Test ban after exceeding FRIEND_REQUEST_HARD_LIMIT
1038 with mock_notification_email() as mock_email:
1039 for _ in range(rate_limit_definition.hard_limit - rate_limit_definition.warning_limit - 1):
1040 friend_user, _ = generate_user()
1041 _ = api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=friend_user.id))
1043 assert mock_email.call_count == 0
1044 friend_user, _ = generate_user()
1045 with pytest.raises(grpc.RpcError) as exc_info:
1046 _ = api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=friend_user.id))
1047 assert exc_info.value.code() == grpc.StatusCode.RESOURCE_EXHAUSTED
1048 assert (
1049 exc_info.value.details()
1050 == "You have sent a lot of friend requests in the past 24 hours. To avoid spam, you can't send any more for now."
1051 )
1053 assert mock_email.call_count == 1
1054 email = mock_email.mock_calls[0].kwargs["plain"]
1055 assert email.startswith(
1056 f"User {user.username} has sent {rate_limit_definition.hard_limit} friend requests in the past {RATE_LIMIT_HOURS} hours."
1057 )
1058 assert "The user has been blocked from sending further friend requests for now." in email
1061def test_ListFriends(db):
1062 user1, token1 = generate_user()
1063 user2, token2 = generate_user()
1064 user3, token3 = generate_user()
1066 # send friend request from user1 to user2 and user3
1067 with api_session(token1) as api:
1068 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
1069 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user3.id))
1071 with api_session(token3) as api:
1072 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
1074 with api_session(token2) as api:
1075 res = api.ListFriendRequests(empty_pb2.Empty())
1076 assert len(res.received) == 2
1078 # order is an implementation detail
1079 user1_req = [req for req in res.received if req.user_id == user1.id][0]
1080 user3_req = [req for req in res.received if req.user_id == user3.id][0]
1082 assert user1_req.state == api_pb2.FriendRequest.FriendRequestStatus.PENDING
1083 assert user1_req.user_id == user1.id
1084 api.RespondFriendRequest(
1085 api_pb2.RespondFriendRequestReq(friend_request_id=user1_req.friend_request_id, accept=True)
1086 )
1088 assert user3_req.state == api_pb2.FriendRequest.FriendRequestStatus.PENDING
1089 assert user3_req.user_id == user3.id
1090 api.RespondFriendRequest(
1091 api_pb2.RespondFriendRequestReq(friend_request_id=user3_req.friend_request_id, accept=True)
1092 )
1094 # check we now have two friends
1095 res = api.ListFriends(empty_pb2.Empty())
1096 assert len(res.user_ids) == 2
1097 assert user1.id in res.user_ids
1098 assert user3.id in res.user_ids
1100 with api_session(token3) as api:
1101 res = api.ListFriends(empty_pb2.Empty())
1102 assert len(res.user_ids) == 1
1103 assert user2.id in res.user_ids
1105 res = api.ListFriendRequests(empty_pb2.Empty())
1106 assert len(res.received) == 1
1107 assert res.received[0].state == api_pb2.FriendRequest.FriendRequestStatus.PENDING
1108 assert res.received[0].user_id == user1.id
1109 fr_id = res.received[0].friend_request_id
1110 api.RespondFriendRequest(api_pb2.RespondFriendRequestReq(friend_request_id=fr_id, accept=True))
1112 res = api.ListFriends(empty_pb2.Empty())
1113 assert len(res.user_ids) == 2
1114 assert user1.id in res.user_ids
1115 assert user2.id in res.user_ids
1117 with api_session(token1) as api:
1118 res = api.ListFriends(empty_pb2.Empty())
1119 assert len(res.user_ids) == 2
1120 assert user2.id in res.user_ids
1121 assert user3.id in res.user_ids
1124def test_ListMutualFriends(db):
1125 user1, token1 = generate_user()
1126 user2, token2 = generate_user()
1127 user3, token3 = generate_user()
1128 user4, token4 = generate_user()
1129 user5, token5 = generate_user()
1131 # arrange friends like this: 1<->2, 1<->3, 1<->4, 1<->5, 3<->2, 3<->4,
1132 # so 1 and 2 should have mutual friend 3 only
1133 make_friends(user1, user2)
1134 make_friends(user1, user3)
1135 make_friends(user1, user4)
1136 make_friends(user1, user5)
1137 make_friends(user3, user2)
1138 make_friends(user3, user4)
1140 with api_session(token1) as api:
1141 mutual_friends = api.ListMutualFriends(api_pb2.ListMutualFriendsReq(user_id=user2.id)).mutual_friends
1142 assert len(mutual_friends) == 1
1143 assert mutual_friends[0].user_id == user3.id
1145 # and other way around same
1146 with api_session(token2) as api:
1147 mutual_friends = api.ListMutualFriends(api_pb2.ListMutualFriendsReq(user_id=user1.id)).mutual_friends
1148 assert len(mutual_friends) == 1
1149 assert mutual_friends[0].user_id == user3.id
1151 # Check pending request doesn't have effect
1152 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user5.id))
1154 mutual_friends = api.ListMutualFriends(api_pb2.ListMutualFriendsReq(user_id=user1.id)).mutual_friends
1155 assert len(mutual_friends) == 1
1156 assert mutual_friends[0].user_id == user3.id
1158 # both ways
1159 with api_session(token1) as api:
1160 mutual_friends = api.ListMutualFriends(api_pb2.ListMutualFriendsReq(user_id=user2.id)).mutual_friends
1161 assert len(mutual_friends) == 1
1162 assert mutual_friends[0].user_id == user3.id
1165def test_mutual_friends_self(db):
1166 user1, token1 = generate_user()
1167 user2, token2 = generate_user()
1168 user3, token3 = generate_user()
1169 user4, token4 = generate_user()
1171 make_friends(user1, user2)
1172 make_friends(user2, user3)
1173 make_friends(user1, user4)
1175 with api_session(token1) as api:
1176 res = api.ListMutualFriends(api_pb2.ListMutualFriendsReq(user_id=user1.id))
1177 assert len(res.mutual_friends) == 0
1179 with api_session(token2) as api:
1180 res = api.ListMutualFriends(api_pb2.ListMutualFriendsReq(user_id=user2.id))
1181 assert len(res.mutual_friends) == 0
1183 with api_session(token3) as api:
1184 res = api.ListMutualFriends(api_pb2.ListMutualFriendsReq(user_id=user3.id))
1185 assert len(res.mutual_friends) == 0
1187 with api_session(token4) as api:
1188 res = api.ListMutualFriends(api_pb2.ListMutualFriendsReq(user_id=user4.id))
1189 assert len(res.mutual_friends) == 0
1192def test_CancelFriendRequest(db):
1193 user1, token1 = generate_user()
1194 user2, token2 = generate_user()
1196 with api_session(token1) as api:
1197 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
1199 res = api.ListFriendRequests(empty_pb2.Empty())
1200 assert res.sent[0].user_id == user2.id
1201 fr_id = res.sent[0].friend_request_id
1203 api.CancelFriendRequest(api_pb2.CancelFriendRequestReq(friend_request_id=fr_id))
1205 # check it's gone
1206 res = api.ListFriendRequests(empty_pb2.Empty())
1207 assert len(res.sent) == 0
1208 assert len(res.received) == 0
1210 # check not friends
1211 res = api.ListFriends(empty_pb2.Empty())
1212 assert len(res.user_ids) == 0
1214 with api_session(token2) as api:
1215 # check it's gone
1216 res = api.ListFriendRequests(empty_pb2.Empty())
1217 assert len(res.sent) == 0
1218 assert len(res.received) == 0
1220 # check we're not friends
1221 res = api.ListFriends(empty_pb2.Empty())
1222 assert len(res.user_ids) == 0
1224 with api_session(token1) as api:
1225 # check we can send another friend req
1226 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
1228 res = api.ListFriendRequests(empty_pb2.Empty())
1229 assert res.sent[0].state == api_pb2.FriendRequest.FriendRequestStatus.PENDING
1230 assert res.sent[0].user_id == user2.id
1233def test_accept_friend_request(db):
1234 user1, token1 = generate_user()
1235 user2, token2 = generate_user()
1237 with session_scope() as session:
1238 friend_request = FriendRelationship(from_user_id=user1.id, to_user_id=user2.id, status=FriendStatus.pending)
1239 session.add(friend_request)
1240 session.commit()
1241 friend_request_id = friend_request.id
1243 with api_session(token2) as api:
1244 # check request pending
1245 res = api.ListFriendRequests(empty_pb2.Empty())
1246 assert len(res.received) == 1
1247 assert res.received[0].user_id == user1.id
1249 api.RespondFriendRequest(api_pb2.RespondFriendRequestReq(friend_request_id=friend_request_id, accept=True))
1251 # check request is gone
1252 res = api.ListFriendRequests(empty_pb2.Empty())
1253 assert len(res.sent) == 0
1254 assert len(res.received) == 0
1256 # check now friends
1257 res = api.ListFriends(empty_pb2.Empty())
1258 assert len(res.user_ids) == 1
1259 assert res.user_ids[0] == user1.id
1261 with api_session(token1) as api:
1262 # check request gone
1263 res = api.ListFriendRequests(empty_pb2.Empty())
1264 assert len(res.sent) == 0
1265 assert len(res.received) == 0
1267 # check now friends
1268 res = api.ListFriends(empty_pb2.Empty())
1269 assert len(res.user_ids) == 1
1270 assert res.user_ids[0] == user2.id
1273def test_reject_friend_request(db):
1274 user1, token1 = generate_user()
1275 user2, token2 = generate_user()
1277 with api_session(token1) as api:
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
1284 with api_session(token2) as api:
1285 res = api.ListFriendRequests(empty_pb2.Empty())
1286 assert res.received[0].state == api_pb2.FriendRequest.FriendRequestStatus.PENDING
1287 assert res.received[0].user_id == user1.id
1289 fr_id = res.received[0].friend_request_id
1291 # reject it
1292 api.RespondFriendRequest(api_pb2.RespondFriendRequestReq(friend_request_id=fr_id, accept=False))
1294 # check it's gone
1295 res = api.ListFriendRequests(empty_pb2.Empty())
1296 assert len(res.sent) == 0
1297 assert len(res.received) == 0
1299 # check not friends
1300 res = api.ListFriends(empty_pb2.Empty())
1301 assert len(res.user_ids) == 0
1303 with api_session(token1) as api:
1304 # check it's gone
1305 res = api.ListFriendRequests(empty_pb2.Empty())
1306 assert len(res.sent) == 0
1307 assert len(res.received) == 0
1309 # check we're not friends
1310 res = api.ListFriends(empty_pb2.Empty())
1311 assert len(res.user_ids) == 0
1313 # check we can send another friend req
1314 api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=user2.id))
1316 res = api.ListFriendRequests(empty_pb2.Empty())
1317 assert res.sent[0].state == api_pb2.FriendRequest.FriendRequestStatus.PENDING
1318 assert res.sent[0].user_id == user2.id
1321def test_hosting_preferences(db):
1322 user1, token1 = generate_user()
1323 user2, token2 = generate_user()
1325 with api_session(token1) as api:
1326 res = api.GetUser(api_pb2.GetUserReq(user=user1.username))
1327 assert not res.HasField("max_guests")
1328 assert not res.HasField("last_minute")
1329 assert not res.HasField("has_pets")
1330 assert not res.HasField("accepts_pets")
1331 assert not res.HasField("pet_details")
1332 assert not res.HasField("has_kids")
1333 assert not res.HasField("accepts_kids")
1334 assert not res.HasField("kid_details")
1335 assert not res.HasField("has_housemates")
1336 assert not res.HasField("housemate_details")
1337 assert not res.HasField("wheelchair_accessible")
1338 assert res.smoking_allowed == api_pb2.SMOKING_LOCATION_UNKNOWN
1339 assert not res.HasField("smokes_at_home")
1340 assert not res.HasField("drinking_allowed")
1341 assert not res.HasField("drinks_at_home")
1342 assert not res.HasField("other_host_info")
1343 assert res.sleeping_arrangement == api_pb2.SLEEPING_ARRANGEMENT_UNKNOWN
1344 assert not res.HasField("sleeping_details")
1345 assert not res.HasField("area")
1346 assert not res.HasField("house_rules")
1347 assert not res.HasField("parking")
1348 assert res.parking_details == api_pb2.PARKING_DETAILS_UNKNOWN
1349 assert not res.HasField("camping_ok")
1351 api.UpdateProfile(
1352 api_pb2.UpdateProfileReq(
1353 max_guests=api_pb2.NullableUInt32Value(value=3),
1354 last_minute=api_pb2.NullableBoolValue(value=True),
1355 has_pets=api_pb2.NullableBoolValue(value=False),
1356 accepts_pets=api_pb2.NullableBoolValue(value=True),
1357 pet_details=api_pb2.NullableStringValue(value="I love dogs"),
1358 has_kids=api_pb2.NullableBoolValue(value=False),
1359 accepts_kids=api_pb2.NullableBoolValue(value=True),
1360 kid_details=api_pb2.NullableStringValue(value="I hate kids"),
1361 has_housemates=api_pb2.NullableBoolValue(value=False),
1362 housemate_details=api_pb2.NullableStringValue(value="I have 7 housemates"),
1363 wheelchair_accessible=api_pb2.NullableBoolValue(value=True),
1364 smoking_allowed=api_pb2.SMOKING_LOCATION_WINDOW,
1365 area=api_pb2.NullableStringValue(value="area!"),
1366 smokes_at_home=api_pb2.NullableBoolValue(value=False),
1367 drinking_allowed=api_pb2.NullableBoolValue(value=True),
1368 drinks_at_home=api_pb2.NullableBoolValue(value=False),
1369 other_host_info=api_pb2.NullableStringValue(value="I'm pretty swell"),
1370 sleeping_arrangement=api_pb2.SLEEPING_ARRANGEMENT_COMMON,
1371 sleeping_details=api_pb2.NullableStringValue(value="Couch in living room"),
1372 house_rules=api_pb2.NullableStringValue(value="RULES!"),
1373 parking=api_pb2.NullableBoolValue(value=True),
1374 parking_details=api_pb2.PARKING_DETAILS_PAID_ONSITE,
1375 camping_ok=api_pb2.NullableBoolValue(value=False),
1376 )
1377 )
1379 # Use a second user to view the hosting preferences just to check
1380 # that it is public information.
1381 with api_session(token2) as api:
1382 res = api.GetUser(api_pb2.GetUserReq(user=user1.username))
1383 assert res.max_guests.value == 3
1384 assert res.last_minute.value
1385 assert not res.has_pets.value
1386 assert res.accepts_pets.value
1387 assert res.pet_details.value == "I love dogs"
1388 assert not res.has_kids.value
1389 assert res.accepts_kids.value
1390 assert res.kid_details.value == "I hate kids"
1391 assert not res.has_housemates.value
1392 assert res.housemate_details.value == "I have 7 housemates"
1393 assert res.wheelchair_accessible.value
1394 assert res.smoking_allowed == api_pb2.SMOKING_LOCATION_WINDOW
1395 assert not res.smokes_at_home.value
1396 assert res.drinking_allowed.value
1397 assert not res.drinks_at_home.value
1398 assert res.other_host_info.value == "I'm pretty swell"
1399 assert res.sleeping_arrangement == api_pb2.SLEEPING_ARRANGEMENT_COMMON
1400 assert res.sleeping_details.value == "Couch in living room"
1401 assert res.area.value == "area!"
1402 assert res.house_rules.value == "RULES!"
1403 assert res.parking.value
1404 assert res.parking_details == api_pb2.PARKING_DETAILS_PAID_ONSITE
1405 assert not res.camping_ok.value
1407 # test unsetting
1408 with api_session(token1) as api:
1409 api.UpdateProfile(
1410 api_pb2.UpdateProfileReq(
1411 max_guests=api_pb2.NullableUInt32Value(is_null=True),
1412 last_minute=api_pb2.NullableBoolValue(is_null=True),
1413 has_pets=api_pb2.NullableBoolValue(is_null=True),
1414 accepts_pets=api_pb2.NullableBoolValue(is_null=True),
1415 pet_details=api_pb2.NullableStringValue(is_null=True),
1416 has_kids=api_pb2.NullableBoolValue(is_null=True),
1417 accepts_kids=api_pb2.NullableBoolValue(is_null=True),
1418 kid_details=api_pb2.NullableStringValue(is_null=True),
1419 has_housemates=api_pb2.NullableBoolValue(is_null=True),
1420 housemate_details=api_pb2.NullableStringValue(is_null=True),
1421 wheelchair_accessible=api_pb2.NullableBoolValue(is_null=True),
1422 smoking_allowed=api_pb2.SMOKING_LOCATION_UNKNOWN,
1423 area=api_pb2.NullableStringValue(is_null=True),
1424 smokes_at_home=api_pb2.NullableBoolValue(is_null=True),
1425 drinking_allowed=api_pb2.NullableBoolValue(is_null=True),
1426 drinks_at_home=api_pb2.NullableBoolValue(is_null=True),
1427 other_host_info=api_pb2.NullableStringValue(is_null=True),
1428 sleeping_arrangement=api_pb2.SLEEPING_ARRANGEMENT_UNKNOWN,
1429 sleeping_details=api_pb2.NullableStringValue(is_null=True),
1430 house_rules=api_pb2.NullableStringValue(is_null=True),
1431 parking=api_pb2.NullableBoolValue(is_null=True),
1432 parking_details=api_pb2.PARKING_DETAILS_UNKNOWN,
1433 camping_ok=api_pb2.NullableBoolValue(is_null=True),
1434 )
1435 )
1437 res = api.GetUser(api_pb2.GetUserReq(user=user1.username))
1438 assert not res.HasField("max_guests")
1439 assert not res.HasField("last_minute")
1440 assert not res.HasField("has_pets")
1441 assert not res.HasField("accepts_pets")
1442 assert not res.HasField("pet_details")
1443 assert not res.HasField("has_kids")
1444 assert not res.HasField("accepts_kids")
1445 assert not res.HasField("kid_details")
1446 assert not res.HasField("has_housemates")
1447 assert not res.HasField("housemate_details")
1448 assert not res.HasField("wheelchair_accessible")
1449 assert res.smoking_allowed == api_pb2.SMOKING_LOCATION_UNKNOWN
1450 assert not res.HasField("smokes_at_home")
1451 assert not res.HasField("drinking_allowed")
1452 assert not res.HasField("drinks_at_home")
1453 assert not res.HasField("other_host_info")
1454 assert res.sleeping_arrangement == api_pb2.SLEEPING_ARRANGEMENT_UNKNOWN
1455 assert not res.HasField("sleeping_details")
1456 assert not res.HasField("area")
1457 assert not res.HasField("house_rules")
1458 assert not res.HasField("parking")
1459 assert res.parking_details == api_pb2.PARKING_DETAILS_UNKNOWN
1460 assert not res.HasField("camping_ok")
1463def test_badges(db):
1464 user1, _ = generate_user(last_donated=None)
1465 user2, _ = generate_user(last_donated=None)
1466 user3, _ = generate_user(last_donated=None)
1467 user4, token = generate_user(last_donated=None)
1469 update_badges(empty_pb2.Empty())
1471 founder_badge = get_badge_dict()["founder"]
1472 board_member_badge = get_badge_dict()["board_member"]
1474 with api_session(token) as api:
1475 assert api.GetUser(api_pb2.GetUserReq(user=user1.username)).badges == ["founder", "board_member"]
1476 assert api.GetUser(api_pb2.GetUserReq(user=user2.username)).badges == ["founder", "board_member"]
1477 assert api.GetUser(api_pb2.GetUserReq(user=user3.username)).badges == []
1479 assert api.ListBadgeUsers(api_pb2.ListBadgeUsersReq(badge_id=founder_badge["id"])).user_ids == [1, 2]
1480 res = api.ListBadgeUsers(api_pb2.ListBadgeUsersReq(badge_id=board_member_badge["id"], page_size=1))
1481 assert res.user_ids == [1]
1482 res2 = api.ListBadgeUsers(
1483 api_pb2.ListBadgeUsersReq(badge_id=board_member_badge["id"], page_token=res.next_page_token)
1484 )
1485 assert res2.user_ids == [2]
1488@pytest.mark.parametrize("flag", ["is_deleted", "is_banned"])
1489def test_GetLiteUser_ghost_user_by_username(db, flag):
1490 """Test that GetLiteUser returns a ghost profile for deleted/banned users when querying by username."""
1491 user1, token1 = generate_user()
1492 user2, _ = generate_user()
1494 # Make user2 invisible
1495 with session_scope() as session:
1496 db_user2 = session.merge(user2)
1497 setattr(db_user2, flag, True)
1498 session.commit()
1500 # Refresh the materialized view
1501 refresh_materialized_views_rapid(None)
1503 with api_session(token1) as api:
1504 # Query by username
1505 lite_user = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user2.username))
1507 assert lite_user.user_id == user2.id
1508 assert lite_user.username == "ghost"
1509 assert lite_user.name == "Ghost"
1510 assert lite_user.lat == 0
1511 assert lite_user.lng == 0
1512 assert lite_user.radius == 0
1513 assert lite_user.city == ""
1514 assert lite_user.age == 0
1515 assert lite_user.avatar_url == ""
1516 assert lite_user.avatar_thumbnail_url == ""
1517 assert not lite_user.has_strong_verification
1520@pytest.mark.parametrize("flag", ["is_deleted", "is_banned"])
1521def test_GetLiteUser_ghost_user_by_id(db, flag):
1522 """Test that GetLiteUser returns a ghost profile for deleted/banned users when querying by ID."""
1523 user1, token1 = generate_user()
1524 user2, _ = generate_user()
1526 # Make user2 invisible
1527 with session_scope() as session:
1528 db_user2 = session.merge(user2)
1529 setattr(db_user2, flag, True)
1530 session.commit()
1532 # Refresh the materialized view
1533 refresh_materialized_views_rapid(None)
1535 with api_session(token1) as api:
1536 # Query by ID
1537 lite_user = api.GetLiteUser(api_pb2.GetLiteUserReq(user=str(user2.id)))
1539 assert lite_user.user_id == user2.id
1540 assert lite_user.username == "ghost"
1541 assert lite_user.name == "Ghost"
1542 assert lite_user.lat == 0
1543 assert lite_user.lng == 0
1544 assert lite_user.radius == 0
1545 assert lite_user.city == ""
1546 assert lite_user.age == 0
1547 assert lite_user.avatar_url == ""
1548 assert lite_user.avatar_thumbnail_url == ""
1549 assert not lite_user.has_strong_verification
1552def test_GetLiteUser_blocked_user(db):
1553 """Test that GetLiteUser returns a ghost profile for blocked users."""
1554 user1, token1 = generate_user()
1555 user2, _ = generate_user()
1557 # User1 blocks user2
1558 make_user_block(user1, user2)
1560 # Refresh the materialized view
1561 refresh_materialized_views_rapid(None)
1563 with api_session(token1) as api:
1564 # Query by username
1565 lite_user = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user2.username))
1567 assert lite_user.user_id == user2.id
1568 assert lite_user.is_ghost
1569 assert lite_user.username == "ghost"
1570 assert lite_user.name == "Ghost"
1572 # Query by ID
1573 lite_user = api.GetLiteUser(api_pb2.GetLiteUserReq(user=str(user2.id)))
1575 assert lite_user.user_id == user2.id
1576 assert lite_user.is_ghost
1577 assert lite_user.username == "ghost"
1578 assert lite_user.name == "Ghost"
1581def test_GetLiteUser_blocking_user(db):
1582 """Test that GetLiteUser returns a ghost profile when the target user has blocked the requester."""
1583 user1, token1 = generate_user()
1584 user2, _ = generate_user()
1586 # User2 blocks user1
1587 make_user_block(user2, user1)
1589 # Refresh the materialized view
1590 refresh_materialized_views_rapid(None)
1592 with api_session(token1) as api:
1593 # Query by username
1594 lite_user = api.GetLiteUser(api_pb2.GetLiteUserReq(user=user2.username))
1596 assert lite_user.user_id == user2.id
1597 assert lite_user.is_ghost
1598 assert lite_user.username == "ghost"
1599 assert lite_user.name == "Ghost"
1601 # Query by ID
1602 lite_user = api.GetLiteUser(api_pb2.GetLiteUserReq(user=str(user2.id)))
1604 assert lite_user.user_id == user2.id
1605 assert lite_user.is_ghost
1606 assert lite_user.username == "ghost"
1607 assert lite_user.name == "Ghost"
1610@pytest.mark.parametrize("flag", ["is_deleted", "is_banned"])
1611def test_GetLiteUsers_ghost_users(db, flag):
1612 """Test that GetLiteUsers returns ghost profiles for deleted/banned users."""
1613 user1, token1 = generate_user()
1614 user2, _ = generate_user()
1615 user3, _ = generate_user()
1616 user4, _ = generate_user()
1618 # Make user2 and user4 invisible
1619 with session_scope() as session:
1620 db_user2 = session.merge(user2)
1621 setattr(db_user2, flag, True)
1622 db_user4 = session.merge(user4)
1623 setattr(db_user4, flag, True)
1624 session.commit()
1626 # Refresh the materialized view
1627 refresh_materialized_views_rapid(None)
1629 with api_session(token1) as api:
1630 res = api.GetLiteUsers(
1631 api_pb2.GetLiteUsersReq(
1632 users=[
1633 user1.username, # visible
1634 user2.username, # ghost
1635 str(user3.id), # visible
1636 str(user4.id), # ghost
1637 ]
1638 )
1639 )
1641 assert len(res.responses) == 4
1643 # user1 - visible, normal profile
1644 assert res.responses[0].query == user1.username
1645 assert not res.responses[0].not_found
1646 assert res.responses[0].user.user_id == user1.id
1647 assert res.responses[0].user.username == user1.username
1648 assert res.responses[0].user.name == user1.name
1650 # user2 - ghost by username
1651 assert res.responses[1].query == user2.username
1652 assert not res.responses[1].not_found
1653 assert res.responses[1].user.user_id == user2.id
1654 assert res.responses[1].user.is_ghost
1655 assert res.responses[1].user.username == "ghost"
1656 assert res.responses[1].user.name == "Ghost"
1658 # user3 - visible, normal profile
1659 assert res.responses[2].query == str(user3.id)
1660 assert not res.responses[2].not_found
1661 assert res.responses[2].user.user_id == user3.id
1662 assert res.responses[2].user.username == user3.username
1663 assert res.responses[2].user.name == user3.name
1665 # user4 - ghost by ID
1666 assert res.responses[3].query == str(user4.id)
1667 assert not res.responses[3].not_found
1668 assert res.responses[3].user.user_id == user4.id
1669 assert res.responses[3].user.is_ghost
1670 assert res.responses[3].user.username == "ghost"
1671 assert res.responses[3].user.name == "Ghost"
1674def test_GetLiteUsers_blocked_users(db):
1675 """Test that GetLiteUsers returns ghost profiles for blocked users."""
1676 user1, token1 = generate_user()
1677 user2, _ = generate_user()
1678 user3, _ = generate_user()
1679 user4, _ = generate_user()
1680 user5, _ = generate_user()
1682 # User1 blocks user2
1683 make_user_block(user1, user2)
1684 # User4 blocks user1
1685 make_user_block(user4, user1)
1687 # Refresh the materialized view
1688 refresh_materialized_views_rapid(None)
1690 with api_session(token1) as api:
1691 res = api.GetLiteUsers(
1692 api_pb2.GetLiteUsersReq(
1693 users=[
1694 user2.username, # user1 blocked user2
1695 str(user3.id), # visible
1696 user4.username, # user4 blocked user1
1697 str(user5.id), # visible
1698 ]
1699 )
1700 )
1702 assert len(res.responses) == 4
1704 # user2 - blocked by user1, should be ghost
1705 assert res.responses[0].query == user2.username
1706 assert not res.responses[0].not_found
1707 assert res.responses[0].user.user_id == user2.id
1708 assert res.responses[0].user.is_ghost
1709 assert res.responses[0].user.username == "ghost"
1710 assert res.responses[0].user.name == "Ghost"
1712 # user3 - visible
1713 assert res.responses[1].query == str(user3.id)
1714 assert not res.responses[1].not_found
1715 assert res.responses[1].user.user_id == user3.id
1716 assert res.responses[1].user.username == user3.username
1718 # user4 - user4 blocked user1, should be ghost
1719 assert res.responses[2].query == user4.username
1720 assert not res.responses[2].not_found
1721 assert res.responses[2].user.user_id == user4.id
1722 assert res.responses[2].user.is_ghost
1723 assert res.responses[2].user.username == "ghost"
1724 assert res.responses[2].user.name == "Ghost"
1726 # user5 - visible
1727 assert res.responses[3].query == str(user5.id)
1728 assert not res.responses[3].not_found
1729 assert res.responses[3].user.user_id == user5.id
1730 assert res.responses[3].user.username == user5.username
1733@pytest.mark.parametrize("flag", ["is_deleted", "is_banned"])
1734def test_GetUser_ghost_user_by_id(db, flag):
1735 """Test that GetUser returns a ghost profile for deleted/banned users when querying by ID."""
1736 user1, token1 = generate_user()
1737 user2, _ = generate_user()
1739 # Make user2 invisible
1740 with session_scope() as session:
1741 db_user2 = session.merge(user2)
1742 setattr(db_user2, flag, True)
1743 session.commit()
1745 with api_session(token1) as api:
1746 # Query by ID
1747 user_pb = api.GetUser(api_pb2.GetUserReq(user=str(user2.id)))
1749 assert user_pb.user_id == user2.id
1750 assert user_pb.username == "ghost"
1751 assert user_pb.name == "Ghost"
1752 assert user_pb.city == ""
1753 assert user_pb.hosting_status == 0
1754 assert user_pb.meetup_status == 0
1757def test_GetUser_blocked_user(db):
1758 """Test that GetUser returns a ghost profile for blocked users."""
1759 user1, token1 = generate_user()
1760 user2, _ = generate_user()
1762 # User1 blocks user2
1763 make_user_block(user1, user2)
1765 with api_session(token1) as api:
1766 # Query by username
1767 user_pb = api.GetUser(api_pb2.GetUserReq(user=user2.username))
1769 assert user_pb.user_id == user2.id
1770 assert user_pb.username == "ghost"
1771 assert user_pb.name == "Ghost"
1773 # Query by ID
1774 user_pb = api.GetUser(api_pb2.GetUserReq(user=str(user2.id)))
1776 assert user_pb.user_id == user2.id
1777 assert user_pb.username == "ghost"
1778 assert user_pb.name == "Ghost"
1781def test_GetUser_blocking_user(db):
1782 """Test that GetUser returns a ghost profile when the target user has blocked the requester."""
1783 user1, token1 = generate_user()
1784 user2, _ = generate_user()
1786 # User2 blocks user1
1787 make_user_block(user2, user1)
1789 with api_session(token1) as api:
1790 # Query by username
1791 user_pb = api.GetUser(api_pb2.GetUserReq(user=user2.username))
1793 assert user_pb.user_id == user2.id
1794 assert user_pb.username == "ghost"
1795 assert user_pb.name == "Ghost"
1797 # Query by ID
1798 user_pb = api.GetUser(api_pb2.GetUserReq(user=str(user2.id)))
1800 assert user_pb.user_id == user2.id
1801 assert user_pb.username == "ghost"
1802 assert user_pb.name == "Ghost"