Files
mnote/wolai-frontend/src/app/api/tree/commands/route.test.ts
T

1747 lines
50 KiB
TypeScript
Raw Normal View History

2026-04-24 06:10:18 +08:00
import { beforeEach, describe, expect, it, vi } from "vitest";
import { TREE_3000_ROUTE_BOUNDARY_MANIFEST } from "@/lib/tree-route-boundary";
2026-04-24 06:10:18 +08:00
const mockIsConvexEnabled = vi.fn(() => true);
const mockGetAuthedConvexClient = vi.fn();
const mockBuildDocumentBridgeContext = vi.fn();
const mockBuildDocumentCommandEnvelope = vi.fn();
const mockResolveRustBridgeCommandPlan = vi.fn();
const mockExecuteRustBridgeMutationTransport = vi.fn();
2026-04-26 19:35:52 +08:00
const mockRecordRustBridgeCommandArtifacts = vi.fn();
2026-04-26 04:29:23 +08:00
const mockRecordBridgeCommandArtifacts = vi.fn();
const mockRecordBridgeCommandFailureArtifacts = vi.fn();
2026-04-24 06:10:18 +08:00
const mockDocumentBridgeErrorResponse = vi.fn((error: unknown) =>
Response.json(
{
error: error instanceof Error ? error.message : String(error),
},
2026-04-26 04:29:23 +08:00
{
status:
typeof (error as { status?: unknown })?.status === "number"
? ((error as { status: number }).status ?? 500)
: 500,
},
2026-04-24 06:10:18 +08:00
),
);
const mockEnsureDocumentScaffold = vi.fn();
2026-04-26 04:29:23 +08:00
const mockCopyMindmapFilesIfExists = vi.fn();
const mockLoadSidebarDataFromConvex = vi.fn();
2026-04-24 06:10:18 +08:00
vi.mock("@/lib/convex/enabled", () => ({
isConvexEnabled: () => mockIsConvexEnabled(),
}));
vi.mock("@/lib/convex/api", () => ({
api: {
documents: {
getMeta: "documents:getMeta",
2026-04-26 04:29:23 +08:00
getContent: "documents:getContent",
2026-04-24 06:10:18 +08:00
},
workspaces: {
ensureDefaultWorkspace: "workspaces:ensureDefaultWorkspace",
},
},
}));
vi.mock("@/lib/convex/route", () => ({
getAuthedConvexClient: mockGetAuthedConvexClient,
}));
vi.mock("@/lib/documents/bridge", () => ({
assertDocumentId: (value: string | null | undefined) => {
const normalized = typeof value === "string" ? value.trim() : "";
if (!normalized) {
throw new Error("缺少 documentId");
}
return normalized;
},
assertTitle: (value: string | null | undefined) => {
const normalized = typeof value === "string" ? value.trim() : "";
if (!normalized) {
throw new Error("缺少标题");
}
return normalized;
},
buildDocumentBridgeContext: mockBuildDocumentBridgeContext,
buildDocumentCommandEnvelope: mockBuildDocumentCommandEnvelope,
2026-04-26 19:35:52 +08:00
documentBridgeErrorResponse: (...args: Parameters<typeof mockDocumentBridgeErrorResponse>) =>
mockDocumentBridgeErrorResponse(...args),
2026-04-24 06:10:18 +08:00
}));
vi.mock("@/lib/documents/rust-runtime", () => ({
2026-04-26 19:35:52 +08:00
resolveRustBridgeCommandPlan: (...args: Parameters<typeof mockResolveRustBridgeCommandPlan>) =>
mockResolveRustBridgeCommandPlan(...args),
executeRustBridgeMutationTransport: (...args: Parameters<typeof mockExecuteRustBridgeMutationTransport>) =>
mockExecuteRustBridgeMutationTransport(...args),
recordRustBridgeCommandArtifacts: (...args: Parameters<typeof mockRecordRustBridgeCommandArtifacts>) =>
mockRecordRustBridgeCommandArtifacts(...args),
readRustTreeDomainEventType: (plan: { argsJson?: Record<string, unknown> }) => {
const eventPlan = plan.argsJson?.domainEventPlan as
| {
family?: string;
eventType?: string;
}
| undefined;
if (eventPlan?.family === "tree" && typeof eventPlan.eventType === "string") {
return eventPlan.eventType;
}
const hint = plan.argsJson?.domainEventHint as
| {
family?: string;
eventType?: string;
}
| undefined;
return hint?.family === "tree" && typeof hint.eventType === "string" ? hint.eventType : null;
},
materializeRustTreeDomainEventPlan: (input: {
plan: { argsJson?: Record<string, unknown> };
streamDelta?: Record<string, unknown> | null;
}) => {
const eventPlan = input.plan.argsJson?.domainEventPlan as
| {
family?: string;
schema?: string;
schemaVersion?: number;
eventType?: string;
}
| undefined;
if (
eventPlan?.family !== "tree" ||
eventPlan.schema !== "mnote.tree.domain_event" ||
eventPlan.schemaVersion !== 1 ||
typeof eventPlan.eventType !== "string"
) {
return null;
}
return {
family: "tree",
schema: "mnote.tree.domain_event",
schemaVersion: 1,
eventType: eventPlan.eventType,
...(input.streamDelta ? { streamDelta: input.streamDelta } : {}),
};
},
materializeRustTreeStreamDelta: (input: { plan: { argsJson?: Record<string, unknown> }; result: unknown }) => {
const hint = input.plan.argsJson?.streamDeltaHint as
| {
family?: string;
kind?: string;
args?: Record<string, unknown>;
}
| undefined;
if (hint?.family !== "tree" || !hint.kind) return null;
const args = hint.args ?? {};
const result = input.result as Record<string, unknown>;
if (hint.kind === "document_result") {
return result.document ? { op: "upsert_document", document: result.document } : null;
}
if (hint.kind === "upsert_document_patch") {
return {
op: "upsert_document",
document: {
id: args.documentId,
...(args.patch as Record<string, unknown>),
updated_at: result.updated_at ?? null,
},
};
}
if (hint.kind === "move_document") {
return {
op: "move_document",
documentId: args.documentId,
parentId: result.parent_id ?? args.parentId ?? null,
sortOrder: result.sort_order ?? args.sortOrder,
updatedAt: result.updated_at,
};
}
if (hint.kind === "remove_document") {
return { op: "remove_document", documentId: args.documentId };
}
if (hint.kind === "noop") {
return { op: "noop" };
}
if (hint.kind === "copy_result") {
return {
op: "upsert_documents",
upsertDocuments: Array.isArray(result.items)
? result.items.map((item) => item?.document).filter(Boolean)
: [],
};
}
return null;
},
2026-04-24 06:10:18 +08:00
}));
2026-04-26 04:29:23 +08:00
vi.mock("@/lib/documents/bridge-log", () => ({
recordBridgeCommandArtifacts: (...args: unknown[]) => mockRecordBridgeCommandArtifacts(...args),
recordBridgeCommandFailureArtifacts: (...args: unknown[]) => mockRecordBridgeCommandFailureArtifacts(...args),
}));
2026-04-24 06:10:18 +08:00
vi.mock("@/lib/documents/page-lifecycle-side-effects", () => ({
ensureDocumentScaffold: (...args: unknown[]) => mockEnsureDocumentScaffold(...args),
2026-04-26 04:29:23 +08:00
copyMindmapFilesIfExists: (...args: unknown[]) => mockCopyMindmapFilesIfExists(...args),
}));
vi.mock("@/lib/server/sidebar-data", () => ({
loadSidebarDataFromConvex: (...args: unknown[]) => mockLoadSidebarDataFromConvex(...args),
2026-04-24 06:10:18 +08:00
}));
describe("/api/tree/commands route", () => {
beforeEach(() => {
mockIsConvexEnabled.mockReturnValue(true);
mockGetAuthedConvexClient.mockReset();
mockBuildDocumentBridgeContext.mockReset();
mockBuildDocumentCommandEnvelope.mockReset();
mockResolveRustBridgeCommandPlan.mockReset();
mockExecuteRustBridgeMutationTransport.mockReset();
2026-04-26 19:35:52 +08:00
mockRecordRustBridgeCommandArtifacts.mockReset().mockResolvedValue(null);
2026-04-26 04:29:23 +08:00
mockRecordBridgeCommandArtifacts.mockReset();
mockRecordBridgeCommandFailureArtifacts.mockReset();
2026-04-24 06:10:18 +08:00
mockEnsureDocumentScaffold.mockReset();
2026-04-26 04:29:23 +08:00
mockCopyMindmapFilesIfExists.mockReset();
mockLoadSidebarDataFromConvex.mockReset();
2026-04-24 06:10:18 +08:00
mockDocumentBridgeErrorResponse.mockClear();
});
it("create action 走 tree.node.create,并保留本地 scaffold 副作用", async () => {
expect(TREE_3000_ROUTE_BOUNDARY_MANIFEST.routes).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: "tree.commands",
role: "next-thin-proxy",
}),
]),
);
expect(TREE_3000_ROUTE_BOUNDARY_MANIFEST.rustOwnedSemantics).toContain("tree.node.create");
expect(TREE_3000_ROUTE_BOUNDARY_MANIFEST.browserSubstrateDuties).toContain(
"新页面 scaffold 文件创建",
);
2026-04-24 06:10:18 +08:00
const client = {
mutation: vi.fn(async () => ({ activeWorkspaceId: "ws_root" })),
query: vi.fn(),
};
mockGetAuthedConvexClient.mockResolvedValue({
auth: {
userId: "user_1",
email: "dev@example.com",
name: "开发用户",
},
client,
});
mockBuildDocumentBridgeContext.mockResolvedValue({
requestId: "req_tree_1",
traceId: "trace_tree_1",
workspaceId: "ws_root",
actor: {
actorType: "user",
actorId: "user_1",
sessionId: null,
},
source: {
channel: "next-route",
client: "wolai-frontend",
},
tenantId: null,
deploymentId: null,
projectId: null,
authToken: null,
idempotencyKey: null,
validateOnly: false,
dryRun: false,
});
mockBuildDocumentCommandEnvelope.mockImplementation((input: unknown) => input);
mockResolveRustBridgeCommandPlan.mockResolvedValue({
kind: "command",
commandName: "tree.node.create",
commandId: "cmd_tree_create_1",
functionName: "documents:createWithParentReference",
workspaceId: "ws_root",
requestId: "req_tree_1",
traceId: "trace_tree_1",
actorId: "user_1",
idempotencyKey: null,
payloadJson: "{}",
2026-04-26 19:35:52 +08:00
argsJson: {
streamDeltaHint: {
family: "tree",
kind: "document_result",
args: { documentField: "document" },
},
domainEventHint: {
family: "tree",
eventType: "tree.node.created",
},
},
2026-04-24 06:10:18 +08:00
});
mockExecuteRustBridgeMutationTransport.mockResolvedValue({
id: "doc_new",
title: "无标题",
parent_id: null,
sort_order: 0,
workspace_id: "ws_root",
access_scope: "private",
is_template: false,
created_at: "2026-04-23T00:00:00Z",
updated_at: "2026-04-23T00:00:00Z",
2026-04-26 19:35:52 +08:00
document: {
id: "doc_new",
workspace_id: "ws_root",
title: "无标题",
parent_id: null,
sort_order: 0,
is_starred: false,
access_scope: "private",
is_template: false,
created_at: "2026-04-23T00:00:00Z",
updated_at: "2026-04-23T00:00:00Z",
},
2026-04-24 06:10:18 +08:00
});
const { POST } = await import("./route");
const response = await POST(
new Request("http://127.0.0.1:3000/api/tree/commands", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
action: "create",
parentId: null,
}),
}),
);
expect(response.status).toBe(200);
const payload = await response.json() as {
result: {
action: string;
documentId: string;
workspaceId: string;
title: string;
};
};
expect(payload.result.action).toBe("create");
expect(payload.result.documentId).toBe("doc_new");
expect(payload.result.workspaceId).toBe("ws_root");
expect(mockBuildDocumentCommandEnvelope).toHaveBeenCalledWith(
expect.objectContaining({
name: "tree.node.create",
payload: expect.objectContaining({
workspaceId: "ws_root",
parentId: null,
title: "无标题",
accessScope: "private",
}),
}),
);
expect(mockEnsureDocumentScaffold).toHaveBeenCalledWith("doc_new", "无标题");
2026-04-26 19:35:52 +08:00
expect(mockRecordRustBridgeCommandArtifacts).toHaveBeenCalledWith(
2026-04-26 04:29:23 +08:00
expect.objectContaining({
2026-04-26 19:35:52 +08:00
context: expect.objectContaining({
requestId: "req_tree_1",
traceId: "trace_tree_1",
}),
2026-04-26 04:29:23 +08:00
envelope: expect.objectContaining({
name: "tree.node.create",
}),
2026-04-26 19:35:52 +08:00
plan: expect.objectContaining({
commandName: "tree.node.create",
functionName: "documents:createWithParentReference",
}),
result: expect.objectContaining({
id: "doc_new",
workspace_id: "ws_root",
2026-04-26 04:29:23 +08:00
}),
}),
);
2026-04-26 19:35:52 +08:00
expect(mockRecordBridgeCommandArtifacts).not.toHaveBeenCalled();
2026-04-24 06:10:18 +08:00
});
it("move action 走 tree.subtree.move,并把 position 归一化为 sortOrder", async () => {
2026-04-26 04:29:23 +08:00
let docMetaReadCount = 0;
2026-04-24 06:10:18 +08:00
const client = {
mutation: vi.fn(),
2026-04-26 04:29:23 +08:00
query: vi.fn(async (name: string, args: { id: string }) => {
if (name === "documents:getMeta" && args.id === "doc_1") {
docMetaReadCount += 1;
return {
id: "doc_1",
workspace_id: "ws_1",
parent_id: docMetaReadCount === 1 ? null : "parent_1",
sort_order: docMetaReadCount === 1 ? 4 : 1,
updated_at: docMetaReadCount === 1 ? "2026-04-23T00:00:00Z" : "2026-04-23T00:01:00Z",
};
}
if (name === "documents:getMeta" && args.id === "parent_1") {
return {
id: "parent_1",
workspace_id: "ws_1",
parent_id: null,
};
}
return null;
}),
2026-04-24 06:10:18 +08:00
};
mockGetAuthedConvexClient.mockResolvedValue({
auth: {
userId: "user_1",
email: "dev@example.com",
name: "开发用户",
},
client,
});
mockBuildDocumentBridgeContext.mockResolvedValue({
requestId: "req_tree_2",
traceId: "trace_tree_2",
workspaceId: "ws_1",
actor: {
actorType: "user",
actorId: "user_1",
sessionId: null,
},
source: {
channel: "next-route",
client: "wolai-frontend",
},
tenantId: null,
deploymentId: null,
projectId: null,
authToken: null,
idempotencyKey: null,
validateOnly: false,
dryRun: false,
});
mockBuildDocumentCommandEnvelope.mockImplementation((input: unknown) => input);
mockResolveRustBridgeCommandPlan.mockResolvedValue({
kind: "command",
commandName: "tree.subtree.move",
commandId: "cmd_tree_move_1",
functionName: "documents:move",
workspaceId: "ws_1",
requestId: "req_tree_2",
traceId: "trace_tree_2",
actorId: "user_1",
idempotencyKey: null,
payloadJson: "{}",
2026-04-26 19:35:52 +08:00
argsJson: {
streamDeltaHint: {
family: "tree",
kind: "move_document",
args: {
documentId: "doc_1",
parentId: "parent_1",
sortOrder: 2,
},
},
domainEventHint: {
family: "tree",
eventType: "tree.subtree.moved",
},
},
2026-04-24 06:10:18 +08:00
});
mockExecuteRustBridgeMutationTransport.mockResolvedValue({
ok: true,
2026-04-26 04:29:23 +08:00
parent_id: "parent_1",
sort_order: 1,
workspace_id: "ws_1",
updated_at: "2026-04-23T00:01:00Z",
});
mockLoadSidebarDataFromConvex.mockResolvedValue({
sidebarInitialData: {
activeWorkspaceId: "ws_1",
workspaces: [],
documents: [
{
id: "doc_1",
workspace_id: "ws_1",
parent_id: null,
},
{
id: "parent_1",
workspace_id: "ws_1",
parent_id: null,
},
],
rootDocuments: [],
sidebarTree: [],
trashedDocuments: [],
mediaAssets: [],
trashedMediaAssets: [],
mindmapDocs: [],
mindmapAssets: [],
trashedMindmapAssets: [],
mindmapAssetChildren: {},
tableAssets: [],
trashedTableAssets: [],
kernelSidebarProjection: {
projectionId: "kernel_projection:sidebar_tree:workspace_root",
projection: "sidebar_tree",
rootNodeId: null,
items: [],
edges: [],
},
kernelFileTreeProjection: {
projectionId: "kernel_projection:file_tree:workspace_root",
projection: "file_tree",
rootNodeId: null,
items: [],
edges: [],
},
},
2026-04-24 06:10:18 +08:00
});
const { POST } = await import("./route");
const response = await POST(
new Request("http://127.0.0.1:3000/api/tree/commands", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
action: "move",
documentId: "doc_1",
parentId: "parent_1",
sortOrder: 2.9,
workspaceId: "ws_1",
}),
}),
);
expect(response.status).toBe(200);
const payload = await response.json() as {
result: {
action: string;
documentId: string;
parentId: string | null;
sortOrder: number | null;
};
};
expect(payload.result).toMatchObject({
action: "move",
documentId: "doc_1",
parentId: "parent_1",
2026-04-26 04:29:23 +08:00
sortOrder: 1,
2026-04-24 06:10:18 +08:00
workspaceId: "ws_1",
});
expect(mockBuildDocumentCommandEnvelope).toHaveBeenCalledWith(
expect.objectContaining({
name: "tree.subtree.move",
2026-04-26 04:29:23 +08:00
preflightData: {
documents: [
{
id: "doc_1",
workspace_id: "ws_1",
parent_id: null,
},
{
id: "parent_1",
workspace_id: "ws_1",
parent_id: null,
},
],
},
payload: expect.objectContaining({
2026-04-24 06:10:18 +08:00
documentId: "doc_1",
parentId: "parent_1",
sortOrder: 2,
2026-04-26 04:29:23 +08:00
}),
2026-04-24 06:10:18 +08:00
}),
);
expect(mockEnsureDocumentScaffold).not.toHaveBeenCalled();
2026-04-26 19:35:52 +08:00
expect(mockRecordRustBridgeCommandArtifacts).toHaveBeenCalledWith(
2026-04-26 04:29:23 +08:00
expect.objectContaining({
envelope: expect.objectContaining({
name: "tree.subtree.move",
}),
2026-04-26 19:35:52 +08:00
plan: expect.objectContaining({
commandName: "tree.subtree.move",
functionName: "documents:move",
}),
result: expect.objectContaining({
parent_id: "parent_1",
sort_order: 1,
2026-04-26 04:29:23 +08:00
}),
}),
);
});
it("move action 应优先使用 mutation 返回的 canonical 结果,而不是再次回读页面元数据", async () => {
const client = {
mutation: vi.fn(),
query: vi.fn(async (name: string, args: { id: string }) => {
if (name === "documents:getMeta" && args.id === "doc_1") {
return {
id: "doc_1",
workspace_id: "ws_1",
parent_id: "parent_old",
sort_order: 4,
updated_at: "2026-04-23T00:00:00Z",
};
}
if (name === "documents:getMeta" && args.id === "parent_1") {
return {
id: "parent_1",
workspace_id: "ws_1",
parent_id: null,
};
}
return null;
}),
};
mockGetAuthedConvexClient.mockResolvedValue({
auth: {
userId: "user_1",
email: "dev@example.com",
name: "开发用户",
},
client,
});
mockBuildDocumentBridgeContext.mockResolvedValue({
requestId: "req_tree_2b",
traceId: "trace_tree_2b",
workspaceId: "ws_1",
actor: {
actorType: "user",
actorId: "user_1",
sessionId: null,
},
source: {
channel: "next-route",
client: "wolai-frontend",
},
tenantId: null,
deploymentId: null,
projectId: null,
authToken: null,
idempotencyKey: null,
validateOnly: false,
dryRun: false,
});
mockBuildDocumentCommandEnvelope.mockImplementation((input: unknown) => input);
mockResolveRustBridgeCommandPlan.mockResolvedValue({
kind: "command",
commandName: "tree.subtree.move",
commandId: "cmd_tree_move_2",
functionName: "documents:move",
workspaceId: "ws_1",
requestId: "req_tree_2b",
traceId: "trace_tree_2b",
actorId: "user_1",
idempotencyKey: null,
payloadJson: "{}",
2026-04-26 19:35:52 +08:00
argsJson: {
streamDeltaHint: {
family: "tree",
kind: "move_document",
args: {
documentId: "doc_1",
parentId: "parent_1",
sortOrder: 2,
},
},
domainEventHint: {
family: "tree",
eventType: "tree.subtree.moved",
},
},
2026-04-26 04:29:23 +08:00
});
mockExecuteRustBridgeMutationTransport.mockResolvedValue({
ok: true,
parent_id: "parent_1",
sort_order: 1,
workspace_id: "ws_1",
updated_at: "2026-04-23T00:01:00Z",
});
mockLoadSidebarDataFromConvex.mockResolvedValue({
sidebarInitialData: {
activeWorkspaceId: "ws_1",
workspaces: [],
documents: [
{
id: "doc_1",
workspace_id: "ws_1",
parent_id: "parent_old",
},
{
id: "parent_1",
workspace_id: "ws_1",
parent_id: null,
},
],
rootDocuments: [],
sidebarTree: [],
trashedDocuments: [],
mediaAssets: [],
trashedMediaAssets: [],
mindmapDocs: [],
mindmapAssets: [],
trashedMindmapAssets: [],
mindmapAssetChildren: {},
tableAssets: [],
trashedTableAssets: [],
kernelSidebarProjection: {
projectionId: "kernel_projection:sidebar_tree:workspace_root",
projection: "sidebar_tree",
rootNodeId: null,
items: [],
edges: [],
},
kernelFileTreeProjection: {
projectionId: "kernel_projection:file_tree:workspace_root",
projection: "file_tree",
rootNodeId: null,
items: [],
edges: [],
},
},
});
const { POST } = await import("./route");
const response = await POST(
new Request("http://127.0.0.1:3000/api/tree/commands", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
action: "move",
documentId: "doc_1",
parentId: "parent_1",
sortOrder: 3,
workspaceId: "ws_1",
}),
}),
);
expect(response.status).toBe(200);
const payload = await response.json() as {
result: {
action: string;
documentId: string;
workspaceId: string;
parentId: string | null;
sortOrder: number | null;
updatedAt: string | null;
execution: {
parent_id: string | null;
sort_order: number | null;
workspace_id: string | null;
};
};
};
expect(payload.result).toMatchObject({
action: "move",
documentId: "doc_1",
workspaceId: "ws_1",
parentId: "parent_1",
sortOrder: 1,
updatedAt: "2026-04-23T00:01:00Z",
execution: {
parent_id: "parent_1",
sort_order: 1,
workspace_id: "ws_1",
},
});
expect(client.query).toHaveBeenCalledTimes(1);
});
it("move action 应把 self move 的 preflight 交给 Rust,并由 Rust 拒绝", async () => {
const client = {
mutation: vi.fn(),
query: vi.fn(async (name: string, args: { id: string }) => {
if (name === "documents:getMeta" && args.id === "doc_1") {
return {
id: "doc_1",
workspace_id: "ws_1",
parent_id: null,
};
}
return null;
}),
};
mockGetAuthedConvexClient.mockResolvedValue({
auth: {
userId: "user_1",
email: "dev@example.com",
name: "开发用户",
},
client,
});
mockBuildDocumentBridgeContext.mockResolvedValue({
requestId: "req_tree_move_self",
traceId: "trace_tree_move_self",
workspaceId: "ws_1",
actor: {
actorType: "user",
actorId: "user_1",
sessionId: null,
},
source: {
channel: "next-route",
client: "wolai-frontend",
},
tenantId: null,
deploymentId: null,
projectId: null,
authToken: null,
idempotencyKey: null,
validateOnly: false,
dryRun: false,
});
mockBuildDocumentCommandEnvelope.mockImplementation((input: unknown) => input);
mockResolveRustBridgeCommandPlan.mockRejectedValue(
Object.assign(new Error("不能把页面移动到自身下面"), { status: 400 }),
);
mockLoadSidebarDataFromConvex.mockResolvedValue({
sidebarInitialData: {
activeWorkspaceId: "ws_1",
workspaces: [],
documents: [
{
id: "doc_1",
workspace_id: "ws_1",
parent_id: null,
},
],
rootDocuments: [],
sidebarTree: [],
trashedDocuments: [],
mediaAssets: [],
trashedMediaAssets: [],
mindmapDocs: [],
mindmapAssets: [],
trashedMindmapAssets: [],
mindmapAssetChildren: {},
tableAssets: [],
trashedTableAssets: [],
kernelSidebarProjection: {
projectionId: "kernel_projection:sidebar_tree:workspace_root",
projection: "sidebar_tree",
rootNodeId: null,
items: [],
edges: [],
},
kernelFileTreeProjection: {
projectionId: "kernel_projection:file_tree:workspace_root",
projection: "file_tree",
rootNodeId: null,
items: [],
edges: [],
},
},
});
const { POST } = await import("./route");
const response = await POST(
new Request("http://127.0.0.1:3000/api/tree/commands", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
action: "move",
documentId: "doc_1",
parentId: "doc_1",
sortOrder: 0,
}),
}),
);
expect(response.status).toBe(400);
expect(await response.json()).toEqual({
error: "不能把页面移动到自身下面",
});
expect(mockBuildDocumentCommandEnvelope).toHaveBeenCalledWith(
expect.objectContaining({
name: "tree.subtree.move",
preflightData: {
documents: [
{
id: "doc_1",
workspace_id: "ws_1",
parent_id: null,
},
],
},
payload: expect.objectContaining({
documentId: "doc_1",
parentId: "doc_1",
sortOrder: 0,
}),
}),
);
expect(mockResolveRustBridgeCommandPlan).toHaveBeenCalledTimes(1);
expect(mockExecuteRustBridgeMutationTransport).not.toHaveBeenCalled();
});
it("move action 应把 descendant move 的 preflight 交给 Rust,并由 Rust 拒绝", async () => {
const client = {
mutation: vi.fn(),
query: vi.fn(async (name: string, args: { id: string }) => {
if (name === "documents:getMeta" && args.id === "doc_1") {
return {
id: "doc_1",
workspace_id: "ws_1",
parent_id: null,
};
}
if (name === "documents:getMeta" && args.id === "child_1") {
return {
id: "child_1",
workspace_id: "ws_1",
parent_id: "doc_1",
};
}
return null;
}),
};
mockGetAuthedConvexClient.mockResolvedValue({
auth: {
userId: "user_1",
email: "dev@example.com",
name: "开发用户",
},
client,
});
mockBuildDocumentBridgeContext.mockResolvedValue({
requestId: "req_tree_move_desc",
traceId: "trace_tree_move_desc",
workspaceId: "ws_1",
actor: {
actorType: "user",
actorId: "user_1",
sessionId: null,
},
source: {
channel: "next-route",
client: "wolai-frontend",
},
tenantId: null,
deploymentId: null,
projectId: null,
authToken: null,
idempotencyKey: null,
validateOnly: false,
dryRun: false,
});
mockBuildDocumentCommandEnvelope.mockImplementation((input: unknown) => input);
mockResolveRustBridgeCommandPlan.mockRejectedValue(
Object.assign(new Error("不能把页面移动到自己的后代下面"), { status: 400 }),
);
mockLoadSidebarDataFromConvex.mockResolvedValue({
sidebarInitialData: {
activeWorkspaceId: "ws_1",
workspaces: [],
documents: [
{
id: "doc_1",
workspace_id: "ws_1",
parent_id: null,
},
{
id: "child_1",
workspace_id: "ws_1",
parent_id: "doc_1",
},
],
rootDocuments: [],
sidebarTree: [],
trashedDocuments: [],
mediaAssets: [],
trashedMediaAssets: [],
mindmapDocs: [],
mindmapAssets: [],
trashedMindmapAssets: [],
mindmapAssetChildren: {},
tableAssets: [],
trashedTableAssets: [],
kernelSidebarProjection: {
projectionId: "kernel_projection:sidebar_tree:workspace_root",
projection: "sidebar_tree",
rootNodeId: null,
items: [],
edges: [],
},
kernelFileTreeProjection: {
projectionId: "kernel_projection:file_tree:workspace_root",
projection: "file_tree",
rootNodeId: null,
items: [],
edges: [],
},
},
});
const { POST } = await import("./route");
const response = await POST(
new Request("http://127.0.0.1:3000/api/tree/commands", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
action: "move",
documentId: "doc_1",
parentId: "child_1",
sortOrder: 0,
}),
}),
);
expect(response.status).toBe(400);
expect(await response.json()).toEqual({
error: "不能把页面移动到自己的后代下面",
});
expect(mockBuildDocumentCommandEnvelope).toHaveBeenCalledWith(
expect.objectContaining({
name: "tree.subtree.move",
preflightData: {
documents: [
{
id: "doc_1",
workspace_id: "ws_1",
parent_id: null,
},
{
id: "child_1",
workspace_id: "ws_1",
parent_id: "doc_1",
},
],
},
payload: expect.objectContaining({
documentId: "doc_1",
parentId: "child_1",
sortOrder: 0,
}),
}),
);
expect(mockResolveRustBridgeCommandPlan).toHaveBeenCalledTimes(1);
expect(mockExecuteRustBridgeMutationTransport).not.toHaveBeenCalled();
2026-04-24 06:10:18 +08:00
});
it("rename action 走 tree.node.rename,并保留标题 payload", async () => {
const client = {
mutation: vi.fn(),
query: vi.fn(async () => ({
id: "doc_1",
workspace_id: "ws_1",
})),
};
mockGetAuthedConvexClient.mockResolvedValue({
auth: {
userId: "user_1",
email: "dev@example.com",
name: "开发用户",
},
client,
});
mockBuildDocumentBridgeContext.mockResolvedValue({
requestId: "req_tree_3",
traceId: "trace_tree_3",
workspaceId: "ws_1",
actor: {
actorType: "user",
actorId: "user_1",
sessionId: null,
},
source: {
channel: "next-route",
client: "wolai-frontend",
},
tenantId: null,
deploymentId: null,
projectId: null,
authToken: null,
idempotencyKey: null,
validateOnly: false,
dryRun: false,
});
mockBuildDocumentCommandEnvelope.mockImplementation((input: unknown) => input);
mockResolveRustBridgeCommandPlan.mockResolvedValue({
kind: "command",
commandName: "tree.node.rename",
commandId: "cmd_tree_rename_1",
functionName: "documents:updateTitle",
workspaceId: "ws_1",
requestId: "req_tree_3",
traceId: "trace_tree_3",
actorId: "user_1",
idempotencyKey: null,
payloadJson: "{}",
2026-04-26 19:35:52 +08:00
argsJson: {
streamDeltaHint: {
family: "tree",
kind: "upsert_document_patch",
args: {
documentId: "doc_1",
patch: { title: "新标题" },
},
},
domainEventHint: {
family: "tree",
eventType: "tree.node.renamed",
},
},
2026-04-24 06:10:18 +08:00
});
mockExecuteRustBridgeMutationTransport.mockResolvedValue({
ok: true,
updated_at: "2026-04-23T00:00:00Z",
});
const { POST } = await import("./route");
const response = await POST(
new Request("http://127.0.0.1:3000/api/tree/commands", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
action: "rename",
documentId: "doc_1",
workspaceId: "ws_1",
title: "新标题",
}),
}),
);
expect(response.status).toBe(200);
const payload = await response.json() as {
result: {
action: string;
documentId: string;
workspaceId: string;
title: string;
};
};
expect(payload.result).toMatchObject({
action: "rename",
documentId: "doc_1",
workspaceId: "ws_1",
title: "新标题",
});
expect(mockBuildDocumentCommandEnvelope).toHaveBeenCalledWith(
expect.objectContaining({
name: "tree.node.rename",
payload: {
documentId: "doc_1",
workspaceId: "ws_1",
title: "新标题",
},
}),
);
expect(mockEnsureDocumentScaffold).not.toHaveBeenCalled();
2026-04-26 19:35:52 +08:00
expect(mockRecordRustBridgeCommandArtifacts).toHaveBeenCalledWith(
2026-04-26 04:29:23 +08:00
expect.objectContaining({
envelope: expect.objectContaining({
name: "tree.node.rename",
}),
2026-04-26 19:35:52 +08:00
plan: expect.objectContaining({
commandName: "tree.node.rename",
}),
result: expect.objectContaining({
updated_at: "2026-04-23T00:00:00Z",
2026-04-26 04:29:23 +08:00
}),
}),
);
});
it("archive action 走 tree.node.archive", async () => {
const client = {
mutation: vi.fn(),
query: vi.fn(async () => ({
id: "doc_1",
workspace_id: "ws_1",
})),
};
mockGetAuthedConvexClient.mockResolvedValue({
auth: {
userId: "user_1",
email: "dev@example.com",
name: "开发用户",
},
client,
});
mockBuildDocumentBridgeContext.mockResolvedValue({
requestId: "req_tree_4",
traceId: "trace_tree_4",
workspaceId: "ws_1",
});
mockBuildDocumentCommandEnvelope.mockImplementation((input: unknown) => input);
mockResolveRustBridgeCommandPlan.mockResolvedValue({
kind: "command",
commandName: "tree.node.archive",
commandId: "cmd_tree_archive_1",
functionName: "documents:softDelete",
workspaceId: "ws_1",
requestId: "req_tree_4",
traceId: "trace_tree_4",
actorId: "user_1",
idempotencyKey: null,
payloadJson: "{}",
2026-04-26 19:35:52 +08:00
argsJson: {
streamDeltaHint: {
family: "tree",
kind: "remove_document",
args: { documentId: "doc_1" },
},
domainEventHint: {
family: "tree",
eventType: "tree.node.archived",
},
},
2026-04-26 04:29:23 +08:00
});
mockExecuteRustBridgeMutationTransport.mockResolvedValue({
ok: true,
updated_at: "2026-04-24T00:00:00Z",
});
const { POST } = await import("./route");
const response = await POST(
new Request("http://127.0.0.1:3000/api/tree/commands", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
action: "archive",
documentId: "doc_1",
workspaceId: "ws_1",
}),
}),
);
expect(response.status).toBe(200);
const payload = await response.json() as {
result: {
action: string;
documentId: string;
workspaceId: string;
};
};
expect(payload.result).toMatchObject({
action: "archive",
documentId: "doc_1",
workspaceId: "ws_1",
});
expect(mockBuildDocumentCommandEnvelope).toHaveBeenCalledWith(
expect.objectContaining({
name: "tree.node.archive",
payload: {
documentId: "doc_1",
workspaceId: "ws_1",
},
}),
);
2026-04-26 19:35:52 +08:00
expect(mockRecordRustBridgeCommandArtifacts).toHaveBeenCalledWith(
2026-04-26 04:29:23 +08:00
expect.objectContaining({
envelope: expect.objectContaining({
name: "tree.node.archive",
}),
2026-04-26 19:35:52 +08:00
plan: expect.objectContaining({
commandName: "tree.node.archive",
2026-04-26 04:29:23 +08:00
}),
2026-04-26 19:35:52 +08:00
result: expect.any(Object),
2026-04-26 04:29:23 +08:00
}),
);
});
2026-04-28 16:30:51 +08:00
it("embed action 走 tree.node.embed,并把 pageReference 组装交给 Rust Page Aggregate preflight", async () => {
2026-04-26 04:29:23 +08:00
const client = {
mutation: vi.fn(),
query: vi.fn(async (name: string, args: { id: string }) => {
if (name === "documents:getMeta" && args.id === "doc_source") {
return {
id: "doc_source",
title: "来源页面",
workspace_id: "ws_1",
};
}
if (name === "documents:getMeta" && args.id === "doc_target") {
return {
id: "doc_target",
workspace_id: "ws_1",
embed_default_block_id: "anchor_1",
};
}
if (name === "documents:getContent" && args.id === "doc_target") {
return {
content: [
{ id: "anchor_1", type: "paragraph" },
],
revision: 7,
conflict_detection_key: "doc_target:7",
};
}
return null;
}),
};
mockGetAuthedConvexClient.mockResolvedValue({
auth: {
userId: "user_1",
email: "dev@example.com",
name: "开发用户",
},
client,
});
mockBuildDocumentBridgeContext.mockResolvedValue({
requestId: "req_tree_5",
traceId: "trace_tree_5",
workspaceId: "ws_1",
});
mockBuildDocumentCommandEnvelope.mockImplementation((input: unknown) => input);
mockResolveRustBridgeCommandPlan.mockResolvedValue({
kind: "command",
commandName: "tree.node.embed",
commandId: "cmd_tree_embed_1",
functionName: "documents:updateContent",
workspaceId: "ws_1",
requestId: "req_tree_5",
traceId: "trace_tree_5",
actorId: "user_1",
idempotencyKey: null,
payloadJson: "{}",
2026-04-26 19:35:52 +08:00
argsJson: {
streamDeltaHint: {
family: "tree",
kind: "noop",
args: {},
},
domainEventHint: {
family: "tree",
eventType: "tree.node.embedded",
},
},
2026-04-26 04:29:23 +08:00
});
mockExecuteRustBridgeMutationTransport.mockResolvedValue({
revision: 8,
conflict_detection_key: "doc_target:8",
});
const { POST } = await import("./route");
const response = await POST(
new Request("http://127.0.0.1:3000/api/tree/commands", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
action: "embed",
sourceId: "doc_source",
targetId: "doc_target",
}),
}),
);
expect(response.status).toBe(200);
const payload = await response.json() as {
result: {
action: string;
documentId: string;
sourceDocumentId: string;
targetDocumentId: string;
};
};
expect(payload.result).toMatchObject({
action: "embed",
documentId: "doc_target",
sourceDocumentId: "doc_source",
targetDocumentId: "doc_target",
});
expect(mockBuildDocumentCommandEnvelope).toHaveBeenCalledWith(
expect.objectContaining({
name: "tree.node.embed",
payload: expect.objectContaining({
documentId: "doc_target",
sourceDocumentId: "doc_source",
targetDocumentId: "doc_target",
revision: 7,
conflictDetectionKey: "doc_target:7",
}),
2026-04-28 16:30:51 +08:00
preflightData: {
pageAggregateEmbed: {
sourceDocumentId: "doc_source",
sourceTitle: "来源页面",
targetDocumentId: "doc_target",
targetContent: [
{ id: "anchor_1", type: "paragraph" },
],
anchorBlockId: "anchor_1",
blockId: expect.any(String),
},
},
2026-04-26 04:29:23 +08:00
}),
);
2026-04-26 19:35:52 +08:00
expect(mockRecordRustBridgeCommandArtifacts).toHaveBeenCalledWith(
2026-04-26 04:29:23 +08:00
expect.objectContaining({
envelope: expect.objectContaining({
name: "tree.node.embed",
}),
2026-04-26 19:35:52 +08:00
plan: expect.objectContaining({
commandName: "tree.node.embed",
2026-04-26 04:29:23 +08:00
}),
2026-04-26 19:35:52 +08:00
result: expect.any(Object),
2026-04-26 04:29:23 +08:00
}),
);
});
it("copy action 走 tree.subtree.copy,并保留 scaffold 与导图文件副作用", async () => {
const client = {
mutation: vi.fn(),
query: vi.fn(async (name: string, args: { id: string }) => {
if (name === "documents:getMeta" && args.id === "parent_1") {
return {
id: "parent_1",
workspace_id: "ws_1",
};
}
return null;
}),
};
mockGetAuthedConvexClient.mockResolvedValue({
auth: {
userId: "user_1",
email: "dev@example.com",
name: "开发用户",
},
client,
});
mockBuildDocumentBridgeContext.mockResolvedValue({
requestId: "req_tree_6",
traceId: "trace_tree_6",
workspaceId: "ws_1",
});
mockBuildDocumentCommandEnvelope.mockImplementation((input: unknown) => input);
mockResolveRustBridgeCommandPlan.mockResolvedValue({
kind: "command",
commandName: "tree.subtree.copy",
commandId: "cmd_tree_copy_1",
functionName: "documents:copyTree",
workspaceId: "ws_1",
requestId: "req_tree_6",
traceId: "trace_tree_6",
actorId: "user_1",
idempotencyKey: null,
payloadJson: "{}",
2026-04-26 19:35:52 +08:00
argsJson: {
streamDeltaHint: {
family: "tree",
kind: "copy_result",
args: { itemsField: "items", documentField: "document" },
},
domainEventHint: {
family: "tree",
eventType: "tree.subtree.copied",
},
},
2026-04-26 04:29:23 +08:00
});
mockExecuteRustBridgeMutationTransport.mockResolvedValue({
items: [
{
oldId: "doc_1",
newId: "doc_2",
title: "复制页面",
2026-04-26 19:35:52 +08:00
document: {
id: "doc_2",
workspace_id: "ws_1",
title: "复制页面",
parent_id: "parent_1",
sort_order: 0,
is_starred: false,
access_scope: "private",
is_template: false,
created_at: "2026-04-24T00:00:00Z",
updated_at: "2026-04-24T00:00:00Z",
},
2026-04-26 04:29:23 +08:00
},
],
});
mockLoadSidebarDataFromConvex.mockResolvedValue({
sidebarInitialData: {
activeWorkspaceId: "ws_1",
workspaces: [],
documents: [],
rootDocuments: [],
sidebarTree: [],
trashedDocuments: [],
mediaAssets: [],
trashedMediaAssets: [],
mindmapDocs: [],
mindmapAssets: [],
trashedMindmapAssets: [],
mindmapAssetChildren: {},
tableAssets: [],
trashedTableAssets: [],
kernelSidebarProjection: {
projectionId: "kernel_projection:sidebar_tree:workspace_root",
projection: "sidebar_tree",
rootNodeId: null,
items: [],
edges: [],
},
kernelFileTreeProjection: {
projectionId: "kernel_projection:file_tree:workspace_root",
projection: "file_tree",
rootNodeId: null,
items: [],
edges: [],
},
},
});
const { POST } = await import("./route");
const response = await POST(
new Request("http://127.0.0.1:3000/api/tree/commands", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
action: "copy",
targetParentId: "parent_1",
items: [{ documentId: "doc_1", recursive: true }],
}),
}),
);
expect(response.status).toBe(200);
const payload = await response.json() as {
result: {
action: string;
items: Array<{ oldId: string; newId: string }>;
};
};
expect(payload.result).toMatchObject({
action: "copy",
items: [{ oldId: "doc_1", newId: "doc_2" }],
});
expect(mockBuildDocumentCommandEnvelope).toHaveBeenCalledWith(
expect.objectContaining({
name: "tree.subtree.copy",
payload: {
workspaceId: "ws_1",
targetParentId: "parent_1",
items: [{ documentId: "doc_1", recursive: true }],
},
}),
);
expect(mockEnsureDocumentScaffold).toHaveBeenCalledWith("doc_2", "复制页面");
expect(mockCopyMindmapFilesIfExists).toHaveBeenCalledWith("doc_1", "doc_2");
2026-04-26 19:35:52 +08:00
expect(mockRecordRustBridgeCommandArtifacts).toHaveBeenCalledWith(
2026-04-26 04:29:23 +08:00
expect.objectContaining({
envelope: expect.objectContaining({
name: "tree.subtree.copy",
}),
2026-04-26 19:35:52 +08:00
plan: expect.objectContaining({
commandName: "tree.subtree.copy",
}),
result: expect.objectContaining({
items: [expect.objectContaining({ newId: "doc_2" })],
2026-04-26 04:29:23 +08:00
}),
}),
);
});
2026-04-26 19:35:52 +08:00
it("restore action 走 tree.node.restore,并附带 upsert_document delta", async () => {
2026-04-26 04:29:23 +08:00
const client = {
mutation: vi.fn(),
query: vi.fn(async () => ({
id: "doc_restore_1",
workspace_id: "ws_1",
})),
};
mockGetAuthedConvexClient.mockResolvedValue({
auth: {
userId: "user_1",
email: "dev@example.com",
name: "开发用户",
},
client,
});
mockBuildDocumentBridgeContext.mockResolvedValue({
requestId: "req_tree_restore_1",
traceId: "trace_tree_restore_1",
workspaceId: "ws_1",
});
mockBuildDocumentCommandEnvelope.mockImplementation((input: unknown) => input);
mockResolveRustBridgeCommandPlan.mockResolvedValue({
kind: "command",
commandName: "tree.node.restore",
commandId: "cmd_tree_restore_1",
functionName: "documents:restore",
workspaceId: "ws_1",
requestId: "req_tree_restore_1",
traceId: "trace_tree_restore_1",
actorId: "user_1",
idempotencyKey: null,
payloadJson: "{}",
2026-04-26 19:35:52 +08:00
argsJson: {
streamDeltaHint: {
family: "tree",
kind: "document_result",
args: { documentField: "document" },
},
domainEventHint: {
family: "tree",
eventType: "tree.node.restored",
},
},
2026-04-26 04:29:23 +08:00
});
mockExecuteRustBridgeMutationTransport.mockResolvedValue({
ok: true,
2026-04-26 19:35:52 +08:00
document: {
id: "doc_restore_1",
workspace_id: "ws_1",
title: "恢复页面",
parent_id: null,
sort_order: 0,
is_starred: false,
access_scope: "private",
is_template: false,
created_at: "2026-04-23T00:00:00Z",
updated_at: "2026-04-24T00:00:00Z",
},
2026-04-26 04:29:23 +08:00
updated_at: "2026-04-24T00:00:00Z",
});
mockLoadSidebarDataFromConvex.mockResolvedValue({
sidebarInitialData: {
activeWorkspaceId: "ws_1",
workspaces: [],
documents: [],
rootDocuments: [],
sidebarTree: [],
trashedDocuments: [],
mediaAssets: [],
trashedMediaAssets: [],
mindmapDocs: [],
mindmapAssets: [],
trashedMindmapAssets: [],
mindmapAssetChildren: {},
tableAssets: [],
trashedTableAssets: [],
kernelSidebarProjection: {
projectionId: "kernel_projection:sidebar_tree:workspace_root",
projection: "sidebar_tree",
rootNodeId: null,
items: [],
edges: [],
},
kernelFileTreeProjection: {
projectionId: "kernel_projection:file_tree:workspace_root",
projection: "file_tree",
rootNodeId: null,
items: [],
edges: [],
},
},
});
const { POST } = await import("./route");
const response = await POST(
new Request("http://127.0.0.1:3000/api/tree/commands", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
action: "restore",
documentId: "doc_restore_1",
workspaceId: "ws_1",
}),
}),
);
expect(response.status).toBe(200);
expect(mockBuildDocumentCommandEnvelope).toHaveBeenCalledWith(
expect.objectContaining({
name: "tree.node.restore",
payload: {
documentId: "doc_restore_1",
workspaceId: "ws_1",
},
}),
);
2026-04-26 19:35:52 +08:00
expect(mockRecordRustBridgeCommandArtifacts).toHaveBeenCalledWith(
2026-04-26 04:29:23 +08:00
expect.objectContaining({
envelope: expect.objectContaining({
name: "tree.node.restore",
}),
2026-04-26 19:35:52 +08:00
plan: expect.objectContaining({
commandName: "tree.node.restore",
}),
result: expect.objectContaining({
document: expect.objectContaining({ id: "doc_restore_1" }),
2026-04-26 04:29:23 +08:00
}),
}),
);
});
it("tree mutation 失败时应记录 failure bridge log", async () => {
const client = {
mutation: vi.fn(),
query: vi.fn(async () => ({
id: "doc_1",
workspace_id: "ws_1",
})),
};
mockGetAuthedConvexClient.mockResolvedValue({
auth: {
userId: "user_1",
email: "dev@example.com",
name: "开发用户",
},
client,
});
mockBuildDocumentBridgeContext.mockResolvedValue({
requestId: "req_tree_fail_1",
traceId: "trace_tree_fail_1",
workspaceId: "ws_1",
actor: {
actorType: "user",
actorId: "user_1",
sessionId: null,
},
source: {
channel: "next-route",
client: "wolai-frontend",
},
tenantId: null,
deploymentId: null,
projectId: null,
authToken: null,
idempotencyKey: null,
validateOnly: false,
dryRun: false,
});
mockBuildDocumentCommandEnvelope.mockImplementation((input: unknown) => input);
mockResolveRustBridgeCommandPlan.mockResolvedValue({
kind: "command",
commandName: "tree.node.archive",
commandId: "cmd_tree_archive_fail_1",
functionName: "documents:softDelete",
workspaceId: "ws_1",
requestId: "req_tree_fail_1",
traceId: "trace_tree_fail_1",
actorId: "user_1",
idempotencyKey: null,
payloadJson: "{}",
2026-04-26 19:35:52 +08:00
argsJson: {
streamDeltaHint: {
family: "tree",
kind: "remove_document",
args: { documentId: "doc_1" },
},
domainEventHint: {
family: "tree",
eventType: "tree.node.archived",
},
},
2026-04-26 04:29:23 +08:00
});
mockExecuteRustBridgeMutationTransport.mockRejectedValue(new Error("archive failed"));
const { POST } = await import("./route");
const response = await POST(
new Request("http://127.0.0.1:3000/api/tree/commands", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
action: "archive",
documentId: "doc_1",
workspaceId: "ws_1",
}),
}),
);
expect(response.status).toBe(500);
expect(mockRecordBridgeCommandFailureArtifacts).toHaveBeenCalledWith(
expect.objectContaining({
envelope: expect.objectContaining({
name: "tree.node.archive",
}),
error: expect.any(Error),
}),
);
2026-04-24 06:10:18 +08:00
});
});