FossilRepo

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

Keyboard Shortcuts

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