crabidy/quality/jamendo.md

78 lines
4.7 KiB
Markdown

# Quality gates — jamendo provider
Gates for the `/jamendo` provider (architecture/jamendo-provider.md). Automatic
tests live inline in `jamendody/src/tests.rs` (unit, over a `FakeJam`) and are
named in each gate. LLM gates are read-and-reason checks an implementing agent
verifies.
## Automatic tests (jamendody/src/tests.rs)
- **T1 `root_has_only_search`** — `get_lib_root` exposes exactly one child,
`/jamendo/search` (creatable), and the root is not queueable. (D3)
- **T2 path parsing** (`*_parse`, `foreign_and_malformed_paths_reject`) —
`/jamendo`, `/jamendo/search`, `/jamendo/search/<term>`, `/jamendo/track/<id>`,
`/jamendo/album/<id>` parse to their variants; foreign roots, empty segments,
wrong arity, and unknown tags are `MalformedPath`. (D3)
- **T3 `is_track_path_only_for_canonical_track`** — true only for
`/jamendo/track/<id>`; false for albums, search nodes, root. (D3/D4)
- **T4 `search_term_lists_tracks_and_albums`** — a search node carries matching
tracks as rows at canonical `/jamendo/track/<id>` paths and matching albums as
**queueable** container children at `/jamendo/album/<id>`; the node is
queueable and `is_downloadable`. (D3/D4)
- **T5 `album_node_lists_tracks`** — `/jamendo/album/<id>` lists the album's
tracks as canonical rows; node is queueable + downloadable. (D3)
- **T6 `get_urls_for_track_returns_audio`** — `get_urls_for_track` on a track
path returns exactly the backend `audio` URL; a non-track path is
`MalformedPath`, not a stream. (D4)
- **T7 `track_metadata_maps_fields`** — `get_metadata_for_track` maps
`name→title`, `artist_name→artist`, `album_name→Album.title`,
`duration(secs)→Track.duration` unchanged, `provider_item_id == "track:<id>"`.
(D4)
- **T8 `create_rename_delete_search_term`** — creating a term adds an editable +
deletable child under `/jamendo/search`; empty/whitespace title →
`InvalidInput`; rename moves the term; delete removes it. (D3)
- **T9 `backend_failures_are_typed`** — a backend whose every call errors makes
`get_lib_node` return `ProviderError::FetchError`, never panics. (D2)
- **T10 `settings_debug_redacts_client_id`** — `format!("{:?}", settings)` with
a `client_id` set does not contain the id and shows `<redacted>`. (D5)
- **T11 `duration_is_seconds_not_ms`** — a track whose backend `duration_secs`
is 210 yields `Track.duration == Some(210)` (guards against a ms/seconds
regression like the web bug). (D1/D4)
## LLM quality gates (read-and-reason)
- **G1 — no panics on external conditions.** No `unwrap`/`expect`/`panic!` on
network, decode, config, or path input in `jamendody`. Lock poisoning
degrades to empty (`unwrap_or_default`), never panics. (Hard rule)
- **G2 — typed error boundary.** Every `JamApi` failure is a `FetchError`
variant; the provider maps it to `ProviderError` (`FetchError` /
`MalformedPath` / `NotSupported` / `InvalidInput`) and logs the typed cause.
No `color-eyre`/`anyhow` leaks across the trait. (Hard rule)
- **G3 — client_id is redacted everywhere.** `Settings` and `JamApi` have
hand-written `Debug` that redact `client_id`; it never appears in a log line,
and a resolved `audio` URL (may carry a signed token) is never logged. (Hard
rule: redact secrets)
- **G4 — timeouts on every external call.** The `reqwest` client is built with a
timeout from `call_timeout_secs`; no unbounded HTTP call exists. (Hard rule)
- **G5 — bounded results, no silent caps.** `search_results` (≤ API max 200) and
`album_tracks` bound listings; when a cap truncates, it is `log`-ged
(`debug`/`warn`), not silent. (Hard rule: no silent caps)
- **G6 — non-fatal init.** A missing/empty `client_id` (or unparseable
`jamendo.toml`) makes `init` return a typed `Config` error; the orchestrator
maps it to `None` so only `/jamendo` is lost, never the server. No blocking
network in `build()`. (D1/D5)
- **G7 — canonical leaf independence.** Track rows and album children always use
`/jamendo/track/<id>` and `/jamendo/album/<id>` regardless of the branch they
were reached through, so playback/expansion never depends on browse context.
(D3)
- **G8 — no proto/wire/TUI/player change.** The feature is a pure path-prefix
subtree: no edit to `crabidy.proto`, `ProviderCommand`, the TUI, or
`audio-player`. Search-term flows reuse the existing create/rename/delete RPCs.
(Assumptions)
- **G9 — defensive DTOs.** Wire structs use `#[serde(default)]`; a missing field
degrades (empty/`None`), a renamed field is a local `JamApi` fix, never a
crash. Ids decode whether numeric or string. (R3)
- **G10 — docs.** Public items (`Client`, `Settings`, `PROVIDER_ROOT`, the `Jam`
trait and its methods, domain types) carry doc comments stating intent and
error behavior. (Preference)