Files
next-tjwater-frontend/src/features/workbench/hooks/use-workbench-runtime-data.test.ts
T
jiang a5e5acb03b fix(workbench): suppress unconfigured demo alerts
The not_configured branch previously promoted mock task failures into critical alerts. Return an explicit empty alert source so the exception badge reflects only actionable backend state.
2026-09-24 15:13:14 +08:00

369 lines
13 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
formatHeaderDataTime,
getMsUntilNextHeaderDataTimeTick,
resolveWorkbenchOperationalDate,
resolveWorkbenchRuntimeData
} from "./use-workbench-runtime-data";
import type { ScheduledConditionRecord, ScheduledWorkOrderItem } from "../types";
const SEPTEMBER_17 = new Date("2026-09-17T02:30:00Z");
const SEPTEMBER_18 = new Date("2026-09-18T02:30:00Z");
const mockCondition: ScheduledConditionRecord = {
id: "mock-condition",
kind: "condition",
taskId: "scada-diagnosis",
sessionId: "scheduled-condition-mock",
title: "SCADA 数据诊断",
summary: "mock",
status: "error",
riskLevel: "critical",
scheduledAt: "2026-09-17T10:00:00+08:00",
updatedAt: Date.parse("2026-09-17T10:00:00+08:00")
};
const backendCondition: ScheduledConditionRecord = {
...mockCondition,
id: "schedule-run-1",
taskId: "backend-schedule",
sessionId: "scheduled-condition-schedule-run-1",
title: "演示 合成低压调查",
modelName: "TJWater scheduler"
};
const mockWorkOrder: ScheduledWorkOrderItem = {
id: "mock-work-order",
kind: "work_order",
code: "WO-MOCK",
scheduledAt: "2026-09-17T11:00:00+08:00",
title: "演示工单",
summary: "mock",
status: "pending",
riskLevel: "normal",
updatedAt: Date.parse("2026-09-17T10:00:00+08:00"),
detail: "mock",
durationMinutes: 30,
source: "演示",
dispatcher: "调度中心",
priority: "normal",
location: "V-1",
assignee: "现场班组",
feedbackRequirements: ["回传设备状态"],
activity: []
};
describe("workbench runtime data", () => {
it("snaps the header time to the current five-minute interval", () => {
expect(formatHeaderDataTime(new Date(2026, 6, 22, 15, 43, 31))).toBe("2026-07-22 15:40");
});
it("preserves the backend observation minute", () => {
expect(formatHeaderDataTime(new Date(2026, 6, 22, 15, 43, 31), false)).toBe("2026-07-22 15:43");
});
it("schedules the next header refresh on the next interval boundary", () => {
expect(getMsUntilNextHeaderDataTimeTick(new Date(2026, 6, 22, 15, 43, 30))).toBe(90_000);
});
it("keeps mock conditions without inventing alerts when backend storage is not configured", () => {
const resolved = resolveWorkbenchRuntimeData([mockCondition], {
availability: "not_configured",
source: "none",
scenarioAvailability: "not_configured",
artifactAvailability: "not_configured",
scheduleAvailability: "not_configured",
workOrderAvailability: "not_configured",
alertAvailability: "not_configured",
generatedAt: "2026-09-17T02:30:00Z",
conditions: [],
alerts: [],
scenarios: [],
latestAnalysisArtifact: null,
simulationOverlay: null,
workOrders: []
}, { operationalDate: SEPTEMBER_17 });
expect(resolved.runtimeDataSource).toBe("mock");
expect(resolved.scheduledConditions).toEqual([mockCondition]);
expect(resolved.workbenchAlerts).toEqual([]);
expect(resolved.alertDataSource).toBe("none");
expect(resolved.scenarioDataSource).toBe("mock");
expect(resolved.artifactDataSource).toBe("mock");
expect(resolved.workOrderDataSource).toBe("none");
});
it("does not use frontend demo work orders as a backend fallback", () => {
const demoWorkOrder = { ...mockWorkOrder, dataSource: "mock" as const };
const resolved = resolveWorkbenchRuntimeData([mockCondition, demoWorkOrder], null, {
operationalDate: SEPTEMBER_17
});
expect(resolved.scheduledConditions).toEqual([mockCondition]);
expect(resolved.workOrderDataSource).toBe("none");
});
it("keeps mock mode separate from the selected backend history day", () => {
const backendAlert = {
id: "scheduled-condition-alert-schedule-run-1",
title: "演示 测试待复核",
description: "运行失败",
time: "10:25"
};
const backendSnapshot = {
availability: "ready" as const,
source: "scheduled_tasks" as const,
scenarioAvailability: "not_configured" as const,
artifactAvailability: "not_configured" as const,
scheduleAvailability: "not_configured" as const,
workOrderAvailability: "not_configured" as const,
alertAvailability: "not_configured" as const,
generatedAt: "2026-09-17T02:30:00Z",
conditions: [backendCondition],
alerts: [backendAlert],
scenarios: [],
latestAnalysisArtifact: null,
simulationOverlay: null,
workOrders: []
};
const resolved = resolveWorkbenchRuntimeData([mockCondition], backendSnapshot, {
operationalDate: SEPTEMBER_17,
runtimeMode: "history"
});
expect(resolved.runtimeDataSource).toBe("mock");
expect(resolved.scheduledConditions).toEqual([mockCondition]);
expect(resolved.workbenchAlerts).toEqual([]);
expect(resolved.alertDataSource).toBe("none");
expect(resolved.scenarioDataSource).toBe("mock");
expect(resolved.workOrderDataSource).toBe("none");
const mockResolved = resolveWorkbenchRuntimeData([mockCondition], backendSnapshot, {
operationalDate: SEPTEMBER_17,
runtimeMode: "demo"
});
expect(mockResolved.runtimeDataSource).toBe("mock");
expect(mockResolved.scheduledConditions).toEqual([mockCondition]);
expect(mockResolved.workOrderDataSource).toBe("none");
});
it("anchors history mode to the latest day that contains backend work orders", () => {
const olderWorkOrder = {
...mockWorkOrder,
id: "older-backend-work-order",
scheduledAt: "2026-08-03T10:05:00+08:00"
};
const latestWorkOrder = {
...mockWorkOrder,
id: "latest-backend-work-order",
scheduledAt: "2026-08-17T17:40:00+08:00"
};
const futureWorkOrder = {
...mockWorkOrder,
id: "future-backend-work-order",
scheduledAt: "2026-09-20T10:00:00+08:00"
};
const now = new Date("2026-09-18T02:30:00Z");
const operationalDate = resolveWorkbenchOperationalDate({
availability: "ready",
source: "scheduled_tasks",
scenarioAvailability: "ready",
artifactAvailability: "ready",
scheduleAvailability: "ready",
workOrderAvailability: "ready",
alertAvailability: "ready",
generatedAt: "2026-09-18T02:30:00Z",
conditions: [backendCondition],
alerts: [],
scenarios: [],
latestAnalysisArtifact: null,
simulationOverlay: null,
workOrders: [olderWorkOrder, latestWorkOrder, futureWorkOrder]
}, { runtimeMode: "history", now });
expect(operationalDate.toISOString()).toBe("2026-08-17T09:40:00.000Z");
expect(resolveWorkbenchOperationalDate({
availability: "ready",
source: "scheduled_tasks",
scenarioAvailability: "ready",
artifactAvailability: "ready",
scheduleAvailability: "ready",
workOrderAvailability: "ready",
alertAvailability: "ready",
generatedAt: "2026-09-18T02:30:00Z",
conditions: [],
alerts: [],
scenarios: [],
latestAnalysisArtifact: null,
simulationOverlay: null,
workOrders: [olderWorkOrder, latestWorkOrder, futureWorkOrder]
}, { runtimeMode: "demo", now }).toISOString()).toBe("2026-08-17T09:40:00.000Z");
});
it("keeps current-day mock conditions when backend schedules are configured", () => {
const currentMockCondition = {
...mockCondition,
id: "mock-current-day",
scheduledAt: "2026-09-18T10:00:00+08:00"
};
const resolved = resolveWorkbenchRuntimeData([currentMockCondition], {
availability: "ready",
source: "scheduled_tasks",
scenarioAvailability: "ready",
artifactAvailability: "ready",
scheduleAvailability: "ready",
workOrderAvailability: "not_configured",
alertAvailability: "not_configured",
generatedAt: "2026-09-18T02:30:00Z",
conditions: [backendCondition],
alerts: [],
scenarios: [],
latestAnalysisArtifact: null,
simulationOverlay: null,
workOrders: []
}, { operationalDate: SEPTEMBER_18 });
expect(resolved.scheduledConditions).toEqual([currentMockCondition]);
expect(resolved.runtimeDataSource).toBe("mock");
});
it("ignores current-day backend conditions and keeps demo conditions", () => {
const currentMockCondition = {
...mockCondition,
id: "mock-current-day",
scheduledAt: "2026-09-18T10:00:00+08:00"
};
const currentBackendCondition = {
...backendCondition,
id: "backend-current-day",
scheduledAt: "2026-09-18T10:05:00+08:00"
};
const resolved = resolveWorkbenchRuntimeData([currentMockCondition], {
availability: "ready",
source: "scheduled_tasks",
scenarioAvailability: "not_configured",
artifactAvailability: "not_configured",
scheduleAvailability: "not_configured",
workOrderAvailability: "not_configured",
alertAvailability: "not_configured",
generatedAt: "2026-09-18T02:30:00Z",
conditions: [currentBackendCondition],
alerts: [],
scenarios: [],
latestAnalysisArtifact: null,
simulationOverlay: null,
workOrders: []
}, { operationalDate: SEPTEMBER_18 });
expect(resolved.scheduledConditions).toEqual([currentMockCondition]);
expect(resolved.runtimeDataSource).toBe("mock");
});
it("treats an empty configured backend work-order partition as authoritative", () => {
const resolved = resolveWorkbenchRuntimeData([mockCondition, mockWorkOrder], {
availability: "ready",
source: "none",
scenarioAvailability: "not_configured",
artifactAvailability: "not_configured",
scheduleAvailability: "not_configured",
workOrderAvailability: "ready",
alertAvailability: "not_configured",
generatedAt: "2026-09-17T02:30:00Z",
conditions: [],
alerts: [],
scenarios: [],
latestAnalysisArtifact: null,
simulationOverlay: null,
workOrders: []
}, { operationalDate: SEPTEMBER_17, runtimeMode: "backend" });
expect(resolved.scheduledConditions).toEqual([mockCondition]);
expect(resolved.runtimeDataSource).toBe("mock");
expect(resolved.workOrderDataSource).toBe("backend");
});
it("uses a provisioned alert list while other sections keep their mock fallback", () => {
const backendAlert = {
id: "backend-alert-alarm-1",
title: "泵站通信告警",
description: "来自正式告警同步适配器。",
severity: "warning" as const,
time: "10:29"
};
const resolved = resolveWorkbenchRuntimeData([mockCondition], {
availability: "ready",
source: "none",
scenarioAvailability: "not_configured",
artifactAvailability: "not_configured",
scheduleAvailability: "not_configured",
workOrderAvailability: "not_configured",
alertAvailability: "ready",
generatedAt: "2026-09-17T02:30:00Z",
conditions: [],
alerts: [backendAlert],
scenarios: [],
latestAnalysisArtifact: null,
simulationOverlay: null,
workOrders: []
}, { operationalDate: SEPTEMBER_17 });
expect(resolved.scheduledConditions).toEqual([mockCondition]);
expect(resolved.runtimeDataSource).toBe("mock");
expect(resolved.workbenchAlerts).toEqual([backendAlert]);
});
it("does not invent demo alerts when the backend alert partition is empty", () => {
const resolved = resolveWorkbenchRuntimeData([mockCondition], {
availability: "ready",
source: "none",
scenarioAvailability: "not_configured",
artifactAvailability: "not_configured",
scheduleAvailability: "ready",
workOrderAvailability: "not_configured",
alertAvailability: "ready",
generatedAt: "2026-09-17T02:30:00Z",
conditions: [],
alerts: [],
scenarios: [],
latestAnalysisArtifact: null,
simulationOverlay: null,
workOrders: []
}, { operationalDate: SEPTEMBER_17 });
expect(resolved.alertDataSource).toBe("backend");
expect(resolved.workbenchAlerts).toEqual([]);
});
it("uses valid backend records retained by a degraded partition", () => {
const backendWorkOrder = { ...mockWorkOrder, id: "backend-work-order", code: "WO-BACKEND" };
const backendAlert = {
id: "backend-alert-1",
title: "后端任务告警",
description: "合法记录与另一条坏记录同时存在。",
severity: "warning" as const,
time: "09-18 10:29"
};
const resolved = resolveWorkbenchRuntimeData([mockCondition, mockWorkOrder], {
availability: "degraded",
source: "none",
scenarioAvailability: "not_configured",
artifactAvailability: "not_configured",
scheduleAvailability: "not_configured",
workOrderAvailability: "degraded",
alertAvailability: "degraded",
generatedAt: "2026-09-18T02:30:00Z",
conditions: [],
alerts: [backendAlert],
scenarios: [],
latestAnalysisArtifact: null,
simulationOverlay: null,
workOrders: [backendWorkOrder]
}, { operationalDate: SEPTEMBER_17 });
expect(resolved.workbenchAlerts).toEqual([backendAlert]);
expect(resolved.alertDataSource).toBe("backend");
expect(resolved.scheduledConditions).toEqual([mockCondition, backendWorkOrder]);
expect(resolved.workOrderDataSource).toBe("backend");
});
});