4-10 树域 Rust 家族化

This commit is contained in:
lix-2026
2026-04-24 06:10:18 +08:00
parent 41e958769e
commit 94631f3636
49 changed files with 5751 additions and 557 deletions
@@ -0,0 +1,374 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
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();
const mockDocumentBridgeErrorResponse = vi.fn((error: unknown) =>
Response.json(
{
error: error instanceof Error ? error.message : String(error),
},
{ status: 500 },
),
);
const mockEnsureDocumentScaffold = vi.fn();
vi.mock("@/lib/convex/enabled", () => ({
isConvexEnabled: () => mockIsConvexEnabled(),
}));
vi.mock("@/lib/convex/api", () => ({
api: {
documents: {
getMeta: "documents:getMeta",
},
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,
documentBridgeErrorResponse: (...args: unknown[]) => mockDocumentBridgeErrorResponse(...args),
}));
vi.mock("@/lib/documents/rust-runtime", () => ({
resolveRustBridgeCommandPlan: (...args: unknown[]) => mockResolveRustBridgeCommandPlan(...args),
executeRustBridgeMutationTransport: (...args: unknown[]) => mockExecuteRustBridgeMutationTransport(...args),
}));
vi.mock("@/lib/documents/page-lifecycle-side-effects", () => ({
ensureDocumentScaffold: (...args: unknown[]) => mockEnsureDocumentScaffold(...args),
}));
describe("/api/tree/commands route", () => {
beforeEach(() => {
mockIsConvexEnabled.mockReturnValue(true);
mockGetAuthedConvexClient.mockReset();
mockBuildDocumentBridgeContext.mockReset();
mockBuildDocumentCommandEnvelope.mockReset();
mockResolveRustBridgeCommandPlan.mockReset();
mockExecuteRustBridgeMutationTransport.mockReset();
mockEnsureDocumentScaffold.mockReset();
mockDocumentBridgeErrorResponse.mockClear();
});
it("create action 走 tree.node.create,并保留本地 scaffold 副作用", async () => {
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: "{}",
argsJson: {},
});
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",
});
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", "无标题");
});
it("move action 走 tree.subtree.move,并把 position 归一化为 sortOrder", 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_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: "{}",
argsJson: {},
});
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: "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",
sortOrder: 2,
workspaceId: "ws_1",
});
expect(mockBuildDocumentCommandEnvelope).toHaveBeenCalledWith(
expect.objectContaining({
name: "tree.subtree.move",
payload: {
documentId: "doc_1",
parentId: "parent_1",
sortOrder: 2,
},
}),
);
expect(mockEnsureDocumentScaffold).not.toHaveBeenCalled();
});
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: "{}",
argsJson: {},
});
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();
});
});