crabidy/quality/fyyd-provider.md

78 lines
4.3 KiB
Markdown

# Quality gates — fyyd provider
LLM-verified gates for `architecture/fyyd-provider.md`. Automatic tests live
in `fyyd/src/lib.rs` (provider logic against a fake `Fyyd`) and
`crabidy-server/src/settings.rs` (the provider toggle).
## Correctness / semantics
- [x] Tree shape matches D3: `/fyyd``search` (creatable) + `hot`;
`/fyyd/search/<term>` and `/fyyd/hot` list **podcasts** as children
(not tracks); `/fyyd/<branch>/<podcast>` lists **episodes** as tracks;
`/fyyd/<branch>/<podcast>/<episode>` is the track leaf. Verified by
`root_lists_search_and_hot`, `search_terms_list_podcasts_as_children`,
`a_podcast_lists_its_episodes_as_tracks`, `hot_lists_featured_podcasts`.
- [x] A podcast listing node is not itself queueable; each podcast child is
queueable and downloadable (queue/capture a whole podcast). An episode
list node is queueable and downloadable. `is_track_path` is true only
for episode leaves. Verified by the tests above and
`tracks_resolve_streams_and_metadata`.
- [x] Search terms are creatable/renamable/deletable exactly like
tidal/youtube (in-memory, dedup, implicit recreation on stale paths),
and only `/fyyd/search` is creatable. Verified by
`search_terms_list_podcasts_as_children`,
`search_terms_rename_and_delete`, `foreign_and_malformed_paths_are_rejected`.
- [x] Playback resolves an episode to its `enclosure` URL; a track's
`artist` is the podcast title and `provider_item_id` is the fyyd
episode id. Verified by `tracks_resolve_streams_and_metadata`.
- [x] The download blessing is applied centrally (node downloadable when
queueable or track-bearing; children mirror queueability), so `W`
captures work with no capture-side change.
## Robustness (hard rules)
- [x] No panic on any input: malformed/foreign paths, empty create/rename
input, a missing enclosure, and every backend failure return a typed
`ProviderError`, never a panic. Verified by
`foreign_and_malformed_paths_are_rejected`,
`a_missing_enclosure_is_an_error_not_a_panic`,
`backend_failures_are_typed_never_panics`.
- [x] Every external call is bounded by a timeout (`call_timeout_secs`,
default 30) set on the `reqwest` client; every listing is capped
(`search_results`, `hot_count`, `episodes_per_podcast`). D5.
- [x] fyyd responses decode defensively: the `data` envelope is unwrapped,
DTO fields use `#[serde(default)]`, entries without a usable id are
dropped, and non-positive durations degrade to `None`. Failures are
typed `FetchError``ProviderError::FetchError`.
- [x] No secrets: fyyd's public API uses no credentials, so nothing secret
is logged; only public URLs/ids appear in traces.
## Integration / operability
- [x] `fyyd` is a registered provider: in `ALL_PROVIDERS`, `ProviderToggles`
(`all()` + `provider_toggles()`), and the default `crabidy-server.toml`.
Disabling it in the `providers` list drops the `/fyyd` subtree. Covered
by the existing settings tests (which iterate `ALL_PROVIDERS`).
- [x] Init is non-fatal: a client that fails to build disables `/fyyd` with
a warning and leaves every other provider and the server running (D1).
A missing `fyyd.toml` is normal (no credentials needed).
- [x] The orchestrator routes every `ProviderClient` method for `/fyyd`
paths to the fyyd client (owns-check + `fyyd_provider()`), and
`get_lib_root` lists `fyyd` only when the client is mounted. No proto
change and no new `ProviderCommand` were needed.
## Live validation
- [x] Validated against the real `api.fyyd.de` on 2026-07-23. All four
endpoints match the `fyyd/src/api.rs` DTOs exactly: responses are
wrapped in `data`; a podcast carries numeric `id` + `title`;
`/podcast/episodes` returns a single object with `title` and an
`episodes` array; each episode carries `id` (int), `title`,
`enclosure` (a direct audio URL, e.g. an `.mp3`), `duration` (int
seconds), and `podcast_id` (int); `/episode?episode_id=` returns one
such object. No DTO change was needed.
- [ ] End-to-end audio playthrough of an enclosure and a `W` capture to
`/crabidy` still want a manual smoke test on the running server (the
resolve path and the capture reqwest client both follow the enclosure,
including podtrac/redirect URLs).