176 lines
5.1 KiB
Dart
176 lines
5.1 KiB
Dart
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<String, _MatchedResponse>? responses})
|
|
: _responses = responses ?? <String, _MatchedResponse>{};
|
|
|
|
final Map<String, _MatchedResponse> _responses;
|
|
final List<_QueuedGet> _getQueue = <_QueuedGet>[];
|
|
final List<http.BaseRequest> requests = <http.BaseRequest>[];
|
|
|
|
/// Captured request bodies indexed by request order in [requests].
|
|
final List<String> capturedBodies = <String>[];
|
|
|
|
void whenGet(String pathSuffix, http.Response response) {
|
|
_responses[pathSuffix] = _MatchedResponse(method: 'GET', response: response);
|
|
}
|
|
|
|
void whenGetJson(String pathSuffix, Map<String, dynamic> body,
|
|
{int statusCode = 200}) {
|
|
whenGet(
|
|
pathSuffix,
|
|
http.Response(jsonEncode(body), statusCode, headers: <String, String>{
|
|
'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<String, dynamic> body,
|
|
{int statusCode = 200}) {
|
|
whenGetQueued(
|
|
pathSuffix,
|
|
http.Response(jsonEncode(body), statusCode, headers: <String, String>{
|
|
'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<String, dynamic> body, {
|
|
int statusCode = 200,
|
|
}) {
|
|
whenGetWhere(
|
|
pathSuffix,
|
|
predicate,
|
|
http.Response(jsonEncode(body), statusCode, headers: <String, String>{
|
|
'content-type': 'application/json',
|
|
}),
|
|
);
|
|
}
|
|
|
|
void whenPost(String pathSuffix, http.Response response) {
|
|
_responses['POST:$pathSuffix'] =
|
|
_MatchedResponse(method: 'POST', response: response);
|
|
}
|
|
|
|
void whenPostJson(String pathSuffix, Map<String, dynamic> body,
|
|
{int statusCode = 200}) {
|
|
whenPost(
|
|
pathSuffix,
|
|
http.Response(jsonEncode(body), statusCode, headers: <String, String>{
|
|
'content-type': 'application/json',
|
|
}),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Future<http.StreamedResponse> 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<List<int>>.value(utf8.encode(queued.response.body)),
|
|
queued.response.statusCode,
|
|
headers: queued.response.headers,
|
|
request: request,
|
|
);
|
|
}
|
|
}
|
|
|
|
for (final MapEntry<String, _MatchedResponse> 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<List<int>>.value(utf8.encode(match.response.body)),
|
|
match.response.statusCode,
|
|
headers: match.response.headers,
|
|
request: request,
|
|
);
|
|
}
|
|
return http.StreamedResponse(
|
|
Stream<List<int>>.value(utf8.encode('{}')),
|
|
404,
|
|
request: request,
|
|
);
|
|
}
|
|
|
|
Future<String> _readBody(http.BaseRequest request) async {
|
|
if (request is http.Request) {
|
|
return request.body;
|
|
}
|
|
final List<int> 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;
|
|
}
|