|
1
|
"""Tests for Unified Audit Log: view, permissions, filtering.""" |
|
2
|
|
|
3
|
import pytest |
|
4
|
from django.contrib.auth.models import Group, Permission |
|
5
|
|
|
6
|
from organization.models import Team |
|
7
|
from projects.models import Project |
|
8
|
|
|
9
|
|
|
10
|
@pytest.fixture |
|
11
|
def org_admin_user(db): |
|
12
|
"""User with ORGANIZATION_CHANGE permission but not superuser.""" |
|
13
|
user = __import__("django.contrib.auth.models", fromlist=["User"]).User.objects.create_user( |
|
14
|
username="orgadmin", email="[email protected]", password="testpass123" |
|
15
|
) |
|
16
|
group, _ = Group.objects.get_or_create(name="OrgAdmins") |
|
17
|
perms = Permission.objects.filter( |
|
18
|
content_type__app_label="organization", |
|
19
|
) |
|
20
|
group.permissions.set(perms) |
|
21
|
user.groups.add(group) |
|
22
|
return user |
|
23
|
|
|
24
|
|
|
25
|
@pytest.fixture |
|
26
|
def org_admin_client(client, org_admin_user): |
|
27
|
client.login(username="orgadmin", password="testpass123") |
|
28
|
return client |
|
29
|
|
|
30
|
|
|
31
|
# --- Access Control --- |
|
32
|
|
|
33
|
|
|
34
|
@pytest.mark.django_db |
|
35
|
class TestAuditLogAccess: |
|
36
|
def test_audit_log_accessible_to_superuser(self, admin_client): |
|
37
|
response = admin_client.get("/settings/audit/") |
|
38
|
assert response.status_code == 200 |
|
39
|
assert "Audit Log" in response.content.decode() |
|
40
|
|
|
41
|
def test_audit_log_accessible_to_org_admin(self, org_admin_client): |
|
42
|
response = org_admin_client.get("/settings/audit/") |
|
43
|
assert response.status_code == 200 |
|
44
|
|
|
45
|
def test_audit_log_denied_for_viewer(self, viewer_client): |
|
46
|
response = viewer_client.get("/settings/audit/") |
|
47
|
assert response.status_code == 403 |
|
48
|
|
|
49
|
def test_audit_log_denied_for_no_perm(self, no_perm_client): |
|
50
|
response = no_perm_client.get("/settings/audit/") |
|
51
|
assert response.status_code == 403 |
|
52
|
|
|
53
|
def test_audit_log_denied_for_anon(self, client): |
|
54
|
response = client.get("/settings/audit/") |
|
55
|
assert response.status_code == 302 # Redirect to login |
|
56
|
|
|
57
|
|
|
58
|
# --- Content --- |
|
59
|
|
|
60
|
|
|
61
|
@pytest.mark.django_db |
|
62
|
class TestAuditLogContent: |
|
63
|
def test_shows_project_history(self, admin_client, admin_user, org): |
|
64
|
Project.objects.create(name="Audit Test Project", organization=org, created_by=admin_user) |
|
65
|
response = admin_client.get("/settings/audit/") |
|
66
|
content = response.content.decode() |
|
67
|
assert "Audit Test Project" in content |
|
68
|
assert "Created" in content |
|
69
|
|
|
70
|
def test_shows_organization_history(self, admin_client, org): |
|
71
|
response = admin_client.get("/settings/audit/") |
|
72
|
content = response.content.decode() |
|
73
|
assert "Organization" in content |
|
74
|
|
|
75
|
def test_shows_team_history(self, admin_client, admin_user, org): |
|
76
|
Team.objects.create(name="Audit Test Team", organization=org, created_by=admin_user) |
|
77
|
response = admin_client.get("/settings/audit/") |
|
78
|
content = response.content.decode() |
|
79
|
assert "Audit Test Team" in content |
|
80
|
|
|
81
|
def test_filter_by_model_type(self, admin_client, admin_user, org): |
|
82
|
Project.objects.create(name="Filter Test", organization=org, created_by=admin_user) |
|
83
|
Team.objects.create(name="Should Not Show", organization=org, created_by=admin_user) |
|
84
|
response = admin_client.get("/settings/audit/?model=Project") |
|
85
|
content = response.content.decode() |
|
86
|
assert "Filter Test" in content |
|
87
|
assert "Should Not Show" not in content |
|
88
|
|
|
89
|
def test_filter_shows_all_when_no_filter(self, admin_client, admin_user, org): |
|
90
|
Project.objects.create(name="Project Entry", organization=org, created_by=admin_user) |
|
91
|
Team.objects.create(name="Team Entry", organization=org, created_by=admin_user) |
|
92
|
response = admin_client.get("/settings/audit/") |
|
93
|
content = response.content.decode() |
|
94
|
assert "Project Entry" in content |
|
95
|
assert "Team Entry" in content |
|
96
|
|
|
97
|
def test_audit_log_entries_sorted_by_date(self, admin_client, admin_user, org): |
|
98
|
Project.objects.create(name="First Project", organization=org, created_by=admin_user) |
|
99
|
Project.objects.create(name="Second Project", organization=org, created_by=admin_user) |
|
100
|
response = admin_client.get("/settings/audit/?model=Project") |
|
101
|
entries = response.context["entries"] |
|
102
|
# Most recent first |
|
103
|
project_entries = [e for e in entries if e["model"] == "Project"] |
|
104
|
dates = [e["date"] for e in project_entries] |
|
105
|
assert dates == sorted(dates, reverse=True) |
|
106
|
|
|
107
|
def test_available_models_in_context(self, admin_client): |
|
108
|
response = admin_client.get("/settings/audit/") |
|
109
|
assert "available_models" in response.context |
|
110
|
assert "Project" in response.context["available_models"] |
|
111
|
assert "Organization" in response.context["available_models"] |
|
112
|
assert "Team" in response.context["available_models"] |
|
113
|
assert "FossilRepository" in response.context["available_models"] |
|
114
|
|
|
115
|
def test_audit_log_sidebar_link_for_superuser(self, admin_client): |
|
116
|
response = admin_client.get("/dashboard/") |
|
117
|
assert "/settings/audit/" in response.content.decode() |
|
118
|
|
|
119
|
def test_audit_log_sidebar_link_hidden_for_viewer(self, viewer_client): |
|
120
|
response = viewer_client.get("/dashboard/") |
|
121
|
assert "/settings/audit/" not in response.content.decode() |
|
122
|
|