FossilRepo

fossilrepo / tests / test_roles.py
Blame History Raw 660 lines
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

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button