test(podcasts): integration suite + RLS auditor + contract widening (task 1.10)
cmd/server/tests/podcast_test.go — six suites, the shared-dev-DB doctrine (test*-named libraries, cascading cleanup, no binary fixtures): - TestPodcastRLSSurfaces: THE AUDITOR (invariant 13) — non-subscriber 404 on feed detail + SSR page (existence leak), episodes absent from generic media reads, admin escape, subscriber visibility, then the hidden-library flip hiding an already-subscribed feed (the feed policy's library-visibility join). Seeding runs as the FIRST ADMIN — the seed identity has neither subscription nor users row, so podcast INSERT..RETURNING (SELECT policy under FORCE) 42501s for it; production pods run as exactly that admin (invariant 12). All direct row probes read through the admin scope for the same reason. - TestPodcastAddFeedFlow: member add + subscribe + metadata-only upsert; duplicate-URL dedupe (no second row); allow_member_feeds gate (member 403 / admin 201 / subscribe-still-allowed). - TestPodcastShrunkenFeedRetention: feed purges an episode from the XML, both rows persist (upsert-without-delete pin, D14). - TestPodcastEditSegregation: generic edit 400 + write-to-file refusal for episodes; control book edit unaffected. - TestPodcastDownloadWorker (ffmpeg-generated tone at runtime): file lands at <download-root>/<feed-slug>/NNNN-…, audio_metadata probed; re-download of a downloaded episode never re-fetches (idempotency pin, enclosure-hit counted); dead enclosure → failed + persisted reason + metadata intact. Fetch/SSRF tests swap the client via SetPodcastHTTPClientForTests (the production client refuses loopback httptest servers by design — the real client gets its own negative test in services). - TestPodcastScannerExclusion: SetFolders on a podcasts library root yields zero scanner folders (invariant 4 pin) — MediaScanner .FolderCount() accessor added. Unit: internal/services/podcast_rss_test.go — namespace-aware parsing fixture (three XML namespaces, fallback chains, hosts/guests), duration spellings, RFC-822 variants, explicit variants, slug/path layout, enclosure-extension selection, stripHTML, and the SSRF matrix (private/loopback/link-local/multicast/mapped-v6 blocked; a loopback httptest server PROVES the production client refuses it). suites green: full integration run 161s / 0 failures; vitest 12 files 163 tests. Contract widening: TestContractPin_MediaListBothBranches' raw-row allowlist gains the 15 podcast episode columns (additive; documented in docs/contract_pins.md — inert defaults on scan rows). Bruno: bruno/ podcasts/ mirrors the §8 endpoints (12 requests) + Bookhoard env vars. tests/ui/COVERAGE.md rows 18-23: podcasts pages/lifecycle/RLS/chain — Playwright sweep pending.
This commit is contained in:
1 parent
28420d56ed
commit
49b0b6eeb9
24 files changed
+1510
-10
No files matched your search
@@ -22,3 +22,20 @@ Gate: every line below executes for BOTH personas unless marked
|
||||
| 15 | Upload: EPUB generated at runtime, upload → appears → delete | 🔲 | n/a |
|
||||
| 16 | Auth edges: restricted → admin URL; anonymous → media URL | 🔲 | ✅ (detail/404) |
|
||||
| 17 | OPDS / KOReader | API-covered (invariant test) | API-covered |
|
||||
| 18 | Podcasts: page renders (empty state before any feed) | 🔲 | 🔲 |
|
||||
| 19 | Podcasts: add-feed modal → feed page (member flow) | 🔲 | 🔲 |
|
||||
| 20 | Podcasts: feed detail — episode list lifecycle (download/queue/failed+retry states inline), Download All, gap banner | 🔲 | 🔲 |
|
||||
| 21 | Podcasts: cross-user invisibility — own feed only (RLS §20 surfaces) | 🔲 | 🔲 |
|
||||
| 22 | Podcasts: order selector re-renders episode list; serial suggestion shown only for serial/audiobook feeds | 🔲 | 🔲 |
|
||||
| 23 | Podcasts: audio reader "Ep N/M" + prev/next episode; chain-advance to next episode | 🔲 | 🔲 |
|
||||
|
||||
### Podcast-specific sweep notes (plan task 1.10)
|
||||
|
||||
- The download lifecycle needs a fetchable enclosure: sweep with a local
|
||||
feed server fixture (the integration tests use `swapFeedServer`; the
|
||||
Playwright run needs the same trick) — otherwise episodes stay
|
||||
pending and rows 20's queue/failed states can't render.
|
||||
- Row 21 rides `TestPodcastRLSSurfaces` (API level) until the sweep
|
||||
lands; the page-level check is the 404 render for a foreign feed id.
|
||||
- Row 23 chains by page navigation — assert the URL becomes the next
|
||||
episode's reader and progress carries over.
|
||||
Reference in new issue
Block a user