refactor: rename MERGE_CHECKLIST to ALL_TESTS + add per-service test files

- tests/MERGE_CHECKLIST.md → tests/ALL_TESTS.md (git rename, updated header + index of sub-files)
- tests/backend_tests.md — §1–9, §18 (auth, users, admin, groups, appearance, service health, plugins, AI/doc settings, infra/security)
- tests/frontend_tests.md — §19 (UI & routing)
- tests/doc-service_tests.md — §10–16 (upload/processing, list/filtering, slide-over, sharing, categories, bulk actions, watch directory)
- tests/ai-service_tests.md — §17 (AI queue & providers)
- CLAUDE.md: updated merge checklist section, file tree, and self-update checkpoint with mandatory test-file update rule
- settings.local.json: added docker inspect/ps, curl, lsof, git merge/branch/log/diff/status/config/mv permissions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
curo1305
2026-04-19 02:19:51 +02:00
parent d345ace86d
commit 50d2348b36
7 changed files with 439 additions and 5 deletions
+14 -1
View File
@@ -5,8 +5,21 @@
"Bash(git add:*)", "Bash(git add:*)",
"Bash(git commit -m ':*)", "Bash(git commit -m ':*)",
"Bash(git push:*)", "Bash(git push:*)",
"Bash(git pull:*)",
"Bash(git checkout:*)",
"Bash(git merge:*)",
"Bash(git branch:*)",
"Bash(git log:*)",
"Bash(git diff:*)",
"Bash(git status:*)",
"Bash(git config:*)",
"Bash(git mv:*)",
"Bash(docker compose:*)", "Bash(docker compose:*)",
"Bash(docker run:*)" "Bash(docker run:*)",
"Bash(docker inspect:*)",
"Bash(docker ps:*)",
"Bash(curl:*)",
"Bash(lsof:*)"
] ]
} }
} }
+15 -2
View File
@@ -11,7 +11,14 @@ This file provides permanent, authoritative guidance to Claude Code for every se
## Merge checklist ## Merge checklist
Before merging any feature branch into `main`, every test relevant to the changed area in `tests/MERGE_CHECKLIST.md` must be marked passing. The checklist covers all 19 feature areas (auth, users, admin, groups, appearance, service health, plugins, AI settings, doc settings, upload/processing, list/filtering, slide-over, sharing, categories, bulk actions, watch directory, AI queue, infrastructure/security, and frontend routing). Do not merge without it. Before merging any feature branch into `main`, every test relevant to the changed area in `tests/ALL_TESTS.md` (and the relevant service-specific file) must be marked passing. The test suite covers all 19 feature areas across four service files:
- `tests/backend_tests.md` — §19, §18
- `tests/frontend_tests.md` — §19
- `tests/doc-service_tests.md` — §1016
- `tests/ai-service_tests.md` — §17
Do not merge without it.
--- ---
@@ -28,6 +35,8 @@ Before merging any feature branch into `main`, every test relevant to the change
- New Docker service, volume, network, or env var → update **Docker Infrastructure** in this file - New Docker service, volume, network, or env var → update **Docker Infrastructure** in this file
- Stack version changed → update **Stack** in this file - Stack version changed → update **Stack** in this file
- New feature or endpoint added → add test rows to **both** `tests/ALL_TESTS.md` (in the relevant section) **and** the matching service-specific file (`tests/backend_tests.md`, `tests/frontend_tests.md`, `tests/doc-service_tests.md`, or `tests/ai-service_tests.md`). Use the same test number and format as existing rows.
This check is mandatory — treat it the same as updating STATUS.md. This check is mandatory — treat it the same as updating STATUS.md.
--- ---
@@ -77,7 +86,11 @@ For service-specific commands (migrations, lint), see `backend/CLAUDE.md` and `f
├── .githooks/pre-commit ← Runs scripts/security_check.py before every commit ├── .githooks/pre-commit ← Runs scripts/security_check.py before every commit
├── scripts/security_check.py ← Static analysis: secrets, weak crypto, SQLi, JWT ├── scripts/security_check.py ← Static analysis: secrets, weak crypto, SQLi, JWT
├── changelog/YYYY-MM-DD_<slug>.md ← Per-date change logs ├── changelog/YYYY-MM-DD_<slug>.md ← Per-date change logs
├── tests/MERGE_CHECKLIST.md ← 148-test pre-merge checklist (all features); must pass before merging to main ├── tests/ALL_TESTS.md ← Full test suite (all 19 areas); must pass before merging to main
├── tests/backend_tests.md ← Backend-only tests (§19, §18)
├── tests/frontend_tests.md ← Frontend-only tests (§19)
├── tests/doc-service_tests.md ← Doc-service tests (§1016)
├── tests/ai-service_tests.md ← AI-service tests (§17)
├── dev-watch/ ← Dev bind-mount for file watcher testing (.gitkeep only) ├── dev-watch/ ← Dev bind-mount for file watcher testing (.gitkeep only)
├── backend/ ← FastAPI gateway (port 8000, internal); see backend/CLAUDE.md ├── backend/ ← FastAPI gateway (port 8000, internal); see backend/CLAUDE.md
@@ -1,6 +1,12 @@
# Merge Checklist — Pre-merge Test Suite # ALL_TESTS — Full Test Suite
Complete test suite covering all 19 feature areas. Run tests relevant to the changed area before merging any feature branch into `main`. Service-specific subsets live in separate files:
- `tests/backend_tests.md` — §19, §18 (auth, users, admin, groups, appearance, service health, plugins, AI/doc settings, infra/security)
- `tests/frontend_tests.md` — §19 (UI & routing)
- `tests/doc-service_tests.md` — §1016 (upload/processing, list/filtering, slide-over, sharing, categories, bulk actions, watch directory)
- `tests/ai-service_tests.md` — §17 (AI queue & providers)
Run all tests relevant to the changed area before merging any feature branch into `main`.
Every test describes the exact UI action or API call to perform and the expected outcome. Every test describes the exact UI action or API call to perform and the expected outcome.
**Test environment:** Feature stack at `http://localhost:$PORT` (see CLAUDE.md §Feature branch workflow). **Test environment:** Feature stack at `http://localhost:$PORT` (see CLAUDE.md §Feature branch workflow).
+36
View File
@@ -0,0 +1,36 @@
# AI-Service Tests
Tests covering the AI provider intermediary (`ai-service:8010`): health, provider configuration, synchronous chat, the priority queue (enqueue/poll/cancel/pause/resume), and provider timeout.
Full combined suite: `tests/ALL_TESTS.md`
**Test environment:** Feature stack at `http://localhost:$PORT` (see CLAUDE.md §Feature branch workflow).
**Admin credentials:** any superuser account created during stack setup.
---
## Legend
| Symbol | Meaning |
|--------|---------|
| ✅ | Pass |
| ❌ | Fail |
| — | N/A for this change |
---
## 17. AI Service — Queue & Providers
| # | Test | Steps | Expected |
|---|------|-------|----------|
| 17.1 | Health check | `GET /health` on ai-service (via backend services endpoint) | `{"status": "ok"}` |
| 17.2 | Provider health | `GET /health/provider` | Active provider name, model, configured=true |
| 17.3 | Unconfigured provider | Set provider to "anthropic" with empty API key → test connection | 503 or 502 with clear error |
| 17.4 | Sync chat | `POST /chat` with valid messages array | Response returned synchronously |
| 17.5 | Queue — async job | `POST /queue/jobs` | `job_id` returned immediately |
| 17.6 | Queue — poll job | `GET /queue/jobs/{id}` after enqueue | Returns status (`pending``done`) and result |
| 17.7 | Queue — cancel job | `DELETE /queue/jobs/{id}` before processing | Job removed; status = cancelled |
| 17.8 | Queue pause | `POST /queue/pause` | 204; current job finishes; no new jobs picked up |
| 17.9 | Queue resume | `POST /queue/resume` after pause | 204; worker resumes; pending jobs process |
| 17.10 | Priority ordering | Enqueue LOW then HIGH job | HIGH job processed first |
| 17.11 | Provider timeout | `POST /chat` when provider is unreachable | 504 returned after timeout |
+172
View File
@@ -0,0 +1,172 @@
# Backend Tests
Tests covering the FastAPI gateway (`backend:8000`): auth, user/profile management, admin (users/groups/appearance), service health, plugin system, AI settings, document settings, and infrastructure/security.
Full combined suite: `tests/ALL_TESTS.md`
**Test environment:** Feature stack at `http://localhost:$PORT` (see CLAUDE.md §Feature branch workflow).
**Admin credentials:** any superuser account created during stack setup.
**Regular user credentials:** a second non-admin account for permission boundary tests.
---
## Legend
| Symbol | Meaning |
|--------|---------|
| ✅ | Pass |
| ❌ | Fail |
| — | N/A for this change |
---
## 1. Authentication
| # | Test | Steps | Expected |
|---|------|-------|----------|
| 1.1 | Register new account | `POST /api/auth/register` with valid email + strong password | 201; user row created; login works immediately |
| 1.2 | Password policy — too short | Register with 7-char password | 422 with validation error |
| 1.3 | Password policy — no uppercase | Register with all-lowercase password | 422 with validation error |
| 1.4 | Password policy — no special char | Register without special character | 422 with validation error |
| 1.5 | Password policy — common word | Register with password containing "password" | 422 with validation error |
| 1.6 | Duplicate email | Register with an already-used email | 400 |
| 1.7 | Login — valid credentials | `POST /api/auth/login` with correct email + password | 200; `access_token` returned |
| 1.8 | Login — wrong password | `POST /api/auth/login` with wrong password | 401 |
| 1.9 | Login — inactive account | Admin deactivates user; attempt login | 401 |
| 1.10 | JWT expiry respected | Manually craft token with `exp` in the past; call any protected route | 401 |
| 1.11 | Logout clears session | Click Logout in UI; try navigating to `/` | Redirected to `/login` |
| 1.12 | Unauthenticated redirect | Open `/` without a token in `localStorage` | Redirected to `/login` |
---
## 2. User — Profile & Preferences
| # | Test | Steps | Expected |
|---|------|-------|----------|
| 2.1 | Fetch own profile | `GET /api/profile/me` | 200; profile auto-created if first request |
| 2.2 | Update profile fields | `PUT /api/profile/me` with full_name, phone, position, address, date_of_birth | 200; fields persisted; visible on `/profile` page |
| 2.3 | Invalid phone format | `PUT /api/profile/me` with letters in phone field | 422 |
| 2.4 | Future date of birth | `PUT /api/profile/me` with DOB = tomorrow | 422 |
| 2.5 | DOB before 1900 | `PUT /api/profile/me` with DOB = 1899-12-31 | 422 |
| 2.6 | Fetch dashboard preferences | `GET /api/users/me/preferences` | 200; `app_ids` list |
| 2.7 | Pin an app | Dashboard → pencil button → press `+` on a card → save | Card appears in pinned grid on next load |
| 2.8 | Unpin an app | Dashboard → pencil button → press `` on a pinned card → save | Card removed from pinned grid |
| 2.9 | Pin limit (50) | `PATCH /api/users/me/preferences` with 51 app IDs | 422 |
| 2.10 | Color mode — user pref | User sets mode to "dark"; reload page | Dark theme applied; preference persists across sessions |
| 2.11 | Color mode — system fallback | User has NULL color_mode; admin default_mode = "light" | Light theme applied |
---
## 3. Admin — Users
| # | Test | Steps | Expected |
|---|------|-------|----------|
| 3.1 | List all users | Admin → `/admin/users` | All registered users shown in table |
| 3.2 | Create user | Admin clicks "Create user"; fills form | 201; new user appears in list; can log in |
| 3.3 | Toggle user active | Admin clicks toggle on active user | User deactivated; login returns 401 |
| 3.4 | Delete user | Admin deletes a user | 204; user no longer in list; their documents remain (orphaned) |
| 3.5 | Non-admin access | Regular user navigates to `/admin/users` | Redirected to `/login` |
| 3.6 | Admin 404 semantics | Regular user calls `GET /api/admin/users` via curl | 404 (not 403) |
---
## 4. Admin — Groups
| # | Test | Steps | Expected |
|---|------|-------|----------|
| 4.1 | List groups | Admin → `/admin/groups` | All groups shown with member count |
| 4.2 | Create group | Fill name + description → submit | Group appears in list; `{group_name}-admin` bootstrap group also exists (auto-created on service start) |
| 4.3 | Edit group | Click edit on group → change name → save | Name updated |
| 4.4 | Delete group | Delete group | 204; group gone; memberships cascade-deleted |
| 4.5 | Add member | Open group → search user → add | 204; user appears in member list |
| 4.6 | Remove member | Click remove on a member | User removed from group |
| 4.7 | Duplicate group name | Create group with name that already exists | 400 / validation error shown |
| 4.8 | Non-admin access | Regular user calls `GET /api/admin/groups` | 404 |
| 4.9 | Set group admin role | Admin → group detail → tick "Group admin" checkbox on a member → save | `PATCH /api/admin/groups/{id}/members/{user_id}/admin` with `{"is_group_admin": true}` returns 200; badge shown in member list |
| 4.10 | Unset group admin role | Admin unticks "Group admin" on an existing group admin member | Returns 200; badge removed; user loses group-admin privileges |
---
## 5. Admin — Appearance
| # | Test | Steps | Expected |
|---|------|-------|----------|
| 5.1 | List themes | Admin → `/admin/appearance` | Built-in themes + any custom themes shown |
| 5.2 | Switch active theme | Select a different theme → save | All users see the new theme on next load |
| 5.3 | Create custom theme | Admin → create theme. Required fields: `id` (slug), `label`, `light` (CSS vars object), `dark` (CSS vars object) | 201; theme appears in selector; can be activated |
| 5.4 | Edit custom theme | Admin edits colour values on a custom theme | Colours update live after activation |
| 5.5 | Delete custom theme | Admin deletes a custom theme | 204; theme gone from selector; active theme reverts to default |
| 5.6 | Set default mode | `PATCH /api/settings/appearance` with `{"theme": "<id>", "default_mode": "dark"}` (both fields required) | 200; new users without a personal preference see dark mode |
---
## 6. Service Health & Dashboard
| # | Test | Steps | Expected |
|---|------|-------|----------|
| 6.1 | Services endpoint | `GET /api/services` (authenticated) | Returns health status for doc-service and ai-service |
| 6.2 | Healthy service card | Both services running → `/apps` page. API response uses `healthy: true` (boolean), not `status: "healthy"` | Cards show "Available" badge; clicking navigates to the app |
| 6.3 | Unhealthy service card | Stop doc-service container → wait 30s → `/apps` | Doc-service card dimmed, "Unavailable", not clickable |
| 6.4 | Service recovery | Restart stopped container → wait 30s | Card returns to "Available" |
| 6.5 | Dashboard pinned cards | Pin a service → go to `/` | Pinned card appears in home grid |
| 6.6 | Customize mode | Click pencil on dashboard → toggle cards | Pinned list updates after save |
---
## 7. Plugin System
| # | Test | Steps | Expected |
|---|------|-------|----------|
| 7.1 | List plugins | `GET /api/plugins` (authenticated) | Returns accessible plugins for current user |
| 7.2 | Superuser sees all plugins | Log in as admin → `GET /api/plugins` | All registered service plugins returned |
| 7.3 | Group member sees plugin | Add user to `doc-service-admin` group → `GET /api/plugins` | doc-service plugin returned |
| 7.4 | Unpermitted user hidden | Regular user not in any admin group → `GET /api/plugins` | Empty list (plugins hidden, not 403) |
| 7.5 | Manifest fetch | `GET /api/plugins/doc-service/manifest` as permitted user | JSON Schema + access rules returned |
| 7.6 | Settings read | `GET /api/plugins/doc-service/settings` | Current doc-service plugin settings returned |
| 7.7 | Settings write | `PATCH /api/plugins/doc-service/settings` with valid payload | 200; setting persisted to volume |
| 7.8 | Unpermitted settings access | Regular user `GET /api/plugins/doc-service/settings` | 404 |
---
## 8. AI Service Settings
| # | Test | Steps | Expected |
|---|------|-------|----------|
| 8.1 | Read AI config | Admin (or `ai-service-admin` member) → `GET /api/settings/ai` | Config returned; API keys masked |
| 8.2 | Update provider | `PATCH /api/settings/ai` with provider = "anthropic" + valid key | 200; config persisted |
| 8.3 | Test connection | `POST /api/settings/ai/test` with valid config | 200; success response from provider |
| 8.4 | Test connection — bad key | `POST /api/settings/ai/test` with wrong API key | 502 or error detail |
| 8.5 | Read system prompts | `GET /api/settings/system-prompts` | All registered service prompts returned |
| 8.6 | Update system prompt | `PATCH /api/settings/system-prompts/doc-service` with new prompt text | 200; doc-service picks up new prompt within 30s |
| 8.7 | Non-admin access | Regular user calls any `/api/settings/ai` endpoint | 404 |
| 8.8 | `ai-service-admin` delegation | Non-superuser added to `ai-service-admin` group → accesses AI settings page | Page loads; can read and write settings |
---
## 9. Document Service Settings
| # | Test | Steps | Expected |
|---|------|-------|----------|
| 9.1 | Read upload limits | `GET /api/settings/documents/limits` (admin or `doc-service-admin`) | `max_pdf_bytes` returned |
| 9.2 | Update upload limit | `PATCH /api/settings/documents/limits` with new value | 200; upload of oversized PDF now rejected with 413 |
| 9.3 | Non-admin access | Regular user calls `GET /api/settings/documents/limits` | 404 |
| 9.4 | Settings page loads | Admin navigates to `/apps/documents/settings` | Upload limits section + watch directory config visible |
| 9.5 | `doc-service-admin` delegation | Non-superuser added to `doc-service-admin` → navigates to settings page | Page loads; settings editable |
---
## 18. Infrastructure & Security
| # | Test | Steps | Expected |
|---|------|-------|----------|
| 18.1 | Non-root containers | `docker inspect <container> --format '{{.Config.User}}'` for each service | Returns `1001` (or `70` for db) |
| 18.2 | No host ports in prod | `docker compose up --build -d``docker ps` | Only port 80 (frontend) exposed; no 8000/8001/8010/5432 |
| 18.3 | backend-net isolation | `curl http://localhost:8000` from host in prod | Connection refused |
| 18.4 | Pre-commit hook runs | Stage a file with `eval("x")``git commit` | Commit blocked; security_check.py output shown |
| 18.5 | Pre-commit hook — clean code | Normal commit | Hook passes; commit succeeds |
| 18.6 | JWT algorithm none rejected | Craft token with `"alg": "none"` → call protected route | 401 |
| 18.7 | XSS — input sanitation | Enter `<script>alert(1)</script>` in title/name fields | Value stored as plain text; not executed in UI |
| 18.8 | SQL injection attempt | Pass `'; DROP TABLE documents; --` as search param | 200 with empty results; no DB error |
| 18.9 | CORS | `curl -H "Origin: http://evil.com" http://localhost/api/users/me` | Request blocked or `access-control-allow-origin` not set for that origin |
| 18.10 | Config volume persistence | Restart all containers | AI provider config + doc limits survive restart |
| 18.11 | Migration auto-apply | Start fresh stack | Both `alembic upgrade head` chains run without error; all tables created |
+157
View File
@@ -0,0 +1,157 @@
# Doc-Service Tests
Tests covering the PDF extraction microservice (`doc-service:8001`): document upload/processing, list/filtering, slide-over detail view, sharing, categories, bulk actions, and the file watcher.
Full combined suite: `tests/ALL_TESTS.md`
**Test environment:** Feature stack at `http://localhost:$PORT` (see CLAUDE.md §Feature branch workflow).
**Admin credentials:** any superuser account created during stack setup.
**Regular user credentials:** a second non-admin account for permission boundary tests.
---
## Legend
| Symbol | Meaning |
|--------|---------|
| ✅ | Pass |
| ❌ | Fail |
| — | N/A for this change |
---
## 10. Document Upload & Processing
| # | Test | Steps | Expected |
|---|------|-------|----------|
| 10.1 | Upload valid PDF | Drag-and-drop or file picker → select a PDF under the size limit | 202; document row appears with `status=pending`; transitions to `done` |
| 10.2 | Upload oversized PDF | Upload a PDF exceeding `max_pdf_bytes` | 413; error shown; no row created |
| 10.3 | Upload non-PDF | Upload a `.docx` or `.jpg` | 415; error shown |
| 10.4 | Multi-file upload | Select 3 PDFs at once | All 3 appear in upload queue panel; each processes independently |
| 10.5 | Upload queue panel | During upload → check bottom-right panel | Per-file status indicator; "Review →" link after each completes |
| 10.6 | Drag-and-drop overlay | Drag file over the documents page | Full-page overlay appears; drop uploads file |
| 10.7 | Processing status poll | Upload a large PDF | Table row auto-updates every 3s until status = `done` or `failed` |
| 10.8 | AI extraction result | Open slide-over for a `done` document | title, document_type, tags, extracted_data fields populated |
| 10.9 | Failed extraction | AI service down → upload PDF | Status = `failed`; error_message shown in slide-over |
| 10.10 | Re-analyse | Click "Re-analyse" in slide-over | 202; status resets to `pending`; re-processes through AI |
---
## 11. Document List & Filtering
| # | Test | Steps | Expected |
|---|------|-------|----------|
| 11.1 | Default list | Navigate to `/apps/documents` | Own documents shown, newest first, 20 per page |
| 11.2 | Search | Type in search box (debounced 400ms) | Results filtered by title / filename / tags / type |
| 11.3 | Filter by status | Add filter chip → Status → "done" | Only completed docs shown |
| 11.4 | Filter by type | Add filter chip → Document type → "invoice" | Only invoices shown |
| 11.5 | Filter by category | Add filter chip → Category → pick one | Only docs in that category shown |
| 11.6 | Remove filter chip | Click × on a chip | Filter removed; full list restored |
| 11.7 | Sort by column | Click "Date" column header | List re-ordered; chevron indicates direction; click again reverses |
| 11.8 | Pagination | Upload > 20 docs → scroll to bottom | Page controls appear; page 2 loads next 20 |
| 11.9 | "Mine" view | Click "Mine" in SourcePanel | Only own (uploaded) documents shown |
| 11.10 | "Shared with me" view | Click "Shared with me" | Docs shared by others via groups; own docs excluded |
| 11.11 | Category filter via SourcePanel | Click a category in the left tree | Table filtered to that category's documents |
| 11.12 | URL state preserved | Apply filters → copy URL → open in new tab | Same filters applied |
---
## 12. Document Detail — Slide-over
| # | Test | Steps | Expected |
|---|------|-------|----------|
| 12.1 | Open slide-over | Click any document row | 480px right panel slides in; metadata loaded |
| 12.2 | Inline title edit | Click pencil icon next to title → type new title → confirm | Title saved; updated in table row |
| 12.3 | Change document type | Click a type chip (Invoice, Receipt, etc.) | Type updated immediately |
| 12.4 | Edit tags | Click into tag area → type a tag → press Enter → remove a tag with × | Tags saved correctly |
| 12.5 | Assign category | Categories combobox → search → select | Category badge appears on document; table row updates |
| 12.6 | Remove category | Click × on an assigned category badge | Category removed from document |
| 12.7 | AI category suggestions | Slide-over shows "Suggested categories" | "Assign" and "Create & Assign" buttons present; clicking assigns |
| 12.8 | Confirm folder suggestion | "Confirm" button next to suggested_folder | Category created (if needed) and assigned; `suggested_folder` cleared |
| 12.9 | Reject folder suggestion | "Reject" button next to suggested_folder | `suggested_folder` cleared; no category created |
| 12.10 | Confirm filename suggestion | "Confirm" button next to suggested_filename | `title` updated to suggested value; `suggested_filename` cleared |
| 12.11 | Reject filename suggestion | "Reject" button next to suggested_filename | `suggested_filename` cleared; title unchanged |
| 12.12 | Extracted data section | Open slide-over on `done` doc | Key-value table of AI-extracted fields (vendor, amounts, dates, etc.) |
| 12.13 | Raw text section | Expand raw text collapse | First ~500k chars of extracted PDF text shown |
| 12.14 | Download | Click "Download" | Browser downloads the original PDF file |
| 12.15 | View in new tab | Click "View" | PDF opens in new browser tab; URL auto-revokes after 60s |
| 12.16 | Delete — owner | Click "Delete" → confirm dialog | Document and file removed; table row gone |
| 12.16b | Delete — admin | Admin opens any doc (not their own) → Delete → confirm | Document deleted; 204 returned |
| 12.16c | Delete — can_delete share | Group member whose share has `can_delete=true` → Delete | 204; document removed; `viewer_can_delete` was `true` in `DocumentOut` |
| 12.16d | Delete — group admin | User is group admin for a group the doc is shared with; no explicit `can_delete` flag → Delete | 204; group admin always has delete rights for docs shared with their group |
| 12.16e | Delete — watch document, admin only | Watch-ingested doc (`source=watch`); regular user → Delete | 403 (not owner); admin can delete it |
| 12.17 | Non-owner cannot edit | Recipient of shared doc opens slide-over (no can_delete, not group admin) | Edit controls (type, tags, title, delete) absent; download available |
---
## 13. Document Sharing
| # | Test | Steps | Expected |
|---|------|-------|----------|
| 13.1 | Share from slide-over | Owner opens sharing section → selects a group from combobox → shares | Group appears in shares list; `share_count` in table row increments |
| 13.2 | Only user's own groups shown | Open group picker in share section | Only groups the current user belongs to are listed |
| 13.3 | Recipient sees shared doc | Log in as group member → "Shared with me" view | Shared document appears with primary accent border |
| 13.4 | Recipient download | Recipient clicks Download on shared doc | PDF downloaded successfully |
| 13.4b | Non-owner calls `GET /documents/{id}/shares` | Regular user on a doc they don't own | 404 (doc-service hides existence, consistent with admin 404 semantics — not 403) |
| 13.5 | Recipient cannot delete | Recipient opens slide-over | Delete button absent |
| 13.6 | Recipient cannot re-share | Recipient opens sharing section | Share controls absent |
| 13.7 | Remove share | Owner clicks remove on a group share | Group removed; `share_count` decrements; recipient no longer sees doc |
| 13.8 | Bulk share | Select multiple rows → bulk share → pick group | All selected docs shared with that group |
| 13.9 | Share count indicator | Document shared with 2 groups | Users icon in table row shows "2" |
| 13.10 | Share with non-member group | `POST /api/documents/{id}/shares` with group not in X-User-Groups | 403 / validation error |
| 13.11 | Share with can_delete enabled | Owner opens sharing section → tick "Allow group members to delete" → share | `can_delete=true` stored; trash icon badge appears next to group name in shares list |
| 13.12 | Share without can_delete (default) | Owner shares without ticking delete checkbox | `can_delete=false`; recipient sees the doc but Delete button absent in slide-over |
| 13.13 | can_delete shown in shares list | Share with can_delete=true → inspect shares list in slide-over | Trash2 icon rendered beside the group name; tooltip "Group members can delete this document" |
| 13.14 | viewer_can_delete in document list | Share with can_delete=true; log in as group member → `GET /api/documents` | `viewer_can_delete=true` in the recipient's list response for that doc |
---
## 14. Categories
| # | Test | Steps | Expected |
|---|------|-------|----------|
| 14.1 | Create category | SourcePanel → "New category" form → submit | Category appears in tree |
| 14.2 | Rename category | Manage categories dialog → edit → save | New name reflected everywhere |
| 14.3 | Delete category | Delete category with documents assigned | 204; documents remain; category assignment removed |
| 14.4 | Category search | More than 4 categories → type in search field | Tree filtered in real time |
| 14.5 | Manage categories dialog | Click "Manage categories" | Modal shows categories grouped by scope (Personal / Group / System) with lock icons on group and system categories |
| 14.6 | New category triggers re-analysis | Create category with name similar to AI suggestion | Background re-analysis triggered (check backend logs) |
| 14.7 | Create personal category | SourcePanel → "New category" → no group selected → submit valid PascalCase name | Created with `scope=personal`; visible only to owner |
| 14.8 | Create group-scoped category | SourcePanel → "New category" → select a group → submit | Created with `scope=group`; visible to all members of that group |
| 14.9 | Group category visible to group members | Log in as another group member | Group category appears in their category list and SourcePanel |
| 14.10 | Non-member cannot see group category | Log in as user not in the group | Group category absent from list |
| 14.11 | Only group admin can rename group category | Regular group member → rename group category | 403; group admin can rename it successfully |
| 14.12 | Only group admin can delete group category | Regular group member → delete group category | 403; group admin can delete it |
| 14.13 | System categories read-only for non-admin | Regular user → Manage categories → rename/delete a system category | 403; lock icon shown; action blocked in UI |
| 14.14 | Admin can manage system categories | Superuser → rename or delete a system category | Succeeds; ManageCategoriesDialog shows edit/delete controls for system rows |
| 14.15 | PascalCase naming enforced — invalid | Create category named `my-invoices` or `Invoice Reports` | 422 with message explaining PascalCase-with-dashes format |
| 14.16 | PascalCase naming enforced — valid | Create category named `Vendor-Invoices` | 201; category created successfully |
| 14.17 | SourcePanel scope sections | Categories exist for all three scopes | SourcePanel tree shows "Mine", per-group, and "System" sections separately |
---
## 15. Bulk Actions
| # | Test | Steps | Expected |
|---|------|-------|----------|
| 15.1 | Select rows | Tick checkboxes on multiple rows | Floating bulk actions bar appears at bottom |
| 15.2 | Bulk share | Select docs → Share with group → confirm | All selected docs shared; confirmation |
| 15.3 | Bulk delete | Select docs → Delete → confirm dialog | All selected docs deleted; bar disappears |
| 15.4 | Clear selection | Click "Clear" in bulk bar | All checkboxes deselected; bar hides |
| 15.5 | Bulk bar — "Mine" view only | Switch to "Shared with me" view | Bulk actions bar not shown (no edit rights for shared docs) |
---
## 16. Watch Directory
| # | Test | Steps | Expected |
|---|------|-------|----------|
| 16.1 | Enable watch | Doc settings page → toggle `watch_enabled` on → save | File watcher starts; backend logs confirm |
| 16.2 | Ingest new file | Drop a PDF into the bind-mounted watch directory | Document appears in "All Documents" view with `source=watch` |
| 16.3 | Sub-folder to category | Place PDF in `watch/invoices/` | Document auto-assigned to "invoices" category |
| 16.4 | Startup scan | Restart doc-service with PDFs already in watch dir | Pre-existing PDFs ingested (idempotent — no duplicates) |
| 16.5 | AI folder suggestion | `ai_folder_suggestion` enabled → ingest file | `suggested_folder` populated; confirm/reject buttons visible in slide-over |
| 16.6 | AI rename suggestion | `ai_rename_suggestion` enabled → ingest file | `suggested_filename` populated; confirm/reject buttons visible |
| 16.7 | No-remove policy | Delete PDF from watch dir | Document record remains in DB |
| 16.8 | Disable watch | Toggle `watch_enabled` off → save | Watcher stops; new files dropped are not ingested |
| 16.9 | Watch docs visible to all users | Log in as any authenticated user | Watch-ingested docs (`user_id = "watch"`) appear in "All Documents" |
+37
View File
@@ -0,0 +1,37 @@
# Frontend Tests
Tests covering the React SPA (`frontend`, port 5173 dev / 80 prod): route guards, navigation, theme, component behaviour, and TanStack Query patterns.
Full combined suite: `tests/ALL_TESTS.md`
**Test environment:** Feature stack at `http://localhost:$PORT` (see CLAUDE.md §Feature branch workflow).
**Admin credentials:** any superuser account created during stack setup.
**Regular user credentials:** a second non-admin account for permission boundary tests.
---
## Legend
| Symbol | Meaning |
|--------|---------|
| ✅ | Pass |
| ❌ | Fail |
| — | N/A for this change |
---
## 19. Frontend — UI & Routing
| # | Test | Steps | Expected |
|---|------|-------|----------|
| 19.1 | PrivateRoute redirect | Open any protected route without token | Redirected to `/login` |
| 19.2 | AdminRoute redirect | Log in as non-admin → navigate to `/admin` | Redirected to `/login` |
| 19.3 | ServiceAdminRoute | Non-admin, non-group-member → navigate to `/apps/documents/settings` | Redirected (access denied) |
| 19.4 | Sidebar collapse | Click collapse button | Sidebar shrinks to icon-only; expand restores labels |
| 19.5 | Apps accordion | Click "Apps" in sidebar | Expands to show "Documents" NavLink |
| 19.6 | SourcePanel visibility | Navigate to `/apps` then `/apps/documents` | SourcePanel only visible on `/apps/documents` route |
| 19.7 | Theme toggle | Click sun/moon button | Mode switches; persists on reload |
| 19.8 | Unknown route | Navigate to `/does-not-exist` | Redirected to `/` |
| 19.9 | TanStack Query cache | Navigate away from docs → back | List loads from cache instantly; background refetch runs |
| 19.10 | 30s service poll | Leave `/apps` open for 30s | `GET /api/services` fires again in network tab |
| 19.11 | Three-dots menu not clipped | Scroll document table → open three-dot actions on any row | Dropdown renders above the table's overflow-hidden container; not cut off |