Files
HiveGrid-Wireframes/03 Quellen und Referenzen/hivegrid-cloud/apps/web/app/services/admin/support-inbox.server.test.ts
T
2026-07-08 16:15:37 +02:00

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