Coverage for ckanext/udc/tests/test_user_actions.py: 100%
193 statements
« prev ^ index » next coverage.py v7.7.1, created at 2026-01-19 23:48 +0000
« prev ^ index » next coverage.py v7.7.1, created at 2026-01-19 23:48 +0000
1"""
2Unit tests for user management actions.
3"""
4import pytest
5import ckan.model as model
6import ckan.plugins.toolkit as tk
7from ckan.tests import helpers, factories
10@pytest.mark.usefixtures("clean_db", "with_plugins")
11class TestDeletedUsersList:
12 """Tests for the deleted_users_list action."""
14 def test_list_deleted_users_as_sysadmin(self):
15 """Sysadmin can list deleted users."""
16 # Create sysadmin
17 sysadmin = factories.Sysadmin()
19 # Create and delete a user
20 user = factories.User()
21 user_obj = model.User.get(user['id'])
22 user_obj.delete()
23 model.Session.commit()
25 # List deleted users
26 context = {'user': sysadmin['name'], 'ignore_auth': False}
27 result = helpers.call_action('deleted_users_list', context=context)
29 # Verify
30 assert isinstance(result, dict)
31 assert result["total"] > 0
32 assert isinstance(result["results"], list)
34 # Find our deleted user
35 deleted_user = next((u for u in result["results"] if u['id'] == user['id']), None)
36 assert deleted_user is not None
37 assert deleted_user['name'] == user['name']
38 assert deleted_user['state'] == 'deleted'
39 assert 'email' in deleted_user
40 assert 'created' in deleted_user
41 assert 'about' in deleted_user
43 def test_list_deleted_users_as_normal_user(self):
44 """Normal users cannot list deleted users."""
45 # Create normal user
46 user = factories.User()
48 # Try to list deleted users
49 context = {'user': user['name'], 'ignore_auth': False}
51 with pytest.raises(tk.NotAuthorized):
52 helpers.call_action('deleted_users_list', context=context)
54 def test_list_deleted_users_anonymous(self):
55 """Anonymous users cannot list deleted users."""
56 context = {'user': None, 'ignore_auth': False}
58 with pytest.raises(tk.NotAuthorized):
59 helpers.call_action('deleted_users_list', context=context)
61 def test_list_deleted_users_empty(self):
62 """Returns empty list when no deleted users exist."""
63 # Create sysadmin
64 sysadmin = factories.Sysadmin()
66 # Purge any existing deleted users first
67 context = {'user': sysadmin['name'], 'ignore_auth': True}
68 helpers.call_action('purge_deleted_users', context=context, ids=[])
70 # List deleted users
71 result = helpers.call_action('deleted_users_list', context=context)
73 assert isinstance(result, dict)
74 assert result["total"] == 0
75 assert result["results"] == []
77 def test_list_multiple_deleted_users(self):
78 """Can list multiple deleted users."""
79 # Create sysadmin
80 sysadmin = factories.Sysadmin()
82 # Create and delete multiple users
83 user1 = factories.User()
84 user2 = factories.User()
85 user3 = factories.User()
87 for user in [user1, user2, user3]:
88 user_obj = model.User.get(user['id'])
89 user_obj.delete()
90 model.Session.commit()
92 # List deleted users
93 context = {'user': sysadmin['name'], 'ignore_auth': False}
94 result = helpers.call_action('deleted_users_list', context=context)
96 # Verify all three are in the list
97 deleted_ids = [u['id'] for u in result["results"]]
98 assert user1['id'] in deleted_ids
99 assert user2['id'] in deleted_ids
100 assert user3['id'] in deleted_ids
102 def test_list_does_not_include_active_users(self):
103 """Active users are not included in deleted users list."""
104 # Create sysadmin and active user
105 sysadmin = factories.Sysadmin()
106 active_user = factories.User()
108 # List deleted users
109 context = {'user': sysadmin['name'], 'ignore_auth': False}
110 result = helpers.call_action('deleted_users_list', context=context)
112 # Verify active user is not in the list
113 deleted_ids = [u['id'] for u in result["results"]]
114 assert active_user['id'] not in deleted_ids
117@pytest.mark.usefixtures("clean_db", "with_plugins")
118class TestPurgeDeletedUsers:
119 """Tests for the purge_deleted_users action."""
121 def test_purge_deleted_users_as_sysadmin(self):
122 """Sysadmin can purge deleted users."""
123 # Create sysadmin
124 sysadmin = factories.Sysadmin()
126 # Create and delete a user
127 user = factories.User()
128 user_id = user['id']
129 user_obj = model.User.get(user_id)
130 user_obj.delete()
131 model.Session.commit()
133 # Purge deleted users
134 context = {'user': sysadmin['name'], 'ignore_auth': False}
135 result = helpers.call_action('purge_deleted_users', context=context, ids=[])
137 # Verify result
138 assert result['success'] is True
139 assert result['count'] >= 1
140 assert 'message' in result
142 # Verify user is completely gone
143 purged_user = model.User.get(user_id)
144 assert purged_user is None
146 def test_purge_deleted_users_as_normal_user(self):
147 """Normal users cannot purge deleted users."""
148 # Create normal user
149 user = factories.User()
151 # Try to purge deleted users
152 context = {'user': user['name'], 'ignore_auth': False}
154 with pytest.raises(tk.NotAuthorized):
155 helpers.call_action('purge_deleted_users', context=context)
157 def test_purge_deleted_users_anonymous(self):
158 """Anonymous users cannot purge deleted users."""
159 context = {'user': None, 'ignore_auth': False}
161 with pytest.raises(tk.NotAuthorized):
162 helpers.call_action('purge_deleted_users', context=context)
164 def test_purge_deleted_users_empty(self):
165 """Purge returns zero count when no deleted users exist."""
166 # Create sysadmin
167 sysadmin = factories.Sysadmin()
169 # Purge any existing deleted users first
170 context = {'user': sysadmin['name'], 'ignore_auth': True}
171 helpers.call_action('purge_deleted_users', context=context, ids=[])
173 # Purge again
174 result = helpers.call_action('purge_deleted_users', context=context, ids=[])
176 assert result['success'] is True
177 assert result['count'] == 0
179 def test_purge_multiple_deleted_users(self):
180 """Can purge multiple deleted users at once."""
181 # Create sysadmin
182 sysadmin = factories.Sysadmin()
184 # Create and delete multiple users
185 user_ids = []
186 for i in range(3):
187 user = factories.User()
188 user_ids.append(user['id'])
189 user_obj = model.User.get(user['id'])
190 user_obj.delete()
191 model.Session.commit()
193 # Purge deleted users
194 context = {'user': sysadmin['name'], 'ignore_auth': False}
195 result = helpers.call_action('purge_deleted_users', context=context, ids=user_ids)
197 # Verify result
198 assert result['success'] is True
199 assert result['count'] >= 3
201 # Verify all users are completely gone
202 for user_id in user_ids:
203 purged_user = model.User.get(user_id)
204 assert purged_user is None
206 def test_purge_does_not_affect_active_users(self):
207 """Active users are not affected by purge."""
208 # Create sysadmin and active user
209 sysadmin = factories.Sysadmin()
210 active_user = factories.User()
212 # Purge deleted users
213 context = {'user': sysadmin['name'], 'ignore_auth': False}
214 helpers.call_action('purge_deleted_users', context=context, ids=[])
216 # Verify active user still exists
217 user_obj = model.User.get(active_user['id'])
218 assert user_obj is not None
219 assert user_obj.state == 'active'
221 def test_purge_removes_user_memberships(self):
222 """Purge removes user's group and organization memberships."""
223 # Create sysadmin
224 sysadmin = factories.Sysadmin()
226 # Create user and organization
227 user = factories.User()
228 org = factories.Organization(
229 users=[{'name': user['name'], 'capacity': 'member'}]
230 )
232 # Verify membership exists
233 member = model.Session.query(model.Member).filter(
234 model.Member.table_id == user['id'],
235 model.Member.group_id == org['id']
236 ).first()
237 assert member is not None
239 # Delete and purge user
240 user_obj = model.User.get(user['id'])
241 user_obj.delete()
242 model.Session.commit()
244 context = {'user': sysadmin['name'], 'ignore_auth': False}
245 helpers.call_action('purge_deleted_users', context=context, ids=[])
247 # Verify membership is gone
248 member = model.Session.query(model.Member).filter(
249 model.Member.table_id == user['id'],
250 model.Member.group_id == org['id']
251 ).first()
252 assert member is None
254 def test_purge_workflow(self):
255 """Complete workflow: list, purge, verify."""
256 # Create sysadmin
257 sysadmin = factories.Sysadmin()
258 context = {'user': sysadmin['name'], 'ignore_auth': False}
260 # Create and delete users
261 user1 = factories.User()
262 user2 = factories.User()
264 for user in [user1, user2]:
265 user_obj = model.User.get(user['id'])
266 user_obj.delete()
267 model.Session.commit()
269 # Step 1: List deleted users
270 deleted_list = helpers.call_action('deleted_users_list', context=context)
271 initial_count = deleted_list["total"]
272 assert initial_count >= 2
274 deleted_ids = [u['id'] for u in deleted_list["results"]]
275 assert user1['id'] in deleted_ids
276 assert user2['id'] in deleted_ids
278 # Step 2: Purge deleted users
279 purge_result = helpers.call_action('purge_deleted_users', context=context, ids=deleted_ids)
280 assert purge_result['success'] is True
281 assert purge_result['count'] == initial_count
283 # Step 3: Verify list is now empty
284 deleted_list_after = helpers.call_action('deleted_users_list', context=context)
285 assert deleted_list_after["total"] == 0
287 # Step 4: Verify users are completely gone
288 assert model.User.get(user1['id']) is None
289 assert model.User.get(user2['id']) is None
292@pytest.mark.usefixtures("clean_db", "with_plugins")
293class TestUserManagementIntegration:
294 """Integration tests for user management APIs."""
296 def test_deleted_user_datasets_remain(self):
297 """Datasets created by deleted users remain after purge."""
298 # Create sysadmin and user
299 sysadmin = factories.Sysadmin()
300 user = factories.User()
302 # Create dataset as user
303 dataset = factories.Dataset(user=user)
304 dataset_id = dataset['id']
306 # Delete and purge user
307 user_obj = model.User.get(user['id'])
308 user_obj.delete()
309 model.Session.commit()
311 context = {'user': sysadmin['name'], 'ignore_auth': False}
312 helpers.call_action('purge_deleted_users', context=context, ids=[])
314 # Verify dataset still exists
315 dataset_obj = model.Package.get(dataset_id)
316 assert dataset_obj is not None
317 assert dataset_obj.state == 'active'
319 def test_cannot_purge_with_api_key_from_deleted_user(self):
320 """Cannot use API key from a deleted user."""
321 # Create user
322 user = factories.User()
323 api_key = user['apikey']
325 # Delete user
326 user_obj = model.User.get(user['id'])
327 user_obj.delete()
328 model.Session.commit()
330 # Try to use deleted user's API key
331 context = {'user': user['name'], 'ignore_auth': False}
333 # This should fail because the user is deleted
334 with pytest.raises((tk.NotAuthorized, tk.ObjectNotFound)):
335 helpers.call_action('purge_deleted_users', context=context, ids=[])
338@pytest.mark.usefixtures("clean_db", "with_plugins")
339class TestUserList:
340 """Tests for the udc_user_list action."""
342 def test_list_users_as_sysadmin(self):
343 sysadmin = factories.Sysadmin()
344 user = factories.User()
346 context = {'user': sysadmin['name'], 'ignore_auth': False}
347 result = helpers.call_action('udc_user_list', context=context, page=1, page_size=25)
349 assert isinstance(result, dict)
350 assert result["total"] >= 1
351 assert any(u["id"] == user["id"] for u in result["results"])
353 def test_list_users_as_normal_user(self):
354 user = factories.User()
355 context = {'user': user['name'], 'ignore_auth': False}
357 with pytest.raises(tk.NotAuthorized):
358 helpers.call_action('udc_user_list', context=context, page=1, page_size=25)