164 lines
7.6 KiB
TypeScript
164 lines
7.6 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import test from "node:test";
|
|
import { backendEventToUiChunk } from "./cad-stream";
|
|
import { activeCheckpointPreview, latestSuccessfulResult } from "./cad-artifacts";
|
|
import { messagesForBackend } from "./cad-messages";
|
|
import type { CadUIMessage } from "./cad-types";
|
|
|
|
test("maps backend cad_result SSE into an AI SDK data part", () => {
|
|
const chunk = backendEventToUiChunk({
|
|
event: "cad_result",
|
|
data: { taskId: "cad_abc", revisionId: "rev_001" },
|
|
}, "text_1");
|
|
assert.equal(chunk?.type, "data-cad-result");
|
|
assert.deepEqual("data" in chunk! ? chunk.data : null, { taskId: "cad_abc", revisionId: "rev_001" });
|
|
});
|
|
|
|
test("keeps progressive revisions as separate data parts", () => {
|
|
const first = backendEventToUiChunk({ event: "cad_result", data: { taskId: "cad_abc", revisionId: "rev_001" } }, "text_1");
|
|
const second = backendEventToUiChunk({ event: "cad_result", data: { taskId: "cad_abc", revisionId: "rev_002" } }, "text_1");
|
|
assert.notEqual(first?.id, second?.id);
|
|
});
|
|
|
|
test("maps final visual repair review into a blocking progress state", () => {
|
|
const chunk = backendEventToUiChunk({
|
|
event: "final_review",
|
|
data: { taskId: "cad_abc", review: { verdict: "repair", confidence: 0.92, evidence: ["missing round"] } },
|
|
}, "text_1");
|
|
assert.equal(chunk?.type, "data-cad-progress");
|
|
assert.deepEqual("data" in chunk! ? chunk.data : null, {
|
|
step: "final_review", label: "最终视觉复核", status: "error", message: "missing round", taskId: "cad_abc",
|
|
});
|
|
});
|
|
|
|
test("maps a rejected independent candidate review into a blocking progress state", () => {
|
|
const chunk = backendEventToUiChunk({
|
|
event: "candidate_review",
|
|
data: { taskId: "cad_abc", review: { verdict: "reject", evidence: ["base is disconnected"] } },
|
|
}, "text_1");
|
|
assert.equal(chunk?.type, "data-cad-progress");
|
|
assert.deepEqual("data" in chunk! ? chunk.data : null, {
|
|
step: "candidate_review", label: "候选独立复核", status: "error", message: "base is disconnected", taskId: "cad_abc",
|
|
});
|
|
});
|
|
|
|
test("maps a modeling plan revision request into a blocking progress state", () => {
|
|
const chunk = backendEventToUiChunk({
|
|
event: "modeling_plan_review",
|
|
data: { taskId: "cad_abc", review: { verdict: "revise", issues: [{ message: "split unrelated finish" }] } },
|
|
}, "text_1");
|
|
assert.equal(chunk?.type, "data-cad-progress");
|
|
assert.equal("data" in chunk! ? (chunk.data as { label?: string }).label : null, "计划独立复核");
|
|
assert.equal("data" in chunk! ? (chunk.data as { status?: string }).status : null, "error");
|
|
});
|
|
|
|
test("keeps a server-verified plan skip visible in the timeline", () => {
|
|
const chunk = backendEventToUiChunk({
|
|
event: "plan_step_skipped",
|
|
data: {
|
|
taskId: "cad_abc",
|
|
planStepId: "step_6",
|
|
nextPlanStepId: "",
|
|
evidenceRef: "completion_ledger:rev_008",
|
|
status: "success",
|
|
message: "The current revision already satisfies this plan step.",
|
|
},
|
|
}, "text_1");
|
|
assert.equal(chunk?.type, "data-cad-progress");
|
|
assert.equal("data" in chunk! ? (chunk.data as { label?: string }).label : null, "计划步骤已满足");
|
|
assert.equal("data" in chunk! ? (chunk.data as { status?: string }).status : null, "success");
|
|
});
|
|
|
|
test("gives repeated tool events unique ordered parts", () => {
|
|
const first = backendEventToUiChunk({ event: "tool_call", data: { taskId: "cad_abc", tool: "inspect_model", status: "running" } }, "text_1", 4);
|
|
const second = backendEventToUiChunk({ event: "tool_call", data: { taskId: "cad_abc", tool: "inspect_model", status: "success" } }, "text_1", 5);
|
|
assert.notEqual(first?.id, second?.id);
|
|
assert.equal("data" in first! ? (first.data as { sequence?: number }).sequence : null, 4);
|
|
assert.equal("data" in second! ? (second.data as { sequence?: number }).sequence : null, 5);
|
|
});
|
|
|
|
test("reuses an invocation id so tool completion updates its running card", () => {
|
|
const running = backendEventToUiChunk({ event: "tool_call", data: { taskId: "cad_abc", eventId: "call_1", invocationId: "call_1", tool: "submit_cdsl_fragment", status: "running" } }, "text_1", 4);
|
|
const complete = backendEventToUiChunk({ event: "tool_call", data: { taskId: "cad_abc", eventId: "call_1", invocationId: "call_1", tool: "submit_cdsl_fragment", status: "success" } }, "text_1", 5);
|
|
assert.equal(running?.id, complete?.id);
|
|
});
|
|
|
|
test("keeps frozen requirement markdown visible in the timeline", () => {
|
|
const chunk = backendEventToUiChunk({
|
|
event: "requirements_document",
|
|
data: { taskId: "cad_abc", status: "frozen", markdown: "# 冻结需求\n\n- 创建底座" },
|
|
}, "text_1", 2);
|
|
assert.equal(chunk?.type, "data-cad-progress");
|
|
assert.equal("data" in chunk! ? (chunk.data as { markdown?: string }).markdown : null, "# 冻结需求\n\n- 创建底座");
|
|
});
|
|
|
|
test("restores the latest successful task revision for the viewer", () => {
|
|
const result = latestSuccessfulResult({
|
|
task_id: "cad_abc",
|
|
current_revision: "rev_002",
|
|
revisions: [
|
|
{ revision_id: "rev_001", status: "success", cdsl_path: "a", step_path: "b", glb_path: "c", report_path: "d" },
|
|
{ revision_id: "rev_002", status: "success", cdsl_path: "aa", step_path: "bb", glb_path: "cc", report_path: "dd", summary: "done" },
|
|
],
|
|
});
|
|
assert.equal(result?.glbPath, "cc");
|
|
assert.equal(result?.summary, "done");
|
|
});
|
|
|
|
test("prefers the published revision over an active checkpoint", () => {
|
|
const result = latestSuccessfulResult({
|
|
task_id: "cad_abc",
|
|
current_revision: "rev_002",
|
|
active_revision: "rev_002",
|
|
published_revision: "rev_001",
|
|
lifecycle: "running",
|
|
revisions: [
|
|
{ revision_id: "rev_001", status: "success", visibility: "final", cdsl_path: "a", step_path: "b", glb_path: "c", report_path: "d" },
|
|
{ revision_id: "rev_002", status: "success", visibility: "checkpoint", cdsl_path: "aa", step_path: "bb", glb_path: "cc", report_path: "dd" },
|
|
],
|
|
});
|
|
assert.equal(result?.revisionId, "rev_001");
|
|
assert.equal(result?.checkpoint, false);
|
|
});
|
|
|
|
test("does not restore a private checkpoint after a failed run", () => {
|
|
const result = latestSuccessfulResult({
|
|
task_id: "cad_abc",
|
|
current_revision: "rev_002",
|
|
active_revision: "rev_002",
|
|
lifecycle: "failed",
|
|
revisions: [
|
|
{ revision_id: "rev_002", status: "success", visibility: "checkpoint", cdsl_path: "aa", step_path: "bb", glb_path: "cc", report_path: "dd" },
|
|
],
|
|
});
|
|
assert.equal(result, null);
|
|
});
|
|
|
|
test("restores an active checkpoint only while the task is running", () => {
|
|
const result = activeCheckpointPreview({
|
|
task_id: "cad_abc", current_revision: "rev_002", active_revision: "rev_002", published_revision: "rev_001", lifecycle: "running",
|
|
revisions: [
|
|
{ revision_id: "rev_001", status: "success", visibility: "final", cdsl_path: "a", step_path: "b", glb_path: "c", report_path: "d" },
|
|
{ revision_id: "rev_002", status: "success", visibility: "checkpoint", cdsl_path: "aa", step_path: "bb", glb_path: "cc", report_path: "dd" },
|
|
],
|
|
});
|
|
assert.equal(result?.revisionId, "rev_002");
|
|
assert.equal(result?.checkpoint, true);
|
|
});
|
|
|
|
test("strips non-text and non-CAD parts before sending to FastAPI", () => {
|
|
const messages: CadUIMessage[] = [{
|
|
id: "m1",
|
|
role: "assistant",
|
|
parts: [
|
|
{ type: "text", text: "hello" },
|
|
{ type: "data-cad-progress", data: { step: "s", label: "S", status: "running" } },
|
|
{ type: "file", mediaType: "model/gltf-binary", url: "/x.glb" },
|
|
],
|
|
}];
|
|
assert.deepEqual(messagesForBackend(messages)[0].parts, [
|
|
{ type: "text", text: "hello" },
|
|
{ type: "data-cad-progress", data: { step: "s", label: "S", status: "running" } },
|
|
]);
|
|
});
|