from __future__ import annotations from datetime import timedelta import pytest from auditlog.models import LogEntry from rest_framework_simplejwt.tokens import AccessToken from rest_framework.test import APIClient from apps.reports.models import ReportExportJob from apps.users.models import User from apps.workspaces.models import Workspace, WorkspaceMembership @pytest.fixture() def api_client(): return APIClient() def _user(index: int) -> User: return User.objects.create_user( mobile=f"093355500{index:02d}", password="secret123", first_name=f"Log{index}", last_name="User", ) @pytest.fixture() def owner(db): return _user(1) @pytest.fixture() def admin(db): return _user(2) @pytest.fixture() def member(db): return _user(3) @pytest.fixture() def outsider(db): return _user(4) @pytest.fixture() def workspace(owner, admin, member): workspace = Workspace.objects.create(name="Logs WS", description="", owner=owner) WorkspaceMembership.objects.create( workspace=workspace, user=admin, role=WorkspaceMembership.Role.ADMIN, is_active=True, ) WorkspaceMembership.objects.create( workspace=workspace, user=member, role=WorkspaceMembership.Role.MEMBER, is_active=True, ) return workspace def _auth_headers(user: User) -> dict: token = str(AccessToken.for_user(user)) return {"HTTP_AUTHORIZATION": f"Bearer {token}"} def _create_tag(client: APIClient, user: User, workspace: Workspace, *, name="Audit Tag"): return client.post( "/api/tags/", {"workspace_id": str(workspace.id), "name": name, "color": "#123456"}, format="json", **_auth_headers(user), ) @pytest.mark.django_db def test_owner_and_admin_can_list_workspace_logs(api_client, owner, admin, workspace): create_response = _create_tag(api_client, owner, workspace) assert create_response.status_code == 201 owner_response = api_client.get( f"/api/logs/?workspace={workspace.id}", **_auth_headers(owner), ) admin_response = api_client.get( f"/api/logs/?workspace={workspace.id}", **_auth_headers(admin), ) assert owner_response.status_code == 200 assert admin_response.status_code == 200 assert owner_response.data["items"][0]["section"] == "tags" @pytest.mark.django_db def test_member_and_non_member_cannot_list_workspace_logs(api_client, owner, member, outsider, workspace): _create_tag(api_client, owner, workspace) member_response = api_client.get( f"/api/logs/?workspace={workspace.id}", **_auth_headers(member), ) outsider_response = api_client.get( f"/api/logs/?workspace={workspace.id}", **_auth_headers(outsider), ) assert member_response.status_code == 403 assert outsider_response.status_code == 403 @pytest.mark.django_db def test_jwt_authenticated_writes_capture_actor_and_workspace_metadata(api_client, owner, workspace): response = _create_tag(api_client, owner, workspace, name="JWT Tag") assert response.status_code == 201 log_entry = LogEntry.objects.filter(content_type__app_label="tags").latest("timestamp") assert log_entry.actor_id == owner.id assert log_entry.additional_data["workspace_id"] == str(workspace.id) assert log_entry.additional_data["section"] == "tags" @pytest.mark.django_db def test_logs_support_section_filter_and_detail(api_client, owner, workspace): tag_response = _create_tag(api_client, owner, workspace, name="Filtered Tag") assert tag_response.status_code == 201 list_response = api_client.get( f"/api/logs/?workspace={workspace.id}§ion=tags", **_auth_headers(owner), ) assert list_response.status_code == 200 assert list_response.data["items"] log_id = list_response.data["items"][0]["id"] detail_response = api_client.get( f"/api/logs/{log_id}/", **_auth_headers(owner), ) assert detail_response.status_code == 200 assert detail_response.data["target"]["name"] == "Filtered Tag" assert detail_response.data["changes"] @pytest.mark.django_db def test_soft_delete_and_actorless_background_logs_are_filtered(api_client, owner, workspace): create_response = _create_tag(api_client, owner, workspace, name="Delete Me") assert create_response.status_code == 201 tag_id = create_response.data["id"] delete_response = api_client.delete( f"/api/tags/{tag_id}/", **_auth_headers(owner), ) assert delete_response.status_code == 204 ReportExportJob.objects.create( requesting_user=owner, workspace=workspace, export_type=ReportExportJob.ExportType.PDF, filters={"workspace": str(workspace.id)}, status=ReportExportJob.Status.PENDING, ) response = api_client.get( f"/api/logs/?workspace={workspace.id}&event=delete", **_auth_headers(owner), ) assert response.status_code == 200 assert any(item["event"] == "delete" and item["section"] == "tags" for item in response.data["items"]) assert all(item["section"] != "report_exports" for item in response.data["items"])