json-schema-library
Version:
Customizable and hackable json-validator and json-schema utilities for traversal, data generation and validation
380 lines (351 loc) • 15.5 kB
text/typescript
import { strict as assert } from "assert";
import { compileSchema } from "../compileSchema";
import { isJsonError } from "../types";
import { reduceOneOfDeclarator, reduceOneOfFuzzy } from "./oneOf";
import settings from "../settings";
const DECLARATOR_ONEOF = settings.DECLARATOR_ONEOF;
describe("keyword : oneof : validate", () => {
it("should validate matching oneOf", () => {
const { errors } = compileSchema({
oneOf: [
{ type: "object", properties: { value: { type: "string" } } },
{ type: "object", properties: { value: { type: "integer" } } }
]
}).validate({ value: "a string" });
assert.equal(errors.length, 0);
});
it("should return error for non-matching oneOf", () => {
const { errors } = compileSchema({
type: "object",
oneOf: [
{ type: "object", properties: { value: { type: "string" } } },
{ type: "object", properties: { value: { type: "integer" } } }
]
}).validate({ value: [] });
assert.equal(errors.length, 1);
assert.equal(errors[0].code, "one-of-error");
});
});
describe("keyword : oneOf : reduce", () => {
it("should resolve matching value schema", () => {
const { node } = compileSchema({
oneOf: [
{ type: "string", title: "A String" },
{ type: "number", title: "A Number" }
]
}).reduceNode(111);
assert.deepEqual(node.schema, { type: "number", title: "A Number" });
assert.equal(node.oneOfIndex, 1, "should have exposed correct resolved oneOfIndex");
});
it("should return error if no matching schema could be found", () => {
const { node, error } = compileSchema({
oneOf: [
{ type: "string", title: "A String" },
{ type: "number", title: "A Number" }
]
}).reduceNode(true);
assert(isJsonError(error));
assert.equal(node, undefined);
});
it("should return error if multiple schema match", () => {
const { node, error } = compileSchema({
oneOf: [
{ type: "string", minLength: 1 },
{ type: "string", maxLength: 3 }
]
}).reduceNode("12");
assert(isJsonError(error));
assert.equal(node, undefined);
});
it("should reduce nested oneOf objects using ref", () => {
const { node } = compileSchema({
$defs: { withData: { oneOf: [{ required: ["b"], properties: { b: { type: "number" } } }] } },
oneOf: [{ required: ["a"], properties: { a: { type: "string" } } }, { $ref: "#/$defs/withData" }]
}).reduceNode({ b: 111 });
assert.deepEqual(node.schema, { required: ["b"], properties: { b: { type: "number" } } });
// @note that we override nested oneOfIndex
assert.equal(node.oneOfIndex, 1, "should have exposed correct resolved oneOfIndex");
});
it("should reduce nested oneOf boolean schema using ref", () => {
const { node } = compileSchema({
$defs: { withData: { oneOf: [{ required: ["b"], properties: { b: true } }] } },
oneOf: [{ required: ["a"], properties: { a: false } }, { $ref: "#/$defs/withData" }]
}).reduceNode({ b: 111 });
assert.deepEqual(node.schema, { required: ["b"], properties: { b: true } });
assert.equal(node.oneOfIndex, 1, "should have exposed correct resolved oneOfIndex");
});
it("should resolve matching object schema", () => {
const { node } = compileSchema({
oneOf: [
{
type: "object",
properties: { title: { type: "string" } }
},
{
type: "object",
properties: { title: { type: "number" } }
}
]
}).reduceNode({ title: 4 });
assert.deepEqual(node.schema, { type: "object", properties: { title: { type: "number" } } });
assert.equal(node.oneOfIndex, 1, "should have exposed correct resolved oneOfIndex");
});
it("should return matching oneOf, for objects missing properties", () => {
const { node } = compileSchema({
oneOf: [
{
type: "object",
additionalProperties: { type: "string" }
},
{
type: "object",
additionalProperties: { type: "number" }
}
]
}).reduceNode({ title: 4, test: 2 });
assert.deepEqual(node.schema, { type: "object", additionalProperties: { type: "number" } });
assert.equal(node.oneOfIndex, 1, "should have exposed correct resolved oneOfIndex");
});
});
describe("keyword : oneof-fuzzy : reduce", () => {
it("should return schema with matching type", () => {
const node = compileSchema({
oneOf: [{ type: "string" }, { type: "number" }, { type: "object" }]
});
const res = reduceOneOfFuzzy({ node, data: 4, pointer: "#", path: [] });
assert.deepEqual(res.schema, { type: "number" });
assert.equal(res.oneOfIndex, 1, "should have exposed correct resolved oneOfIndex");
});
it("should return schema with matching pattern", () => {
const node = compileSchema({
oneOf: [
{ type: "string", pattern: "obelix" },
{ type: "string", pattern: "asterix" }
]
});
const res = reduceOneOfFuzzy({ node, data: "anasterixcame", pointer: "#", path: [] });
assert.deepEqual(res.schema, { type: "string", pattern: "asterix" });
assert.equal(res.oneOfIndex, 1, "should have exposed correct resolved oneOfIndex");
});
it("should resolve $ref before schema", () => {
const node = compileSchema({
definitions: {
a: { type: "string", pattern: "obelix" },
b: { type: "string", pattern: "asterix" }
},
oneOf: [{ $ref: "#/definitions/a" }, { $ref: "#/definitions/b" }]
});
const res = reduceOneOfFuzzy({ node, data: "anasterixcame", pointer: "#", path: [] });
assert.deepEqual(res.schema, { type: "string", pattern: "asterix" });
assert.equal(res.oneOfIndex, 1, "should have exposed correct resolved oneOfIndex");
});
describe("object", () => {
it("should return schema with matching properties", () => {
const node = compileSchema({
oneOf: [
{ type: "object", properties: { title: { type: "string" } } },
{ type: "object", properties: { description: { type: "string" } } },
{ type: "object", properties: { content: { type: "string" } } }
]
});
const res = reduceOneOfFuzzy({ node, data: { description: "..." }, pointer: "#", path: [] });
assert.deepEqual(res.schema, {
type: "object",
properties: { description: { type: "string" } }
});
});
it("should return schema matching nested properties", () => {
const node = compileSchema({
oneOf: [
{ type: "object", properties: { title: { type: "number" } } },
{ type: "object", properties: { title: { type: "string" } } }
]
});
const res = reduceOneOfFuzzy({ node, data: { title: "asterix" }, pointer: "#", path: [] });
assert.deepEqual(res.schema, {
type: "object",
properties: { title: { type: "string" } }
});
});
it("should return schema with least missing properties", () => {
const t = { type: "number" };
const node = compileSchema({
oneOf: [
{ type: "object", properties: { a: t, c: t, d: t } },
{ type: "object", properties: { a: t, b: t, c: t } },
{ type: "object", properties: { a: t, d: t, e: t } }
]
});
const res = reduceOneOfFuzzy({ node, data: { a: 0, b: 1 }, pointer: "#", path: [] });
assert.deepEqual(res.schema, {
type: "object",
properties: { a: t, b: t, c: t }
});
assert.equal(res.oneOfIndex, 1, "should have exposed correct resolved oneOfIndex");
});
it("should only count properties that match the schema", () => {
const t = { type: "number" };
const node = compileSchema({
oneOf: [
{ type: "object", properties: { a: { type: "string" }, b: t, c: t } },
{ type: "object", properties: { a: { type: "boolean" }, b: t, d: t } },
{ type: "object", properties: { a: { type: "number" }, b: t, e: t } }
]
});
const res = reduceOneOfFuzzy({ node, data: { a: true, b: 1 }, pointer: "#", path: [] });
assert.deepEqual(res.schema, {
type: "object",
properties: { a: { type: "boolean" }, b: t, d: t }
});
});
it("should find correct pay type", () => {
const node = compileSchema({
type: "object",
oneOf: [
{
type: "object",
properties: { type: { type: "string", default: "free", pattern: "^free" } }
},
{
type: "object",
properties: {
redirectUrl: { format: "url", type: "string" },
type: { type: "string", default: "teaser", pattern: "^teaser" }
}
},
{
type: "object",
properties: {
redirectUrl: { format: "url", type: "string" },
type: { type: "string", default: "article", pattern: "^article" }
}
}
]
});
const res = reduceOneOfFuzzy({
pointer: "#",
path: [],
node,
data: { type: "teaser", redirectUrl: "http://example.com/test/pay/article.html" }
});
assert.deepEqual(res.schema, {
type: "object",
properties: {
redirectUrl: { format: "url", type: "string" },
type: { type: "string", default: "teaser", pattern: "^teaser" }
}
});
});
});
});
describe("keyword : oneof-property : reduce", () => {
describe("object", () => {
it("should return schema matching oneOfProperty", () => {
const node = compileSchema({
[DECLARATOR_ONEOF]: "name",
oneOf: [
{
type: "object",
properties: { name: { type: "string", pattern: "^1$" }, title: { type: "number" } }
},
{
type: "object",
properties: { name: { type: "string", pattern: "^2$" }, title: { type: "number" } }
},
{
type: "object",
properties: { name: { type: "string", pattern: "^3$" }, title: { type: "number" } }
}
]
});
const res = reduceOneOfDeclarator({ node, data: { name: "2", title: 123 }, pointer: "#", path: [] });
assert.deepEqual(res.schema, {
type: "object",
properties: {
name: { type: "string", pattern: "^2$" },
title: { type: "number" }
}
});
assert.equal(res.oneOfIndex, 1, "should have exposed correct resolved oneOfIndex");
});
it("should return schema matching oneOfProperty even it is invalid", () => {
const node = compileSchema({
[DECLARATOR_ONEOF]: "name",
oneOf: [
{
type: "object",
properties: { name: { type: "string", pattern: "^1$" }, title: { type: "number" } }
},
{
type: "object",
properties: { name: { type: "string", pattern: "^2$" }, title: { type: "number" } }
},
{
type: "object",
properties: { name: { type: "string", pattern: "^3$" }, title: { type: "number" } }
}
]
});
const res = reduceOneOfDeclarator({
node,
data: { name: "2", title: "not a number" },
pointer: "#",
path: []
});
assert.deepEqual(res.schema, {
type: "object",
properties: {
name: { type: "string", pattern: "^2$" },
title: { type: "number" }
}
});
});
it("should return an error if value at oneOfProperty is undefined", () => {
const node = compileSchema({
[DECLARATOR_ONEOF]: "name",
oneOf: [
{
type: "object",
properties: { name: { type: "string", pattern: "^1$" }, title: { type: "number" } }
},
{
type: "object",
properties: { name: { type: "string", pattern: "^2$" }, title: { type: "number" } }
},
{
type: "object",
properties: { name: { type: "string", pattern: "^3$" }, title: { type: "number" } }
}
]
});
const res = reduceOneOfDeclarator({ node, data: { title: "not a number" }, pointer: "#", path: [] });
assert(isJsonError(res), "expected result to be an error");
assert.deepEqual(res.code, "missing-one-of-property-error");
});
});
describe("array", () => {
it("should return an error if no oneOfProperty could be matched", () => {
const node = compileSchema({
oneOf: [
{
type: "object",
required: ["title"],
properties: {
title: {
type: "string"
}
}
}
]
});
const res = reduceOneOfDeclarator({
node,
data: { name: "2", title: "not a number" },
pointer: "#",
path: []
});
assert(isJsonError(res), "expected result to be an error");
assert.deepEqual(res.code, "missing-one-of-property-error");
});
});
});