import 'package:flutter_test/flutter_test.dart'; import 'package:pluriwave/estado/estado_alarmas.dart'; import 'package:pluriwave/modelos/alarma_musical.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../helpers/fakes_alarmas.dart'; /// The native scheduler records three failures entirely on its own, outside /// any Dart call: a pre-notice that could not be armed, a refused /// foreground-service start when the alarm should have rung, and a per-alarm /// reschedule that failed after a reboot. /// /// Before this wiring existed, all three logged to logcat and stopped there. /// The alarm stayed switched on in the list, drawn exactly as if scheduling /// had succeeded, and simply never fired — the reported "as if there were no /// alarm at all". These tests pin the drain path that makes them visible. void main() { TestWidgetsFlutterBinding.ensureInitialized(); late FakePuertoAlarmasAndroid android; setUp(() { SharedPreferences.setMockInitialValues({}); android = FakePuertoAlarmasAndroid(); }); EstadoAlarmas crearEstado() => EstadoAlarmas(android: android, iniciarAutomaticamente: false); test('a native pre-notice failure becomes a per-alarm exception', () async { android.fallosNativos = [ {'alarmaId': 'a1', 'tipo': ExcepcionAlarma.tipoFalloPreaviso}, ]; final estado = crearEstado(); addTearDown(estado.dispose); await estado.cargarFallosNativos(); final fallo = estado.ultimaExcepcionPara('a1'); expect(fallo, isNotNull); expect(fallo!.tipo, ExcepcionAlarma.tipoFalloPreaviso); }); test( 'a refused foreground-service start becomes a per-alarm exception', () async { android.fallosNativos = [ {'alarmaId': 'a2', 'tipo': ExcepcionAlarma.tipoFalloServicioSonido}, ]; final estado = crearEstado(); addTearDown(estado.dispose); await estado.cargarFallosNativos(); expect( estado.ultimaExcepcionPara('a2')?.tipo, ExcepcionAlarma.tipoFalloServicioSonido, ); }, ); test('only the alarm whose reschedule failed is marked', () async { android.fallosNativos = [ { 'alarmaId': 'a3', 'tipo': ExcepcionAlarma.tipoFalloReprogramacionArranque, }, ]; final estado = crearEstado(); addTearDown(estado.dispose); await estado.cargarFallosNativos(); expect(estado.ultimaExcepcionPara('a3'), isNotNull); expect( estado.ultimaExcepcionPara('a4'), isNull, reason: 'a sibling alarm that scheduled fine must stay unmarked', ); }); test('malformed native entries are skipped without throwing', () async { android.fallosNativos = [ {'tipo': ExcepcionAlarma.tipoFalloPreaviso}, // no alarmaId {'alarmaId': 'a5'}, // no tipo ]; final estado = crearEstado(); addTearDown(estado.dispose); await estado.cargarFallosNativos(); expect(estado.ultimaExcepcionPara('a5'), isNull); }); test('a failing native read never surfaces as an alarm error', () async { // A diagnostics gap must not look like a scheduling problem: an older // native build simply has no such channel method. android.fallaLecturaFallosNativos = true; final estado = crearEstado(); addTearDown(estado.dispose); await estado.cargarFallosNativos(); expect(estado.error, isNull); }); }