fix(alarm): stop corrupt entries and unreadable payloads from wiping saved alarms
A single malformed alarm entry (bad/missing id, wrong type) used to discard the ENTIRE persisted list on next load, and a fully unparseable payload let the periodic recalculation silently overwrite it with an empty one -- both destroyed valid alarms with no user action. Adds a shared per-entry tolerant-parse helper (persistencia_tolerante.dart) that skips and logs only the bad entry; ServicioAlarmas now normalizes its cached raw after a partial load (no dirty-guard thrash) and sets a degraded-read flag after a total decode failure that suppresses automatic writes until a good read or an explicit user mutation restores authority.
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:pluriwave/servicios/persistencia_tolerante.dart';
|
||||
|
||||
void main() {
|
||||
late DebugPrintCallback debugPrintOriginal;
|
||||
late List<String> logs;
|
||||
|
||||
setUp(() {
|
||||
debugPrintOriginal = debugPrint;
|
||||
logs = [];
|
||||
debugPrint = (String? message, {int? wrapWidth}) {
|
||||
if (message != null) logs.add(message);
|
||||
};
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
debugPrint = debugPrintOriginal;
|
||||
});
|
||||
|
||||
group('parseListaTolerante', () {
|
||||
test(
|
||||
'salta 1 entrada invalida entre 3 validas, retorna las 3 sobrevivientes y loguea el salto',
|
||||
() {
|
||||
final List<dynamic> datos = [
|
||||
{'valor': 1},
|
||||
{'valor': 2},
|
||||
'entrada-invalida', // not a Map -> must be skipped, not thrown
|
||||
{'valor': 3},
|
||||
];
|
||||
|
||||
final resultado = parseListaTolerante<int>(
|
||||
datos,
|
||||
(entrada) => entrada['valor'] as int,
|
||||
subsistema: 'prueba',
|
||||
coleccion: 'items',
|
||||
);
|
||||
|
||||
expect(resultado.validas, [1, 2, 3]);
|
||||
expect(resultado.saltadas, 1);
|
||||
expect(
|
||||
logs.any((l) => l.startsWith('[PluriWave][persistencia]')),
|
||||
isTrue,
|
||||
reason: 'debe loguear el salto con el prefijo de diagnostico',
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'entrada valida cuyo parser lanza tambien se salta y se loguea (no solo tipos incorrectos)',
|
||||
() {
|
||||
final List<dynamic> datos = [
|
||||
{'valor': 1},
|
||||
{'otraClave': 'sin valor'}, // Map valido, pero el parser fallara
|
||||
];
|
||||
|
||||
final resultado = parseListaTolerante<int>(
|
||||
datos,
|
||||
(entrada) => entrada['valor'] as int,
|
||||
subsistema: 'prueba',
|
||||
coleccion: 'items',
|
||||
);
|
||||
|
||||
expect(resultado.validas, [1]);
|
||||
expect(resultado.saltadas, 1);
|
||||
},
|
||||
);
|
||||
|
||||
test('datos null retorna sobrevivientes vacios sin loguear', () {
|
||||
final resultado = parseListaTolerante<int>(
|
||||
null,
|
||||
(entrada) => entrada['valor'] as int,
|
||||
subsistema: 'prueba',
|
||||
coleccion: 'items',
|
||||
);
|
||||
|
||||
expect(resultado.validas, isEmpty);
|
||||
expect(resultado.saltadas, 0);
|
||||
expect(logs, isEmpty);
|
||||
});
|
||||
});
|
||||
|
||||
group('parseMapaTolerante', () {
|
||||
test(
|
||||
'salta 1 VALOR invalido entre 3 entradas, preserva las claves originales',
|
||||
() {
|
||||
final Map<String, dynamic> datos = {
|
||||
'k1': {'valor': 10},
|
||||
'k2': 'valor-no-parseable',
|
||||
'k3': {'valor': 30},
|
||||
'k4': {'valor': 40},
|
||||
};
|
||||
|
||||
final resultado = parseMapaTolerante<int>(
|
||||
datos,
|
||||
(valor) => (valor as Map)['valor'] as int,
|
||||
subsistema: 'prueba',
|
||||
coleccion: 'mapa',
|
||||
);
|
||||
|
||||
expect(resultado.validas, {'k1': 10, 'k3': 30, 'k4': 40});
|
||||
expect(resultado.saltadas, 1);
|
||||
expect(
|
||||
resultado.validas.keys,
|
||||
containsAll(['k1', 'k3', 'k4']),
|
||||
reason: 'las claves originales de las entradas sobrevivientes no deben alterarse',
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
group('degradado total', () {
|
||||
test(
|
||||
'ambos helpers en entrada 100% invalida retornan sobrevivientes vacios sin lanzar excepcion, y loguean',
|
||||
() {
|
||||
final List<dynamic> datosLista = ['x', 'y', 'z'];
|
||||
final resultadoLista = parseListaTolerante<int>(
|
||||
datosLista,
|
||||
(entrada) => entrada['valor'] as int,
|
||||
subsistema: 'prueba',
|
||||
coleccion: 'items',
|
||||
);
|
||||
expect(resultadoLista.validas, isEmpty);
|
||||
expect(resultadoLista.saltadas, 3);
|
||||
expect(logs, isNotEmpty);
|
||||
|
||||
logs.clear();
|
||||
final Map<String, dynamic> datosMapa = {'k1': 'x', 'k2': 'y'};
|
||||
final resultadoMapa = parseMapaTolerante<int>(
|
||||
datosMapa,
|
||||
(valor) => (valor as Map)['valor'] as int,
|
||||
subsistema: 'prueba',
|
||||
coleccion: 'mapa',
|
||||
);
|
||||
expect(resultadoMapa.validas, isEmpty);
|
||||
expect(resultadoMapa.saltadas, 2);
|
||||
expect(logs, isNotEmpty);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user