arblant
firefox-vpn-client / app / test / core_controller_test.dart
core_controller_test.dart106 lines · 3.1 KB
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
import 'package:flutter_test/flutter_test.dart';
import 'package:firefox_vpn/core/core_api.dart';
import 'package:firefox_vpn/core/core_controller.dart';
import 'package:firefox_vpn/core/models.dart';

/// A core whose next [failNext] reads fail as if the connection dropped.
class _FlakyApi implements CoreApi {
  int failNext = 0;

  @override
  Future<CoreStatus> status() async {
    if (failNext > 0) {
      failNext--;
      throw const CoreUnavailable(CoreProblem.notAnswering, 'connection reset');
    }
    return CoreStatus.fromJson({
      'state': {'phase': 'off', 'message': 'Proxy off'},
    });
  }

  @override
  Future<AccountList> accounts() async => AccountList.fromJson({});

  @override
  void close() {}

  @override
  dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
}

/// A running core whose auto-connect tests the connection; it counts the
/// app's own tests.
class _AutoConnectingApi implements CoreApi {
  int tests = 0;
  bool autoConnecting = true;

  @override
  Future<CoreStatus> status() async => CoreStatus.fromJson({
    'state': {'phase': 'running', 'message': 'Proxy running'},
    'system_proxy': 'set',
    'system_proxy_supported': true,
    'auto_connecting': autoConnecting,
  });

  @override
  Future<ConnectionTest> test() async {
    tests++;
    return ConnectionTest.fromJson({'ok': false});
  }

  @override
  Future<AccountList> accounts() async => AccountList.fromJson({});

  @override
  void close() {}

  @override
  dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
}

void main() {
  // The core's auto-connect tests by itself; the app adds none meanwhile.
  test('no tests of its own while the core auto-connects', () async {
    final api = _AutoConnectingApi();
    final core = CoreController(
      () async => api,
      pollInterval: const Duration(hours: 1),
    );
    await core.refresh();
    await Future<void>.delayed(const Duration(milliseconds: 50));
    expect(core.status?.autoConnecting, isTrue);
    expect(api.tests, 0);

    api.autoConnecting = false;
    await core.refresh();
    await Future<void>.delayed(const Duration(milliseconds: 50));
    expect(api.tests, 1, reason: 'unconfirmed once auto-connect is over');
    core.dispose();
  });

  // Android closes the app's connections when the phone's network goes
  // away; one failed read must not show the core as lost.
  test('one failed read is retried before the core counts as lost', () async {
    final api = _FlakyApi();
    final core = CoreController(
      () async => api,
      pollInterval: const Duration(hours: 1),
    );
    await core.refresh();
    expect(core.unavailable, isNull);

    api.failNext = 1;
    await core.refresh();
    expect(core.unavailable, isNull, reason: 'a single failure is retried');
    await core.refresh();
    expect(core.unavailable, isNull);

    api.failNext = 2;
    await core.refresh();
    await core.refresh();
    expect(core.unavailable?.problem, CoreProblem.notAnswering);
    await core.refresh();
    expect(core.unavailable, isNull, reason: 'it answers again');
    core.dispose();
  });
}