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/baka_auth.dart'; /// Einfacher Speicher im Arbeitsspeicher — FlutterSecureStorage braucht /// Plattform-Kanäle und ist im Test nicht verfügbar. 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); } void main() { test('Anmeldung mit gültigen Daten speichert den Token', () async { final speicher = _MemorySpeicher(); late String gesendet; final client = MockClient((req) async { gesendet = req.body; return http.Response(jsonEncode({'token': 'jwt-abc'}), 200); }); final auth = BakaAuth(client: client, speicher: speicher); final fehler = await auth.anmelden('Baka', 'geheim'); expect(fehler, isNull); expect(auth.istAngemeldet, isTrue); expect(auth.benutzer, 'Baka'); expect(speicher.werte['baka_token'], 'jwt-abc'); expect(jsonDecode(gesendet), {'username': 'Baka', 'password': 'geheim'}); }); test('Auth-Header trägt den Token als Bearer', () async { final client = MockClient( (_) async => http.Response(jsonEncode({'token': 'jwt-abc'}), 200)); final auth = BakaAuth(client: client, speicher: _MemorySpeicher()); await auth.anmelden('Baka', 'geheim'); expect(auth.authHeader['Authorization'], 'Bearer jwt-abc'); }); test('Falsches Passwort liefert die Server-Meldung, obwohl HTTP 200 kommt', () async { // Der Baka-Auth-Server antwortet auch bei Fehlern mit Status 200. final client = MockClient((_) async => http.Response( jsonEncode({'status': 'error', 'message': 'Falscher Benutzer'}), 200)); final auth = BakaAuth(client: client, speicher: _MemorySpeicher()); final fehler = await auth.anmelden('Baka', 'falsch'); expect(fehler, 'Falscher Benutzer'); expect(auth.istAngemeldet, isFalse); }); test('Nicht erreichbarer Server meldet einen Verbindungsfehler', () async { final client = MockClient((_) async => throw const SocketAusfall()); final auth = BakaAuth(client: client, speicher: _MemorySpeicher()); final fehler = await auth.anmelden('Baka', 'geheim'); expect(fehler, 'Keine Verbindung zum Anmelde-Server'); expect(auth.istAngemeldet, isFalse); }); test('laden() stellt einen gespeicherten Token wieder her', () async { final speicher = _MemorySpeicher(); speicher.werte['baka_token'] = 'jwt-alt'; speicher.werte['baka_user'] = 'Baka'; final auth = BakaAuth( client: MockClient((_) async => http.Response('', 500)), speicher: speicher); await auth.laden(); expect(auth.istAngemeldet, isTrue); expect(auth.benutzer, 'Baka'); }); test('autoAnmelden meldet mit den übergebenen Zugangsdaten an', () async { late String gesendet; final client = MockClient((req) async { gesendet = req.body; return http.Response(jsonEncode({'token': 'jwt-server'}), 200); }); final auth = BakaAuth(client: client, speicher: _MemorySpeicher()); await auth.autoAnmelden('Tinker', 'geheim2'); expect(auth.istAngemeldet, isTrue); expect(auth.benutzer, 'Tinker'); expect(jsonDecode(gesendet), {'username': 'Tinker', 'password': 'geheim2'}); }); test('autoAnmelden greift nicht ein, wenn schon angemeldet', () async { var aufrufe = 0; final client = MockClient((_) async { aufrufe++; return http.Response(jsonEncode({'token': 'jwt-abc'}), 200); }); final auth = BakaAuth(client: client, speicher: _MemorySpeicher()); await auth.anmelden('Baka', 'geheim'); await auth.autoAnmelden('Tinker', 'geheim2'); expect(aufrufe, 1); expect(auth.benutzer, 'Baka'); }); test( 'autoAnmelden löst bei gleichzeitigen Aufrufen nur EINEN HTTP-Request aus ' '(Race Condition durch IndexedStack, das mehrere Tabs gleichzeitig baut)', () async { var aufrufe = 0; final client = MockClient((_) async { aufrufe++; // Künstliche Verzögerung: simuliert, dass der erste Request noch // nicht zurück ist, wenn der zweite Aufruf startet. await Future.delayed(const Duration(milliseconds: 50)); return http.Response(jsonEncode({'token': 'jwt-tinker'}), 200); }); final auth = BakaAuth(client: client, speicher: _MemorySpeicher()); await Future.wait([ auth.autoAnmelden('Tinker', 'geheim'), auth.autoAnmelden('Tinker', 'geheim'), ]); expect(aufrufe, 1); expect(auth.istAngemeldet, isTrue); expect(auth.benutzer, 'Tinker'); }); test( 'autoAnmelden merkt sich den Fehlergrund für die Diagnose, da es ' 'selbst keinen Rückgabewert liefert', () async { final client = MockClient((_) async => http.Response( jsonEncode({'status': 'error', 'message': 'Falsches Passwort'}), 200)); final auth = BakaAuth(client: client, speicher: _MemorySpeicher()); await auth.autoAnmelden('Tinker', 'falsch'); expect(auth.letzterAutoLoginFehler, 'Falsches Passwort'); expect(auth.istAngemeldet, isFalse); }); test('autoAnmelden tut nichts ohne Zugangsdaten', () async { var aufrufe = 0; final client = MockClient((_) async { aufrufe++; return http.Response(jsonEncode({'token': 'jwt'}), 200); }); final auth = BakaAuth(client: client, speicher: _MemorySpeicher()); await auth.autoAnmelden('', ''); expect(aufrufe, 0); expect(auth.istAngemeldet, isFalse); }); test('Abmelden löscht Token und Benutzer aus dem Speicher', () async { final speicher = _MemorySpeicher(); final auth = BakaAuth( client: MockClient( (_) async => http.Response(jsonEncode({'token': 'jwt-abc'}), 200)), speicher: speicher); await auth.anmelden('Baka', 'geheim'); await auth.abmelden(); expect(auth.istAngemeldet, isFalse); expect(speicher.werte, isEmpty); }); } /// Steht im Test für einen Netzwerkausfall. class SocketAusfall implements Exception { const SocketAusfall(); }