87 lines
4.6 KiB
Markdown
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.
|