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/acr_cloud.dart'; import 'package:melo/services/baka_auth.dart'; class _MemorySpeicher implements TokenSpeicher { 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); } /// Antwort von ACRCloud mit einem Treffer. String _trefferJson() => jsonEncode({ 'status': {'code': 0, 'msg': 'Success'}, 'metadata': { 'music': [ { 'title': 'Roses', 'artists': [ {'name': 'Neoni'} ], 'album': {'name': 'Chapter One'}, } ] } }); /// Holt den Wert eines Multipart-Feldes aus dem Rumpf. String? _feld(String body, String name) { final treffer = RegExp('name="$name"\r\n\r\n(.*?)\r\n').firstMatch(body); return treffer?.group(1); } void main() { test('Signatur entspricht dem geprüften Referenzwert', () { final signatur = AcrCloudService.signatur( accessKey: 'testkey', secretKey: 'testsecret', timestamp: 1700000000, ); expect(signatur, 'd9SrXTXWNeH1N0Axic0hvCXJlms='); }); test('parseAntwort liest Titel, Künstler und Album aus einem Treffer', () { final treffer = AcrCloudService.parseAntwort(_trefferJson()); expect(treffer, isNotNull); expect(treffer!.titel, 'Roses'); expect(treffer.kuenstler, 'Neoni'); expect(treffer.album, 'Chapter One'); }); test('Code 1001 (kein Treffer) ergibt null statt eines Fehlers', () { final antwort = jsonEncode({ 'status': {'code': 1001, 'msg': 'No result'} }); expect(AcrCloudService.parseAntwort(antwort), isNull); }); test('Anderer Fehlercode wirft AcrCloudException mit der Server-Meldung', () { final antwort = jsonEncode({ 'status': {'code': 3003, 'msg': 'Limit exceeded'} }); expect( () => AcrCloudService.parseAntwort(antwort), throwsA(isA() .having((e) => e.nachricht, 'nachricht', 'Limit exceeded')), ); }); test('erkenne schickt die geforderten Felder und liefert den Treffer', () async { String body = ''; Uri? ziel; final dienst = AcrCloudService( accessKey: 'testkey', secretKey: 'testsecret', client: MockClient((req) async { ziel = req.url; body = latin1.decode(req.bodyBytes); return http.Response(_trefferJson(), 200); }), ); final treffer = await dienst.erkenne(utf8.encode('FAKE-WAV-DATEN')); expect(ziel.toString(), 'https://identify-eu-west-1.acrcloud.com/v1/identify'); expect(_feld(body, 'access_key'), 'testkey'); expect(_feld(body, 'data_type'), 'audio'); expect(_feld(body, 'signature_version'), '1'); expect(_feld(body, 'sample_bytes'), '14'); expect(body, contains('name="sample"; filename=')); expect(body, contains('FAKE-WAV-DATEN')); // Die Signatur muss zum mitgeschickten Zeitstempel passen. final timestamp = int.parse(_feld(body, 'timestamp')!); expect( _feld(body, 'signature'), AcrCloudService.signatur( accessKey: 'testkey', secretKey: 'testsecret', timestamp: timestamp), ); expect(treffer!.titel, 'Roses'); }); test('AcrZugang speichert die Schlüssel und lädt sie wieder', () async { final speicher = _MemorySpeicher(); final zugang = AcrZugang(speicher: speicher); expect(zugang.istKonfiguriert, isFalse); await zugang.speichern('key-123', 'secret-456'); expect(speicher.werte['acr_access_key'], 'key-123'); expect(speicher.werte['acr_secret_key'], 'secret-456'); final neu = AcrZugang(speicher: speicher); await neu.laden(); expect(neu.istKonfiguriert, isTrue); expect(neu.accessKey, 'key-123'); expect(neu.secretKey, 'secret-456'); }); }