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 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); } Future _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.value(value: db), ChangeNotifierProvider.value(value: LibraryService(db)), 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())), ); } 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. }