217 lines
7.5 KiB
Dart
217 lines
7.5 KiB
Dart
// ignore_for_file: depend_on_referenced_packages
|
|
import 'package:fake_async/fake_async.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
import 'package:pj_supermarket_cloud/common/index.dart';
|
|
|
|
PrintHistoryEntity _task(PrintStatus status) => PrintHistoryEntity(
|
|
orderId: '8001',
|
|
serviceOrderId: '8001',
|
|
servicePrintId: '9001',
|
|
invoiceUrl: 'https://example.test/invoice.png',
|
|
printType: '1',
|
|
status: status,
|
|
createTime: DateTime(2026, 1, 1),
|
|
);
|
|
|
|
ServicePrintRecord _record(String status) => ServicePrintRecord(
|
|
id: '9001',
|
|
orderId: '8001',
|
|
printType: '1',
|
|
printNo: 1,
|
|
retryVersion: 0,
|
|
attemptCount: 0,
|
|
status: status,
|
|
);
|
|
|
|
PrintAwaitTracker buildTracker(
|
|
FakeAsync async, {
|
|
required Future<List<PrintHistoryEntity>> Function(String orderId) taskReader,
|
|
Future<List<ServicePrintRecord>> Function(String orderId)? recordsReader,
|
|
bool Function()? availableReader,
|
|
Future<void> Function()? pendingPuller,
|
|
}) {
|
|
// fake_async 只虚拟化 Timer,不虚拟化 DateTime.now;用固定起点 + 已流逝
|
|
// 虚拟时长拼出与定时器同步推进的注入时钟。
|
|
final baseTime = DateTime(2026, 1, 1);
|
|
return PrintAwaitTracker(
|
|
orderIds: const ['8001'],
|
|
printType: '1',
|
|
clock: () => baseTime.add(async.elapsed),
|
|
taskReader: taskReader,
|
|
recordsReader: recordsReader ?? (_) async => const [],
|
|
availableReader: availableReader ?? (() => true),
|
|
pendingPuller: pendingPuller ?? () async {},
|
|
);
|
|
}
|
|
|
|
void main() {
|
|
test('打印机物理不可用时直接进入 offline 终态', () {
|
|
fakeAsync((async) {
|
|
final tracker = buildTracker(
|
|
async,
|
|
taskReader: (_) async => [],
|
|
availableReader: () => false,
|
|
);
|
|
tracker.start();
|
|
expect(tracker.phase, PrintAwaitPhase.offline);
|
|
expect(tracker.phase.isTerminal, isTrue);
|
|
async.elapse(const Duration(seconds: 20));
|
|
// offline 后不再轮询,阶段保持不变。
|
|
expect(tracker.phase, PrintAwaitPhase.offline);
|
|
tracker.dispose();
|
|
});
|
|
});
|
|
|
|
test('本地任务从 NONE 变 SUCCESS 时经历 printing 后进入 done', () {
|
|
fakeAsync((async) {
|
|
var status = PrintStatus.NONE;
|
|
final tracker =
|
|
buildTracker(async, taskReader: (_) async => [_task(status)]);
|
|
final phases = <PrintAwaitPhase>[];
|
|
tracker.addListener(() => phases.add(tracker.phase));
|
|
tracker.start();
|
|
async.elapse(const Duration(seconds: 1));
|
|
expect(tracker.phase, PrintAwaitPhase.printing);
|
|
status = PrintStatus.SUCCESS;
|
|
async.elapse(const Duration(seconds: 1));
|
|
expect(tracker.phase, PrintAwaitPhase.done);
|
|
expect(tracker.phase.isTerminal, isTrue);
|
|
expect(phases,
|
|
containsAll([PrintAwaitPhase.printing, PrintAwaitPhase.done]));
|
|
tracker.dispose();
|
|
});
|
|
});
|
|
|
|
test('寻单与补拉宽限内都没有任务才判定本单无需打印', () {
|
|
fakeAsync((async) {
|
|
var pullCount = 0;
|
|
final tracker = buildTracker(
|
|
async,
|
|
taskReader: (_) async => [],
|
|
pendingPuller: () async => pullCount++,
|
|
);
|
|
tracker.start();
|
|
// 启动立即补拉一次。
|
|
expect(pullCount, 1);
|
|
async.elapse(const Duration(seconds: 3));
|
|
expect(tracker.phase, PrintAwaitPhase.preparing);
|
|
// 寻单窗口到期触发第二次补拉,进入宽限期。
|
|
async.elapse(const Duration(seconds: 5));
|
|
expect(pullCount, 2);
|
|
expect(tracker.phase, PrintAwaitPhase.preparing);
|
|
// 宽限期结束仍无任务痕迹才收尾。
|
|
async.elapse(const Duration(seconds: 5));
|
|
expect(pullCount, 2);
|
|
expect(tracker.phase, PrintAwaitPhase.noPrintNeeded);
|
|
tracker.dispose();
|
|
});
|
|
});
|
|
|
|
test('MQTT 断连时补拉把任务落库后进入打印中', () {
|
|
fakeAsync((async) {
|
|
var hasTask = false;
|
|
var pullCount = 0;
|
|
Future<void> pull() async {
|
|
pullCount++;
|
|
// 寻单到期那次补拉成功把服务端待办拉回本地队列。
|
|
if (pullCount >= 2) hasTask = true;
|
|
}
|
|
|
|
final tracker = buildTracker(
|
|
async,
|
|
taskReader: (_) async =>
|
|
hasTask ? [_task(PrintStatus.NONE)] : const <PrintHistoryEntity>[],
|
|
pendingPuller: pull,
|
|
);
|
|
tracker.start();
|
|
async.elapse(const Duration(seconds: 3));
|
|
expect(tracker.phase, PrintAwaitPhase.preparing);
|
|
async.elapse(const Duration(seconds: 4));
|
|
expect(hasTask, isTrue);
|
|
expect(tracker.phase, PrintAwaitPhase.printing);
|
|
tracker.dispose();
|
|
});
|
|
});
|
|
|
|
test('服务端记录确认有票后暴露 taskLocated 供角标提前浮现', () {
|
|
fakeAsync((async) {
|
|
var located = false;
|
|
final tracker = buildTracker(
|
|
async,
|
|
taskReader: (_) async => [],
|
|
recordsReader: (_) async =>
|
|
located ? [_record('0')] : const <ServicePrintRecord>[],
|
|
);
|
|
tracker.start();
|
|
expect(tracker.taskLocated, isFalse);
|
|
located = true;
|
|
async.elapse(const Duration(seconds: 2));
|
|
expect(tracker.taskLocated, isTrue);
|
|
// 记录还是待生成状态,界面阶段保持准备中。
|
|
expect(tracker.phase, PrintAwaitPhase.preparing);
|
|
tracker.dispose();
|
|
});
|
|
});
|
|
|
|
test('服务端已发布但本地未收到任务时保持准备中,不虚报打印中', () {
|
|
fakeAsync((async) {
|
|
var dispatched = false;
|
|
final tracker = buildTracker(
|
|
async,
|
|
taskReader: (_) async => [],
|
|
recordsReader: (_) async =>
|
|
dispatched ? [_record('1')] : const <ServicePrintRecord>[],
|
|
);
|
|
tracker.start();
|
|
dispatched = true;
|
|
async.elapse(const Duration(seconds: 2));
|
|
// 服务端 status '1' 只代表已发布;本地队列没有任务就不是真的在打印。
|
|
expect(tracker.phase, PrintAwaitPhase.preparing);
|
|
// 但任务存在已被确认,收银角标可以提前浮现"正在准备发票"。
|
|
expect(tracker.taskLocated, isTrue);
|
|
// 有票但本地一直收不到(MQTT 断连且补拉无果)时,等待到总上限收尾。
|
|
async.elapse(const Duration(seconds: 14));
|
|
expect(tracker.phase, PrintAwaitPhase.timeout);
|
|
tracker.dispose();
|
|
});
|
|
});
|
|
|
|
test('任务长时间未完成时在等待上限进入 timeout', () {
|
|
fakeAsync((async) {
|
|
final tracker = buildTracker(
|
|
async, taskReader: (_) async => [_task(PrintStatus.NONE)]);
|
|
tracker.start();
|
|
async.elapse(const Duration(seconds: 14));
|
|
expect(tracker.phase, PrintAwaitPhase.printing);
|
|
async.elapse(const Duration(seconds: 2));
|
|
expect(tracker.phase, PrintAwaitPhase.timeout);
|
|
tracker.dispose();
|
|
});
|
|
});
|
|
|
|
test('本地任务进入 ERROR 判定打印失败', () {
|
|
fakeAsync((async) {
|
|
final tracker = buildTracker(
|
|
async, taskReader: (_) async => [_task(PrintStatus.ERROR)]);
|
|
tracker.start();
|
|
async.elapse(const Duration(seconds: 1));
|
|
expect(tracker.phase, PrintAwaitPhase.failed);
|
|
tracker.dispose();
|
|
});
|
|
});
|
|
|
|
test('非期望凭证类型的任务不参与等待判定', () {
|
|
fakeAsync((async) {
|
|
final other = _task(PrintStatus.NONE)..printType = '2';
|
|
final tracker =
|
|
buildTracker(async, taskReader: (_) async => [other]);
|
|
tracker.start();
|
|
// 寻单窗口加补拉宽限全部走完,printType '2' 的兑换券任务对发票等待不可见。
|
|
async.elapse(const Duration(seconds: 11));
|
|
expect(tracker.phase, PrintAwaitPhase.noPrintNeeded);
|
|
tracker.dispose();
|
|
});
|
|
});
|
|
}
|