153 lines
4.6 KiB
TypeScript
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",
|
|
);
|
|
});
|
|
});
|