Files
pluriwave/test/servicios/persistencia_tolerante_test.dart
T
FreeTLab 65c1ac2085
Build & Deploy PluriWave / Análisis de código (push) Successful in 38s
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 1m38s
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.
2026-07-11 12:27:39 +02:00

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);
},
);
});
}