feat(alarmas): verify native registration after a successful save

android.programar() returning without throwing was treated as proof
the OS registered the alarm -- this is exactly the gap the reported
case fell through. guardarAlarma now cross-checks a fresh native
pending-alarm count against how many alarms Dart believes are
active-with-a-next-run right after a successful schedule call, and
records a failure for the just-saved alarm when the native count
falls short.

FakePuertoAlarmasAndroid.alarmasNativasPendientes now defaults to a
count derived from programar()/cancelar() calls (mirroring the real
native scheduler's own registry) instead of a frozen 0, while any
test that explicitly assigns the field keeps getting exactly that
value regardless of what programar/cancelar do afterward -- verified
against the full suite, no regressions.
This commit is contained in:
2026-07-31 21:26:41 +02:00
parent c107c0e18a
commit 7722f204ca
3 changed files with 136 additions and 4 deletions
@@ -0,0 +1,76 @@
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: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);
});
}