import 'package:flutter_test/flutter_test.dart'; import 'package:pluriwave/estado/estado_alarmas.dart'; import 'package:pluriwave/modelos/alarma_musical.dart'; import 'package:pluriwave/servicios/servicio_alarmas_android.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. /// /// They deliberately build fixtures through [FalloProgramacionNativo.fromMap] /// using the REAL native key names (`alarmId`/`type`/`atMillis`, see /// `AlarmScheduler.kt:1389-1391`). An earlier draft seeded the fake with /// guessed Spanish keys and consumed the same guess, so every entry would /// have been silently dropped in production while the tests stayed green. void main() { TestWidgetsFlutterBinding.ensureInitialized(); late FakePuertoAlarmasAndroid android; setUp(() { SharedPreferences.setMockInitialValues({}); android = FakePuertoAlarmasAndroid(); }); EstadoAlarmas crearEstado() => EstadoAlarmas(android: android, iniciarAutomaticamente: false); /// Mirrors exactly what the native side puts on the channel. FalloProgramacionNativo falloNativo(String alarmaId, String tipo) => FalloProgramacionNativo.fromMap({ 'alarmId': alarmaId, 'type': tipo, 'atMillis': 1700000000000, }); test('the fixture helper decodes the real native key names', () { final fallo = falloNativo('a0', ExcepcionAlarma.tipoFalloPreaviso); expect(fallo.alarmaId, 'a0'); expect(fallo.tipo, ExcepcionAlarma.tipoFalloPreaviso); }); test('a native pre-notice failure becomes a per-alarm exception', () async { android.fallosNativos = [ falloNativo('a1', 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 = [ falloNativo('a2', 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 = [ falloNativo('a3', 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('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); }); }