Files
EpicNext-Cms/src/actions/people-shared-wrappers.test.ts
T

153 lines
4.6 KiB
TypeScript

import { revalidatePath } from "next/cache";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { requirePermission } from "@/lib/admin/guard";
const { execute, staff } = vi.hoisted(() => ({
execute: vi.fn(),
staff: { id: 1, rank: 7, username: "admin" },
}));
vi.mock("@/features/housekeeping/domains/people/services/mutations", () => ({
createPeopleMutationInvocation: vi.fn((staff, correlationId) => ({
expectedActorId: staff.id,
correlationId,
legacy: true,
})),
peopleMutationService: { execute },
}));
vi.mock("@/lib/admin/guard", () => ({ requirePermission: vi.fn() }));
vi.mock("@/lib/permissions", () => ({
PERMS: {
USERS_EDIT: "admin.users.edit",
USERS_BAN: "admin.users.ban",
USERS_RESET_PASSWORD: "admin.users.reset_password",
WORDFILTER_EDIT: "admin.wordfilter.edit",
},
}));
vi.mock("@/lib/safe-action", () => ({
adminAction:
(_options: unknown, handler: (context: unknown) => unknown) =>
(data: unknown) =>
handler({ data, session: { user: staff } }),
}));
vi.mock("@/lib/safe-action-shared", () => ({
ActionError: class ActionError extends Error {
constructor(message: string) {
super(message);
this.name = "ActionError";
}
},
actionOk: (data?: unknown) => ({ ok: true, data: data ?? {} }),
actionError: (error: string) => ({ ok: false, error }),
}));
vi.mock("@/lib/auth/password", () => ({ hashPassword: vi.fn() }));
vi.mock("@/lib/db", () => ({
db: {},
User: {},
UsersBadges: {},
UsersCurrency: {},
UsersSettings: {},
}));
vi.mock("@/lib/services/audit", () => ({ logAudit: vi.fn() }));
vi.mock("@/lib/services/rcon", () => ({ rcon: {} }));
vi.mock("@/lib/services/webhook", () => ({ notify: vi.fn() }));
vi.mock("next/cache", () => ({ revalidatePath: vi.fn() }));
import { dismissApplication } from "./admin-applications";
import { addWord, deleteWord } from "./admin-wordfilter";
import { banUser, resetPassword, updateUser } from "./users";
const form = (data: Record<string, string>) =>
({ get: (key: string) => data[key] ?? null }) as FormData;
beforeEach(() => {
vi.clearAllMocks();
vi.mocked(requirePermission).mockResolvedValue(staff);
execute.mockImplementation(async (context, operation) => ({
ok: true,
data: {
before: {},
after: operation === "word-filter.update" ? { id: 12 } : {},
output:
operation === "user.reset-password"
? { newPassword: "temporary-password" }
: undefined,
},
correlationId: context.correlationId,
}));
});
describe("legacy user safe-action wrappers", () => {
it("preserves exact ACL-specific service delegation", async () => {
await (updateUser as never as (input: unknown) => Promise<unknown>)({
id: 7,
motto: "Ready",
});
await (banUser as never as (input: unknown) => Promise<unknown>)({
userId: 7,
reason: "abuse",
duration: 0,
type: "account",
});
const reset = await (
resetPassword as never as (input: unknown) => Promise<{
ok: boolean;
data: { newPassword: string };
}>
)({ userId: 7 });
expect(execute.mock.calls.map((call) => call[1])).toEqual([
"user.update",
"user.ban",
"user.reset-password",
]);
expect(execute.mock.calls.map((call) => call[0].expectedActorId)).toEqual([
1, 1, 1,
]);
expect(reset.data.newPassword).toBe("temporary-password");
});
});
describe("legacy application and word-filter wrappers", () => {
it("keeps tolerant application dismissal and ASE revalidation", async () => {
await dismissApplication(form({ id: "9" }));
expect(execute).toHaveBeenCalledWith(
expect.objectContaining({ expectedActorId: 1 }),
"application.decide",
{ applicationId: "9", decision: "dismiss" },
);
expect(revalidatePath).toHaveBeenCalledWith(
"/ase/people/staff/applications",
);
});
it("preserves application and wordfilter IDs above Number.MAX_SAFE_INTEGER", async () => {
await dismissApplication(form({ id: "9007199254740993" }));
await expect(deleteWord({ id: "9007199254740993" })).resolves.toEqual({
ok: true,
data: {},
});
expect(execute.mock.calls.slice(-2).map((call) => call[2])).toEqual([
{ applicationId: "9007199254740993", decision: "dismiss" },
{ action: "delete", id: "9007199254740993" },
]);
});
it("preserves word-filter ActionResult shapes and ASE revalidation", async () => {
await expect(addWord({ word: "spam" })).resolves.toEqual({
ok: true,
data: { id: "12" },
});
await expect(deleteWord({ id: "12" })).resolves.toEqual({
ok: true,
data: {},
});
expect(execute.mock.calls.slice(-2).map((call) => call[2])).toEqual([
{ action: "add", word: "spam" },
{ action: "delete", id: "12" },
]);
expect(revalidatePath).toHaveBeenCalledWith(
"/ase/people/moderation/word-filter",
);
});
});