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 = []; final states = []; 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.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 [ 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 = []; 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.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 = []; final sub = api.errorStream.listen(errors.add); addTearDown(sub.cancel); await api.connect(); await expectLater( api.sendRequest(Opcode.contactInfoByPhone, {'phone': '+79000000000'}), throwsA(isA()), ); await Future.delayed(Duration.zero); expect(api.state, SessionState.online); expect(errors, ['Пользователь не найден']); }); }