import 'dart:convert'; import 'package:http/http.dart' as http; /// Records requests and returns canned responses for Alpaca client unit tests. class MockHttpClient extends http.BaseClient { MockHttpClient({Map? responses}) : _responses = responses ?? {}; final Map _responses; final List<_QueuedGet> _getQueue = <_QueuedGet>[]; final List requests = []; /// Captured request bodies indexed by request order in [requests]. final List capturedBodies = []; void whenGet(String pathSuffix, http.Response response) { _responses[pathSuffix] = _MatchedResponse(method: 'GET', response: response); } void whenGetJson(String pathSuffix, Map body, {int statusCode = 200}) { whenGet( pathSuffix, http.Response(jsonEncode(body), statusCode, headers: { 'content-type': 'application/json', }), ); } /// Returns [response] for the next GET whose path ends with [pathSuffix]. /// /// Useful for paginated endpoints where the same path is hit multiple /// times with different `page_token` query values. void whenGetQueued(String pathSuffix, http.Response response) { _getQueue.add(_QueuedGet(pathSuffix, response)); } void whenGetQueuedJson(String pathSuffix, Map body, {int statusCode = 200}) { whenGetQueued( pathSuffix, http.Response(jsonEncode(body), statusCode, headers: { 'content-type': 'application/json', }), ); } /// Returns [response] when [predicate] matches the request URI. void whenGetWhere( String pathSuffix, bool Function(Uri uri) predicate, http.Response response, ) { _responses['GET:$pathSuffix:${_responses.length}'] = _MatchedResponse( method: 'GET', response: response, pathSuffix: pathSuffix, uriPredicate: predicate, ); } void whenGetWhereJson( String pathSuffix, bool Function(Uri uri) predicate, Map body, { int statusCode = 200, }) { whenGetWhere( pathSuffix, predicate, http.Response(jsonEncode(body), statusCode, headers: { 'content-type': 'application/json', }), ); } void whenPost(String pathSuffix, http.Response response) { _responses['POST:$pathSuffix'] = _MatchedResponse(method: 'POST', response: response); } void whenPostJson(String pathSuffix, Map body, {int statusCode = 200}) { whenPost( pathSuffix, http.Response(jsonEncode(body), statusCode, headers: { 'content-type': 'application/json', }), ); } @override Future send(http.BaseRequest request) async { requests.add(request); final String body = await _readBody(request); capturedBodies.add(body); final String path = request.url.path; final String method = request.method.toUpperCase(); if (method == 'GET' && _getQueue.isNotEmpty) { final int idx = _getQueue.indexWhere( (_QueuedGet q) => path.endsWith(q.pathSuffix), ); if (idx >= 0) { final _QueuedGet queued = _getQueue.removeAt(idx); return http.StreamedResponse( Stream>.value(utf8.encode(queued.response.body)), queued.response.statusCode, headers: queued.response.headers, request: request, ); } } for (final MapEntry entry in _responses.entries) { final _MatchedResponse match = entry.value; if (match.method != method) continue; final String suffix = match.pathSuffix ?? (entry.key.startsWith('$method:') ? entry.key.substring(method.length + 1).split(':').first : entry.key); final bool pathOk = path.endsWith(suffix) || request.url.toString().contains(suffix); if (!pathOk) continue; if (match.uriPredicate != null && !match.uriPredicate!(request.url)) { continue; } return http.StreamedResponse( Stream>.value(utf8.encode(match.response.body)), match.response.statusCode, headers: match.response.headers, request: request, ); } return http.StreamedResponse( Stream>.value(utf8.encode('{}')), 404, request: request, ); } Future _readBody(http.BaseRequest request) async { if (request is http.Request) { return request.body; } final List bytes = await request.finalize().toBytes(); return utf8.decode(bytes); } @override void close() {} } class _QueuedGet { _QueuedGet(this.pathSuffix, this.response); final String pathSuffix; final http.Response response; } class _MatchedResponse { _MatchedResponse({ required this.method, required this.response, this.pathSuffix, this.uriPredicate, }); final String method; final http.Response response; final String? pathSuffix; final bool Function(Uri uri)? uriPredicate; }