129 lines
5.6 KiB
Dart
129 lines
5.6 KiB
Dart
|
|
/// P3 Task 11 集成测试 · 后台延续与杀进程恢复(A04/A05,模拟器级)。
|
|||
|
|
///
|
|||
|
|
/// 由 `scripts/run-mobile-it.sh --background` 编排,两阶段经
|
|||
|
|
/// `--dart-define IT_PHASE=` 区分(App 无法自己 HOME/杀自己——设备动作
|
|||
|
|
/// 由脚本在见到 MARKER 后执行,属计划明示的 adb 编排场景):
|
|||
|
|
///
|
|||
|
|
/// - **prepare**:登录(本次运行专属账号)→ API 建相册 → 直接经准备器
|
|||
|
|
/// 入队 2 张大图(~15MB,保证传输有可被中断的时间窗)→ 等任务进入
|
|||
|
|
/// uploading → 打 `MARKER:UPLOADING`(脚本此刻 HOME 或 force-stop)→
|
|||
|
|
/// 就地等待全部 completed(HOME 场景:WorkManager 后台续传完成后本
|
|||
|
|
/// 测试自然通过;KILL 场景:进程被杀,本测试非零退出是预期)。
|
|||
|
|
/// - **verify**:全新进程登录同账号 → 断言 2 张全部 completed(KILL 场景
|
|||
|
|
/// 覆盖 A05:drift 任务库持久 + 崩溃恢复 uploading→waitingNetwork→
|
|||
|
|
/// 幂等重传)→ 服务端历史可见。
|
|||
|
|
library;
|
|||
|
|
|
|||
|
|
import 'package:flutter/foundation.dart';
|
|||
|
|
import 'package:flutter_test/flutter_test.dart';
|
|||
|
|
import 'package:integration_test/integration_test.dart';
|
|||
|
|
import 'package:path/path.dart' as p;
|
|||
|
|
import 'package:path_provider/path_provider.dart';
|
|||
|
|
import 'package:photo_live/core/api_client.dart';
|
|||
|
|
import 'package:photo_live/features/albums/models.dart';
|
|||
|
|
import 'package:photo_live/features/uploads/providers.dart';
|
|||
|
|
import 'package:photo_live/features/uploads/task_state.dart';
|
|||
|
|
|
|||
|
|
import 'helpers/app_boot.dart';
|
|||
|
|
import 'helpers/it_config.dart';
|
|||
|
|
import 'helpers/ui_steps.dart';
|
|||
|
|
|
|||
|
|
void main() {
|
|||
|
|
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
|||
|
|
final phase = const String.fromEnvironment('IT_PHASE', defaultValue: '');
|
|||
|
|
if (phase.isEmpty) {
|
|||
|
|
fail('缺少 --dart-define IT_PHASE=prepare|verify(经 run-mobile-it.sh 运行)');
|
|||
|
|
}
|
|||
|
|
final harness = ItAppHarness.create();
|
|||
|
|
|
|||
|
|
testWidgets(
|
|||
|
|
'P3 后台延续/杀进程恢复 [$phase]',
|
|||
|
|
(tester) async {
|
|||
|
|
final account = ItConfig.mainAccount;
|
|||
|
|
|
|||
|
|
if (phase == 'prepare') {
|
|||
|
|
await tester.pumpWidget(harness.appWidget());
|
|||
|
|
await loginViaUi(tester, account, ItConfig.mainPassword);
|
|||
|
|
|
|||
|
|
// 相册走 API(建相册的 UI 驱动已在主流程覆盖;本场景主题是传输)。
|
|||
|
|
final api = harness.container.read(apiClientProvider);
|
|||
|
|
final album = await api.createAlbum(
|
|||
|
|
AlbumCreateRequest(
|
|||
|
|
translations: [
|
|||
|
|
AlbumTranslation(
|
|||
|
|
locale: 'zh-CN',
|
|||
|
|
title: '后台测试相册 ${ItConfig.runStamp}',
|
|||
|
|
),
|
|||
|
|
AlbumTranslation(
|
|||
|
|
locale: 'en',
|
|||
|
|
title: 'BG Album ${ItConfig.runStamp}',
|
|||
|
|
),
|
|||
|
|
],
|
|||
|
|
defaultLocale: 'zh-CN',
|
|||
|
|
startAt: DateTime.now().toUtc(),
|
|||
|
|
),
|
|||
|
|
);
|
|||
|
|
debugPrint('[IT] 后台场景相册:${album.id}');
|
|||
|
|
|
|||
|
|
// 大图经准备器直接入队(选图 UI 已在主流程覆盖)。
|
|||
|
|
final support = await getApplicationSupportDirectory();
|
|||
|
|
final dir = await resolveImageDir(support.path);
|
|||
|
|
final largeFiles = [ItConfig.largeFileA, ItConfig.largeFileB]
|
|||
|
|
.map((name) => p.join(dir, name))
|
|||
|
|
.toList();
|
|||
|
|
for (final file in largeFiles) {
|
|||
|
|
final task = await harness
|
|||
|
|
.container
|
|||
|
|
.read(uploadPreparerProvider)
|
|||
|
|
.prepare(
|
|||
|
|
accountId: account,
|
|||
|
|
albumId: album.id,
|
|||
|
|
sourcePath: file,
|
|||
|
|
);
|
|||
|
|
expect(task.state, UploadTaskState.queued, reason: '入队 $file');
|
|||
|
|
}
|
|||
|
|
debugPrint('[IT] 2 张大图已入队:$largeFiles');
|
|||
|
|
|
|||
|
|
// 等首个任务进入 uploading(引擎已领取、原生桥接已注册)。
|
|||
|
|
await pollUntil<Map<UploadTaskState, int>>(
|
|||
|
|
tester,
|
|||
|
|
() => queueSnapshot(harness, account),
|
|||
|
|
(counts) =>
|
|||
|
|
(counts[UploadTaskState.uploading] ?? 0) >= 1 ||
|
|||
|
|
(counts[UploadTaskState.uploadedConfirming] ?? 0) >= 1 ||
|
|||
|
|
(counts[UploadTaskState.completed] ?? 0) >= 1,
|
|||
|
|
timeout: const Duration(seconds: 60),
|
|||
|
|
because: '任务进入传输中(脚本随后执行 HOME/force-stop)',
|
|||
|
|
);
|
|||
|
|
// 脚本协调点:轮询本行出现在 flutter test 输出后立即动作。
|
|||
|
|
debugPrint('[IT] MARKER:UPLOADING');
|
|||
|
|
|
|||
|
|
// HOME 场景:后台完成 → 本断言通过;KILL 场景:进程死于此循环
|
|||
|
|
// (非零退出是预期,脚本以 || true 容忍并以 verify 阶段收口)。
|
|||
|
|
await waitAllCompleted(tester, harness, account, 2);
|
|||
|
|
debugPrint('[IT] MARKER:BG_COMPLETED');
|
|||
|
|
} else if (phase == 'verify') {
|
|||
|
|
await tester.pumpWidget(harness.appWidget());
|
|||
|
|
await loginViaUi(tester, account, ItConfig.mainPassword);
|
|||
|
|
// 崩溃恢复收敛断言:in-flight 残留态经 waitingNetwork 重传幂等收口。
|
|||
|
|
await waitAllCompleted(tester, harness, account, 2);
|
|||
|
|
debugPrint('[IT] verify:2 张大图在持久任务库中均为 completed');
|
|||
|
|
|
|||
|
|
// 服务端侧旁证:跨设备上传历史 2 条已确认记录。
|
|||
|
|
final api = harness.container.read(apiClientProvider);
|
|||
|
|
final page = await api.listUploads(limit: 20);
|
|||
|
|
final confirmed = page.items
|
|||
|
|
.where((record) => record.albumId.isNotEmpty)
|
|||
|
|
.length;
|
|||
|
|
expect(confirmed, greaterThanOrEqualTo(2));
|
|||
|
|
debugPrint('[IT] verify:服务端上传历史 $confirmed 条 OK');
|
|||
|
|
} else {
|
|||
|
|
fail('未知 IT_PHASE:$phase');
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
timeout: const Timeout(Duration(minutes: 10)),
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
tearDownAll(harness.container.dispose);
|
|||
|
|
}
|