Fix: Cloud-Playlists lassen sich nicht löschen (DELETE statt POST)
deletePlaylist() schickte ein POST auf /api/v1/cloud/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` zurück. Zusätzlich wurde der Rückgabewert in cloud_screen.dart ignoriert: die UI meldete "Playlist gelöscht", während die Playlist auf dem Server bestehen blieb und beim nächsten Sync wieder auftauchte. - cloud_service.dart: DELETE mit id im Body, Fehler werden geloggt - cloud_screen.dart: Rückgabewert auswerten statt pauschal Erfolg melden - cloud_service_test.dart: 3 Regressionstests (Methode ist DELETE, id im Body, status != ok → false, Netzwerkfehler → false) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
8f9ed1ff53
commit
22238138e8
@@ -816,9 +816,14 @@ class _CloudScreenState extends State<CloudScreen>
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
if (ok == true) {
|
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();
|
await _ladePlaylists();
|
||||||
_setzeStatus('Playlist gelöscht', ok: true);
|
_setzeStatus(
|
||||||
|
geloescht ? 'Playlist gelöscht' : 'Löschen fehlgeschlagen',
|
||||||
|
ok: geloescht,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -199,10 +199,27 @@ class CloudService extends ChangeNotifier {
|
|||||||
return r?['playlist'];
|
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<bool> deletePlaylist(int id) async {
|
Future<bool> deletePlaylist(int id) async {
|
||||||
final r = await _post('/api/v1/cloud/playlists', {'id': id});
|
try {
|
||||||
return r?['status'] == 'ok';
|
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
|
/// Playlist-Details mit Songs abrufen
|
||||||
|
|||||||
@@ -355,4 +355,43 @@ void main() {
|
|||||||
expect(await cloud401.globalList(), isEmpty);
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user