116 lines
No EOL
4.1 KiB
TypeScript
116 lines
No EOL
4.1 KiB
TypeScript
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<typeof import("@tasks/database/chat")>()),
|
|
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<ReturnType<typeof chatMutationCatchUp>>);
|
|
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();
|
|
});
|
|
}); |