import { beforeEach, describe, expect, it, vi } from "vitest"; import { db } from "@tasks/database"; import type { Session } from "next-auth"; import { ChatMutationError, mutateChatMessage, createMessageMutationRunner, chatMutationCatchUp, } from "@tasks/database/chat"; import { resolveWorkspace } from "@/server/lib/resolve-workspace"; import { createCallerFactory } from "@/server/trpc"; import { chatRouter } from "./chat"; vi.mock("@tasks/database/chat", async (importOriginal) => ({ ...(await importOriginal()), mutateChatMessage: vi.fn(), chatMutationCatchUp: vi.fn(), createMessageMutationRunner: vi.fn(() => ({ transaction: vi.fn() })), })); vi.mock("@/server/lib/resolve-workspace", () => ({ resolveWorkspace: vi.fn(), })); vi.mock("@/lib/auth", () => ({ auth: vi.fn() })); const workspaceId = "11111111-1111-4111-8111-111111111111"; const channelId = "33333333-3333-4333-8333-333333333333"; const userId = "44444444-4444-4444-8444-444444444444"; const messageId = "77777777-7777-4777-8777-777777777777"; const clientMutationId = "66666666-6666-4666-8666-666666666666"; const caller = () => createCallerFactory(chatRouter)({ db, session: { user: { id: userId } } as Session, }); const command = { type: "edit_message" as const, workspaceId, channelId, context: { type: "workspace" as const, workspaceId }, messageId, body: "corrected", expectedRevision: 2, clientMutationId, }; beforeEach(() => { vi.clearAllMocks(); vi.mocked(resolveWorkspace).mockResolvedValue({ id: workspaceId, slug: "acme", name: "Acme", role: "member", ownerUserId: userId, }); }); describe("chat message mutation procedures", () => { it("delegates validated edit and delete with actor and exact command", async () => { const result = { changed: true, result: { messageId, revision: 3, status: "edited" as const }, }; vi.mocked(mutateChatMessage).mockResolvedValue(result); await expect(caller().editMessage({ workspace: workspaceId, command })).resolves.toEqual(result); expect(mutateChatMessage).toHaveBeenCalledWith(expect.anything(), { actorUserId: userId, command, }); }); }); describe("mutation recovery procedure", () => { const input = { workspace: "acme", channelId, after: "0", limit: 2 }; const result = { mutations: [{ messageId, mutationSequence: "1", revision: 3, kind: "edited" as const }], next: "1", through: "3", hasMore: true, }; it("passes the resolved workspace and session actor, and preserves the fixed high water", async () => { vi.mocked(chatMutationCatchUp).mockResolvedValue(result as Awaited>); await expect(caller().mutationCatchUp(input)).resolves.toEqual(result); expect(chatMutationCatchUp).toHaveBeenCalledWith(db, { workspaceId, actorUserId: userId, channelId, after: "0", through: undefined, limit: 2, }); await caller().mutationCatchUp({ ...input, after: "1", through: "3" }); expect(chatMutationCatchUp).toHaveBeenLastCalledWith(db, { workspaceId, actorUserId: userId, channelId, after: "1", through: "3", limit: 2, }); }); it("rejects malformed cursors and maps read denial without exposing a channel", async () => { await expect(caller().mutationCatchUp({ ...input, after: "01" })).rejects.toMatchObject({ code: "BAD_REQUEST" }); await expect(caller().mutationCatchUp({ ...input, through: "0", after: "1" })).rejects.toMatchObject({ code: "BAD_REQUEST" }); expect(chatMutationCatchUp).not.toHaveBeenCalled(); vi.mocked(chatMutationCatchUp).mockRejectedValueOnce(new (await import("@tasks/database/chat")).ChatReadError()); await expect(caller().mutationCatchUp(input)).rejects.toMatchObject({ code: "NOT_FOUND" }); }); it("requires an authenticated workspace member", async () => { vi.mocked(resolveWorkspace).mockRejectedValueOnce(new Error("not a member")); await expect(caller().mutationCatchUp(input)).rejects.toThrow(); expect(chatMutationCatchUp).not.toHaveBeenCalled(); }); });