81 lines
3.5 KiB
TypeScript
81 lines
3.5 KiB
TypeScript
import { describe, expect, it, vi } from "vitest";
|
|
import {
|
|
assignSupportTicket,
|
|
postSupportInternalNote,
|
|
postSupportReply,
|
|
setSupportTicketStatus,
|
|
SupportInboxError,
|
|
type SupportInboxDeps,
|
|
} from "./support-inbox.server";
|
|
|
|
function makeDeps(): {
|
|
deps: SupportInboxDeps;
|
|
createMessage: ReturnType<typeof vi.fn>;
|
|
updateTicket: ReturnType<typeof vi.fn>;
|
|
recordAction: ReturnType<typeof vi.fn>;
|
|
} {
|
|
const createMessage = vi.fn(async () => ({}));
|
|
const updateTicket = vi.fn(async () => ({}));
|
|
const recordAction = vi.fn(async () => {});
|
|
const deps = {
|
|
db: {
|
|
supportTicket: { update: updateTicket },
|
|
supportTicketMessage: { create: createMessage },
|
|
},
|
|
recordAction,
|
|
} as unknown as SupportInboxDeps;
|
|
return { deps, createMessage, updateTicket, recordAction };
|
|
}
|
|
|
|
describe("postSupportReply", () => {
|
|
it("creates a customer-visible message, marks answered and audits", async () => {
|
|
const { deps, createMessage, updateTicket, recordAction } = makeDeps();
|
|
await postSupportReply({ ticketId: "t1", body: " Hallo ", authorUserId: "a1" }, deps);
|
|
expect(createMessage.mock.calls[0][0].data).toMatchObject({ supportTicketId: "t1", body: "Hallo", internalNote: false });
|
|
expect(updateTicket.mock.calls[0][0].data).toMatchObject({ status: "answered" });
|
|
expect(recordAction.mock.calls[0][0]).toMatchObject({ action: "support.replied", targetId: "t1" });
|
|
});
|
|
|
|
it("rejects an empty body before any write", async () => {
|
|
const { deps, createMessage } = makeDeps();
|
|
await expect(postSupportReply({ ticketId: "t1", body: " ", authorUserId: "a1" }, deps)).rejects.toBeInstanceOf(SupportInboxError);
|
|
expect(createMessage).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe("postSupportInternalNote", () => {
|
|
it("creates an internal note (internalNote=true) and audits without touching status", async () => {
|
|
const { deps, createMessage, updateTicket, recordAction } = makeDeps();
|
|
await postSupportInternalNote({ ticketId: "t1", body: "intern", authorUserId: "a1" }, deps);
|
|
expect(createMessage.mock.calls[0][0].data).toMatchObject({ internalNote: true });
|
|
expect(updateTicket).not.toHaveBeenCalled();
|
|
expect(recordAction.mock.calls[0][0]).toMatchObject({ action: "support.noteAdded" });
|
|
});
|
|
});
|
|
|
|
describe("setSupportTicketStatus", () => {
|
|
it("rejects an unknown status", async () => {
|
|
const { deps, updateTicket } = makeDeps();
|
|
await expect(setSupportTicketStatus({ ticketId: "t1", status: "frozen", actorUserId: "a1" }, deps)).rejects.toMatchObject({
|
|
code: "INVALID_STATUS",
|
|
});
|
|
expect(updateTicket).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("updates a valid status and audits", async () => {
|
|
const { deps, updateTicket, recordAction } = makeDeps();
|
|
await setSupportTicketStatus({ ticketId: "t1", status: "closed", actorUserId: "a1" }, deps);
|
|
expect(updateTicket.mock.calls[0][0].data).toMatchObject({ status: "closed" });
|
|
expect(recordAction.mock.calls[0][0]).toMatchObject({ action: "support.statusChanged", after: { status: "closed" } });
|
|
});
|
|
});
|
|
|
|
describe("assignSupportTicket", () => {
|
|
it("sets the assignee and audits, allowing unassignment (null)", async () => {
|
|
const { deps, updateTicket, recordAction } = makeDeps();
|
|
await assignSupportTicket({ ticketId: "t1", assigneeUserId: null, actorUserId: "a1" }, deps);
|
|
expect(updateTicket.mock.calls[0][0].data).toMatchObject({ assignedToUserId: null });
|
|
expect(recordAction.mock.calls[0][0]).toMatchObject({ action: "support.assigned" });
|
|
});
|
|
});
|