docs(phase-6.1): update tracking after wave 1 — both plans complete
11 tests passing (7 shares + 4 audit), 309 total, 0 failures. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
---
|
||||
plan: 06.1-02
|
||||
title: Promote test_audit.py stubs to real tests (ADMIN-06)
|
||||
wave: 1
|
||||
depends_on: []
|
||||
phase: "6.1"
|
||||
requirements_addressed: [ADMIN-06]
|
||||
files_modified:
|
||||
- backend/tests/test_audit.py
|
||||
autonomous: 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.py` line 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 call `pytest.xfail("not implemented yet")`
|
||||
- Test infrastructure: `async_client`, `auth_user`, `admin_user`, `db_session` from 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_dict` field names: id, event_type, user_id, actor_id, resource_id, ip_address, metadata_, created_at
|
||||
- backend/services/audit.py — `write_audit_log` signature for seeding entries in tests
|
||||
</read_first>
|
||||
|
||||
<action>
|
||||
Rewrite `backend/tests/test_audit.py` entirely. Add imports at the top:
|
||||
|
||||
```
|
||||
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"
|
||||
</action>
|
||||
|
||||
<acceptance_criteria>
|
||||
- `test_audit.py` has zero `pytest.xfail` calls — every test has real assertions
|
||||
- `test_audit.py` has zero `@pytest.mark.xfail` decorators
|
||||
- `import os` is removed (was unused)
|
||||
- Running `docker compose exec backend python -m pytest tests/test_audit.py -v` shows 4 PASSED (no XFAIL, no XPASS)
|
||||
- `test_audit_log_regular_user_403` asserts status 403 — admin gate tested
|
||||
- `test_audit_log_no_doc_content` asserts "filename" not in any item and "extracted_text" not in any item
|
||||
- `test_audit_log_export_csv` asserts 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_403` proves the admin gate blocks regular users
|
||||
- `test_audit_log_no_doc_content` proves ADMIN-06 metadata safety invariant: no filename or extracted_text in any response field
|
||||
- `test_audit_log_export_csv` proves the CSV export endpoint is functional
|
||||
Reference in New Issue
Block a user