'use strict'
const { test } = require('node:test')
const build = require('..')
process.env.TZ = 'UTC'
test('allOf: combine type and format ', (t) => {
t.plan(1)
const schema = {
allOf: [
{ type: 'string' },
{ format: 'time' }
]
}
const stringify = build(schema)
const date = new Date(1674263005800)
const value = stringify(date)
t.assert.equal(value, '"01:03:25"')
})
test('allOf: combine additional properties ', (t) => {
t.plan(1)
const schema = {
allOf: [
{ type: 'object' },
{
type: 'object',
additionalProperties: { type: 'boolean' }
}
]
}
const stringify = build(schema)
const data = { property: true }
const value = stringify(data)
t.assert.equal(value, JSON.stringify(data))
})
test('allOf: combine pattern properties', (t) => {
t.plan(1)
const schema = {
allOf: [
{ type: 'object' },
{
type: 'object',
patternProperties: {
foo: {
type: 'number'
}
}
}
]
}
const stringify = build(schema)
const data = { foo: 42 }
const value = stringify(data)
t.assert.equal(value, JSON.stringify(data))
})
test('object with allOf and multiple schema on the allOf', (t) => {
t.plan(4)
const schema = {
title: 'object with allOf and multiple schema on the allOf',
type: 'object',
allOf: [
{
type: 'object',
required: [
'name'
],
properties: {
name: {
type: 'string'
},
tag: {
type: 'string'
}
}
},
{
required: [
'id'
],
type: 'object',
properties: {
id: {
type: 'integer'
}
}
}
]
}
const stringify = build(schema)
try {
stringify({
id: 1
})
} catch (e) {
t.assert.equal(e.message, '"name" is required!')
}
try {
stringify({
name: 'string'
})
} catch (e) {
t.assert.equal(e.message, '"id" is required!')
}
t.assert.equal(stringify({
id: 1,
name: 'string'
}), '{"name":"string","id":1}')
t.assert.equal(stringify({
id: 1,
name: 'string',
tag: 'otherString'
}), '{"name":"string","id":1,"tag":"otherString"}')
})
test('object with allOf and one schema on the allOf', (t) => {
t.plan(1)
const schema = {
title: 'object with allOf and one schema on the allOf',
type: 'object',
allOf: [
{
required: [
'id'
],
type: 'object',
properties: {
id: {
type: 'integer'
}
}
}
]
}
const stringify = build(schema)
const value = stringify({
id: 1
})
t.assert.equal(value, '{"id":1}')
})
test('object with allOf and no schema on the allOf', (t) => {
t.plan(1)
const schema = {
title: 'object with allOf and no schema on the allOf',
type: 'object',
allOf: []
}
try {
build(schema)
t.fail()
} catch (e) {
t.assert.equal(e.message, 'schema is invalid: data/allOf must NOT have fewer than 1 items')
}
})
test('object with nested allOfs', (t) => {
t.plan(1)
const schema = {
title: 'object with nested allOfs',
type: 'object',
allOf: [
{
required: [
'id1'
],
type: 'object',
properties: {
id1: {
type: 'integer'
}
}
},
{
allOf: [
{
type: 'object',
properties: {
id2: {
type: 'integer'
}
}
},
{
type: 'object',
properties: {
id3: {
type: 'integer'
}
}
}
]
}
]
}
const stringify = build(schema)
const value = stringify({
id1: 1,
id2: 2,
id3: 3,
id4: 4 // extra prop shouldn't be in result
})
t.assert.equal(value, '{"id1":1,"id2":2,"id3":3}')
})
test('object with anyOf nested inside allOf', (t) => {
t.plan(1)
const schema = {
title: 'object with anyOf nested inside allOf',
type: 'object',
allOf: [
{
required: ['id1', 'obj'],
type: 'object',
properties: {
id1: {
type: 'integer'
},
obj: {
type: 'object',
properties: {
nested: { type: 'string' }
}
}
}
},
{
anyOf: [
{
type: 'object',
properties: {
id2: { type: 'string' }
},
required: ['id2']
},
{
type: 'object',
properties: {
id3: {
type: 'integer'
},
nestedObj: {
type: 'object',
properties: {
nested: { type: 'string' }
}
}
},
required: ['id3']
},
{
type: 'object',
properties: {
id4: { type: 'integer' }
},
required: ['id4']
}
]
}
]
}
const stringify = build(schema)
const value = stringify({
id1: 1,
id3: 3,
id4: 4, // extra prop shouldn't be in result
obj: { nested: 'yes' },
nestedObj: { nested: 'yes' }
})
t.assert.equal(value, '{"id1":1,"obj":{"nested":"yes"},"id3":3,"nestedObj":{"nested":"yes"}}')
})
test('object with $ref in allOf', (t) => {
t.plan(1)
const schema = {
title: 'object with $ref in allOf',
type: 'object',
definitions: {
id1: {
type: 'object',
properties: {
id1: {
type: 'integer'
}
}
}
},
allOf: [
{
$ref: '#/definitions/id1'
}
]
}
const stringify = build(schema)
const value = stringify({
id1: 1,
id2: 2 // extra prop shouldn't be in result
})
t.assert.equal(value, '{"id1":1}')
})
test('object with $ref and other object in allOf', (t) => {
t.plan(1)
const schema = {
title: 'object with $ref in allOf',
type: 'object',
definitions: {
id1: {
type: 'object',
properties: {
id1: {
type: 'integer'
}
}
}
},
allOf: [
{
$ref: '#/definitions/id1'
},
{
type: 'object',
properties: {
id2: {
type: 'integer'
}
}
}
]
}
const stringify = build(schema)
const value = stringify({
id1: 1,
id2: 2,
id3: 3 // extra prop shouldn't be in result
})
t.assert.equal(value, '{"id1":1,"id2":2}')
})
test('object with multiple $refs in allOf', (t) => {
t.plan(1)
const schema = {
title: 'object with $ref in allOf',
type: 'object',
definitions: {
id1: {
type: 'object',
properties: {
id1: {
type: 'integer'
}
}
},
id2: {
type: 'object',
properties: {
id2: {
type: 'integer'
}
}
}
},
allOf: [
{
$ref: '#/definitions/id1'
},
{
$ref: '#/definitions/id2'
}
]
}
const stringify = build(schema)
const value = stringify({
id1: 1,
id2: 2,
id3: 3 // extra prop shouldn't be in result
})
t.assert.equal(value, '{"id1":1,"id2":2}')
})
test('allOf with nested allOf in $ref', (t) => {
t.plan(1)
const schema = {
title: 'allOf with nested allOf in $ref',
type: 'object',
definitions: {
group: {
type: 'object',
allOf: [{
properties: {
id2: {
type: 'integer'
}
}
}, {
properties: {
id3: {
type: 'integer'
}
}
}]
}
},
allOf: [
{
type: 'object',
properties: {
id1: {
type: 'integer'
}
},
required: [
'id1'
]
},
{
$ref: '#/definitions/group'
}
]
}
const stringify = build(schema)
const value = stringify({
id1: 1,
id2: 2,
id3: 3,
id4: 4 // extra prop shouldn't be in result
})
t.assert.equal(value, '{"id1":1,"id2":2,"id3":3}')
})
test('object with external $refs in allOf', (t) => {
t.plan(1)
const externalSchema = {
first: {
definitions: {
id1: {
type: 'object',
properties: {
id1: {
type: 'integer'
}
}
}
}
},
second: {
definitions: {
id2: {
$id: '#id2',
type: 'object',
properties: {
id2: {
type: 'integer'
}
}
}
}
}
}
const schema = {
title: 'object with $ref in allOf',
type: 'object',
allOf: [
{
$ref: 'first#/definitions/id1'
},
{
$ref: 'second#/definitions/id2'
}
]
}
const stringify = build(schema, { schema: externalSchema })
const value = stringify({
id1: 1,
id2: 2,
id3: 3 // extra prop shouldn't be in result
})
t.assert.equal(value, '{"id1":1,"id2":2}')
})
test('allof with local anchor reference', (t) => {
t.plan(1)
const externalSchemas = {
Test: {
$id: 'Test',
definitions: {
Problem: {
type: 'object',
properties: {
type: {
type: 'string'
}
}
},
ValidationFragment: {
type: 'string'
},
ValidationErrorProblem: {
type: 'object',
allOf: [
{
$ref: '#/definitions/Problem'
},
{
type: 'object',
properties: {
validation: {
$ref: '#/definitions/ValidationFragment'
}
}
}
]
}
}
}
}
const schema = { $ref: 'Test#/definitions/ValidationErrorProblem' }
const stringify = build(schema, { schema: externalSchemas })
const data = { type: 'foo', validation: 'bar' }
t.assert.equal(stringify(data), JSON.stringify(data))
})
test('allOf: multiple nested $ref properties', (t) => {
t.plan(2)
const externalSchema1 = {
$id: 'externalSchema1',
oneOf: [
{ $ref: '#/definitions/id1' }
],
definitions: {
id1: {
type: 'object',
properties: {
id1: {
type: 'integer'
}
},
additionalProperties: false
}
}
}
const externalSchema2 = {
$id: 'externalSchema2',
oneOf: [
{ $ref: '#/definitions/id2' }
],
definitions: {
id2: {
type: 'object',
properties: {
id2: {
type: 'integer'
}
},
additionalProperties: false
}
}
}
const schema = {
anyOf: [
{ $ref: 'externalSchema1' },
{ $ref: 'externalSchema2' }
]
}
const stringify = build(schema, { schema: [externalSchema1, externalSchema2] })
t.assert.equal(stringify({ id1: 1 }), JSON.stringify({ id1: 1 }))
t.assert.equal(stringify({ id2: 2 }), JSON.stringify({ id2: 2 }))
})
test('allOf: throw Error if types mismatch ', (t) => {
t.plan(1)
const schema = {
allOf: [
{ type: 'string' },
{ type: 'number' }
]
}
t.assert.throws(() => {
build(schema)
}, {
message: 'Failed to merge "type" keyword schemas.',
schemas: [['string'], ['number']]
})
})
test('allOf: throw Error if format mismatch ', (t) => {
t.plan(1)
const schema = {
allOf: [
{ format: 'date' },
{ format: 'time' }
]
}
t.assert.throws(() => {
build(schema)
}, {
message: 'Failed to merge "format" keyword schemas.'
// schemas: ['date', 'time']
})
})
test('recursive nested allOfs', (t) => {
t.plan(1)
const schema = {
type: 'object',
properties: {
foo: {
additionalProperties: false,
allOf: [{ $ref: '#' }]
}
}
}
const data = { foo: {} }
const stringify = build(schema)
t.assert.equal(stringify(data), JSON.stringify(data))
})
test('recursive nested allOfs', (t) => {
t.plan(1)
const schema = {
type: 'object',
properties: {
foo: {
additionalProperties: false,
allOf: [{ allOf: [{ $ref: '#' }] }]
}
}
}
const data = { foo: {} }
const stringify = build(schema)
t.assert.equal(stringify(data), JSON.stringify(data))
})
test('external recursive allOfs', (t) => {
t.plan(1)
const externalSchema = {
type: 'object',
properties: {
foo: {
properties: {
bar: { type: 'string' }
},
allOf: [{ $ref: '#' }]
}
}
}
const schema = {
type: 'object',
properties: {
a: { $ref: 'externalSchema#/properties/foo' },
b: { $ref: 'externalSchema#/properties/foo' }
}
}
const data = {
a: {
foo: {},
bar: '42',
baz: 42
},
b: {
foo: {},
bar: '42',
baz: 42
}
}
const stringify = build(schema, { schema: { externalSchema } })
t.assert.equal(stringify(data), '{"a":{"bar":"42","foo":{}},"b":{"bar":"42","foo":{}}}')
})
test('do not crash with $ref prop', (t) => {
t.plan(1)
const schema = {
title: 'object with $ref',
type: 'object',
properties: {
outside: {
$ref: '#/$defs/outside'
}
},
$defs: {
inside: {
type: 'object',
properties: {
$ref: {
type: 'string'
}
}
},
outside: {
allOf: [{
$ref: '#/$defs/inside'
}]
}
}
}
const stringify = build(schema)
const value = stringify({
outside: {
$ref: 'true'
}
})
t.assert.equal(value, '{"outside":{"$ref":"true"}}')
})