Einseitige Playlist-Sicherung: melden, merken, bei leerer Tabelle holen
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012A2pmbnVNPiHdyf2GW8eLP
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
0519c68b26
commit
faabf28abc
@@ -0,0 +1,254 @@
|
||||
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<String, String> werte;
|
||||
@override
|
||||
Future<String?> lesen(String key) async => werte[key];
|
||||
@override
|
||||
Future<void> schreiben(String key, String wert) async => werte[key] = wert;
|
||||
@override
|
||||
Future<void> loeschen(String key) async => werte.remove(key);
|
||||
}
|
||||
|
||||
Future<MeloCloudService> cloudMit(
|
||||
Future<http.Response> Function(http.Request) antwort) async {
|
||||
final auth = BakaAuth(
|
||||
speicher: _MemorySpeicher({'baka_token': 'tok', 'baka_user': 'Baka'}),
|
||||
);
|
||||
await auth.laden();
|
||||
return MeloCloudService(auth: auth, client: MockClient(antwort));
|
||||
}
|
||||
|
||||
void main() {
|
||||
test('eine neue Playlist wird gemeldet und ihre Server-ID gemerkt',
|
||||
() async {
|
||||
final db = MeloDb(NativeDatabase.memory());
|
||||
addTearDown(db.close);
|
||||
final dienst = PlaylistService(
|
||||
db,
|
||||
cloud: await cloudMit((_) async => http.Response(
|
||||
// Form von handle_playlist_create: die ID liegt unter „playlist".
|
||||
jsonEncode({
|
||||
'status': 'ok',
|
||||
'playlist': {'id': 7, 'name': 'Road Trip'}
|
||||
}),
|
||||
200,
|
||||
)),
|
||||
);
|
||||
|
||||
final id = await dienst.createPlaylist('Road Trip');
|
||||
|
||||
expect((await db.playlistById(id))!.cloudId, '7');
|
||||
});
|
||||
|
||||
test('ein Song ohne cloudId wird nicht mitgemeldet', () async {
|
||||
final db = MeloDb(NativeDatabase.memory());
|
||||
addTearDown(db.close);
|
||||
var songMeldungen = 0;
|
||||
final dienst = PlaylistService(
|
||||
db,
|
||||
cloud: await cloudMit((anfrage) async {
|
||||
if (anfrage.url.path.endsWith('/songs')) songMeldungen++;
|
||||
return http.Response(
|
||||
jsonEncode({
|
||||
'status': 'ok',
|
||||
'playlist': {'id': 7, 'name': 'Mix'}
|
||||
}),
|
||||
200,
|
||||
);
|
||||
}),
|
||||
);
|
||||
await db.into(db.songs).insert(SongsCompanion.insert(
|
||||
id: 'song-1', path: '/a.mp3', title: 'A',
|
||||
dateAddedMs: 0, updatedAtMs: 0,
|
||||
));
|
||||
final id = await dienst.createPlaylist('Mix');
|
||||
|
||||
await dienst.addSongToPlaylist(id, 'song-1', 0);
|
||||
|
||||
expect(songMeldungen, 0);
|
||||
// Lokal ist er trotzdem drin — kein Fehler, nur nichts zu melden.
|
||||
expect(await db.watchPlaylistSongs(id).first, hasLength(1));
|
||||
});
|
||||
|
||||
/// Legt einen Titel mit Server-ID an. Ohne den fällt jeder Push aus, und
|
||||
/// die Negativtests allein hätten den ganzen Vertrag nie berührt.
|
||||
Future<void> legeSongAn(MeloDb db, String id, String cloudId) async {
|
||||
await db.into(db.songs).insert(SongsCompanion.insert(
|
||||
id: id, path: '/$id.mp3', title: id,
|
||||
dateAddedMs: 0, updatedAtMs: 0,
|
||||
));
|
||||
await db.setCloudId(id, cloudId);
|
||||
}
|
||||
|
||||
test('ein Song mit cloudId wird an die Server-Playlist gemeldet', () async {
|
||||
final db = MeloDb(NativeDatabase.memory());
|
||||
addTearDown(db.close);
|
||||
Object? koerper;
|
||||
String? pfad;
|
||||
final dienst = PlaylistService(
|
||||
db,
|
||||
cloud: await cloudMit((anfrage) async {
|
||||
if (anfrage.method == 'POST' && anfrage.url.path.endsWith('/songs')) {
|
||||
pfad = anfrage.url.path;
|
||||
koerper = jsonDecode(anfrage.body);
|
||||
}
|
||||
return http.Response(
|
||||
jsonEncode({
|
||||
'status': 'ok',
|
||||
'playlist': {'id': 7, 'name': 'Mix'}
|
||||
}),
|
||||
200,
|
||||
);
|
||||
}),
|
||||
);
|
||||
await legeSongAn(db, 'song-1', 'c1');
|
||||
final id = await dienst.createPlaylist('Mix');
|
||||
|
||||
await dienst.addSongToPlaylist(id, 'song-1', 0);
|
||||
|
||||
expect(pfad, endsWith('/playlists/7/songs'));
|
||||
expect(koerper, {
|
||||
'song_ids': ['c1']
|
||||
});
|
||||
});
|
||||
|
||||
test('das Entfernen geht als DELETE auf den Song-Pfad', () async {
|
||||
final db = MeloDb(NativeDatabase.memory());
|
||||
addTearDown(db.close);
|
||||
String? geloeschterPfad;
|
||||
final dienst = PlaylistService(
|
||||
db,
|
||||
cloud: await cloudMit((anfrage) async {
|
||||
if (anfrage.method == 'DELETE') geloeschterPfad = anfrage.url.path;
|
||||
return http.Response(
|
||||
jsonEncode({
|
||||
'status': 'ok',
|
||||
'playlist': {'id': 7, 'name': 'Mix'}
|
||||
}),
|
||||
200,
|
||||
);
|
||||
}),
|
||||
);
|
||||
await legeSongAn(db, 'song-1', 'c1');
|
||||
final id = await dienst.createPlaylist('Mix');
|
||||
await dienst.addSongToPlaylist(id, 'song-1', 0);
|
||||
|
||||
await dienst.removeSongFromPlaylist(id, 'song-1');
|
||||
|
||||
expect(geloeschterPfad, endsWith('/playlists/7/songs/c1'));
|
||||
expect(await db.watchPlaylistSongs(id).first, isEmpty);
|
||||
});
|
||||
|
||||
test('eine neue Reihenfolge geht als positions-Paare raus', () async {
|
||||
final db = MeloDb(NativeDatabase.memory());
|
||||
addTearDown(db.close);
|
||||
Object? koerper;
|
||||
String? methode;
|
||||
final dienst = PlaylistService(
|
||||
db,
|
||||
cloud: await cloudMit((anfrage) async {
|
||||
if (anfrage.url.path.endsWith('/positions')) {
|
||||
methode = anfrage.method;
|
||||
koerper = jsonDecode(anfrage.body);
|
||||
}
|
||||
return http.Response(
|
||||
jsonEncode({
|
||||
'status': 'ok',
|
||||
'playlist': {'id': 7, 'name': 'Mix'}
|
||||
}),
|
||||
200,
|
||||
);
|
||||
}),
|
||||
);
|
||||
await legeSongAn(db, 'song-1', 'c1');
|
||||
await legeSongAn(db, 'song-2', 'c2');
|
||||
final id = await dienst.createPlaylist('Mix');
|
||||
await dienst.addSongToPlaylist(id, 'song-1', 0);
|
||||
await dienst.addSongToPlaylist(id, 'song-2', 1);
|
||||
|
||||
await dienst.reorderAll(id, ['song-2', 'song-1']);
|
||||
|
||||
// Unter „song_ids" hätte der Server eine leere Liste gelesen und stumm
|
||||
// „ok" geantwortet — dieser Test ist der einzige Ort, an dem das auffällt.
|
||||
expect(methode, 'PUT');
|
||||
expect(koerper, {
|
||||
'positions': [
|
||||
{'id': 'c2', 'position': 0},
|
||||
{'id': 'c1', 'position': 1},
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
test('ein Endpunkt-Fehler ändert lokal nichts', () async {
|
||||
final db = MeloDb(NativeDatabase.memory());
|
||||
addTearDown(db.close);
|
||||
final dienst = PlaylistService(
|
||||
db,
|
||||
cloud: await cloudMit(
|
||||
(_) async => http.Response(jsonEncode({'error': 'weg'}), 500)),
|
||||
);
|
||||
|
||||
final id = await dienst.createPlaylist('Mix');
|
||||
|
||||
expect((await db.playlistById(id))!.cloudId, isNull);
|
||||
expect(await db.watchPlaylists().first, hasLength(1));
|
||||
});
|
||||
|
||||
test('Wiederherstellung greift nur bei leerer Playlisten-Tabelle', () async {
|
||||
final db = MeloDb(NativeDatabase.memory());
|
||||
addTearDown(db.close);
|
||||
final dienst = PlaylistService(
|
||||
db,
|
||||
cloud: await cloudMit((anfrage) async {
|
||||
if (anfrage.url.path.endsWith('/playlists')) {
|
||||
return http.Response(
|
||||
jsonEncode({
|
||||
'playlists': [
|
||||
{'id': 7, 'name': 'Vom Server'}
|
||||
]
|
||||
}),
|
||||
200,
|
||||
);
|
||||
}
|
||||
return http.Response(jsonEncode({'songs': []}), 200);
|
||||
}),
|
||||
);
|
||||
|
||||
expect(await dienst.stelleWiederHer(), 1);
|
||||
final angelegt = await db.watchPlaylists().first;
|
||||
expect(angelegt.single.name, 'Vom Server');
|
||||
expect(angelegt.single.cloudId, '7');
|
||||
});
|
||||
|
||||
test('mit einer lokalen Playlist wird nichts angelegt (kein Rück-Merge)',
|
||||
() async {
|
||||
final db = MeloDb(NativeDatabase.memory());
|
||||
addTearDown(db.close);
|
||||
final dienst = PlaylistService(
|
||||
db,
|
||||
cloud: await cloudMit((_) async => http.Response(
|
||||
jsonEncode({
|
||||
'playlists': [
|
||||
{'id': 7, 'name': 'Vom Server'}
|
||||
]
|
||||
}),
|
||||
200,
|
||||
)),
|
||||
);
|
||||
await db.createPlaylist('Meine eigene');
|
||||
|
||||
expect(await dienst.stelleWiederHer(), 0);
|
||||
expect(await db.watchPlaylists().first, hasLength(1));
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user