feat: land page aggregate and phase7 document ai mainline
- 收口 page aggregate 读取、本地状态与命令客户端\n- 接入 phase7 document ai sidecar 与前端编排入口\n- 更新 architecture 与 design 状态迁移
This commit is contained in:
@@ -0,0 +1,212 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import type { ConvexHttpClient } from "convex/browser";
|
||||
|
||||
vi.mock("@/lib/auth/authContext", () => ({
|
||||
HttpError: class HttpError extends Error {
|
||||
status: number;
|
||||
|
||||
constructor(status: number, message: string) {
|
||||
super(message);
|
||||
this.status = status;
|
||||
}
|
||||
},
|
||||
requireAuthContext: vi.fn(async () => ({
|
||||
userId: "user_1",
|
||||
})),
|
||||
}));
|
||||
|
||||
vi.mock("@/lib/api-utils", () => ({
|
||||
apiErrorResponse: vi.fn((message: string, status = 500, details?: unknown) => ({
|
||||
message,
|
||||
status,
|
||||
details,
|
||||
})),
|
||||
}));
|
||||
|
||||
import {
|
||||
buildDocumentCommandEnvelope,
|
||||
type BridgeContext,
|
||||
} from "@/lib/documents/bridge";
|
||||
import { buildDocumentSavePayload } from "@/lib/documents/save-contract";
|
||||
import { executePageWriteBridgeCommand } from "@/lib/documents/page-write-command-adapter";
|
||||
|
||||
vi.mock("@/lib/convex/route", () => ({
|
||||
getAuthedConvexClient: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@/lib/documents/bridge-log", () => ({
|
||||
recordBridgeCommandArtifacts: vi.fn(),
|
||||
recordBridgeCommandFailureArtifacts: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@/lib/documents/rust-runtime", () => ({
|
||||
resolveRustBridgeCommandPlan: vi.fn(),
|
||||
executeRustBridgeMutationTransport: vi.fn(),
|
||||
}));
|
||||
|
||||
const mockContext: BridgeContext = {
|
||||
deploymentId: null,
|
||||
projectId: null,
|
||||
workspaceId: "ws_1",
|
||||
requestId: "req_1",
|
||||
traceId: "trace_1",
|
||||
actor: {
|
||||
actorType: "user",
|
||||
actorId: "user_1",
|
||||
sessionId: "sess_1",
|
||||
},
|
||||
source: {
|
||||
channel: "next-route",
|
||||
client: "vitest",
|
||||
},
|
||||
tenantId: null,
|
||||
authToken: null,
|
||||
idempotencyKey: "idem_1",
|
||||
validateOnly: false,
|
||||
dryRun: false,
|
||||
};
|
||||
|
||||
describe("page-write-command-adapter", () => {
|
||||
it("标题命令应走 rust bridge transport", async () => {
|
||||
const { getAuthedConvexClient } = await import("@/lib/convex/route");
|
||||
const {
|
||||
resolveRustBridgeCommandPlan,
|
||||
executeRustBridgeMutationTransport,
|
||||
} = await import("@/lib/documents/rust-runtime");
|
||||
|
||||
vi.mocked(getAuthedConvexClient).mockResolvedValue({
|
||||
auth: { userId: "user_1" },
|
||||
client: { mutation: vi.fn() } as unknown as ConvexHttpClient,
|
||||
});
|
||||
vi.mocked(resolveRustBridgeCommandPlan).mockResolvedValue({
|
||||
kind: "command",
|
||||
commandName: "page.head.updateTitle",
|
||||
commandId: "cmd_title_1",
|
||||
functionName: "documents:updateTitle",
|
||||
workspaceId: "ws_1",
|
||||
requestId: "req_1",
|
||||
traceId: "trace_1",
|
||||
actorId: "user_1",
|
||||
idempotencyKey: "idem_1",
|
||||
payloadJson: "{\"kind\":\"command\"}",
|
||||
argsJson: {
|
||||
id: "doc_1",
|
||||
title: "新标题",
|
||||
},
|
||||
});
|
||||
vi.mocked(executeRustBridgeMutationTransport).mockResolvedValue({ ok: true });
|
||||
|
||||
const result = await executePageWriteBridgeCommand({
|
||||
context: mockContext,
|
||||
envelope: buildDocumentCommandEnvelope({
|
||||
name: "page.head.updateTitle",
|
||||
payload: {
|
||||
documentId: "doc_1",
|
||||
workspaceId: "ws_1",
|
||||
title: "新标题",
|
||||
},
|
||||
context: mockContext,
|
||||
target: { workspaceId: "ws_1", pageId: "doc_1" },
|
||||
}),
|
||||
});
|
||||
|
||||
expect(resolveRustBridgeCommandPlan).toHaveBeenCalledWith({
|
||||
context: mockContext,
|
||||
envelope: expect.objectContaining({
|
||||
name: "page.head.updateTitle",
|
||||
}),
|
||||
});
|
||||
expect(result.commandName).toBe("page.head.updateTitle");
|
||||
expect(result.revision).toBeNull();
|
||||
expect(result.conflictDetectionKey).toBeNull();
|
||||
});
|
||||
|
||||
it("页面设置命令应走 bridge mutation request", async () => {
|
||||
const mutation = vi.fn().mockResolvedValue({ ok: true });
|
||||
const { getAuthedConvexClient } = await import("@/lib/convex/route");
|
||||
vi.mocked(getAuthedConvexClient).mockResolvedValue({
|
||||
auth: { userId: "user_1" },
|
||||
client: { mutation } as unknown as ConvexHttpClient,
|
||||
});
|
||||
|
||||
const result = await executePageWriteBridgeCommand({
|
||||
context: mockContext,
|
||||
envelope: buildDocumentCommandEnvelope({
|
||||
name: "page.layout.updateOptions",
|
||||
payload: {
|
||||
documentId: "doc_1",
|
||||
workspaceId: "ws_1",
|
||||
options: {
|
||||
showToc: true,
|
||||
layoutDensity: "compact",
|
||||
embedDefaultBlockId: null,
|
||||
},
|
||||
},
|
||||
context: mockContext,
|
||||
target: { workspaceId: "ws_1", pageId: "doc_1" },
|
||||
}),
|
||||
});
|
||||
|
||||
expect(mutation).toHaveBeenCalledTimes(1);
|
||||
expect(result.commandName).toBe("page.layout.updateOptions");
|
||||
expect(result.revision).toBeNull();
|
||||
expect(result.conflictDetectionKey).toBeNull();
|
||||
});
|
||||
|
||||
it("正文保存命令应返回 revision 与 conflictDetectionKey", async () => {
|
||||
const { getAuthedConvexClient } = await import("@/lib/convex/route");
|
||||
const {
|
||||
resolveRustBridgeCommandPlan,
|
||||
executeRustBridgeMutationTransport,
|
||||
} = await import("@/lib/documents/rust-runtime");
|
||||
|
||||
vi.mocked(getAuthedConvexClient).mockResolvedValue({
|
||||
auth: { userId: "user_1" },
|
||||
client: { mutation: vi.fn() } as unknown as ConvexHttpClient,
|
||||
});
|
||||
vi.mocked(resolveRustBridgeCommandPlan).mockResolvedValue({
|
||||
kind: "command",
|
||||
commandName: "page.body.save",
|
||||
commandId: "cmd_save_1",
|
||||
functionName: "documents:updateContent",
|
||||
workspaceId: "ws_1",
|
||||
requestId: "req_1",
|
||||
traceId: "trace_1",
|
||||
actorId: "user_1",
|
||||
idempotencyKey: "idem_1",
|
||||
payloadJson: "{\"kind\":\"command\"}",
|
||||
argsJson: {
|
||||
id: "doc_1",
|
||||
content: [{ id: "block_1" }],
|
||||
expectedRevision: 7,
|
||||
conflictDetectionKey: "conflict_1",
|
||||
},
|
||||
});
|
||||
vi.mocked(executeRustBridgeMutationTransport).mockResolvedValue({
|
||||
revision: 8,
|
||||
conflict_detection_key: "conflict_2",
|
||||
});
|
||||
|
||||
const payload = buildDocumentSavePayload({
|
||||
documentId: "doc_1",
|
||||
workspaceId: "ws_1",
|
||||
revision: 7,
|
||||
content: [{ id: "block_1" }],
|
||||
conflictDetectionKey: "conflict_1",
|
||||
});
|
||||
|
||||
const result = await executePageWriteBridgeCommand({
|
||||
context: mockContext,
|
||||
envelope: buildDocumentCommandEnvelope({
|
||||
name: "page.body.save",
|
||||
payload,
|
||||
context: mockContext,
|
||||
target: { workspaceId: "ws_1", pageId: "doc_1" },
|
||||
}),
|
||||
});
|
||||
|
||||
expect(result.commandName).toBe("page.body.save");
|
||||
expect(result.revision).toBe(8);
|
||||
expect(result.conflictDetectionKey).toBe("conflict_2");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user