|
1
|
import pytest |
|
2
|
from django.contrib.auth.models import Group, Permission, User |
|
3
|
from django.test import Client |
|
4
|
from django.urls import reverse |
|
5
|
|
|
6
|
from organization.models import OrganizationMember, OrgRole |
|
7
|
|
|
8
|
|
|
9
|
@pytest.fixture |
|
10
|
def roles(db): |
|
11
|
"""Seed default roles via management command.""" |
|
12
|
from django.core.management import call_command |
|
13
|
|
|
14
|
call_command("seed_roles") |
|
15
|
return OrgRole.objects.all() |
|
16
|
|
|
17
|
|
|
18
|
@pytest.fixture |
|
19
|
def admin_role(roles): |
|
20
|
return OrgRole.objects.get(slug="admin") |
|
21
|
|
|
22
|
|
|
23
|
@pytest.fixture |
|
24
|
def viewer_role(roles): |
|
25
|
return OrgRole.objects.get(slug="viewer") |
|
26
|
|
|
27
|
|
|
28
|
@pytest.fixture |
|
29
|
def developer_role(roles): |
|
30
|
return OrgRole.objects.get(slug="developer") |
|
31
|
|
|
32
|
|
|
33
|
@pytest.fixture |
|
34
|
def manager_role(roles): |
|
35
|
return OrgRole.objects.get(slug="manager") |
|
36
|
|
|
37
|
|
|
38
|
@pytest.fixture |
|
39
|
def target_user(db, org, admin_user): |
|
40
|
user = User.objects.create_user( |
|
41
|
username="targetuser", email="[email protected]", password="testpass123", first_name="Target", last_name="User" |
|
42
|
) |
|
43
|
OrganizationMember.objects.create(member=user, organization=org, created_by=admin_user) |
|
44
|
return user |
|
45
|
|
|
46
|
|
|
47
|
@pytest.fixture |
|
48
|
def org_admin_user(db, org): |
|
49
|
"""Non-superuser with ORGANIZATION_CHANGE permission.""" |
|
50
|
user = User.objects.create_user(username="orgadmin", email="[email protected]", password="testpass123") |
|
51
|
group, _ = Group.objects.get_or_create(name="OrgAdmins") |
|
52
|
change_perm = Permission.objects.get(content_type__app_label="organization", codename="change_organization") |
|
53
|
view_perm = Permission.objects.get(content_type__app_label="organization", codename="view_organization") |
|
54
|
view_member_perm = Permission.objects.get(content_type__app_label="organization", codename="view_organizationmember") |
|
55
|
group.permissions.add(change_perm, view_perm, view_member_perm) |
|
56
|
user.groups.add(group) |
|
57
|
OrganizationMember.objects.create(member=user, organization=org) |
|
58
|
return user |
|
59
|
|
|
60
|
|
|
61
|
@pytest.fixture |
|
62
|
def org_admin_client(org_admin_user): |
|
63
|
c = Client() |
|
64
|
c.login(username="orgadmin", password="testpass123") |
|
65
|
return c |
|
66
|
|
|
67
|
|
|
68
|
# --- OrgRole model --- |
|
69
|
|
|
70
|
|
|
71
|
@pytest.mark.django_db |
|
72
|
class TestOrgRoleModel: |
|
73
|
def test_seed_creates_four_roles(self, roles): |
|
74
|
assert OrgRole.objects.count() == 4 |
|
75
|
|
|
76
|
def test_seed_idempotent(self, roles): |
|
77
|
from django.core.management import call_command |
|
78
|
|
|
79
|
call_command("seed_roles") |
|
80
|
assert OrgRole.objects.count() == 4 |
|
81
|
|
|
82
|
def test_admin_role_has_all_app_permissions(self, admin_role): |
|
83
|
app_perms = Permission.objects.filter(content_type__app_label__in=["organization", "projects", "pages", "fossil"]).count() |
|
84
|
assert admin_role.permissions.count() == app_perms |
|
85
|
|
|
86
|
def test_viewer_role_is_default(self, viewer_role): |
|
87
|
assert viewer_role.is_default is True |
|
88
|
|
|
89
|
def test_admin_role_not_default(self, admin_role): |
|
90
|
assert admin_role.is_default is False |
|
91
|
|
|
92
|
def test_viewer_has_only_view_permissions(self, viewer_role): |
|
93
|
for perm in viewer_role.permissions.all(): |
|
94
|
assert perm.codename.startswith("view_"), f"Viewer role should only have view_ permissions, got {perm.codename}" |
|
95
|
|
|
96
|
def test_developer_has_add_page(self, developer_role): |
|
97
|
assert developer_role.permissions.filter(codename="add_page").exists() |
|
98
|
|
|
99
|
def test_developer_no_delete_project(self, developer_role): |
|
100
|
assert not developer_role.permissions.filter(codename="delete_project").exists() |
|
101
|
|
|
102
|
def test_manager_has_change_organization(self, manager_role): |
|
103
|
assert manager_role.permissions.filter(codename="change_organization").exists() |
|
104
|
|
|
105
|
|
|
106
|
# --- apply_to_user --- |
|
107
|
|
|
108
|
|
|
109
|
@pytest.mark.django_db |
|
110
|
class TestApplyToUser: |
|
111
|
def test_apply_creates_role_group(self, viewer_role, target_user): |
|
112
|
viewer_role.apply_to_user(target_user) |
|
113
|
assert target_user.groups.filter(name="role_viewer").exists() |
|
114
|
|
|
115
|
def test_apply_sets_permissions(self, viewer_role, target_user): |
|
116
|
viewer_role.apply_to_user(target_user) |
|
117
|
assert target_user.has_perm("organization.view_organization") |
|
118
|
|
|
119
|
def test_apply_replaces_old_role(self, viewer_role, admin_role, target_user): |
|
120
|
viewer_role.apply_to_user(target_user) |
|
121
|
admin_role.apply_to_user(target_user) |
|
122
|
# Should only be in admin role group now |
|
123
|
role_groups = target_user.groups.filter(name__startswith="role_") |
|
124
|
assert role_groups.count() == 1 |
|
125
|
assert role_groups.first().name == "role_admin" |
|
126
|
|
|
127
|
def test_remove_role_groups(self, viewer_role, target_user): |
|
128
|
viewer_role.apply_to_user(target_user) |
|
129
|
assert target_user.groups.filter(name__startswith="role_").count() == 1 |
|
130
|
OrgRole.remove_role_groups(target_user) |
|
131
|
assert target_user.groups.filter(name__startswith="role_").count() == 0 |
|
132
|
|
|
133
|
|
|
134
|
# --- role_list view --- |
|
135
|
|
|
136
|
|
|
137
|
@pytest.mark.django_db |
|
138
|
class TestRoleListView: |
|
139
|
def test_list_empty(self, admin_client, org): |
|
140
|
response = admin_client.get(reverse("organization:role_list")) |
|
141
|
assert response.status_code == 200 |
|
142
|
assert "No roles defined" in response.content.decode() |
|
143
|
|
|
144
|
def test_list_with_roles(self, admin_client, org, roles): |
|
145
|
response = admin_client.get(reverse("organization:role_list")) |
|
146
|
assert response.status_code == 200 |
|
147
|
content = response.content.decode() |
|
148
|
assert "Admin" in content |
|
149
|
assert "Manager" in content |
|
150
|
assert "Developer" in content |
|
151
|
assert "Viewer" in content |
|
152
|
|
|
153
|
def test_list_denied_for_no_perm(self, no_perm_client, org): |
|
154
|
response = no_perm_client.get(reverse("organization:role_list")) |
|
155
|
assert response.status_code == 403 |
|
156
|
|
|
157
|
def test_list_allowed_for_viewer(self, viewer_client, org, roles): |
|
158
|
response = viewer_client.get(reverse("organization:role_list")) |
|
159
|
assert response.status_code == 200 |
|
160
|
|
|
161
|
def test_list_denied_for_anon(self, client, org): |
|
162
|
response = client.get(reverse("organization:role_list")) |
|
163
|
assert response.status_code == 302 # redirect to login |
|
164
|
|
|
165
|
|
|
166
|
# --- role_detail view --- |
|
167
|
|
|
168
|
|
|
169
|
@pytest.mark.django_db |
|
170
|
class TestRoleDetailView: |
|
171
|
def test_detail_shows_role_info(self, admin_client, org, admin_role): |
|
172
|
response = admin_client.get(reverse("organization:role_detail", kwargs={"slug": "admin"})) |
|
173
|
assert response.status_code == 200 |
|
174
|
content = response.content.decode() |
|
175
|
assert "Admin" in content |
|
176
|
assert "Full access" in content |
|
177
|
|
|
178
|
def test_detail_shows_permissions(self, admin_client, org, viewer_role): |
|
179
|
response = admin_client.get(reverse("organization:role_detail", kwargs={"slug": "viewer"})) |
|
180
|
assert response.status_code == 200 |
|
181
|
content = response.content.decode() |
|
182
|
assert "view_organization" in content |
|
183
|
|
|
184
|
def test_detail_shows_members(self, admin_client, org, viewer_role, target_user): |
|
185
|
membership = OrganizationMember.objects.get(member=target_user, organization=org) |
|
186
|
membership.role = viewer_role |
|
187
|
membership.save() |
|
188
|
response = admin_client.get(reverse("organization:role_detail", kwargs={"slug": "viewer"})) |
|
189
|
assert response.status_code == 200 |
|
190
|
assert "targetuser" in response.content.decode() |
|
191
|
|
|
192
|
def test_detail_denied_for_no_perm(self, no_perm_client, org, viewer_role): |
|
193
|
response = no_perm_client.get(reverse("organization:role_detail", kwargs={"slug": "viewer"})) |
|
194
|
assert response.status_code == 403 |
|
195
|
|
|
196
|
def test_detail_404_for_missing_role(self, admin_client, org): |
|
197
|
response = admin_client.get(reverse("organization:role_detail", kwargs={"slug": "nonexistent"})) |
|
198
|
assert response.status_code == 404 |
|
199
|
|
|
200
|
|
|
201
|
# --- role_initialize view --- |
|
202
|
|
|
203
|
|
|
204
|
@pytest.mark.django_db |
|
205
|
class TestRoleInitializeView: |
|
206
|
def test_initialize_creates_roles(self, admin_client, org): |
|
207
|
assert OrgRole.objects.count() == 0 |
|
208
|
response = admin_client.post(reverse("organization:role_initialize")) |
|
209
|
assert response.status_code == 302 |
|
210
|
assert OrgRole.objects.count() == 4 |
|
211
|
|
|
212
|
def test_initialize_denied_for_viewer(self, viewer_client, org): |
|
213
|
response = viewer_client.post(reverse("organization:role_initialize")) |
|
214
|
assert response.status_code == 403 |
|
215
|
|
|
216
|
def test_initialize_denied_for_no_perm(self, no_perm_client, org): |
|
217
|
response = no_perm_client.post(reverse("organization:role_initialize")) |
|
218
|
assert response.status_code == 403 |
|
219
|
|
|
220
|
def test_initialize_denied_for_anon(self, client, org): |
|
221
|
response = client.post(reverse("organization:role_initialize")) |
|
222
|
assert response.status_code == 302 # redirect to login |
|
223
|
|
|
224
|
def test_initialize_allowed_for_org_admin(self, org_admin_client, org): |
|
225
|
response = org_admin_client.post(reverse("organization:role_initialize")) |
|
226
|
assert response.status_code == 302 |
|
227
|
assert OrgRole.objects.count() == 4 |
|
228
|
|
|
229
|
|
|
230
|
# --- user_create with role --- |
|
231
|
|
|
232
|
|
|
233
|
@pytest.mark.django_db |
|
234
|
class TestUserCreateWithRole: |
|
235
|
def test_create_user_with_role(self, admin_client, org, viewer_role): |
|
236
|
response = admin_client.post( |
|
237
|
reverse("organization:user_create"), |
|
238
|
{ |
|
239
|
"username": "roleuser", |
|
240
|
"email": "[email protected]", |
|
241
|
"first_name": "Role", |
|
242
|
"last_name": "User", |
|
243
|
"password1": "Str0ng!Pass99", |
|
244
|
"password2": "Str0ng!Pass99", |
|
245
|
"role": viewer_role.pk, |
|
246
|
}, |
|
247
|
) |
|
248
|
assert response.status_code == 302 |
|
249
|
user = User.objects.get(username="roleuser") |
|
250
|
membership = OrganizationMember.objects.get(member=user, organization=org) |
|
251
|
assert membership.role == viewer_role |
|
252
|
# Verify role group was applied |
|
253
|
assert user.groups.filter(name="role_viewer").exists() |
|
254
|
|
|
255
|
def test_create_user_without_role(self, admin_client, org, roles): |
|
256
|
response = admin_client.post( |
|
257
|
reverse("organization:user_create"), |
|
258
|
{ |
|
259
|
"username": "noroleuser", |
|
260
|
"email": "[email protected]", |
|
261
|
"password1": "Str0ng!Pass99", |
|
262
|
"password2": "Str0ng!Pass99", |
|
263
|
}, |
|
264
|
) |
|
265
|
assert response.status_code == 302 |
|
266
|
user = User.objects.get(username="noroleuser") |
|
267
|
membership = OrganizationMember.objects.get(member=user, organization=org) |
|
268
|
assert membership.role is None |
|
269
|
|
|
270
|
def test_create_form_has_role_field(self, admin_client, org, roles): |
|
271
|
response = admin_client.get(reverse("organization:user_create")) |
|
272
|
assert response.status_code == 200 |
|
273
|
content = response.content.decode() |
|
274
|
assert "role" in content.lower() |
|
275
|
|
|
276
|
|
|
277
|
# --- user_edit with role --- |
|
278
|
|
|
279
|
|
|
280
|
@pytest.mark.django_db |
|
281
|
class TestUserEditWithRole: |
|
282
|
def test_edit_assigns_role(self, admin_client, org, target_user, viewer_role): |
|
283
|
response = admin_client.post( |
|
284
|
reverse("organization:user_edit", kwargs={"username": "targetuser"}), |
|
285
|
{ |
|
286
|
"email": "[email protected]", |
|
287
|
"first_name": "Target", |
|
288
|
"last_name": "User", |
|
289
|
"is_active": "on", |
|
290
|
"role": viewer_role.pk, |
|
291
|
}, |
|
292
|
) |
|
293
|
assert response.status_code == 302 |
|
294
|
membership = OrganizationMember.objects.get(member=target_user, organization=org) |
|
295
|
assert membership.role == viewer_role |
|
296
|
assert target_user.groups.filter(name="role_viewer").exists() |
|
297
|
|
|
298
|
def test_edit_changes_role(self, admin_client, org, target_user, viewer_role, admin_role): |
|
299
|
# First assign viewer role |
|
300
|
membership = OrganizationMember.objects.get(member=target_user, organization=org) |
|
301
|
membership.role = viewer_role |
|
302
|
membership.save() |
|
303
|
viewer_role.apply_to_user(target_user) |
|
304
|
|
|
305
|
# Now change to admin role via edit |
|
306
|
response = admin_client.post( |
|
307
|
reverse("organization:user_edit", kwargs={"username": "targetuser"}), |
|
308
|
{ |
|
309
|
"email": "[email protected]", |
|
310
|
"first_name": "Target", |
|
311
|
"last_name": "User", |
|
312
|
"is_active": "on", |
|
313
|
"role": admin_role.pk, |
|
314
|
}, |
|
315
|
) |
|
316
|
assert response.status_code == 302 |
|
317
|
membership.refresh_from_db() |
|
318
|
assert membership.role == admin_role |
|
319
|
# Old role group should be gone, new one should be present |
|
320
|
assert not target_user.groups.filter(name="role_viewer").exists() |
|
321
|
assert target_user.groups.filter(name="role_admin").exists() |
|
322
|
|
|
323
|
def test_edit_removes_role(self, admin_client, org, target_user, viewer_role): |
|
324
|
membership = OrganizationMember.objects.get(member=target_user, organization=org) |
|
325
|
membership.role = viewer_role |
|
326
|
membership.save() |
|
327
|
viewer_role.apply_to_user(target_user) |
|
328
|
|
|
329
|
# Submit without role |
|
330
|
response = admin_client.post( |
|
331
|
reverse("organization:user_edit", kwargs={"username": "targetuser"}), |
|
332
|
{ |
|
333
|
"email": "[email protected]", |
|
334
|
"first_name": "Target", |
|
335
|
"last_name": "User", |
|
336
|
"is_active": "on", |
|
337
|
# role intentionally omitted |
|
338
|
}, |
|
339
|
) |
|
340
|
assert response.status_code == 302 |
|
341
|
membership.refresh_from_db() |
|
342
|
assert membership.role is None |
|
343
|
assert not target_user.groups.filter(name__startswith="role_").exists() |
|
344
|
|
|
345
|
def test_edit_form_pre_selects_role(self, admin_client, org, target_user, viewer_role): |
|
346
|
membership = OrganizationMember.objects.get(member=target_user, organization=org) |
|
347
|
membership.role = viewer_role |
|
348
|
membership.save() |
|
349
|
|
|
350
|
response = admin_client.get(reverse("organization:user_edit", kwargs={"username": "targetuser"})) |
|
351
|
assert response.status_code == 200 |
|
352
|
content = response.content.decode() |
|
353
|
# The viewer option should be selected |
|
354
|
assert "selected" in content |
|
355
|
|
|
356
|
|
|
357
|
# --- member_list role column --- |
|
358
|
|
|
359
|
|
|
360
|
@pytest.mark.django_db |
|
361
|
class TestMemberListRoleColumn: |
|
362
|
def test_role_shown_in_member_list(self, admin_client, org, admin_user, viewer_role): |
|
363
|
membership = OrganizationMember.objects.get(member=admin_user, organization=org) |
|
364
|
membership.role = viewer_role |
|
365
|
membership.save() |
|
366
|
|
|
367
|
response = admin_client.get(reverse("organization:members")) |
|
368
|
assert response.status_code == 200 |
|
369
|
content = response.content.decode() |
|
370
|
assert "Role" in content # Column header |
|
371
|
assert "Viewer" in content # Role name |
|
372
|
|
|
373
|
def test_no_role_shown_as_dash(self, admin_client, org, admin_user): |
|
374
|
response = admin_client.get(reverse("organization:members")) |
|
375
|
assert response.status_code == 200 |
|
376
|
content = response.content.decode() |
|
377
|
assert "Role" in content # Column header |
|
378
|
|
|
379
|
|
|
380
|
# --- user_detail role display --- |
|
381
|
|
|
382
|
|
|
383
|
@pytest.mark.django_db |
|
384
|
class TestUserDetailRole: |
|
385
|
def test_detail_shows_role(self, admin_client, org, target_user, viewer_role): |
|
386
|
membership = OrganizationMember.objects.get(member=target_user, organization=org) |
|
387
|
membership.role = viewer_role |
|
388
|
membership.save() |
|
389
|
|
|
390
|
response = admin_client.get(reverse("organization:user_detail", kwargs={"username": "targetuser"})) |
|
391
|
assert response.status_code == 200 |
|
392
|
content = response.content.decode() |
|
393
|
assert "Viewer" in content |
|
394
|
assert "Read-only access" in content |
|
395
|
|
|
396
|
def test_detail_shows_no_role_assigned(self, admin_client, org, target_user): |
|
397
|
response = admin_client.get(reverse("organization:user_detail", kwargs={"username": "targetuser"})) |
|
398
|
assert response.status_code == 200 |
|
399
|
content = response.content.decode() |
|
400
|
assert "No role assigned" in content |
|
401
|
|
|
402
|
|
|
403
|
# --- role_create view --- |
|
404
|
|
|
405
|
|
|
406
|
@pytest.mark.django_db |
|
407
|
class TestRoleCreateView: |
|
408
|
def test_create_get_shows_form(self, admin_client, org): |
|
409
|
response = admin_client.get(reverse("organization:role_create")) |
|
410
|
assert response.status_code == 200 |
|
411
|
content = response.content.decode() |
|
412
|
assert "New Role" in content |
|
413
|
assert "Permissions" in content |
|
414
|
|
|
415
|
def test_create_saves_role(self, admin_client, org): |
|
416
|
perm = Permission.objects.filter(content_type__app_label="organization", codename="view_organization").first() |
|
417
|
response = admin_client.post( |
|
418
|
reverse("organization:role_create"), |
|
419
|
{"name": "Custom Role", "description": "A custom role", "permissions": [perm.pk]}, |
|
420
|
) |
|
421
|
assert response.status_code == 302 |
|
422
|
role = OrgRole.objects.get(slug="custom-role") |
|
423
|
assert role.name == "Custom Role" |
|
424
|
assert role.description == "A custom role" |
|
425
|
assert perm in role.permissions.all() |
|
426
|
|
|
427
|
def test_create_without_permissions(self, admin_client, org): |
|
428
|
response = admin_client.post( |
|
429
|
reverse("organization:role_create"), |
|
430
|
{"name": "Empty Role", "description": "No permissions"}, |
|
431
|
) |
|
432
|
assert response.status_code == 302 |
|
433
|
role = OrgRole.objects.get(slug="empty-role") |
|
434
|
assert role.permissions.count() == 0 |
|
435
|
|
|
436
|
def test_create_with_is_default(self, admin_client, org): |
|
437
|
response = admin_client.post( |
|
438
|
reverse("organization:role_create"), |
|
439
|
{"name": "Default Custom", "description": "Default", "is_default": "on"}, |
|
440
|
) |
|
441
|
assert response.status_code == 302 |
|
442
|
role = OrgRole.objects.get(slug="default-custom") |
|
443
|
assert role.is_default is True |
|
444
|
|
|
445
|
def test_create_denied_for_viewer(self, viewer_client, org): |
|
446
|
response = viewer_client.get(reverse("organization:role_create")) |
|
447
|
assert response.status_code == 403 |
|
448
|
|
|
449
|
def test_create_denied_for_no_perm(self, no_perm_client, org): |
|
450
|
response = no_perm_client.get(reverse("organization:role_create")) |
|
451
|
assert response.status_code == 403 |
|
452
|
|
|
453
|
def test_create_denied_for_anon(self, client, org): |
|
454
|
response = client.get(reverse("organization:role_create")) |
|
455
|
assert response.status_code == 302 # redirect to login |
|
456
|
|
|
457
|
def test_create_allowed_for_org_admin(self, org_admin_client, org): |
|
458
|
response = org_admin_client.post( |
|
459
|
reverse("organization:role_create"), |
|
460
|
{"name": "OrgAdmin Role", "description": "Created by org admin"}, |
|
461
|
) |
|
462
|
assert response.status_code == 302 |
|
463
|
assert OrgRole.objects.filter(slug="orgadmin-role").exists() |
|
464
|
|
|
465
|
def test_create_sets_created_by(self, admin_client, org, admin_user): |
|
466
|
response = admin_client.post( |
|
467
|
reverse("organization:role_create"), |
|
468
|
{"name": "Tracked Role", "description": "test"}, |
|
469
|
) |
|
470
|
assert response.status_code == 302 |
|
471
|
role = OrgRole.objects.get(slug="tracked-role") |
|
472
|
assert role.created_by == admin_user |
|
473
|
|
|
474
|
def test_create_invalid_missing_name(self, admin_client, org): |
|
475
|
response = admin_client.post( |
|
476
|
reverse("organization:role_create"), |
|
477
|
{"description": "Missing name"}, |
|
478
|
) |
|
479
|
assert response.status_code == 200 # re-renders form |
|
480
|
assert OrgRole.objects.count() == 0 |
|
481
|
|
|
482
|
|
|
483
|
# --- role_edit view --- |
|
484
|
|
|
485
|
|
|
486
|
@pytest.mark.django_db |
|
487
|
class TestRoleEditView: |
|
488
|
def test_edit_get_shows_form(self, admin_client, org, viewer_role): |
|
489
|
response = admin_client.get(reverse("organization:role_edit", kwargs={"slug": "viewer"})) |
|
490
|
assert response.status_code == 200 |
|
491
|
content = response.content.decode() |
|
492
|
assert "Edit Viewer" in content |
|
493
|
assert "Permissions" in content |
|
494
|
|
|
495
|
def test_edit_updates_role(self, admin_client, org, viewer_role): |
|
496
|
response = admin_client.post( |
|
497
|
reverse("organization:role_edit", kwargs={"slug": "viewer"}), |
|
498
|
{"name": "Viewer Updated", "description": "Updated description"}, |
|
499
|
) |
|
500
|
assert response.status_code == 302 |
|
501
|
viewer_role.refresh_from_db() |
|
502
|
assert viewer_role.name == "Viewer Updated" |
|
503
|
assert viewer_role.description == "Updated description" |
|
504
|
|
|
505
|
def test_edit_updates_permissions(self, admin_client, org, viewer_role): |
|
506
|
add_perm = Permission.objects.get(content_type__app_label="organization", codename="add_organization") |
|
507
|
response = admin_client.post( |
|
508
|
reverse("organization:role_edit", kwargs={"slug": "viewer"}), |
|
509
|
{"name": "Viewer", "description": "Updated", "permissions": [add_perm.pk]}, |
|
510
|
) |
|
511
|
assert response.status_code == 302 |
|
512
|
viewer_role.refresh_from_db() |
|
513
|
assert list(viewer_role.permissions.values_list("pk", flat=True)) == [add_perm.pk] |
|
514
|
|
|
515
|
def test_edit_clears_permissions(self, admin_client, org, viewer_role): |
|
516
|
assert viewer_role.permissions.count() > 0 |
|
517
|
response = admin_client.post( |
|
518
|
reverse("organization:role_edit", kwargs={"slug": "viewer"}), |
|
519
|
{"name": "Viewer", "description": "No perms now"}, |
|
520
|
) |
|
521
|
assert response.status_code == 302 |
|
522
|
viewer_role.refresh_from_db() |
|
523
|
assert viewer_role.permissions.count() == 0 |
|
524
|
|
|
525
|
def test_edit_pre_populates_permissions(self, admin_client, org, viewer_role): |
|
526
|
response = admin_client.get(reverse("organization:role_edit", kwargs={"slug": "viewer"})) |
|
527
|
assert response.status_code == 200 |
|
528
|
content = response.content.decode() |
|
529
|
assert "checked" in content |
|
530
|
|
|
531
|
def test_edit_sets_updated_by(self, admin_client, org, viewer_role, admin_user): |
|
532
|
response = admin_client.post( |
|
533
|
reverse("organization:role_edit", kwargs={"slug": "viewer"}), |
|
534
|
{"name": "Viewer", "description": "Audit check"}, |
|
535
|
) |
|
536
|
assert response.status_code == 302 |
|
537
|
viewer_role.refresh_from_db() |
|
538
|
assert viewer_role.updated_by == admin_user |
|
539
|
|
|
540
|
def test_edit_denied_for_viewer(self, viewer_client, org, viewer_role): |
|
541
|
response = viewer_client.get(reverse("organization:role_edit", kwargs={"slug": "viewer"})) |
|
542
|
assert response.status_code == 403 |
|
543
|
|
|
544
|
def test_edit_denied_for_no_perm(self, no_perm_client, org, viewer_role): |
|
545
|
response = no_perm_client.get(reverse("organization:role_edit", kwargs={"slug": "viewer"})) |
|
546
|
assert response.status_code == 403 |
|
547
|
|
|
548
|
def test_edit_denied_for_anon(self, client, org, viewer_role): |
|
549
|
response = client.get(reverse("organization:role_edit", kwargs={"slug": "viewer"})) |
|
550
|
assert response.status_code == 302 # redirect to login |
|
551
|
|
|
552
|
def test_edit_404_for_deleted_role(self, admin_client, org, viewer_role, admin_user): |
|
553
|
viewer_role.soft_delete(user=admin_user) |
|
554
|
response = admin_client.get(reverse("organization:role_edit", kwargs={"slug": "viewer"})) |
|
555
|
assert response.status_code == 404 |
|
556
|
|
|
557
|
def test_edit_404_for_missing_role(self, admin_client, org): |
|
558
|
response = admin_client.get(reverse("organization:role_edit", kwargs={"slug": "nonexistent"})) |
|
559
|
assert response.status_code == 404 |
|
560
|
|
|
561
|
|
|
562
|
# --- role_delete view --- |
|
563
|
|
|
564
|
|
|
565
|
@pytest.mark.django_db |
|
566
|
class TestRoleDeleteView: |
|
567
|
def test_delete_get_shows_confirmation(self, admin_client, org, viewer_role): |
|
568
|
response = admin_client.get(reverse("organization:role_delete", kwargs={"slug": "viewer"})) |
|
569
|
assert response.status_code == 200 |
|
570
|
content = response.content.decode() |
|
571
|
assert "Delete Role" in content |
|
572
|
assert "Viewer" in content |
|
573
|
|
|
574
|
def test_delete_soft_deletes_role(self, admin_client, org, viewer_role): |
|
575
|
response = admin_client.post(reverse("organization:role_delete", kwargs={"slug": "viewer"})) |
|
576
|
assert response.status_code == 302 |
|
577
|
viewer_role.refresh_from_db() |
|
578
|
assert viewer_role.deleted_at is not None |
|
579
|
|
|
580
|
def test_delete_blocked_when_members_assigned(self, admin_client, org, viewer_role, target_user): |
|
581
|
membership = OrganizationMember.objects.get(member=target_user, organization=org) |
|
582
|
membership.role = viewer_role |
|
583
|
membership.save() |
|
584
|
|
|
585
|
response = admin_client.post(reverse("organization:role_delete", kwargs={"slug": "viewer"})) |
|
586
|
assert response.status_code == 302 # redirects back to detail |
|
587
|
viewer_role.refresh_from_db() |
|
588
|
assert viewer_role.deleted_at is None # not deleted |
|
589
|
|
|
590
|
def test_delete_shows_warning_for_members(self, admin_client, org, viewer_role, target_user): |
|
591
|
membership = OrganizationMember.objects.get(member=target_user, organization=org) |
|
592
|
membership.role = viewer_role |
|
593
|
membership.save() |
|
594
|
|
|
595
|
response = admin_client.get(reverse("organization:role_delete", kwargs={"slug": "viewer"})) |
|
596
|
assert response.status_code == 200 |
|
597
|
content = response.content.decode() |
|
598
|
assert "active member" in content |
|
599
|
assert "targetuser" in content |
|
600
|
|
|
601
|
def test_delete_denied_for_viewer(self, viewer_client, org, viewer_role): |
|
602
|
response = viewer_client.get(reverse("organization:role_delete", kwargs={"slug": "viewer"})) |
|
603
|
assert response.status_code == 403 |
|
604
|
|
|
605
|
def test_delete_denied_for_no_perm(self, no_perm_client, org, viewer_role): |
|
606
|
response = no_perm_client.get(reverse("organization:role_delete", kwargs={"slug": "viewer"})) |
|
607
|
assert response.status_code == 403 |
|
608
|
|
|
609
|
def test_delete_denied_for_anon(self, client, org, viewer_role): |
|
610
|
response = client.get(reverse("organization:role_delete", kwargs={"slug": "viewer"})) |
|
611
|
assert response.status_code == 302 # redirect to login |
|
612
|
|
|
613
|
def test_delete_404_for_deleted_role(self, admin_client, org, viewer_role, admin_user): |
|
614
|
viewer_role.soft_delete(user=admin_user) |
|
615
|
response = admin_client.get(reverse("organization:role_delete", kwargs={"slug": "viewer"})) |
|
616
|
assert response.status_code == 404 |
|
617
|
|
|
618
|
def test_delete_htmx_returns_redirect_header(self, admin_client, org, developer_role): |
|
619
|
response = admin_client.post( |
|
620
|
reverse("organization:role_delete", kwargs={"slug": "developer"}), |
|
621
|
HTTP_HX_REQUEST="true", |
|
622
|
) |
|
623
|
assert response.status_code == 200 |
|
624
|
assert response.headers.get("HX-Redirect") == "/settings/roles/" |
|
625
|
|
|
626
|
|
|
627
|
# --- role_list Create Role button --- |
|
628
|
|
|
629
|
|
|
630
|
@pytest.mark.django_db |
|
631
|
class TestRoleListCreateButton: |
|
632
|
def test_create_button_shown_for_admin(self, admin_client, org, roles): |
|
633
|
response = admin_client.get(reverse("organization:role_list")) |
|
634
|
assert response.status_code == 200 |
|
635
|
assert "Create Role" in response.content.decode() |
|
636
|
|
|
637
|
def test_create_button_hidden_for_viewer(self, viewer_client, org, roles): |
|
638
|
response = viewer_client.get(reverse("organization:role_list")) |
|
639
|
assert response.status_code == 200 |
|
640
|
assert "Create Role" not in response.content.decode() |
|
641
|
|
|
642
|
|
|
643
|
# --- role_detail Edit/Delete buttons --- |
|
644
|
|
|
645
|
|
|
646
|
@pytest.mark.django_db |
|
647
|
class TestRoleDetailButtons: |
|
648
|
def test_edit_button_shown_for_admin(self, admin_client, org, viewer_role): |
|
649
|
response = admin_client.get(reverse("organization:role_detail", kwargs={"slug": "viewer"})) |
|
650
|
assert response.status_code == 200 |
|
651
|
content = response.content.decode() |
|
652
|
assert "Edit" in content |
|
653
|
assert "Delete" in content |
|
654
|
|
|
655
|
def test_edit_button_hidden_for_viewer(self, viewer_client, org, viewer_role): |
|
656
|
response = viewer_client.get(reverse("organization:role_detail", kwargs={"slug": "viewer"})) |
|
657
|
assert response.status_code == 200 |
|
658
|
content = response.content.decode() |
|
659
|
assert "Edit" not in content or "role_edit" not in content |
|
660
|
|