Files
EpicNext-Cms/src/features/housekeeping/foundation/commands/registry.test.ts
T
Simo 01dceac073
CI / check (pull_request) Successful in 30s
CI / release (pull_request) Skipped
CI / deploy (pull_request) Skipped
fix(housekeeping): close schema reflection escapes
2026-08-27 20:31:10 +02:00

517 lines
17 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { z } from "zod";
import { anyCapability, ok } from "../contracts";
import {
getHousekeepingCommand,
type HousekeepingCommand,
registerHousekeepingCommand,
sealHousekeepingCommandRegistry,
UnsupportedHousekeepingCommandSchemaError,
} from "./registry";
function command(
id: string,
owner: "people" | "system" = "people",
): HousekeepingCommand<{ id: number }, { id: number }> {
return {
id,
owner,
risk: "safe",
capability: anyCapability("admin.users.view"),
input: z.object({ id: z.number().int().positive() }),
requiresReason: false,
rateLimit: { attempts: 4, windowMs: 30_000 },
execute: async (context, input) => ok(input, context.correlationId),
};
}
type DescriptorReader = (
target: object,
property: PropertyKey,
) => PropertyDescriptor | undefined;
const descriptorReaders = [
[
"Object.getOwnPropertyDescriptor",
"object-descriptor",
Object.getOwnPropertyDescriptor,
],
[
"Object.getOwnPropertyDescriptors",
"object-descriptors",
(target: object, property: PropertyKey) =>
Reflect.get(Object.getOwnPropertyDescriptors(target), property) as
| PropertyDescriptor
| undefined,
],
[
"Reflect.getOwnPropertyDescriptor",
"reflect-descriptor",
Reflect.getOwnPropertyDescriptor,
],
] as const satisfies readonly (readonly [string, string, DescriptorReader])[];
function readDescriptorValue(
target: object,
property: PropertyKey,
readDescriptor: DescriptorReader,
): unknown {
const descriptor = readDescriptor(target, property);
if (!descriptor) throw new Error(`missing descriptor: ${String(property)}`);
if ("value" in descriptor) return descriptor.value;
if (descriptor.get) return Reflect.apply(descriptor.get, target, []);
return undefined;
}
function attemptMutation(mutate: () => void): void {
try {
mutate();
} catch {
// Readonly public views may reject the mutation directly.
}
}
describe("housekeeping command registry", () => {
it("returns the validated snapshot registered under its global ID", () => {
const registered = command("people.registry.lookup");
registerHousekeepingCommand(registered);
expect(getHousekeepingCommand(registered.id)).toMatchObject({
id: "people.registry.lookup",
owner: "people",
risk: "safe",
requiresReason: false,
rateLimit: { attempts: 4, windowMs: 30_000 },
});
expect(getHousekeepingCommand(registered.id)).not.toBe(registered);
});
it("rejects a duplicate global command ID before it can shadow its owner", () => {
registerHousekeepingCommand(command("people.registry.duplicate"));
expect(() =>
registerHousekeepingCommand(command("people.registry.duplicate")),
).toThrow("duplicate command id: people.registry.duplicate");
});
it("rejects a command whose namespace disagrees with its declared owner", () => {
expect(() =>
registerHousekeepingCommand(
command("people.registry.owner-mismatch", "system"),
),
).toThrow("command owner mismatch: people.registry.owner-mismatch");
});
it("stores a frozen snapshot that resists mutation through the original definition", () => {
const original = command("people.registry.immutable");
const callerOwnedInput = original.input;
registerHousekeepingCommand(original);
const registered = getHousekeepingCommand(original.id);
if (!registered) throw new Error("registered command missing");
const registeredInput = registered.input;
const registeredExecute = registered.execute;
(original as { risk: "safe" | "sensitive" }).risk = "sensitive";
(original as { owner: "people" | "system" }).owner = "system";
(original as { input: z.ZodType<{ id: number }> }).input = z.object({
id: z.literal(999),
});
(original.capability.slugs as string[]).push("admin.settings.edit");
(original.rateLimit as { attempts: number }).attempts = 999;
(
original as {
execute: HousekeepingCommand<{ id: number }, { id: number }>["execute"];
}
).execute = async (context) => ok({ id: 999 }, context.correlationId);
expect(registered).toMatchObject({
risk: "safe",
owner: "people",
capability: { slugs: ["admin.users.view"] },
rateLimit: { attempts: 4, windowMs: 30_000 },
});
expect(registered.input).toBe(registeredInput);
expect(registered.input).not.toBe(callerOwnedInput);
expect(Object.isFrozen(callerOwnedInput)).toBe(false);
expect(registered.execute).toBe(registeredExecute);
expect(Object.isFrozen(registered)).toBe(true);
expect(Object.isFrozen(registered.input)).toBe(true);
expect(Object.isFrozen(registered.capability)).toBe(true);
expect(Object.isFrozen(registered.capability.slugs)).toBe(true);
expect(Object.isFrozen(registered.rateLimit)).toBe(true);
});
it.each([
["risk", { risk: "dangerous" }],
["requiresReason", { requiresReason: "yes" }],
[
"capability mode",
{ capability: { mode: "none", slugs: ["admin.users.view"] } },
],
["empty capability", { capability: { mode: "any", slugs: [] } }],
[
"unknown capability",
{ capability: { mode: "any", slugs: ["forged.permission"] } },
],
["execute", { execute: null }],
[
"Zod schema",
{ input: { safeParse: () => ({ success: true, data: {} }) } },
],
["normalized ID", { id: " people.registry.invalid-id " }],
] as const)("rejects an invalid %s definition", (label, override) => {
const invalid = {
...command(
`people.registry.invalid-${label.toLowerCase().replaceAll(" ", "-")}`,
),
...override,
} as unknown as HousekeepingCommand<{ id: number }, { id: number }>;
expect(() => registerHousekeepingCommand(invalid)).toThrow();
});
it("keeps registered validation unchanged after original shape and child mutation", () => {
const child = z.string().min(3);
const input = z.object({ value: child, guard: child });
registerHousekeepingCommand({
...command("people.registry.deep-validation"),
input,
execute: async (context, value) =>
ok({ id: value.value.length }, context.correlationId),
} as HousekeepingCommand<{ value: string; guard: string }, { id: number }>);
const registered = getHousekeepingCommand(
"people.registry.deep-validation",
);
if (!registered) throw new Error("registered command missing");
(input.def as { shape: { value: z.ZodType } }).shape.value = z.number();
const minCheck = (child.def as { checks: unknown[] }).checks[0] as {
_zod: { def: { minimum: number } };
};
minCheck._zod.def.minimum = 0;
expect(input.safeParse({ value: 4, guard: "a" }).success).toBe(true);
expect(
registered.input.safeParse({ value: "abcd", guard: "ab" }).success,
).toBe(false);
expect(
registered.input.safeParse({ value: "abcd", guard: "abcd" }).success,
).toBe(true);
expect(
registered.input.safeParse({ value: 4, guard: "abcd" }).success,
).toBe(false);
});
it("snapshots caller-owned lazy targets across supported container schemas", () => {
let lazyChild: z.ZodType = z.string().min(2);
let defaultValue = "registered-default";
const input = z.object({
choice: z.union([z.lazy(() => lazyChild), z.number().int()]),
optional: z.lazy(() => lazyChild).optional(),
defaulted: z.string().default(() => defaultValue),
list: z.array(z.lazy(() => lazyChild)),
lookup: z.record(
z.string(),
z.lazy(() => lazyChild),
),
});
registerHousekeepingCommand({
...command("people.registry.lazy-containers"),
input,
execute: async (context) => ok({ id: 1 }, context.correlationId),
} as HousekeepingCommand<z.output<typeof input>, { id: number }>);
const registered = getHousekeepingCommand(
"people.registry.lazy-containers",
);
if (!registered) throw new Error("registered command missing");
lazyChild = z.boolean();
defaultValue = "caller-mutated-default";
const parsed = registered.input.safeParse({
choice: "ok",
list: ["one"],
lookup: { key: "two" },
});
expect(parsed).toMatchObject({
success: true,
data: { defaulted: "registered-default" },
});
expect(
registered.input.safeParse({
choice: true,
optional: true,
list: [true],
lookup: { key: true },
}).success,
).toBe(false);
});
it("snapshots recursive lazy back-edges with cycle safety", () => {
type TreeNode = { name: string; children: TreeNode[] };
let nameSchema: z.ZodType = z.string().min(2);
let recursiveSchema: z.ZodType<TreeNode>;
recursiveSchema = z.object({
name: z.lazy(() => nameSchema),
children: z.array(z.lazy(() => recursiveSchema)),
}) as z.ZodType<TreeNode>;
registerHousekeepingCommand({
...command("people.registry.recursive-lazy"),
input: recursiveSchema,
execute: async (context) => ok({ id: 1 }, context.correlationId),
} as HousekeepingCommand<TreeNode, { id: number }>);
const registered = getHousekeepingCommand("people.registry.recursive-lazy");
if (!registered) throw new Error("registered command missing");
nameSchema = z.number();
recursiveSchema = z.object({
name: z.number(),
children: z.array(z.unknown()),
}) as unknown as z.ZodType<TreeNode>;
expect(
registered.input.safeParse({
name: "root",
children: [{ name: "leaf", children: [] }],
}).success,
).toBe(true);
expect(
registered.input.safeParse({
name: "root",
children: [{ name: 7, children: [] }],
}).success,
).toBe(false);
});
it.each([
["refine", "custom", z.string().refine((value) => value === "allowed")],
["super-refine", "custom", z.string().superRefine(() => undefined)],
[
"preprocess",
"transform",
z.preprocess((value) => String(value), z.string()),
],
["transform", "transform", z.string().transform((value) => value.length)],
["async-refine", "custom", z.string().refine(async () => true)],
] as const)(
"rejects unsupported executable validation schema %s",
(suffix, schemaKind, input) => {
const id = `people.registry.unsupported-${suffix}`;
let thrown: unknown;
try {
registerHousekeepingCommand({
...command(id),
input,
execute: async (context) => ok({ id: 1 }, context.correlationId),
} as HousekeepingCommand<unknown, { id: number }>);
} catch (error) {
thrown = error;
}
expect(thrown).toBeInstanceOf(UnsupportedHousekeepingCommandSchemaError);
expect(thrown).toMatchObject({
name: "UnsupportedHousekeepingCommandSchemaError",
code: "UNSUPPORTED_COMMAND_INPUT_SCHEMA",
commandId: id,
schemaKind,
});
expect(getHousekeepingCommand(id)).toBeUndefined();
},
);
it("rejects a lazy edge that cannot be resolved at registration", () => {
const id = "people.registry.unresolvable-lazy";
let thrown: unknown;
try {
registerHousekeepingCommand({
...command(id),
input: z.lazy(() => {
throw new Error("caller lazy secret");
}),
execute: async (context) => ok({ id: 1 }, context.correlationId),
} as HousekeepingCommand<unknown, { id: number }>);
} catch (error) {
thrown = error;
}
expect(thrown).toBeInstanceOf(UnsupportedHousekeepingCommandSchemaError);
expect(thrown).toMatchObject({
name: "UnsupportedHousekeepingCommandSchemaError",
code: "UNSUPPORTED_COMMAND_INPUT_SCHEMA",
commandId: id,
schemaKind: "lazy",
});
expect(String(thrown)).not.toContain("caller lazy secret");
});
it.each(descriptorReaders)(
"keeps private validation unchanged after nested mutation through %s",
(_api, suffix, readDescriptor) => {
const input = z.object({ value: z.string().min(3) });
const id = `people.registry.${suffix}`;
registerHousekeepingCommand({
...command(id),
input,
execute: async (context) => ok({ id: 1 }, context.correlationId),
} as HousekeepingCommand<z.output<typeof input>, { id: number }>);
const registered = getHousekeepingCommand(id);
if (!registered) throw new Error("registered command missing");
const definition = readDescriptorValue(
registered.input,
"def",
readDescriptor,
) as object;
const shape = readDescriptorValue(
definition,
"shape",
readDescriptor,
) as { value: z.ZodType };
const child = shape.value;
const childDefinition = readDescriptorValue(
child,
"def",
readDescriptor,
) as object;
const checks = readDescriptorValue(
childDefinition,
"checks",
readDescriptor,
) as readonly object[];
const internal = readDescriptorValue(
checks[0] as object,
"_zod",
readDescriptor,
) as object;
const checkDefinition = readDescriptorValue(
internal,
"def",
readDescriptor,
) as { minimum: number };
attemptMutation(() => {
shape.value = z.number();
});
attemptMutation(() => {
checkDefinition.minimum = 0;
});
expect(registered.input.safeParse({ value: "ab" }).success).toBe(false);
expect(registered.input.safeParse({ value: "abcd" }).success).toBe(true);
expect(registered.input.safeParse({ value: 7 }).success).toBe(false);
},
);
it("keeps own-key and symbol iteration detached from private checks", () => {
const input = z.object({ value: z.string().min(3) });
const id = "people.registry.symbol-iteration";
registerHousekeepingCommand({
...command(id),
input,
execute: async (context) => ok({ id: 1 }, context.correlationId),
} as HousekeepingCommand<z.output<typeof input>, { id: number }>);
const registered = getHousekeepingCommand(id);
if (!registered) throw new Error("registered command missing");
const publicInput = registered.input as z.ZodObject<{
value: z.ZodString;
}>;
const shape = publicInput.shape;
expect(Reflect.ownKeys(publicInput.def)).toContain("shape");
expect(Object.keys(shape)).toEqual(["value"]);
const child = Object.entries(shape)[0]?.[1];
if (!child) throw new Error("public child schema missing");
const checks = child.def.checks ?? [];
const spreadChecks = [...checks];
const iterator = checks[Symbol.iterator]();
const iteratedCheck = iterator.next().value;
if (!iteratedCheck) throw new Error("public check missing");
expect(spreadChecks[0]).toBe(iteratedCheck);
attemptMutation(() => {
(
iteratedCheck as unknown as {
_zod: { def: { minimum: number } };
}
)._zod.def.minimum = 0;
});
expect(registered.input.safeParse({ value: "ab" }).success).toBe(false);
expect(registered.input.safeParse({ value: "abcd" }).success).toBe(true);
});
it("keeps prototype methods operational without passing the private schema to callbacks", async () => {
const input = z.object({ value: z.string().min(3) });
const id = "people.registry.prototype-methods";
registerHousekeepingCommand({
...command(id),
input,
execute: async (context) => ok({ id: 1 }, context.correlationId),
} as HousekeepingCommand<z.output<typeof input>, { id: number }>);
const registered = getHousekeepingCommand(id);
if (!registered) throw new Error("registered command missing");
const publicInput = registered.input as z.ZodObject<{
value: z.ZodString;
}>;
const publicPrototype = Object.getPrototypeOf(publicInput);
expect(publicPrototype).toBe(Reflect.getPrototypeOf(publicInput));
const prototypeMutation = Symbol("prototype-mutation");
Object.defineProperty(publicPrototype, prototypeMutation, {
configurable: true,
value(this: z.ZodObject<{ value: z.ZodString }>): unknown {
attemptMutation(() => {
this.def.shape.value = z.number() as never;
});
return this.def;
},
});
try {
(
publicInput as typeof publicInput & {
[prototypeMutation](): unknown;
}
)[prototypeMutation]();
} finally {
Reflect.deleteProperty(publicPrototype, prototypeMutation);
}
expect(publicInput.safeParse({ value: "abcd" }).success).toBe(true);
expect(publicInput.safeParse({ value: 7 }).success).toBe(false);
let appliedSchema: z.ZodType | undefined;
const applied = publicInput.apply((schema) => {
appliedSchema = schema;
return schema;
});
expect(appliedSchema).toBe(publicInput);
expect(applied).toBe(publicInput);
let externallyRegistered: z.ZodType | undefined;
const externalRegistry = {
add(schema: z.ZodType): void {
externallyRegistered = schema;
},
};
expect(
publicInput.register(externalRegistry as never, undefined as never),
).toBe(publicInput);
expect(externallyRegistered).toBe(publicInput);
const partial = publicInput.partial();
const picked = publicInput.pick({ value: true });
expect(partial.safeParse({}).success).toBe(true);
expect(picked.safeParse({ value: "abcd" }).success).toBe(true);
expect((await publicInput.safeParseAsync({ value: "ab" })).success).toBe(
false,
);
expect((await publicInput.safeParseAsync({ value: "abcd" })).success).toBe(
true,
);
});
it("seals the global registry after deterministic bootstrap", () => {
sealHousekeepingCommandRegistry();
expect(() =>
registerHousekeepingCommand(command("people.registry.after-seal")),
).toThrow("command registry is sealed");
});
});