import 'dart:convert'; import 'package:drift/native.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:http/http.dart' as http; import 'package:http/testing.dart'; import 'package:melo/library/database.dart'; import 'package:melo/library/playlist_service.dart'; import 'package:melo/services/baka_auth.dart'; import 'package:melo/services/melo_cloud_service.dart'; class _MemorySpeicher implements TokenSpeicher { _MemorySpeicher(this.werte); final Map werte; @override Future lesen(String key) async => werte[key]; @override Future schreiben(String key, String wert) async => werte[key] = wert; @override Future loeschen(String key) async => werte.remove(key); } Future _angemeldeteAuth() async { final auth = BakaAuth( speicher: _MemorySpeicher({'baka_token': 'tok', 'baka_user': 'Baka'}), ); await auth.laden(); return auth; } void main() { late MeloDb db; late PlaylistService service; setUp(() { db = MeloDb(NativeDatabase.memory()); service = PlaylistService(db); }); tearDown(() async => db.close()); test('createPlaylist delegates to db and notifies listeners', () async { var notified = false; service.addListener(() => notified = true); await service.createPlaylist('Road Trip'); final playlists = await db.watchPlaylists().first; expect(playlists.length, 1); expect(playlists.first.name, 'Road Trip'); expect(notified, true); }); test('toggleFavorite delegates to db and notifies listeners', () async { await db.into(db.songs).insert(SongsCompanion.insert( id: 'song-1', path: '/a.mp3', title: 'A', dateAddedMs: 0, updatedAtMs: 0, )); var notified = false; service.addListener(() => notified = true); await service.toggleFavorite('song-1'); expect(await db.watchIsFavorite('song-1').first, true); expect(notified, true); }); test('reorderAll schreibt lückenlose Positionen 0..N-1 in neuer Reihenfolge', () async { final playlistId = await db.createPlaylist('Mix'); for (final id in ['song-1', 'song-2', 'song-3']) { await db.into(db.songs).insert(SongsCompanion.insert( id: id, path: '/$id.mp3', title: id, dateAddedMs: 0, updatedAtMs: 0, )); } // Ursprüngliche (evtl. lückenhafte) Positionen. await db.addSongToPlaylist(playlistId, 'song-1', 0); await db.addSongToPlaylist(playlistId, 'song-2', 5); await db.addSongToPlaylist(playlistId, 'song-3', 9); var notified = false; service.addListener(() => notified = true); await service.reorderAll(playlistId, ['song-3', 'song-1', 'song-2']); final songs = await db.watchPlaylistSongs(playlistId).first; expect(songs.map((s) => s.id).toList(), ['song-3', 'song-1', 'song-2']); expect(notified, true); }); group('Sofort-Push der Favoriten', () { Future legeSongAn(MeloDb db, {String? cloudId}) async { await db.into(db.songs).insert(SongsCompanion.insert( id: 'song-1', path: '/a.mp3', title: 'A', dateAddedMs: 0, updatedAtMs: 0, )); if (cloudId != null) await db.setCloudId('song-1', cloudId); } test('setzt deterministisch true beim Favorisieren', () async { final db2 = MeloDb(NativeDatabase.memory()); addTearDown(db2.close); await legeSongAn(db2, cloudId: 'c1'); final gesendet = >[]; final dienst = PlaylistService( db2, cloud: MeloCloudService( auth: await _angemeldeteAuth(), client: MockClient((anfrage) async { gesendet.add(jsonDecode(anfrage.body) as Map); return http.Response(jsonEncode({'status': 'ok'}), 200); }), ), ); await dienst.toggleFavorite('song-1'); expect(gesendet, [ {'song_id': 'c1', 'set': true} ]); }); test('setzt deterministisch false beim Ent-Favorisieren', () async { final db2 = MeloDb(NativeDatabase.memory()); addTearDown(db2.close); await legeSongAn(db2, cloudId: 'c1'); await db2.setFavorite('song-1', true); final gesendet = >[]; final dienst = PlaylistService( db2, cloud: MeloCloudService( auth: await _angemeldeteAuth(), client: MockClient((anfrage) async { gesendet.add(jsonDecode(anfrage.body) as Map); return http.Response(jsonEncode({'status': 'ok'}), 200); }), ), ); await dienst.toggleFavorite('song-1'); expect(gesendet, [ {'song_id': 'c1', 'set': false} ]); }); test('ohne cloudId wird nichts gemeldet', () async { final db2 = MeloDb(NativeDatabase.memory()); addTearDown(db2.close); await legeSongAn(db2); var anfragen = 0; final dienst = PlaylistService( db2, cloud: MeloCloudService( auth: await _angemeldeteAuth(), client: MockClient((_) async { anfragen++; return http.Response(jsonEncode({'status': 'ok'}), 200); }), ), ); await dienst.toggleFavorite('song-1'); expect(anfragen, 0); expect(await db2.watchIsFavorite('song-1').first, isTrue); }); test('ein Fehler des Servers ändert lokal nichts und wirft nicht', () async { final db2 = MeloDb(NativeDatabase.memory()); addTearDown(db2.close); await legeSongAn(db2, cloudId: 'c1'); final dienst = PlaylistService( db2, cloud: MeloCloudService( auth: await _angemeldeteAuth(), client: MockClient( (_) async => http.Response(jsonEncode({'error': 'weg'}), 500)), ), ); // Der nächste Voll-Abgleich holt den Push additiv nach. await dienst.toggleFavorite('song-1'); expect(await db2.watchIsFavorite('song-1').first, isTrue); }); }); }