/// App 壳 401 接线测试(P3 计划 Task 3)。 /// /// 覆盖:业务端点 401 → ApiClient 清会话并触发 onUnauthorized → /// 根导航清栈回 /login(PhotoLiveApp.initState 注册的回调)。 library; import 'package:dio/dio.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:photo_live/app/app.dart'; import 'package:photo_live/pages/login_page.dart'; import 'package:photo_live/core/api_client.dart'; import 'package:photo_live/core/error_codes.dart'; import 'package:photo_live/core/session_store.dart'; import '../helpers/fakes.dart'; void main() { testWidgets('业务 401 → onUnauthorized 清栈回 /login', (tester) async { final store = FakeSessionStore(); store.session = Session( token: 'tok-live-1', expiresAt: DateTime.utc(2099, 1, 1), account: 'staff01', ); final adapter = FakeAdapter( (o) => (401, errorBody(ErrorCodes.authTokenExpired)), ); final client = ApiClient( sessionStore: store, dio: Dio(BaseOptions(baseUrl: 'http://test.local')) ..httpClientAdapter = adapter, ); // PhotoLiveApp.initState 读取被 override 的客户端并注册 onUnauthorized。 // Task 9 起根组件 watch 引擎作用域(会话→引擎 start),真实 // flutter_secure_storage/path_provider 平台通道在测试环境挂起,须替身。 await tester.pumpWidget( ProviderScope( overrides: [...pageTestOverrides(session: store, client: client)], child: const PhotoLiveApp(), ), ); await tester.pumpAndSettle(); // UI06 启动门:有会话直接渲染 HomePage(无须手动 push)。 // 触发 401 但不在测试体内 await(fake-async 下时钟由 pump 推进, // 阻塞等待会死锁);AppException 由 ignore 吞掉,断言在下方。 client.listAlbums().ignore(); await tester.pumpAndSettle(); expect(store.session, isNull, reason: '业务 401 清空本地会话'); expect(find.byType(LoginPage), findsOneWidget, reason: '清栈回登录页'); }); }