diff --git a/test/api/fake_api.dart b/test/api/fake_api.dart index 1cbbffcdc6..87c450f3dc 100644 --- a/test/api/fake_api.dart +++ b/test/api/fake_api.dart @@ -8,19 +8,22 @@ import 'package:zulip/model/store.dart'; import '../example_data.dart' as eg; sealed class _PreparedResponse { + final Duration delay; + + _PreparedResponse({this.delay = Duration.zero}); } class _PreparedException extends _PreparedResponse { final Object exception; - _PreparedException({required this.exception}); + _PreparedException({super.delay, required this.exception}); } class _PreparedSuccess extends _PreparedResponse { final int httpStatus; final List bytes; - _PreparedSuccess({required this.httpStatus, required this.bytes}); + _PreparedSuccess({super.delay, required this.httpStatus, required this.bytes}); } /// An [http.Client] that accepts and replays canned responses, for testing. @@ -53,12 +56,13 @@ class FakeHttpClient extends http.BaseClient { int? httpStatus, Map? json, String? body, + Duration delay = Duration.zero, }) { assert(_nextResponse == null, 'FakeApiConnection.prepare was called while already expecting a request'); if (exception != null) { assert(httpStatus == null && json == null && body == null); - _nextResponse = _PreparedException(exception: exception); + _nextResponse = _PreparedException(exception: exception, delay: delay); } else { assert((json == null) || (body == null)); final String resolvedBody = switch ((body, json)) { @@ -69,6 +73,7 @@ class FakeHttpClient extends http.BaseClient { _nextResponse = _PreparedSuccess( httpStatus: httpStatus ?? 200, bytes: utf8.encode(resolvedBody), + delay: delay, ); } } @@ -89,14 +94,16 @@ class FakeHttpClient extends http.BaseClient { final response = _nextResponse!; _nextResponse = null; + final http.StreamedResponse Function() computation; switch (response) { case _PreparedException(:var exception): - return Future(() => throw exception); + computation = () => throw exception; case _PreparedSuccess(:var bytes, :var httpStatus): final byteStream = http.ByteStream.fromBytes(bytes); - return Future(() => http.StreamedResponse( - byteStream, httpStatus, request: request)); + computation = () => http.StreamedResponse( + byteStream, httpStatus, request: request); } + return Future.delayed(response.delay, computation); } } @@ -203,13 +210,20 @@ class FakeApiConnection extends ApiConnection { /// /// If `exception` is non-null, then `httpStatus`, `body`, and `json` must /// all be null, and the next request will throw the given exception. + /// + /// In either case, the next request will complete a duration of `delay` + /// after being started. void prepare({ Object? exception, int? httpStatus, Map? json, String? body, + Duration delay = Duration.zero, }) { client.prepare( - exception: exception, httpStatus: httpStatus, json: json, body: body); + exception: exception, + httpStatus: httpStatus, json: json, body: body, + delay: delay, + ); } } diff --git a/test/api/fake_api_test.dart b/test/api/fake_api_test.dart index cc2f0d70c2..243757ace4 100644 --- a/test/api/fake_api_test.dart +++ b/test/api/fake_api_test.dart @@ -2,6 +2,7 @@ import 'package:checks/checks.dart'; import 'package:test/scaffolding.dart'; import 'package:zulip/api/exception.dart'; +import '../fake_async.dart'; import 'exception_checks.dart'; import 'fake_api.dart'; @@ -21,4 +22,36 @@ void main() { ..asString.contains('no response was prepared') ..asString.contains('FakeApiConnection.prepare')); }); + + test('delay success', () => awaitFakeAsync((async) async { + final connection = FakeApiConnection(); + connection.prepare(delay: const Duration(seconds: 2), + json: {'a': 3}); + + Map? result; + connection.get('aRoute', (json) => json, '/', null) + .then((r) { result = r; }); + + async.elapse(const Duration(seconds: 1)); + check(result).isNull(); + + async.elapse(const Duration(seconds: 1)); + check(result).isNotNull().deepEquals({'a': 3}); + })); + + test('delay exception', () => awaitFakeAsync((async) async { + final connection = FakeApiConnection(); + connection.prepare(delay: const Duration(seconds: 2), + exception: Exception("oops")); + + Object? error; + connection.get('aRoute', (json) => null, '/', null) + .catchError((Object e) { error = e; }); + + async.elapse(const Duration(seconds: 1)); + check(error).isNull(); + + async.elapse(const Duration(seconds: 1)); + check(error).isA().asString.contains("oops"); + })); }