128 lines
4.1 KiB
TypeScript
128 lines
4.1 KiB
TypeScript
import { describe, expect, it, vi } from "vitest";
|
|
import { PERMS } from "@/lib/permission-slugs";
|
|
import { confirmHousekeepingCommand } from "../../../foundation/commands/confirmation";
|
|
import type { HousekeepingCommand } from "../../../foundation/commands/registry";
|
|
import type { HousekeepingCapabilityContext } from "../../../foundation/contracts";
|
|
|
|
vi.mock("@/lib/auth", () => ({ invalidateLoginCache: vi.fn() }));
|
|
vi.mock("@/lib/auth/password", () => ({ hashPassword: vi.fn() }));
|
|
|
|
import {
|
|
COMMUNITY_COMMAND_IDS,
|
|
COMMUNITY_COMMANDS,
|
|
createCommunityCommands,
|
|
} from "./community-commands";
|
|
|
|
const expectedCommunityCommandIds = [
|
|
"people.guild.disband",
|
|
"people.application.decide",
|
|
"people.team.change",
|
|
"people.ip.action",
|
|
"people.vpn.configure",
|
|
"people.word-filter.update",
|
|
] as const;
|
|
|
|
const commands = COMMUNITY_COMMANDS as unknown as readonly HousekeepingCommand<
|
|
unknown,
|
|
unknown
|
|
>[];
|
|
|
|
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)),
|
|
};
|
|
}
|
|
|
|
describe("People community and staff commands", () => {
|
|
it("declares the exact stable People-owned command IDs", () => {
|
|
expect(COMMUNITY_COMMAND_IDS).toEqual(expectedCommunityCommandIds);
|
|
expect(commands.map((command) => command.id)).toEqual(
|
|
expectedCommunityCommandIds,
|
|
);
|
|
expect(commands.every((command) => command.owner === "people")).toBe(true);
|
|
});
|
|
|
|
it("preserves the exact legacy ACL boundary", () => {
|
|
expect(
|
|
Object.fromEntries(
|
|
commands.map((command) => [command.id, command.capability.slugs]),
|
|
),
|
|
).toEqual({
|
|
"people.guild.disband": [PERMS.USERS_EDIT],
|
|
"people.application.decide": [PERMS.USERS_EDIT],
|
|
"people.team.change": [PERMS.USERS_EDIT],
|
|
"people.ip.action": [PERMS.SETTINGS_EDIT],
|
|
"people.vpn.configure": [PERMS.SETTINGS_EDIT],
|
|
"people.word-filter.update": [PERMS.WORDFILTER_EDIT],
|
|
});
|
|
});
|
|
|
|
it("requires a nonblank reason for every destructive, global, or security mutation", () => {
|
|
expect(commands.every((command) => command.requiresReason)).toBe(true);
|
|
for (const command of commands) {
|
|
expect(
|
|
confirmHousekeepingCommand(command, "\t", "community-reason"),
|
|
).toMatchObject({
|
|
ok: false,
|
|
error: { code: "VALIDATION" },
|
|
});
|
|
}
|
|
});
|
|
|
|
it.each([
|
|
["people.guild.disband", { guildId: 0 }],
|
|
["people.application.decide", { applicationId: 7, decision: "accept" }],
|
|
["people.team.change", { action: "create", rankName: " " }],
|
|
["people.ip.action", { action: "add-whitelist", ipAddress: " " }],
|
|
[
|
|
"people.vpn.configure",
|
|
{ enabled: true, provider: "unknown", apiKey: "", blockMessage: "" },
|
|
],
|
|
["people.word-filter.update", { action: "add", word: " " }],
|
|
] as const)(
|
|
"rejects an invalid bounded payload for %s",
|
|
(commandId, input) => {
|
|
const command = commands.find((entry) => entry.id === commandId);
|
|
if (!command) throw new Error(`command missing: ${commandId}`);
|
|
expect(command.input.safeParse(input).success).toBe(false);
|
|
},
|
|
);
|
|
|
|
it("delegates the canonical operation without a redirect", async () => {
|
|
const execute = vi.fn(async (context, operation, input) => ({
|
|
ok: true as const,
|
|
data: { before: { id: 9 }, after: null, operation, input },
|
|
correlationId: context.correlationId,
|
|
}));
|
|
const created = createCommunityCommands({
|
|
execute,
|
|
}) as unknown as readonly HousekeepingCommand<unknown, unknown>[];
|
|
const command = created.find(
|
|
(entry) => entry.id === "people.guild.disband",
|
|
);
|
|
if (!command) throw new Error("command missing");
|
|
const input = command.input.parse({ guildId: 9 });
|
|
await command.execute(
|
|
{
|
|
capability: capabilityContext([PERMS.USERS_EDIT]),
|
|
correlationId: "guild-command",
|
|
ipAddress: "198.51.100.8",
|
|
},
|
|
input,
|
|
);
|
|
|
|
expect(execute).toHaveBeenCalledWith(
|
|
expect.objectContaining({ correlationId: "guild-command" }),
|
|
"guild.disband",
|
|
{ guildId: 9 },
|
|
);
|
|
});
|
|
});
|