Completes the bridge the native side already exposed. AlarmScheduler and PluriWaveAlarmService record a pre-notice that could not be armed, a refused foreground-service start, and a per-alarm reschedule that failed after a reboot -- but nothing read them, so all three still ended at logcat. EstadoAlarmas now drains them at startup and turns each into a per-alarm exception, which the card already knows how to mark. An alarm that never reached the OS stops looking identical to one that did. The read is deliberately tolerant: a failure to read is logged and swallowed, never surfaced as an alarm error, so a diagnostics gap cannot masquerade as a scheduling problem.
140 lines
4.6 KiB
Dart
140 lines
4.6 KiB
Dart
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.dart';
|
|
import 'package:pluriwave/servicios/servicio_alarmas_android.dart';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
|
|
import '../helpers/fakes_alarmas.dart';
|
|
|
|
/// fix/alarmas-fallos-silenciosos, item 3: "verify the alarm is actually
|
|
/// registered, and say so if it is not". `android.programar` returning
|
|
/// without throwing is not proof enough by itself -- this is the check that
|
|
/// would have caught the reported case immediately (the native side can
|
|
/// silently fail to persist the registration even when the channel call
|
|
/// itself reports success).
|
|
void main() {
|
|
setUp(() {
|
|
SharedPreferences.setMockInitialValues({});
|
|
});
|
|
|
|
test('guardarAlarma detecta que el conteo nativo no refleja la alarma '
|
|
'guardada, aunque android.programar no haya lanzado', () async {
|
|
// Fixed at 0 regardless of what programar() does internally --
|
|
// simulates the native side accepting the channel call but never
|
|
// actually persisting the registration.
|
|
final android = FakePuertoAlarmasAndroid()..alarmasNativasPendientes = 0;
|
|
final estado = EstadoAlarmas(
|
|
servicio: ServicioAlarmas(reloj: () => DateTime(2026, 5, 25, 7)),
|
|
android: android,
|
|
iniciarAutomaticamente: false,
|
|
);
|
|
addTearDown(estado.dispose);
|
|
addTearDown(android.dispose);
|
|
|
|
await estado.guardarAlarma(
|
|
const AlarmaMusical(
|
|
id: 'silenciosa1',
|
|
nombre: 'Diaria',
|
|
hora: 7,
|
|
minuto: 30,
|
|
tipoProgramacion: TipoProgramacionAlarma.diaria,
|
|
diasSemana: [],
|
|
),
|
|
);
|
|
|
|
final excepcion = estado.ultimaExcepcionPara('silenciosa1');
|
|
expect(excepcion, isNotNull);
|
|
expect(excepcion!.tipo, ExcepcionAlarma.tipoFalloProgramacion);
|
|
expect(estado.error, isNotNull);
|
|
});
|
|
|
|
test('guardarAlarma en el camino feliz (conteo nativo coincide) no registra '
|
|
'fallo alguno', () async {
|
|
final android = FakePuertoAlarmasAndroid();
|
|
final estado = EstadoAlarmas(
|
|
servicio: ServicioAlarmas(reloj: () => DateTime(2026, 5, 25, 7)),
|
|
android: android,
|
|
iniciarAutomaticamente: false,
|
|
);
|
|
addTearDown(estado.dispose);
|
|
addTearDown(android.dispose);
|
|
|
|
await estado.guardarAlarma(
|
|
const AlarmaMusical(
|
|
id: 'sana1',
|
|
nombre: 'Diaria',
|
|
hora: 7,
|
|
minuto: 30,
|
|
tipoProgramacion: TipoProgramacionAlarma.diaria,
|
|
diasSemana: [],
|
|
),
|
|
);
|
|
|
|
expect(estado.ultimaExcepcionPara('sana1'), isNull);
|
|
expect(estado.error, isNull);
|
|
});
|
|
|
|
test(
|
|
'inicializar importa los fallos nativos (pre-aviso, servicio en primer '
|
|
'plano, reprogramacion tras arranque) como excepciones por alarma',
|
|
() async {
|
|
final android =
|
|
FakePuertoAlarmasAndroid()
|
|
..fallosProgramacionNativos.addAll([
|
|
FalloProgramacionNativo(
|
|
alarmaId: 'con-preaviso-roto',
|
|
tipo: ExcepcionAlarma.tipoFalloPreaviso,
|
|
ocurridoEn: DateTime(2026, 5, 25, 7),
|
|
),
|
|
FalloProgramacionNativo(
|
|
alarmaId: 'servicio-rechazado',
|
|
tipo: ExcepcionAlarma.tipoFalloServicioSonido,
|
|
ocurridoEn: DateTime(2026, 5, 25, 7),
|
|
),
|
|
]);
|
|
final servicio = ServicioAlarmas(
|
|
reloj: () => DateTime(2026, 5, 25, 7, 30),
|
|
);
|
|
await servicio.guardarAlarma(
|
|
AlarmaMusical(
|
|
id: 'con-preaviso-roto',
|
|
nombre: 'Diaria',
|
|
hora: 7,
|
|
minuto: 30,
|
|
tipoProgramacion: TipoProgramacionAlarma.diaria,
|
|
diasSemana: const [],
|
|
),
|
|
);
|
|
await servicio.guardarAlarma(
|
|
AlarmaMusical(
|
|
id: 'servicio-rechazado',
|
|
nombre: 'Diaria',
|
|
hora: 8,
|
|
minuto: 0,
|
|
tipoProgramacion: TipoProgramacionAlarma.diaria,
|
|
diasSemana: const [],
|
|
),
|
|
);
|
|
|
|
final estado = EstadoAlarmas(
|
|
servicio: servicio,
|
|
android: android,
|
|
iniciarAutomaticamente: false,
|
|
);
|
|
addTearDown(estado.dispose);
|
|
addTearDown(android.dispose);
|
|
|
|
await estado.inicializar();
|
|
|
|
final falloPreaviso = estado.ultimaExcepcionPara('con-preaviso-roto');
|
|
expect(falloPreaviso, isNotNull);
|
|
expect(falloPreaviso!.tipo, ExcepcionAlarma.tipoFalloPreaviso);
|
|
|
|
final falloServicio = estado.ultimaExcepcionPara('servicio-rechazado');
|
|
expect(falloServicio, isNotNull);
|
|
expect(falloServicio!.tipo, ExcepcionAlarma.tipoFalloServicioSonido);
|
|
},
|
|
);
|
|
}
|