Neuer GastZugang-Service für den anonymen YouTube-Gast-Zugang

Analog zu BakaAuth, aber ohne Passwort: holt/speichert einen
geräte-gebundenen Gast-Token vom neuen /api/guest-token-Endpunkt
(siehe docs/superpowers/specs/2026-08-26-youtube-guest-quota-design.md).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_$(uuidgen | cut -d- -f1)
This commit is contained in:
Hermes (Server)
2026-08-26 20:33:22 +02:00
co-authored by Claude Sonnet 5
parent eee94467a0
commit 2e14256ded
2 changed files with 202 additions and 0 deletions
+120
View File
@@ -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);
});
}