summaryrefslogtreecommitdiff
path: root/player-server/test
AgeCommit message (Collapse)Author
2026-05-19Fix four defects flagged by S19/S20/S24; tighten scenariosPaul Buetow
1. tagService.AssignTag and RemoveTag now use verifyModifyAccess (owner role required) instead of verifyAccess. Tags are global state visible to every user with access to a media item, so a viewer must not be able to add or remove them. Favorites and notes stay on verifyAccess because they're per-user data (favorites.user_id, media_notes.user_id) and don't affect anyone else. Verified via curl: viewer POST /media/{id}/tags now 403, admin still 200. 2. serveFileResult now emits a strong ETag header ("<size>-<mtime-nanos>") before calling http.ServeContent. Go's ServeContent honours If-None-Match when ETag is set, so iOS audio clients and podcast apps can revalidate cached downloads with conditional GETs. Verified via curl: ETag present on /stream; If-None-Match matching the ETag returns 304. 3. MediaFilter gains IncludeDeleted flag; ListMedia skips the implicit `deleted_at IS NULL` predicate when it is set. FSScanner.loadExistingMedia now passes IncludeDeleted=true so the dedup map includes soft-deleted rows. Previously a re-scan of a soft-deleted file tried to CreateMedia and hit the UNIQUE(set_id, rel_path) constraint, failing the whole scan and setting progress.last_error. Now the rescan skips the row cleanly; soft-delete sticks. 4. FSScanner.reconcileOrphans soft-deletes media rows whose underlying file disappeared between scans. The scanner used to only walk files that exist and never compare against the DB, leaving phantom rows in GET /api/v1/media that 404'd on stream. Verified via curl: rm /testdata/.../orphan.mp3, rescan, row now has deleted_at != NULL. Scenarios updated to lock in the fixed behaviour: S19 step 15 — viewer tag-add now asserts 403, not 200. S20 step 14 — asserts ETag is present and If-None-Match → 304. S24 step 13 — asserts clean rescan (no last_error from UNIQUE). S24 step 20 — asserts orphan rows are soft-deleted by rescan. Verified: full Go unit suite passes; 25/25 LLM e2e scenarios; 22/22 Playwright e2e-web. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19Round 4-7 tests: 7 LLM scenarios + 10 Playwright UI testsPaul Buetow
LLM e2e scenarios (S19-S25): S19 — permissions matrix (viewer vs owner across two sets). Flags a design mismatch: viewer role currently permits tags/favorites/ notes via verifyAccess instead of verifyModifyAccess, contrary to the model.RoleViewer doc comment. Not fixed; documented. S20 — HTTP Range and HEAD on /stream, /download, /thumbnail. Flags no-ETag (cacheability gap) and locks in stdlib Range semantics (single, suffix, open, 416, multi-range). S21 — upload negatives (missing parts, bad extension, traversal, 404, 403, dedup collisions, 413 skip note). S22 — share expiry (sqlite UPDATE on expires_at, then verify 410 on all three /s/{token}/... routes) + 5-token uniqueness via crypto/rand audit. S23 — user deletion cascade with schema audit: every user FK has ON DELETE CASCADE; tags are global by design. S24 — soft-delete persistence across rescan. Surfaces TWO real bugs in scanner: (1) re-INSERT of soft-deleted media hits UNIQUE constraint and fails the scan; (2) files deleted from disk leave orphan media rows that never get reconciled. S25 — SQL injection + XSS + path-traversal probes. SQL surface fully parameterised (audited repository/media.go); XSS storage is API-correct (UI escapes); share path traversal blocked by Go ServeMux path cleaning. Playwright e2e-web extensions (Round 7, 10 new tests): share-page.test.ts (4) — share metadata payload, audio/video stage elements, invalid-token 404 page. search-filter.test.ts (3) — search filter, like:1 favourites syntax, clearing input restores full grid. admin-panel.test.ts (3) — user list, permissions section, rescan button + scan-progress UI. Round 8 audit (Android): 24/24 Flutter widget tests pass; the app is currently a stub with UnimplementedError-only API client, so no additional test scaffolding is justified until production code lands. Verified: 25/25 LLM scenarios pass; 22/22 Playwright tests pass; full Go unit-test suite green. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19Commit license-cleared test media; retire ./testmedia in testsPaul Buetow
Add player-server/testdata/media/ with ten public-domain media files: five LibriVox Aesop's-Fables mp3 chapters (audiobooks/), four NASA image-library jpgs (images/), and one NASA mp4 short (videos/). Total ~11 MB; each file's source URL and license is documented in testdata/LICENSES.md and README.md. Switch every default reference in tests, scenarios, docs, and CI from MEDIA_ROOT=./testmedia to MEDIA_ROOT=./testdata/media so a fresh clone can run mage E2E and the Playwright smoke suite without supplying any external media. The ./testmedia path remains gitignored — it's the local-only personal library directory. Verified: 12/12 Playwright e2e-web pass; 18/18 LLM e2e scenarios pass against the new fixture set. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19Verify access on progress updates; add S16/S17/S18 scenariosPaul Buetow
Bug fix: POST /api/v1/progress and POST /api/v1/progress/batch did not verify that the supplied media_id belonged to a media row the caller could see. Two failure modes: - Missing media_id triggered an FK violation in UpsertProgress, which fell through handleError to HTTP 500 instead of 404. - Soft-deleted media_id (row still exists, deleted_at != nil) was accepted silently with HTTP 200, recording progress on an item the user could no longer reach. Both now route through accessHelper.verifyAccess in progressService, which returns ErrNotFound (404) for missing/soft-deleted rows and ErrForbidden (403) for unauthorized sets. Verified via curl: POST /progress media_id=999999999 → 404; POST /progress/batch with a bad id → 404. Tests: progress_test.go and no_rows_test.go now seed MediaRepo and UserRepo so the verifyAccess branch finds a real (admin) caller and a real media row. All other tests untouched. S16 covers media list pagination, filtering, sort, and the parser's intentional fail-open behaviour for malformed limit/offset/type. S17 covers podcast list endpoints (GET /podcasts, GET /podcasts/{id}/ episodes) and the admin-only subscribe gate. S18 covers single POST /progress + GET /in-progress, including the new 404 path for missing/forbidden media. Full LLM e2e suite passes 18/18. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19Map service errors to correct HTTP status codes (was 500)Paul Buetow
Three more handlers had the same pattern as the RevokeShare bug fixed in 0848488: the service returned a plain errors.New(...) for an expected condition, so handleError fell through to its default 500 branch instead of mapping to 404/400. - browse.GetThumbnail → ErrNotFound (404 instead of 500) when the media row has no thumbnail path. - tag.RemoveTag → ErrNotFound (404 instead of 500) when the tag name does not exist. DELETE /media/{id}/tags/{unknown} now returns 404. - write.RegenerateSetCover → new sentinel ErrEmptySetForCover, mapped to 400 in handleError, when the target set contains no media files eligible to be promoted to a cover. S15 (auth-boundary negatives) is tightened: the share-revoke step used to accept {404, 500} as a workaround for the bug; it now requires 404. A new section C2 locks in the three regressions above so they cannot silently return 500 again. Verified end-to-end via curl: DELETE /media/{id}/tags/unknown → 404, DELETE /shares/unknown-token → 404. Full LLM e2e suite 15/15 still passes against the rebuilt server. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19Fix RevokeShare 500 on unknown token; add S14 rescan + S15 auth boundary ↵Paul Buetow
scenarios Bug fix: shareService.RevokeShare returned errors.New("share not found") instead of the sentinel ErrShareNotFound, so handleError fell through to HTTP 500 instead of 404 for DELETE /api/v1/shares/{unknown-token}. The sister method ValidateShareToken already used the sentinel; this aligns them. Verified with curl: DELETE on a missing token now returns 404. The bug surfaced while drafting S15 (auth-boundary negative tests), which exercises 401 unauthenticated, 403 non-admin → admin routes, and 404 unknown resources. S14 covers admin rescan + scan-progress polling. All 15 LLM e2e scenarios pass against the fixed server. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19Fix LLM e2e runner: precheck server, correct S10 table namePaul Buetow
precheckServer() polls /healthz once before iterating scenarios. When the server is down, the runner now exits 2 with a clear startup hint instead of spawning Playwright per scenario (each timing out and filing a duplicate "did not become healthy" task via ask add). Also corrects S10-progress-batch.md: the YAML db assertion referenced `progress` but the actual table (per internal/repository/playback_progress.go) is `playback_progress`, with `media_id` as the joinable column. Verified: precheck cleanly exits 2 when the server is down (15 s timeout); full LLM e2e suite passes 13/13 with the server up. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18Add e2e-llm scenarios S06–S13Paul Buetow
Covers: set browse + cover (S06), favorites/tags/notes (S07), media streaming/download/thumbnail (S08), soft-delete/trash/restore (S09), progress batch sync (S10), shares management (S11), admin user management (S12), and admin permissions (S13). Each scenario follows the S01–S05 YAML front-matter + numbered Markdown step format. Routes and response shapes were verified against player-server/internal/api/* handlers. Full suite passes 13/13; Playwright e2e-web suite passes 12/12. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18Add e2e-llm scenario S05: auth token lifecycle + logoutPaul Buetow
Covers the full API token flow: login as admin, create a token via POST /api/v1/auth/tokens, list and confirm it appears, verify the token authenticates a Bearer request, revoke via DELETE /api/v1/auth/tokens/{id}, confirm it disappears from the list and a Bearer request now returns 401. Then exercise POST /api/v1/logout and confirm the session cookie is invalidated server-side (a subsequent authenticated request returns 401). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18Switch oracle from Claude Haiku to Ollama cloud (qwen3-vl:235b-instruct)Paul Buetow
Replaces the Anthropic SDK dependency in oracle.ts with direct fetch calls to the Ollama cloud API (https://ollama.com/v1/chat/completions). Uses qwen3-vl:235b-instruct — the strongest vision-language model available on the Ollama cloud — for screenshot analysis. The model is configurable via OLLAMA_MODEL and the endpoint via OLLAMA_BASE_URL so local Ollama instances can be used during development. OLLAMA_API_KEY carries the cloud bearer token. Smoke-tested: gate-off returns true without a network call; yes/no questions against a test PNG return correct answers via the cloud API. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18Fix Playwright race condition in admin-gate testPaul Buetow
The admin check test registered waitForResponse after page.goto(), so the 403 from API.users() (fired at SPA init) could arrive before the listener was active — causing a 10 s timeout. Fix: inject the session cookie and register the response listener before navigation, then await the promise after goto(). Also corrects the URL filter: the SPA calls /api/admin/users (not /api/v1/admin/users) so the pattern now matches the actual request. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18Add e2e-llm scenario files S01-S04Paul Buetow
S01: bootstrap fresh DB → first admin user (browser + API assertions). S02: podcast subscribe → episode download → mark complete (uses mock RSS server fixture). S03: upload via API with Bearer token → verify media card on web (Haiku visual check). S04: share-link round-trip — unauthenticated access, root redirects to /login.html (Haiku visual check). Includes fixtures/mock-rss-server.js: minimal Node.js HTTP server serving a valid RSS 2.0 podcast feed at http://localhost:8888/feed.xml with two episodes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18Add Haiku screenshot oracle for LLM e2e visual checksPaul Buetow
Implements oracle.ts (Layer 5 of the assertion stack) which sends a PNG screenshot to Claude Haiku with a yes/no question and returns true when the answer starts with "yes". Gated behind LLM_E2E_SCREENSHOTS=true so CI runs that don't set this variable skip visual checks silently without burning API credits. Adds @anthropic-ai/sdk dependency to the runner package. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18Add e2e-llm harness runner and READMEPaul Buetow
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18Add CI pipeline design proposalPaul Buetow
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18Add web UI smoke test suitePaul Buetow
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18Choose Playwright for web UI smoke testsPaul Buetow
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>