fix(housekeeping): isolate executable command schemas
This commit is contained in:
1 parent
00618fb2a3
commit
7895188b56
5 files changed
+431
-67
No files matched your search
@@ -6,6 +6,7 @@ import {
|
||||
type HousekeepingCommand,
|
||||
registerHousekeepingCommand,
|
||||
sealHousekeepingCommandRegistry,
|
||||
UnsupportedHousekeepingCommandSchemaError,
|
||||
} from "./registry";
|
||||
|
||||
function command(
|
||||
@@ -154,6 +155,147 @@ describe("housekeeping command registry", () => {
|
||||
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("seals the global registry after deterministic bootstrap", () => {
|
||||
sealHousekeepingCommandRegistry();
|
||||
|
||||
|
||||
Reference in new issue
Block a user