import 'dart:convert'; import 'package:podman/podman.dart'; final class FakePodmanTransport implements PodmanTransport { final List sentRequests = []; final List<_ExpectedExchange> _queue = <_ExpectedExchange>[]; void enqueue({ required HttpMethod method, required String path, Map> queryParameters = const >{}, Object? body, int statusCode = 200, Map> headers = const >{}, 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 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 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 _encodeResponseBody(Object? body) { if (body == null) { return const []; } if (body is String) { return utf8.encode(body); } if (body is List) { 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 = {}; 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; }