v2.53.1 — MED-3: Tests für Tombstone-Heuristik, SyncBericht, songAusServerMap + delete-deleted_at

## MED-3 (Review-Fix)
- test/sync_service_test.dart NEU (14 Tests): tombstoneAnwenden-Matrix
  (letzterSync null → true, deletedAt null → true, älter → false, neuer →
  true, Gleichstand → false, ungültiger Timestamp → true), SyncBericht
  (hatAenderungen, zusammenfassung kombiniert/leer, details), songAusServerMap
  (cloud_id/Titel/Künstler/Dauer/Größe/istKorrupt-Mapping, Defaults bei
  fehlenden Feldern, String-Robustheit) — deckt die MED-1-Heuristik + den
  MED-2-DB-Insert ab (123→140 Tests)
- test/cloud_service_test.dart +3: delete mit deletedAt sendet deleted_at im
  Body, delete ohne deletedAt sendet KEIN deleted_at-Feld, delete
  Netzwerkfehler → false
This commit is contained in:
Dustin
2026-08-05 11:24:51 +02:00
parent 2cd34d61dc
commit f14b658566
2 changed files with 189 additions and 0 deletions
+151
View File
@@ -0,0 +1,151 @@
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);
});
});
}