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.
142 lines
4.0 KiB
Dart
142 lines
4.0 KiB
Dart
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);
|
|
},
|
|
);
|
|
});
|
|
}
|