fix(radio): treat custom-station path resolution as part of the IO surface
_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:
@@ -529,7 +529,22 @@ class EstadoRadio extends ChangeNotifier {
|
|||||||
/// shared [parseListaTolerante] (D1): survivors are kept, no flag/
|
/// shared [parseListaTolerante] (D1): survivors are kept, no flag/
|
||||||
/// quarantine at all.
|
/// quarantine at all.
|
||||||
Future<void> _cargarEmisorasCustom() async {
|
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);
|
final contenido = await _leerContenidoCustom(archivo);
|
||||||
if (contenido == null) return; // ya resuelto: vacio o degradado por IO.
|
if (contenido == null) return; // ya resuelto: vacio o degradado por IO.
|
||||||
|
|
||||||
|
|||||||
@@ -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(
|
test(
|
||||||
'JSON invalido al nivel superior pone en cuarentena el archivo '
|
'JSON invalido al nivel superior pone en cuarentena el archivo '
|
||||||
'original (D5 parse-fail)',
|
'original (D5 parse-fail)',
|
||||||
|
|||||||
Reference in New Issue
Block a user