import 'dart:convert'; import 'package:flutter_test/flutter_test.dart'; import 'package:pluriwave/modelos/alarma_musical.dart'; import 'package:pluriwave/servicios/servicio_alarmas.dart'; import 'package:shared_preferences/shared_preferences.dart'; const _claveAlarmas = 'alarmas_musicales_v1'; /// SharedPreferences spy: only the members ServicioAlarmas touches are /// implemented; everything else throws via noSuchMethod. Mirrors /// servicio_alarmas_cache_test.dart's _PrefsEspia, plus initial-value /// seeding so corruption scenarios can be set up directly. class _PrefsEspia implements SharedPreferences { _PrefsEspia({Map? inicial}) : _datos = {...?inicial}; final Map _datos; int escriturasString = 0; int lecturasString = 0; @override String? getString(String key) { lecturasString++; return _datos[key] as String?; } @override Future setString(String key, String value) async { escriturasString++; _datos[key] = value; return true; } @override dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); } Map _alarmaValida(String id, int hora) => { 'id': id, 'nombre': 'Alarma $id', 'hora': hora, 'minuto': 0, 'tipoProgramacion': 'diaria', 'diasSemana': [], }; void main() { group( 'ServicioAlarmas — lectura tolerante y guardia de degradacion ' '(persistence-resilience)', () { test( 'carga parcial (2 validas + 1 con id de tipo incorrecto): ' 'recalcularTodas no reescribe en cada tick (D3, no-thrash)', () async { final raw = jsonEncode({ 'alarmas': [ _alarmaValida('a1', 7), _alarmaValida('a2', 8), // id con tipo incorrecto -> AlarmaMusical.fromJson lanza al // hacer `json['id'] as String`; debe saltarse, no tumbar todo. {..._alarmaValida('a3', 9), 'id': 42}, ], 'vacaciones': [], 'excepciones': [], }); final prefs = _PrefsEspia(inicial: {_claveAlarmas: raw}); final ahora = DateTime(2026, 6, 11, 6, 0); final servicio = ServicioAlarmas(prefs: prefs, reloj: () => ahora); final config = await servicio.cargar(); expect(config.alarmas, hasLength(2)); expect(config.alarmas.map((a) => a.id).toSet(), {'a1', 'a2'}); final escriturasBase = prefs.escriturasString; await servicio.recalcularTodas(); await servicio.recalcularTodas(); expect( prefs.escriturasString - escriturasBase, lessThanOrEqualTo(1), reason: 'la cache normalizada evita que el dirty-guard se dispare ' 'en cada tick de 60s (D3)', ); }, ); test( 'carga totalmente corrupta: cargar() queda vacio, recalcularTodas ' 'no escribe, el raw original persiste intacto (D4)', () async { const raw = '{bad'; final prefs = _PrefsEspia(inicial: {_claveAlarmas: raw}); final servicio = ServicioAlarmas( prefs: prefs, reloj: () => DateTime(2026, 6, 11, 6, 0), ); final config = await servicio.cargar(); expect(config.alarmas, isEmpty); final escriturasBase = prefs.escriturasString; await servicio.recalcularTodas(); expect(prefs.escriturasString, escriturasBase); expect( prefs.getString(_claveAlarmas), raw, reason: 'el raw corrupto original debe preservarse intacto en ' 'disco mientras la lectura este degradada', ); }, ); test( 'guardarAlarma tras una lectura degradada escribe una vez y ' 'restaura la autoridad de escritura (D4)', () async { const raw = '{bad'; final prefs = _PrefsEspia(inicial: {_claveAlarmas: raw}); var ahora = DateTime(2026, 6, 11, 6, 0); final servicio = ServicioAlarmas(prefs: prefs, reloj: () => ahora); await servicio.cargar(); final escriturasBase = prefs.escriturasString; final nueva = servicio.crearAlarma( nombre: 'Nueva', hora: 7, minuto: 0, tipoProgramacion: TipoProgramacionAlarma.diaria, diasSemana: const [], ); await servicio.guardarAlarma(nueva); expect(prefs.escriturasString, escriturasBase + 1); expect(prefs.getString(_claveAlarmas), isNot(raw)); // Un cambio real de agenda despues del guardado explicito debe // volver a escribir con normalidad -- la bandera de degradacion // no puede quedar pegada para siempre. final escriturasTrasGuardar = prefs.escriturasString; ahora = DateTime(2026, 6, 12, 8, 0); await servicio.recalcularTodas(); expect(prefs.escriturasString, escriturasTrasGuardar + 1); }, ); test( 'entrada sin id se omite y jamas se le fabrica uno nuevo (D2)', () async { final raw = jsonEncode({ 'alarmas': [ { 'nombre': 'Sin id', 'hora': 6, 'minuto': 0, 'tipoProgramacion': 'diaria', 'diasSemana': [], }, _alarmaValida('a1', 7), _alarmaValida('a2', 8), ], 'vacaciones': [], 'excepciones': [], }); final prefs = _PrefsEspia(inicial: {_claveAlarmas: raw}); final servicio = ServicioAlarmas( prefs: prefs, reloj: () => DateTime(2026, 6, 11, 6, 0), ); final config = await servicio.cargar(); expect(config.alarmas, hasLength(2)); expect(config.alarmas.map((a) => a.id).toSet(), {'a1', 'a2'}); }, ); }, ); }