322 lines
11 KiB
TypeScript
322 lines
11 KiB
TypeScript
import { afterEach, describe, expect, it, vi } from "vitest";
|
||
import {
|
||
fetchWorkbenchAnalysisRun,
|
||
parseWorkbenchRuntimeSnapshot
|
||
} from "./workbench-runtime-client";
|
||
|
||
const projectPage = {
|
||
items: [{ project_id: "project-1", gs_workspace: "tjwater_next", status: "active" }],
|
||
total: 1,
|
||
limit: 1000,
|
||
offset: 0
|
||
};
|
||
|
||
const runtimeSnapshot = {
|
||
availability: "ready",
|
||
source: "scheduled_tasks",
|
||
scenario_availability: "ready",
|
||
artifact_availability: "ready",
|
||
schedule_availability: "not_configured",
|
||
work_order_availability: "not_configured",
|
||
alert_availability: "ready",
|
||
generated_at: "2026-09-17T02:30:00Z",
|
||
conditions: [{
|
||
id: "schedule-run-1",
|
||
task_id: "backend-schedule",
|
||
session_id: "scheduled-condition-schedule-run-1",
|
||
title: "演示 合成低压调查",
|
||
summary: "来自后端持久化 Action Run 的 演示 沙箱测试状态。",
|
||
status: "error",
|
||
risk_level: "critical",
|
||
scheduled_at: "2026-09-17T02:20:00Z",
|
||
updated_at: "2026-09-17T02:25:00Z",
|
||
detail: "演示 测试运行状态:已失败。",
|
||
recommendation: "请检查失败步骤。",
|
||
evidence: ["运行状态:failed"],
|
||
model_name: "TJWater scheduler"
|
||
}],
|
||
alerts: [{
|
||
id: "scheduled-condition-alert-schedule-run-1",
|
||
condition_id: "schedule-run-1",
|
||
source: {
|
||
task_type: "analysis_task",
|
||
task_id: "backend-schedule",
|
||
run_id: "schedule-run-1"
|
||
},
|
||
title: "演示 测试待复核:演示 合成低压调查",
|
||
description: "运行失败。",
|
||
occurred_at: "2026-09-17T02:25:00Z",
|
||
severity: "critical"
|
||
}],
|
||
scenarios: [{
|
||
id: "analysis-run-analysis-1",
|
||
run_id: "analysis-1",
|
||
name: "北区爆管定位",
|
||
description: "爆管定位 · 已完成 · 创建人 alice",
|
||
status: "review",
|
||
run_type: "burst_location",
|
||
run_type_label: "爆管定位",
|
||
run_status: "completed",
|
||
created_at: "2026-09-17T02:20:00Z"
|
||
}],
|
||
latest_analysis: {
|
||
run_id: "analysis-1",
|
||
name: "北区爆管定位",
|
||
run_type: "burst_location",
|
||
run_type_label: "爆管定位",
|
||
status: "completed",
|
||
status_label: "已完成",
|
||
created_by: "alice",
|
||
created_at: "2026-09-17T02:20:00Z",
|
||
started_at: "2026-09-17T02:10:00Z",
|
||
results: [{
|
||
result_type: "burst_location",
|
||
created_at: "2026-09-17T02:20:00Z",
|
||
facts: [{ label: "关联管段", value: "P-42" }]
|
||
}],
|
||
map_payload: {
|
||
type: "FeatureCollection",
|
||
features: [{
|
||
type: "Feature",
|
||
properties: { label: "后端爆管定位" },
|
||
geometry: { type: "Point", coordinates: [121.5, 30.9] }
|
||
}]
|
||
}
|
||
},
|
||
work_orders: []
|
||
};
|
||
|
||
describe("workbench runtime API client", () => {
|
||
afterEach(() => vi.unstubAllGlobals());
|
||
|
||
it("maps a persisted scheduled condition", () => {
|
||
const snapshot = parseWorkbenchRuntimeSnapshot(runtimeSnapshot);
|
||
|
||
expect(snapshot.source).toBe("scheduled_tasks");
|
||
expect(snapshot.conditions).toEqual([
|
||
expect.objectContaining({
|
||
id: "schedule-run-1",
|
||
kind: "condition",
|
||
taskId: "backend-schedule",
|
||
status: "error",
|
||
modelName: "TJWater scheduler"
|
||
})
|
||
]);
|
||
expect(snapshot.alerts[0]).toEqual(expect.objectContaining({
|
||
conditionId: "schedule-run-1",
|
||
id: "scheduled-condition-alert-schedule-run-1",
|
||
title: "演示 测试待复核:演示 合成低压调查",
|
||
source: {
|
||
taskType: "analysis_task",
|
||
taskId: "backend-schedule",
|
||
runId: "schedule-run-1"
|
||
}
|
||
}));
|
||
expect(snapshot.scenarios[0]).toEqual(expect.objectContaining({
|
||
id: "analysis-run-analysis-1",
|
||
runId: "analysis-1",
|
||
runType: "burst_location"
|
||
}));
|
||
expect(snapshot.latestAnalysisArtifact).toEqual(expect.objectContaining({
|
||
id: "persisted-analysis-analysis-1",
|
||
title: "北区爆管定位",
|
||
confidence: "后端未提供",
|
||
mapRelation: "required",
|
||
spatialOverlay: expect.objectContaining({ type: "FeatureCollection" }),
|
||
sources: ["项目 BizDB · analysis.runs", "项目 BizDB · analysis.results"]
|
||
}));
|
||
expect(snapshot.latestAnalysisArtifact?.blocks).toEqual(expect.arrayContaining([
|
||
expect.objectContaining({
|
||
kind: "data-table",
|
||
rows: [expect.objectContaining({ facts: "关联管段:P-42" })]
|
||
})
|
||
]));
|
||
expect(snapshot.simulationOverlay?.features).toHaveLength(1);
|
||
});
|
||
|
||
it("keeps legacy analysis conditions out of the operational task feed", () => {
|
||
const snapshot = parseWorkbenchRuntimeSnapshot({
|
||
...runtimeSnapshot,
|
||
source: "analysis_runs",
|
||
conditions: [{
|
||
id: "analysis-run-analysis-1",
|
||
task_id: "backend-analysis",
|
||
session_id: "scheduled-condition-analysis-run-analysis-1",
|
||
title: "北区爆管定位",
|
||
summary: "来自旧版后端的分析运行投影。",
|
||
status: "completed",
|
||
risk_level: "normal",
|
||
scheduled_at: "2026-09-17T02:10:00Z",
|
||
updated_at: "2026-09-17T02:20:00Z",
|
||
detail: "分析结果已完成。",
|
||
recommendation: null,
|
||
evidence: ["运行 ID:analysis-1"],
|
||
model_name: "TJWater analysis runtime"
|
||
}]
|
||
});
|
||
|
||
expect(snapshot.source).toBe("none");
|
||
expect(snapshot.conditions).toEqual([]);
|
||
expect(snapshot.latestAnalysisArtifact).toEqual(expect.objectContaining({
|
||
id: "persisted-analysis-analysis-1",
|
||
title: "北区爆管定位"
|
||
}));
|
||
expect(snapshot.scenarios).toHaveLength(1);
|
||
});
|
||
|
||
it("rejects an untyped runtime payload instead of treating it as real data", () => {
|
||
expect(() => parseWorkbenchRuntimeSnapshot({ ...runtimeSnapshot, source: "production" })).toThrow();
|
||
});
|
||
|
||
it("loads the requested analysis run instead of reusing the latest result", async () => {
|
||
const runId = "86fd7c19-5b75-434f-999c-ec8fdf67d10b";
|
||
const selectedAnalysis = {
|
||
...runtimeSnapshot.latest_analysis,
|
||
run_id: runId,
|
||
name: "历史爆管定位",
|
||
results: [{
|
||
result_type: "burst_location",
|
||
created_at: "2026-09-17T01:20:00Z",
|
||
facts: [{ label: "关联管段", value: "P-older" }]
|
||
}],
|
||
map_payload: {
|
||
type: "FeatureCollection",
|
||
features: [{
|
||
type: "Feature",
|
||
properties: { label: "历史运行几何" },
|
||
geometry: { type: "Point", coordinates: [121.4, 30.8] }
|
||
}]
|
||
}
|
||
};
|
||
const fetchMock = vi
|
||
.fn()
|
||
.mockResolvedValueOnce(new Response(JSON.stringify(projectPage), { status: 200 }))
|
||
.mockResolvedValueOnce(new Response(JSON.stringify(selectedAnalysis), { status: 200 }));
|
||
vi.stubGlobal("fetch", fetchMock);
|
||
|
||
const projection = await fetchWorkbenchAnalysisRun(
|
||
runId,
|
||
vi.fn().mockResolvedValue("access-token")
|
||
);
|
||
|
||
expect(projection.runId).toBe(runId);
|
||
expect(projection.artifact).toEqual(expect.objectContaining({
|
||
id: `persisted-analysis-${runId}`,
|
||
title: "历史爆管定位"
|
||
}));
|
||
expect(projection.artifact.blocks).toEqual(expect.arrayContaining([
|
||
expect.objectContaining({
|
||
kind: "data-table",
|
||
rows: [expect.objectContaining({ facts: "关联管段:P-older" })]
|
||
})
|
||
]));
|
||
expect(projection.simulationOverlay?.features[0]?.properties).toEqual({
|
||
label: "历史运行几何"
|
||
});
|
||
expect(String(fetchMock.mock.calls[1]?.[0])).toContain(
|
||
`/api/v1/workbench/analysis-runs/${runId}`
|
||
);
|
||
const headers = new Headers(fetchMock.mock.calls[1]?.[1]?.headers);
|
||
expect(headers.get("Authorization")).toBe("Bearer access-token");
|
||
expect(headers.get("X-Project-Id")).toBe("project-1");
|
||
});
|
||
|
||
it("drops invalid backend map geometry without discarding other real sections", () => {
|
||
const snapshot = parseWorkbenchRuntimeSnapshot({
|
||
...runtimeSnapshot,
|
||
latest_analysis: {
|
||
...runtimeSnapshot.latest_analysis,
|
||
map_payload: {
|
||
type: "FeatureCollection",
|
||
features: [{
|
||
type: "Feature",
|
||
properties: {},
|
||
geometry: { type: "Point", coordinates: ["east", "north"] }
|
||
}]
|
||
}
|
||
}
|
||
});
|
||
|
||
expect(snapshot.source).toBe("scheduled_tasks");
|
||
expect(snapshot.scenarios).toHaveLength(1);
|
||
expect(snapshot.simulationOverlay).toBeNull();
|
||
expect(snapshot.latestAnalysisArtifact?.mapRelation).toBe("none");
|
||
expect(snapshot.latestAnalysisArtifact).not.toHaveProperty("spatialOverlay");
|
||
});
|
||
|
||
it("maps provisioned backend work orders without using the demo shape", () => {
|
||
const backendWorkOrder = {
|
||
id: "wo-1",
|
||
code: "WO-20260917-001",
|
||
scheduled_at: "2026-09-17T03:00:00Z",
|
||
title: "北区阀门现场复核",
|
||
summary: "复核阀门开度并回传。",
|
||
status: "completed",
|
||
risk_level: "attention",
|
||
updated_at: "2026-09-17T02:30:00Z",
|
||
detail: "来自正式工单同步适配器。",
|
||
recommendation: "完成后复核压力。",
|
||
duration_minutes: 45,
|
||
source: "生产工单系统",
|
||
dispatcher: "调度中心",
|
||
priority: "urgent",
|
||
location: "北区阀门 V-1",
|
||
assignee: "北区班组",
|
||
feedback_requirements: ["回传阀门开度"],
|
||
activity: [{ sequence: 1, occurred_at: "2026-09-17T02:30:00Z", actor: "调度中心",
|
||
kind: "created", title: "工单创建", detail: "等待现场复核。" }]
|
||
};
|
||
const snapshot = parseWorkbenchRuntimeSnapshot({
|
||
...runtimeSnapshot,
|
||
work_order_availability: "ready",
|
||
work_orders: [backendWorkOrder]
|
||
});
|
||
|
||
expect(snapshot.workOrderAvailability).toBe("ready");
|
||
expect(snapshot.workOrders).toEqual([expect.objectContaining({
|
||
id: "demo-work-order:wo-1",
|
||
kind: "work_order",
|
||
code: "WO-20260917-001",
|
||
status: "completed",
|
||
source: "生产工单系统",
|
||
location: "北区阀门 V-1",
|
||
assignee: "北区班组",
|
||
feedbackRequirements: ["回传阀门开度"],
|
||
activity: [expect.objectContaining({ title: "工单创建" })]
|
||
})]);
|
||
});
|
||
|
||
it("maps a provisioned backend alert without inventing a condition link", () => {
|
||
const snapshot = parseWorkbenchRuntimeSnapshot({
|
||
...runtimeSnapshot,
|
||
alert_availability: "ready",
|
||
alerts: [{
|
||
id: "backend-alert-alarm-1",
|
||
condition_id: null,
|
||
source: {
|
||
task_type: "scheduled_task",
|
||
task_id: "scada-diagnosis-5m",
|
||
run_id: "trigger-1"
|
||
},
|
||
title: "泵站通信告警",
|
||
description: "由 SCADA 数据诊断任务明确推送。",
|
||
occurred_at: "2026-09-17T02:29:00Z",
|
||
severity: "warning"
|
||
}]
|
||
});
|
||
|
||
expect(snapshot.alertAvailability).toBe("ready");
|
||
expect(snapshot.alerts).toEqual([expect.objectContaining({
|
||
id: "backend-alert-alarm-1",
|
||
title: "泵站通信告警",
|
||
severity: "warning",
|
||
source: {
|
||
taskType: "scheduled_task",
|
||
taskId: "scada-diagnosis-5m",
|
||
runId: "trigger-1"
|
||
}
|
||
})]);
|
||
expect(snapshot.alerts[0]).not.toHaveProperty("conditionId");
|
||
});
|
||
});
|