Erfolgs-Flag, Zeitstempel-Snapshot und Zaehler fuer den Sync-Bericht
This commit is contained in:
@@ -634,4 +634,193 @@ void main() {
|
||||
expect(sync.fehler, contains('Abgleich'));
|
||||
});
|
||||
});
|
||||
|
||||
group('Sync-Bericht', () {
|
||||
test('beim allerersten Lauf gibt es keinen Bericht', () async {
|
||||
final sync = await baue((anfrage) async {
|
||||
if (anfrage.url.path.endsWith('/list')) {
|
||||
return http.Response(jsonEncode({'songs': []}), 200);
|
||||
}
|
||||
return http.Response(jsonEncode({'status': 'ok'}), 200);
|
||||
});
|
||||
|
||||
await sync.synchronisiere();
|
||||
|
||||
// letzterErfolg war null: „Willkommen zurück!" auf einem frisch
|
||||
// eingerichteten Gerät wäre Unsinn.
|
||||
expect(sync.bericht, isNull);
|
||||
});
|
||||
|
||||
test('nach mehr als 24 Stunden kommt der Bericht mit Zählern', () async {
|
||||
final vorgestern = DateTime.now().subtract(const Duration(days: 2));
|
||||
SharedPreferences.setMockInitialValues({
|
||||
'cloud_sync_letzter_erfolg': vorgestern.millisecondsSinceEpoch,
|
||||
});
|
||||
|
||||
final sync = await baue((anfrage) async {
|
||||
final pfad = anfrage.url.path;
|
||||
if (pfad.endsWith('/list')) {
|
||||
return http.Response(
|
||||
jsonEncode({
|
||||
'songs': [
|
||||
{'id': 'c1', 'title': 'Neu', 'artist': 'X', 'duration': 100}
|
||||
]
|
||||
}),
|
||||
200,
|
||||
);
|
||||
}
|
||||
if (pfad.contains('/download/')) {
|
||||
return http.Response.bytes([1], 200,
|
||||
headers: {'content-type': 'audio/mpeg'});
|
||||
}
|
||||
if (pfad.endsWith('/favorites')) {
|
||||
return http.Response(jsonEncode({'favorites': []}), 200);
|
||||
}
|
||||
return http.Response(jsonEncode({'status': 'ok'}), 200);
|
||||
});
|
||||
await sync.laden();
|
||||
|
||||
await sync.synchronisiere();
|
||||
|
||||
expect(sync.bericht, isNotNull);
|
||||
expect(sync.bericht!.neueSongs, 1);
|
||||
sync.berichtGesehen();
|
||||
expect(sync.bericht, isNull);
|
||||
});
|
||||
|
||||
test('eine ausgefallene Phase verschiebt den Erfolgs-Zeitstempel nicht',
|
||||
() async {
|
||||
SharedPreferences.setMockInitialValues({});
|
||||
final sync = await baue((anfrage) async {
|
||||
final pfad = anfrage.url.path;
|
||||
if (pfad.endsWith('/list')) {
|
||||
return http.Response(jsonEncode({'songs': []}), 200);
|
||||
}
|
||||
if (pfad.endsWith('/favorites')) {
|
||||
// Fehler im 200er-Körper: die Favoriten-Phase fällt aus.
|
||||
return http.Response(
|
||||
jsonEncode({'status': 'error', 'error': 'kaputt'}), 200);
|
||||
}
|
||||
return http.Response(jsonEncode({'status': 'ok'}), 200);
|
||||
});
|
||||
|
||||
await sync.synchronisiere();
|
||||
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
expect(prefs.getInt('cloud_sync_letzter_erfolg'), isNull);
|
||||
// Die Drossel läuft trotzdem weiter — sonst rennt der Sync bei jedem
|
||||
// Tab-Wechsel neu los.
|
||||
expect(sync.letzterLauf, isNotNull);
|
||||
});
|
||||
|
||||
test('eine gescheiterte Löschmeldung verschiebt den Erfolgs-Zeitstempel '
|
||||
'nicht', () async {
|
||||
SharedPreferences.setMockInitialValues({});
|
||||
await db.upsertSongs([
|
||||
SongsCompanion.insert(
|
||||
id: 'lokal-1',
|
||||
path: '${tempDir.path}/weg.mp3',
|
||||
title: 'Weg',
|
||||
dateAddedMs: 0,
|
||||
updatedAtMs: 0,
|
||||
deleted: const Value(true),
|
||||
),
|
||||
]);
|
||||
await db.setCloudId('lokal-1', 'c5');
|
||||
|
||||
final sync = await baue((anfrage) async {
|
||||
final pfad = anfrage.url.path;
|
||||
if (pfad.endsWith('/list')) {
|
||||
return http.Response(
|
||||
jsonEncode({
|
||||
'songs': [
|
||||
{'id': 'c5', 'title': 'Weg'}
|
||||
]
|
||||
}),
|
||||
200,
|
||||
);
|
||||
}
|
||||
if (pfad.endsWith('/delete')) {
|
||||
// _meldeLoeschungen schluckt die CloudException — ohne Rückgabe
|
||||
// bis zum Flag hätte der Lauf trotzdem als erfolgreich gegolten.
|
||||
return http.Response(jsonEncode({'error': 'kaputt'}), 500);
|
||||
}
|
||||
if (pfad.endsWith('/favorites')) {
|
||||
return http.Response(jsonEncode({'favorites': []}), 200);
|
||||
}
|
||||
return http.Response(jsonEncode({'status': 'ok'}), 200);
|
||||
});
|
||||
|
||||
await sync.synchronisiere();
|
||||
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
expect(prefs.getInt('cloud_sync_letzter_erfolg'), isNull);
|
||||
expect(sync.letzterLauf, isNotNull);
|
||||
});
|
||||
|
||||
test('ein gescheiterter Upload verschiebt den Erfolgs-Zeitstempel nicht',
|
||||
() async {
|
||||
SharedPreferences.setMockInitialValues({});
|
||||
final datei = File('${tempDir.path}/zu-gross.mp3');
|
||||
await datei.writeAsBytes([1]);
|
||||
await db.upsertSongs([
|
||||
SongsCompanion.insert(
|
||||
id: 'lokal-1',
|
||||
path: datei.path,
|
||||
title: 'Zu groß',
|
||||
dateAddedMs: 0,
|
||||
updatedAtMs: 0,
|
||||
),
|
||||
]);
|
||||
|
||||
final sync = await baue((anfrage) async {
|
||||
final pfad = anfrage.url.path;
|
||||
if (pfad.endsWith('/list')) {
|
||||
return http.Response(jsonEncode({'songs': []}), 200);
|
||||
}
|
||||
if (pfad.endsWith('/upload')) {
|
||||
return http.Response(
|
||||
jsonEncode({'error': 'Datei zu groß (max 50 MB)'}), 200);
|
||||
}
|
||||
if (pfad.endsWith('/favorites')) {
|
||||
return http.Response(jsonEncode({'favorites': []}), 200);
|
||||
}
|
||||
return http.Response(jsonEncode({'status': 'ok'}), 200);
|
||||
});
|
||||
|
||||
await sync.synchronisiere();
|
||||
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
expect(prefs.getInt('cloud_sync_letzter_erfolg'), isNull);
|
||||
});
|
||||
|
||||
test('der Zeitstempel ist der Stand VOR dem Listen', () async {
|
||||
final sync = await baue((anfrage) async {
|
||||
if (anfrage.url.path.endsWith('/list')) {
|
||||
// Während des Laufs vergeht Zeit — der Zeitstempel darf nicht
|
||||
// danach genommen werden, sonst fallen zwischenzeitliche
|
||||
// Änderungen durchs Raster (Tombstone-Race, v2-Lektion).
|
||||
await Future<void>.delayed(const Duration(milliseconds: 50));
|
||||
return http.Response(jsonEncode({'songs': []}), 200);
|
||||
}
|
||||
return http.Response(jsonEncode({'status': 'ok'}), 200);
|
||||
});
|
||||
|
||||
final vorher = DateTime.now();
|
||||
await sync.synchronisiere();
|
||||
final nachher = DateTime.now();
|
||||
|
||||
expect(sync.letzterLauf!.isBefore(nachher), isTrue);
|
||||
expect(
|
||||
sync.letzterLauf!
|
||||
.isAfter(vorher.subtract(const Duration(milliseconds: 1))),
|
||||
isTrue,
|
||||
);
|
||||
expect(
|
||||
nachher.difference(sync.letzterLauf!) >=
|
||||
const Duration(milliseconds: 50),
|
||||
isTrue,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user