// flow_auth_test.dart — L2 登陆/重启续登/登出 流程(真 AuthNotifier + 内存 TokenStore)。 // // 驱动真实的 AuthNotifier(非桩),只在可注入接缝(TokenStore)处换内存实现, // 覆盖你最初诉求里的「自动登陆 / 重启续登 / 登出」。 import 'package:flutter_test/flutter_test.dart'; import 'package:pangolin_vpn/services/auth_api.dart'; import 'package:pangolin_vpn/services/token_store.dart'; import 'package:pangolin_vpn/state/auth_provider.dart'; /// 内存 TokenStore:实现接口、用字段存,避免 FlutterSecureStorage 平台依赖。 class _MemTokenStore implements TokenStore { String? _access; String? _refresh; bool _onboarded = false; String? _email; @override Future saveTokens({required String access, required String refresh}) async { _access = access; _refresh = refresh; } @override Future loadAccessToken() async => _access; @override Future loadRefreshToken() async => _refresh; @override Future clear() async { _access = null; _refresh = null; } @override Future markOnboarded() async => _onboarded = true; @override Future isOnboarded() async => _onboarded; @override Future saveLastEmail(String email) async => _email = email; @override Future loadLastEmail() async => _email; } /// 等 AuthNotifier 构造时的 _loadFromStore() 这个 microtask 跑完。 Future _settle() => Future.delayed(Duration.zero); void main() { test('登陆:saveTokens → isLoggedIn 且令牌持久化', () async { final store = _MemTokenStore(); final auth = AuthNotifier(store); await _settle(); expect(auth.state.isLoggedIn, isFalse, reason: '初始未登录'); await auth.saveTokens( const AuthTokens(accessToken: 'access-1', refreshToken: 'refresh-1'), ); expect(auth.state.isLoggedIn, isTrue); expect(auth.state.accessToken, 'access-1'); expect(await store.loadAccessToken(), 'access-1', reason: '令牌落盘'); expect(await store.loadRefreshToken(), 'refresh-1'); auth.dispose(); }); test('重启续登:新 AuthNotifier 从同一 store 恢复登录态', () async { final store = _MemTokenStore(); await store.saveTokens(access: 'persisted-access', refresh: 'persisted-refresh'); // 模拟「重启 app」:全新 AuthNotifier 实例读同一持久化 store。 final auth = AuthNotifier(store); await _settle(); // 等 _loadFromStore expect(auth.state.isLoggedIn, isTrue, reason: '续登:无需重新输密码'); expect(auth.state.accessToken, 'persisted-access'); auth.dispose(); }); test('登出:logout → 未登录 且 store 清空(下次重启不再续登)', () async { final store = _MemTokenStore(); await store.saveTokens(access: 'a', refresh: 'r'); final auth = AuthNotifier(store); await _settle(); expect(auth.state.isLoggedIn, isTrue); await auth.logout(); expect(auth.state.isLoggedIn, isFalse); expect(await store.loadAccessToken(), isNull, reason: '令牌已清'); // 登出后再「重启」→ 仍是未登录。 final after = AuthNotifier(store); await _settle(); expect(after.state.isLoggedIn, isFalse); auth.dispose(); after.dispose(); }); }