v2.52 — Favoriten-Sync-Fix: bidirektionaler Merge (lokal↔Server), Toggle-Push sofort, cloud_id-Verdrahtung
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
import 'dart:convert';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:http/testing.dart';
|
||||
import 'package:melo_app/services/cloud_service.dart';
|
||||
import 'package:melo_app/services/favoriten_sync.dart';
|
||||
|
||||
/// Tests für den bidirektionalen Favoriten-Sync (Sprint D v2.52).
|
||||
///
|
||||
/// Root-Cause-Regression: `_syncAlles` spiegelte Server-Favoriten nur zurück
|
||||
/// (Server → Server) — lokale ⭐-Toggles gingen verloren. Der Fix merge-t
|
||||
/// lokal ∪ Server (favoritenMerge) und pusht Toggles deterministisch via
|
||||
/// `set` (kein Doppel-Toggle-Risiko).
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
http.Response jsonOk(Map<String, dynamic> body) =>
|
||||
http.Response(jsonEncode(body), 200,
|
||||
headers: {'content-type': 'application/json'});
|
||||
|
||||
group('favoritenMerge (Vereinigung lokal ∪ Server)', () {
|
||||
test('lokale Favoriten werden dem Server-Push hinzugefügt', () {
|
||||
final merged = favoritenMerge(
|
||||
lokal: {'cloud-1', 'cloud-2'},
|
||||
server: {'cloud-9'},
|
||||
);
|
||||
expect(merged, containsAll(['cloud-1', 'cloud-2', 'cloud-9']));
|
||||
expect(merged, hasLength(3));
|
||||
});
|
||||
|
||||
test('Server-Favoriten bleiben erhalten (kein Datenverlust)', () {
|
||||
final merged = favoritenMerge(
|
||||
lokal: <String>{},
|
||||
server: {'cloud-7', 'cloud-8'},
|
||||
);
|
||||
expect(merged, containsAll(['cloud-7', 'cloud-8']));
|
||||
});
|
||||
|
||||
test('Doppelte IDs (lokal == server) werden dedupliziert', () {
|
||||
final merged = favoritenMerge(
|
||||
lokal: {'cloud-1', 'cloud-2'},
|
||||
server: {'cloud-2', 'cloud-3'},
|
||||
);
|
||||
expect(merged, hasLength(3));
|
||||
expect(merged.where((id) => id == 'cloud-2'), hasLength(1));
|
||||
});
|
||||
|
||||
test('leere Mengen → leere Liste', () {
|
||||
expect(favoritenMerge(lokal: {}, server: {}), isEmpty);
|
||||
});
|
||||
|
||||
test('deterministisch sortiert (für Tests/Stabilität)', () {
|
||||
final a = favoritenMerge(lokal: {'b', 'a'}, server: {'c'});
|
||||
final b = favoritenMerge(lokal: {'c'}, server: {'b', 'a'});
|
||||
expect(a, b);
|
||||
expect(a, ['a', 'b', 'c']);
|
||||
});
|
||||
});
|
||||
|
||||
group('CloudService.setFavorite (deterministischer Push)', () {
|
||||
test('sendet set:true und wertet status ok aus', () async {
|
||||
late Map<String, dynamic> gesendeterBody;
|
||||
String? pfad;
|
||||
final cloud = CloudService(
|
||||
client: MockClient((req) async {
|
||||
pfad = req.url.path;
|
||||
gesendeterBody = jsonDecode(req.body) as Map<String, dynamic>;
|
||||
return jsonOk({'status': 'ok', 'favorited': true});
|
||||
}),
|
||||
);
|
||||
final ok = await cloud.setFavorite('cloud-1', favorit: true);
|
||||
expect(ok, isTrue);
|
||||
expect(pfad, '/api/v1/cloud/favorites/toggle');
|
||||
expect(gesendeterBody['song_id'], 'cloud-1');
|
||||
expect(gesendeterBody['set'], isTrue);
|
||||
});
|
||||
|
||||
test('sendet set:false beim Entfernen', () async {
|
||||
late Map<String, dynamic> gesendeterBody;
|
||||
final cloud = CloudService(
|
||||
client: MockClient((req) async {
|
||||
gesendeterBody = jsonDecode(req.body) as Map<String, dynamic>;
|
||||
return jsonOk({'status': 'ok', 'favorited': false});
|
||||
}),
|
||||
);
|
||||
final ok = await cloud.setFavorite('cloud-2', favorit: false);
|
||||
expect(ok, isTrue);
|
||||
expect(gesendeterBody['set'], isFalse);
|
||||
});
|
||||
|
||||
test('Fehlerantwort (kein status ok) → false', () async {
|
||||
final cloud = CloudService(
|
||||
client: MockClient((req) async => jsonOk({'status': 'error'})),
|
||||
);
|
||||
expect(await cloud.setFavorite('cloud-3', favorit: true), isFalse);
|
||||
});
|
||||
|
||||
test('Netzwerkfehler → false (kein Crash)', () async {
|
||||
final cloud = CloudService(
|
||||
client: MockClient(
|
||||
(req) async => throw http.ClientException('offline')),
|
||||
);
|
||||
expect(await cloud.setFavorite('cloud-4', favorit: true), isFalse);
|
||||
});
|
||||
});
|
||||
|
||||
group('pusheFavoritAnServer (Feuer-und-Vergessen)', () {
|
||||
test('leitet Toggle-Status an setFavorite weiter', () async {
|
||||
final bodies = <Map<String, dynamic>>[];
|
||||
final cloud = CloudService(
|
||||
client: MockClient((req) async {
|
||||
bodies.add(jsonDecode(req.body) as Map<String, dynamic>);
|
||||
return jsonOk({'status': 'ok'});
|
||||
}),
|
||||
);
|
||||
await pusheFavoritAnServer(cloud, 'cloud-5', true);
|
||||
await pusheFavoritAnServer(cloud, 'cloud-5', false);
|
||||
expect(bodies, hasLength(2));
|
||||
expect(bodies[0]['set'], isTrue);
|
||||
expect(bodies[1]['set'], isFalse);
|
||||
expect(bodies[0]['song_id'], 'cloud-5');
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user