Commit Graph
4 Commits
Author SHA1 Message Date
John O'Keefe 431fec05ad feat(security): library visibility enforced at the source; per-library series; annotation ownership; admin user creation
The family-lens hardening: library_visibility was only enforced where
file bytes flowed (download/reader/OPDS) — metadata surfaces leaked
hidden libraries to any authenticated account, and registration was
open by default with no admin-side user creation.

- Series are grouped PER LIBRARY now (same name in two libraries =
  two entries: manga adaptations / LN splits no longer merge);
  /api/series aggregates only the requester's VISIBLE libraries and
  /api/series/books REQUIRES a visible library_id (breaking contract;
  web + app clients updated in the same change).
- /api/media-items, item detail, and the reader content endpoints
  (chapters/panels/outline/THUMBNAILS — page images leaked unguarded
  next to a gated GetPage) all enforce the visible set; resolveLibrary
  no longer honors a hand-typed library_id outside the visible set.
- Annotation/note/bookmark mutations (update/delete/tombstone, sync
  LWW paths incl. conflict resolution) carry ownership predicates:
  another account's bare row id is a 404, not a silent delete.
- Admin user creation: POST /api/auth/users (AdminMiddleware) + a
  Create User card in the admin Users UI — accounts stay creatable
  with self-service registration OFF, which is now the schema default
  (fresh installs; the first-admin bootstrap exception is unchanged).
- Tests: series contract re-pinned (per-library, visibility 403s),
  new ownership + admin-create integration suites, OPDS all=1 test
  now passes an explicit page size (shared-DB growth made the fresh
  books fall off page 1), device-cap helper switched to the admin
  endpoint.
2026-10-13 12:03:01 -04:00
John O'Keefe f24a2d6c22 test(server): fixture collisions fixed, stale contract asserts re-anchored, gofmt — suite now fully green
Closes the remaining integration-suite failures (handoff open bug 3),
line by line:

- Fixture family (the upsert equivalence): tags_filter_test
  ("Science Fiction" found 1 of 3; "Fantasy" 404'd on zero results),
  media_item_isbn_test (normalization read back the first case's ISBN
  everywhere; edge cases saw a nil ISBN; pagination collapsed 5
  creates onto 1 row), search_unified_test (its 3 books were actually
  1 row + two no-op creates) — all POSTed a constant file_path into
  one library while CreateMediaItem upserts ON CONFLICT
  (library_id, file_path). Every fixture now uses unique per-item
  paths, the suite's universal convention elsewhere. TestTagsFilter
  confirmed same-family: counts came right with no second bug.
- New TestMediaItemCreateDuplicatePath pins the honest-create
  contract from da13898: 201 for the first create; 200 + duplicate
  flag + the EXISTING row (never a 201 lie), second payload not
  overwritten.
- library_browse_test: the admin subtest browsed the container's
  /tmp and required it non-empty — env-coupled (empty /tmp → "was
  []"). Now browses a self-created t.TempDir with two subdirs and a
  decoy file, asserting ONLY directories are listed.
- series_integration_test: three stale assertions re-anchored to the
  shipped contracts, each with a ground-truth comment: /dashboard/
  sections key identity on the collection NAME (BuildSections ID =
  CollectionName; the web/app reorder + hidden prefs persist those
  strings) — the kebab query_type is the storage key and is not
  exposed; /api/series/books is name-ONLY (the app client and the
  web SSR page both send name only; library_id is ignored, never
  validated); /api/series is optional-scope BY DESIGN (GetDistinct-
  Series: narg IS NULL OR library_id = narg aggregates across
  libraries when omitted). The former 400-on-missing-library_id
  expectations asserted validation that never shipped.
- gofmt rider on three untouched-in-this-arc test files
  (dashboard_integration, search, test_helpers).

Full suite with -parallel=1: 217 groups pass, zero failures.
2026-10-09 17:11:12 -04:00
john-okeefe a57694b738 fix(tests): URL-encode series name in special characters test
The TestGetSeries_SpecialCharactersInName test was failing with a 400
status because the series name 'Series: Book & Other (Vol. 1)' was
interpolated directly into the URL without encoding. The ampersand was
parsed as a query parameter delimiter, corrupting the request.

Use url.QueryEscape() to properly encode the name parameter.
2026-05-08 20:31:30 -04:00
john-okeefe cce7ad4907 test(series): add unit and integration tests for series feature
Unit tests:
- series_service_test.go: test continueSeriesRowToMediaItems conversion
  with valid fields, null fields, and comprehensive field mapping
- series_test.go: test handler initialization and textToString helper

Integration tests (series_integration_test.go):
- GET /api/series: requires library_id, rejects invalid UUID, returns
  empty array for empty library, pagination params, limit clamped to
  100, response structure validation, special characters in names
- GET /api/series/books: requires library_id and name, handles
  nonexistent series, unauthorized access
- Restore Continue Series system collection
- Dashboard sections include all 5 collections (including continue-series)

Update test helpers:
- Add SeriesHandler to setupTestServer router config
- Add 5th Continue Series collection to createDefaultCollectionsForUser
- Update dashboard integration test for 5 collections
2026-05-08 20:27:40 -04:00