Files
Epicnabbo-Catalogus-Updated…/src/actions/housekeeping-command.test.ts
T

185 lines
5.2 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from "vitest";
import { z } from "zod";
import { registerHousekeepingCommand } from "@/features/housekeeping/foundation/commands/registry";
import {
anyCapability,
ok,
} from "@/features/housekeeping/foundation/contracts";
import type { AuditEntry } from "@/lib/services/audit";
const {
auditEntries,
auditWriteMock,
commandExecutions,
context,
getContextMock,
getIpMock,
rateLimitCalls,
} = vi.hoisted(() => ({
auditEntries: [] as AuditEntry[],
auditWriteMock: vi.fn(),
commandExecutions: [] as string[],
context: {
actor: { id: 71, username: "server-operator", rank: 4 },
isSuperAdmin: false,
has: (slug: string) => slug === "admin.settings.edit",
hasAny: (...slugs: string[]) => slugs.includes("admin.settings.edit"),
hasAll: (...slugs: string[]) =>
slugs.every((slug) => slug === "admin.settings.edit"),
},
getContextMock: vi.fn(),
getIpMock: vi.fn(),
rateLimitCalls: [] as Array<[string, number, number]>,
}));
vi.mock("@/features/housekeeping/foundation/server-capability-context", () => ({
getHousekeepingCapabilityContext: getContextMock,
}));
vi.mock("@/lib/services/audit", () => ({
housekeepingAuditWriter: { write: auditWriteMock },
}));
vi.mock("@/lib/rate-limit", () => ({
clientIp: getIpMock,
rateLimit: async (key: string, attempts: number, windowMs: number) => {
rateLimitCalls.push([key, attempts, windowMs]);
return { ok: true, retryAfter: 0 };
},
}));
import { executeHousekeepingCommand } from "./housekeeping-command";
registerHousekeepingCommand({
id: "system.server-action.serializable",
owner: "system",
risk: "safe",
capability: anyCapability("admin.settings.edit"),
input: z.object({ value: z.string() }),
requiresReason: false,
rateLimit: { attempts: 5, windowMs: 120_000 },
execute: async (commandContext, input) => {
commandExecutions.push(input.value);
return ok(
{
value: input.value,
actorId: commandContext.capability.actor.id,
ipAddress: commandContext.ipAddress,
},
commandContext.correlationId,
);
},
});
beforeEach(() => {
auditEntries.length = 0;
commandExecutions.length = 0;
rateLimitCalls.length = 0;
getContextMock.mockReset().mockResolvedValue(context);
getIpMock.mockReset().mockResolvedValue("203.0.113.7");
auditWriteMock.mockReset().mockImplementation(async (entry: AuditEntry) => {
auditEntries.push({ ...entry });
});
});
describe("executeHousekeepingCommand", () => {
it("accepts a plain request and derives all policy metadata server-side", async () => {
const result = await executeHousekeepingCommand({
commandId: "system.server-action.serializable",
input: { value: "saved" },
});
expect(result).toMatchObject({
ok: true,
data: {
value: "saved",
actorId: 71,
ipAddress: "203.0.113.7",
},
});
expect(rateLimitCalls).toEqual([
[
"housekeeping-command:71:203.0.113.7:system.server-action.serializable",
5,
120_000,
],
]);
expect(auditEntries).toMatchObject([
{
userId: 71,
action: "system.server-action.serializable",
target: "system",
domain: "system",
ipAddress: "203.0.113.7",
outcome: "success",
},
]);
expect(auditEntries[0]?.correlationId).toBe(result.correlationId);
});
it("strictly rejects spoofed server-owned metadata before execution", async () => {
const result = await executeHousekeepingCommand({
commandId: "system.server-action.serializable",
input: { value: "forged" },
risk: "sensitive",
owner: "people",
capability: { mode: "any", slugs: ["forged.permission"] },
actor: { id: 999 },
ipAddress: "198.51.100.9",
rateLimit: { attempts: 999, windowMs: 1 },
audit: { action: "forged.action", target: "forged-target" },
} as never);
expect(result).toMatchObject({
ok: false,
error: { code: "VALIDATION" },
});
expect(commandExecutions).toEqual([]);
expect(rateLimitCalls).toEqual([]);
expect(auditEntries).toEqual([]);
});
it("sanitizes server context acquisition failures into typed results", async () => {
getContextMock.mockRejectedValue(
new Error("session database secret exposed"),
);
const result = await executeHousekeepingCommand({
commandId: "system.server-action.serializable",
input: { value: "blocked" },
});
expect(result).toMatchObject({
ok: false,
error: { code: "INTERNAL", messageKey: "errors.housekeeping.internal" },
});
expect(JSON.stringify(result)).not.toContain("secret exposed");
expect(commandExecutions).toEqual([]);
});
it("maps completed-operation audit failures to a typed partial result", async () => {
auditWriteMock.mockImplementation(async (entry: AuditEntry) => {
if (entry.outcome === "success") {
throw new Error("success audit unavailable");
}
auditEntries.push({ ...entry });
});
const result = await executeHousekeepingCommand({
commandId: "system.server-action.serializable",
input: { value: "changed" },
});
expect(commandExecutions).toEqual(["changed"]);
expect(result).toMatchObject({
ok: false,
error: {
code: "INTERNAL",
messageKey: "errors.housekeeping.partial",
},
});
expect(auditEntries.map((entry) => entry.outcome)).toEqual(["partial"]);
expect(auditEntries[0]?.correlationId).toBe(result.correlationId);
});
});