import 'dart:convert'; import 'package:aioa_mobile/app/app.dart'; import 'package:aioa_mobile/app/router.dart'; import 'package:aioa_mobile/core/config/runtime_config.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:http/http.dart' as http; import 'package:integration_test/integration_test.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); testWidgets('complete MVP frontend business scenario', (tester) async { final api = _E2eApi(RuntimeConfig.e2eAccessToken); await api.registerDevice('90000000-0000-4000-8000-000000000001'); await tester.pumpWidget(const ProviderScope(child: AioaApp())); await tester.pumpAndSettle(const Duration(seconds: 10)); expect(find.text('工作台'), findsOneWidget); switch (RuntimeConfig.e2eRole) { case 'manager': await _managerScenario(tester); case 'admin': await _adminScenario(tester); default: await _employeeScenario(tester, api); } }); } Future _employeeScenario(WidgetTester tester, _E2eApi api) async { await tester.tap(find.text('发起请假')); await tester.pumpAndSettle(const Duration(seconds: 8)); expect(find.text('请假申请'), findsOneWidget); expect(find.text('千问表单助手'), findsOneWidget); expect(find.byKey(const ValueKey('type')), findsOneWidget); expect(find.byKey(const ValueKey('startsAt')), findsOneWidget); expect(find.byKey(const ValueKey('endsAt')), findsOneWidget); expect(find.byKey(const ValueKey('reason')), findsOneWidget); appRouter.go('/workspace'); await tester.pumpAndSettle(); final reason = 'iOS MVP 集成测试 ${DateTime.now().millisecondsSinceEpoch}'; final draft = await api.createDraft(reason); await tester.tap(find.text('我的请假申请')); await tester.pumpAndSettle(const Duration(seconds: 5)); expect(find.textContaining(reason), findsOneWidget); await tester.tap(find.textContaining(reason)); await tester.pumpAndSettle(); expect(find.text('草稿'), findsOneWidget); await tester.tap(find.text('提交审批')); await tester.pumpAndSettle(); await tester.tap(find.widgetWithText(FilledButton, '确认')); await tester.pumpAndSettle(const Duration(seconds: 5)); expect(find.text('审批中'), findsOneWidget); appRouter.go('/workspace'); await tester.pumpAndSettle(); await tester.tap(find.text('AI 助手')); await tester.pumpAndSettle(); expect(find.text('AI 流程助手'), findsOneWidget); await tester.enterText(find.byType(TextField).first, '我最近提交的请假到哪一步了?'); await tester.tap(find.text('查询进度')); await tester.pumpAndSettle(const Duration(seconds: 8)); expect(find.textContaining('查询失败'), findsWidgets); await tester.tap(find.text('待办')); await tester.pumpAndSettle(); expect(find.textContaining('通知'), findsWidgets); await tester.tap(find.text('我的')); await tester.pumpAndSettle(const Duration(seconds: 5)); expect(find.text('登录设备'), findsOneWidget); expect(draft['id'], isNotNull); } Future _managerScenario(WidgetTester tester) async { final employee = _E2eApi(RuntimeConfig.e2eEmployeeToken); await employee.registerDevice('90000000-0000-4000-8000-000000000002'); final reason = '主管 iOS 审批测试 ${DateTime.now().millisecondsSinceEpoch}'; final draft = await employee.createDraft( reason, deviceId: '90000000-0000-4000-8000-000000000002', ); await employee.submitDraft( draft, deviceId: '90000000-0000-4000-8000-000000000002', ); await tester.tap(find.text('待办')); await tester.pumpAndSettle(const Duration(seconds: 8)); expect(find.text(reason), findsOneWidget); final taskCard = find.ancestor( of: find.text(reason), matching: find.byType(Card), ); await tester.tap(find.descendant(of: taskCard, matching: find.text('批准'))); await tester.pumpAndSettle(); await tester.tap(find.widgetWithText(FilledButton, '确认批准')); await tester.pumpAndSettle(const Duration(seconds: 8)); expect(find.text('已批准'), findsOneWidget); } Future _adminScenario(WidgetTester tester) async { await tester.tap(find.text('我的')); await tester.pumpAndSettle(const Duration(seconds: 8)); expect(find.text('OA 管理中心'), findsOneWidget); await tester.tap(find.text('OA 管理中心')); await tester.pumpAndSettle(const Duration(seconds: 10)); expect(find.text('指标'), findsOneWidget); expect(find.text('组织'), findsOneWidget); expect(find.text('流程'), findsOneWidget); expect(find.text('审计'), findsOneWidget); await tester.tap(find.text('组织')); await tester.pumpAndSettle(); expect(find.text('新增部门'), findsOneWidget); await tester.tap(find.text('流程')); await tester.pumpAndSettle(); expect(find.text('流程定义'), findsOneWidget); await tester.tap(find.text('审计')); await tester.pumpAndSettle(); expect(find.byType(Card), findsWidgets); } class _E2eApi { _E2eApi(this.token); final String token; String get base => RuntimeConfig.apiBaseUrl; Map headers([String? device]) => { 'Authorization': 'Bearer $token', 'Content-Type': 'application/json', 'X-AIOA-Device-Id': ?device, }; static const device = '90000000-0000-4000-8000-000000000001'; Future registerDevice(String id) async { final r = await http.post( Uri.parse('$base/devices/register'), headers: headers(), body: jsonEncode({ 'id': id, 'name': 'iOS integration test', 'platform': 'IOS', }), ); expect(r.statusCode, 200); } Future> createDraft( String reason, { String deviceId = device, }) async { final now = DateTime.now().toUtc().add(const Duration(days: 2)); final r = await http.post( Uri.parse('$base/leave-requests/drafts'), headers: { ...headers(deviceId), 'Idempotency-Key': 'ios-e2e-${DateTime.now().microsecondsSinceEpoch}', }, body: jsonEncode({ 'type': 'PERSONAL', 'startsAt': now.toIso8601String(), 'endsAt': now.add(const Duration(hours: 4)).toIso8601String(), 'reason': reason, 'version': 0, }), ); expect(r.statusCode, 201); return Map.from(jsonDecode(r.body) as Map); } Future submitDraft( Map draft, { String deviceId = device, }) async { final r = await http.post( Uri.parse('$base/leave-requests/${draft['id']}/submit'), headers: { ...headers(deviceId), 'Idempotency-Key': 'ios-submit-${DateTime.now().microsecondsSinceEpoch}', }, body: jsonEncode({'version': draft['version']}), ); expect(r.statusCode, 200); } }