Files
Tobias GesellchenandClaude Opus 4.8 6efad165f6 test(http-client): mock TuneIn upstream so playback tests run offline (refs #451)
Make the BMX TuneIn integration tests independent of the live TuneIn
(radiotime.com) service, the same way Spotify/Amazon are already mocked.

- pkg/service/bmx: the TuneIn upstream base URLs become configurable vars with
  a SetTuneInEndpoints(opmlBase, apiBase) setter that also registers the host in
  the outbound allowlist. Defaults are unchanged (real radiotime hosts), so
  production behaviour is identical; tests can redirect to a mock.
- cmd/soundtouch-service: new --tunein-opml-url / --tunein-api-url flags
  (TUNEIN_OPML_URL / TUNEIN_API_URL) wired through to SetTuneInEndpoints.
- cmd/mock-tunein + pkg/testutils/tunein: a mock TuneIn server serving Tune.ashx
  (stream URLs) and describe.ashx (name/logo) with RFC-5737 values; unmocked
  endpoints 404 so a test needing them fails loudly.
- docker-compose.ci.yml: add the tunein-mock service and point the service at it.
- tunein_playback_station.http now asserts the mock-served stream URL + name,
  proving the path is offline. tunein_favorite.http covers the local-only
  favorite add/remove (202).
- TUNEIN-MOCK-MISSING.md lists the upstream captures still needed (episode /
  navigate / search) before those routes can be mocked + tested.

make test-http-client: 61 requests, 0 failed. golangci-lint clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 19:11:24 +02:00

2.5 KiB

TuneIn mock: missing upstream fixtures

The mock-tunein service (cmd/mock-tunein, pkg/testutils/tunein) lets the integration suite exercise the BMX TuneIn endpoints without hitting the live TuneIn (radiotime.com) service. The service is pointed at it in CI via TUNEIN_OPML_URL / TUNEIN_API_URL (see docker-compose.ci.yml).

Mocked today (hermetic)

  • GET /Tune.ashx?id=...&formats=... (station stream resolution)
  • GET /describe.ashx?id=... (station name + logo)

These back GET /bmx/tunein/v1/playback/station/{stationID} (tunein_playback_station.http), which is therefore fully offline.

Local-only BMX routes (no upstream at all, already hermetic): /bmx/tunein/v1/favorite/{id} (POST/DELETE), /bmx/tunein/v1/token, /bmx/tunein/v1/report.

NOT mocked yet — need real upstream captures

The following BMX TuneIn routes call api.radiotime.com / opml.radiotime.com endpoints we do not have recorded upstream responses for. The mock returns 404 for these (so a test that needs them fails loudly). To cover them we need to capture the raw radiotime responses (service -> TuneIn), not just the final BMX responses the speaker received:

BMX route (speaker-facing) Upstream call the service makes Needed capture
GET /bmx/tunein/v1/playback/episode/{id} GET api.radiotime.com/profiles/{p<N>}/contents?version=1.3 then Tune.ashx?id=<episode> profiles/<id>/contents JSON
GET /bmx/tunein/v1/playback/episodes/{id} same profile-contents JSON profiles/<id>/contents JSON
GET /bmx/tunein/v1/navigate GET opml.radiotime.com/?render=json (and Browse.ashx) OPML-as-JSON browse pages
GET /bmx/tunein/v1/search GET api.radiotime.com/profiles?fulltextsearch=true&version=1.3&query=... search-results JSON
GET /bmx/tunein/v1/search/next opaque cursor URL from a prior search search next-page JSON

How to collect

Run the service against live TuneIn with RECORD_INTERACTIONS on and the proxy/recorder capturing the upstream category, drive the speaker (or the /api/tunein/* UI) through podcast playback / browse / search, then copy the sanitized radiotime responses here. Once captured, extend pkg/testutils/tunein/handlers.go with the matching /profiles*, /?render=json handlers and add the corresponding .http flows.

Keep captured fixtures sanitized (no real account ids/tokens); the radiotime station/program ids themselves are public catalog ids and are fine to keep.