From 50d2348b36fcd19be22ba2779f9981a1cf7c15e9 Mon Sep 17 00:00:00 2001 From: curo1305 Date: Sun, 19 Apr 2026 02:19:51 +0200 Subject: [PATCH] refactor: rename MERGE_CHECKLIST to ALL_TESTS + add per-service test files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- .claude/settings.local.json | 15 +- CLAUDE.md | 17 +- tests/{MERGE_CHECKLIST.md => ALL_TESTS.md} | 10 +- tests/ai-service_tests.md | 36 +++++ tests/backend_tests.md | 172 +++++++++++++++++++++ tests/doc-service_tests.md | 157 +++++++++++++++++++ tests/frontend_tests.md | 37 +++++ 7 files changed, 439 insertions(+), 5 deletions(-) rename tests/{MERGE_CHECKLIST.md => ALL_TESTS.md} (97%) create mode 100644 tests/ai-service_tests.md create mode 100644 tests/backend_tests.md create mode 100644 tests/doc-service_tests.md create mode 100644 tests/frontend_tests.md diff --git a/.claude/settings.local.json b/.claude/settings.local.json index abf33f5..18e1e29 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -5,8 +5,21 @@ "Bash(git add:*)", "Bash(git commit -m ':*)", "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 run:*)" + "Bash(docker run:*)", + "Bash(docker inspect:*)", + "Bash(docker ps:*)", + "Bash(curl:*)", + "Bash(lsof:*)" ] } } diff --git a/CLAUDE.md b/CLAUDE.md index 575dc80..0345fc0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -11,7 +11,14 @@ This file provides permanent, authoritative guidance to Claude Code for every se ## 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` — §1–9, §18 +- `tests/frontend_tests.md` — §19 +- `tests/doc-service_tests.md` — §10–16 +- `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 - 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. --- @@ -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 ├── scripts/security_check.py ← Static analysis: secrets, weak crypto, SQLi, JWT ├── changelog/YYYY-MM-DD_.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 (§1–9, §18) +├── tests/frontend_tests.md ← Frontend-only tests (§19) +├── tests/doc-service_tests.md ← Doc-service tests (§10–16) +├── tests/ai-service_tests.md ← AI-service tests (§17) ├── dev-watch/ ← Dev bind-mount for file watcher testing (.gitkeep only) │ ├── backend/ ← FastAPI gateway (port 8000, internal); see backend/CLAUDE.md diff --git a/tests/MERGE_CHECKLIST.md b/tests/ALL_TESTS.md similarity index 97% rename from tests/MERGE_CHECKLIST.md rename to tests/ALL_TESTS.md index 8f3d4e3..3f439fe 100644 --- a/tests/MERGE_CHECKLIST.md +++ b/tests/ALL_TESTS.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` — §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) -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. **Test environment:** Feature stack at `http://localhost:$PORT` (see CLAUDE.md §Feature branch workflow). diff --git a/tests/ai-service_tests.md b/tests/ai-service_tests.md new file mode 100644 index 0000000..46339fb --- /dev/null +++ b/tests/ai-service_tests.md @@ -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 | diff --git a/tests/backend_tests.md b/tests/backend_tests.md new file mode 100644 index 0000000..9571ea5 --- /dev/null +++ b/tests/backend_tests.md @@ -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": "", "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 --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 `` 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 | diff --git a/tests/doc-service_tests.md b/tests/doc-service_tests.md new file mode 100644 index 0000000..8530f6c --- /dev/null +++ b/tests/doc-service_tests.md @@ -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" | diff --git a/tests/frontend_tests.md b/tests/frontend_tests.md new file mode 100644 index 0000000..dab06e4 --- /dev/null +++ b/tests/frontend_tests.md @@ -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 |