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"); }); });