Merge branch 'feature/youtube-gast-zugang'
# Conflicts: # CHANGELOG.md # test/downloads/online_screen_test.dart
This commit is contained in:
@@ -11,6 +11,7 @@ import 'package:melo/library/category_service.dart';
|
||||
import 'package:melo/library/database.dart';
|
||||
import 'package:melo/services/baka_auth.dart';
|
||||
import 'package:melo/services/download_service.dart';
|
||||
import 'package:melo/services/gast_zugang.dart';
|
||||
import 'package:melo/services/navidrome_service.dart';
|
||||
import 'package:melo/services/yt_download_service.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
@@ -29,7 +30,9 @@ class _MemorySpeicher implements TokenSpeicher {
|
||||
Widget _wrap(BakaAuth auth,
|
||||
{required MeloDb db,
|
||||
required CategoryService categories,
|
||||
NavidromeService? navidrome}) {
|
||||
NavidromeService? navidrome,
|
||||
GastZugang? gast}) {
|
||||
final gastZugang = gast ?? GastZugang();
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
Provider<MeloDb>.value(value: db),
|
||||
@@ -38,9 +41,11 @@ Widget _wrap(BakaAuth auth,
|
||||
// Aufräum-Timer von drift im Test-Rahmen hängen.
|
||||
ChangeNotifierProvider<CategoryService>.value(value: categories),
|
||||
ChangeNotifierProvider<BakaAuth>.value(value: auth),
|
||||
ChangeNotifierProvider<GastZugang>.value(value: gastZugang),
|
||||
ChangeNotifierProvider<YtDownloadService>(
|
||||
create: (_) => YtDownloadService(
|
||||
auth: auth,
|
||||
gast: gastZugang,
|
||||
client: MockClient((_) async => http.Response('', 500)),
|
||||
),
|
||||
),
|
||||
@@ -301,6 +306,21 @@ void main() {
|
||||
expect(auth.istAngemeldet, isTrue);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'Angemeldeter Cloud-Account sieht das Kontingent nach einem Download',
|
||||
(tester) async {
|
||||
final auth = await _angemeldet();
|
||||
|
||||
await tester.pumpWidget(_wrap(auth, db: db, categories: categories));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.textContaining('von 100 heute'), findsNothing);
|
||||
|
||||
auth.merkeVerbleibend(63);
|
||||
await tester.pump();
|
||||
|
||||
expect(find.text('Noch 63 von 100 heute'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'Server-User meldet sich im Hintergrund mit denselben Zugangsdaten '
|
||||
'beim Baka-Konto an', (tester) async {
|
||||
@@ -323,4 +343,91 @@ void main() {
|
||||
expect(gesendeterBenutzer, 'Tinker');
|
||||
expect(auth.istAngemeldet, isTrue);
|
||||
});
|
||||
|
||||
testWidgets('Gast sieht zwei Buttons: Anmelden und Als Gast fortfahren',
|
||||
(tester) async {
|
||||
final auth = BakaAuth(
|
||||
client: MockClient((_) async => http.Response('', 500)),
|
||||
speicher: _MemorySpeicher());
|
||||
|
||||
await tester.pumpWidget(_wrap(auth, db: db, categories: categories));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Beim Baka-Konto anmelden'), findsOneWidget);
|
||||
expect(
|
||||
find.text('Als Gast fortfahren (5 Downloads/Tag)'), findsOneWidget);
|
||||
expect(find.text('YouTube-Adresse'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'Als Gast fortfahren holt einen Token und schaltet das Adressfeld frei',
|
||||
(tester) async {
|
||||
final auth = BakaAuth(
|
||||
client: MockClient((_) async => http.Response('', 500)),
|
||||
speicher: _MemorySpeicher());
|
||||
final gast = GastZugang(
|
||||
client: MockClient((_) async =>
|
||||
http.Response(jsonEncode({'guest_token': 'g-1'}), 200)),
|
||||
speicher: _MemorySpeicher(),
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
_wrap(auth, db: db, categories: categories, gast: gast));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.text('Als Gast fortfahren (5 Downloads/Tag)'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('YouTube-Adresse'), findsOneWidget);
|
||||
expect(find.text('Als Gast unterwegs (5 Downloads/Tag)'), findsOneWidget);
|
||||
expect(find.text('YouTube-Cookies des Servers verwenden'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('Fehler beim Gast-Token wird angezeigt', (tester) async {
|
||||
final auth = BakaAuth(
|
||||
client: MockClient((_) async => http.Response('', 500)),
|
||||
speicher: _MemorySpeicher());
|
||||
final gast = GastZugang(
|
||||
client: MockClient((_) async => http.Response('', 500)),
|
||||
speicher: _MemorySpeicher(),
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
_wrap(auth, db: db, categories: categories, gast: gast));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.text('Als Gast fortfahren (5 Downloads/Tag)'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Gast-Zugang fehlgeschlagen (500)'), findsOneWidget);
|
||||
expect(find.text('YouTube-Adresse'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'Anmelden im Gast-Modus verwirft den Gast-Token und zeigt wieder die '
|
||||
'Wahl', (tester) async {
|
||||
final auth = BakaAuth(
|
||||
client: MockClient((_) async => http.Response('', 500)),
|
||||
speicher: _MemorySpeicher());
|
||||
final gast = GastZugang(
|
||||
client: MockClient((_) async =>
|
||||
http.Response(jsonEncode({'guest_token': 'g-1'}), 200)),
|
||||
speicher: _MemorySpeicher(),
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
_wrap(auth, db: db, categories: categories, gast: gast));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.text('Als Gast fortfahren (5 Downloads/Tag)'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('YouTube-Adresse'), findsOneWidget);
|
||||
|
||||
await tester.tap(find.text('Anmelden'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Beim Baka-Konto anmelden'), findsOneWidget);
|
||||
expect(find.text('YouTube-Adresse'), findsNothing);
|
||||
expect(gast.hatToken, isFalse);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ 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/gast_zugang.dart';
|
||||
import 'package:melo/services/navidrome_service.dart';
|
||||
import 'package:melo/services/yt_download_service.dart';
|
||||
import 'package:melo/services/yt_search_service.dart';
|
||||
@@ -43,12 +44,14 @@ Widget _wrap({
|
||||
required YtSearchService search,
|
||||
required YtDownloadService download,
|
||||
NavidromeService? navidrome,
|
||||
GastZugang? gast,
|
||||
}) {
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
Provider<MeloDb>.value(value: db),
|
||||
ChangeNotifierProvider<LibraryService>.value(value: LibraryService(db)),
|
||||
ChangeNotifierProvider<BakaAuth>.value(value: auth),
|
||||
ChangeNotifierProvider<GastZugang>.value(value: gast ?? GastZugang()),
|
||||
ChangeNotifierProvider<YtSearchService>.value(value: search),
|
||||
ChangeNotifierProvider<YtDownloadService>.value(value: download),
|
||||
// Nur für die Server-User-Prüfung gebraucht (siehe
|
||||
@@ -89,7 +92,7 @@ void main() {
|
||||
db: db, auth: auth, search: search, download: download));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.textContaining('Baka-Anmeldung'), findsOneWidget);
|
||||
expect(find.textContaining('Baka-Server'), findsOneWidget);
|
||||
expect(find.text('Nach Musikvideos suchen'), findsOneWidget);
|
||||
|
||||
await db.close();
|
||||
@@ -173,6 +176,26 @@ void main() {
|
||||
await db.close();
|
||||
});
|
||||
|
||||
testWidgets('Angemeldeter Cloud-Account sieht das Kontingent nach Download',
|
||||
(tester) async {
|
||||
final db = MeloDb(NativeDatabase.memory());
|
||||
final auth = await _angemeldeteAuth();
|
||||
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('von 100 heute'), findsNothing);
|
||||
|
||||
auth.merkeVerbleibend(98);
|
||||
await tester.pump();
|
||||
|
||||
expect(find.text('Noch 98 von 100 heute'), 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
|
||||
@@ -182,4 +205,86 @@ void main() {
|
||||
// _YouTubeBereich (downloads_screen.dart) keinen eigenen Widget-Test —
|
||||
// die Download-Logik selbst ist über yt_download_test.dart ausführlich
|
||||
// abgedeckt.
|
||||
|
||||
testWidgets('Gast sieht den Button "Als Gast fortfahren"', (tester) async {
|
||||
final db = MeloDb(NativeDatabase.memory());
|
||||
final auth = BakaAuth(
|
||||
client: MockClient((_) async => http.Response('', 500)),
|
||||
speicher: _MemorySpeicher());
|
||||
final gast = GastZugang(
|
||||
client: MockClient((_) async => http.Response('', 500)),
|
||||
speicher: _MemorySpeicher(),
|
||||
);
|
||||
final search = YtSearchService(auth: auth, gast: gast);
|
||||
final download = YtDownloadService(auth: auth, gast: gast);
|
||||
|
||||
await tester.pumpWidget(_wrap(
|
||||
db: db, auth: auth, search: search, download: download, gast: gast));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(
|
||||
find.text('Als Gast fortfahren (5 Downloads/Tag)'), findsOneWidget);
|
||||
|
||||
await db.close();
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'Als Gast fortfahren blendet den Baka-Hinweis aus und zeigt den Zähler',
|
||||
(tester) async {
|
||||
final db = MeloDb(NativeDatabase.memory());
|
||||
final auth = BakaAuth(
|
||||
client: MockClient((_) async => http.Response('', 500)),
|
||||
speicher: _MemorySpeicher());
|
||||
final gast = GastZugang(
|
||||
client: MockClient((_) async =>
|
||||
http.Response(jsonEncode({'guest_token': 'g-1'}), 200)),
|
||||
speicher: _MemorySpeicher(),
|
||||
);
|
||||
final search = YtSearchService(auth: auth, gast: gast);
|
||||
final download = YtDownloadService(auth: auth, gast: gast);
|
||||
|
||||
await tester.pumpWidget(_wrap(
|
||||
db: db, auth: auth, search: search, download: download, gast: gast));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.text('Als Gast fortfahren (5 Downloads/Tag)'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.textContaining('Baka-Anmeldung'), findsNothing);
|
||||
expect(find.text('Als Gast unterwegs (5 Downloads/Tag)'), findsOneWidget);
|
||||
|
||||
await db.close();
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'Anmelden im Gast-Modus verwirft den Gast-Token und zeigt wieder den '
|
||||
'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 gast = GastZugang(
|
||||
client: MockClient((_) async =>
|
||||
http.Response(jsonEncode({'guest_token': 'g-1'}), 200)),
|
||||
speicher: _MemorySpeicher(),
|
||||
);
|
||||
|
||||
await tester.pumpWidget(_wrap(
|
||||
db: db, auth: auth, search: search, download: download, gast: gast));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.text('Als Gast fortfahren (5 Downloads/Tag)'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('Als Gast unterwegs (5 Downloads/Tag)'), findsOneWidget);
|
||||
|
||||
await tester.tap(find.text('Anmelden'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.textContaining('Baka-Server'), findsOneWidget);
|
||||
expect(gast.hatToken, isFalse);
|
||||
|
||||
await db.close();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import 'package:melo/player/audio_handler.dart';
|
||||
import 'package:melo/player/player_expansion_controller.dart';
|
||||
import 'package:melo/services/baka_auth.dart';
|
||||
import 'package:melo/services/download_service.dart';
|
||||
import 'package:melo/services/gast_zugang.dart';
|
||||
import 'package:melo/services/melo_cloud_service.dart';
|
||||
import 'package:melo/services/navidrome_service.dart';
|
||||
import 'package:melo/services/offline_mode.dart';
|
||||
@@ -52,6 +53,7 @@ void main() {
|
||||
late YtSearchService ytSearch;
|
||||
late DownloadService downloads;
|
||||
late SyncService sync;
|
||||
late GastZugang gast;
|
||||
MeloAudioHandler? handler;
|
||||
PlayerExpansionController? expansion;
|
||||
|
||||
@@ -64,8 +66,9 @@ void main() {
|
||||
auth = BakaAuth(speicher: _MemorySpeicher());
|
||||
einstellungen = AppSettings();
|
||||
offline = OfflineMode();
|
||||
ytDownload = YtDownloadService(auth: auth);
|
||||
ytSearch = YtSearchService(auth: auth);
|
||||
gast = GastZugang();
|
||||
ytDownload = YtDownloadService(auth: auth, gast: gast);
|
||||
ytSearch = YtSearchService(auth: auth, gast: gast);
|
||||
downloads = DownloadService(db: db, navidrome: NavidromeService());
|
||||
sync = SyncService(db: db, cloud: MeloCloudService(auth: auth));
|
||||
});
|
||||
@@ -101,6 +104,7 @@ void main() {
|
||||
ChangeNotifierProvider<AppSettings>.value(value: einstellungen),
|
||||
ChangeNotifierProvider<OfflineMode>.value(value: offline),
|
||||
ChangeNotifierProvider<BakaAuth>.value(value: auth),
|
||||
ChangeNotifierProvider<GastZugang>.value(value: gast),
|
||||
ChangeNotifierProvider<YtDownloadService>.value(value: ytDownload),
|
||||
ChangeNotifierProvider<YtSearchService>.value(value: ytSearch),
|
||||
ChangeNotifierProvider<DownloadService>.value(value: downloads),
|
||||
|
||||
@@ -10,6 +10,7 @@ import 'package:melo/player/audio_handler.dart';
|
||||
import 'package:melo/player/player_expansion_controller.dart';
|
||||
import 'package:melo/services/baka_auth.dart';
|
||||
import 'package:melo/services/download_service.dart';
|
||||
import 'package:melo/services/gast_zugang.dart';
|
||||
import 'package:melo/services/melo_cloud_service.dart';
|
||||
import 'package:melo/services/navidrome_service.dart';
|
||||
import 'package:melo/services/offline_mode.dart';
|
||||
@@ -52,6 +53,7 @@ void main() {
|
||||
ChangeNotifierProvider<AppSettings>(create: (_) => AppSettings()),
|
||||
ChangeNotifierProvider<OfflineMode>(create: (_) => OfflineMode()),
|
||||
ChangeNotifierProvider<BakaAuth>(create: (_) => auth),
|
||||
ChangeNotifierProvider<GastZugang>(create: (_) => GastZugang()),
|
||||
ChangeNotifierProvider<YtDownloadService>(
|
||||
create: (_) => YtDownloadService(auth: auth)),
|
||||
ChangeNotifierProvider<YtSearchService>(
|
||||
@@ -97,6 +99,7 @@ void main() {
|
||||
ChangeNotifierProvider<AppSettings>(create: (_) => AppSettings()),
|
||||
ChangeNotifierProvider<OfflineMode>(create: (_) => OfflineMode()),
|
||||
ChangeNotifierProvider<BakaAuth>(create: (_) => auth),
|
||||
ChangeNotifierProvider<GastZugang>(create: (_) => GastZugang()),
|
||||
ChangeNotifierProvider<YtDownloadService>(
|
||||
create: (_) => YtDownloadService(auth: auth)),
|
||||
ChangeNotifierProvider<YtSearchService>(
|
||||
|
||||
@@ -11,6 +11,7 @@ import 'package:melo/player/mini_player.dart';
|
||||
import 'package:melo/player/player_expansion_controller.dart';
|
||||
import 'package:melo/services/baka_auth.dart';
|
||||
import 'package:melo/services/download_service.dart';
|
||||
import 'package:melo/services/gast_zugang.dart';
|
||||
import 'package:melo/services/melo_cloud_service.dart';
|
||||
import 'package:melo/services/navidrome_service.dart';
|
||||
import 'package:melo/services/offline_mode.dart';
|
||||
@@ -56,6 +57,7 @@ void main() {
|
||||
ChangeNotifierProvider<AppSettings>(create: (_) => AppSettings()),
|
||||
ChangeNotifierProvider<OfflineMode>(create: (_) => OfflineMode()),
|
||||
ChangeNotifierProvider<BakaAuth>(create: (_) => auth),
|
||||
ChangeNotifierProvider<GastZugang>(create: (_) => GastZugang()),
|
||||
ChangeNotifierProvider<YtDownloadService>(
|
||||
create: (_) => YtDownloadService(auth: auth)),
|
||||
ChangeNotifierProvider<YtSearchService>(
|
||||
@@ -125,6 +127,7 @@ void main() {
|
||||
ChangeNotifierProvider<AppSettings>(create: (_) => AppSettings()),
|
||||
ChangeNotifierProvider<OfflineMode>(create: (_) => OfflineMode()),
|
||||
ChangeNotifierProvider<BakaAuth>(create: (_) => auth),
|
||||
ChangeNotifierProvider<GastZugang>(create: (_) => GastZugang()),
|
||||
ChangeNotifierProvider<YtDownloadService>(
|
||||
create: (_) => YtDownloadService(auth: auth)),
|
||||
ChangeNotifierProvider<YtSearchService>(
|
||||
|
||||
@@ -14,6 +14,7 @@ import 'package:melo/player/player_expansion_controller.dart';
|
||||
import 'package:melo/playlists/playlist_detail_screen.dart';
|
||||
import 'package:melo/services/baka_auth.dart';
|
||||
import 'package:melo/services/download_service.dart';
|
||||
import 'package:melo/services/gast_zugang.dart';
|
||||
import 'package:melo/services/melo_cloud_service.dart';
|
||||
import 'package:melo/services/navidrome_service.dart';
|
||||
import 'package:melo/services/offline_mode.dart';
|
||||
@@ -98,6 +99,7 @@ void main() {
|
||||
ChangeNotifierProvider<AppSettings>(create: (_) => AppSettings()),
|
||||
ChangeNotifierProvider<OfflineMode>(create: (_) => OfflineMode()),
|
||||
ChangeNotifierProvider<BakaAuth>(create: (_) => auth),
|
||||
ChangeNotifierProvider<GastZugang>(create: (_) => GastZugang()),
|
||||
ChangeNotifierProvider<YtDownloadService>(
|
||||
create: (_) => YtDownloadService(auth: auth)),
|
||||
ChangeNotifierProvider<YtSearchService>(
|
||||
|
||||
@@ -167,6 +167,34 @@ void main() {
|
||||
expect(auth.istAngemeldet, isFalse);
|
||||
});
|
||||
|
||||
test('merkeVerbleibend übernimmt den Kontingent-Stand und meldet Listener',
|
||||
() async {
|
||||
final auth = BakaAuth(
|
||||
client: MockClient((_) async => http.Response('', 500)),
|
||||
speicher: _MemorySpeicher());
|
||||
var benachrichtigt = 0;
|
||||
auth.addListener(() => benachrichtigt++);
|
||||
|
||||
auth.merkeVerbleibend(37);
|
||||
|
||||
expect(auth.verbleibend, 37);
|
||||
expect(benachrichtigt, 1);
|
||||
});
|
||||
|
||||
test('Abmelden setzt auch den Kontingent-Stand zurück', () async {
|
||||
final speicher = _MemorySpeicher();
|
||||
final auth = BakaAuth(
|
||||
client: MockClient(
|
||||
(_) async => http.Response(jsonEncode({'token': 'jwt-abc'}), 200)),
|
||||
speicher: speicher);
|
||||
await auth.anmelden('Baka', 'geheim');
|
||||
auth.merkeVerbleibend(42);
|
||||
|
||||
await auth.abmelden();
|
||||
|
||||
expect(auth.verbleibend, isNull);
|
||||
});
|
||||
|
||||
test('Abmelden löscht Token und Benutzer aus dem Speicher', () async {
|
||||
final speicher = _MemorySpeicher();
|
||||
final auth = BakaAuth(
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
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';
|
||||
import 'package:melo/services/gast_zugang.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);
|
||||
}
|
||||
|
||||
void main() {
|
||||
test('Ohne Token ist gastHeader leer', () {
|
||||
final gast = GastZugang(
|
||||
client: MockClient((_) async => http.Response('', 500)),
|
||||
speicher: _MemorySpeicher(),
|
||||
);
|
||||
|
||||
expect(gast.hatToken, isFalse);
|
||||
expect(gast.gastHeader, isEmpty);
|
||||
});
|
||||
|
||||
test('holeToken() speichert den Token bei Erfolg', () async {
|
||||
final speicher = _MemorySpeicher();
|
||||
final gast = GastZugang(
|
||||
client: MockClient(
|
||||
(_) async => http.Response(jsonEncode({'guest_token': 'g-1'}), 200)),
|
||||
speicher: speicher,
|
||||
);
|
||||
|
||||
final fehler = await gast.holeToken();
|
||||
|
||||
expect(fehler, isNull);
|
||||
expect(gast.hatToken, isTrue);
|
||||
expect(gast.gastHeader, {'X-Guest-Token': 'g-1'});
|
||||
expect(speicher.werte['guest_token'], 'g-1');
|
||||
});
|
||||
|
||||
test('holeToken() meldet einen Fehler bei einer toten Verbindung',
|
||||
() async {
|
||||
final gast = GastZugang(
|
||||
client: MockClient((_) async => throw Exception('down')),
|
||||
speicher: _MemorySpeicher(),
|
||||
);
|
||||
|
||||
final fehler = await gast.holeToken();
|
||||
|
||||
expect(fehler, 'Proxy nicht erreichbar');
|
||||
expect(gast.hatToken, isFalse);
|
||||
});
|
||||
|
||||
test('holeToken() meldet einen Fehler bei einem Server-Fehlerstatus',
|
||||
() async {
|
||||
final gast = GastZugang(
|
||||
client: MockClient((_) async => http.Response('', 500)),
|
||||
speicher: _MemorySpeicher(),
|
||||
);
|
||||
|
||||
final fehler = await gast.holeToken();
|
||||
|
||||
expect(fehler, 'Gast-Zugang fehlgeschlagen (500)');
|
||||
expect(gast.hatToken, isFalse);
|
||||
});
|
||||
|
||||
test('holeToken() meldet einen Fehler bei unlesbarer Antwort', () async {
|
||||
final gast = GastZugang(
|
||||
client: MockClient((_) async => http.Response('nicht json', 200)),
|
||||
speicher: _MemorySpeicher(),
|
||||
);
|
||||
|
||||
final fehler = await gast.holeToken();
|
||||
|
||||
expect(fehler, 'Proxy antwortet unverständlich (200)');
|
||||
expect(gast.hatToken, isFalse);
|
||||
});
|
||||
|
||||
test('holeToken() meldet einen Fehler ohne guest_token-Feld', () async {
|
||||
final gast = GastZugang(
|
||||
client: MockClient((_) async => http.Response(jsonEncode({}), 200)),
|
||||
speicher: _MemorySpeicher(),
|
||||
);
|
||||
|
||||
final fehler = await gast.holeToken();
|
||||
|
||||
expect(fehler, 'Proxy hat keinen Gast-Token geliefert');
|
||||
expect(gast.hatToken, isFalse);
|
||||
});
|
||||
|
||||
test('laden() stellt einen gespeicherten Gast-Token wieder her', () async {
|
||||
final speicher = _MemorySpeicher();
|
||||
speicher.werte['guest_token'] = 'g-alt';
|
||||
final gast = GastZugang(
|
||||
client: MockClient((_) async => http.Response('', 500)),
|
||||
speicher: speicher,
|
||||
);
|
||||
|
||||
await gast.laden();
|
||||
|
||||
expect(gast.hatToken, isTrue);
|
||||
expect(gast.gastHeader, {'X-Guest-Token': 'g-alt'});
|
||||
});
|
||||
|
||||
test('merkeVerbleibend übernimmt den zuletzt gemeldeten Stand', () {
|
||||
final gast = GastZugang(
|
||||
client: MockClient((_) async => http.Response('', 500)),
|
||||
speicher: _MemorySpeicher(),
|
||||
);
|
||||
|
||||
gast.merkeVerbleibend(3);
|
||||
|
||||
expect(gast.verbleibend, 3);
|
||||
});
|
||||
}
|
||||
@@ -6,6 +6,7 @@ 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';
|
||||
import 'package:melo/services/gast_zugang.dart';
|
||||
import 'package:melo/services/yt_download_service.dart';
|
||||
|
||||
class _MemorySpeicher implements TokenSpeicher {
|
||||
@@ -284,4 +285,171 @@ void main() {
|
||||
|
||||
expect((jsonDecode(auftrag!) as Map)['cookies'], isFalse);
|
||||
});
|
||||
|
||||
group('Gast-Zugang', () {
|
||||
test('Mit Gast-Token statt Anmeldung wird der Proxy trotzdem gefragt',
|
||||
() async {
|
||||
final speicher = _MemorySpeicher();
|
||||
speicher.werte['guest_token'] = 'gast-xyz';
|
||||
final gast = GastZugang(
|
||||
client: MockClient((_) async => http.Response('', 500)),
|
||||
speicher: speicher,
|
||||
);
|
||||
await gast.laden();
|
||||
|
||||
final dienst = YtDownloadService(
|
||||
auth: BakaAuth(
|
||||
client: MockClient((_) async => http.Response('', 500)),
|
||||
speicher: _MemorySpeicher()),
|
||||
gast: gast,
|
||||
client: MockClient((req) async {
|
||||
if (req.url.path == '/api/yt-dl') {
|
||||
expect(req.headers['X-Guest-Token'], 'gast-xyz');
|
||||
expect(req.headers.containsKey('Authorization'), isFalse);
|
||||
return http.Response(
|
||||
jsonEncode({
|
||||
'titel': 'Gast-Lied',
|
||||
'dauer': 30,
|
||||
'mp3_url': '/api/dl/g.mp3',
|
||||
'guest_remaining': 4,
|
||||
}),
|
||||
200);
|
||||
}
|
||||
return http.Response.bytes([1], 200);
|
||||
}),
|
||||
);
|
||||
|
||||
final ergebnis = await dienst.herunterladen('https://youtu.be/abc',
|
||||
zielOrdner: ziel.path);
|
||||
|
||||
expect(ergebnis, isNotNull);
|
||||
expect(gast.verbleibend, 4);
|
||||
});
|
||||
|
||||
test('Ohne Anmeldung UND ohne Gast-Token wird gar nicht erst gefragt',
|
||||
() async {
|
||||
var aufrufe = 0;
|
||||
final dienst = YtDownloadService(
|
||||
auth: BakaAuth(
|
||||
client: MockClient((_) async => http.Response('', 500)),
|
||||
speicher: _MemorySpeicher()),
|
||||
client: MockClient((_) async {
|
||||
aufrufe++;
|
||||
return http.Response('', 200);
|
||||
}),
|
||||
);
|
||||
|
||||
final ergebnis = await dienst.herunterladen('https://youtu.be/abc',
|
||||
zielOrdner: ziel.path);
|
||||
|
||||
expect(ergebnis, isNull);
|
||||
expect(dienst.fehler, 'Bitte zuerst beim Baka-Konto anmelden');
|
||||
expect(aufrufe, 0);
|
||||
});
|
||||
|
||||
test('401 bei Gast-Zugang holt automatisch einen neuen Gast-Token',
|
||||
() async {
|
||||
var tokenAufrufe = 0;
|
||||
final speicher = _MemorySpeicher();
|
||||
speicher.werte['guest_token'] = 'alter-token';
|
||||
final gast = GastZugang(
|
||||
client: MockClient((_) async {
|
||||
tokenAufrufe++;
|
||||
return http.Response(
|
||||
jsonEncode({'guest_token': 'neuer-token'}), 200);
|
||||
}),
|
||||
speicher: speicher,
|
||||
);
|
||||
await gast.laden();
|
||||
|
||||
final dienst = YtDownloadService(
|
||||
auth: BakaAuth(
|
||||
client: MockClient((_) async => http.Response('', 500)),
|
||||
speicher: _MemorySpeicher()),
|
||||
gast: gast,
|
||||
client: MockClient((_) async => http.Response('Unauthorized', 401)),
|
||||
);
|
||||
|
||||
final ergebnis = await dienst.herunterladen('https://youtu.be/abc',
|
||||
zielOrdner: ziel.path);
|
||||
// Das Token-Nachholen läuft unawaited im Hintergrund.
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
|
||||
expect(ergebnis, isNull);
|
||||
expect(dienst.fehler, 'Gast-Zugang abgelaufen — bitte erneut versuchen');
|
||||
expect(tokenAufrufe, 1);
|
||||
});
|
||||
});
|
||||
|
||||
group('Cloud-Kontingent', () {
|
||||
test('cloud_remaining aus der Erfolgsantwort landet in auth.verbleibend',
|
||||
() async {
|
||||
final auth = await _angemeldeteAuth();
|
||||
final dienst = YtDownloadService(
|
||||
auth: auth,
|
||||
client: MockClient((req) async {
|
||||
if (req.url.path == '/api/yt-dl') {
|
||||
return http.Response(
|
||||
jsonEncode({
|
||||
'titel': 'Cloud-Lied',
|
||||
'dauer': 30,
|
||||
'mp3_url': '/api/dl/c.mp3',
|
||||
'cloud_remaining': 99,
|
||||
}),
|
||||
200);
|
||||
}
|
||||
return http.Response.bytes([1], 200);
|
||||
}),
|
||||
);
|
||||
|
||||
final ergebnis = await dienst.herunterladen('https://youtu.be/abc',
|
||||
zielOrdner: ziel.path);
|
||||
|
||||
expect(ergebnis, isNotNull);
|
||||
expect(auth.verbleibend, 99);
|
||||
});
|
||||
|
||||
test(
|
||||
'Bei Gast-Zugriff wird cloud_remaining nicht gelesen, selbst wenn es '
|
||||
'im Body steht', () async {
|
||||
final speicher = _MemorySpeicher();
|
||||
speicher.werte['guest_token'] = 'gast-xyz';
|
||||
final gast = GastZugang(
|
||||
client: MockClient((_) async => http.Response('', 500)),
|
||||
speicher: speicher,
|
||||
);
|
||||
await gast.laden();
|
||||
final auth = BakaAuth(
|
||||
client: MockClient((_) async => http.Response('', 500)),
|
||||
speicher: _MemorySpeicher());
|
||||
|
||||
final dienst = YtDownloadService(
|
||||
auth: auth,
|
||||
gast: gast,
|
||||
client: MockClient((req) async {
|
||||
if (req.url.path == '/api/yt-dl') {
|
||||
return http.Response(
|
||||
jsonEncode({
|
||||
'titel': 'Gast-Lied',
|
||||
'dauer': 30,
|
||||
'mp3_url': '/api/dl/g.mp3',
|
||||
'guest_remaining': 4,
|
||||
// Sollte serverseitig nie gleichzeitig vorkommen, aber
|
||||
// falls doch: darf nicht in auth.verbleibend landen.
|
||||
'cloud_remaining': 77,
|
||||
}),
|
||||
200);
|
||||
}
|
||||
return http.Response.bytes([1], 200);
|
||||
}),
|
||||
);
|
||||
|
||||
final ergebnis = await dienst.herunterladen('https://youtu.be/abc',
|
||||
zielOrdner: ziel.path);
|
||||
|
||||
expect(ergebnis, isNotNull);
|
||||
expect(gast.verbleibend, 4);
|
||||
expect(auth.verbleibend, isNull);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ 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';
|
||||
import 'package:melo/services/gast_zugang.dart';
|
||||
import 'package:melo/services/yt_search_service.dart';
|
||||
|
||||
class _MemorySpeicher implements TokenSpeicher {
|
||||
@@ -200,5 +201,61 @@ void main() {
|
||||
await dienst.suchen('zweite suche');
|
||||
expect(dienst.treffer.single.title, 'Zweites');
|
||||
});
|
||||
|
||||
test('Mit Gast-Token statt Anmeldung wird trotzdem gesucht', () async {
|
||||
final speicher = _MemorySpeicher();
|
||||
speicher.werte['guest_token'] = 'gast-xyz';
|
||||
final gast = GastZugang(
|
||||
client: MockClient((_) async => http.Response('', 500)),
|
||||
speicher: speicher,
|
||||
);
|
||||
await gast.laden();
|
||||
|
||||
final dienst = YtSearchService(
|
||||
auth: BakaAuth(
|
||||
client: MockClient((_) async => http.Response('', 500)),
|
||||
speicher: _MemorySpeicher()),
|
||||
gast: gast,
|
||||
client: MockClient((req) async {
|
||||
expect(req.headers['X-Guest-Token'], 'gast-xyz');
|
||||
expect(req.headers.containsKey('Authorization'), isFalse);
|
||||
return http.Response('[]', 200);
|
||||
}),
|
||||
);
|
||||
|
||||
await dienst.suchen('roses');
|
||||
|
||||
expect(dienst.fehler, isNull);
|
||||
});
|
||||
|
||||
test('401 bei Gast-Zugang holt automatisch einen neuen Gast-Token',
|
||||
() async {
|
||||
var tokenAufrufe = 0;
|
||||
final speicher = _MemorySpeicher();
|
||||
speicher.werte['guest_token'] = 'alter-token';
|
||||
final gast = GastZugang(
|
||||
client: MockClient((_) async {
|
||||
tokenAufrufe++;
|
||||
return http.Response(
|
||||
jsonEncode({'guest_token': 'neuer-token'}), 200);
|
||||
}),
|
||||
speicher: speicher,
|
||||
);
|
||||
await gast.laden();
|
||||
|
||||
final dienst = YtSearchService(
|
||||
auth: BakaAuth(
|
||||
client: MockClient((_) async => http.Response('', 500)),
|
||||
speicher: _MemorySpeicher()),
|
||||
gast: gast,
|
||||
client: MockClient((_) async => http.Response('Unauthorized', 401)),
|
||||
);
|
||||
|
||||
await dienst.suchen('roses');
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
|
||||
expect(dienst.fehler, 'Gast-Zugang abgelaufen — bitte erneut versuchen');
|
||||
expect(tokenAufrufe, 1);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user