// connection_controller_test.dart — 连接状态机:严格三态、禁乐观显示 // // ConnectionController 通过 Riverpod ProviderContainer 测试。 // 使用演示节点(uuid 为空)→ 走 mock 分支(1.2s Timer 进入 on)。 // 覆盖 tokenStoreProvider 避免 FlutterSecureStorage 平台依赖。 import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:pangolin_vpn/bridge/vpn_bridge_provider.dart'; import 'package:pangolin_vpn/bridge/vpn_bridge_mock.dart'; import 'package:pangolin_vpn/models/node.dart'; import 'package:pangolin_vpn/services/token_store.dart'; import 'package:pangolin_vpn/state/connection_provider.dart'; import 'package:pangolin_vpn/state/nodes_provider.dart'; import 'package:pangolin_vpn/state/auth_provider.dart'; // ── Stub TokenStore(无平台依赖)────────────────────────────────── class _NullTokenStore implements TokenStore { const _NullTokenStore(); @override Future saveTokens({required String access, required String refresh}) async {} @override Future loadAccessToken() async => null; @override Future loadRefreshToken() async => null; @override Future clear() async {} } // ── 辅助 ────────────────────────────────────────────────────────── /// 演示节点(uuid 为空 → _connect 走 1.2s mock 分支)。 const _demoNode = Node( code: 'HK', nameZh: '香港', nameEn: 'Hong Kong', ping: 18, ); ProviderContainer makeContainer() => ProviderContainer( overrides: [ tokenStoreProvider.overrideWithValue(const _NullTokenStore()), effectiveNodeProvider.overrideWithValue(_demoNode), vpnBridgeProvider.overrideWithValue(VpnBridgeMock()), ], ); void main() { test('初始为 off', () { final c = makeContainer(); addTearDown(c.dispose); expect(c.read(connectionProvider).phase, VpnPhase.off); }); test('toggle → off 态立即切为 connecting', () { final c = makeContainer(); addTearDown(c.dispose); c.read(connectionProvider.notifier).toggle(); expect(c.read(connectionProvider).phase, VpnPhase.connecting); }); test('connecting 时再次 toggle 被忽略(禁止乐观回退)', () { final c = makeContainer(); addTearDown(c.dispose); c.read(connectionProvider.notifier).toggle(); expect(c.read(connectionProvider).phase, VpnPhase.connecting); c.read(connectionProvider.notifier).toggle(); // connecting 中点 → 无效 expect(c.read(connectionProvider).phase, VpnPhase.connecting); }); test('off 态 onNodeChanged 不触发连接', () { final c = makeContainer(); addTearDown(c.dispose); c.read(connectionProvider.notifier).onNodeChanged(); expect(c.read(connectionProvider).phase, VpnPhase.off); }); }