feat(workbench): integrate operational workflows and map state

This commit is contained in:
2026-09-24 04:34:46 +08:00
parent 1752b9ef84
commit 436d5500f1
76 changed files with 5081 additions and 1937 deletions
@@ -1,7 +1,7 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import {
fetchWorkbenchAnalysisRun,
fetchWorkbenchRuntimeSnapshot
parseWorkbenchRuntimeSnapshot
} from "./workbench-runtime-client";
const projectPage = {
@@ -89,16 +89,8 @@ const runtimeSnapshot = {
describe("workbench runtime API client", () => {
afterEach(() => vi.unstubAllGlobals());
it("resolves the project and maps a persisted scheduled condition", async () => {
const fetchMock = vi
.fn()
.mockResolvedValueOnce(new Response(JSON.stringify(projectPage), { status: 200 }))
.mockResolvedValueOnce(new Response(JSON.stringify(runtimeSnapshot), { status: 200 }));
vi.stubGlobal("fetch", fetchMock);
const snapshot = await fetchWorkbenchRuntimeSnapshot(
vi.fn().mockResolvedValue("access-token")
);
it("maps a persisted scheduled condition", () => {
const snapshot = parseWorkbenchRuntimeSnapshot(runtimeSnapshot);
expect(snapshot.source).toBe("scheduled_tasks");
expect(snapshot.conditions).toEqual([
@@ -140,17 +132,10 @@ describe("workbench runtime API client", () => {
})
]));
expect(snapshot.simulationOverlay?.features).toHaveLength(1);
const runtimeHeaders = new Headers(fetchMock.mock.calls[1]?.[1]?.headers);
expect(runtimeHeaders.get("Authorization")).toBe("Bearer access-token");
expect(runtimeHeaders.get("X-Project-Id")).toBe("project-1");
expect(String(fetchMock.mock.calls[1]?.[0])).toContain("/api/v1/workbench/runtime");
});
it("keeps legacy analysis conditions out of the operational task feed", async () => {
const fetchMock = vi
.fn()
.mockResolvedValueOnce(new Response(JSON.stringify(projectPage), { status: 200 }))
.mockResolvedValueOnce(new Response(JSON.stringify({
it("keeps legacy analysis conditions out of the operational task feed", () => {
const snapshot = parseWorkbenchRuntimeSnapshot({
...runtimeSnapshot,
source: "analysis_runs",
conditions: [{
@@ -168,10 +153,7 @@ describe("workbench runtime API client", () => {
evidence: ["运行 ID:analysis-1"],
model_name: "TJWater analysis runtime"
}]
}), { status: 200 }));
vi.stubGlobal("fetch", fetchMock);
const snapshot = await fetchWorkbenchRuntimeSnapshot();
});
expect(snapshot.source).toBe("none");
expect(snapshot.conditions).toEqual([]);
@@ -182,16 +164,8 @@ describe("workbench runtime API client", () => {
expect(snapshot.scenarios).toHaveLength(1);
});
it("rejects an untyped runtime payload instead of treating it as real data", async () => {
const fetchMock = vi
.fn()
.mockResolvedValueOnce(new Response(JSON.stringify(projectPage), { status: 200 }))
.mockResolvedValueOnce(new Response(JSON.stringify({ ...runtimeSnapshot, source: "production" }), {
status: 200
}));
vi.stubGlobal("fetch", fetchMock);
await expect(fetchWorkbenchRuntimeSnapshot()).rejects.toThrow();
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 () => {
@@ -247,11 +221,8 @@ describe("workbench runtime API client", () => {
expect(headers.get("X-Project-Id")).toBe("project-1");
});
it("drops invalid backend map geometry without discarding other real sections", async () => {
const fetchMock = vi
.fn()
.mockResolvedValueOnce(new Response(JSON.stringify(projectPage), { status: 200 }))
.mockResolvedValueOnce(new Response(JSON.stringify({
it("drops invalid backend map geometry without discarding other real sections", () => {
const snapshot = parseWorkbenchRuntimeSnapshot({
...runtimeSnapshot,
latest_analysis: {
...runtimeSnapshot.latest_analysis,
@@ -264,10 +235,7 @@ describe("workbench runtime API client", () => {
}]
}
}
}), { status: 200 }));
vi.stubGlobal("fetch", fetchMock);
const snapshot = await fetchWorkbenchRuntimeSnapshot();
});
expect(snapshot.source).toBe("scheduled_tasks");
expect(snapshot.scenarios).toHaveLength(1);
@@ -276,7 +244,7 @@ describe("workbench runtime API client", () => {
expect(snapshot.latestAnalysisArtifact).not.toHaveProperty("spatialOverlay");
});
it("maps provisioned backend work orders without using the demo shape", async () => {
it("maps provisioned backend work orders without using the demo shape", () => {
const backendWorkOrder = {
id: "wo-1",
code: "WO-20260917-001",
@@ -334,21 +302,15 @@ describe("workbench runtime API client", () => {
reply_requirements: ["回传阀门开度"]
}]
};
const fetchMock = vi
.fn()
.mockResolvedValueOnce(new Response(JSON.stringify(projectPage), { status: 200 }))
.mockResolvedValueOnce(new Response(JSON.stringify({
const snapshot = parseWorkbenchRuntimeSnapshot({
...runtimeSnapshot,
work_order_availability: "ready",
work_orders: [backendWorkOrder]
}), { status: 200 }));
vi.stubGlobal("fetch", fetchMock);
const snapshot = await fetchWorkbenchRuntimeSnapshot();
});
expect(snapshot.workOrderAvailability).toBe("ready");
expect(snapshot.workOrders).toEqual([expect.objectContaining({
id: "wo-1",
id: "demo-work-order:wo-1",
kind: "work_order",
code: "WO-20260917-001",
status: "completed",
@@ -364,11 +326,8 @@ describe("workbench runtime API client", () => {
})]);
});
it("maps a provisioned backend alert without inventing a condition link", async () => {
const fetchMock = vi
.fn()
.mockResolvedValueOnce(new Response(JSON.stringify(projectPage), { status: 200 }))
.mockResolvedValueOnce(new Response(JSON.stringify({
it("maps a provisioned backend alert without inventing a condition link", () => {
const snapshot = parseWorkbenchRuntimeSnapshot({
...runtimeSnapshot,
alert_availability: "ready",
alerts: [{
@@ -384,10 +343,7 @@ describe("workbench runtime API client", () => {
occurred_at: "2026-09-17T02:29:00Z",
severity: "warning"
}]
}), { status: 200 }));
vi.stubGlobal("fetch", fetchMock);
const snapshot = await fetchWorkbenchRuntimeSnapshot();
});
expect(snapshot.alertAvailability).toBe("ready");
expect(snapshot.alerts).toEqual([expect.objectContaining({