Skip to content

fake_api [nfc]: Add delay option to connection.prepare #770

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 21 additions & 7 deletions test/api/fake_api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<int> 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.
Expand Down Expand Up @@ -53,12 +56,13 @@ class FakeHttpClient extends http.BaseClient {
int? httpStatus,
Map<String, dynamic>? 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)) {
Expand All @@ -69,6 +73,7 @@ class FakeHttpClient extends http.BaseClient {
_nextResponse = _PreparedSuccess(
httpStatus: httpStatus ?? 200,
bytes: utf8.encode(resolvedBody),
delay: delay,
);
}
}
Expand All @@ -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);
}
}

Expand Down Expand Up @@ -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<String, dynamic>? 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,
);
}
}
33 changes: 33 additions & 0 deletions test/api/fake_api_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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<String, dynamic>? 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<NetworkException>().asString.contains("oops");
}));
}
Loading