Add ruff config to exclude alembic E402, SQLAlchemy F821, and pre-existing E501 line-length issues. Auto-fix 69 unused imports and 2 f-strings without placeholders. Manually fix 8 unused variables. Apply ruff format to 127 files. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
89 lines
2.4 KiB
Python
89 lines
2.4 KiB
Python
"""Unit tests for the audit service and model.
|
|
|
|
Tests cover:
|
|
- AuditLog model can be imported
|
|
- log_action function signature is correct
|
|
- Audit logs router is importable with expected endpoints
|
|
- CSV export endpoint exists
|
|
"""
|
|
|
|
import uuid
|
|
from unittest.mock import AsyncMock
|
|
|
|
import pytest
|
|
|
|
|
|
class TestAuditLogModel:
|
|
"""Tests for the AuditLog ORM model."""
|
|
|
|
def test_model_importable(self):
|
|
from app.models.audit_log import AuditLog
|
|
|
|
assert AuditLog.__tablename__ == "audit_logs"
|
|
|
|
def test_model_has_required_columns(self):
|
|
from app.models.audit_log import AuditLog
|
|
|
|
mapper = AuditLog.__table__.columns
|
|
expected_columns = {
|
|
"id",
|
|
"tenant_id",
|
|
"user_id",
|
|
"action",
|
|
"resource_type",
|
|
"resource_id",
|
|
"device_id",
|
|
"details",
|
|
"ip_address",
|
|
"created_at",
|
|
}
|
|
actual_columns = {c.name for c in mapper}
|
|
assert expected_columns.issubset(actual_columns), (
|
|
f"Missing columns: {expected_columns - actual_columns}"
|
|
)
|
|
|
|
def test_model_exported_from_init(self):
|
|
from app.models import AuditLog
|
|
|
|
assert AuditLog.__tablename__ == "audit_logs"
|
|
|
|
|
|
class TestAuditService:
|
|
"""Tests for the audit service log_action function."""
|
|
|
|
def test_log_action_importable(self):
|
|
from app.services.audit_service import log_action
|
|
|
|
assert callable(log_action)
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_log_action_does_not_raise_on_db_error(self):
|
|
"""log_action must swallow exceptions so it never breaks the caller."""
|
|
from app.services.audit_service import log_action
|
|
|
|
mock_db = AsyncMock()
|
|
mock_db.execute = AsyncMock(side_effect=Exception("DB down"))
|
|
|
|
# Should NOT raise even though the DB call fails
|
|
await log_action(
|
|
db=mock_db,
|
|
tenant_id=uuid.uuid4(),
|
|
user_id=uuid.uuid4(),
|
|
action="test_action",
|
|
)
|
|
|
|
|
|
class TestAuditRouter:
|
|
"""Tests for the audit logs router."""
|
|
|
|
def test_router_importable(self):
|
|
from app.routers.audit_logs import router
|
|
|
|
assert router is not None
|
|
|
|
def test_router_has_audit_logs_endpoint(self):
|
|
from app.routers.audit_logs import router
|
|
|
|
paths = [route.path for route in router.routes]
|
|
assert "/audit-logs" in paths or any("/audit-logs" in p for p in paths)
|