// connection_controller_test.dart — 连接状态机:严格三态、错误冒泡 // // ConnectionController 通过 Riverpod ProviderContainer 测试。 // 节点 uuid 为空(未就绪)→ _connect 不走网络,直接回 off 并给出错误提示。 // 覆盖 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 {} @override Future markOnboarded() async {} @override Future isOnboarded() async => true; @override Future saveLastEmail(String email) async {} @override Future loadLastEmail() async => null; } // ── 辅助 ────────────────────────────────────────────────────────── /// 未就绪节点(uuid 为空)→ _connect 不走网络,直接回 off+错误。 const _notReadyNode = Node(code: 'HK', nameZh: '香港', nameEn: 'Hong Kong', ping: 18); ProviderContainer makeContainer() => ProviderContainer( overrides: [ tokenStoreProvider.overrideWithValue(const _NullTokenStore()), effectiveNodeProvider.overrideWithValue(_notReadyNode), vpnBridgeProvider.overrideWithValue(VpnBridgeMock()), ], ); void main() { test('初始为 off', () { final c = makeContainer(); addTearDown(c.dispose); expect(c.read(connectionProvider).phase, VpnPhase.off); }); test('节点未就绪时 toggle → 回 off 并冒泡错误(不再静默)', () { final c = makeContainer(); addTearDown(c.dispose); c.read(connectionProvider.notifier).toggle(); final s = c.read(connectionProvider); expect(s.phase, VpnPhase.off); expect(s.error, isNotNull); }); test('off 态 onNodeChanged 不触发连接', () { final c = makeContainer(); addTearDown(c.dispose); c.read(connectionProvider.notifier).onNodeChanged(); expect(c.read(connectionProvider).phase, VpnPhase.off); }); }