178 lines
4.5 KiB
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;
|
|
}
|