podman/test/support/fake_podman_transport.dart

178 lines
4.5 KiB
Dart

import 'dart:convert';
import 'package:podman/podman.dart';
final class FakePodmanTransport implements PodmanTransport {
final List<PodmanTransportRequest> sentRequests = <PodmanTransportRequest>[];
final List<_ExpectedExchange> _queue = <_ExpectedExchange>[];
void enqueue({
required HttpMethod method,
required String path,
Map<String, List<String>> queryParameters = const <String, List<String>>{},
Object? body,
int statusCode = 200,
Map<String, List<String>> headers = const <String, List<String>>{},
Object? responseBody,
}) {
_queue.add(
_ExpectedExchange(
expectedRequest: PodmanTransportRequest(
method: method,
path: path,
queryParameters: queryParameters,
body: body,
),
response: PodmanTransportResponse(
statusCode: statusCode,
headers: headers,
bodyBytes: _encodeResponseBody(responseBody),
),
),
);
}
@override
Future<PodmanTransportResponse> send(PodmanTransportRequest request) async {
sentRequests.add(request);
if (_queue.isEmpty) {
throw StateError(
'Unexpected request with no queued response: '
'${request.method.name.toUpperCase()} ${request.path}',
);
}
final expected = _queue.removeAt(0);
_assertRequestMatches(expected.expectedRequest, request);
return expected.response;
}
@override
Future<void> close() async {}
void expectNoPending() {
if (_queue.isNotEmpty) {
final pending = _queue
.map(
(item) =>
'${item.expectedRequest.method.name.toUpperCase()} '
'${item.expectedRequest.path}',
)
.join('\n');
throw StateError('Pending expected requests:\n$pending');
}
}
void _assertRequestMatches(
PodmanTransportRequest expected,
PodmanTransportRequest actual,
) {
if (expected.method != actual.method || expected.path != actual.path) {
throw StateError(
'Unexpected request line.\n'
'Expected: ${expected.method.name.toUpperCase()} ${expected.path}\n'
'Actual: ${actual.method.name.toUpperCase()} ${actual.path}',
);
}
if (!_deepEquals(expected.queryParameters, actual.queryParameters)) {
throw StateError(
'Unexpected query parameters.\n'
'Expected: ${expected.queryParameters}\n'
'Actual: ${actual.queryParameters}',
);
}
if (!_deepEquals(expected.body, actual.body)) {
throw StateError(
'Unexpected request body.\n'
'Expected: ${_canonicalJson(expected.body)}\n'
'Actual: ${_canonicalJson(actual.body)}',
);
}
}
List<int> _encodeResponseBody(Object? body) {
if (body == null) {
return const <int>[];
}
if (body is String) {
return utf8.encode(body);
}
if (body is List<int>) {
return body;
}
return utf8.encode(jsonEncode(body));
}
bool _deepEquals(Object? left, Object? right) {
if (left == null || right == null) {
return left == right;
}
if (left is Map && right is Map) {
if (left.length != right.length) {
return false;
}
for (final key in left.keys) {
if (!right.containsKey(key)) {
return false;
}
if (!_deepEquals(left[key], right[key])) {
return false;
}
}
return true;
}
if (left is List && right is List) {
if (left.length != right.length) {
return false;
}
for (var i = 0; i < left.length; i++) {
if (!_deepEquals(left[i], right[i])) {
return false;
}
}
return true;
}
return left == right;
}
String _canonicalJson(Object? value) {
if (value == null) {
return 'null';
}
final normalized = _normalize(value);
return jsonEncode(normalized);
}
Object? _normalize(Object? value) {
if (value is Map) {
final keys = value.keys.map((key) => key.toString()).toList()..sort();
final output = <String, Object?>{};
for (final key in keys) {
output[key] = _normalize(value[key]);
}
return output;
}
if (value is List) {
return value.map(_normalize).toList(growable: false);
}
return value;
}
}
final class _ExpectedExchange {
const _ExpectedExchange({
required this.expectedRequest,
required this.response,
});
final PodmanTransportRequest expectedRequest;
final PodmanTransportResponse response;
}