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.
369 lines
13 KiB
TypeScript
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");
|
|
});
|
|
});
|