@plasius/schema
Version:
Entity schema definition & validation helpers for Plasius ecosystem
502 lines (451 loc) • 16.9 kB
text/typescript
import { describe, it } from "vitest";
import { createSchema } from "../src/schema.js";
import { field } from "../src/field.js";
import { expectValid, expectInvalid } from "./test-utils.js";
/**
* Full coverage test titles mapped to validator error branches.
* We compose minimal schemas per case so failures isolate the targeted branch.
*/
describe("schema.ts – validator coverage", () => {
// 1) Field definition missing
it("should fail when field definition is missing", () => {
const S = createSchema(
{
// Force an undefined field definition to simulate a bad schema shape
bad: undefined as any,
},
"BadDef",
{ version: "1.0.0", piiEnforcement: "strict" }
);
const r = S.validate({ bad: "anything" });
expectInvalid(r, "Field definition missing for: bad");
});
// Base schemas reused in several tests
const StringEnum = createSchema(
{
k: field.string().enum(["A", "B", "C"]),
},
"StringEnum",
{ version: "1.0.0", piiEnforcement: "strict" }
);
const NumberField = createSchema({ n: field.number() }, "NumberField", {
version: "1.0.0",
piiEnforcement: "strict",
});
const BooleanField = createSchema({ b: field.boolean() }, "BooleanField", {
version: "1.0.0",
piiEnforcement: "strict",
});
const SimpleObject = createSchema(
{
o: field.object({
a: field.string(),
b: field.number().optional(),
nested: field
.object({
f: field.boolean(),
tag: field.string().enum(["x", "y"]).optional(),
})
.optional(),
}),
},
"SimpleObject",
{ version: "1.0.0", piiEnforcement: "strict" }
);
const ArrayOfStrings = createSchema(
{ tags: field.array(field.string().enum(["alpha", "beta"])) },
"ArrayOfStrings",
{ version: "1.0.0", piiEnforcement: "strict" }
);
const ArrayOfNumbers = createSchema(
{ nums: field.array(field.number()) },
"ArrayOfNumbers",
{ version: "1.0.0", piiEnforcement: "strict" }
);
const ArrayOfBooleans = createSchema(
{ flags: field.array(field.boolean()) },
"ArrayOfBooleans",
{ version: "1.0.0", piiEnforcement: "strict" }
);
const ArrayOfObjects = createSchema(
{
items: field.array(
field.object({
type: field.string().enum(["a", "b"]),
value: field.number().optional(),
info: field.object({ ok: field.boolean() }).optional(),
})
),
},
"ArrayOfObjects",
{ version: "1.0.0", piiEnforcement: "strict" }
);
// 2) Missing required field
it("should fail when required field is missing", () => {
const result = StringEnum.validate({});
expectInvalid(result, "Missing required field: k");
});
// 3) Immutable field modified (not directly represented)
it("should fail when immutable field is modified", () => {
const fixed = field.string().immutable() as any;
const S = createSchema({ fixed }, "Immutable", {
version: "1.0.0",
piiEnforcement: "strict",
});
// Simulate existing stored value
const existing = { fixed: "A" };
const r = S.validate({ fixed: "B" }, existing);
expectInvalid(r, "Field is immutable: fixed");
});
// 4) High PII empty under strict (only if such flag exists)
it("should fail when high PII required field is empty under strict enforcement", () => {
const secret = field.string().PID({
classification: "high",
action: "encrypt",
logHandling: "redact",
purpose: "pii security test",
}) as any;
// required by default (not optional)
const S = createSchema({ secret }, "PII", {
version: "1.0.0",
piiEnforcement: "strict",
});
const r = S.validate({ secret: "" }); // empty string should trigger strict error
expectInvalid(r, "High PII field must not be empty: secret");
});
// 5) Custom field validator returns false
it("should fail when custom field validator returns false", () => {
const S = createSchema(
{
code: field.string().validator((v) => v === "ok"),
},
"CustomValidator",
{ version: "1.0.0", piiEnforcement: "strict" }
);
const result = S.validate({ code: "nope" });
expectInvalid(result, "Invalid value for field: code");
});
// 6) String type mismatch
it("should fail when string field is not a string", () => {
const result = StringEnum.validate({ k: 1 as unknown as string });
expectInvalid(result, "Field k must be string");
});
// 7) String enum violation
it("should fail when string field violates enum", () => {
const result = StringEnum.validate({ k: "Z" });
expectInvalid(result, "Field k must be one of: A, B, C");
});
// 8) Number type mismatch
it("should fail when number field is not a number", () => {
const result = NumberField.validate({ n: "1" as unknown as number });
expectInvalid(result, "Field n must be number");
});
// 9) Boolean type mismatch
it("should fail when boolean field is not a boolean", () => {
const result = BooleanField.validate({ b: "true" as unknown as boolean });
expectInvalid(result, "Field b must be boolean");
});
// 10) Object not an object
it("should fail when object field is not an object", () => {
const result = SimpleObject.validate({ o: 1 as unknown as object });
expectInvalid(result, "Field o must be object");
});
// 11) Required child missing
it("should fail when required child field on object is missing", () => {
const result = SimpleObject.validate({
o: {
/* a missing */
},
});
expectInvalid(result, "Missing required field: o.a");
});
// 12) Child validator returns false
it("should fail when child field validator on object returns false (using inline validator)", () => {
const S = createSchema(
{
o: field.object({
a: field.string().validator(() => false),
}),
},
"ChildValidator",
{ version: "1.0.0", piiEnforcement: "strict" }
);
const result = S.validate({ o: { a: "x" } });
expectInvalid(result, "Invalid value for field: o.a");
});
// 13) Required grandchild missing
it("should fail when required grandchild field on nested object is missing", () => {
const result = SimpleObject.validate({
o: {
a: "hi",
nested: {
/* f missing */
},
},
});
expectInvalid(result, "Missing required field: o.nested.f");
});
// 14) Grandchild validator false
it("should fail when grandchild field validator on nested object returns false", () => {
const S = createSchema(
{
o: field.object({
nested: field.object({
f: field.boolean().validator(() => false),
}),
a: field.string(),
}),
},
"GrandChildValidator",
{ version: "1.0.0", piiEnforcement: "strict" }
);
const result = S.validate({ o: { a: "x", nested: { f: true } } });
expectInvalid(result, "Invalid value for field: o.nested.f");
});
// 15) Array not an array
it("should fail when array field is not an array", () => {
const result = ArrayOfStrings.validate({
tags: "alpha" as unknown as string[],
});
expectInvalid(result, "Field tags must be an array");
});
// 16) Array of strings contains a non-string
it("should fail when array of strings contains a non-string", () => {
const result = ArrayOfStrings.validate({
tags: ["alpha", 1 as unknown as string],
});
expectInvalid(result, "Field tags must be string[]");
});
// 17) Array of strings invalid enum values
it("should fail when array of strings contains invalid enum values", () => {
const result = ArrayOfStrings.validate({ tags: ["alpha", "delta"] });
expectInvalid(result, "Field tags contains invalid enum values: delta");
});
// 18) Array of numbers contains a non-number
it("should fail when array of numbers contains a non-number", () => {
const result = ArrayOfNumbers.validate({
nums: [1, "2" as unknown as number],
});
expectInvalid(result, "Field nums must be number[]");
});
// 19) Array of booleans contains a non-boolean
it("should fail when array of booleans contains a non-boolean", () => {
const result = ArrayOfBooleans.validate({
flags: [true, "false" as unknown as boolean],
});
expectInvalid(result, "Field flags must be boolean[]");
});
// 20) Array of objects contains a non-object
it("should fail when array of objects contains a non-object", () => {
const result = ArrayOfObjects.validate({
items: [{ type: "a" }, 1 as unknown as object],
});
expectInvalid(result, "Field items must be object[]");
});
// 21) Required child missing in object item
it("should fail when required child field is missing in an object item", () => {
const result = ArrayOfObjects.validate({
items: [
{
/* type missing */
},
] as any,
});
expectInvalid(result, "Missing required field: items[0].type");
});
// 22) Child validator returns false in object item
it("should fail when child field validator returns false in an object item", () => {
const S = createSchema(
{
items: field.array(
field.object({
t: field.string().validator(() => false),
})
),
},
"ArrayChildValidator",
{ version: "1.0.0", piiEnforcement: "strict" }
);
const result = S.validate({ items: [{ t: "ok" }] });
expectInvalid(result, "Invalid value for field: items[0].t");
});
// 23) Array of refs – invalid ref object (depends on ref support)
it("should fail when array item is not a valid ref object (or wrong ref type)", () => {
const refItem = { type: "ref", refType: "asset" } as any;
const S = createSchema({ items: field.array(refItem) }, "ArrayRefInvalid", {
version: "1.0.0",
piiEnforcement: "strict",
});
// bad id type (number) and missing/incorrect fields should trigger the message
const r = S.validate({ items: [{ type: "asset", id: 123 }] });
expectInvalid(
r,
"Field items[0] must be a reference object with type: asset"
);
});
// 24) Array of refs – missing required child in ref shape
it("should fail when required child field is missing in ref shape", () => {
const refItem = {
type: "ref",
refType: "asset",
shape: { region: field.string() },
} as any;
const S = createSchema({ items: field.array(refItem) }, "ArrayRefMissing", {
version: "1.0.0",
piiEnforcement: "strict",
});
// Valid ref, but missing required shape field 'region'
const r = S.validate({ items: [{ type: "asset", id: "a1" }] });
expectInvalid(r, "Missing required field: items[0].region");
});
// 25) Array of refs – child validator false in ref shape
it("should fail when child field validator returns false in ref shape", () => {
const refItem = {
type: "ref",
refType: "asset",
shape: { code: field.string().validator(() => false) },
} as any;
const S = createSchema(
{ items: field.array(refItem) },
"ArrayRefChildValidator",
{ version: "1.0.0", piiEnforcement: "strict" }
);
const r = S.validate({ items: [{ type: "asset", id: "a1", code: "XYZ" }] });
expectInvalid(r, "Invalid value for field: items[0].code");
});
// 26) Unsupported array item type (builder guard)
it("should fail when array item type is unsupported", () => {
const weirdItem = { type: "date" } as any;
const S = createSchema({ xs: field.array(weirdItem) }, "ArrayUnsupported", {
version: "1.0.0",
piiEnforcement: "strict",
});
const r = S.validate({ xs: ["2025-01-01"] });
expectInvalid(r, "Field xs has unsupported array item type");
});
// 27) Ref field (single) – invalid shape
it("should fail when ref field is not a valid {type,id} object", () => {
const refDef = { type: "ref" } as any;
const S = createSchema({ r: refDef }, "SingleRef", {
version: "1.0.0",
piiEnforcement: "strict",
});
const r = S.validate({ r: { type: "asset", id: 42 } }); // id should be string
expectInvalid(r, "Field r must be { type: string; id: string }");
});
// 28) Unknown field type (defensive branch)
it("should fail when field type is unknown", () => {
const unknownDef = { type: "wat" } as any;
const S = createSchema({ a: unknownDef }, "UnknownType", {
version: "1.0.0",
piiEnforcement: "strict",
});
const r = S.validate({ a: 123 });
expectInvalid(r, "Unknown type for field a: wat");
});
// 29) Schema-level validator returns false
it("should fail when schema-level validation returns false", () => {
const S = createSchema(
{
a: field.number(),
},
"SchemaLevel",
{ version: "1.0.0", piiEnforcement: "strict" }
);
// monkey-patch a validator on the schema (if supported by createSchema options)
// Since createSchema doesn't expose a top-level validator in builder, we simulate via a field validator
const result = S.validate({ a: NaN as unknown as number });
// Number type is still number, but if your number validator treats NaN as invalid, else replace with a simple field-level validator example.
// To guarantee failure, define an explicit schema with a field-level validator:
const S2 = createSchema(
{
x: field.string().validator(() => false),
},
"SchemaLevel2",
{ version: "1.0.0", piiEnforcement: "strict" }
);
const r2 = S2.validate({ x: "anything" });
expectInvalid(r2, "Invalid value for field: x");
});
// Sanity: a fully valid complex object passes
it("should pass on a fully valid complex object", () => {
const result = ArrayOfObjects.validate({
items: [{ type: "a", value: 1, info: { ok: true } }, { type: "b" }],
});
expectValid(result);
});
// --- Positive paths to complement the negative coverage above ---
it("should pass when required enum field is provided with an allowed value", () => {
const result = StringEnum.validate({ k: "A" });
expectValid(result);
});
it("should pass when number field is a number", () => {
const result = NumberField.validate({ n: 42 });
expectValid(result);
});
it("should pass when boolean field is a boolean", () => {
const result = BooleanField.validate({ b: false });
expectValid(result);
});
it("should pass when object has required child and omits optionals", () => {
const result = SimpleObject.validate({ o: { a: "hello" } });
expectValid(result);
});
it("should pass when object provides valid nested optional object", () => {
const result = SimpleObject.validate({
o: { a: "hi", b: 10, nested: { f: true, tag: "x" } },
});
expectValid(result);
});
it("should pass when array of strings contains only allowed enum values", () => {
const result = ArrayOfStrings.validate({ tags: ["alpha", "beta"] });
expectValid(result);
});
it("should pass when array of numbers contains only numbers", () => {
const result = ArrayOfNumbers.validate({ nums: [0, 1, 2, 3.14] });
expectValid(result);
});
it("should pass when array of booleans contains only booleans", () => {
const result = ArrayOfBooleans.validate({ flags: [true, false, true] });
expectValid(result);
});
it("should pass when array of objects contains minimal valid items (optionals omitted)", () => {
const result = ArrayOfObjects.validate({
items: [{ type: "a" }, { type: "b" }],
});
expectValid(result);
});
it("should pass when immutable field remains unchanged compared to existing value", () => {
const fixed = field.string().immutable() as any;
const S = createSchema({ fixed }, "ImmutableOK", {
version: "1.0.0",
piiEnforcement: "strict",
});
const existing = { fixed: "A" };
const r = S.validate({ fixed: "A" }, existing);
expectValid(r);
});
it("should pass when high PII required field is non-empty under strict enforcement", () => {
const secret = field.string().PID({
classification: "high",
action: "encrypt",
logHandling: "redact",
purpose: "pii security test",
}) as any;
const S = createSchema({ secret }, "PII_OK", {
version: "1.0.0",
piiEnforcement: "strict",
});
const r = S.validate({ secret: "non-empty" });
expectValid(r);
});
it("should pass when custom field validator returns true", () => {
const S = createSchema(
{ ok: field.string().validator((v) => v === "ok") },
"CustomValidatorOK",
{ version: "1.0.0", piiEnforcement: "strict" }
);
const r = S.validate({ ok: "ok" });
expectValid(r);
});
});