json-schema-library
Version:
Customizable and hackable json-validator and json-schema utilities for traversal, data generation and validation
1,021 lines (920 loc) • 40.4 kB
text/typescript
import { compileSchema } from "../compileSchema";
import { strict as assert } from "assert";
import { Draft, JsonError, SchemaNode } from "../types";
import { draft2019 } from "../draft2019";
const $schema = "draft-2019-09";
describe("compileSchema.validate (2019)", () => {
describe("integer", () => {
describe("exclusiveMaximum", () => {
it("should fail if value is equal to 0", () => {
const { errors } = compileSchema({
$schema,
exclusiveMaximum: 0
}).validate(0);
assert.deepEqual(errors.length, 1);
});
it("should succeed if value is below to 0", () => {
const { errors } = compileSchema({
$schema,
exclusiveMaximum: 0
}).validate(-1);
assert.deepEqual(errors.length, 0);
});
});
describe("exclusiveMinimum", () => {
it("should fail if value is equal to 0", () => {
const { errors } = compileSchema({
$schema,
exclusiveMinimum: 0
}).validate(0);
assert.deepEqual(errors.length, 1);
});
it("should succeed if value is above to 0", () => {
const { errors } = compileSchema({
$schema,
exclusiveMinimum: 0
}).validate(1);
assert.deepEqual(errors.length, 0);
});
});
describe("oneOf", () => {
it("should validate on a matching oneOf definition", () => {
const { errors } = compileSchema({
$schema,
oneOf: [{ type: "integer" }, { type: "string" }]
}).validate(3);
assert.deepEqual(errors.length, 0);
});
it("should return an error for multiple matching oneOf schemas", () => {
const { errors } = compileSchema({
$schema,
oneOf: [{ type: "integer" }, { minimum: 2 }]
}).validate(3);
assert.deepEqual(errors.length, 1);
assert.deepEqual(errors[0].code, "multiple-one-of-error");
});
});
describe("allOf", () => {
it("should validate if all allOf-schemas are valid", () => {
const { errors } = compileSchema({
$schema,
allOf: [{ type: "integer" }, { minimum: 2 }]
}).validate(3);
assert.deepEqual(errors.length, 0);
});
it("should return error if not all schemas match", () => {
const { errors } = compileSchema({
$schema,
allOf: [{ type: "integer" }, { minimum: 4 }]
}).validate(3);
assert.deepEqual(errors.length, 1);
assert.deepEqual(errors[0].code, "minimum-error");
});
it("should return all errors for each non-matching schemas", () => {
const { errors } = compileSchema({
$schema,
allOf: [{ type: "integer" }, { minimum: 4 }, { maximum: 2 }]
}).validate(3);
assert.deepEqual(errors.length, 2);
assert.deepEqual(errors[0].code, "minimum-error");
assert.deepEqual(errors[1].code, "maximum-error");
});
});
describe("anyOf", () => {
it("should validate if one schemas in anyOf validates", () => {
const { errors } = compileSchema({
$schema,
anyOf: [{ minimum: 4 }, { maximum: 4 }]
}).validate(3);
assert.deepEqual(errors.length, 0);
});
it("should return error if not all schemas match", () => {
const { errors } = compileSchema({
$schema,
anyOf: [{ minimum: 4 }, { maximum: 2 }]
}).validate(3);
assert.deepEqual(errors.length, 1);
assert.deepEqual(errors[0].code, "any-of-error");
});
it("should validate null", () => {
const { errors } = compileSchema({
$schema,
anyOf: [{ type: "null" }]
}).validate(null);
assert.deepEqual(errors.length, 0);
});
it("should return error if invalid null", () => {
const { errors } = compileSchema({
$schema,
anyOf: [{ type: "null" }]
}).validate(3);
assert.deepEqual(errors.length, 1);
assert.deepEqual(errors[0].code, "any-of-error");
});
it("should resolve references", () => {
const { errors } = compileSchema({
$schema,
definitions: { integer: { type: "integer" } },
anyOf: [{ type: "null" }, { $ref: "#/definitions/integer" }]
}).validate(3);
assert.deepEqual(errors.length, 0);
});
});
});
describe("if-then-else", () => {
it("should validate if-then constructs", () => {
const node = compileSchema({
$schema,
if: { exclusiveMaximum: 0 }, // if this schema matches
then: { minimum: -10 } // also test this schema
});
assert.deepEqual(node.validate(-1).errors.length, 0, "valid through then");
assert.deepEqual(node.validate(-100).errors.length, 1, "invalid through then");
assert.deepEqual(node.validate(3).errors.length, 0, "valid when if test fails");
});
it("should validate if-else constructs", () => {
const node = compileSchema({
$schema,
if: { exclusiveMaximum: 0 }, // valid if 'if' valid
else: { multipleOf: 2 } // test if 'if' fails
});
assert.deepEqual(node.validate(-1).errors.length, 0, "valid when if test passes");
assert.deepEqual(node.validate(4).errors.length, 0, "valid through else");
assert.deepEqual(node.validate(3).errors.length, 1, "invalid through else");
});
});
describe("object", () => {
it("should still be valid for missing type", () => {
const { errors } = compileSchema({
$schema,
maxProperties: 1,
minProperties: 1
}).validate({ a: 1 });
assert.deepEqual(errors.length, 0);
});
it("should return all errors", () => {
const { errors } = compileSchema({
$schema,
type: "object",
additionalProperties: false,
properties: {
a: { type: "string" },
id: { type: "string", pattern: /^first$/ }
}
}).validate({ id: "first", a: "correct", b: "notallowed", c: false });
assert.deepEqual(errors.length, 2);
assert.deepEqual(errors[0].code, "no-additional-properties-error");
assert.deepEqual(errors[1].code, "no-additional-properties-error");
});
describe("min/maxProperties", () => {
it("should return min-properties-error for too few properties", () => {
const { errors } = compileSchema({
$schema,
type: "object",
minProperties: 2
}).validate({ a: 1 });
assert.deepEqual(errors.length, 1);
assert.deepEqual(errors[0].code, "min-properties-error");
});
it("should return max-properties-error for too many properties", () => {
const { errors } = compileSchema({
$schema,
type: "object",
maxProperties: 1
}).validate({ a: 1, b: 2 });
assert.deepEqual(errors.length, 1);
assert.deepEqual(errors[0].code, "max-properties-error");
});
it("should be valid if property count is within range", () => {
const { errors } = compileSchema({
$schema,
type: "object",
maxProperties: 1,
minProperties: 1
}).validate({ a: 1 });
assert.deepEqual(errors.length, 0);
});
});
describe("not", () => {
it("should be invalid if 'not' keyword does match", () => {
const { errors } = compileSchema({
$schema,
type: "object",
not: { type: "object", properties: { a: { type: "number" } } }
}).validate({ a: 1 });
assert.deepEqual(errors.length, 1);
assert.deepEqual(errors[0].code, "not-error");
});
});
describe("dependencies", () => {
it("should ignore any dependencies if the property is no set", () => {
const { errors } = compileSchema({
$schema,
type: "object",
properties: { title: { type: "string" }, url: { type: "string" }, target: { type: "string" } },
dependencies: { url: ["target"] }
}).validate({ title: "Check this out" });
assert.deepEqual(errors.length, 0);
});
it("should return a 'missing-dependency-error' if the dependent property is missing", () => {
const { errors } = compileSchema({
$schema,
type: "object",
properties: { title: { type: "string" }, url: { type: "string" }, target: { type: "string" } },
dependencies: {
url: ["target"]
}
}).validate({ title: "Check this out", url: "http://example.com" });
assert.deepEqual(errors.length, 1);
assert.deepEqual(errors[0].code, "missing-dependency-error");
});
it("should return a 'missing-dependency-error' if the dependent counterpart is missing", () => {
const { errors } = compileSchema({
$schema,
type: "object",
properties: { title: { type: "string" }, url: { type: "string" }, target: { type: "string" } },
dependencies: { url: ["target"], target: ["url"] }
}).validate({ title: "Check this out", target: "_blank" });
assert.deepEqual(errors.length, 1);
assert.deepEqual(errors[0].code, "missing-dependency-error");
});
it("should be valid for a matching schema dependency", () => {
const { errors } = compileSchema({
$schema,
type: "object",
properties: { title: { type: "string" }, url: { type: "string" }, target: { type: "string" } },
dependencies: { url: { properties: { target: { type: "string" } } } }
}).validate({ title: "Check this out", url: "http://example.com", target: "_blank" });
assert.deepEqual(errors.length, 0);
});
it("should return validation error for a non-matching schema dependency", () => {
const { errors } = compileSchema({
$schema,
type: "object",
properties: { title: { type: "string" }, url: { type: "string" }, target: { type: "string" } },
dependencies: { url: { required: ["target"], properties: { target: { type: "string" } } } }
}).validate({ url: "http://example.com" });
assert.deepEqual(errors.length, 1);
assert.deepEqual(errors[0].code, "required-property-error");
});
it("should return correct error for invalid dependency", () => {
const { errors } = compileSchema({
$schema,
type: "object",
properties: {
nested: {
type: "object",
properties: { test: { type: "string" } },
dependencies: {
test: {
required: ["dynamic"],
properties: { dynamic: { type: "string", minLength: 1 } }
}
}
}
}
}).validate({ nested: { test: "with then", dynamic: "" } });
assert.deepEqual(errors.length, 1, "should have returned an error");
assert.deepEqual(errors[0].data.pointer, "#/nested/dynamic");
});
});
});
describe("array", () => {
it("should return error for invalid index", () => {
const { errors } = compileSchema({
$schema,
type: "array",
items: [{ type: "string" }]
}).validate([1]);
assert.deepEqual(errors.length, 1);
assert.deepEqual(errors[0].code, "type-error");
});
it("should be valid for matching indices", () => {
const { errors } = compileSchema({
$schema,
type: "array",
items: [{ type: "string" }, { type: "number" }]
}).validate(["1", 2]);
assert.deepEqual(errors.length, 0);
});
it("should return all errors", () => {
const { errors } = compileSchema({
$schema,
type: "array",
items: { type: "string" },
maxItems: 1
}).validate(["1", 2]);
assert.deepEqual(errors.length, 2);
assert.deepEqual(errors[0].code, "type-error");
assert.deepEqual(errors[1].code, "max-items-error");
});
describe("min/maxItems", () => {
it("should return MinItemsError for too few items", () => {
const { errors } = compileSchema({
$schema,
type: "array",
minItems: 2
}).validate([1]);
assert.deepEqual(errors.length, 1);
assert.deepEqual(errors[0].code, "min-items-error");
});
it("should return MaxItemsError for too many items", () => {
const { errors } = compileSchema({
$schema,
type: "array",
maxItems: 1
}).validate([1, 2]);
assert.deepEqual(errors.length, 1);
assert.deepEqual(errors[0].code, "max-items-error");
});
it("should be valid if item count is within range", () => {
const { errors } = compileSchema({
$schema,
type: "array",
minItems: 2,
maxItems: 2
}).validate([1, 2]);
assert.deepEqual(errors.length, 0);
});
it("should still be valid for missing type", () => {
const { errors } = compileSchema({
$schema,
minItems: 2,
maxItems: 2
}).validate([1, 2]);
assert.deepEqual(errors.length, 0);
});
});
describe("not", () => {
it("should be invalid if 'not' keyword does match", () => {
const { errors } = compileSchema({
$schema,
type: "array",
items: [{ type: "string" }, { type: "number" }],
additionalItems: { type: "object" },
not: { items: {} }
}).validate(["1", 2, {}]);
assert.deepEqual(errors.length, 1);
assert.deepEqual(errors[0].code, "not-error");
});
});
describe("uniqueItems", () => {
it("should not validate for duplicated values", () => {
const { errors } = compileSchema({
$schema,
type: "array",
uniqueItems: true
}).validate([1, 2, 3, 4, 3]);
assert.deepEqual(errors.length, 1);
assert.deepEqual(errors[0].code, "unique-items-error");
});
it("should not validate for duplicated objects", () => {
const { errors } = compileSchema({
$schema,
type: "array",
uniqueItems: true
}).validate([{ id: "first" }, { id: "second" }, { id: "first" }]);
assert.deepEqual(errors.length, 1);
assert.deepEqual(errors[0].code, "unique-items-error");
});
it("should validate for mismatching objects with equal properties", () => {
const { errors } = compileSchema({
$schema,
type: "array",
uniqueItems: true
}).validate([
{ id: "first", val: 1 },
{ id: "first", val: 2 },
{ id: "first", val: 3 }
]);
assert.deepEqual(errors.length, 0);
});
});
describe("oneOf", () => {
it("should return no error for valid oneOf items", () => {
const { errors } = compileSchema({
$schema,
type: "array",
items: {
oneOf: [
{ type: "number" },
{ type: "object", properties: { a: { type: "string" } }, additionalProperties: false }
]
}
}).validate([100, { a: "string" }]);
assert.deepEqual(errors.length, 0);
});
it("should return error if no item does match", () => {
const { errors } = compileSchema({
$schema,
type: "array",
items: {
oneOf: [
{ type: "number" },
{ type: "object", properties: { a: { type: "string" } }, additionalProperties: false }
]
},
additionalItems: false
}).validate([100, { a: "correct", b: "not correct" }]);
assert.deepEqual(errors.length, 1);
});
it("should return multiple-one-of-error if multiple oneOf definitions match the given value", () => {
const { errors } = compileSchema({
$schema,
type: "array",
items: { oneOf: [{ type: "integer" }, { minimum: 2 }] }
}).validate([3]);
assert.deepEqual(errors.length, 1);
assert.deepEqual(errors[0].code, "multiple-one-of-error");
});
});
});
describe("string", () => {
it("should return min-length-error if string is too short", () => {
const { errors } = compileSchema({
$schema,
type: "string",
minLength: 2
}).validate("a");
assert.deepEqual(errors.length, 1);
assert.deepEqual(errors[0].code, "min-length-error");
});
it("should return max-length-error if string is too long", () => {
const { errors } = compileSchema({
$schema,
type: "string",
maxLength: 2
}).validate("abc");
assert.deepEqual(errors.length, 1);
assert.deepEqual(errors[0].code, "max-length-error");
});
it("should be valid if string is within range", () => {
const { errors } = compileSchema({
$schema,
type: "string",
minLength: 2,
maxLength: 2
}).validate("ab");
assert.deepEqual(errors.length, 0);
});
it("should still be valid for missing type", () => {
const { errors } = compileSchema({
$schema,
minLength: 2,
maxLength: 2
}).validate("ab");
assert.deepEqual(errors.length, 0);
});
it("should return enum-error if value is not within enum list", () => {
const { errors } = compileSchema({
$schema,
type: "string",
enum: ["a", "c"]
}).validate("b");
assert.deepEqual(errors.length, 1);
assert.deepEqual(errors[0].code, "enum-error");
});
it("should be valid if value is within enum list", () => {
const { errors } = compileSchema({
$schema,
type: "string",
enum: ["a", "b", "c"]
}).validate("b");
assert.deepEqual(errors.length, 0);
});
it("should be invalid if 'not' keyword does match", () => {
const { errors } = compileSchema({
$schema,
type: "string",
not: { type: "string", pattern: "^b$" }
}).validate("b");
assert.deepEqual(errors.length, 1);
assert.deepEqual(errors[0].code, "not-error");
});
});
describe("number", () => {
it("should return minimum-error if number is too small", () => {
const { errors } = compileSchema({
$schema,
type: "number",
minimum: 2
}).validate(1);
assert.deepEqual(errors.length, 1);
assert.deepEqual(errors[0].code, "minimum-error");
});
it("should return minimum-error if number is equal and exclusiveMinimum is set", () => {
const { errors } = compileSchema({
$schema,
type: "number",
minimum: 2,
exclusiveMinimum: true
}).validate(2);
assert.deepEqual(errors.length, 1);
assert.deepEqual(errors[0].code, "minimum-error");
});
it("should return maximum-error if number is too large", () => {
const { errors } = compileSchema({
$schema,
type: "number",
maximum: 1
}).validate(2);
assert.deepEqual(errors.length, 1);
assert.deepEqual(errors[0].code, "maximum-error");
});
it("should return maximum-error if number same and exclusiveMaximum is set", () => {
const { errors } = compileSchema({
$schema,
type: "number",
maximum: 2,
exclusiveMaximum: true
}).validate(2);
assert.deepEqual(errors.length, 1);
assert.deepEqual(errors[0].code, "maximum-error");
});
it("should be valid if number is within range", () => {
const { errors } = compileSchema({
$schema,
type: "number",
minimum: 1,
maximum: 1
}).validate(1);
assert.deepEqual(errors.length, 0);
});
it("should still be valid for missing type", () => {
const { errors } = compileSchema({
$schema,
minimum: 1,
maximum: 1
}).validate(1);
assert.deepEqual(errors.length, 0);
});
it("should validate NaN", () => {
const { errors } = compileSchema({
$schema,
type: "number"
}).validate(parseInt("a"));
assert.deepEqual(errors.length, 0);
});
it("should return enum-error if value is not within enum list", () => {
const { errors } = compileSchema({
$schema,
type: "number",
enum: [21, 27, 42]
}).validate(13);
assert.deepEqual(errors.length, 1);
assert.deepEqual(errors[0].code, "enum-error");
});
it("should be valid if value is within enum list", () => {
const { errors } = compileSchema({
$schema,
type: "number",
enum: [21, 27, 42]
}).validate(27);
assert.deepEqual(errors.length, 0);
});
it("should return error if value is not multiple of 1.5", () => {
const { errors } = compileSchema({
$schema,
type: "number",
multipleOf: 1.5
}).validate(4);
assert.deepEqual(errors.length, 1);
assert.deepEqual(errors[0].code, "multiple-of-error");
});
it("should be valid if value if a multiple of 1.5", () => {
const { errors } = compileSchema({
$schema,
type: "number",
multipleOf: 1.5
}).validate(4.5);
assert.deepEqual(errors.length, 0);
});
it("should be valid if 'multipleOf' is not a number", () => {
const { errors } = compileSchema({
$schema,
type: "number",
multipleOf: "non-number"
}).validate(4.5);
assert.deepEqual(errors.length, 0);
});
it("should be invalid if 'not' keyword does match", () => {
const { errors } = compileSchema({
$schema,
type: "number",
not: { type: "number", minimum: 4 }
}).validate(4.5);
assert.deepEqual(errors.length, 1);
assert.deepEqual(errors[0].code, "not-error");
});
});
describe("arrays of types", () => {
it("should not return an error for a valid type", () => {
assert(compileSchema({ $schema, type: ["object", "null"] }).validate({}).valid);
assert(compileSchema({ $schema, type: ["object", "null"] }).validate(null).valid);
});
it("should return a TypeError if passed type is not within array", () => {
const { errors } = compileSchema({
$schema,
type: ["object", "null"]
}).validate([]);
assert.deepEqual(errors.length, 1);
assert.deepEqual(errors[0].code, "type-error");
});
it("should support 'integer' as a valid type within array", () => {
const { errors } = compileSchema({
$schema,
type: ["integer", "null"]
}).validate(1);
assert.deepEqual(errors.length, 0);
});
});
describe("heterogeneous types", () => {
describe("enum", () => {
it("should validate a matching value within enum", () => {
const { errors } = compileSchema({
$schema,
enum: [1, "second", []]
}).validate("second");
assert.deepEqual(errors.length, 0);
});
it("should validate a matching array within enum", () => {
const { errors } = compileSchema({
$schema,
enum: [1, "second", []]
}).validate([]);
assert.deepEqual(errors.length, 0);
});
it("should validate a matching object within enum", () => {
const { errors } = compileSchema({
$schema,
enum: [1, "second", { id: "third" }]
}).validate({ id: "third" });
assert.deepEqual(errors.length, 0);
});
it("should return error for non-matching object", () => {
const { errors } = compileSchema({
$schema,
enum: [1, "second", { id: "third" }]
}).validate({ id: "first" });
assert.deepEqual(errors.length, 1);
assert.deepEqual(errors[0].code, "enum-error");
});
it("should return error for invalid null", () => {
const { errors } = compileSchema({
$schema,
enum: [1, "second", { id: "third" }]
}).validate(null);
assert.deepEqual(errors.length, 1);
assert.deepEqual(errors[0].code, "enum-error");
});
});
describe("$ref", () => {
it("should correctly validate data through nested $ref", () => {
const { errors } = compileSchema({
$schema,
$ref: "#/definitions/c",
definitions: {
a: { type: "integer" },
b: { $ref: "#/definitions/a" },
c: { $ref: "#/definitions/b" }
}
}).validate("a");
assert.deepEqual(errors.length, 1);
assert.deepEqual(errors[0].code, "type-error");
});
it("should correctly validate combination of remote, allOf, and allOf-$ref", () => {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const draft04Meta = require("../../remotes/draft04.json");
const { errors } = compileSchema({
$schema,
$ref: "http://json-schema.org/draft-04/schema#",
_id: "input"
})
.addRemoteSchema("http://json-schema.org/draft-04/schema", draft04Meta)
.validate({ minLength: -1 });
assert.deepEqual(errors.length, 1);
assert.deepEqual(errors[0].code, "minimum-error");
});
it("should correctly resolve local remote url", () => {
const { errors } = compileSchema({
$schema,
$ref: "http://localhost:1234/integer.json",
_id: "input"
})
.addRemoteSchema(
"http://localhost:1234/integer.json",
// eslint-disable-next-line @typescript-eslint/no-var-requires
require("json-schema-test-suite/remotes/integer.json")
)
.validate("not an integer");
assert.deepEqual(errors.length, 1);
assert.deepEqual(errors[0].code, "type-error");
});
it("spec/unevaluatedProperties : dynamic evalation inside nested refs : should validate a", () => {
const node = compileSchema({
$schema: "https://json-schema.org/draft/2019-09/schema",
$defs: {
one: {
oneOf: [
{ $ref: "#/$defs/two" },
{ required: ["b"], properties: { b: true } },
{ required: ["xx"], patternProperties: { x: true } },
{ required: ["all"], unevaluatedProperties: true }
]
},
two: {
oneOf: [
{ required: ["c"], properties: { c: true } },
{ required: ["d"], properties: { d: true } }
]
}
},
oneOf: [{ $ref: "#/$defs/one" }, { required: ["a"], properties: { a: true } }],
unevaluatedProperties: false
});
const { errors } = node.validate({ a: 1 });
assert(errors.length === 0);
});
});
});
describe("recursiveRef (spec)", () => {
describe("$recursiveRef without using nesting", () => {
it("integer does not match as a property value", () => {
// how it should resolve
// { foo } » root:anyOf: [false, ?]
// 1. resolve http://localhost:4242/draft2019-09/recursiveRef2/schema.json#/$defs/myobject
// => domain + local path (fragments 2) => myobject-schema
// 2. { foo } » anyOf: [false, true + ?]
// 3. { foo } » myObject:anyof:additionalProperties => recursiveRef
// => recursiveAnchor = myObject
// 4. 1 » anyOf: [false, false] => error
const node = compileSchema({
$schema: "https://json-schema.org/draft/2019-09/schema",
$id: "http://localhost:4242/draft2019-09/recursiveRef2/schema.json",
$defs: {
myobject: {
$id: "myobject.json",
$recursiveAnchor: true,
anyOf: [
{ type: "string" },
{
type: "object",
additionalProperties: { $recursiveRef: "#" }
}
]
}
},
anyOf: [{ type: "integer" }, { $ref: "#/$defs/myobject" }]
});
const { errors } = node.validate({ foo: 1 });
assert(errors.length > 0, "should have returned error for invalid integer");
});
});
describe("$recursiveRef with $recursiveAnchor: false works like $ref", () => {
let node: SchemaNode;
beforeEach(() => {
node = compileSchema({
$schema: "https://json-schema.org/draft/2019-09/schema",
$id: "http://localhost:4242/draft2019-09/recursiveRef4/schema.json",
$recursiveAnchor: false,
$defs: {
myobject: {
$id: "myobject.json",
$recursiveAnchor: false,
anyOf: [
{ type: "string" },
{
type: "object",
additionalProperties: { $recursiveRef: "#" }
}
]
}
},
anyOf: [{ type: "integer" }, { $ref: "#/$defs/myobject" }]
});
});
it("single level match", () => {
// how it should resolve
// { foo } » root:anyOf: [false, ?]
// 1. resolve http://localhost:4242/draft2019-09/recursiveRef2/schema.json#/$defs/myobject
// => domain + local path (fragments 2) => myobject-schema
// 2. { foo } » anyOf: [false, true + ?]
// 3. { foo } » myObject:anyof:additionalProperties => recursiveRef
// => recursiveAnchor = myObject
// 4. 1 » anyOf: [false, false] => error
const { errors } = node.validate({ foo: "hi" });
assert(errors.length === 0, "should have validated data");
});
it("integer does not match as a property value", () => {
const { errors } = node.validate({ foo: 1 });
assert(errors.length > 0, "should have returned error for integer");
});
});
});
});
describe("compileSchema.validate : format", () => {
describe("time", () => {
it("should validate HH:mm:ss-HH:mm", () => {
const { errors } = compileSchema({
$schema,
type: "string",
format: "time"
}).validate("15:31:12-02:30");
assert.deepEqual(errors, []);
});
it("should validate HH:mm:ssZ", () => {
const { errors } = compileSchema({
$schema,
type: "string",
format: "time"
}).validate("15:31:12Z");
assert.deepEqual(errors, []);
});
it("should not validate minutes above 59", () => {
const { errors } = compileSchema({
$schema,
type: "string",
format: "time"
}).validate("15:60:12");
assert.equal(errors.length, 1);
});
it("should not validate seconds above 59", () => {
const { errors } = compileSchema({
$schema,
type: "string",
format: "time"
}).validate("15:31:60");
assert.equal(errors.length, 1);
});
it("should not validate HH:mm", () => {
const { errors } = compileSchema({
$schema,
type: "string",
format: "time"
}).validate("15:31");
assert.equal(errors.length, 1);
});
});
describe("url", () => {
it("should validate format url", () => {
const { errors } = compileSchema({
$schema,
type: "string",
format: "url"
}).validate("https://developer.mozilla.org/en-US/");
assert.deepEqual(errors, []);
});
it("should return error UrlFormatError for invalid urls", () => {
const { errors } = compileSchema({
$schema,
type: "string",
format: "url"
}).validate("123");
assert.equal(errors.length, 1);
assert.equal(errors[0].code, "format-url-error");
});
});
});
describe("async validation", () => {
let draft: Draft;
beforeEach(() => {
draft = {
...draft2019,
keywords: [
...draft2019.keywords,
{
id: "async",
keyword: "async-error",
addValidate: (node) => node.schema.asyncError != null,
validate: async ({ node }): Promise<JsonError> => {
if (node.schema.asyncError === false) {
return undefined;
}
return node.createError("type-error", {
schema: {},
pointer: "",
value: ""
});
}
}
]
};
it("should resolve async validation returning no error", async () => {
const { errors, errorsAsync } = compileSchema(
{ type: "number", asyncError: false },
{ drafts: [draft] }
).validate(4);
const asyncErrors = await Promise.all(errorsAsync);
assert.deepEqual(errors.length, 0);
assert.deepEqual(asyncErrors.length, 0);
});
it("should resolve async validation errors", async () => {
const { errorsAsync } = compileSchema({ type: "number", asyncError: true }, { drafts: [draft] }).validate(
4
);
const asyncErrors = await Promise.all(errorsAsync);
assert.deepEqual(asyncErrors.length, 1);
assert.deepEqual(asyncErrors[0].code, "type-error");
});
});
});