146 lines
4.8 KiB
Dart
146 lines
4.8 KiB
Dart
import 'dart:async';
|
|
import 'dart:convert';
|
|
import 'dart:io';
|
|
import 'dart:typed_data';
|
|
|
|
import 'package:web_socket_channel/web_socket_channel.dart';
|
|
import 'package:sync_relay_core/core.dart';
|
|
|
|
int _failures = 0;
|
|
|
|
void check(String name, bool ok, [String detail = '']) {
|
|
// ignore: avoid_print
|
|
print('${ok ? 'PASS' : 'FAIL'} $name${detail.isEmpty ? '' : ' ($detail)'}');
|
|
if (!ok) _failures++;
|
|
}
|
|
|
|
bool _bytesEqual(List<int> a, List<int> b) {
|
|
if (a.length != b.length) return false;
|
|
for (var i = 0; i < a.length; i++) {
|
|
if (a[i] != b[i]) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
Future<void> main(List<String> args) async {
|
|
var server = 'http://127.0.0.1:8080';
|
|
var secret = 'change-me';
|
|
var name = 'viewer';
|
|
String? expectFile;
|
|
for (var i = 0; i < args.length; i++) {
|
|
switch (args[i]) {
|
|
case '--server':
|
|
server = args[++i];
|
|
case '--secret':
|
|
secret = args[++i];
|
|
case '--name':
|
|
name = args[++i];
|
|
case '--file':
|
|
expectFile = args[++i];
|
|
}
|
|
}
|
|
final base = Uri.parse(server);
|
|
final wsUri = base.replace(scheme: base.scheme == 'https' ? 'wss' : 'ws', path: '/sync');
|
|
|
|
final ch = WebSocketChannel.connect(wsUri);
|
|
final firstCompleter = Completer<String>();
|
|
final sub = ch.stream.listen(
|
|
(m) {
|
|
if (!firstCompleter.isCompleted) firstCompleter.complete('$m');
|
|
},
|
|
onError: (Object e) {
|
|
if (!firstCompleter.isCompleted) firstCompleter.completeError(e);
|
|
},
|
|
);
|
|
ch.sink.add(jsonEncode({
|
|
't': 'hello',
|
|
'proto': RelayConstants.protoVersion,
|
|
'secret': secret,
|
|
'user': {'name': name},
|
|
'media': {'name': '', 'size': 0, 'duration': 0.0},
|
|
}));
|
|
late Map<String, Object?> welcome;
|
|
try {
|
|
final raw = await firstCompleter.future.timeout(const Duration(seconds: 10));
|
|
welcome = Map<String, Object?>.from(jsonDecode(raw) as Map);
|
|
} catch (e) {
|
|
check('hello -> welcome', false, '$e');
|
|
exitCode = 1;
|
|
return;
|
|
}
|
|
check('hello -> welcome', welcome['t'] == 'welcome', '${welcome['t']}');
|
|
final token = '${welcome['token']}';
|
|
check('token issued', token.length == 32, 'len=${token.length}');
|
|
final mediaUrl = welcome['mediaUrl'] as String?;
|
|
check('mediaUrl present', mediaUrl != null && mediaUrl.contains(token));
|
|
if (mediaUrl == null) {
|
|
exitCode = 1;
|
|
return;
|
|
}
|
|
final mediaUri = Uri.parse(mediaUrl);
|
|
|
|
Uint8List? expected;
|
|
if (expectFile != null) expected = await File(expectFile).readAsBytes();
|
|
final client = HttpClient();
|
|
try {
|
|
Future<(HttpClientResponse, List<int>)> get({String? range}) async {
|
|
final req = await client.getUrl(mediaUri);
|
|
if (range != null) req.headers.set('range', range);
|
|
final resp = await req.close();
|
|
final body = <int>[];
|
|
await for (final c in resp) {
|
|
body.addAll(c);
|
|
}
|
|
return (resp, body);
|
|
}
|
|
|
|
var (resp, body) = await get();
|
|
check('no-range -> 200', resp.statusCode == 200, '${resp.statusCode}');
|
|
check('accept-ranges', resp.headers.value('accept-ranges') == 'bytes');
|
|
if (expected != null) {
|
|
check('full body byte-exact', _bytesEqual(body, expected),
|
|
'got=${body.length} want=${expected.length}');
|
|
}
|
|
final size = expected?.length ?? int.parse(resp.headers.value('content-length') ?? '0');
|
|
check('size known', size > 0, 'size=$size');
|
|
|
|
(resp, body) = await get(range: 'bytes=0-99');
|
|
check('closed range -> 206', resp.statusCode == 206, '${resp.statusCode}');
|
|
check('content-range', resp.headers.value('content-range') == 'bytes 0-99/$size');
|
|
if (expected != null) check('closed bytes exact', _bytesEqual(body, expected.sublist(0, 100)));
|
|
|
|
(resp, body) = await get(range: 'bytes=${size - 1000}-');
|
|
check('open-ended -> 206', resp.statusCode == 206, '${resp.statusCode}');
|
|
if (expected != null) {
|
|
check('open-ended bytes exact', _bytesEqual(body, expected.sublist(size - 1000)));
|
|
}
|
|
|
|
(resp, body) = await get(range: 'bytes=-256');
|
|
check('suffix tail -> 206', resp.statusCode == 206, '${resp.statusCode}');
|
|
if (expected != null) {
|
|
check('suffix bytes exact', _bytesEqual(body, expected.sublist(size - 256)));
|
|
}
|
|
|
|
(resp, body) = await get(range: 'bytes=${size + 100}-${size + 200}');
|
|
check('out-of-range -> 416', resp.statusCode == 416, '${resp.statusCode}');
|
|
|
|
final badUri = mediaUri.replace(
|
|
pathSegments: [...mediaUri.pathSegments.take(2), '0' * 32, ...mediaUri.pathSegments.skip(3)]);
|
|
final badReq = await client.getUrl(badUri);
|
|
final badResp = await badReq.close();
|
|
await badResp.drain();
|
|
check('bad token -> 401', badResp.statusCode == 401, '${badResp.statusCode}');
|
|
} finally {
|
|
client.close();
|
|
try {
|
|
await sub.cancel();
|
|
} catch (_) {}
|
|
try {
|
|
await ch.sink.close();
|
|
} catch (_) {}
|
|
}
|
|
// ignore: avoid_print
|
|
print(_failures == 0 ? 'ALL PASS' : '$_failures FAILURES');
|
|
exitCode = _failures == 0 ? 0 : 1;
|
|
}
|