import { describe, expect, it } from "vitest"; import { SCHEMA_LIMITS, compareSchemas, generateSample, inspectWorkspace, parseSchemaDocument, validateJsonInstance, validateJsonInstanceSafe, } from "../../src/schema/model"; const entry = { name: "person.schema.json", language: "json-schema" as const, source: JSON.stringify({ $schema: "https://json-schema.org/draft/2020-12/schema", type: "object", required: ["name", "address"], properties: { name: { type: "string", minLength: 1 }, address: { $ref: "defs/address.json#/$defs/address" }, }, additionalProperties: false, }), }; const support = { name: "defs/address.json", language: "json-schema" as const, source: JSON.stringify({ $schema: "https://json-schema.org/draft/2020-12/schema", $defs: { address: { type: "object", required: ["city"], properties: { city: { type: "string" } }, }, }, }), }; function nestedJsonSchema(levels: number): object { let schema: object = { const: "leaf" }; for (let level = 1; level < levels; level += 1) schema = { type: "object", required: ["child"], properties: { child: schema }, }; return schema; } function jsonValueDepth(value: unknown): number { if (!value || typeof value !== "object") return 1; const children = Object.values(value); return 1 + (children.length ? Math.max(...children.map(jsonValueDepth)) : 0); } function xsdChain(levels: number): string { const types = Array.from({ length: levels - 1 }, (_, index) => { const number = index + 1; const childType = number === levels - 1 ? "xs:string" : `Type${number + 1}`; return ``; }).join(""); return `${types}`; } function relaxNgChain(levels: number): string { let pattern = ""; for (let level = levels; level >= 1; level -= 1) pattern = `${pattern}`; return `${pattern}`; } function hasUnpairedSurrogate(value: string): boolean { for (let index = 0; index < value.length; index += 1) { const code = value.charCodeAt(index); if (code >= 0xd800 && code <= 0xdbff) { const next = value.charCodeAt(index + 1); if (next < 0xdc00 || next > 0xdfff) return true; index += 1; } else if (code >= 0xdc00 && code <= 0xdfff) return true; } return false; } const amplifiedChoiceSupport = { name: "choices.json", source: JSON.stringify({ $defs: { impossible: { anyOf: Array.from({ length: 600 }, () => false), }, amplified: { anyOf: Array.from({ length: 100 }, () => ({ $ref: "#/$defs/impossible", })), }, }, }), }; function captureError(run: () => unknown): Error { try { run(); } catch (error) { if (error instanceof Error) return error; throw error; } throw new Error("Expected operation to throw."); } describe("schema workspace", () => { it("validates pattern and patternProperties through a bounded batch runner", async () => { const workspace = inspectWorkspace([ { name: "pattern.schema.json", source: JSON.stringify({ type: "object", propertyNames: { pattern: "^[a-z-]+$" }, properties: { label: { type: "string", pattern: "^ok(?:-|$)" }, }, patternProperties: { "^x-": { type: "integer", minimum: 0 }, }, additionalProperties: false, }), }, ]); const runner = async ( patterns: readonly string[], candidates: readonly string[], ) => new Map( patterns.map((source) => { const expression = new RegExp(source, "u"); return [ source, new Set( candidates.filter((candidate) => expression.test(candidate)), ), ] as const; }), ); expect( ( await validateJsonInstanceSafe( workspace, JSON.stringify({ label: "ok-value", "x-count": 2 }), workspace.entry, runner, ) ).valid, ).toBe(true); const invalid = await validateJsonInstanceSafe( workspace, JSON.stringify({ label: "bad", "x-count": "two", Other: true }), workspace.entry, runner, ); expect(invalid.valid).toBe(false); expect(invalid.diagnostics.map((item) => item.message).join("\n")).toMatch( /pattern.*does not match|type.*integer|additionalProperties|false schema/iu, ); }); it("refuses synchronous regex validation instead of executing on the caller thread", () => { const workspace = inspectWorkspace([ { name: "pattern.schema.json", source: JSON.stringify({ type: "string", pattern: "^(a+)+$" }), }, ]); const result = validateJsonInstance(workspace, JSON.stringify("aaaa")); expect(result.valid).toBe(false); expect(result.diagnostics[0]?.message).toMatch( /bounded asynchronous validation|isolated worker/iu, ); }); it("resolves local references, generates a sample, and validates instances", () => { const workspace = inspectWorkspace([entry, support], entry.name); expect(workspace.references).toEqual([ expect.objectContaining({ target: support.name, status: "resolved" }), ]); expect(JSON.parse(generateSample(workspace).output)).toEqual({ name: "string", address: { city: "string" }, }); expect( validateJsonInstance( workspace, JSON.stringify({ name: "Ada", address: { city: "London" } }), ).valid, ).toBe(true); const invalid = validateJsonInstance( workspace, JSON.stringify({ name: "Ada" }), ); expect(invalid.valid).toBe(false); expect(invalid.diagnostics[0]?.message).toContain("required"); }); it("bounds repeated large literal samples reached through local references", () => { const properties = Object.fromEntries( Array.from({ length: 100 }, (_, index) => [ `value${index}`, { $ref: "literal.json#/$defs/large" }, ]), ); const workspace = inspectWorkspace([ { name: "root.json", source: JSON.stringify({ type: "object", required: Object.keys(properties), properties, }), }, { name: "literal.json", source: JSON.stringify({ $defs: { large: { default: "x".repeat(100_000) } }, }), }, ]); const sample = generateSample(workspace); const values = Object.values(JSON.parse(sample.output) as object); expect(sample.output.length).toBeLessThanOrEqual( SCHEMA_LIMITS.sampleOutputChars, ); expect(values).toHaveLength(100); expect( values.every( (value) => String(value).length <= SCHEMA_LIMITS.sampleValueChars, ), ).toBe(true); expect(sample.notices.join("\n")).toMatch(/truncated.*safety bound/iu); }); it("applies the exact JSON node budget to copied literal collections", () => { const workspace = inspectWorkspace([ { name: "literal-array.json", source: JSON.stringify({ default: Array.from({ length: 100 }, () => Array.from({ length: 20 }, () => null), ), }), }, ]); const sample = generateSample(workspace); const value = JSON.parse(sample.output) as unknown; const countNodes = (item: unknown): number => item && typeof item === "object" ? 1 + Object.values(item).reduce((sum, child) => sum + countNodes(child), 0) : 1; expect(countNodes(value)).toBe(SCHEMA_LIMITS.sampleNodes); expect(sample.notices.join("\n")).toMatch(/node.*safety bound/iu); }); it("monotonically bounds amplified JSON choice attempts through reused refs", () => { const properties = Object.fromEntries( Array.from({ length: 100 }, (_, index) => [ `value${index}`, { $ref: "choices.json#/$defs/value" }, ]), ); const workspace = inspectWorkspace([ { name: "root.json", source: JSON.stringify({ type: "object", required: Object.keys(properties), properties, }), }, { name: "choices.json", source: JSON.stringify({ $defs: { value: { anyOf: [ ...Array.from({ length: 600 }, () => false), { const: "viable" }, ], }, }, }), }, ]); const started = performance.now(); const error = captureError(() => generateSample(workspace)); const elapsed = performance.now() - started; expect(error.message).toMatch(/sample budget was exhausted/iu); expect(error.message).not.toMatch(/first viable/iu); expect(elapsed).toBeLessThan(2_000); }); it("never treats exhausted all-impossible anyOf or oneOf branches as viable", () => { for (const keyword of ["anyOf", "oneOf"] as const) { const workspace = inspectWorkspace([ { name: `${keyword}.json`, source: JSON.stringify({ [keyword]: Array.from({ length: 100 }, () => ({ $ref: "choices.json#/$defs/impossible", })), }), }, amplifiedChoiceSupport, ]); const error = captureError(() => generateSample(workspace)); expect(error.message).toMatch(/sample budget was exhausted/iu); expect(error.message).not.toMatch(/first viable/iu); } }); it("refuses a partial mandatory allOf when prior work exhausts the budget", () => { const properties = Object.fromEntries( Array.from({ length: 100 }, (_, index) => [ `value${index}`, { $ref: "choices.json#/$defs/amplified" }, ]), ); const workspace = inspectWorkspace([ { name: "all-of.json", source: JSON.stringify({ allOf: [ { type: "object", required: Object.keys(properties), properties, }, false, ], }), }, amplifiedChoiceSupport, ]); expect(() => generateSample(workspace)).toThrow( /sample budget was exhausted/iu, ); }); it("propagates optional-property exhaustion before later required constraints", () => { for (const requiredSchema of [{ const: "required" }, false] as const) { const workspace = inspectWorkspace([ { name: "optional-before-required.json", source: JSON.stringify({ type: "object", required: ["required"], properties: { optional: { $ref: "choices.json#/$defs/amplified" }, required: requiredSchema, }, }), }, amplifiedChoiceSupport, ]); expect(() => generateSample(workspace)).toThrow( /sample budget was exhausted/iu, ); } }); it("propagates work exhaustion from a required array item", () => { const workspace = inspectWorkspace([ { name: "required-item.json", source: JSON.stringify({ type: "array", minItems: 1, items: { $ref: "choices.json#/$defs/amplified" }, }), }, amplifiedChoiceSupport, ]); expect(() => generateSample(workspace)).toThrow( /sample budget was exhausted/iu, ); }); it("charges cached wide JSON property visits on every reused schema", () => { const wideProperties = Object.fromEntries( Array.from({ length: 2_000 }, (_, index) => [ `optional${index}`, { type: "string" }, ]), ); const workspace = inspectWorkspace([ { name: "wide-references.json", source: JSON.stringify({ allOf: Array.from({ length: 2_000 }, () => ({ $ref: "wide.json#/$defs/wide", })), }), }, { name: "wide.json", source: JSON.stringify({ $defs: { wide: { type: "object", properties: wideProperties }, }, }), }, ]); const started = performance.now(); const error = captureError(() => generateSample(workspace)); expect(error.message).toMatch(/sample budget was exhausted/iu); expect(performance.now() - started).toBeLessThan(2_000); }); it("admits exactly 20 generated JSON levels and omits level 21", () => { const exact = inspectWorkspace([ { name: "depth-20.json", source: JSON.stringify(nestedJsonSchema(20)), }, ]); const exactSample = generateSample(exact); expect(jsonValueDepth(JSON.parse(exactSample.output))).toBe(20); expect(exactSample.notices.join("\n")).not.toMatch(/level safety bound/iu); const excessive = inspectWorkspace([ { name: "depth-21.json", source: JSON.stringify(nestedJsonSchema(21)), }, ]); const boundedSample = generateSample(excessive); expect(jsonValueDepth(JSON.parse(boundedSample.output))).toBe(20); expect(boundedSample.notices.join("\n")).toMatch(/level safety bound/iu); }); it("truncates JSON literals without splitting surrogate pairs", () => { const value = `a${"😀".repeat(600)}`; const workspace = inspectWorkspace([ { name: "unicode.json", source: JSON.stringify({ default: value }), }, ]); const generated = JSON.parse(generateSample(workspace).output) as string; expect(generated.length).toBeLessThanOrEqual( SCHEMA_LIMITS.sampleValueChars, ); expect(hasUnpairedSurrogate(generated)).toBe(false); }); it("refuses to claim a sample for the always-invalid false schema", () => { const workspace = inspectWorkspace([ { name: "impossible.json", source: "false" }, ]); expect(() => generateSample(workspace)).toThrow(/no valid sample/iu); }); it("refuses false schemas reached through references and required combiners", () => { const referenced = inspectWorkspace([ { name: "root.json", source: JSON.stringify({ $ref: "defs.json" }) }, { name: "defs.json", source: "false" }, ]); expect(() => generateSample(referenced)).toThrow(/no valid sample/iu); const combined = inspectWorkspace([ { name: "combined.json", source: JSON.stringify({ allOf: [{ type: "object" }, false] }), }, ]); expect(() => generateSample(combined)).toThrow(/no valid sample/iu); }); it("skips impossible alternatives when a viable anyOf sample exists", () => { const workspace = inspectWorkspace([ { name: "choice.json", source: JSON.stringify({ anyOf: [false, { type: "string", const: "viable" }], }), }, ]); expect(JSON.parse(generateSample(workspace).output)).toBe("viable"); }); it("blocks remote and missing references without attempting resolution", () => { const workspace = inspectWorkspace([ { name: "unsafe.json", source: JSON.stringify({ $ref: "https://example.test/schema.json" }), }, { name: "missing.json", source: JSON.stringify({ $ref: "other.json" }) }, ]); expect(workspace.references.map((item) => item.status)).toEqual([ "blocked", "unresolved", ]); expect(workspace.diagnostics).toEqual( expect.arrayContaining([ expect.objectContaining({ level: "error", message: expect.stringContaining("blocked"), }), expect.objectContaining({ level: "error", message: expect.stringContaining("not supplied"), }), ]), ); }); it("routes executable regex keywords away from the synchronous validator", () => { const workspace = inspectWorkspace([ { name: "pattern.json", source: JSON.stringify({ type: "string", pattern: "^(a+)+$" }), }, ]); const result = validateJsonInstance(workspace, JSON.stringify("aaaa")); expect(result.valid).toBe(false); expect(result.diagnostics[0]?.message).toMatch( /regular expressions.*bounded asynchronous validation/iu, ); }); it("also refuses regex and dynamic-reference keywords in supplied reference documents", () => { const regexWorkspace = inspectWorkspace([ { name: "root.json", source: JSON.stringify({ $ref: "defs.json" }) }, { name: "defs.json", source: JSON.stringify({ type: "string", pattern: "^(a+)+$" }), }, ]); expect( validateJsonInstance(regexWorkspace, JSON.stringify("aaaa")).valid, ).toBe(false); const dynamicWorkspace = inspectWorkspace([ { name: "dynamic.json", source: JSON.stringify({ $dynamicRef: "#node" }), }, ]); expect( validateJsonInstance(dynamicWorkspace, JSON.stringify({})).diagnostics[0] ?.message, ).toContain("$dynamicRef"); }); it("validates the documented 2020-12 object, array, dependency, and combiner subset", () => { const workspace = inspectWorkspace([ { name: "bounded.json", source: JSON.stringify({ $schema: "https://json-schema.org/draft/2020-12/schema", type: "object", required: ["kind", "values"], properties: { kind: { enum: ["measurement"] }, note: { type: "string", minLength: 2, maxLength: 8 }, values: { type: "array", prefixItems: [{ type: "integer" }], items: { type: "number", minimum: 0 }, contains: { const: 2 }, minContains: 1, uniqueItems: true, }, }, dependentRequired: { note: ["kind"] }, allOf: [{ minProperties: 2 }], additionalProperties: false, }), }, ]); expect( validateJsonInstance( workspace, JSON.stringify({ kind: "measurement", note: "ok", values: [1, 2] }), ).valid, ).toBe(true); const invalid = validateJsonInstance( workspace, JSON.stringify({ kind: "measurement", values: [1, 1], extra: true }), ); expect(invalid.valid).toBe(false); expect(invalid.diagnostics.map((item) => item.message).join("\n")).toMatch( /uniqueItems|contains|false schema/u, ); }); it("supports Draft 6 local files even when root identifiers are remote-looking", () => { const workspace = inspectWorkspace([ { name: "root.json", source: JSON.stringify({ $schema: "http://json-schema.org/draft-06/schema#", $id: "https://schemas.example.test/root.json", $ref: "defs.json#/$defs/value", }), }, { name: "defs.json", source: JSON.stringify({ $schema: "http://json-schema.org/draft-06/schema#", $defs: { value: { type: "integer", minimum: 1 } }, }), }, ]); expect(validateJsonInstance(workspace, "2").valid).toBe(true); expect(validateJsonInstance(workspace, "0").valid).toBe(false); }); it("rejects active XML constructs before structural inspection", () => { expect(() => parseSchemaDocument({ name: "unsafe.xsd", source: `]>`, }), ).toThrow(/DTD and entity/u); }); }); describe("schema languages", () => { it("inventories XSD and generates a clearly heuristic XML instance", () => { const workspace = inspectWorkspace([ { name: "person.xsd", source: ``, }, ]); expect(workspace.documents.get("person.xsd")?.language).toBe("xsd"); expect( workspace.diagnostics.some((item) => item.message.includes("not provided"), ), ).toBe(true); const sample = generateSample(workspace); expect(sample.output).toContain(""); expect(sample.output).toContain("string"); expect(sample.notices[0]).toContain("Heuristic XSD"); }); it("applies the aggregate text budget to repeated XSD type literals", () => { const branches = Array.from( { length: 50 }, (_, index) => ``, ).join(""); const leaves = Array.from( { length: 50 }, (_, index) => ``, ).join(""); const workspace = inspectWorkspace([ { name: "amplified.xsd", source: `${leaves}${branches}`, }, ]); const sample = generateSample(workspace); const parsed = new DOMParser().parseFromString(sample.output, "text/xml"); expect(parsed.querySelector("parsererror")).toBeNull(); expect(parsed.documentElement.textContent?.length ?? 0).toBeLessThanOrEqual( SCHEMA_LIMITS.sampleTextChars, ); expect(parsed.querySelectorAll("*").length).toBeLessThanOrEqual( SCHEMA_LIMITS.sampleNodes, ); expect(sample.output.length).toBeLessThanOrEqual( SCHEMA_LIMITS.sampleOutputChars, ); expect(sample.notices.join("\n")).toMatch(/text safety bound/iu); }); it("linearly inspects and preindexes a large reused XSD type", () => { const leaves = Array.from( { length: 20_000 }, () => ``, ).join(""); const branches = Array.from( { length: 50 }, (_, index) => ``, ).join(""); const source = `${leaves}${branches}`; const inspectStarted = performance.now(); const workspace = inspectWorkspace([{ name: "large-flat.xsd", source }]); const inspectElapsed = performance.now() - inspectStarted; const generateStarted = performance.now(); const sample = generateSample(workspace); const generateElapsed = performance.now() - generateStarted; const parsed = new DOMParser().parseFromString(sample.output, "text/xml"); expect(parsed.querySelector("parsererror")).toBeNull(); expect(parsed.querySelectorAll("*").length).toBeLessThanOrEqual( SCHEMA_LIMITS.sampleNodes, ); expect(inspectElapsed).toBeLessThan(2_000); expect(generateElapsed).toBeLessThan(2_000); }); it("linearly inspects a wide XSD schema container", () => { const annotations = Array.from( { length: 20_000 }, () => "", ).join(""); const source = `${annotations}`; const started = performance.now(); const workspace = inspectWorkspace([{ name: "wide-root.xsd", source }]); const elapsed = performance.now() - started; expect(workspace.documents.get(workspace.entry)?.language).toBe("xsd"); expect(elapsed).toBeLessThan(2_000); }); it("admits exactly 20 generated XSD levels and omits level 21", () => { for (const [levels, noticeExpected] of [ [20, false], [21, true], ] as const) { const workspace = inspectWorkspace([ { name: `depth-${levels}.xsd`, source: xsdChain(levels) }, ]); const sample = generateSample(workspace); const parsed = new DOMParser().parseFromString(sample.output, "text/xml"); expect(parsed.querySelector("parsererror")).toBeNull(); expect(parsed.querySelectorAll("*")).toHaveLength(20); expect(/level safety bound/iu.test(sample.notices.join("\n"))).toBe( noticeExpected, ); } }); it("keeps truncated XSD astral literals well formed", () => { const value = `a${"😀".repeat(600)}`; const workspace = inspectWorkspace([ { name: "unicode.xsd", source: ``, }, ]); const sample = generateSample(workspace); const parsed = new DOMParser().parseFromString(sample.output, "text/xml"); expect(parsed.querySelector("parsererror")).toBeNull(); expect(hasUnpairedSurrogate(parsed.documentElement.textContent ?? "")).toBe( false, ); }); it("omits duplicate and fallback-colliding XSD attributes", () => { const workspace = inspectWorkspace([ { name: "attributes.xsd", source: ``, }, ]); const sample = generateSample(workspace); const parsed = new DOMParser().parseFromString(sample.output, "text/xml"); expect(parsed.querySelector("parsererror")).toBeNull(); expect(parsed.documentElement.attributes).toHaveLength(2); expect(parsed.documentElement.hasAttribute("same")).toBe(true); expect(parsed.documentElement.hasAttribute("attribute")).toBe(true); expect(sample.notices.join("\n")).toMatch(/duplicate.*attribute/iu); }); it("caches a wide inline XSD type across repeated rendering", () => { const annotations = Array.from( { length: 20_000 }, () => "", ).join(""); const branches = Array.from( { length: 50 }, (_, index) => ``, ).join(""); const workspace = inspectWorkspace([ { name: "wide-inline.xsd", source: `${annotations}${branches}`, }, ]); const started = performance.now(); const sample = generateSample(workspace); const elapsed = performance.now() - started; const parsed = new DOMParser().parseFromString(sample.output, "text/xml"); expect(parsed.querySelector("parsererror")).toBeNull(); expect(elapsed).toBeLessThan(2_000); }); it("inventories Relax NG and Schematron without executing expressions", () => { const rng = inspectWorkspace([ { name: "note.rng", source: ``, }, ]); expect(generateSample(rng).output).toContain( "string", ); const sch = inspectWorkspace([ { name: "rules.sch", source: `A name is required.`, }, ]); expect(sch.documents.get("rules.sch")?.language).toBe("schematron"); expect( sch.diagnostics.some( (item) => item.message.includes("never executed") || item.message.includes("not executed"), ), ).toBe(true); }); it("retains a direct-root Relax NG element in the generated sample", () => { const workspace = inspectWorkspace([ { name: "direct.rng", source: ``, }, ]); const sample = generateSample(workspace); const parsed = new DOMParser().parseFromString(sample.output, "text/xml"); expect(parsed.querySelector("parsererror")).toBeNull(); expect(parsed.documentElement.localName).toBe("root"); expect(parsed.documentElement.textContent).toBe("string"); }); it("never emits more than the exact Relax NG sample node budget", () => { const repeatedElements = Array.from( { length: SCHEMA_LIMITS.sampleNodes }, () => ``, ).join(""); const workspace = inspectWorkspace([ { name: "bounded.rng", source: `${repeatedElements}`, }, ]); const sample = generateSample(workspace); const parsed = new DOMParser().parseFromString(sample.output, "text/xml"); expect(parsed.querySelector("parsererror")).toBeNull(); expect(parsed.querySelectorAll("*")).toHaveLength( SCHEMA_LIMITS.sampleNodes, ); }); it("applies the aggregate text budget through repeated Relax NG refs", () => { const references = Array.from( { length: SCHEMA_LIMITS.sampleNodes }, () => ``, ).join(""); const workspace = inspectWorkspace([ { name: "text-budget.rng", source: `${"x".repeat(SCHEMA_LIMITS.sampleValueChars)}${references}`, }, ]); const sample = generateSample(workspace); const parsed = new DOMParser().parseFromString(sample.output, "text/xml"); expect(parsed.querySelector("parsererror")).toBeNull(); expect(parsed.documentElement.textContent?.length ?? 0).toBeLessThanOrEqual( SCHEMA_LIMITS.sampleTextChars, ); expect(sample.notices.join("\n")).toMatch(/text safety bound/iu); }); it("bounds amplified Relax NG pattern visits independently of output", () => { const width = 300; const emptyPatterns = Array.from({ length: width }, () => "").join( "", ); const references = Array.from( { length: width }, () => ``, ).join(""); const workspace = inspectWorkspace([ { name: "amplified-work.rng", source: `${emptyPatterns}${references}`, }, ]); const started = performance.now(); const error = captureError(() => generateSample(workspace)); const elapsed = performance.now() - started; expect(error.message).toMatch(/sample budget was exhausted/iu); expect(elapsed).toBeLessThan(2_000); }); it("caches wide reused Relax NG pattern containers", () => { const ignored = Array.from({ length: 19_000 }, () => "").join(""); const references = Array.from( { length: 1_000 }, () => '', ).join(""); const workspace = inspectWorkspace([ { name: "wide-reused.rng", source: `${ignored}${references}`, }, ]); const started = performance.now(); const sample = generateSample(workspace); const elapsed = performance.now() - started; const parsed = new DOMParser().parseFromString(sample.output, "text/xml"); expect(parsed.querySelector("parsererror")).toBeNull(); expect(parsed.documentElement.localName).toBe("root"); expect(elapsed).toBeLessThan(2_000); }); it("counts generated Relax NG levels rather than wrapper patterns", () => { for (const [levels, noticeExpected] of [ [20, false], [21, true], ] as const) { const workspace = inspectWorkspace([ { name: `depth-${levels}.rng`, source: relaxNgChain(levels) }, ]); const sample = generateSample(workspace); const parsed = new DOMParser().parseFromString(sample.output, "text/xml"); expect(parsed.querySelector("parsererror")).toBeNull(); expect(parsed.querySelectorAll("*")).toHaveLength(20); expect(/level safety bound/iu.test(sample.notices.join("\n"))).toBe( noticeExpected, ); } }); it("keeps truncated Relax NG astral literals well formed", () => { const value = `a${"😀".repeat(600)}`; const workspace = inspectWorkspace([ { name: "unicode.rng", source: `${value}`, }, ]); const sample = generateSample(workspace); const parsed = new DOMParser().parseFromString(sample.output, "text/xml"); expect(parsed.querySelector("parsererror")).toBeNull(); expect(hasUnpairedSurrogate(parsed.documentElement.textContent ?? "")).toBe( false, ); }); it("omits duplicate and fallback-colliding Relax NG attributes", () => { const workspace = inspectWorkspace([ { name: "attributes.rng", source: ``, }, ]); const sample = generateSample(workspace); const parsed = new DOMParser().parseFromString(sample.output, "text/xml"); expect(parsed.querySelector("parsererror")).toBeNull(); expect(parsed.documentElement.attributes).toHaveLength(2); expect(parsed.documentElement.hasAttribute("same")).toBe(true); expect(parsed.documentElement.hasAttribute("attribute")).toBe(true); expect(sample.notices.join("\n")).toMatch(/duplicate.*attribute/iu); }); it("parses OpenAPI YAML, inventories operations, and derives component samples", () => { const workspace = inspectWorkspace([ { name: "openapi.yaml", source: `openapi: 3.1.0 info: title: Local API version: 1.0.0 paths: /people: get: responses: "200": { description: ok } components: schemas: Person: type: object required: [name] properties: name: { type: string } `, }, ]); const document = workspace.documents.get("openapi.yaml")!; expect(document.language).toBe("openapi"); expect(document.summary).toContainEqual({ label: "Operations", value: "1", }); expect(JSON.parse(generateSample(workspace).output)).toEqual({ name: "string", }); }); it("refuses an impossible OpenAPI component sample", () => { const workspace = inspectWorkspace([ { name: "impossible-openapi.json", source: JSON.stringify({ openapi: "3.1.0", info: { title: "Impossible", version: "1" }, paths: {}, components: { schemas: { Impossible: false } }, }), }, ]); expect(() => generateSample(workspace)).toThrow(/no valid sample/iu); }); it("applies the same 20-level depth ceiling to OpenAPI samples", () => { const workspace = inspectWorkspace([ { name: "deep-openapi.json", source: JSON.stringify({ openapi: "3.1.0", info: { title: "Deep", version: "1" }, paths: {}, components: { schemas: { Deep: nestedJsonSchema(21) } }, }), }, ]); const sample = generateSample(workspace); expect(jsonValueDepth(JSON.parse(sample.output))).toBe(20); expect(sample.notices.join("\n")).toMatch(/level safety bound/iu); }); }); describe("conservative comparison", () => { it("signals newly required fields and removed enum values", () => { const changes = compareSchemas( { name: "before.json", source: JSON.stringify({ type: "object", properties: { state: { enum: ["on", "off"] } }, }), }, { name: "after.json", source: JSON.stringify({ type: "object", required: ["state"], properties: { state: { enum: ["on"] } }, }), }, ); expect(changes).toEqual( expect.arrayContaining([ expect.objectContaining({ level: "incompatible", message: expect.stringContaining("required"), }), expect.objectContaining({ level: "incompatible", path: "/properties/state/enum", message: expect.stringContaining("removed"), }), ]), ); }); it("signals removed OpenAPI operations", () => { const base = { openapi: "3.1.0", info: { title: "Example", version: "1" }, }; const changes = compareSchemas( { name: "before.json", source: JSON.stringify({ ...base, paths: { "/items": { get: { responses: { "200": { description: "ok" } } } }, }, }), }, { name: "after.json", source: JSON.stringify({ ...base, paths: {} }) }, ); expect(changes).toContainEqual( expect.objectContaining({ level: "incompatible", path: "GET /items" }), ); }); });