diff --git a/lib/screens/cloud_screen.dart b/lib/screens/cloud_screen.dart index 79092d3..05a69be 100644 --- a/lib/screens/cloud_screen.dart +++ b/lib/screens/cloud_screen.dart @@ -816,9 +816,14 @@ class _CloudScreenState extends State ), ); if (ok == true) { - await widget.cloud.deletePlaylist(id); + // Rückgabewert auswerten — sonst meldet die UI Erfolg, während die + // Playlist auf dem Server bleibt und beim nächsten Sync wieder auftaucht. + final geloescht = await widget.cloud.deletePlaylist(id); await _ladePlaylists(); - _setzeStatus('Playlist gelöscht', ok: true); + _setzeStatus( + geloescht ? 'Playlist gelöscht' : 'Löschen fehlgeschlagen', + ok: geloescht, + ); } } diff --git a/lib/services/cloud_service.dart b/lib/services/cloud_service.dart index fc2606f..9421dd1 100644 --- a/lib/services/cloud_service.dart +++ b/lib/services/cloud_service.dart @@ -199,10 +199,27 @@ class CloudService extends ChangeNotifier { return r?['playlist']; } - /// Playlist löschen + /// Playlist löschen. + /// + /// Muss DELETE sein: der Server unterscheidet die Methode auf demselben Pfad + /// (POST = anlegen, DELETE = löschen). Ein POST landete früher im + /// Anlegen-Handler, scheiterte dort an der leeren Namensprüfung und lieferte + /// HTTP 200 mit `status: error` — die Playlist blieb unbemerkt bestehen. Future deletePlaylist(int id) async { - final r = await _post('/api/v1/cloud/playlists', {'id': id}); - return r?['status'] == 'ok'; + try { + final r = await _client + .delete(Uri.parse('$_base/api/v1/cloud/playlists'), + headers: _jsonHeader, body: jsonEncode({'id': id})) + .timeout(const Duration(seconds: 10)); + if (r.statusCode != 200) { + MeloLogger().fehler('cloud_playlist_delete', '${r.statusCode}'); + return false; + } + return jsonDecode(r.body)['status'] == 'ok'; + } catch (e) { + MeloLogger().fehler('cloud_playlist_delete_error', '$e'); + return false; + } } /// Playlist-Details mit Songs abrufen diff --git a/test/cloud_service_test.dart b/test/cloud_service_test.dart index a4fc77c..27c8320 100644 --- a/test/cloud_service_test.dart +++ b/test/cloud_service_test.dart @@ -355,4 +355,43 @@ void main() { expect(await cloud401.globalList(), isEmpty); }); }); + + // Regression: deletePlaylist schickte ein POST auf /playlists. Der Server + // unterscheidet dort nach Methode (POST = anlegen, DELETE = löschen), also + // landete das Löschen im Anlegen-Handler, scheiterte an der leeren + // Namensprüfung und lieferte HTTP 200 mit `status: error`. Die UI meldete + // trotzdem Erfolg, die Playlist blieb bestehen. + group('deletePlaylist', () { + test('nutzt DELETE (nicht POST) und sendet die id im Body', () async { + String? methode; + String? body; + final cloud = CloudService( + client: MockClient((req) async { + methode = req.method; + body = req.body; + return jsonOk({'status': 'ok'}); + }), + ); + + expect(await cloud.deletePlaylist(42), isTrue); + expect(methode, 'DELETE'); + expect(jsonDecode(body!)['id'], 42); + }); + + test('status != ok → false (meldet keinen falschen Erfolg)', () async { + final cloud = CloudService( + client: MockClient( + (req) async => jsonOk({'status': 'error', 'error': 'Name erforderlich'})), + ); + expect(await cloud.deletePlaylist(42), isFalse); + }); + + test('Netzwerkfehler → false (kein Crash)', () async { + final cloud = CloudService( + client: MockClient( + (req) async => throw http.ClientException('offline')), + ); + expect(await cloud.deletePlaylist(42), isFalse); + }); + }); }