Files
IKAR/test/api_offline_errors_test.dart

128 lines
4.2 KiB
Dart

import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:flutter_test/flutter_test.dart';
import 'package:http/http.dart' as http;
import 'package:http/testing.dart';
import 'package:ikar/backend/api.dart';
import 'package:ikar/backend/ikar_transport.dart';
import 'package:ikar/core/protocol/opcode_map.dart';
import 'package:ikar/core/protocol/packet.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
setUp(() => SharedPreferences.setMockInitialValues({}));
test(
'repeated failed connections stay quiet and recover to online',
() async {
var available = false;
final api = Api(
transportFactory: (uri) => IkarTransport(
uri,
client: MockClient((request) async {
if (!available) return http.Response('Gateway unavailable', 503);
return http.Response(jsonEncode({'cmd': 1, 'payload': {}}), 200);
}),
),
);
addTearDown(api.dispose);
final errors = <String>[];
final states = <SessionState>[];
final errorsSub = api.errorStream.listen(errors.add);
final statesSub = api.stateStream.listen(states.add);
addTearDown(errorsSub.cancel);
addTearDown(statesSub.cancel);
await api.connect();
await api.connect();
expect(api.state, SessionState.disconnected);
available = true;
await api.connect();
await Future<void>.delayed(Duration.zero);
expect(errors, isEmpty);
expect(states.where((s) => s == SessionState.disconnected), hasLength(2));
expect(api.state, SessionState.online);
expect(states.last, SessionState.online);
},
);
for (final failure in <Object>[
const SocketException('Server unavailable'),
TimeoutException('Server unavailable'),
const HttpException('Server unavailable'),
http.ClientException('Server unavailable'),
]) {
test(
'a ${failure.runtimeType} request immediately changes connection state without a toast',
() async {
var fail = false;
final api = Api(
transportFactory: (uri) => IkarTransport(
uri,
client: MockClient((request) async {
if (fail) throw failure;
return http.Response(jsonEncode({'cmd': 1, 'payload': {}}), 200);
}),
),
);
addTearDown(api.dispose);
final errors = <String>[];
final sub = api.errorStream.listen(errors.add);
addTearDown(sub.cancel);
await api.connect();
fail = true;
await expectLater(
api.sendRequest(Opcode.contactsGet, {}),
throwsA(same(failure)),
);
expect(api.state, SessionState.disconnected);
await Future<void>.delayed(Duration.zero);
expect(errors, isEmpty);
fail = false;
await api.connect();
expect(api.state, SessionState.online);
},
);
}
test('operation errors on an online server are still reported', () async {
final api = Api(
transportFactory: (uri) => IkarTransport(
uri,
client: MockClient((request) async {
final opcode = int.parse(request.url.pathSegments.last);
return http.Response(
jsonEncode(
opcode == Opcode.ping
? {'cmd': 1, 'payload': {}}
: {
'cmd': 3,
'payload': {
'error': 'user.not.found',
'message': 'Пользователь не найден',
},
},
),
200,
headers: {'content-type': 'application/json; charset=utf-8'},
);
}),
),
);
addTearDown(api.dispose);
final errors = <String>[];
final sub = api.errorStream.listen(errors.add);
addTearDown(sub.cancel);
await api.connect();
await expectLater(
api.sendRequest(Opcode.contactInfoByPhone, {'phone': '+79000000000'}),
throwsA(isA<PacketError>()),
);
await Future<void>.delayed(Duration.zero);
expect(api.state, SessionState.online);
expect(errors, ['Пользователь не найден']);
});
}