Favoriten additiv abgleichen statt ersetzen; Upload-Timeout ist Einzelfehler
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
@@ -163,7 +164,7 @@ void main() {
|
||||
expect(await db.watchSongs().first, isEmpty);
|
||||
});
|
||||
|
||||
test('Favoriten werden mit ihren Server-IDs gemeldet', () async {
|
||||
test('lokale Favoriten werden additiv gepusht, nie als Voll-Ersatz', () async {
|
||||
await db.upsertSongs([
|
||||
SongsCompanion.insert(
|
||||
id: 'lokal-1',
|
||||
@@ -176,7 +177,54 @@ void main() {
|
||||
await db.setCloudId('lokal-1', 'c5');
|
||||
await db.setFavorite('lokal-1', true);
|
||||
|
||||
List<String>? gemeldet;
|
||||
final gepusht = <Map<String, dynamic>>[];
|
||||
var vollErsatz = 0;
|
||||
final sync = await baue((anfrage) async {
|
||||
final pfad = anfrage.url.path;
|
||||
if (pfad.endsWith('/list')) {
|
||||
return http.Response(
|
||||
jsonEncode({
|
||||
'songs': [
|
||||
{'id': 'c5', 'title': 'Lieblingslied'}
|
||||
]
|
||||
}),
|
||||
200,
|
||||
);
|
||||
}
|
||||
if (pfad.endsWith('/favorites/toggle')) {
|
||||
gepusht.add(jsonDecode(anfrage.body) as Map<String, dynamic>);
|
||||
return http.Response(jsonEncode({'status': 'ok'}), 200);
|
||||
}
|
||||
if (pfad.endsWith('/favorites')) {
|
||||
if (anfrage.method == 'POST') vollErsatz++;
|
||||
return http.Response(jsonEncode({'favorites': []}), 200);
|
||||
}
|
||||
return http.Response(jsonEncode({'status': 'ok'}), 200);
|
||||
});
|
||||
|
||||
await sync.synchronisiere();
|
||||
|
||||
expect(gepusht, [
|
||||
{'song_id': 'c5', 'set': true}
|
||||
]);
|
||||
// Der Datenverlust-Bug ist strukturell weg: es gibt keinen Aufruf mehr,
|
||||
// der den Server-Stand ersetzen könnte.
|
||||
expect(vollErsatz, 0);
|
||||
expect(sync.fehler, isNull);
|
||||
});
|
||||
|
||||
test('ein Server-Favorit wird lokal nachgezogen', () async {
|
||||
await db.upsertSongs([
|
||||
SongsCompanion.insert(
|
||||
id: 'lokal-1',
|
||||
path: '${tempDir.path}/fav.mp3',
|
||||
title: 'Lieblingslied',
|
||||
dateAddedMs: 0,
|
||||
updatedAtMs: 0,
|
||||
),
|
||||
]);
|
||||
await db.setCloudId('lokal-1', 'c5');
|
||||
|
||||
final sync = await baue((anfrage) async {
|
||||
final pfad = anfrage.url.path;
|
||||
if (pfad.endsWith('/list')) {
|
||||
@@ -190,15 +238,146 @@ void main() {
|
||||
);
|
||||
}
|
||||
if (pfad.endsWith('/favorites')) {
|
||||
final d = jsonDecode(anfrage.body) as Map<String, dynamic>;
|
||||
gemeldet = (d['song_ids'] as List).cast<String>();
|
||||
return http.Response(
|
||||
jsonEncode({
|
||||
'favorites': [
|
||||
{'id': 'c5'}
|
||||
]
|
||||
}),
|
||||
200,
|
||||
);
|
||||
}
|
||||
return http.Response(jsonEncode({'status': 'ok'}), 200);
|
||||
});
|
||||
|
||||
await sync.synchronisiere();
|
||||
|
||||
expect(gemeldet, ['c5']);
|
||||
expect(await db.favoriteSongIds(), ['lokal-1']);
|
||||
});
|
||||
|
||||
test('200 mit Fehlerkörper überspringt die Favoriten-Phase ohne Push',
|
||||
() async {
|
||||
await db.upsertSongs([
|
||||
SongsCompanion.insert(
|
||||
id: 'lokal-1',
|
||||
path: '${tempDir.path}/fav.mp3',
|
||||
title: 'Lieblingslied',
|
||||
dateAddedMs: 0,
|
||||
updatedAtMs: 0,
|
||||
),
|
||||
]);
|
||||
await db.setCloudId('lokal-1', 'c5');
|
||||
await db.setFavorite('lokal-1', true);
|
||||
|
||||
var pushes = 0;
|
||||
final sync = await baue((anfrage) async {
|
||||
final pfad = anfrage.url.path;
|
||||
if (pfad.endsWith('/list')) {
|
||||
return http.Response(
|
||||
jsonEncode({
|
||||
'songs': [
|
||||
{'id': 'c5', 'title': 'Lieblingslied'}
|
||||
]
|
||||
}),
|
||||
200,
|
||||
);
|
||||
}
|
||||
if (pfad.endsWith('/favorites/toggle')) {
|
||||
pushes++;
|
||||
return http.Response(jsonEncode({'status': 'ok'}), 200);
|
||||
}
|
||||
if (pfad.endsWith('/favorites')) {
|
||||
return http.Response(
|
||||
jsonEncode({'status': 'error', 'error': 'kaputt'}), 200);
|
||||
}
|
||||
return http.Response(jsonEncode({'status': 'ok'}), 200);
|
||||
});
|
||||
|
||||
await sync.synchronisiere();
|
||||
|
||||
expect(pushes, 0);
|
||||
// Nur diese Phase fällt aus, der Lauf geht weiter.
|
||||
expect(sync.fehler, isNull);
|
||||
});
|
||||
|
||||
test('200 mit leerer Favoritenliste läuft normal durch', () async {
|
||||
// Gegenprobe zum Test darüber: eine echte Leerantwort darf NICHT als
|
||||
// Fehler gelten, sonst wäre die Sicherheitsregel trivial erfüllt.
|
||||
await db.upsertSongs([
|
||||
SongsCompanion.insert(
|
||||
id: 'lokal-1',
|
||||
path: '${tempDir.path}/fav.mp3',
|
||||
title: 'Lieblingslied',
|
||||
dateAddedMs: 0,
|
||||
updatedAtMs: 0,
|
||||
),
|
||||
]);
|
||||
await db.setCloudId('lokal-1', 'c5');
|
||||
await db.setFavorite('lokal-1', true);
|
||||
|
||||
var pushes = 0;
|
||||
final sync = await baue((anfrage) async {
|
||||
final pfad = anfrage.url.path;
|
||||
if (pfad.endsWith('/list')) {
|
||||
return http.Response(
|
||||
jsonEncode({
|
||||
'songs': [
|
||||
{'id': 'c5', 'title': 'Lieblingslied'}
|
||||
]
|
||||
}),
|
||||
200,
|
||||
);
|
||||
}
|
||||
if (pfad.endsWith('/favorites/toggle')) {
|
||||
pushes++;
|
||||
return http.Response(jsonEncode({'status': 'ok'}), 200);
|
||||
}
|
||||
if (pfad.endsWith('/favorites')) {
|
||||
return http.Response(jsonEncode({'favorites': []}), 200);
|
||||
}
|
||||
return http.Response(jsonEncode({'status': 'ok'}), 200);
|
||||
});
|
||||
|
||||
await sync.synchronisiere();
|
||||
|
||||
expect(pushes, 1);
|
||||
});
|
||||
|
||||
test('eine Zeitüberschreitung beim Hochladen reißt den Lauf nicht ab',
|
||||
() async {
|
||||
final datei = File('${tempDir.path}/haengt.mp3');
|
||||
await datei.writeAsBytes([1]);
|
||||
await db.upsertSongs([
|
||||
SongsCompanion.insert(
|
||||
id: 'lokal-1',
|
||||
path: datei.path,
|
||||
title: 'Hängt',
|
||||
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')) {
|
||||
// Der 120-s-Timeout in melo_cloud_service wirft TimeoutException,
|
||||
// nicht CloudException — ohne eigenen Zweig riss ein einziger
|
||||
// hängender Upload den ganzen Lauf ab.
|
||||
throw TimeoutException('zu lang');
|
||||
}
|
||||
if (pfad.endsWith('/favorites')) {
|
||||
return http.Response(jsonEncode({'favorites': []}), 200);
|
||||
}
|
||||
return http.Response(jsonEncode({'status': 'ok'}), 200);
|
||||
});
|
||||
|
||||
await sync.synchronisiere();
|
||||
|
||||
expect(sync.fehler, isNull);
|
||||
expect((await db.songById('lokal-1'))!.cloudId, isNull);
|
||||
});
|
||||
|
||||
test('eine Löschwelle wird nicht zum Server durchgereicht', () async {
|
||||
|
||||
Reference in New Issue
Block a user