11 tests passing (7 shares + 4 audit), 309 total, 0 failures. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
5.3 KiB
plan, title, wave, depends_on, phase, requirements_addressed, files_modified, autonomous
| plan | title | wave | depends_on | phase | requirements_addressed | files_modified | autonomous | ||
|---|---|---|---|---|---|---|---|---|---|
| 06.1-02 | Promote test_audit.py stubs to real tests (ADMIN-06) | 1 | 6.1 |
|
|
true |
Plan 06.1-02 — Promote test_audit.py stubs to real tests
Objective
The admin audit log API (api/audit.py) is fully implemented with GET /api/admin/audit-log (paginated + filtered) and GET /api/admin/audit-log/export (CSV streaming). test_audit.py contains 4 xfail stubs that call pytest.xfail("not implemented yet"). This plan replaces every stub with a real test.
Context
- Backend implementation:
backend/api/audit.py—GET /api/admin/audit-log?start=&end=&user_id=&event_type=&page=&per_page=+ CSV export - Router registration:
backend/main.pyline 191-193 —from api.audit import router as audit_router; app.include_router(audit_router) - Frontend:
frontend/src/components/admin/AuditLogTab.vue— full filter UI (start/end date, user_id, event_type) - Test stubs:
backend/tests/test_audit.py— 4 tests all callpytest.xfail("not implemented yet") - Test infrastructure:
async_client,auth_user,admin_user,db_sessionfrom conftest.py
The tests need at least one AuditLog row to verify filtering behaviour. The write_audit_log service function (backend/services/audit.py) can be called directly in tests to seed entries without going through an endpoint.
Tasks
Task 1 — Implement real tests in test_audit.py
<read_first>
- backend/tests/test_audit.py — full file (stubs to replace)
- backend/api/audit.py — endpoint response shape:
{"items": [...], "total": int, "page": int, "per_page": int};_audit_to_dictfield names: id, event_type, user_id, actor_id, resource_id, ip_address, metadata_, created_at - backend/services/audit.py —
write_audit_logsignature for seeding entries in tests </read_first>
from __future__ import annotations
import pytest
Add pytestmark = pytest.mark.asyncio at the module level so all test coroutines are discovered without per-test decorators.
Add a module-level helper async def _seed_audit(db_session, user_id) that calls write_audit_log(session=db_session, event_type="document.uploaded", user_id=user_id, actor_id=user_id, resource_id=None, ip_address=None, metadata_={"size_bytes": 100}) followed by await db_session.commit(). Import write_audit_log from services.audit inside the function body to avoid top-level import ordering issues.
Then implement each test without any @pytest.mark.xfail decorator:
test_audit_log_viewer(async_client, admin_user, db_session)
- Seed one audit entry via
_seed_audit(db_session, admin_user["user"].id) - GET /api/admin/audit-log with admin_user headers
- Assert response status 200
- Assert response body has keys: "items", "total", "page", "per_page"
- Assert "total" >= 1 (the seeded entry is present)
- Assert items is a list and items[0] has keys: "id", "event_type", "user_id", "created_at"
test_audit_log_no_doc_content(async_client, admin_user, db_session)
- Seed an entry whose metadata_ contains
{"size_bytes": 100}(no filename, no extracted_text) - GET /api/admin/audit-log with admin_user headers
- Assert status 200
- For every item in response["items"]:
- Assert "filename" not in item (ADMIN-06, D-15)
- Assert "extracted_text" not in item
- Assert "password_hash" not in item
- Assert "credentials_enc" not in item
- Assert no item has a "metadata_" key whose value (if a dict) contains "filename" or "extracted_text"
test_audit_log_regular_user_403(async_client, auth_user)
- GET /api/admin/audit-log with auth_user headers (regular user, not admin)
- Assert status 403
test_audit_log_export_csv(async_client, admin_user, db_session)
- Seed one entry via
_seed_audit - GET /api/admin/audit-log/export?format=csv with admin_user headers
- Assert status 200
- Assert response headers["content-type"] starts with "text/csv"
- Assert response headers["content-disposition"] contains "audit-export.csv"
- Assert response text contains the CSV header line: "id,event_type,user_id,actor_id,resource_id,ip_address,metadata_,created_at"
<acceptance_criteria>
test_audit.pyhas zeropytest.xfailcalls — every test has real assertionstest_audit.pyhas zero@pytest.mark.xfaildecoratorsimport osis removed (was unused)- Running
docker compose exec backend python -m pytest tests/test_audit.py -vshows 4 PASSED (no XFAIL, no XPASS) test_audit_log_regular_user_403asserts status 403 — admin gate testedtest_audit_log_no_doc_contentasserts "filename" not in any item and "extracted_text" not in any itemtest_audit_log_export_csvasserts content-type starts with "text/csv" and disposition contains "audit-export.csv" </acceptance_criteria>
Verification
docker compose exec backend python -m pytest tests/test_audit.py -v
Expected: 4 passed, 0 failed, 0 xfailed, 0 xpassed.
Must-haves
- No test uses
pytest.xfail("not implemented yet")— all 4 stubs replaced with real assertions test_audit_log_regular_user_403proves the admin gate blocks regular userstest_audit_log_no_doc_contentproves ADMIN-06 metadata safety invariant: no filename or extracted_text in any response fieldtest_audit_log_export_csvproves the CSV export endpoint is functional