From f604dc7635d1faea5729c4128078b3097db9d88b Mon Sep 17 00:00:00 2001 From: "Hermes (Server)" Date: Wed, 26 Aug 2026 00:18:49 +0200 Subject: [PATCH] YouTube-Tab: Server-User brauchen keine eigene Baka-Anmeldung mehr MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Server-User (Dustin, Baka, Tinker) melden sich beim Öffnen des YouTube-Bereichs automatisch im Hintergrund mit ihren Navidrome- Zugangsdaten beim Baka-Konto an (BakaAuth.autoAnmelden) — die manuelle Anmeldung bleibt nur für Gäste sichtbar. Schlägt die Auto-Anmeldung fehl, gibt es einen Rückfallweg zur manuellen Anmeldung, statt den User auszusperren. Login-Buttons vergrößert. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01CcDiyJdVRqh1TtJk5JiabX --- CHANGELOG.md | 31 ++++++ lib/downloads/downloads_screen.dart | 94 ++++++++++++++----- lib/downloads/youtube_search_screen.dart | 30 +++++- lib/services/baka_auth.dart | 28 ++++++ lib/services/navidrome_service.dart | 6 ++ test/downloads/online_screen_test.dart | 78 ++++++++++++++- .../downloads/youtube_search_screen_test.dart | 64 +++++++++++++ test/services/baka_auth_test.dart | 44 +++++++++ 8 files changed, 346 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b26175..739cd5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,37 @@ Format angelehnt an [Keep a Changelog](https://keepachangelog.com/de/1.0.0/). ## [Unreleased] +### 📺🔑 YouTube-Tab: Server-User brauchen keine eigene Baka-Anmeldung mehr (2026-08-26) + +- 🔑 **Server-User (Dustin, Baka, Tinker) sehen den Baka-Anmelde-Schritt nicht + mehr.** Bisher verlangte der YouTube-Downloader/die YT-Suche IMMER eine + eigene, separate Anmeldung am Baka-Konto — unabhängig davon, ob man schon + am Melo-Server angemeldet war. Neu: Wer mit dem Melo-Server verbunden ist + (`NavidromeService.istVerbunden`), meldet sich beim Öffnen des + YouTube-Bereichs automatisch im Hintergrund mit denselben Zugangsdaten + beim Baka-Konto an (`BakaAuth.autoAnmelden`, neu in `baka_auth.dart`) — + kein Dialog, kein Warten. Voraussetzung: Navidrome- und Baka-Konto teilen + sich Benutzername + Passwort (so bei Dustin/Baka/Tinker der Fall). + Schlägt die Hintergrund-Anmeldung fehl (z. B. abweichendes Passwort), + bekommt der Server-User den bisherigen manuellen Anmelde-Weg als + Rückfalloption zurück — sonst gäbe es keinen Weg mehr zu `_AnmeldeDialog`, + das war ein im @code-review-Fund entdeckter Aussperr-Bug (siehe + `pruefeServerUser` in `baka_auth.dart`). +- 👋 **Die Anmeldung ist jetzt wirklich nur noch für Gäste sichtbar.** Ohne + Server-Verbindung (kein eigener Melo-Account) erscheint weiterhin der + Hinweistext + Anmelde-Button wie bisher — Server-User sehen stattdessen + direkt das Adressfeld, ganz ohne Konto-Status-Zeile. +- 🎨 **Login-UI vergrößert.** "Beim Baka-Konto anmelden" ist jetzt ein + Button über die volle Breite (52 px hoch, größere Schrift); die Buttons + "Abbrechen"/"Anmelden" im Anmelde-Dialog haben jetzt eine Mindesthöhe von + 48 px. Der Dialog hatte ohnehin nie ein Adressfeld — die Proxy-URL bleibt + fest im Code hinterlegt, nicht sichtbar oder änderbar. +- ✅ **Getestet:** 582 Tests grün (vorher 574), `flutter analyze` ohne + Befund. 8 neue Tests: `BakaAuth.autoAnmelden` (meldet an, greift nicht + ein wenn schon angemeldet, tut nichts ohne Zugangsdaten) sowie + Widget-Tests für den Server-User-Weg und den Rückfallweg bei + fehlgeschlagener Auto-Anmeldung in Download-Tab und YT-Suche-Tab. + ### 🎚️📺 Equalizer-Fix (Xiaomi) + In-App-YouTube-Suche, Extra-Tab (2026-08-25) - 🎚️ **Equalizer: "kein System-Klangeffekte" trotz Dolby Atmos behoben.** diff --git a/lib/downloads/downloads_screen.dart b/lib/downloads/downloads_screen.dart index 538a602..5bac6a2 100644 --- a/lib/downloads/downloads_screen.dart +++ b/lib/downloads/downloads_screen.dart @@ -606,11 +606,32 @@ class _YouTubeBereichState extends State<_YouTubeBereich> { /// Gewählter Datenträger; `null` = Android entscheidet (nur ein Ort da). String? _volume; + /// Wer am Melo-Server angemeldet ist, braucht keine eigene + /// Baka-Anmeldung mehr — nur Gäste (kein Server-Account) sehen sie. + bool _serverUser = false; + + /// Die automatische Anmeldung ist fehlgeschlagen (z. B. abweichendes + /// Baka-Passwort) — dann bleibt der manuelle Anmelde-Weg als Rückfalloption + /// sichtbar, sonst käme so ein User nie mehr an den Dialog heran. + bool _autoLoginFehlgeschlagen = false; + @override void initState() { super.initState(); _ladeEinstellungen(); _uebernimmAdresse(); + _pruefeServerUser(); + } + + Future _pruefeServerUser() async { + final nav = context.read().navidrome; + final auth = context.read(); + final ergebnis = await pruefeServerUser(nav, auth); + if (!mounted) return; + setState(() { + _serverUser = ergebnis.serverUser; + _autoLoginFehlgeschlagen = ergebnis.autoLoginFehlgeschlagen; + }); } @override @@ -716,38 +737,54 @@ class _YouTubeBereichState extends State<_YouTubeBereich> { Widget build(BuildContext context) { final auth = context.watch(); final dienst = context.watch(); + // Server-User gelten sofort als berechtigt — die Anmeldung passiert für + // sie im Hintergrund (_pruefeServerUser). Nur wenn die automatische + // Anmeldung nachweislich fehlgeschlagen ist, bleibt der manuelle Weg + // als Rückfalloption sichtbar — sonst gäbe es keinen Weg mehr zurück. + final zugriffOk = + auth.istAngemeldet || (_serverUser && !_autoLoginFehlgeschlagen); return ListView( padding: const EdgeInsets.fromLTRB(16, 12, 16, 24), children: [ - if (!auth.istAngemeldet) ...[ - const Text( - 'Der Downloader läuft über den Baka-Server. Dafür brauchst du ' - 'deine Baka-Anmeldung.', - style: TextStyle(color: MeloTheme.text2, fontSize: 13), + if (!zugriffOk) ...[ + Text( + _autoLoginFehlgeschlagen + ? 'Die automatische Anmeldung hat nicht geklappt. Bitte ' + 'manuell beim Baka-Konto anmelden.' + : 'Der Downloader läuft über den Baka-Server. Dafür brauchst ' + 'du deine Baka-Anmeldung.', + style: const TextStyle(color: MeloTheme.text2, fontSize: 13), ), const SizedBox(height: 12), - FilledButton.icon( - icon: const Icon(Icons.login), - label: const Text('Beim Baka-Konto anmelden'), - onPressed: _anmelden, + SizedBox( + width: double.infinity, + height: MeloTheme.minTouchTarget + 4, + child: FilledButton.icon( + icon: const Icon(Icons.login), + label: const Text('Beim Baka-Konto anmelden', + style: TextStyle(fontSize: 16)), + onPressed: _anmelden, + ), ), ] else ...[ - Row( - children: [ - const Icon(Icons.verified_user, size: 18, color: MeloTheme.text3), - const SizedBox(width: 8), - Expanded( - child: Text('Angemeldet als ${auth.benutzer}', - style: const TextStyle(color: MeloTheme.text2, fontSize: 13)), - ), - TextButton( - onPressed: auth.abmelden, - child: const Text('Abmelden'), - ), - ], - ), - const SizedBox(height: 8), + if (!_serverUser) ...[ + Row( + children: [ + const Icon(Icons.verified_user, size: 18, color: MeloTheme.text3), + const SizedBox(width: 8), + Expanded( + child: Text('Angemeldet als ${auth.benutzer}', + style: const TextStyle(color: MeloTheme.text2, fontSize: 13)), + ), + TextButton( + onPressed: auth.abmelden, + child: const Text('Abmelden'), + ), + ], + ), + const SizedBox(height: 8), + ], TextField( controller: _url, enabled: !dienst.laeuft, @@ -909,14 +946,19 @@ class _AnmeldeDialogState extends State<_AnmeldeDialog> { ), ], ), + actionsPadding: const EdgeInsets.fromLTRB(16, 0, 16, 16), actions: [ TextButton( + style: TextButton.styleFrom( + minimumSize: const Size(88, MeloTheme.minTouchTarget)), onPressed: _verbindet ? null : () => Navigator.pop(context), - child: const Text('Abbrechen'), + child: const Text('Abbrechen', style: TextStyle(fontSize: 16)), ), FilledButton( + style: FilledButton.styleFrom( + minimumSize: const Size(120, MeloTheme.minTouchTarget)), onPressed: _verbindet ? null : _anmelden, - child: const Text('Anmelden'), + child: const Text('Anmelden', style: TextStyle(fontSize: 16)), ), ], ); diff --git a/lib/downloads/youtube_search_screen.dart b/lib/downloads/youtube_search_screen.dart index a17983d..6cd35df 100644 --- a/lib/downloads/youtube_search_screen.dart +++ b/lib/downloads/youtube_search_screen.dart @@ -6,6 +6,7 @@ import 'package:provider/provider.dart'; import '../library/database.dart'; import '../library/library_service.dart'; import '../services/baka_auth.dart'; +import '../services/download_service.dart'; import '../services/media_store.dart'; import '../services/yt_download_service.dart'; import '../services/yt_search_service.dart'; @@ -25,6 +26,31 @@ class YoutubeSearchScreen extends StatefulWidget { class _YoutubeSearchScreenState extends State { final _query = TextEditingController(); + /// Server-User (siehe _YouTubeBereich in downloads_screen.dart) brauchen + /// hier keinen Anmelde-Hinweis — die Anmeldung passiert im Hintergrund. + bool _serverUser = false; + + /// Die automatische Anmeldung ist fehlgeschlagen — dann bleibt der + /// Hinweis auf die manuelle Anmeldung (siehe Download-Tab) sichtbar. + bool _autoLoginFehlgeschlagen = false; + + @override + void initState() { + super.initState(); + _pruefeServerUser(); + } + + Future _pruefeServerUser() async { + final nav = context.read().navidrome; + final auth = context.read(); + final ergebnis = await pruefeServerUser(nav, auth); + if (!mounted) return; + setState(() { + _serverUser = ergebnis.serverUser; + _autoLoginFehlgeschlagen = ergebnis.autoLoginFehlgeschlagen; + }); + } + @override void dispose() { _query.dispose(); @@ -42,6 +68,8 @@ class _YoutubeSearchScreenState extends State { Widget build(BuildContext context) { final auth = context.watch(); final suche = context.watch(); + final zugriffOk = + auth.istAngemeldet || (_serverUser && !_autoLoginFehlgeschlagen); return SafeArea( child: Column( @@ -69,7 +97,7 @@ class _YoutubeSearchScreenState extends State { ], ), ), - if (!auth.istAngemeldet) + if (!zugriffOk) const Padding( padding: EdgeInsets.symmetric(horizontal: 16, vertical: 4), child: Text( diff --git a/lib/services/baka_auth.dart b/lib/services/baka_auth.dart index fada530..ecfa178 100644 --- a/lib/services/baka_auth.dart +++ b/lib/services/baka_auth.dart @@ -4,6 +4,8 @@ import 'package:flutter/foundation.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:http/http.dart' as http; +import 'navidrome_service.dart'; + /// Ablage für den Anmelde-Token. Als Schnittstelle, damit Tests ohne die /// Plattform-Kanäle von FlutterSecureStorage auskommen. abstract class TokenSpeicher { @@ -100,6 +102,15 @@ class BakaAuth extends ChangeNotifier { 'Anmeldung fehlgeschlagen (${antwort.statusCode})'; } + /// Meldet automatisch an, falls noch nicht angemeldet — für Server-User + /// (Dustin, Baka, Tinker), die keine eigene Baka-Anmeldung mehr sehen + /// sollen: der Proxy nutzt seine YouTube-Cookies für jeden + /// authentifizierten Aufruf ohnehin automatisch. + Future autoAnmelden(String benutzer, String passwort) async { + if (istAngemeldet || benutzer.isEmpty || passwort.isEmpty) return; + await anmelden(benutzer, passwort); + } + Future abmelden() async { _token = null; _benutzer = ''; @@ -108,3 +119,20 @@ class BakaAuth extends ChangeNotifier { notifyListeners(); } } + +/// Prüft, ob ein Server-User verbunden ist, und meldet ihn dann automatisch +/// beim Baka-Konto an — geteilte Logik für den YouTube-Bereich +/// (downloads_screen.dart) und die YT-Suche (youtube_search_screen.dart). +/// Gäste (kein Server-Account) bleiben unangetastet. Schlägt die +/// Hintergrund-Anmeldung fehl (falsche/fehlende Zugangsdaten, Server nicht +/// erreichbar), meldet [autoLoginFehlgeschlagen] das zurück — sonst bliebe +/// so ein User ohne jeden Weg mehr zur manuellen Baka-Anmeldung. +Future<({bool serverUser, bool autoLoginFehlgeschlagen})> pruefeServerUser( + NavidromeService navidrome, BakaAuth auth) async { + await navidrome.ladeGespeicherteZugangsdaten(); + if (!navidrome.istVerbunden) { + return (serverUser: false, autoLoginFehlgeschlagen: false); + } + await auth.autoAnmelden(navidrome.benutzer, navidrome.passwort); + return (serverUser: true, autoLoginFehlgeschlagen: !auth.istAngemeldet); +} diff --git a/lib/services/navidrome_service.dart b/lib/services/navidrome_service.dart index b1573e1..a55bafe 100644 --- a/lib/services/navidrome_service.dart +++ b/lib/services/navidrome_service.dart @@ -140,6 +140,12 @@ class NavidromeService { bool get istVerbunden => _serverUrl.isNotEmpty && _user.isNotEmpty; + /// Für die automatische Baka-Anmeldung verbundener Server-User (siehe + /// [BakaAuth.autoAnmelden]) — Navidrome- und Baka-Konto teilen sich die + /// Zugangsdaten. + String get benutzer => _user; + String get passwort => _password; + /// Dekodiert eine Subsonic-JSON-Antwort und wirft [NavidromeException], /// wenn der Server `status:"failed"` meldet (z. B. falsches Passwort) — /// solche Antworten kommen mit HTTP 200 und dürfen nicht als „leer" gelten. diff --git a/test/downloads/online_screen_test.dart b/test/downloads/online_screen_test.dart index 501acc4..1d21203 100644 --- a/test/downloads/online_screen_test.dart +++ b/test/downloads/online_screen_test.dart @@ -27,7 +27,9 @@ class _MemorySpeicher implements TokenSpeicher { } Widget _wrap(BakaAuth auth, - {required MeloDb db, required CategoryService categories}) { + {required MeloDb db, + required CategoryService categories, + NavidromeService? navidrome}) { return MultiProvider( providers: [ Provider.value(value: db), @@ -45,7 +47,8 @@ Widget _wrap(BakaAuth auth, // Nur fürs Badge im Unterreiter-Kopf gebraucht (dienst.laeuft) — der // Download-Tab beobachtet ihn app-weit, unabhängig vom Unterreiter. ChangeNotifierProvider( - create: (_) => DownloadService(db: db, navidrome: NavidromeService()), + create: (_) => DownloadService( + db: db, navidrome: navidrome ?? NavidromeService()), ), ], child: const MaterialApp(home: Scaffold(body: DownloadsScreen())), @@ -79,6 +82,14 @@ void main() { }); } + // _YouTubeBereich prüft beim Öffnen per NavidromeService. + // ladeGespeicherteZugangsdaten(), ob ein Server-User verbunden ist — ohne + // diese Antwort bleibt der Aufruf im Test unbeantwortet hängen. + messenger.setMockMethodCallHandler( + const MethodChannel('plugins.it_nomads.com/flutter_secure_storage'), + (call) async => null, + ); + late MeloDb db; late CategoryService categories; @@ -196,4 +207,67 @@ void main() { expect(find.byType(DropdownButtonFormField), findsNothing); }); + + testWidgets( + 'Server-User sieht das Adressfeld sofort, ohne Baka-Anmeldung', + (tester) async { + final auth = BakaAuth( + client: MockClient( + (_) async => http.Response(jsonEncode({'token': 'jwt'}), 200)), + speicher: _MemorySpeicher(), + ); + final nav = NavidromeService() + ..setCredentials('https://musik.baka-net.de', 'Baka', 'geheim'); + + await tester.pumpWidget(_wrap(auth, + db: db, categories: categories, navidrome: nav)); + await tester.pumpAndSettle(); + + expect(find.text('Beim Baka-Konto anmelden'), findsNothing); + expect(find.text('Angemeldet als Baka'), findsNothing); + expect(find.text('YouTube-Adresse'), findsOneWidget); + }); + + testWidgets( + 'Server-User bekommt bei fehlgeschlagener Auto-Anmeldung einen ' + 'Rückfallweg zur manuellen Anmeldung', (tester) async { + final auth = BakaAuth( + client: MockClient((_) async => http.Response('', 500)), + speicher: _MemorySpeicher()); + final nav = NavidromeService() + ..setCredentials('https://musik.baka-net.de', 'Baka', 'falsch'); + + await tester.pumpWidget(_wrap(auth, + db: db, categories: categories, navidrome: nav)); + await tester.pumpAndSettle(); + + expect(find.text('Beim Baka-Konto anmelden'), findsOneWidget); + expect(find.text('YouTube-Adresse'), findsNothing); + expect( + find.textContaining('automatische Anmeldung hat nicht geklappt'), + findsOneWidget); + }); + + testWidgets( + 'Server-User meldet sich im Hintergrund mit denselben Zugangsdaten ' + 'beim Baka-Konto an', (tester) async { + String? gesendeterBenutzer; + final auth = BakaAuth( + client: MockClient((request) async { + final body = jsonDecode(request.body) as Map; + gesendeterBenutzer = body['username'] as String?; + return http.Response(jsonEncode({'token': 'jwt'}), 200); + }), + speicher: _MemorySpeicher(), + ); + final nav = NavidromeService() + ..setCredentials('https://musik.baka-net.de', 'Tinker', 'geheim2'); + + await tester.pumpWidget(_wrap(auth, + db: db, categories: categories, navidrome: nav)); + await tester.pumpAndSettle(); + + expect(gesendeterBenutzer, 'Tinker'); + expect(auth.istAngemeldet, isTrue); + }); } diff --git a/test/downloads/youtube_search_screen_test.dart b/test/downloads/youtube_search_screen_test.dart index aeb5826..1123b9d 100644 --- a/test/downloads/youtube_search_screen_test.dart +++ b/test/downloads/youtube_search_screen_test.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'package:drift/native.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:http/http.dart' as http; import 'package:http/testing.dart'; @@ -9,6 +10,8 @@ import 'package:melo/downloads/youtube_search_screen.dart'; import 'package:melo/library/database.dart'; import 'package:melo/library/library_service.dart'; import 'package:melo/services/baka_auth.dart'; +import 'package:melo/services/download_service.dart'; +import 'package:melo/services/navidrome_service.dart'; import 'package:melo/services/yt_download_service.dart'; import 'package:melo/services/yt_search_service.dart'; import 'package:provider/provider.dart'; @@ -39,6 +42,7 @@ Widget _wrap({ required BakaAuth auth, required YtSearchService search, required YtDownloadService download, + NavidromeService? navidrome, }) { return MultiProvider( providers: [ @@ -47,6 +51,13 @@ Widget _wrap({ ChangeNotifierProvider.value(value: auth), ChangeNotifierProvider.value(value: search), ChangeNotifierProvider.value(value: download), + // Nur für die Server-User-Prüfung gebraucht (siehe + // _pruefeServerUser) — ohne gespeicherte Zugangsdaten bleibt sie ein + // Gast. + ChangeNotifierProvider( + create: (_) => DownloadService( + db: db, navidrome: navidrome ?? NavidromeService()), + ), ], child: const MaterialApp(home: Scaffold(body: YoutubeSearchScreen())), ); @@ -56,6 +67,15 @@ void main() { TestWidgetsFlutterBinding.ensureInitialized(); setUp(() => SharedPreferences.setMockInitialValues({})); + // _pruefeServerUser prüft per NavidromeService.ladeGespeicherteZugangs- + // daten(), ob ein Server-User verbunden ist — ohne diese Antwort bleibt + // der Aufruf im Test unbeantwortet hängen. + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler( + const MethodChannel('plugins.it_nomads.com/flutter_secure_storage'), + (call) async => null, + ); + testWidgets('ohne Anmeldung erscheint der Hinweis auf die Baka-Anmeldung', (tester) async { final db = MeloDb(NativeDatabase.memory()); @@ -75,6 +95,50 @@ void main() { await db.close(); }); + testWidgets('Server-User sieht keinen Anmelde-Hinweis', (tester) async { + final db = MeloDb(NativeDatabase.memory()); + final auth = BakaAuth( + client: MockClient( + (_) async => http.Response(jsonEncode({'token': 'jwt'}), 200)), + speicher: _MemorySpeicher(), + ); + final search = YtSearchService(auth: auth); + final download = YtDownloadService(auth: auth); + final nav = NavidromeService() + ..setCredentials('https://musik.baka-net.de', 'Dustin', 'geheim'); + + await tester.pumpWidget(_wrap( + db: db, auth: auth, search: search, download: download, + navidrome: nav)); + await tester.pumpAndSettle(); + + expect(find.textContaining('Baka-Anmeldung'), findsNothing); + + await db.close(); + }); + + testWidgets( + 'Server-User bekommt bei fehlgeschlagener Auto-Anmeldung wieder ' + 'den Anmelde-Hinweis', (tester) async { + final db = MeloDb(NativeDatabase.memory()); + final auth = BakaAuth( + client: MockClient((_) async => http.Response('', 500)), + speicher: _MemorySpeicher()); + final search = YtSearchService(auth: auth); + final download = YtDownloadService(auth: auth); + final nav = NavidromeService() + ..setCredentials('https://musik.baka-net.de', 'Dustin', 'falsch'); + + await tester.pumpWidget(_wrap( + db: db, auth: auth, search: search, download: download, + navidrome: nav)); + await tester.pumpAndSettle(); + + expect(find.textContaining('Baka-Anmeldung'), findsOneWidget); + + await db.close(); + }); + testWidgets('Suche zeigt Titel und Dauer der Treffer', (tester) async { final db = MeloDb(NativeDatabase.memory()); final auth = await _angemeldeteAuth(); diff --git a/test/services/baka_auth_test.dart b/test/services/baka_auth_test.dart index e0c8d63..6be2650 100644 --- a/test/services/baka_auth_test.dart +++ b/test/services/baka_auth_test.dart @@ -85,6 +85,50 @@ void main() { 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 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(