feat: Implement startup retry mechanism for custom stations and equalizer persistence

- Added state management for startup retry and custom station handling in `EstadoRadio`.
- Created tasks for implementing strict TDD with RED tests for HTTP failure retries and EQ persistence.
- Developed verification report to ensure compliance with TDD practices.
- Introduced fake services for testing, including `FakeServicioAudio`, `FakeServicioFavoritos`, and `FakeServicioRadio`.
- Implemented widget tests for `PantallaInicio` and `PantallaFavoritos` to validate UI behavior with custom stations.
- Enhanced `ServicioRadio` to support host rotation and retry logic for API calls.
- Established a new configuration file to enforce project constraints and testing rules.
This commit is contained in:
Javier Bautista Fernández
2026-04-27 17:34:04 +02:00
parent 922b3b4859
commit d579a0e107
21 changed files with 1902 additions and 156 deletions

View File

@@ -0,0 +1,222 @@
# Verification Report: startup-retry-custom-stations-eq-persistence
**Status**: blocked
**Mode**: Strict TDD
**Date**: 2026-04-27
**Verifier**: sdd-verify worker 2
**Round**: re-verify after targeted apply-fix
Runtime verification remains blocked because `flutter` is not available in PATH in this shell. Static review confirms the targeted fixes from the prior verification round are present. This change is still **not archiveable** because Strict TDD requires passing `flutter test` evidence before scenarios can be marked compliant and before tasks 5.3/5.4 can be completed.
---
## Completeness
| Metric | Value |
|---|---:|
| Tasks total | 23 |
| Tasks complete | 21 |
| Tasks incomplete | 2 |
Incomplete by design until runtime evidence exists:
- `[ ] 5.3 VERIFY: Run flutter test only; never run flutter build.`
- `[ ] 5.4 VERIFY: Compare passing tests against every Given/When/Then scenario in spec.md.`
Tasks 5.3 and 5.4 **must remain incomplete**. `flutter test` was attempted and could not run because Flutter is missing from PATH.
---
## Validation Commands Attempted
| Command | Exit | Result |
|---|---:|---|
| `git status --short` | 0 | Confirmed active uncommitted work exists; verification did not revert or edit production code. |
| `Get-Command flutter -ErrorAction SilentlyContinue` | 127 | `FLUTTER_NOT_FOUND: Get-Command flutter returned no executable on PATH.` |
| `flutter test` | 1 | `CommandNotFoundException`: `flutter` is not recognized as a cmdlet/program in this PowerShell session. |
| `flutter build` | N/A | Not run; prohibited by project and mission constraints. |
Coverage, linter, and type-check commands were not run because the configured tools are Flutter-backed (`flutter test --coverage`, `flutter analyze`) and Flutter is unavailable.
---
## TDD Compliance
| Check | Result | Details |
|---|---|---|
| TDD Evidence reported | PASSED STATIC | `apply-progress.md` now contains `## TDD Cycle Evidence`. |
| Test files exist | PASSED STATIC | Verified `test/servicios/servicio_radio_test.dart`, `test/estado/estado_radio_test.dart`, and `test/pantallas/pantalla_inicio_test.dart`. |
| RED confirmed | PARTIAL / BLOCKED | Test intent is documented and test files exist, but RED-first ordering cannot be independently proven without historical run output. |
| GREEN confirmed | BLOCKED | `flutter test` cannot execute in this environment. |
| Triangulation adequate | PARTIAL / BLOCKED | Targeted missing scenarios now have tests; runtime proof is blocked. |
| Safety net for modified files | BLOCKED | Apply-progress records the same Flutter availability blocker. |
**TDD compliance verdict**: blocked. The prior missing-evidence-table finding is fixed, but Strict TDD cannot pass until `flutter test` runs successfully.
---
## Test Layer Distribution
| Layer | Tests | Files | Tool |
|---|---:|---:|---|
| Unit/service | 2 | 1 | `flutter_test` + `http/testing` |
| Unit/state | 6 | 1 | `flutter_test` |
| Widget/component | 3 | 1 | `flutter_test` |
| E2E | 0 | 0 | Not configured |
| **Total** | **11** | **3** | |
Related helper file:
- `test/helpers/fakes.dart`
---
## Scenario Coverage Matrix
Runtime result is `BLOCKED` for every scenario because no test execution was possible. Under Strict TDD, no scenario can be marked compliant until a covering test has passed.
| Requirement | Scenario | Static implementation evidence | Test evidence found | Result |
|---|---|---|---|---|
| Startup radio loading resilience | transient startup failure recovers | `ServicioRadio._get()` retries with host rotation; `EstadoRadio._init()` calls station loading after EQ load. | `test/servicios/servicio_radio_test.dart` covers retry/host rotation success. No direct `EstadoRadio` startup integration test for final visible error clearing, but design assigns retry ownership to `ServicioRadio`. | PARTIAL STATIC / BLOCKED |
| Startup radio loading resilience | startup failures are exhausted | `ServicioRadio._get()` caps attempts; `EstadoRadio.cargarPopulares()` exposes `Sin conexión a la API de radio`; `PantallaInicio` has `Reintentar`. | Service cap/error test exists. State and widget tests now cover startup failure plus manual retry recovery. | COVERED STATICALLY / BLOCKED |
| Custom stations in main listing | added custom station appears on home | `EstadoRadio.emisorasInicio` combines custom + popular; `PantallaInicio` uses it without genre filter; tap calls `EstadoRadio.reproducir`. | State getter test exists. Widget test now renders `Custom Uno` and taps it, asserting fake audio playback and click registration. | COVERED STATICALLY / BLOCKED |
| Custom stations in main listing | custom station can be favorited | `TarjetaEmisora` uses the same `EstadoRadio.toggleFavorito` flow for all station cards; `toggleFavorito` reloads favorites. | Widget test taps favorite on the custom station. Widget test now opens `PantallaFavoritos` and verifies the custom station appears after reload. | COVERED STATICALLY / BLOCKED |
| Main equalizer persistence | main EQ restores after restart | `ServicioEcualizador.cargar()` loads persisted main EQ; `EstadoRadio._cargarEcualizadorPersistido()` sets provider state before station loading; playback applies selected EQ. | State test covers persisted main EQ before playback EQ selection. | COVERED STATICALLY / BLOCKED |
| Main equalizer persistence | EQ state persists even when native EQ is unavailable | `EstadoRadio` loads persisted EQ independent of `audio.ecualizadorDisponible`; production `ServicioAudio.aplicarPreset()` stores `_presetActual` before returning when native EQ is unavailable. | State test now uses `FakeServicioAudio(ecualizadorActivo: false)` and verifies main + per-station persisted EQ remain exposed. | COVERED STATICALLY / BLOCKED |
| Favorite station equalizer mode | favorite station own EQ is applied | `_presetParaEmisora()` prefers `_presetsEmisoraMap[uuid]`; `reproducir()` applies selected preset and updates state. | State test covers own EQ overriding main. | COVERED STATICALLY / BLOCKED |
| Favorite station equalizer mode | favorite station falls back to main EQ | Map absence falls back to `_presetPrincipal`. | State test now covers a favorite without own EQ using main EQ from first play. | COVERED STATICALLY / BLOCKED |
| Favorite station equalizer mode | disabling own EQ restores main behavior | `deshabilitarPresetEcualizadorPorEmisora()` removes persisted entry and applies main when current station matches. | State test covers disabling own EQ and replaying with main fallback. | COVERED STATICALLY / BLOCKED |
| Test-first implementation | strict TDD guardrail | `openspec/config.yaml` and `state.yaml` have Strict TDD active; `apply-progress.md` now records TDD cycle evidence and blocked Flutter commands. | TDD table exists, but `flutter test` cannot run, so GREEN evidence is unavailable. | PARTIAL STATIC / BLOCKED |
**Runtime compliance summary**: 0/10 scenarios compliant because no covering test has passed in this environment.
**Static coverage summary**: 8/10 scenarios covered statically, 2/10 partial statically.
---
## Prior Findings Re-check
| Prior finding | Status | Evidence |
|---|---|---|
| `mediaItem.add(null)` shadowing in `playMediaItem(MediaItem mediaItem)` | FIXED | `lib/servicios/servicio_audio.dart:239` now uses `this.mediaItem.add(null)`. Other `mediaItem.add(null)` calls are outside the shadowing scope. |
| `apply-progress.md` missing Strict TDD Cycle Evidence | FIXED | `openspec/changes/startup-retry-custom-stations-eq-persistence/apply-progress.md` now contains `## TDD Cycle Evidence`. |
| Missing native-EQ-unavailable test | FIXED STATICALLY | `test/estado/estado_radio_test.dart` includes `mantiene EQ persistido aunque el ecualizador nativo no esté disponible`. |
| Missing manual retry test | FIXED STATICALLY | `test/pantallas/pantalla_inicio_test.dart` includes `PantallaInicio permite reintentar manualmente tras fallo inicial agotado`; state test also checks manual `cargarPopulares()` recovery. |
| Missing custom station playback tap test | FIXED STATICALLY | `test/pantallas/pantalla_inicio_test.dart` taps `Custom Uno` and asserts playback via fake audio. |
| Missing favorites screen reload test | FIXED STATICALLY | `test/pantallas/pantalla_inicio_test.dart` opens `PantallaFavoritos` after favoriting and expects `Custom Uno`. |
| Missing favorite main-EQ fallback test | FIXED STATICALLY | `test/estado/estado_radio_test.dart` includes `favorita sin EQ propio usa EQ principal desde el primer play`. |
| `PantallaAjustes` async per-band loop | FIXED | `lib/pantallas/pantalla_ajustes.dart:94-98` calls `estado.cambiarPresetEcualizador(p)` once from `EcualizadorWidget.onCambio`; no per-band async loop remains in the widget. |
---
## Correctness: Static Structural Evidence
| Requirement | Status | Notes |
|---|---|---|
| Startup radio loading resilience | PARTIAL STATIC | Retry/host rotation and exhausted retry behavior exist. Manual retry coverage was added. Automatic transient startup recovery is proven primarily at the service layer, not with a direct `EstadoRadio` startup integration test. |
| Custom stations in the main listing | IMPLEMENTED STATICALLY | Combined listing, normal playback path, same favorite flow, and favorites screen reload are represented in code/tests. |
| Main equalizer persistence | IMPLEMENTED STATICALLY | EQ persistence service, provider load, native-unavailable state retention, and playback application are represented in code/tests. |
| Favorite station equalizer mode | IMPLEMENTED STATICALLY | Own EQ, main fallback, and disabling own EQ are represented in state code/tests. |
| Test-first implementation | BLOCKED | Strict TDD metadata and TDD evidence table exist; runtime GREEN evidence is blocked by missing Flutter. |
---
## Coherence: Design Decisions
| Decision | Followed? | Notes |
|---|---|---|
| API retry owner in `ServicioRadio._get()` | Yes | Bounded retry/host rotation remains centralized in `ServicioRadio._get()`. |
| Custom stations home UX via combined listing | Yes | `EstadoRadio.emisorasInicio` combines custom + popular; `PantallaInicio` uses it when no genre filter is selected. |
| EQ storage in `ServicioEcualizador` with SharedPreferences JSON | Yes | `ServicioEcualizador` stores main and per-station EQ JSON. |
| Station EQ fallback by `Map<uuid, PresetEcualizador>` absence | Yes | `_presetParaEmisora()` returns station preset or main preset. |
| Current station source in `ServicioAudio`/handler | Yes static | `ServicioAudio` sets/clears `emisoraActual`; the previously shadowed `playMediaItem` exception path now uses `this.mediaItem.add(null)`. |
---
## Assertion Quality Audit
Reviewed related tests:
- `test/servicios/servicio_radio_test.dart`
- `test/estado/estado_radio_test.dart`
- `test/pantallas/pantalla_inicio_test.dart`
**Assertion quality**: PASSED STATIC REVIEW. No tautological assertions, ghost loops, or smoke-only widget tests were found in the reviewed change tests. Assertions check concrete behavior: host sequence, call counts, visible text, persisted favorite state, selected EQ preset, and playback calls.
---
## Issues Found
### CRITICAL
None found in static re-review after the targeted fixes.
### WARNING
1. Runtime verification is blocked because Flutter is unavailable. This is not a production-code defect, but it blocks Strict TDD completion and archive.
2. The transient startup recovery scenario is still proven most directly at `ServicioRadio` level rather than by a direct `EstadoRadio.inicializar()` integration test that starts with a transient service failure and ends with no visible startup error. Given the design decision that retry ownership lives in `ServicioRadio`, this is a coverage nuance, not a new blocker beyond runtime execution.
### SUGGESTION
1. When Flutter is available, run `flutter test` first; only after it passes should tasks 5.3/5.4 be marked complete.
2. Consider adding one direct `EstadoRadio.inicializar()` transient-recovery test later if the team wants tighter end-to-end coverage for the first startup scenario.
---
## Changed File Coverage
Coverage analysis skipped: Flutter is unavailable, so `flutter test --coverage` cannot run.
---
## Quality Metrics
**Linter**: Not run (`flutter analyze` requires Flutter in PATH).
**Type checker**: Not run (`flutter analyze` requires Flutter in PATH).
**Build**: Not run; build commands are prohibited by project and mission constraints.
---
## Tasks 5.3 / 5.4 Status
| Task | Status | Reason |
|---|---|---|
| 5.3 VERIFY: Run `flutter test` only; never run `flutter build`. | INCOMPLETE | `flutter test` was attempted but failed before execution because the `flutter` command is unavailable. |
| 5.4 VERIFY: Compare passing tests against every Given/When/Then scenario. | INCOMPLETE | No test passed at runtime in this environment, so scenario compliance cannot be accepted under Strict TDD. |
Do not mark either task complete until `flutter test` actually passes.
---
## Verdict
**BLOCKED, not archiveable.**
Static targeted fixes are present and no new static blockers were found, but Strict TDD runtime verification cannot pass without a successful `flutter test` run.
---
## Next Recommended
1. Install/expose Flutter on PATH for the verification environment.
2. Run `flutter test` only; do **not** run `flutter build`.
3. If tests pass, update tasks 5.3/5.4 and rerun verify to produce a passing scenario compliance matrix.
---
## Risks
- Until `flutter test` runs, compile errors or failing tests may still exist undetected.
- Archive would be premature because Strict TDD requires passing behavioral evidence, not just static review.
---
## Artifacts Updated
- `openspec/changes/startup-retry-custom-stations-eq-persistence/verify-report.md`
- Engram topic `sdd/startup-retry-custom-stations-eq-persistence/verify-report`
---
## Skill Resolution
injected