Coverage for app / backend / src / tests / test_model_constraints.py: 97%
133 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-05-05 09:44 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-05-05 09:44 +0000
1import pytest
2from sqlalchemy.exc import IntegrityError
3from sqlalchemy.sql import func
5from couchers.db import session_scope
6from couchers.models import (
7 ActivenessProbe,
8 ActivenessProbeStatus,
9 Cluster,
10 FriendRelationship,
11 FriendStatus,
12 ModerationObjectType,
13 ModerationState,
14 ModerationVisibility,
15 Node,
16 NodeType,
17 Page,
18 PageType,
19 PageVersion,
20 Thread,
21)
22from couchers.utils import create_polygon_lat_lng, to_multi
23from tests.fixtures.db import generate_user
24from tests.test_communities import create_1d_polygon, create_community
27@pytest.fixture(autouse=True)
28def _(testconfig):
29 pass
32def test_node_constraints(db):
33 # check we can't have two official clusters for a given node
34 with pytest.raises(IntegrityError) as e:
35 with session_scope() as session:
36 node = Node(geom=to_multi(create_1d_polygon(0, 2)), node_type=NodeType.world)
37 session.add(node)
38 session.flush()
39 cluster1 = Cluster(
40 name="Testing community, cluster 1",
41 description="Testing community description",
42 parent_node_id=node.id,
43 is_official_cluster=True,
44 )
45 session.add(cluster1)
46 cluster2 = Cluster(
47 name="Testing community, cluster 2",
48 description="Testing community description",
49 parent_node_id=node.id,
50 is_official_cluster=True,
51 )
52 session.add(cluster2)
53 assert "violates unique constraint" in str(e.value)
54 assert "ix_clusters_owner_parent_node_id_is_official_cluster" in str(e.value)
57def test_page_constraints(db):
58 user, token = generate_user()
60 with session_scope() as session:
61 c_id = create_community(session, 0, 2, "Root node", [user], [], None).id
63 # check we can't create a page without an owner
64 with pytest.raises(IntegrityError) as e:
65 with session_scope() as session:
66 thread = Thread()
67 session.add(thread)
68 session.flush()
69 page = Page(
70 parent_node_id=c_id,
71 # note no owner
72 creator_user_id=user.id,
73 type=PageType.guide,
74 thread_id=thread.id,
75 )
76 session.add(page)
77 session.flush()
78 session.add(
79 PageVersion(
80 page_id=page.id,
81 editor_user_id=user.id,
82 title="Title",
83 content="Content",
84 )
85 )
86 assert "violates check constraint" in str(e.value)
87 assert "one_owner" in str(e.value)
89 with session_scope() as session:
90 node = Node(
91 geom=to_multi(create_polygon_lat_lng([[0, 0], [0, 2], [2, 2], [2, 0], [0, 0]])), node_type=NodeType.world
92 )
93 session.add(node)
94 session.flush()
95 cluster = Cluster(
96 name="Testing Community",
97 description="Description for testing community",
98 parent_node_id=node.id,
99 )
100 session.add(cluster)
101 session.flush()
102 cluster_parent_id = cluster.parent_node_id
103 cluster_id = cluster.id
105 # check we can't create a page with two owners
106 with pytest.raises(IntegrityError) as e:
107 with session_scope() as session:
108 thread = Thread()
109 session.add(thread)
110 session.flush()
111 page = Page(
112 parent_node_id=cluster_parent_id,
113 creator_user_id=user.id,
114 owner_cluster_id=cluster_id,
115 owner_user_id=user.id,
116 type=PageType.guide,
117 thread_id=thread.id,
118 )
119 session.add(page)
120 session.flush()
121 session.add(
122 PageVersion(
123 page_id=page.id,
124 editor_user_id=user.id,
125 title="Title",
126 content="Content",
127 )
128 )
129 assert "violates check constraint" in str(e.value)
130 assert "one_owner" in str(e.value)
132 # main page must be owned by the right cluster
133 with pytest.raises(IntegrityError) as e:
134 with session_scope() as session:
135 thread = Thread()
136 session.add(thread)
137 session.flush()
138 main_page = Page(
139 parent_node_id=cluster_parent_id,
140 # note owner is not cluster
141 creator_user_id=user.id,
142 owner_user_id=user.id,
143 type=PageType.main_page,
144 thread_id=thread.id,
145 )
146 session.add(main_page)
147 session.flush()
148 session.add(
149 PageVersion(
150 page_id=main_page.id,
151 editor_user_id=user.id,
152 title="Main page for the testing community",
153 content="Empty.",
154 )
155 )
156 assert "violates check constraint" in str(e.value)
157 assert "main_page_owned_by_cluster" in str(e.value)
159 # can only have one main page
160 with pytest.raises(IntegrityError) as e:
161 with session_scope() as session:
162 thread1 = Thread()
163 session.add(thread1)
164 session.flush()
165 main_page1 = Page(
166 parent_node_id=cluster_parent_id,
167 creator_user_id=user.id,
168 owner_cluster_id=cluster_id,
169 type=PageType.main_page,
170 thread_id=thread1.id,
171 )
172 session.add(main_page1)
173 session.flush()
174 session.add(
175 PageVersion(
176 page_id=main_page1.id,
177 editor_user_id=user.id,
178 title="Main page 1 for the testing community",
179 content="Empty.",
180 )
181 )
182 thread2 = Thread()
183 session.add(thread2)
184 session.flush()
185 main_page2 = Page(
186 parent_node_id=cluster_parent_id,
187 creator_user_id=user.id,
188 owner_cluster_id=cluster_id,
189 type=PageType.main_page,
190 thread_id=thread2.id,
191 )
192 session.add(main_page2)
193 session.flush()
194 session.add(
195 PageVersion(
196 page_id=main_page2.id,
197 editor_user_id=user.id,
198 title="Main page 2 for the testing community",
199 content="Empty.",
200 )
201 )
202 assert "violates unique constraint" in str(e.value)
203 assert "ix_pages_owner_cluster_id_type" in str(e.value)
206def test_activeness_probes_cant_have_multiple(db):
207 # can't have two active activeness probes for a given user
208 user, token = generate_user()
210 with session_scope() as session:
211 # we can create one
212 first_probe = ActivenessProbe(user_id=user.id)
213 session.add(first_probe)
214 session.commit()
216 # change it to expired
217 first_probe.response = ActivenessProbeStatus.expired
218 first_probe.responded = func.now()
219 session.commit()
221 # can create another one
222 session.add(ActivenessProbe(user_id=user.id))
223 session.commit()
225 # can't create one more
226 with pytest.raises(IntegrityError) as e:
227 with session_scope() as session:
228 session.add(ActivenessProbe(user_id=user.id))
229 assert "violates unique constraint" in str(e.value)
232def _add_friend_relationship(session, from_user_id, to_user_id, status):
233 moderation_state = ModerationState(
234 object_type=ModerationObjectType.friend_request,
235 object_id=0,
236 visibility=ModerationVisibility.visible,
237 )
238 session.add(moderation_state)
239 session.flush()
240 fr = FriendRelationship(
241 from_user_id=from_user_id,
242 to_user_id=to_user_id,
243 status=status,
244 moderation_state_id=moderation_state.id,
245 )
246 session.add(fr)
247 session.flush()
248 moderation_state.object_id = fr.id
249 return fr
252def test_friend_relationship_unique_active_pair(db):
253 # can't have two active (pending/accepted) FriendRelationship rows for the same user pair,
254 # regardless of direction
255 user1, _ = generate_user()
256 user2, _ = generate_user()
257 user3, _ = generate_user()
259 # baseline: one pending relationship is fine
260 with session_scope() as session:
261 _add_friend_relationship(session, user1.id, user2.id, FriendStatus.pending)
263 # can't add a second active row in the same direction
264 with pytest.raises(IntegrityError) as e:
265 with session_scope() as session:
266 _add_friend_relationship(session, user1.id, user2.id, FriendStatus.pending)
267 assert "violates unique constraint" in str(e.value)
268 assert "uq_friend_relationships_active_pair" in str(e.value)
270 # can't add a second active row in the reverse direction either
271 with pytest.raises(IntegrityError) as e:
272 with session_scope() as session:
273 _add_friend_relationship(session, user2.id, user1.id, FriendStatus.accepted)
274 assert "violates unique constraint" in str(e.value)
275 assert "uq_friend_relationships_active_pair" in str(e.value)
277 # a cancelled or rejected row for the same pair is allowed alongside the active one
278 with session_scope() as session:
279 _add_friend_relationship(session, user1.id, user2.id, FriendStatus.cancelled)
280 _add_friend_relationship(session, user2.id, user1.id, FriendStatus.rejected)
282 # and active rows for different pairs are unaffected
283 with session_scope() as session:
284 _add_friend_relationship(session, user1.id, user3.id, FriendStatus.pending)
285 _add_friend_relationship(session, user3.id, user2.id, FriendStatus.accepted)