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/services/musicbrainz_service.dart'; /// Antwort von MusicBrainz mit zwei Aufnahmen — die zweite ohne Release. String _antwortJson() => jsonEncode({ 'created': '2026-08-20T12:00:00.000Z', 'count': 2, 'offset': 0, 'recordings': [ { 'id': 'f9a1c8d2-1111-4d3c-9f9b-0e1d2c3b4a5e', 'score': 100, 'title': 'Roses', 'length': 221000, 'artist-credit': [ { 'name': 'Neoni', 'artist': { 'id': 'a1b2c3d4-2222-4a5b-8c7d-6e5f4a3b2c1d', 'name': 'Neoni', }, } ], 'releases': [ { 'id': 'c3d4e5f6-3333-4b6c-9d8e-7f6a5b4c3d2e', 'title': 'Chapter One', 'status': 'Official', } ], }, { 'id': '11112222-4444-4c7d-8e9f-0a1b2c3d4e5f', 'score': 72, 'title': 'Roses (Live)', 'artist-credit': [ {'name': 'Neoni'} ], } ], }); void main() { test('parseAntwort liest Titel, Künstler, Album und Release-MBID', () { final vorschlaege = MusicBrainzService.parseAntwort(_antwortJson()); expect(vorschlaege, hasLength(2)); final erster = vorschlaege.first; expect(erster.titel, 'Roses'); expect(erster.kuenstler, 'Neoni'); expect(erster.album, 'Chapter One'); expect(erster.releaseMbid, 'c3d4e5f6-3333-4b6c-9d8e-7f6a5b4c3d2e'); expect(erster.score, 100); }); test('Ohne Release bleibt das Album leer, statt geraten zu werden', () { final vorschlaege = MusicBrainzService.parseAntwort(_antwortJson()); expect(vorschlaege.last.titel, 'Roses (Live)'); expect(vorschlaege.last.album, ''); expect(vorschlaege.last.releaseMbid, ''); }); test('Keine Aufnahmen ergeben eine leere Liste', () { final leer = jsonEncode({'count': 0, 'offset': 0, 'recordings': []}); expect(MusicBrainzService.parseAntwort(leer), isEmpty); }); test('Kaputte Antwort liefert nichts, statt abzustürzen', () { expect(MusicBrainzService.parseAntwort('502 Bad Gateway'), isEmpty); expect(MusicBrainzService.parseAntwort(''), isEmpty); expect(MusicBrainzService.parseAntwort(jsonEncode({'recordings': 'kaputt'})), isEmpty); }); test('suche schickt den Pflicht-User-Agent und die Lucene-Abfrage', () async { http.Request? gesehen; final dienst = MusicBrainzService( client: MockClient((req) async { gesehen = req; return http.Response(_antwortJson(), 200, headers: {'content-type': 'application/json; charset=utf-8'}); }), ); final vorschlaege = await dienst.suche(titel: 'Roses', kuenstler: 'Neoni'); expect(gesehen!.headers['User-Agent'], 'Melo/1.0 (https://baka-net.de)'); expect(gesehen!.url.host, 'musicbrainz.org'); expect(gesehen!.url.path, '/ws/2/recording'); expect(gesehen!.url.queryParameters['query'], 'recording:"Roses" AND artist:"Neoni"'); expect(gesehen!.url.queryParameters['fmt'], 'json'); expect(gesehen!.url.queryParameters['limit'], '5'); expect(vorschlaege.first.titel, 'Roses'); }); test('Ohne Künstler fragt die Suche nur nach dem Titel', () async { Uri? ziel; final dienst = MusicBrainzService( client: MockClient((req) async { ziel = req.url; return http.Response(_antwortJson(), 200); }), ); await dienst.suche(titel: 'Roses'); expect(ziel!.queryParameters['query'], 'recording:"Roses"'); await dienst.suche(titel: 'Roses', kuenstler: ' '); expect(ziel!.queryParameters['query'], 'recording:"Roses"'); }); test('Anführungszeichen im Titel zerlegen die Abfrage nicht', () async { Uri? ziel; final dienst = MusicBrainzService( client: MockClient((req) async { ziel = req.url; return http.Response(_antwortJson(), 200); }), ); await dienst.suche(titel: 'Say "Hi"', kuenstler: 'AC\\DC'); expect(ziel!.queryParameters['query'], r'recording:"Say \"Hi\"" AND artist:"AC\\DC"'); }); test('Umlaute in der Antwort kommen unverfälscht an', () async { final dienst = MusicBrainzService( client: MockClient((_) async => http.Response.bytes( utf8.encode(jsonEncode({ 'recordings': [ { 'title': 'Über den Wolken', 'score': 90, 'artist-credit': [ {'name': 'Reinhard Mey'} ], } ], })), 200, )), ); final vorschlaege = await dienst.suche(titel: 'Über den Wolken'); expect(vorschlaege.first.titel, 'Über den Wolken'); expect(vorschlaege.first.kuenstler, 'Reinhard Mey'); }); test('Ein Serverfehler bleibt nicht stumm', () async { final dienst = MusicBrainzService( client: MockClient((_) async => http.Response('Service Unavailable', 503)), ); expect(dienst.suche(titel: 'Roses'), throwsA(isA())); }); test('coverUrl zeigt auf das Frontcover im Cover Art Archive', () { final dienst = MusicBrainzService( client: MockClient((_) async => http.Response('', 200)), ); expect( dienst.coverUrl('c3d4e5f6-3333-4b6c-9d8e-7f6a5b4c3d2e'), 'https://coverartarchive.org/release/c3d4e5f6-3333-4b6c-9d8e-7f6a5b4c3d2e/front-250', ); }); }