Die App signierte LiveKit-Tokens selbst – dafür steckte das SFU-Secret im App-Code (jeder mit der APK konnte sich Tokens für beliebige Räume und Identitäten ausstellen). Jetzt holt die App das Token von der bereits vorhandenen Server-Route /api/livekit-token (whoami-Prüfung, Identität = geprüfte Matrix-ID). Secret und JWT-Signieren sind aus dem Client entfernt. - Lesbare Fehlermeldungen (offline, 401, Serverfehler) statt stiller Fehler. - Gruppenanruf aus dem Chat-Kopf übergibt jetzt den Matrix-Client. - Tests: Unit-Test mit Mock-HTTP + echter LiveKit-Beitritt (Testkonto). - Offen: Handy-Anruftest; danach Secret-Rotation durch Bernd (Doku). Co-Authored-By: Claude Opus 5.5 <[email protected]>
88 lines
2.7 KiB
Dart
88 lines
2.7 KiB
Dart
import 'dart:convert';
|
|
import 'dart:io';
|
|
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:http/http.dart' as http;
|
|
import 'package:http/testing.dart';
|
|
import 'package:pyramid/core/livekit_token.dart';
|
|
|
|
/// Beitritts-Token kommt vom Server; das LiveKit-Secret steckt nicht mehr
|
|
/// in der App (docs/LIVEKIT_TOKEN_MIGRATION.md).
|
|
void main() {
|
|
test('schickt Raum + Matrix-Token und liefert Token und Adresse', () async {
|
|
late Map<String, dynamic> sent;
|
|
final client = MockClient((req) async {
|
|
expect(req.url.toString(), LiveKitTokenService.endpoint);
|
|
sent = jsonDecode(req.body) as Map<String, dynamic>;
|
|
return http.Response(
|
|
jsonEncode({'token': 'abc.def.ghi', 'url': 'wss://lk.example'}),
|
|
200,
|
|
);
|
|
});
|
|
final r = await LiveKitTokenService.fetch(
|
|
room: '!raum:server',
|
|
matrixToken: 'mt',
|
|
httpClient: client,
|
|
);
|
|
expect(sent, {'room': '!raum:server', 'matrix_token': 'mt'});
|
|
expect(r.token, 'abc.def.ghi');
|
|
expect(r.url, 'wss://lk.example');
|
|
});
|
|
|
|
test('ohne brauchbare Adresse: Standard-LiveKit', () async {
|
|
final client = MockClient(
|
|
(_) async => http.Response(jsonEncode({'token': 't'}), 200),
|
|
);
|
|
final r = await LiveKitTokenService.fetch(
|
|
room: 'r',
|
|
matrixToken: 'm',
|
|
httpClient: client,
|
|
);
|
|
expect(r.url, LiveKitTokenService.fallbackUrl);
|
|
});
|
|
|
|
Future<String> failureFor(MockClient client) async {
|
|
try {
|
|
await LiveKitTokenService.fetch(
|
|
room: 'r',
|
|
matrixToken: 'm',
|
|
httpClient: client,
|
|
);
|
|
} on LiveKitTokenException catch (e) {
|
|
return e.message;
|
|
}
|
|
fail('LiveKitTokenException erwartet');
|
|
}
|
|
|
|
test('Fehler werden verständlich gemeldet statt still geschluckt', () async {
|
|
expect(
|
|
await failureFor(MockClient((_) async => http.Response('{}', 401))),
|
|
contains('neu anmelden'),
|
|
);
|
|
expect(
|
|
await failureFor(MockClient((_) async => http.Response('kaputt', 500))),
|
|
contains('HTTP 500'),
|
|
);
|
|
expect(
|
|
await failureFor(MockClient((_) async => http.Response('{}', 200))),
|
|
contains('kein Zugangstoken'),
|
|
);
|
|
expect(
|
|
await failureFor(MockClient((_) async => http.Response('<html>', 200))),
|
|
contains('unlesbare'),
|
|
);
|
|
expect(
|
|
await failureFor(
|
|
MockClient((_) async => throw const SocketException('offline')),
|
|
),
|
|
contains('nicht erreichbar'),
|
|
);
|
|
});
|
|
|
|
test('App-Code enthält kein LiveKit-Secret und signiert nicht selbst', () {
|
|
final src = File('lib/core/livekit_token.dart').readAsStringSync();
|
|
expect(src, isNot(contains('apiSecret')));
|
|
expect(src, isNot(contains('jsonwebtoken')));
|
|
});
|
|
}
|