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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CcDiyJdVRqh1TtJk5JiabX
186 lines
6.6 KiB
Dart
186 lines
6.6 KiB
Dart
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';
|
|
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';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
|
|
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);
|
|
}
|
|
|
|
Future<BakaAuth> _angemeldeteAuth() async {
|
|
final auth = BakaAuth(
|
|
client: MockClient(
|
|
(_) async => http.Response(jsonEncode({'token': 'jwt-abc'}), 200)),
|
|
speicher: _MemorySpeicher(),
|
|
);
|
|
await auth.anmelden('Baka', 'geheim');
|
|
return auth;
|
|
}
|
|
|
|
Widget _wrap({
|
|
required MeloDb db,
|
|
required BakaAuth auth,
|
|
required YtSearchService search,
|
|
required YtDownloadService download,
|
|
NavidromeService? navidrome,
|
|
}) {
|
|
return MultiProvider(
|
|
providers: [
|
|
Provider<MeloDb>.value(value: db),
|
|
ChangeNotifierProvider<LibraryService>.value(value: LibraryService(db)),
|
|
ChangeNotifierProvider<BakaAuth>.value(value: auth),
|
|
ChangeNotifierProvider<YtSearchService>.value(value: search),
|
|
ChangeNotifierProvider<YtDownloadService>.value(value: download),
|
|
// Nur für die Server-User-Prüfung gebraucht (siehe
|
|
// _pruefeServerUser) — ohne gespeicherte Zugangsdaten bleibt sie ein
|
|
// Gast.
|
|
ChangeNotifierProvider<DownloadService>(
|
|
create: (_) => DownloadService(
|
|
db: db, navidrome: navidrome ?? NavidromeService()),
|
|
),
|
|
],
|
|
child: const MaterialApp(home: Scaffold(body: YoutubeSearchScreen())),
|
|
);
|
|
}
|
|
|
|
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());
|
|
final auth = BakaAuth(
|
|
client: MockClient((_) async => http.Response('', 500)),
|
|
speicher: _MemorySpeicher());
|
|
final search = YtSearchService(auth: auth);
|
|
final download = YtDownloadService(auth: auth);
|
|
|
|
await tester.pumpWidget(_wrap(
|
|
db: db, auth: auth, search: search, download: download));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.textContaining('Baka-Anmeldung'), findsOneWidget);
|
|
expect(find.text('Nach Musikvideos suchen'), findsOneWidget);
|
|
|
|
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();
|
|
final search = YtSearchService(
|
|
auth: auth,
|
|
client: MockClient((req) async {
|
|
expect(req.url.queryParameters['q'], 'roses');
|
|
return http.Response(
|
|
jsonEncode([
|
|
{
|
|
'title': 'Neoni - Roses',
|
|
'url': 'https://youtu.be/dQw4w9WgXcQ',
|
|
'duration': 187,
|
|
},
|
|
]),
|
|
200);
|
|
}),
|
|
);
|
|
final download = YtDownloadService(auth: auth);
|
|
|
|
await tester.pumpWidget(_wrap(
|
|
db: db, auth: auth, search: search, download: download));
|
|
|
|
await tester.enterText(find.byType(TextField), 'roses');
|
|
await tester.tap(find.text('Suchen'));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('Neoni - Roses'), findsOneWidget);
|
|
expect(find.text('3:07'), findsOneWidget);
|
|
expect(find.byIcon(Icons.download_for_offline_outlined), findsOneWidget);
|
|
|
|
await db.close();
|
|
});
|
|
|
|
// Kein Widget-Test für den Download-Knopf selbst: YtDownloadService.
|
|
// herunterladen() kombiniert Future.any mit .timeout() — dessen Timer
|
|
// bleibt unter Flutters fake_async-Testbindung immer als "pending" stehen
|
|
// (auch ganz ohne dieses Widget reproduzierbar) und lässt den Testlauf
|
|
// entweder hängen oder an der Pending-Timer-Prüfung scheitern. Aus
|
|
// demselben Grund hat auch der bestehende Download-Knopf in
|
|
// _YouTubeBereich (downloads_screen.dart) keinen eigenen Widget-Test —
|
|
// die Download-Logik selbst ist über yt_download_test.dart ausführlich
|
|
// abgedeckt.
|
|
}
|