crabidy/quality/audiobookshelf-provider.md

87 lines
4.6 KiB
Markdown

# Quality gates — audiobookshelf provider
Criteria an implementation of `absdy` (architecture/audiobookshelf-provider.md)
must satisfy. Each is pass/fail by reading/reasoning; automated coverage lives
in `absdy/src/tests.rs` (unit, no network) and `absdy/tests/live.rs` (ignored,
real server).
## Secrets (hard rule — highest priority)
- [x] **G1 — `api_key` never in `Debug`.** `Settings` and `Client` `Debug`
output redact the key. *(test: `settings_debug_redacts_the_api_key`.)*
- [x] **G2 — The token never reaches a log or error.** The `?token=` stream
URL is built only in `Abs::stream_url` and returned; it is never passed to
`debug!`/`warn!`/`error!`, and never to a `reqwest` call inside `absdy` (so
it cannot appear in a `reqwest` error). Browse URLs (logged at `debug`)
carry auth in the bearer header, not the URL.
- [x] **G3 — `AbsApi::Debug` redacts the token** (manual impl, not derived).
## Errors and robustness (hard rule: no panics on input/network)
- [x] **G4 — No panics on bad input or network failures.** All backend
failures map to a typed `ProviderError` (`FetchError` for fetches,
`MalformedPath` for bad paths, `InvalidInput` for empty titles, `Config` for
missing creds). No `unwrap`/`expect`/`panic!` on request, path, or
response data. *(tests: `backend_failures_are_typed_never_panics`,
`foreign_and_malformed_paths_are_rejected`,
`track_metadata_picks_the_file_by_ino`.)*
- [x] **G5 — Defensive decoding.** Wire DTOs use `#[serde(default)]`; missing
fields degrade (empty string / `None` / dropped entry), never error the
whole call. Items/files without an id/ino are dropped, not panicked on.
- [x] **G6 — Timeouts on every external call.** `AbsApi` sets a per-call
`reqwest` timeout from `call_timeout_secs` (default 30).
## Tree and path contract
- [x] **G7 — Path parsing is total and reserved-word safe.** Every `/abs/...`
shape maps to a variant or `MalformedPath`; the literal `search` segment
routes to the search branch and never collides with a (UUID) item id.
Empty segments (`/abs//x`) are `MalformedPath`.
- [x] **G8 — Root lists only book libraries.** Podcast libraries are filtered
(D6). *(test: `root_lists_only_book_libraries`.)*
- [x] **G9 — Queueability reflects audio.** A book child is queueable iff
`num_audio_files > 0`; an ebook is shown but not queueable/downloadable; an
audio-less book node is empty and non-queueable. *(tests:
`a_library_lists_search_then_books_with_audio_gating_queueability`,
`an_audio_less_book_is_empty_and_not_queueable`.)*
- [x] **G10 — Download blessing matches the other providers.** A node is
downloadable iff it is queueable or holds tracks; a child is downloadable iff
it is queueable. The `search` child is neither.
- [x] **G11 — Search terms mirror tidal/youtube/fyyd and are per-library.**
`search` is creatable; terms are editable + deletable, deduped, recreated
implicitly on stale paths, isolated per library id, and rename/delete are
idempotent. *(tests: `search_terms_list_books_and_are_per_library`,
`search_terms_rename_and_delete`.)*
## Playback
- [x] **G12 — Stream URL is derivable with no API call.** `get_urls_for_track`
builds the URL from the path (item id + ino) plus the token, making no
network request; the direct and search branches yield the same URL. *(test:
`track_stream_url_embeds_the_token_and_path_ids_without_a_call`.)*
- [x] **G13 — Track metadata.** `artist` = author, `album` = book title,
`title` = file title, `duration` = seconds (`None` when absent),
`provider_item_id` = `"<item>:<ino>"`. *(test:
`a_book_lists_its_files_as_tracks`.)*
## Wiring and lifecycle
- [x] **G14 — Non-fatal init.** Missing/incomplete `abs.toml` (no
`base_url`/`api_key`) disables `/abs` with a warning; it never aborts
startup. *(test: `init_requires_base_url_and_api_key`; orchestrator build
block mirrors fyyd.)*
- [x] **G15 — Orchestrator parity.** `abs` is wired at every dispatch point
(`is_track_path`, `get_urls_for_track`, `get_metadata_for_track`,
`get_lib_node`, `create`/`rename`/`delete`, `resolve_tracks_into`, root
child) and in settings (`ALL_PROVIDERS`, `ProviderToggles`, `all`,
`provider_toggles`).
- [x] **G16 — Bounded listings.** `items_per_library` and `search_results`
cap every listing; truncation to the cap is `log`-ged, not silent.
## Live validation (drift gate)
- [x] **G17 — DTOs decode the real server.** `absdy/tests/live.rs`
(`#[ignore]`) exercises libraries → items → search → detail against a real
server and confirms the shapes. Verified during implementation against the
provided test server.