import 'package:flutter_test/flutter_test.dart'; import 'package:pluriwave/modelos/emisora.dart'; import 'package:pluriwave/modelos/grupo_favoritos.dart'; import 'package:pluriwave/modelos/preset_ecualizador.dart'; import 'package:pluriwave/servicios/servicio_export_import.dart'; void main() { const servicio = ServicioExportImport(); // Shared fixture data used across multiple tests. const gruposFavoritosFixture = [ GrupoFavoritos(id: 'g1', nombre: 'Rock', orden: 1), GrupoFavoritos( id: GrupoFavoritos.sinAsignarId, nombre: 'Sin asignar', orden: 0, protegido: true, ), ]; const favoritosFixture = [ Emisora( uuid: 'fav-1', nombre: 'Radio Uno', url: 'https://uno.example/stream', grupoFavoritosId: 'g1', bitrate: 192, ), ]; const emisorasCustomFixture = [ Emisora( uuid: 'custom-1', nombre: 'Mi Radio', url: 'https://mia.example/stream', ), ]; const alarmasFixture = { 'alarmas': [ {'id': 'a1', 'hora': 7, 'minuto': 30, 'activa': true, 'snoozeMinutos': 5}, ], 'vacaciones': [ {'desde': '2026-07-01', 'hasta': '2026-07-15'}, ], }; group('ServicioExportImport (S4-R4)', () { test('v2 round-trip: exportar then importar yields a deep-equal config ' '(S4-R4-A, S6-R2 test #4)', () { final config = servicio.construirExportacion( gruposFavoritos: gruposFavoritosFixture, favoritos: favoritosFixture, emisorasCustom: emisorasCustomFixture, presetPrincipal: PresetEcualizador.jazz, presetsPorEmisora: {'fav-1': PresetEcualizador.rock}, // Raw alarm block exactly as ServicioAlarmas persists it (alarms + // vacations + exceptions) — must pass through untouched. alarmas: alarmasFixture, emisoraPreferidaUuid: 'fav-1', ordenListas: 'calidad', timerSuenoPresetsSegundos: const [300, 600, 1800], ); final json = servicio.exportar(config); final importado = servicio.importar(json); expect(importado, isNotNull); expect(importado, equals(config)); expect(importado!['version'], 2); // Alarm raw-JSON passthrough survives the round-trip intact. expect(importado['alarmas'], equals(config['alarmas'])); // The protected "sin asignar" group is never exported. final grupos = importado['gruposFavoritos'] as List; expect(grupos, hasLength(1)); expect((grupos.single as Map)['id'], 'g1'); }); test('malformed JSON returns null without throwing (S4-R4)', () { expect(servicio.importar('{not valid json'), isNull); expect(servicio.importar(''), isNull); // Valid JSON but not an object — also rejected gracefully. expect(servicio.importar('[1, 2, 3]'), isNull); }); }); group('ServicioExportImport v3 — multi-device EQ (Phase 6)', () { test( '6.1-A: v3 export includes schemaVersion 3 and device/matrix preset keys', () { final presetDispositivo = PresetEcualizador.rock; final presetMatriz = PresetEcualizador.jazz; final config = servicio.construirExportacion( gruposFavoritos: gruposFavoritosFixture, favoritos: favoritosFixture, emisorasCustom: emisorasCustomFixture, presetPrincipal: PresetEcualizador.flat, presetsPorEmisora: {}, alarmas: null, emisoraPreferidaUuid: null, ordenListas: 'calidad', timerSuenoPresetsSegundos: const [300], presetsPorDispositivo: { 'bt_a2dp:AA:BB:CC:DD:EE:FF': presetDispositivo, }, presetsMatriz: {'fav-1:bt_a2dp:AA:BB:CC:DD:EE:FF': presetMatriz}, eqMultiDeviceEnabled: true, ); // v3 schema version. expect(config['version'], 3); // Device preset map present and correct. expect(config['presetsPorDispositivo'], isA()); final dispositivoMap = config['presetsPorDispositivo'] as Map; expect(dispositivoMap, contains('bt_a2dp:AA:BB:CC:DD:EE:FF')); // Matrix preset map present and correct. expect(config['presetsMatriz'], isA()); final matrizMap = config['presetsMatriz'] as Map; expect(matrizMap, contains('fav-1:bt_a2dp:AA:BB:CC:DD:EE:FF')); // Toggle exported. expect(config['eqMultiDeviceEnabled'], isTrue); }, ); test( '6.1-B: v3 round-trip preserves device and matrix presets exactly', () { final dispositivoPreset = PresetEcualizador.rock; final matrizPreset = PresetEcualizador.jazz; final config = servicio.construirExportacion( gruposFavoritos: gruposFavoritosFixture, favoritos: favoritosFixture, emisorasCustom: emisorasCustomFixture, presetPrincipal: PresetEcualizador.flat, presetsPorEmisora: {'fav-1': PresetEcualizador.pop}, alarmas: alarmasFixture, emisoraPreferidaUuid: 'fav-1', ordenListas: 'calidad', timerSuenoPresetsSegundos: const [300, 1800], presetsPorDispositivo: { 'bt_a2dp:AA:BB:CC:DD:EE:FF': dispositivoPreset, 'wired_headset': PresetEcualizador.flat, }, presetsMatriz: {'fav-1:bt_a2dp:AA:BB:CC:DD:EE:FF': matrizPreset}, eqMultiDeviceEnabled: true, ); final json = servicio.exportar(config); final importado = servicio.importar(json); expect(importado, isNotNull); expect(importado!['version'], 3); // Device presets survive the round-trip. final deviceMap = importado['presetsPorDispositivo'] as Map; expect( deviceMap['bt_a2dp:AA:BB:CC:DD:EE:FF'], equals(dispositivoPreset.toJson()), ); expect( deviceMap['wired_headset'], equals(PresetEcualizador.flat.toJson()), ); // Matrix presets survive the round-trip. final matrizMap = importado['presetsMatriz'] as Map; expect( matrizMap['fav-1:bt_a2dp:AA:BB:CC:DD:EE:FF'], equals(matrizPreset.toJson()), ); // Toggle survives. expect(importado['eqMultiDeviceEnabled'], isTrue); }, ); test( '6.1-C: v2 file (no device fields) imports cleanly — missing keys default to empty maps', () { // Simulate a v2 export file (no presetsPorDispositivo / presetsMatriz). final v2Config = servicio.construirExportacion( gruposFavoritos: gruposFavoritosFixture, favoritos: favoritosFixture, emisorasCustom: emisorasCustomFixture, presetPrincipal: PresetEcualizador.jazz, presetsPorEmisora: {'fav-1': PresetEcualizador.rock}, alarmas: null, emisoraPreferidaUuid: null, ordenListas: 'calidad', timerSuenoPresetsSegundos: const [300], // No device fields — emulates a v2 export from the old app. ); // Verify the export has no device keys (a v2 file would not). expect(v2Config.containsKey('presetsPorDispositivo'), isFalse); expect(v2Config.containsKey('presetsMatriz'), isFalse); expect(v2Config.containsKey('eqMultiDeviceEnabled'), isFalse); // Import it — parsing should succeed. final json = servicio.exportar(v2Config); final importado = servicio.importar(json); expect(importado, isNotNull); // Missing device keys should gracefully resolve to null (caller uses ?? {}). expect(importado!['presetsPorDispositivo'], isNull); expect(importado['presetsMatriz'], isNull); expect(importado['eqMultiDeviceEnabled'], isNull); }, ); test( '6.1-D: unknown schemaVersion (v4-future) does not crash importar()', () { // Simulate a future v4 export (unknown version, extra fields). final futurista = { 'version': 99, 'presetsPorDispositivo': {}, 'presetsMatriz': {}, 'unknownFutureField': 'some-value', }; final json = servicio.exportar(futurista); // importar() should parse without throwing — version checking is the // caller's responsibility (EstadoRadio.importarConfig). expect(() => servicio.importar(json), returnsNormally); final importado = servicio.importar(json); expect(importado, isNotNull); expect(importado!['version'], 99); }, ); }); }