import 'package:flutter_test/flutter_test.dart'; import 'package:melo_app/services/sync_service.dart'; /// SyncService-Tests (Sprint E, MED-3-Review-Fix). /// /// Abgedeckt: /// - `tombstoneAnwenden`-Matrix (null-letzterSync / null-deletedAt / /// älter → false / neuer → true / Gleichstand / ungültiger Timestamp) /// - `SyncBericht` (hatAenderungen, zusammenfassung, details) /// - `songAusServerMap` (MED-2: Server-Listeneintrag → lokaler Song-DB- /// Eintrag mit cloud_id + Titeldaten) /// /// Reine, statische Funktionen — kein DB-/Plattform-Zugriff nötig. void main() { TestWidgetsFlutterBinding.ensureInitialized(); final letzterSync = DateTime(2026, 8, 4, 12, 0, 0); group('tombstoneAnwenden (Tombstone-Race-Heuristik, MED-1)', () { test('letzterSync null (Erst-Sync) → anwenden', () { expect( SyncService.tombstoneAnwenden( letzterSync: null, deletedAt: '2026-08-05T10:00:00'), isTrue, ); }); test('deletedAt null (Alt-Tombstone ohne Zeitstempel) → anwenden', () { expect( SyncService.tombstoneAnwenden( letzterSync: letzterSync, deletedAt: null), isTrue, ); }); test('deletedAt älter als letzterSync → NICHT anwenden', () { expect( SyncService.tombstoneAnwenden( letzterSync: letzterSync, deletedAt: '2026-08-03T10:00:00'), isFalse, ); }); test('deletedAt neuer als letzterSync → anwenden', () { expect( SyncService.tombstoneAnwenden( letzterSync: letzterSync, deletedAt: '2026-08-05T10:00:00'), isTrue, ); }); test('deletedAt == letzterSync (Gleichstand) → NICHT anwenden', () { expect( SyncService.tombstoneAnwenden( letzterSync: letzterSync, deletedAt: '2026-08-04T12:00:00'), isFalse, ); }); test('ungültiger deletedAt-String → anwenden (d == null → sicher)', () { expect( SyncService.tombstoneAnwenden( letzterSync: letzterSync, deletedAt: 'kein-timestamp'), isTrue, ); }); }); group('SyncBericht (Konflikt-Report, Sprint E)', () { test('hatAenderungen: false bei leerem Bericht', () { const bericht = SyncBericht( neueSongs: 0, geloeschteSongs: 0, favoritenGeaendert: 0); expect(bericht.hatAenderungen, isFalse); }); test('hatAenderungen: true sobald eine Kategorie > 0', () { const bericht = SyncBericht( neueSongs: 1, geloeschteSongs: 0, favoritenGeaendert: 0); expect(bericht.hatAenderungen, isTrue); }); test('zusammenfassung kombiniert alle Kategorien', () { const bericht = SyncBericht( neueSongs: 8, geloeschteSongs: 3, favoritenGeaendert: 5); expect(bericht.zusammenfassung, '+8 neue Songs · −3 gelöscht · ⭐5 Favoriten geändert'); }); test('zusammenfassung leer bei 0 Änderungen', () { const bericht = SyncBericht( neueSongs: 0, geloeschteSongs: 0, favoritenGeaendert: 0); expect(bericht.zusammenfassung, isEmpty); }); test('details werden durchgereicht', () { const bericht = SyncBericht( neueSongs: 1, geloeschteSongs: 1, favoritenGeaendert: 0, details: ['+ Neuer Song: A', '− Gelöscht: B'], ); expect(bericht.details, hasLength(2)); expect(bericht.details.first, '+ Neuer Song: A'); }); }); group('songAusServerMap (MED-2: Server-Listeneintrag → lokaler Song)', () { test('mappt cloud_id + Titeldaten + Flags', () { final song = SyncService.songAusServerMap({ 'id': 'srv-42', 'title': 'Testtitel', 'artist': ' Künstler ', 'duration': 217, 'size': 5242880, 'istKorrupt': true, }, '/data/music/Testtitel'); expect(song.cloudId, 'srv-42'); expect(song.titel, 'Testtitel'); expect(song.kuenstler, 'Künstler'); // getrimmt expect(song.dauerSekunden, 217); expect(song.groesseBytes, 5242880); expect(song.dateiPfad, '/data/music/Testtitel'); expect(song.downloadQuelle, 'cloud'); expect(song.istHeruntergeladen, isTrue); expect(song.istKorrupt, isTrue); expect(song.ytUrl, isNull); // v2.52-F2: Server bleibt YT-Link-Quelle }); test('fehlende Felder → Defaults (kein Crash)', () { final song = SyncService.songAusServerMap({'id': 'srv-1'}, '/x'); expect(song.titel, 'unknown'); expect(song.kuenstler, ''); expect(song.dauerSekunden, 0); expect(song.groesseBytes, 0); expect(song.istKorrupt, isFalse); expect(song.cloudId, 'srv-1'); }); test('duration/size als String → robust geparst', () { final song = SyncService.songAusServerMap({ 'id': 'srv-2', 'title': 'A', 'duration': '180', 'size': '1024', }, '/x'); expect(song.dauerSekunden, 180); expect(song.groesseBytes, 1024); }); }); }