Files
Melo/test/services/baka_auth_test.dart
T
Hermes (Server) 196303145a Manuelle Baka-Anmeldung übernimmt das Passwort jetzt dauerhaft; Auto-Login-Fehler werden geloggt
Nach erfolgreichem manuellem Login im "Baka-Konto"-Dialog wird das neue
Passwort jetzt zusätzlich in NavidromeService persistiert (dafür ergänzt:
öffentlicher serverUrl-Getter) — vorher schrieb der Dialog es nur ins
BakaAuth-Token, nie zurück in die lokale Navidrome-Ablage, aus der
autoAnmelden() sich beim nächsten App-Start bedient. Ohne diesen Fix kam
der Auto-Login-Fehler nach Ablauf des 7-Tage-Tokens garantiert wieder.

Zusätzlich hält BakaAuth.letzterAutoLoginFehler jetzt den Fehlertext des
letzten Auto-Login-Versuchs fest, statt ihn zu verwerfen — bisher war aus
der App nicht diagnostizierbar, woran ein Auto-Login scheiterte.
2026-08-29 12:38:20 +02:00

189 lines
6.2 KiB
Dart

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<String, String> werte = {};
@override
Future<String?> lesen(String key) async => werte[key];
@override
Future<void> schreiben(String key, String wert) async => werte[key] = wert;
@override
Future<void> 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();
}