747303246a
Folders, Sharing, Quotas & Document UX — plans verified (0 blockers, 2 non-blocking warnings). Covers FOLD-01..05, SHARE-01..05, SEC-08/09, ADMIN-06, DOC-01/02. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
220 lines
12 KiB
Markdown
220 lines
12 KiB
Markdown
---
|
|
phase: 04-folders-sharing-quotas-document-ux
|
|
plan: 01
|
|
type: execute
|
|
wave: 1
|
|
depends_on: []
|
|
files_modified:
|
|
- backend/tests/test_folders.py
|
|
- backend/tests/test_shares.py
|
|
- backend/tests/test_audit.py
|
|
- backend/tests/test_documents.py
|
|
- backend/tests/test_security.py
|
|
- backend/tests/test_migration.py
|
|
autonomous: true
|
|
requirements:
|
|
- FOLD-01
|
|
- FOLD-02
|
|
- FOLD-03
|
|
- FOLD-04
|
|
- FOLD-05
|
|
- SHARE-01
|
|
- SHARE-02
|
|
- SHARE-03
|
|
- SHARE-04
|
|
- SHARE-05
|
|
- SEC-08
|
|
- SEC-09
|
|
- ADMIN-06
|
|
- DOC-01
|
|
- DOC-02
|
|
|
|
must_haves:
|
|
truths:
|
|
- "Wave 0 test stubs exist for every requirement in Phase 4"
|
|
- "All new test functions are xfail(strict=False) so CI stays green before implementation"
|
|
- "Shared fixtures from Phase 3 conftest are reused without modification"
|
|
artifacts:
|
|
- path: "backend/tests/test_folders.py"
|
|
provides: "FOLD-01..05 test stubs (create, rename, delete empty, cascade-delete, move, breadcrumb, sort, FTS)"
|
|
- path: "backend/tests/test_shares.py"
|
|
provides: "SHARE-01..05 stubs + IDOR negative tests"
|
|
- path: "backend/tests/test_audit.py"
|
|
provides: "ADMIN-06 stubs: viewer, no-doc-content, regular-user-403"
|
|
- path: "backend/tests/test_documents.py"
|
|
provides: "DOC-02 proxy stubs appended: 200, 206, admin-403, no-presigned-url"
|
|
- path: "backend/tests/test_security.py"
|
|
provides: "SEC-08 credentials_enc exclusion + SEC-09 delete-user-cleans-files stubs"
|
|
key_links:
|
|
- from: "backend/tests/test_folders.py"
|
|
to: "backend/tests/conftest.py"
|
|
via: "auth_user / admin_user / async_client fixtures"
|
|
pattern: "from tests.conftest import|fixture"
|
|
- from: "backend/tests/test_shares.py"
|
|
to: "backend/tests/conftest.py"
|
|
via: "auth_user fixture"
|
|
pattern: "auth_user|async_client"
|
|
---
|
|
|
|
<objective>
|
|
Create Wave 0 test scaffolds for Phase 4. Every Phase 4 requirement gets at least one
|
|
xfail test stub. All stubs use `strict=False` so unexpected passes do not break CI.
|
|
No implementation code is written in this plan.
|
|
|
|
Purpose: Establish the Nyquist test gate before any backend code is written.
|
|
Output: Five test files (three new, two extended) with named xfail stubs for all 22 planned tests.
|
|
</objective>
|
|
|
|
<execution_context>
|
|
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
|
|
@$HOME/.claude/get-shit-done/templates/summary.md
|
|
</execution_context>
|
|
|
|
<context>
|
|
@.planning/PROJECT.md
|
|
@.planning/ROADMAP.md
|
|
@.planning/phases/04-folders-sharing-quotas-document-ux/04-CONTEXT.md
|
|
@.planning/phases/04-folders-sharing-quotas-document-ux/04-VALIDATION.md
|
|
@backend/tests/conftest.py
|
|
@backend/tests/test_documents.py
|
|
@backend/tests/test_security.py
|
|
</context>
|
|
|
|
<tasks>
|
|
|
|
<task type="auto" tdd="true">
|
|
<name>Task 1: Create test_folders.py and test_shares.py stubs</name>
|
|
<files>backend/tests/test_folders.py, backend/tests/test_shares.py</files>
|
|
<read_first>
|
|
backend/tests/conftest.py — read the entire file to understand available fixtures (auth_user, admin_user, async_client, db_session) and how they are declared; extract fixture names and signatures before writing any test
|
|
backend/tests/test_documents.py — read lines 1-50 for established import and fixture-injection patterns
|
|
</read_first>
|
|
<behavior>
|
|
- test_create_folder: POST /api/folders creates folder, returns 201
|
|
- test_create_folder_duplicate_name: POST /api/folders with same name under same parent returns 409
|
|
- test_rename_folder: PATCH /api/folders/{id} changes name, returns 200
|
|
- test_rename_folder_wrong_owner: PATCH /api/folders/{id} by non-owner returns 404
|
|
- test_delete_empty_folder: DELETE /api/folders/{id} on empty folder returns 204
|
|
- test_delete_folder_cascade: DELETE /api/folders/{id} on non-empty folder deletes all docs + decrements quota
|
|
- test_delete_folder_wrong_owner: DELETE /api/folders/{id} by non-owner returns 404
|
|
- test_move_document: PATCH /api/documents/{id}/folder moves doc to target folder, returns 200
|
|
- test_move_wrong_owner_404: PATCH /api/documents/{id}/folder where doc or target folder belongs to other user returns 404
|
|
- test_breadcrumb_path: GET /api/folders/{id} returns breadcrumb array of {id, name} from root to current
|
|
- test_document_sort: GET /api/documents?sort=name|date|size returns correctly ordered results
|
|
- test_fts_search: GET /api/documents?q=term returns matching docs only; marked with pytest.mark.skipif for non-PostgreSQL
|
|
- test_fts_search_scoped_to_owner: GET /api/documents?q=term does not return other user's matching docs
|
|
- test_share_success: POST /api/shares grants share, recipient can see doc via GET /api/shares/received
|
|
- test_share_handle_not_found: POST /api/shares with unknown handle returns 404
|
|
- test_shared_with_me: GET /api/shares/received lists docs shared with current user
|
|
- test_share_no_quota_impact: share does not increment recipient's quota used_bytes
|
|
- test_revoke_share: DELETE /api/shares/{id} removes share; GET /api/shares/received no longer lists the doc
|
|
- test_share_revoke_wrong_owner_404: DELETE /api/shares/{id} by non-owner returns 404
|
|
- test_share_duplicate: POST /api/shares same doc+recipient twice returns 409
|
|
</behavior>
|
|
<action>
|
|
Create backend/tests/test_folders.py. Header imports: `import pytest` and `pytest.mark.xfail(strict=False)`.
|
|
Import `pytest_asyncio` and any fixtures from conftest using injection (no direct import — pytest injects by name).
|
|
Every test function body is `pytest.xfail("not implemented yet")` as the first line — no other code.
|
|
Name stubs exactly as listed in the behavior block (test_create_folder, test_create_folder_duplicate_name, etc.).
|
|
Mark test_fts_search and test_fts_search_scoped_to_owner with both `@pytest.mark.xfail(strict=False)` and a `pytest.mark.skipif` checking for the INTEGRATION env var — pattern: `pytest.mark.skipif(not os.environ.get("INTEGRATION"), reason="requires PostgreSQL")`.
|
|
|
|
Create backend/tests/test_shares.py with the same xfail stub pattern.
|
|
Stubs: test_share_success, test_share_handle_not_found, test_shared_with_me, test_share_no_quota_impact, test_revoke_share, test_share_revoke_wrong_owner_404, test_share_duplicate.
|
|
|
|
Do NOT write any assertion code in stub bodies. One line only: `pytest.xfail("not implemented yet")`.
|
|
</action>
|
|
<verify>
|
|
<automated>cd /Users/nik/Documents/Progamming/document_scanner/backend && python -m pytest tests/test_folders.py tests/test_shares.py -v --no-header 2>&1 | tail -30</automated>
|
|
</verify>
|
|
<acceptance_criteria>
|
|
- backend/tests/test_folders.py exists with all 13 stub functions listed in the behavior block
|
|
- backend/tests/test_shares.py exists with all 7 stub functions listed in the behavior block
|
|
- pytest reports all stubs as xfail (x) or xpass — zero failures (F) or errors (E)
|
|
- test_fts_search and test_fts_search_scoped_to_owner have both @pytest.mark.xfail and @pytest.mark.skipif decorators
|
|
- No import errors: `python -c "import tests.test_folders; import tests.test_shares"` exits 0
|
|
</acceptance_criteria>
|
|
<done>Both files exist; pytest collects them with zero errors; all tests show as xfail.</done>
|
|
</task>
|
|
|
|
<task type="auto" tdd="true">
|
|
<name>Task 2: Extend test_documents.py, test_audit.py, test_security.py, test_migration.py with Phase 4 stubs</name>
|
|
<files>backend/tests/test_documents.py, backend/tests/test_audit.py, backend/tests/test_security.py, backend/tests/test_migration.py</files>
|
|
<read_first>
|
|
backend/tests/test_documents.py — read the entire file; identify the last test function and existing imports to find the correct append point; note the fixture names in use
|
|
backend/tests/test_security.py — read the entire file; note what already exists to avoid duplicate function names; if test_delete_user_cleans_files already exists skip it
|
|
backend/tests/test_audit.py — this file does NOT exist yet; create it fresh
|
|
</read_first>
|
|
<behavior>
|
|
Additions to test_documents.py:
|
|
- test_content_stream_200: GET /api/documents/{id}/content returns 200 with correct Content-Type and Content-Disposition: inline
|
|
- test_content_stream_206_range: GET /api/documents/{id}/content with Range header returns 206 and Content-Range header
|
|
- test_content_stream_admin_403: GET /api/documents/{id}/content with admin JWT returns 403
|
|
- test_content_stream_no_presigned_url: GET /api/documents/{id}/content response body does not contain any presigned URL token (no "X-Amz-Signature" or similar in body)
|
|
|
|
New test_audit.py:
|
|
- test_audit_log_viewer: GET /api/admin/audit-log returns paginated entries
|
|
- test_audit_log_no_doc_content: audit log entries contain no "filename", "extracted_text" keys in metadata_
|
|
- test_audit_log_regular_user_403: GET /api/admin/audit-log with regular user token returns 403
|
|
- test_audit_log_export_csv: GET /api/admin/audit-log/export?format=csv returns CSV content-type
|
|
|
|
Additions to test_security.py:
|
|
- test_credentials_enc_not_in_response: no API response for current user includes credentials_enc field
|
|
- test_delete_user_cleans_files: admin DELETE /api/admin/users/{id} triggers MinIO object deletion before DB removal
|
|
</behavior>
|
|
<action>
|
|
For test_documents.py: read the existing file, then APPEND the four new stub functions at the end. Each is `@pytest.mark.xfail(strict=False)` decorated, body is `pytest.xfail("not implemented yet")`.
|
|
|
|
Create backend/tests/test_audit.py from scratch with the four stubs plus necessary imports (pytest, pytest_asyncio pattern from conftest — no direct imports, fixture injection only).
|
|
|
|
For test_security.py: read the existing file first. Append the two new stubs ONLY if they do not already exist (check for test_credentials_enc_not_in_response and test_delete_user_cleans_files). If test_security.py does not exist, create it with both stubs.
|
|
|
|
All new stubs: `@pytest.mark.xfail(strict=False)`, body `pytest.xfail("not implemented yet")`.
|
|
</action>
|
|
<verify>
|
|
<automated>cd /Users/nik/Documents/Progamming/document_scanner/backend && python -m pytest tests/test_documents.py tests/test_audit.py tests/test_security.py -v --no-header 2>&1 | tail -30</automated>
|
|
</verify>
|
|
<acceptance_criteria>
|
|
- backend/tests/test_audit.py exists with 4 stub functions
|
|
- backend/tests/test_documents.py contains test_content_stream_200, test_content_stream_206_range, test_content_stream_admin_403, test_content_stream_no_presigned_url (verified by grep)
|
|
- backend/tests/test_security.py contains test_credentials_enc_not_in_response and test_delete_user_cleans_files
|
|
- `cd backend && python -m pytest tests/ -v --no-header 2>&1 | grep -E "FAILED|ERROR"` produces zero lines
|
|
- Total xfail count increases by at least 10 compared to pre-plan baseline (all new stubs collected)
|
|
</acceptance_criteria>
|
|
<done>All three files contain Phase 4 stubs; full test suite runs with zero failures or errors.</done>
|
|
</task>
|
|
|
|
</tasks>
|
|
|
|
<threat_model>
|
|
## Trust Boundaries
|
|
|
|
| Boundary | Description |
|
|
|----------|-------------|
|
|
| Test code → conftest fixtures | Test stubs must not import production secrets or live services |
|
|
|
|
## STRIDE Threat Register
|
|
|
|
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|
|
|-----------|----------|-----------|-------------|-----------------|
|
|
| T-04-00-01 | Tampering | test stub files | mitigate | xfail(strict=False) ensures stubs cannot falsely pass and mask missing implementation |
|
|
| T-04-00-02 | Information Disclosure | test fixture reuse | accept | Phase 3 conftest fixtures already use ephemeral DB + mock MinIO; no real credentials in tests |
|
|
| T-04-SC | Tampering | npm/pip/cargo installs | accept | No new packages installed in this plan |
|
|
</threat_model>
|
|
|
|
<verification>
|
|
Run full suite: `cd /Users/nik/Documents/Progamming/document_scanner/backend && python -m pytest -v --no-header 2>&1 | tail -20`
|
|
|
|
Expected: zero FAILED, zero ERROR. All new stubs appear as xfail (x) in summary.
|
|
</verification>
|
|
|
|
<success_criteria>
|
|
- Five test files collectively contain all 23 Phase 4 test stubs
|
|
- `pytest -v` exits 0 (green)
|
|
- No existing passing tests regress
|
|
- All stubs are properly named (exact names matching 04-VALIDATION.md Per-Task Verification Map)
|
|
</success_criteria>
|
|
|
|
<output>
|
|
Create `.planning/phases/04-folders-sharing-quotas-document-ux/04-01-SUMMARY.md` when done.
|
|
</output>
|