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 { createPeopleMutationService, type PeopleMutationAdapter, } from "../services/mutations"; import { createUserCommands, USER_COMMAND_IDS, USER_COMMANDS, } from "./user-commands"; const expectedUserCommandIds = [ "people.user.update", "people.user.ban", "people.user.unban", "people.user.alert", "people.user.disconnect", "people.user.mute", "people.user.unmute", "people.user.reset-password", "people.user.send-currency", "people.user.trade-lock", "people.users.bulk-ban", "people.users.bulk-unban", "people.users.bulk-currency", "people.users.bulk-badge", ] as const; const commands = USER_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 user commands", () => { it("declares the exact deterministic user command list", () => { expect(USER_COMMAND_IDS).toEqual(expectedUserCommandIds); expect(commands.map((command) => command.id)).toEqual( expectedUserCommandIds, ); expect(commands.every((command) => command.owner === "people")).toBe(true); expect(commands.every((command) => command.risk === "sensitive")).toBe( true, ); }); it("uses the exact legacy ACL for every user operation", () => { const capabilities = Object.fromEntries( commands.map((command) => [command.id, command.capability.slugs]), ); expect(capabilities).toEqual({ "people.user.update": [PERMS.USERS_EDIT], "people.user.ban": [PERMS.USERS_BAN], "people.user.unban": [PERMS.USERS_BAN], "people.user.alert": [PERMS.USERS_EDIT], "people.user.disconnect": [PERMS.USERS_EDIT], "people.user.mute": [PERMS.USERS_EDIT], "people.user.unmute": [PERMS.USERS_EDIT], "people.user.reset-password": [PERMS.USERS_RESET_PASSWORD], "people.user.send-currency": [PERMS.USERS_EDIT], "people.user.trade-lock": [PERMS.USERS_EDIT], "people.users.bulk-ban": [PERMS.USERS_EDIT], "people.users.bulk-unban": [PERMS.USERS_EDIT], "people.users.bulk-currency": [PERMS.USERS_EDIT], "people.users.bulk-badge": [PERMS.USERS_EDIT], }); }); it("requires reasons for sanctions, security changes, currency, and bulk mutations", () => { const withoutReason = commands .filter((command) => !command.requiresReason) .map((command) => command.id); expect(withoutReason).toEqual(["people.user.update", "people.user.alert"]); for (const command of commands.filter((entry) => entry.requiresReason)) { expect( confirmHousekeepingCommand(command, " ", "people-reason"), ).toMatchObject({ ok: false, error: { code: "VALIDATION", fieldErrors: { reason: ["errors.validation.required"] }, }, }); } }); it.each([ ["people.user.update", { userId: 7, fields: {} }], ["people.user.alert", { userId: 7, message: " " }], ["people.user.mute", { userId: 7, duration: 87_601 }], ["people.user.send-currency", { userId: 7, amount: 1_000_001 }], ["people.users.bulk-ban", { userIds: [], reason: "reason", duration: 0 }], [ "people.users.bulk-badge", { userIds: Array.from({ length: 101 }, (_, index) => index + 1), badgeCode: "ADM", }, ], ] 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 parsed input and correlation to the redirect-free service", async () => { const execute = vi.fn(async (context, operation, input) => ({ ok: true as const, data: { before: null, after: { operation, input } }, correlationId: context.correlationId, })); const created = createUserCommands({ execute, }) as unknown as readonly HousekeepingCommand[]; const command = created.find( (entry) => entry.id === "people.user.send-currency", ); if (!command) throw new Error("command missing"); const input = command.input.parse({ userId: 7, amount: 25 }); const result = await command.execute( { capability: capabilityContext([PERMS.USERS_EDIT]), correlationId: "people-command", ipAddress: "198.51.100.8", }, input, ); expect(execute).toHaveBeenCalledWith( expect.objectContaining({ correlationId: "people-command" }), "user.send-currency", { userId: 7, amount: 25 }, ); expect(result).toMatchObject({ ok: true, data: { before: null, after: { operation: "user.send-currency" } }, }); }); }); describe("People mutation service boundary", () => { it("fails closed before the adapter when the exact capability is absent", async () => { const execute = vi.fn(async () => ({ before: null, after: null })); const service = createPeopleMutationService({ execute }); const result = await service.execute( { capability: capabilityContext([PERMS.USERS_EDIT]), correlationId: "denied-people", }, "user.ban", { userId: 7, reason: "abuse", duration: 0, type: "account" }, ); expect(result).toMatchObject({ ok: false, error: { code: "FORBIDDEN" }, correlationId: "denied-people", }); expect(execute).not.toHaveBeenCalled(); }); it("returns adapter before and after snapshots and sanitizes failures", async () => { const snapshotAdapter: PeopleMutationAdapter = { execute: async () => ({ before: { rank: 2 }, after: { rank: 3 }, }), }; const success = await createPeopleMutationService(snapshotAdapter).execute( { capability: capabilityContext([PERMS.USERS_EDIT]), correlationId: "snapshot-people", }, "user.update", { userId: 7, fields: { rank: 3 } }, ); expect(success).toMatchObject({ ok: true, data: { before: { rank: 2 }, after: { rank: 3 } }, }); const failureAdapter: PeopleMutationAdapter = { execute: async () => { throw new Error("database password=secret"); }, }; const failure = await createPeopleMutationService(failureAdapter).execute( { capability: capabilityContext([PERMS.USERS_EDIT]), correlationId: "failed-people", }, "user.update", { userId: 7, fields: { motto: "Ready" } }, ); expect(failure).toMatchObject({ ok: false, error: { code: "DEPENDENCY_UNAVAILABLE", messageKey: "errors.housekeeping.dependencyUnavailable", }, }); expect(JSON.stringify(failure)).not.toContain("password=secret"); }); });