fix(radio): treat custom-station path resolution as part of the IO surface
Build & Deploy PluriWave / Análisis de código (push) Successful in 36s
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 2m30s

_cargarEmisorasCustom resolved the file path outside the IO guard, so
a throw from the resolver escaped into _init()'s Future.wait and took
the sibling loads (populares, favoritos, grupos) down with it — a gap
the old catch-all used to cover. Path resolution now gets the same
IO-fail treatment as an unreadable file: degraded flag, logged skip,
siblings unaffected.
This commit is contained in:
2026-07-11 17:19:44 +02:00
parent d824ef5c21
commit 48e74e6bfe
2 changed files with 40 additions and 1 deletions
+16 -1
View File
@@ -529,7 +529,22 @@ class EstadoRadio extends ChangeNotifier {
/// shared [parseListaTolerante] (D1): survivors are kept, no flag/
/// quarantine at all.
Future<void> _cargarEmisorasCustom() async {
final archivo = await _archivoCustom();
// Resolving the path itself is part of the IO surface: a throw here must
// get the same IO-fail treatment as an unreadable file, not escape into
// _init()'s Future.wait and take the sibling loads down with it.
final File archivo;
try {
archivo = await _archivoCustom();
} catch (e) {
_customDegradado = true;
registrarSaltoPersistencia(
subsistema: 'emisoras_custom',
detalle: 'resolucion de ruta',
razon: e.toString(),
);
notifyListeners();
return;
}
final contenido = await _leerContenidoCustom(archivo);
if (contenido == null) return; // ya resuelto: vacio o degradado por IO.
+24
View File
@@ -408,6 +408,30 @@ void main() {
},
);
test(
'si resolver la ruta del archivo custom falla, la inicializacion '
'sobrevive y las cargas hermanas no se pierden (D5 IO-fail)',
() async {
final estado = EstadoRadio(
audio: FakeServicioAudio(),
favoritos: FakeServicioFavoritos(),
radio: FakeServicioRadio(),
servicioEcualizador: FakeServicioEcualizador(),
resolverArchivoCustom:
() async => throw const FileSystemException('sin storage'),
iniciarAutomaticamente: false,
);
// Path resolution failing must be treated as an IO-fail, not
// escape _cargarEmisorasCustom: it runs inside _init()'s
// Future.wait, so an uncaught throw would also reject the
// sibling loads (populares/favoritos/grupos).
await estado.inicializar();
expect(estado.emisorasCustom, isEmpty);
},
);
test(
'JSON invalido al nivel superior pone en cuarentena el archivo '
'original (D5 parse-fail)',