105 lines
2.9 KiB
TypeScript
105 lines
2.9 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { PERMS } from "@/lib/permission-slugs";
|
|
import type { HousekeepingCapabilityContext } from "../../../foundation/contracts";
|
|
|
|
const { audit, disconnectUser, insertValues, selectLimit } = vi.hoisted(() => ({
|
|
audit: vi.fn(),
|
|
disconnectUser: vi.fn(),
|
|
insertValues: vi.fn(),
|
|
selectLimit: vi.fn(),
|
|
}));
|
|
|
|
vi.mock("@/lib/auth", () => ({ invalidateLoginCache: vi.fn() }));
|
|
vi.mock("@/lib/auth/password", () => ({ hashPassword: vi.fn() }));
|
|
vi.mock("@/lib/db", async (importOriginal) => {
|
|
const actual = await importOriginal<typeof import("@/lib/db")>();
|
|
return {
|
|
...actual,
|
|
db: {
|
|
...actual.db,
|
|
select: vi.fn(() => ({
|
|
from: vi.fn(() => ({
|
|
where: vi.fn(() => ({ limit: selectLimit })),
|
|
})),
|
|
})),
|
|
insert: vi.fn(() => ({ values: insertValues })),
|
|
},
|
|
};
|
|
});
|
|
vi.mock("@/lib/services/audit", async (importOriginal) => ({
|
|
...(await importOriginal<typeof import("@/lib/services/audit")>()),
|
|
logAudit: audit,
|
|
}));
|
|
vi.mock("@/lib/services/rcon", async (importOriginal) => ({
|
|
...(await importOriginal<typeof import("@/lib/services/rcon")>()),
|
|
rcon: { disconnectUser },
|
|
}));
|
|
vi.mock("@/lib/services/webhook", () => ({ notify: vi.fn() }));
|
|
|
|
import { peopleMutationService } from "./mutations";
|
|
|
|
function capabilityContext(
|
|
granted: readonly string[],
|
|
): HousekeepingCapabilityContext {
|
|
const permissions = new Set(granted);
|
|
return {
|
|
actor: { id: 42, username: "operator", rank: 6 },
|
|
isSuperAdmin: false,
|
|
has: (slug) => permissions.has(slug),
|
|
hasAny: (...slugs) => slugs.some((slug) => permissions.has(slug)),
|
|
hasAll: (...slugs) => slugs.every((slug) => permissions.has(slug)),
|
|
};
|
|
}
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
selectLimit.mockResolvedValue([
|
|
{
|
|
id: 7,
|
|
username: "Alice",
|
|
mail: "[email protected]",
|
|
rank: 2,
|
|
motto: "Ready",
|
|
credits: 100,
|
|
pixels: 50,
|
|
online: "1",
|
|
},
|
|
]);
|
|
insertValues.mockResolvedValue([{}]);
|
|
disconnectUser.mockResolvedValue(true);
|
|
audit.mockResolvedValue(undefined);
|
|
});
|
|
|
|
describe("People production reason audit", () => {
|
|
it("persists the nonblank sanction reason with before and after evidence", async () => {
|
|
const result = await peopleMutationService.execute(
|
|
{
|
|
capability: capabilityContext([PERMS.USERS_BAN]),
|
|
correlationId: "production-ban",
|
|
},
|
|
"user.ban",
|
|
{
|
|
userId: 7,
|
|
reason: "Repeated harassment",
|
|
duration: 24,
|
|
type: "account",
|
|
},
|
|
);
|
|
|
|
expect(result).toMatchObject({ ok: true });
|
|
expect(audit).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
action: "people.user.ban",
|
|
before: expect.objectContaining({ id: 7, banned: false }),
|
|
after: expect.objectContaining({
|
|
id: 7,
|
|
banned: true,
|
|
reason: "Repeated harassment",
|
|
}),
|
|
}),
|
|
);
|
|
expect(audit.mock.calls[0]?.[0]?.before).not.toHaveProperty("mail");
|
|
expect(audit.mock.calls[0]?.[0]?.after).not.toHaveProperty("mail");
|
|
});
|
|
});
|