feat: add audit log table and integration with admin/tree endpoints

Creates AuditLog model with JSONB details column for tracking admin
actions. Integrates log_audit() helper into admin endpoints (role
change, team admin toggle, deactivate, activate) and tree delete.
IP address column reserved for future Railway proxy header support.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
chihlasm
2026-02-05 23:28:41 -05:00
parent 02d06acfb8
commit 3a5ac0f201
7 changed files with 159 additions and 0 deletions

View File

@@ -2,6 +2,9 @@
import pytest
from httpx import AsyncClient
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.models.audit_log import AuditLog
class TestAdminEndpoints:
@@ -127,3 +130,42 @@ class TestAdminEndpoints:
)
assert response.status_code == 400
assert "own account" in response.json()["detail"].lower()
@pytest.mark.asyncio
async def test_audit_log_created_on_role_change(
self, client: AsyncClient, admin_auth_headers: dict, test_user: dict, test_db: AsyncSession
):
"""Test that changing a user's role creates an audit log entry."""
user_id = test_user["user_data"]["id"]
await client.put(
f"/api/v1/admin/users/{user_id}/role",
json={"role": "viewer"},
headers=admin_auth_headers
)
result = await test_db.execute(
select(AuditLog).where(AuditLog.action == "user.role_change")
)
log = result.scalar_one_or_none()
assert log is not None
assert str(log.resource_id) == user_id
assert log.details["old_role"] == "engineer"
assert log.details["new_role"] == "viewer"
@pytest.mark.asyncio
async def test_audit_log_created_on_deactivate(
self, client: AsyncClient, admin_auth_headers: dict, test_user: dict, test_db: AsyncSession
):
"""Test that deactivating a user creates an audit log entry."""
user_id = test_user["user_data"]["id"]
await client.put(
f"/api/v1/admin/users/{user_id}/deactivate",
headers=admin_auth_headers
)
result = await test_db.execute(
select(AuditLog).where(AuditLog.action == "user.deactivate")
)
log = result.scalar_one_or_none()
assert log is not None
assert str(log.resource_id) == user_id