Coverage for src/tests/test_communities.py: 100%
542 statements
« prev ^ index » next coverage.py v7.6.10, created at 2025-05-12 02:28 +0000
« prev ^ index » next coverage.py v7.6.10, created at 2025-05-12 02:28 +0000
1from datetime import timedelta
3import grpc
4import pytest
5from google.protobuf import wrappers_pb2
7from couchers import errors
8from couchers.db import session_scope
9from couchers.materialized_views import refresh_materialized_views
10from couchers.models import (
11 Cluster,
12 ClusterRole,
13 ClusterSubscription,
14 Node,
15 Page,
16 PageType,
17 PageVersion,
18 SignupFlow,
19 Thread,
20)
21from couchers.sql import couchers_select as select
22from couchers.tasks import enforce_community_memberships
23from couchers.utils import Timestamp_from_datetime, create_coordinate, create_polygon_lat_lng, now, to_multi
24from proto import api_pb2, auth_pb2, communities_pb2, discussions_pb2, events_pb2, pages_pb2
25from tests.test_auth import get_session_cookie_tokens
26from tests.test_fixtures import ( # noqa
27 auth_api_session,
28 communities_session,
29 db,
30 discussions_session,
31 events_session,
32 generate_user,
33 get_user_id_and_token,
34 pages_session,
35 recreate_database,
36 testconfig,
37)
40@pytest.fixture(autouse=True)
41def _(testconfig):
42 pass
45# For testing purposes, restrict ourselves to a 1D-world, consisting of "intervals" that have width 2, and coordinates
46# that are points at (x, 1).
47# we'll stick to EPSG4326, even though it's not ideal, so don't use too large values, but it's around the equator, so
48# mostly fine
51def create_1d_polygon(lb, ub):
52 # given a lower bound and upper bound on x, creates the given interval
53 return create_polygon_lat_lng([[lb, 0], [lb, 2], [ub, 2], [ub, 0], [lb, 0]])
56def create_1d_point(x):
57 return create_coordinate(x, 1)
60def create_community(session, interval_lb, interval_ub, name, admins, extra_members, parent):
61 node = Node(
62 geom=to_multi(create_1d_polygon(interval_lb, interval_ub)),
63 parent_node=parent,
64 )
65 session.add(node)
66 cluster = Cluster(
67 name=f"{name}",
68 description=f"Description for {name}",
69 parent_node=node,
70 is_official_cluster=True,
71 )
72 session.add(cluster)
73 main_page = Page(
74 parent_node=cluster.parent_node,
75 creator_user_id=admins[0].id,
76 owner_cluster=cluster,
77 type=PageType.main_page,
78 thread=Thread(),
79 )
80 session.add(main_page)
81 page_version = PageVersion(
82 page=main_page,
83 editor_user_id=admins[0].id,
84 title=f"Main page for the {name} community",
85 content="There is nothing here yet...",
86 )
87 session.add(page_version)
88 for admin in admins:
89 cluster.cluster_subscriptions.append(
90 ClusterSubscription(
91 user_id=admin.id,
92 role=ClusterRole.admin,
93 )
94 )
95 for member in extra_members:
96 cluster.cluster_subscriptions.append(
97 ClusterSubscription(
98 user_id=member.id,
99 role=ClusterRole.member,
100 )
101 )
102 session.commit()
103 # other members will be added by enforce_community_memberships()
104 return node
107def create_group(session, name, admins, members, parent_community):
108 cluster = Cluster(
109 name=f"{name}",
110 description=f"Description for {name}",
111 parent_node=parent_community,
112 )
113 session.add(cluster)
114 main_page = Page(
115 parent_node=cluster.parent_node,
116 creator_user=admins[0],
117 owner_cluster=cluster,
118 type=PageType.main_page,
119 thread=Thread(),
120 )
121 session.add(main_page)
122 page_version = PageVersion(
123 page=main_page,
124 editor_user=admins[0],
125 title=f"Main page for the {name} community",
126 content="There is nothing here yet...",
127 )
128 session.add(page_version)
129 for admin in admins:
130 cluster.cluster_subscriptions.append(
131 ClusterSubscription(
132 user=admin,
133 role=ClusterRole.admin,
134 )
135 )
136 for member in members:
137 cluster.cluster_subscriptions.append(
138 ClusterSubscription(
139 user=member,
140 role=ClusterRole.member,
141 )
142 )
143 session.commit()
144 return cluster
147def create_place(token, title, content, address, x):
148 with pages_session(token) as api:
149 res = api.CreatePlace(
150 pages_pb2.CreatePlaceReq(
151 title=title,
152 content=content,
153 address=address,
154 location=pages_pb2.Coordinate(
155 lat=x,
156 lng=1,
157 ),
158 )
159 )
162def create_discussion(token, community_id, group_id, title, content):
163 # set group_id or community_id to None
164 with discussions_session(token) as api:
165 res = api.CreateDiscussion(
166 discussions_pb2.CreateDiscussionReq(
167 title=title,
168 content=content,
169 owner_community_id=community_id,
170 owner_group_id=group_id,
171 )
172 )
175def create_event(token, community_id, group_id, title, content, start_td):
176 with events_session(token) as api:
177 res = api.CreateEvent(
178 events_pb2.CreateEventReq(
179 title=title,
180 content=content,
181 offline_information=events_pb2.OfflineEventInformation(
182 address="Near Null Island",
183 lat=0.1,
184 lng=0.2,
185 ),
186 start_time=Timestamp_from_datetime(now() + start_td),
187 end_time=Timestamp_from_datetime(now() + start_td + timedelta(hours=2)),
188 timezone="UTC",
189 )
190 )
191 api.TransferEvent(
192 events_pb2.TransferEventReq(
193 event_id=res.event_id,
194 new_owner_community_id=community_id,
195 new_owner_group_id=group_id,
196 )
197 )
200def get_community_id(session, community_name):
201 return (
202 session.execute(select(Cluster).where(Cluster.is_official_cluster).where(Cluster.name == community_name))
203 .scalar_one()
204 .parent_node_id
205 )
208def get_group_id(session, group_name):
209 return (
210 session.execute(select(Cluster).where(~Cluster.is_official_cluster).where(Cluster.name == group_name))
211 .scalar_one()
212 .id
213 )
216@pytest.fixture(scope="class")
217def testing_communities(testconfig):
218 recreate_database()
219 user1, token1 = generate_user(username="user1", geom=create_1d_point(1), geom_radius=0.1)
220 user2, token2 = generate_user(username="user2", geom=create_1d_point(2), geom_radius=0.1)
221 user3, token3 = generate_user(username="user3", geom=create_1d_point(3), geom_radius=0.1)
222 user4, token4 = generate_user(username="user4", geom=create_1d_point(8), geom_radius=0.1)
223 user5, token5 = generate_user(username="user5", geom=create_1d_point(6), geom_radius=0.1)
224 user6, token6 = generate_user(username="user6", geom=create_1d_point(65), geom_radius=0.1)
225 user7, token7 = generate_user(username="user7", geom=create_1d_point(80), geom_radius=0.1)
226 user8, token8 = generate_user(username="user8", geom=create_1d_point(51), geom_radius=0.1)
228 with session_scope() as session:
229 w = create_community(session, 0, 100, "Global", [user1, user3, user7], [], None)
230 c2 = create_community(session, 52, 100, "Country 2", [user6, user7], [], w)
231 c2r1 = create_community(session, 52, 71, "Country 2, Region 1", [user6], [user8], c2)
232 c2r1c1 = create_community(session, 53, 70, "Country 2, Region 1, City 1", [user8], [], c2r1)
233 c1 = create_community(session, 0, 50, "Country 1", [user1, user2], [], w)
234 c1r1 = create_community(session, 0, 10, "Country 1, Region 1", [user1, user2], [], c1)
235 c1r1c1 = create_community(session, 0, 5, "Country 1, Region 1, City 1", [user2], [], c1r1)
236 c1r1c2 = create_community(session, 7, 10, "Country 1, Region 1, City 2", [user4, user5], [user2], c1r1)
237 c1r2 = create_community(session, 20, 25, "Country 1, Region 2", [user2], [], c1)
238 c1r2c1 = create_community(session, 21, 23, "Country 1, Region 2, City 1", [user2], [], c1r2)
240 h = create_group(session, "Hitchhikers", [user1, user2], [user5, user8], w)
241 create_group(session, "Country 1, Region 1, Foodies", [user1], [user2, user4], c1r1)
242 create_group(session, "Country 1, Region 1, Skaters", [user2], [user1], c1r1)
243 create_group(session, "Country 1, Region 2, Foodies", [user2], [user4, user5], c1r2)
244 create_group(session, "Country 2, Region 1, Foodies", [user6], [user7], c2r1)
246 w_id = w.id
247 c1r1c2_id = c1r1c2.id
248 h_id = h.id
249 c1_id = c1.id
251 create_discussion(token1, w_id, None, "Discussion title 1", "Discussion content 1")
252 create_discussion(token3, w_id, None, "Discussion title 2", "Discussion content 2")
253 create_discussion(token3, w_id, None, "Discussion title 3", "Discussion content 3")
254 create_discussion(token3, w_id, None, "Discussion title 4", "Discussion content 4")
255 create_discussion(token3, w_id, None, "Discussion title 5", "Discussion content 5")
256 create_discussion(token3, w_id, None, "Discussion title 6", "Discussion content 6")
257 create_discussion(token4, c1r1c2_id, None, "Discussion title 7", "Discussion content 7")
258 create_discussion(token5, None, h_id, "Discussion title 8", "Discussion content 8")
259 create_discussion(token1, None, h_id, "Discussion title 9", "Discussion content 9")
260 create_discussion(token2, None, h_id, "Discussion title 10", "Discussion content 10")
261 create_discussion(token3, None, h_id, "Discussion title 11", "Discussion content 11")
262 create_discussion(token4, None, h_id, "Discussion title 12", "Discussion content 12")
263 create_discussion(token5, None, h_id, "Discussion title 13", "Discussion content 13")
264 create_discussion(token8, None, h_id, "Discussion title 14", "Discussion content 14")
266 create_event(token3, c1_id, None, "Event title 1", "Event content 1", timedelta(hours=1))
267 create_event(token1, c1_id, None, "Event title 2", "Event content 2", timedelta(hours=2))
268 create_event(token3, c1_id, None, "Event title 3", "Event content 3", timedelta(hours=3))
269 create_event(token1, c1_id, None, "Event title 4", "Event content 4", timedelta(hours=4))
270 create_event(token3, c1_id, None, "Event title 5", "Event content 5", timedelta(hours=5))
271 create_event(token1, c1_id, None, "Event title 6", "Event content 6", timedelta(hours=6))
272 create_event(token2, None, h_id, "Event title 7", "Event content 7", timedelta(hours=7))
273 create_event(token2, None, h_id, "Event title 8", "Event content 8", timedelta(hours=8))
274 create_event(token2, None, h_id, "Event title 9", "Event content 9", timedelta(hours=9))
275 create_event(token2, None, h_id, "Event title 10", "Event content 10", timedelta(hours=10))
276 create_event(token2, None, h_id, "Event title 11", "Event content 11", timedelta(hours=11))
277 create_event(token2, None, h_id, "Event title 12", "Event content 12", timedelta(hours=12))
279 enforce_community_memberships()
281 create_place(token1, "Country 1, Region 1, Attraction", "Place content", "Somewhere in c1r1", 6)
282 create_place(token2, "Country 1, Region 1, City 1, Attraction 1", "Place content", "Somewhere in c1r1c1", 3)
283 create_place(token2, "Country 1, Region 1, City 1, Attraction 2", "Place content", "Somewhere in c1r1c1", 4)
284 create_place(token8, "Global, Attraction", "Place content", "Somewhere in w", 51.5)
285 create_place(token6, "Country 2, Region 1, Attraction", "Place content", "Somewhere in c2r1", 59)
287 refresh_materialized_views(None)
289 yield
292class TestCommunities:
293 @staticmethod
294 def test_GetCommunity(testing_communities):
295 with session_scope() as session:
296 user2_id, token2 = get_user_id_and_token(session, "user2")
297 w_id = get_community_id(session, "Global")
298 c1_id = get_community_id(session, "Country 1")
299 c1r1_id = get_community_id(session, "Country 1, Region 1")
300 c1r1c1_id = get_community_id(session, "Country 1, Region 1, City 1")
301 c2_id = get_community_id(session, "Country 2")
303 with communities_session(token2) as api:
304 res = api.GetCommunity(
305 communities_pb2.GetCommunityReq(
306 community_id=w_id,
307 )
308 )
309 assert res.name == "Global"
310 assert res.slug == "global"
311 assert res.description == "Description for Global"
312 assert len(res.parents) == 1
313 assert res.parents[0].HasField("community")
314 assert res.parents[0].community.community_id == w_id
315 assert res.parents[0].community.name == "Global"
316 assert res.parents[0].community.slug == "global"
317 assert res.parents[0].community.description == "Description for Global"
318 assert res.main_page.type == pages_pb2.PAGE_TYPE_MAIN_PAGE
319 assert res.main_page.slug == "main-page-for-the-global-community"
320 assert res.main_page.last_editor_user_id == 1
321 assert res.main_page.creator_user_id == 1
322 assert res.main_page.owner_community_id == w_id
323 assert res.main_page.title == "Main page for the Global community"
324 assert res.main_page.content == "There is nothing here yet..."
325 assert not res.main_page.can_edit
326 assert not res.main_page.can_moderate
327 assert res.main_page.editor_user_ids == [1]
328 assert res.member
329 assert not res.admin
330 assert res.member_count == 8
331 assert res.admin_count == 3
333 res = api.GetCommunity(
334 communities_pb2.GetCommunityReq(
335 community_id=c1r1c1_id,
336 )
337 )
338 assert res.community_id == c1r1c1_id
339 assert res.name == "Country 1, Region 1, City 1"
340 assert res.slug == "country-1-region-1-city-1"
341 assert res.description == "Description for Country 1, Region 1, City 1"
342 assert len(res.parents) == 4
343 assert res.parents[0].HasField("community")
344 assert res.parents[0].community.community_id == w_id
345 assert res.parents[0].community.name == "Global"
346 assert res.parents[0].community.slug == "global"
347 assert res.parents[0].community.description == "Description for Global"
348 assert res.parents[1].HasField("community")
349 assert res.parents[1].community.community_id == c1_id
350 assert res.parents[1].community.name == "Country 1"
351 assert res.parents[1].community.slug == "country-1"
352 assert res.parents[1].community.description == "Description for Country 1"
353 assert res.parents[2].HasField("community")
354 assert res.parents[2].community.community_id == c1r1_id
355 assert res.parents[2].community.name == "Country 1, Region 1"
356 assert res.parents[2].community.slug == "country-1-region-1"
357 assert res.parents[2].community.description == "Description for Country 1, Region 1"
358 assert res.parents[3].HasField("community")
359 assert res.parents[3].community.community_id == c1r1c1_id
360 assert res.parents[3].community.name == "Country 1, Region 1, City 1"
361 assert res.parents[3].community.slug == "country-1-region-1-city-1"
362 assert res.parents[3].community.description == "Description for Country 1, Region 1, City 1"
363 assert res.main_page.type == pages_pb2.PAGE_TYPE_MAIN_PAGE
364 assert res.main_page.slug == "main-page-for-the-country-1-region-1-city-1-community"
365 assert res.main_page.last_editor_user_id == 2
366 assert res.main_page.creator_user_id == 2
367 assert res.main_page.owner_community_id == c1r1c1_id
368 assert res.main_page.title == "Main page for the Country 1, Region 1, City 1 community"
369 assert res.main_page.content == "There is nothing here yet..."
370 assert res.main_page.can_edit
371 assert res.main_page.can_moderate
372 assert res.main_page.editor_user_ids == [2]
373 assert res.member
374 assert res.admin
375 assert res.member_count == 3
376 assert res.admin_count == 1
378 res = api.GetCommunity(
379 communities_pb2.GetCommunityReq(
380 community_id=c2_id,
381 )
382 )
383 assert res.community_id == c2_id
384 assert res.name == "Country 2"
385 assert res.slug == "country-2"
386 assert res.description == "Description for Country 2"
387 assert len(res.parents) == 2
388 assert res.parents[0].HasField("community")
389 assert res.parents[0].community.community_id == w_id
390 assert res.parents[0].community.name == "Global"
391 assert res.parents[0].community.slug == "global"
392 assert res.parents[0].community.description == "Description for Global"
393 assert res.parents[1].HasField("community")
394 assert res.parents[1].community.community_id == c2_id
395 assert res.parents[1].community.name == "Country 2"
396 assert res.parents[1].community.slug == "country-2"
397 assert res.parents[1].community.description == "Description for Country 2"
398 assert res.main_page.type == pages_pb2.PAGE_TYPE_MAIN_PAGE
399 assert res.main_page.slug == "main-page-for-the-country-2-community"
400 assert res.main_page.last_editor_user_id == 6
401 assert res.main_page.creator_user_id == 6
402 assert res.main_page.owner_community_id == c2_id
403 assert res.main_page.title == "Main page for the Country 2 community"
404 assert res.main_page.content == "There is nothing here yet..."
405 assert not res.main_page.can_edit
406 assert not res.main_page.can_moderate
407 assert res.main_page.editor_user_ids == [6]
408 assert not res.member
409 assert not res.admin
410 assert res.member_count == 2
411 assert res.admin_count == 2
413 @staticmethod
414 def test_ListCommunities(testing_communities):
415 with session_scope() as session:
416 user1_id, token1 = get_user_id_and_token(session, "user1")
417 c1_id = get_community_id(session, "Country 1")
418 c1r1_id = get_community_id(session, "Country 1, Region 1")
419 c1r2_id = get_community_id(session, "Country 1, Region 2")
421 with communities_session(token1) as api:
422 res = api.ListCommunities(
423 communities_pb2.ListCommunitiesReq(
424 community_id=c1_id,
425 )
426 )
427 assert [c.community_id for c in res.communities] == [c1r1_id, c1r2_id]
429 @staticmethod
430 def test_ListCommunities_all(testing_communities):
431 with session_scope() as session:
432 user1_id, token1 = get_user_id_and_token(session, "user1")
433 w_id = get_community_id(session, "Global")
434 c1_id = get_community_id(session, "Country 1")
435 c1r1_id = get_community_id(session, "Country 1, Region 1")
436 c1r1c1_id = get_community_id(session, "Country 1, Region 1, City 1")
437 c1r1c2_id = get_community_id(session, "Country 1, Region 1, City 2")
438 c1r2_id = get_community_id(session, "Country 1, Region 2")
439 c1r2c1_id = get_community_id(session, "Country 1, Region 2, City 1")
440 c2_id = get_community_id(session, "Country 2")
441 c2r1_id = get_community_id(session, "Country 2, Region 1")
442 c2r1c1_id = get_community_id(session, "Country 2, Region 1, City 1")
444 # Fetch all communities ordered by name
445 with communities_session(token1) as api:
446 res = api.ListCommunities(
447 communities_pb2.ListCommunitiesReq(
448 page_size=5,
449 )
450 )
451 assert [c.community_id for c in res.communities] == [c1_id, c1r1_id, c1r1c1_id, c1r1c2_id, c1r2_id]
452 res = api.ListCommunities(
453 communities_pb2.ListCommunitiesReq(
454 page_size=2,
455 page_token=res.next_page_token,
456 )
457 )
458 assert [c.community_id for c in res.communities] == [c1r2c1_id, c2_id]
459 res = api.ListCommunities(
460 communities_pb2.ListCommunitiesReq(
461 page_size=5,
462 page_token=res.next_page_token,
463 )
464 )
465 assert [c.community_id for c in res.communities] == [c2r1_id, c2r1c1_id, w_id]
467 @staticmethod
468 def test_ListUserCommunities(testing_communities):
469 with session_scope() as session:
470 user2_id, token2 = get_user_id_and_token(session, "user2")
471 w_id = get_community_id(session, "Global")
472 c1_id = get_community_id(session, "Country 1")
473 c1r1_id = get_community_id(session, "Country 1, Region 1")
474 c1r1c1_id = get_community_id(session, "Country 1, Region 1, City 1")
475 c1r1c2_id = get_community_id(session, "Country 1, Region 1, City 2")
476 c1r2_id = get_community_id(session, "Country 1, Region 2")
477 c1r2c1_id = get_community_id(session, "Country 1, Region 2, City 1")
479 # Fetch user2's communities from user2's account
480 with communities_session(token2) as api:
481 res = api.ListUserCommunities(communities_pb2.ListUserCommunitiesReq())
482 assert [c.community_id for c in res.communities] == [
483 w_id,
484 c1_id,
485 c1r1_id,
486 c1r1c1_id,
487 c1r1c2_id,
488 c1r2_id,
489 c1r2c1_id,
490 ]
492 @staticmethod
493 def test_ListOtherUserCommunities(testing_communities):
494 with session_scope() as session:
495 user1_id, token1 = get_user_id_and_token(session, "user1")
496 user2_id, token2 = get_user_id_and_token(session, "user2")
497 w_id = get_community_id(session, "Global")
498 c1_id = get_community_id(session, "Country 1")
499 c1r1_id = get_community_id(session, "Country 1, Region 1")
500 c1r1c1_id = get_community_id(session, "Country 1, Region 1, City 1")
501 c1r1c2_id = get_community_id(session, "Country 1, Region 1, City 2")
502 c1r2_id = get_community_id(session, "Country 1, Region 2")
503 c1r2c1_id = get_community_id(session, "Country 1, Region 2, City 1")
505 # Fetch user2's communities from user1's account
506 with communities_session(token1) as api:
507 res = api.ListUserCommunities(communities_pb2.ListUserCommunitiesReq(user_id=user2_id))
508 assert [c.community_id for c in res.communities] == [
509 w_id,
510 c1_id,
511 c1r1_id,
512 c1r1c1_id,
513 c1r1c2_id,
514 c1r2_id,
515 c1r2c1_id,
516 ]
518 @staticmethod
519 def test_ListGroups(testing_communities):
520 with session_scope() as session:
521 user1_id, token1 = get_user_id_and_token(session, "user1")
522 user5_id, token5 = get_user_id_and_token(session, "user5")
523 w_id = get_community_id(session, "Global")
524 hitchhikers_id = get_group_id(session, "Hitchhikers")
525 c1r1_id = get_community_id(session, "Country 1, Region 1")
526 foodies_id = get_group_id(session, "Country 1, Region 1, Foodies")
527 skaters_id = get_group_id(session, "Country 1, Region 1, Skaters")
529 with communities_session(token1) as api:
530 res = api.ListGroups(
531 communities_pb2.ListGroupsReq(
532 community_id=c1r1_id,
533 )
534 )
535 assert [g.group_id for g in res.groups] == [foodies_id, skaters_id]
537 with communities_session(token5) as api:
538 res = api.ListGroups(
539 communities_pb2.ListGroupsReq(
540 community_id=w_id,
541 )
542 )
543 assert len(res.groups) == 1
544 assert res.groups[0].group_id == hitchhikers_id
546 @staticmethod
547 def test_ListAdmins(testing_communities):
548 with session_scope() as session:
549 user1_id, token1 = get_user_id_and_token(session, "user1")
550 user3_id, token3 = get_user_id_and_token(session, "user3")
551 user4_id, token4 = get_user_id_and_token(session, "user4")
552 user5_id, token5 = get_user_id_and_token(session, "user5")
553 user7_id, token7 = get_user_id_and_token(session, "user7")
554 w_id = get_community_id(session, "Global")
555 c1r1c2_id = get_community_id(session, "Country 1, Region 1, City 2")
557 with communities_session(token1) as api:
558 res = api.ListAdmins(
559 communities_pb2.ListAdminsReq(
560 community_id=w_id,
561 )
562 )
563 assert res.admin_user_ids == [user1_id, user3_id, user7_id]
565 res = api.ListAdmins(
566 communities_pb2.ListAdminsReq(
567 community_id=c1r1c2_id,
568 )
569 )
570 assert res.admin_user_ids == [user4_id, user5_id]
572 @staticmethod
573 def test_AddAdmin(testing_communities):
574 with session_scope() as session:
575 user4_id, token4 = get_user_id_and_token(session, "user4")
576 user5_id, _ = get_user_id_and_token(session, "user5")
577 user2_id, _ = get_user_id_and_token(session, "user2")
578 user8_id, token8 = get_user_id_and_token(session, "user8")
579 node_id = get_community_id(session, "Country 1, Region 1, City 2")
581 with communities_session(token8) as api:
582 with pytest.raises(grpc.RpcError) as err:
583 api.AddAdmin(communities_pb2.AddAdminReq(community_id=node_id, user_id=user2_id))
584 assert err.value.code() == grpc.StatusCode.FAILED_PRECONDITION
585 assert err.value.details() == errors.NODE_MODERATE_PERMISSION_DENIED
587 with communities_session(token4) as api:
588 res = api.ListAdmins(communities_pb2.ListAdminsReq(community_id=node_id))
589 assert res.admin_user_ids == [user4_id, user5_id]
591 with pytest.raises(grpc.RpcError) as err:
592 api.AddAdmin(communities_pb2.AddAdminReq(community_id=node_id, user_id=user8_id))
593 assert err.value.code() == grpc.StatusCode.FAILED_PRECONDITION
594 assert err.value.details() == errors.USER_NOT_MEMBER
596 with pytest.raises(grpc.RpcError) as err:
597 api.AddAdmin(communities_pb2.AddAdminReq(community_id=node_id, user_id=user5_id))
598 assert err.value.code() == grpc.StatusCode.FAILED_PRECONDITION
599 assert err.value.details() == errors.USER_ALREADY_ADMIN
601 api.AddAdmin(communities_pb2.AddAdminReq(community_id=node_id, user_id=user2_id))
602 res = api.ListAdmins(communities_pb2.ListAdminsReq(community_id=node_id))
603 assert res.admin_user_ids == [user2_id, user4_id, user5_id]
604 # Cleanup because database changes do not roll back
605 api.RemoveAdmin(communities_pb2.RemoveAdminReq(community_id=node_id, user_id=user2_id))
607 @staticmethod
608 def test_RemoveAdmin(testing_communities):
609 with session_scope() as session:
610 user4_id, token4 = get_user_id_and_token(session, "user4")
611 user5_id, _ = get_user_id_and_token(session, "user5")
612 user2_id, _ = get_user_id_and_token(session, "user2")
613 user8_id, token8 = get_user_id_and_token(session, "user8")
614 node_id = get_community_id(session, "Country 1, Region 1, City 2")
616 with communities_session(token8) as api:
617 with pytest.raises(grpc.RpcError) as err:
618 api.AddAdmin(communities_pb2.AddAdminReq(community_id=node_id, user_id=user2_id))
619 assert err.value.code() == grpc.StatusCode.FAILED_PRECONDITION
620 assert err.value.details() == errors.NODE_MODERATE_PERMISSION_DENIED
622 with communities_session(token4) as api:
623 res = api.ListAdmins(communities_pb2.ListAdminsReq(community_id=node_id))
624 assert res.admin_user_ids == [user4_id, user5_id]
626 with pytest.raises(grpc.RpcError) as err:
627 api.RemoveAdmin(communities_pb2.RemoveAdminReq(community_id=node_id, user_id=user8_id))
628 assert err.value.code() == grpc.StatusCode.FAILED_PRECONDITION
629 assert err.value.details() == errors.USER_NOT_MEMBER
631 with pytest.raises(grpc.RpcError) as err:
632 api.RemoveAdmin(communities_pb2.RemoveAdminReq(community_id=node_id, user_id=user2_id))
633 assert err.value.code() == grpc.StatusCode.FAILED_PRECONDITION
634 assert err.value.details() == errors.USER_NOT_ADMIN
636 api.RemoveAdmin(communities_pb2.RemoveAdminReq(community_id=node_id, user_id=user5_id))
637 res = api.ListAdmins(communities_pb2.ListAdminsReq(community_id=node_id))
638 assert res.admin_user_ids == [user4_id]
639 # Cleanup because database changes do not roll back
640 api.AddAdmin(communities_pb2.AddAdminReq(community_id=node_id, user_id=user5_id))
642 @staticmethod
643 def test_ListMembers(testing_communities):
644 with session_scope() as session:
645 user1_id, token1 = get_user_id_and_token(session, "user1")
646 user2_id, token2 = get_user_id_and_token(session, "user2")
647 user3_id, token3 = get_user_id_and_token(session, "user3")
648 user4_id, token4 = get_user_id_and_token(session, "user4")
649 user5_id, token5 = get_user_id_and_token(session, "user5")
650 user6_id, token6 = get_user_id_and_token(session, "user6")
651 user7_id, token7 = get_user_id_and_token(session, "user7")
652 user8_id, token8 = get_user_id_and_token(session, "user8")
653 w_id = get_community_id(session, "Global")
654 c1r1c2_id = get_community_id(session, "Country 1, Region 1, City 2")
656 with communities_session(token1) as api:
657 res = api.ListMembers(
658 communities_pb2.ListMembersReq(
659 community_id=w_id,
660 )
661 )
662 assert res.member_user_ids == [
663 user1_id,
664 user2_id,
665 user3_id,
666 user4_id,
667 user5_id,
668 user6_id,
669 user7_id,
670 user8_id,
671 ]
673 res = api.ListMembers(
674 communities_pb2.ListMembersReq(
675 community_id=c1r1c2_id,
676 )
677 )
678 assert res.member_user_ids == [user2_id, user4_id, user5_id]
680 @staticmethod
681 def test_ListNearbyUsers(testing_communities):
682 with session_scope() as session:
683 user1_id, token1 = get_user_id_and_token(session, "user1")
684 user2_id, token2 = get_user_id_and_token(session, "user2")
685 user3_id, token3 = get_user_id_and_token(session, "user3")
686 user4_id, token4 = get_user_id_and_token(session, "user4")
687 user5_id, token5 = get_user_id_and_token(session, "user5")
688 user6_id, token6 = get_user_id_and_token(session, "user6")
689 user7_id, token7 = get_user_id_and_token(session, "user7")
690 user8_id, token8 = get_user_id_and_token(session, "user8")
691 w_id = get_community_id(session, "Global")
692 c1r1c2_id = get_community_id(session, "Country 1, Region 1, City 2")
694 with communities_session(token1) as api:
695 res = api.ListNearbyUsers(
696 communities_pb2.ListNearbyUsersReq(
697 community_id=w_id,
698 )
699 )
700 assert res.nearby_user_ids == [
701 user1_id,
702 user2_id,
703 user3_id,
704 user4_id,
705 user5_id,
706 user6_id,
707 user7_id,
708 user8_id,
709 ]
711 res = api.ListNearbyUsers(
712 communities_pb2.ListNearbyUsersReq(
713 community_id=c1r1c2_id,
714 )
715 )
716 assert res.nearby_user_ids == [user4_id]
718 @staticmethod
719 def test_ListDiscussions(testing_communities):
720 with session_scope() as session:
721 user1_id, token1 = get_user_id_and_token(session, "user1")
722 w_id = get_community_id(session, "Global")
723 c1r1c2_id = get_community_id(session, "Country 1, Region 1, City 2")
725 with communities_session(token1) as api:
726 res = api.ListDiscussions(
727 communities_pb2.ListDiscussionsReq(
728 community_id=w_id,
729 page_size=3,
730 )
731 )
732 assert [d.title for d in res.discussions] == [
733 "Discussion title 6",
734 "Discussion title 5",
735 "Discussion title 4",
736 ]
737 for d in res.discussions:
738 assert d.thread.thread_id > 0
739 assert d.thread.num_responses == 0
741 res = api.ListDiscussions(
742 communities_pb2.ListDiscussionsReq(
743 community_id=w_id,
744 page_token=res.next_page_token,
745 page_size=2,
746 )
747 )
748 assert [d.title for d in res.discussions] == [
749 "Discussion title 3",
750 "Discussion title 2",
751 ]
752 for d in res.discussions:
753 assert d.thread.thread_id > 0
754 assert d.thread.num_responses == 0
756 res = api.ListDiscussions(
757 communities_pb2.ListDiscussionsReq(
758 community_id=w_id,
759 page_token=res.next_page_token,
760 page_size=2,
761 )
762 )
763 assert [d.title for d in res.discussions] == [
764 "Discussion title 1",
765 ]
766 for d in res.discussions:
767 assert d.thread.thread_id > 0
768 assert d.thread.num_responses == 0
770 res = api.ListDiscussions(
771 communities_pb2.ListDiscussionsReq(
772 community_id=c1r1c2_id,
773 )
774 )
775 assert [d.title for d in res.discussions] == [
776 "Discussion title 7",
777 ]
778 for d in res.discussions:
779 assert d.thread.thread_id > 0
780 assert d.thread.num_responses == 0
782 @staticmethod
783 def test_node_contained_user_ids_association_proxy(testing_communities):
784 with session_scope() as session:
785 c1_id = get_community_id(session, "Country 1")
786 node = session.execute(select(Node).where(Node.id == c1_id)).scalar_one_or_none()
787 assert node.contained_user_ids == [1, 2, 3, 4, 5]
788 assert len(node.contained_user_ids) == len(node.contained_users)
790 @staticmethod
791 def test_ListEvents(testing_communities):
792 with session_scope() as session:
793 user1_id, token1 = get_user_id_and_token(session, "user1")
794 c1_id = get_community_id(session, "Country 1")
796 with communities_session(token1) as api:
797 res = api.ListEvents(
798 communities_pb2.ListEventsReq(
799 community_id=c1_id,
800 page_size=3,
801 )
802 )
803 assert [d.title for d in res.events] == [
804 "Event title 1",
805 "Event title 2",
806 "Event title 3",
807 ]
809 res = api.ListEvents(
810 communities_pb2.ListEventsReq(
811 community_id=c1_id,
812 page_token=res.next_page_token,
813 page_size=2,
814 )
815 )
816 assert [d.title for d in res.events] == [
817 "Event title 4",
818 "Event title 5",
819 ]
821 res = api.ListEvents(
822 communities_pb2.ListEventsReq(
823 community_id=c1_id,
824 page_token=res.next_page_token,
825 page_size=2,
826 )
827 )
828 assert [d.title for d in res.events] == [
829 "Event title 6",
830 ]
831 assert not res.next_page_token
834def test_JoinCommunity_and_LeaveCommunity(testing_communities):
835 # these are separate as they mutate the database
836 with session_scope() as session:
837 # at x=1, inside c1 (country 1)
838 user1_id, token1 = get_user_id_and_token(session, "user1")
839 # at x=51, not inside c1
840 user8_id, token8 = get_user_id_and_token(session, "user8")
841 c1_id = get_community_id(session, "Country 1")
843 with communities_session(token1) as api:
844 assert api.GetCommunity(communities_pb2.GetCommunityReq(community_id=c1_id)).member
846 # user1 is already part of c1, cannot join
847 with pytest.raises(grpc.RpcError) as e:
848 res = api.JoinCommunity(
849 communities_pb2.JoinCommunityReq(
850 community_id=c1_id,
851 )
852 )
853 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
854 assert e.value.details() == errors.ALREADY_IN_COMMUNITY
856 assert api.GetCommunity(communities_pb2.GetCommunityReq(community_id=c1_id)).member
858 # user1 is inside c1, cannot leave
859 with pytest.raises(grpc.RpcError) as e:
860 res = api.LeaveCommunity(
861 communities_pb2.LeaveCommunityReq(
862 community_id=c1_id,
863 )
864 )
865 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
866 assert e.value.details() == errors.CANNOT_LEAVE_CONTAINING_COMMUNITY
868 assert api.GetCommunity(communities_pb2.GetCommunityReq(community_id=c1_id)).member
870 with communities_session(token8) as api:
871 assert not api.GetCommunity(communities_pb2.GetCommunityReq(community_id=c1_id)).member
873 # user8 is not in c1 yet, cannot leave
874 with pytest.raises(grpc.RpcError) as e:
875 res = api.LeaveCommunity(
876 communities_pb2.LeaveCommunityReq(
877 community_id=c1_id,
878 )
879 )
880 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
881 assert e.value.details() == errors.NOT_IN_COMMUNITY
883 assert not api.GetCommunity(communities_pb2.GetCommunityReq(community_id=c1_id)).member
885 # user8 is not in c1 and not part, can join
886 res = api.JoinCommunity(
887 communities_pb2.JoinCommunityReq(
888 community_id=c1_id,
889 )
890 )
892 assert api.GetCommunity(communities_pb2.GetCommunityReq(community_id=c1_id)).member
894 # user8 is not in c1 and but now part, can't join again
895 with pytest.raises(grpc.RpcError) as e:
896 res = api.JoinCommunity(
897 communities_pb2.JoinCommunityReq(
898 community_id=c1_id,
899 )
900 )
901 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
902 assert e.value.details() == errors.ALREADY_IN_COMMUNITY
904 assert api.GetCommunity(communities_pb2.GetCommunityReq(community_id=c1_id)).member
906 # user8 is not in c1 yet, but part of it, can leave
907 res = api.LeaveCommunity(
908 communities_pb2.LeaveCommunityReq(
909 community_id=c1_id,
910 )
911 )
912 assert not api.GetCommunity(communities_pb2.GetCommunityReq(community_id=c1_id)).member
915def test_LeaveCommunity_regression(db):
916 # See github issue #1444, repro:
917 # 1. Join more than one community
918 # 2. Leave one of them
919 # 3. You are no longer in any community
920 # admin
921 user1, token1 = generate_user(username="user1", geom=create_1d_point(200), geom_radius=0.1)
922 # joiner/leaver
923 user2, token2 = generate_user(username="user2", geom=create_1d_point(201), geom_radius=0.1)
925 with session_scope() as session:
926 c0 = create_community(session, 0, 100, "Community 0", [user1], [], None)
927 c1 = create_community(session, 0, 50, "Community 1", [user1], [], c0)
928 c2 = create_community(session, 0, 10, "Community 2", [user1], [], c0)
929 c0_id = c0.id
930 c1_id = c1.id
931 c2_id = c2.id
933 enforce_community_memberships()
935 with communities_session(token1) as api:
936 assert api.GetCommunity(communities_pb2.GetCommunityReq(community_id=c0_id)).member
937 assert api.GetCommunity(communities_pb2.GetCommunityReq(community_id=c1_id)).member
938 assert api.GetCommunity(communities_pb2.GetCommunityReq(community_id=c2_id)).member
940 with communities_session(token2) as api:
941 # first check we're not in any communities
942 assert not api.GetCommunity(communities_pb2.GetCommunityReq(community_id=c0_id)).member
943 assert not api.GetCommunity(communities_pb2.GetCommunityReq(community_id=c1_id)).member
944 assert not api.GetCommunity(communities_pb2.GetCommunityReq(community_id=c2_id)).member
946 # join some communities
947 api.JoinCommunity(communities_pb2.JoinCommunityReq(community_id=c1_id))
948 api.JoinCommunity(communities_pb2.JoinCommunityReq(community_id=c2_id))
950 # check memberships
951 assert not api.GetCommunity(communities_pb2.GetCommunityReq(community_id=c0_id)).member
952 assert api.GetCommunity(communities_pb2.GetCommunityReq(community_id=c1_id)).member
953 assert api.GetCommunity(communities_pb2.GetCommunityReq(community_id=c2_id)).member
955 # leave just c2
956 api.LeaveCommunity(communities_pb2.LeaveCommunityReq(community_id=c2_id))
958 # check memberships
959 assert not api.GetCommunity(communities_pb2.GetCommunityReq(community_id=c0_id)).member
960 assert api.GetCommunity(communities_pb2.GetCommunityReq(community_id=c1_id)).member
961 assert not api.GetCommunity(communities_pb2.GetCommunityReq(community_id=c2_id)).member
964def test_enforce_community_memberships_for_user(testing_communities):
965 """
966 Make sure the user is added to the right communities on signup
967 """
968 with auth_api_session() as (auth_api, metadata_interceptor):
969 res = auth_api.SignupFlow(
970 auth_pb2.SignupFlowReq(
971 basic=auth_pb2.SignupBasic(name="testing", email="email@couchers.org.invalid"),
972 account=auth_pb2.SignupAccount(
973 username="frodo",
974 password="a very insecure password",
975 birthdate="1970-01-01",
976 gender="Bot",
977 hosting_status=api_pb2.HOSTING_STATUS_CAN_HOST,
978 city="Country 1, Region 1, City 2",
979 # lat=8, lng=1 is equivalent to creating this coordinate with create_coordinate(8)
980 lat=8,
981 lng=1,
982 radius=500,
983 accept_tos=True,
984 ),
985 feedback=auth_pb2.ContributorForm(),
986 accept_community_guidelines=wrappers_pb2.BoolValue(value=True),
987 )
988 )
989 with session_scope() as session:
990 email_token = (
991 session.execute(select(SignupFlow).where(SignupFlow.flow_token == res.flow_token)).scalar_one().email_token
992 )
993 with auth_api_session() as (auth_api, metadata_interceptor):
994 res = auth_api.SignupFlow(auth_pb2.SignupFlowReq(email_token=email_token))
995 user_id = res.auth_res.user_id
997 # now check the user is in the right communities
998 with session_scope() as session:
999 w_id = get_community_id(session, "Global")
1000 c1_id = get_community_id(session, "Country 1")
1001 c1r1_id = get_community_id(session, "Country 1, Region 1")
1002 c1r1c2_id = get_community_id(session, "Country 1, Region 1, City 2")
1004 token, _ = get_session_cookie_tokens(metadata_interceptor)
1006 with communities_session(token) as api:
1007 res = api.ListUserCommunities(communities_pb2.ListUserCommunitiesReq())
1008 assert [c.community_id for c in res.communities] == [w_id, c1_id, c1r1_id, c1r1c2_id]
1011# TODO: requires transferring of content
1013# def test_ListPlaces(db, testing_communities):
1014# pass
1016# def test_ListGuides(db, testing_communities):
1017# pass
1019# def test_ListEvents(db, testing_communities):
1020# pass