Files
Epicnabbo-Catalogus-Updated…/src/actions/people-support-moderation-wrappers.test.ts
T

301 lines
8.6 KiB
TypeScript

import { revalidatePath } from "next/cache";
import { beforeEach, describe, expect, it, vi } from "vitest";
const { execute, registrations, staff } = vi.hoisted(() => ({
execute: vi.fn(),
registrations: [] as Array<{ permission: string | readonly string[] }>,
staff: { id: 42, rank: 4, username: "moderator" },
}));
function wrapper(
options: {
permission: string | readonly string[];
schema?: {
safeParse(
value: unknown,
): { success: true; data: unknown } | { success: false; error: unknown };
};
},
handler: (context: {
data: unknown;
session: { user: typeof staff };
}) => unknown,
) {
registrations.push(options);
return async (data: unknown) => {
const parsed = options.schema?.safeParse(data);
if (parsed && !parsed.success) {
return { ok: false, error: "Validation failed" };
}
try {
return await handler({
data: parsed?.data ?? data,
session: { user: staff },
});
} catch (error) {
return {
ok: false,
error: error instanceof Error ? error.message : "Internal server error",
};
}
};
}
vi.mock("@/features/housekeeping/domains/people/services/mutations", () => ({
createPeopleMutationInvocation: vi.fn((actor, correlationId) => ({
expectedActorId: actor.id,
correlationId,
legacy: true,
})),
peopleMutationService: { execute },
}));
vi.mock("@/lib/safe-action", () => ({ adminAction: wrapper }));
vi.mock("@/lib/safe-action-shared", () => ({
ActionError: class ActionError extends Error {},
actionOk: (data?: unknown) => ({ ok: true, data: data ?? {} }),
}));
vi.mock("@/lib/foundation/action", () => ({
adminAction: wrapper,
actionOk: (data?: unknown) => ({ ok: true, data: data ?? {} }),
}));
vi.mock("@/lib/permissions", () => ({
PERMS: {
TICKETS_EDIT: "admin.tickets.edit",
MOD_TICKETS_EDIT: "mod.tickets.edit",
USERS_BAN: "admin.users.ban",
MODERATION_EDIT: "admin.moderation.edit",
MOD_CFH_EDIT: "mod.cfh.edit",
MOD_ACTIONS: "mod.actions",
},
}));
vi.mock("next/cache", () => ({ revalidatePath: vi.fn() }));
import {
closeHelpCenterTicket,
liftBanFromHelpTicket,
reopenHelpCenterTicket,
replyHelpCenterTicket,
} from "./admin-help-tickets";
import {
assignCfhTicket,
broadcastAlert,
closeCfhTicket,
quickAlert,
quickKick,
quickMute,
quickRoomKick,
quickUnmute,
updateCfhState,
} from "./moderation";
import {
createTemplate,
deleteTemplate,
updateTemplate,
} from "./ticket-templates";
import {
adminReplyTicket,
assignTicket,
updateTicketPriority,
updateTicketStatus,
} from "./tickets";
type LegacyAction = (input: unknown) => Promise<unknown>;
const call = (action: unknown, input: unknown) =>
(action as LegacyAction)(input);
beforeEach(() => {
vi.clearAllMocks();
execute.mockImplementation(async (invocation, operation) => ({
ok: true,
data: {
before: null,
after: operation === "ticket-template.change" ? { id: "88" } : {},
output:
operation === "help-ticket.unban"
? { removed: 2, userId: 7 }
: undefined,
},
correlationId: invocation.correlationId,
}));
});
describe("legacy People support and moderation wrappers", () => {
it("keeps mid-rank ACL alternatives without an admin.dashboard dependency", () => {
const permissions = registrations.flatMap((entry) =>
typeof entry.permission === "string"
? [entry.permission]
: entry.permission,
);
expect(permissions).toEqual(
expect.arrayContaining([
"admin.tickets.edit",
"mod.tickets.edit",
"admin.moderation.edit",
"mod.cfh.edit",
"mod.actions",
]),
);
expect(permissions).not.toContain("admin.dashboard");
});
it("delegates tickets and templates with their established result shapes", async () => {
await expect(
call(adminReplyTicket, { ticketId: 7, message: "Handled" }),
).resolves.toEqual({ ok: true, data: {} });
await call(assignTicket, { ticketId: 7, assigneeId: 42 });
await call(updateTicketStatus, { ticketId: 7, status: "closed" });
await call(updateTicketPriority, { ticketId: 7, priority: "urgent" });
await expect(
call(createTemplate, {
title: "Greeting",
content: "Hello",
category: "general",
sortOrder: 0,
}),
).resolves.toEqual({ ok: true, data: { id: 88 } });
await expect(
call(updateTemplate, { id: 88, title: "Updated" }),
).resolves.toEqual({ ok: true, data: { id: 88 } });
await expect(call(deleteTemplate, { id: 88 })).resolves.toEqual({
ok: true,
data: {},
});
expect(execute.mock.calls.map((entry) => entry[1])).toEqual([
"ticket.reply",
"ticket.assign",
"ticket.status",
"ticket.priority",
"ticket-template.change",
"ticket-template.change",
"ticket-template.change",
]);
});
it("preserves BIGINT help-ticket IDs, outputs, and every legacy refresh", async () => {
const ticketId = 9_007_199_254_740_993n;
await call(replyHelpCenterTicket, { ticketId, content: " Handled " });
await call(closeHelpCenterTicket, { ticketId });
await call(reopenHelpCenterTicket, { ticketId });
await expect(call(liftBanFromHelpTicket, { ticketId })).resolves.toEqual({
ok: true,
data: { removed: 2, userId: 7 },
});
expect(execute.mock.calls.map((entry) => entry[2])).toEqual([
{ ticketId: "9007199254740993", content: "Handled" },
{ ticketId: "9007199254740993", status: "close" },
{ ticketId: "9007199254740993", status: "reopen" },
{ ticketId: "9007199254740993" },
]);
expect(revalidatePath).toHaveBeenCalledWith(
"/ase/people/support/help-tickets",
);
expect(revalidatePath).toHaveBeenCalledWith(
"/ase/people/support/help-tickets/9007199254740993",
);
expect(revalidatePath).toHaveBeenCalledWith(
"/help/tickets/9007199254740993",
);
expect(revalidatePath).toHaveBeenCalledWith("/ase/people/moderation/bans");
expect(revalidatePath).toHaveBeenCalledWith("/ase/people/users/7");
});
it("delegates every CFH and moderation transport action", async () => {
await call(assignCfhTicket, { ticketId: 9 });
await call(updateCfhState, { ticketId: 9, state: 3 });
await call(closeCfhTicket, { ticketId: 9 });
await call(quickKick, { userId: 7 });
await call(quickMute, { userId: 7, duration: 60 });
await call(quickUnmute, { userId: 7 });
await call(quickAlert, { userId: 7, message: "Stop" });
await call(quickRoomKick, { roomId: 12 });
await call(broadcastAlert, { message: "Notice", type: "staff" });
expect(execute.mock.calls.map((entry) => entry[1])).toEqual([
"cfh.resolve",
"cfh.resolve",
"cfh.resolve",
"moderation.action",
"moderation.action",
"moderation.action",
"moderation.action",
"moderation.action",
"moderation.action",
]);
expect(execute.mock.calls.map((entry) => entry[0].expectedActorId)).toEqual(
Array(9).fill(42),
);
});
it.each([
["kick", quickKick, { userId: 7 }],
["mute", quickMute, { userId: 7, duration: 60 }],
["unmute", quickUnmute, { userId: 7 }],
["alert", quickAlert, { userId: 7, message: "Stop" }],
["room kick", quickRoomKick, { roomId: 12 }],
["broadcast", broadcastAlert, { message: "Notice", type: "staff" }],
] as const)(
"maps a non-ok %s service result to the historical legacy failure boundary",
async (_label, action, input) => {
execute.mockResolvedValueOnce({
ok: false,
error: {
code: "DEPENDENCY_UNAVAILABLE",
messageKey: "errors.housekeeping.dependencyUnavailable",
},
correlationId: "quick-action-failure",
});
await expect(call(action, input)).resolves.toEqual({
ok: false,
error: "Could not execute moderation action",
});
},
);
it("maps a thrown moderation service failure instead of reporting success", async () => {
execute.mockRejectedValueOnce(new Error("RCON unavailable"));
await expect(call(quickKick, { userId: 7 })).resolves.toEqual({
ok: false,
error: "RCON unavailable",
});
});
it.each([
9_007_199_254_740_992,
"01",
"0",
0,
-1,
"18446744073709551616",
] as const)(
"rejects noncanonical help-ticket identifier %s at every legacy action schema",
async (ticketId) => {
for (const [action, input] of [
[replyHelpCenterTicket, { ticketId, content: "Handled" }],
[closeHelpCenterTicket, { ticketId }],
[reopenHelpCenterTicket, { ticketId }],
[liftBanFromHelpTicket, { ticketId }],
] as const) {
await expect(call(action, input)).resolves.toEqual({
ok: false,
error: "Validation failed",
});
}
expect(execute).not.toHaveBeenCalled();
},
);
it("keeps close-CFH missing rows as a successful legacy no-op", async () => {
execute.mockResolvedValueOnce({
ok: false,
error: { code: "NOT_FOUND", messageKey: "errors.housekeeping.notFound" },
correlationId: "missing-cfh",
});
await expect(call(closeCfhTicket, { ticketId: 404 })).resolves.toEqual({
ok: true,
data: {},
});
});
});