"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); /*--------------------------------------------------------------------------------------------- * Copyright (c) IBM Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ const testHelper_1 = require("./utils/testHelper"); const serviceSetup_1 = require("./utils/serviceSetup"); const chai_1 = require("chai"); const yamlSettings_1 = require("../src/yamlSettings"); const schemaUrls_1 = require("../src/languageservice/utils/schemaUrls"); describe('Validation Tests', () => { let languageSettingsSetup; let validationHandler; let yamlSettings; let schemaProvider; const toContent = (data) => JSON.stringify(data, null, 2); before(() => { languageSettingsSetup = new serviceSetup_1.ServiceSetup() .withValidate() .withCompletion() .withCustomTags(['!Test', '!Ref sequence']) .withSchemaFileMatch({ uri: schemaUrls_1.KUBERNETES_SCHEMA_URL, fileMatch: ['.drone.yml'] }) .withSchemaFileMatch({ uri: 'https://json.schemastore.org/drone', fileMatch: ['.drone.yml'] }) .withSchemaFileMatch({ uri: schemaUrls_1.KUBERNETES_SCHEMA_URL, fileMatch: ['test.yml'] }) .withSchemaFileMatch({ uri: 'https://raw.githubusercontent.com/composer/composer/master/res/composer-schema.json', fileMatch: ['test.yml'], }); const { validationHandler: valHandler, yamlSettings: settings, schemaProvider: testSchemaProvider, } = (0, testHelper_1.setupLanguageService)(languageSettingsSetup.languageSettings); validationHandler = valHandler; yamlSettings = settings; schemaProvider = testSchemaProvider; }); function parseSetup(content, customSchemaID) { const testTextDocument = (0, testHelper_1.setupSchemaIDTextDocument)(content, customSchemaID); yamlSettings.documents = new yamlSettings_1.TextDocumentTestManager(); yamlSettings.documents.set(testTextDocument); return validationHandler.validateTextDocument(testTextDocument); } afterEach(() => { schemaProvider.deleteSchema(testHelper_1.SCHEMA_ID); }); describe('$ref resolution', () => { it('root pointer ref', async () => { schemaProvider.addSchema(testHelper_1.SCHEMA_ID, { $schema: 'https://json-schema.org/draft/2019-09/schema', properties: { foo: { $ref: '#' }, }, additionalProperties: false, }); // match let content = toContent({ foo: false }); let result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; // recursive match content = toContent({ foo: { foo: false } }); result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; // mismatch content = toContent({ bar: false }); result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('not allowed'); // recursive mismatch content = toContent({ foo: { bar: false } }); result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('not allowed'); }); it('relative pointer ref to object', async () => { schemaProvider.addSchema(testHelper_1.SCHEMA_ID, { $schema: 'https://json-schema.org/draft/2019-09/schema', properties: { foo: { type: 'integer' }, bar: { $ref: '#/properties/foo' }, }, }); // match let content = toContent({ bar: 3 }); let result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; // mismatch content = toContent({ bar: true }); result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Incorrect type'); }); it('relative pointer ref to array', async () => { schemaProvider.addSchema(testHelper_1.SCHEMA_ID, { $schema: 'https://json-schema.org/draft/2019-09/schema', items: [{ type: 'integer' }, { $ref: '#/items/0' }], }); // match array let content = toContent([1, 2]); let result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; // mismatch array content = toContent([1, 'foo']); result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Incorrect type'); }); it('escaped pointer ref', async () => { schemaProvider.addSchema(testHelper_1.SCHEMA_ID, { $schema: 'https://json-schema.org/draft/2019-09/schema', $defs: { 'tilde~field': { type: 'integer' }, 'slash/field': { type: 'integer' }, 'percent%field': { type: 'integer' }, }, properties: { tilde: { $ref: '#/$defs/tilde~0field' }, slash: { $ref: '#/$defs/slash~1field' }, percent: { $ref: '#/$defs/percent%25field' }, }, }); // slash invalid let content = toContent({ slash: 'aoeu' }); let result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Incorrect type'); // tilde invalid content = toContent({ tilde: 'aoeu' }); result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Incorrect type'); // percent invalid content = toContent({ percent: 'aoeu' }); result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Incorrect type'); // slash valid content = toContent({ slash: 123 }); result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; // tilde valid content = toContent({ tilde: 123 }); result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; // percent valid content = toContent({ percent: 123 }); result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; }); it('nested refs', async () => { schemaProvider.addSchema(testHelper_1.SCHEMA_ID, { $schema: 'https://json-schema.org/draft/2019-09/schema', $defs: { a: { type: 'integer' }, b: { $ref: '#/$defs/a' }, c: { $ref: '#/$defs/b' }, }, $ref: '#/$defs/c', }); // nested ref valid let content = toContent(5); let result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; // nested ref invalid content = toContent('a'); result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Incorrect type'); }); it('ref applies alongside sibling keywords', async () => { schemaProvider.addSchema(testHelper_1.SCHEMA_ID, { $schema: 'https://json-schema.org/draft/2019-09/schema', $defs: { reffed: { type: 'array', }, }, properties: { foo: { $ref: '#/$defs/reffed', maxItems: 2, }, }, }); // ref valid, maxItems valid let content = toContent({ foo: [] }); let result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; // ref valid, maxItems invalid content = toContent({ foo: [1, 2, 3] }); result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Array has too many items'); // ref invalid content = toContent({ foo: 'string' }); result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Incorrect type'); }); it('property named $ref that is not a reference', async () => { schemaProvider.addSchema(testHelper_1.SCHEMA_ID, { $schema: 'https://json-schema.org/draft/2019-09/schema', properties: { $ref: { type: 'string' }, }, }); // property named $ref valid let content = toContent({ $ref: 'a' }); let result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; // property named $ref invalid content = toContent({ $ref: 2 }); result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Incorrect type'); }); it('property named $ref, containing an actual $ref', async () => { schemaProvider.addSchema(testHelper_1.SCHEMA_ID, { $schema: 'https://json-schema.org/draft/2019-09/schema', properties: { $ref: { $ref: '#/$defs/is-string' }, }, $defs: { 'is-string': { type: 'string', }, }, }); // property named $ref valid let content = toContent({ $ref: 'a' }); let result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; // property named $ref invalid content = toContent({ $ref: 2 }); result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Incorrect type'); }); it('$ref to boolean schema true', async () => { schemaProvider.addSchema(testHelper_1.SCHEMA_ID, { $schema: 'https://json-schema.org/draft/2019-09/schema', $ref: '#/$defs/bool', $defs: { bool: true, }, }); // any value is valid const content = toContent('foo'); const result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; }); it('$ref to boolean schema false', async () => { schemaProvider.addSchema(testHelper_1.SCHEMA_ID, { $schema: 'https://json-schema.org/draft/2019-09/schema', $ref: '#/$defs/bool', $defs: { bool: false, }, }); // any value is invalid const content = toContent('foo'); const result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Matches a schema that is not allowed'); }); }); describe('$anchor resolution', () => { it('Location-independent identifier', async () => { schemaProvider.addSchema(testHelper_1.SCHEMA_ID, { $schema: 'https://json-schema.org/draft/2019-09/schema', $ref: '#foo', $defs: { A: { $anchor: 'foo', type: 'integer', }, }, }); // match let content = toContent(1); let result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; // mismatch content = toContent('a'); result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Incorrect type'); (0, chai_1.expect)(result[0].message).to.include('integer'); }); it('Location-independent identifier with absolute URI', async () => { schemaProvider.addSchema(testHelper_1.SCHEMA_ID, { $schema: 'https://json-schema.org/draft/2019-09/schema', $ref: 'http://localhost:1234/draft2019-09/bar#foo', $defs: { A: { $id: 'http://localhost:1234/draft2019-09/bar', $anchor: 'foo', type: 'integer', }, }, }); // match let content = toContent(1); let result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; // mismatch content = toContent('a'); result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Incorrect type'); (0, chai_1.expect)(result[0].message).to.include('integer'); }); it('Location-independent identifier with base URI change in subschema', async () => { schemaProvider.addSchema(testHelper_1.SCHEMA_ID, { $schema: 'https://json-schema.org/draft/2019-09/schema', $id: 'http://localhost:1234/draft2019-09/root', $ref: 'http://localhost:1234/draft2019-09/nested.json#foo', $defs: { A: { $id: 'nested.json', $defs: { B: { $anchor: 'foo', type: 'integer', }, }, }, }, }); // match let content = toContent(1); let result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; // mismatch content = toContent('a'); result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Incorrect type'); (0, chai_1.expect)(result[0].message).to.include('integer'); }); it('same $anchor with different base uri', async () => { schemaProvider.addSchema(testHelper_1.SCHEMA_ID, { $schema: 'https://json-schema.org/draft/2019-09/schema', $id: 'http://localhost:1234/draft2019-09/foobar', $defs: { A: { $id: 'child1', allOf: [ { $id: 'child2', $anchor: 'my_anchor', type: 'number', }, { $anchor: 'my_anchor', type: 'string', }, ], }, }, $ref: 'child1#my_anchor', }); // $ref resolves to /$defs/A/allOf/1 let content = toContent('a'); let result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; // $ref does not resolve to /$defs/A/allOf/0 content = toContent(1); result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Incorrect type'); (0, chai_1.expect)(result[0].message).to.include('string'); }); it('resolves $ref "#name" via $anchor in same document', async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', $defs: { Name: { $anchor: 'name', type: 'string', minLength: 2, }, }, $ref: '#name', }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); const content = `A`; const result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('String is shorter than the minimum length of 2.'); }); it('resolves external $ref to a root $anchor', async () => { const rootSchema = { $schema: 'https://json-schema.org/draft/2019-09/schema', $anchor: 'rootThing', type: 'object', properties: { x: { type: 'number', }, }, required: ['x'], }; const useRootSchema = { $schema: 'https://json-schema.org/draft/2019-09/schema', $ref: 'file:///root.schema.json#rootThing', }; schemaProvider.addSchemaWithUri(testHelper_1.SCHEMA_ID, 'file:///root.schema.json', rootSchema); schemaProvider.addSchemaWithUri(testHelper_1.SCHEMA_ID, 'file:///use-root.schema.json', useRootSchema); const content = `# yaml-language-server: $schema=file:///use-root.schema.json\n{}`; const result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Missing property'); }); it('resolves external $ref to $anchor in another schema', async () => { const typesSchema = { $schema: 'https://json-schema.org/draft/2019-09/schema', $defs: { Port: { $anchor: 'port', type: 'integer', minimum: 1, maximum: 65535, }, }, }; const serverSchema = { $schema: 'https://json-schema.org/draft/2019-09/schema', type: 'object', properties: { port: { $ref: 'file:///types.schema.json#port' }, }, }; schemaProvider.addSchemaWithUri(testHelper_1.SCHEMA_ID, 'file:///types.schema.json', typesSchema); schemaProvider.addSchemaWithUri(testHelper_1.SCHEMA_ID, 'file:///server.schema.json', serverSchema); const content = `# yaml-language-server: $schema=file:///server.schema.json\nport: 70000`; const result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Value is above the maximum of 65535.'); }); }); describe('keyword: unevaluatedProperties', () => { it('unevaluatedProperties as schema validates remaining property values', async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', type: 'object', properties: { known: { type: 'string' }, }, unevaluatedProperties: { type: 'number' }, }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); const content = `known: ok\nextra: hi`; const result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Incorrect type.'); (0, chai_1.expect)(result[0].message).to.include('number'); }); it('unevaluatedProperties=false sees evaluated props across allOf', async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', allOf: [ { type: 'object', properties: { a: { type: 'string' } } }, { type: 'object', properties: { b: { type: 'number' } } }, ], unevaluatedProperties: false, }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); const content = `a: ok\nb: 1\nc: 2`; const result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Property c is not allowed'); }); it('unevaluatedProperties sees properties defined across $ref', async () => { const baseSchema = { $schema: 'https://json-schema.org/draft/2019-09/schema', type: 'object', properties: { a: { type: 'string' }, }, }; const strictSchema = { $schema: 'https://json-schema.org/draft/2019-09/schema', allOf: [{ $ref: 'file:///base-uneval.schema.json' }], unevaluatedProperties: false, }; schemaProvider.addSchemaWithUri(testHelper_1.SCHEMA_ID, 'file:///base-uneval.schema.json', baseSchema); schemaProvider.addSchemaWithUri(testHelper_1.SCHEMA_ID, 'file:///strict-uneval.schema.json', strictSchema); const content = `# yaml-language-server: $schema=file:///strict-uneval.schema.json\na: ok\nc: nope`; const result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Property c is not allowed'); }); it('unevaluatedProperties true', async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', type: 'object', unevaluatedProperties: true, }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); (0, chai_1.expect)(await parseSetup(toContent({}))).to.be.empty; (0, chai_1.expect)(await parseSetup(toContent({ foo: 'foo' }))).to.be.empty; }); it('unevaluatedProperties schema', async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', type: 'object', unevaluatedProperties: { type: 'string', minLength: 3, }, }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); (0, chai_1.expect)(await parseSetup(toContent({}))).to.be.empty; (0, chai_1.expect)(await parseSetup(toContent({ foo: 'foo' }))).to.be.empty; const result = await parseSetup(toContent({ foo: 'fo' })); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('String is shorter than the minimum length of 3.'); }); it('unevaluatedProperties false', async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', type: 'object', unevaluatedProperties: false, }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); (0, chai_1.expect)(await parseSetup(toContent({}))).to.be.empty; const result = await parseSetup(toContent({ foo: 'foo' })); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Property foo is not allowed.'); }); it('unevaluatedProperties with adjacent properties', async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', type: 'object', properties: { foo: { type: 'string' }, }, unevaluatedProperties: false, }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); (0, chai_1.expect)(await parseSetup(toContent({ foo: 'foo' }))).to.be.empty; const result = await parseSetup(toContent({ foo: 'foo', bar: 'bar' })); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Property bar is not allowed.'); }); it('unevaluatedProperties with adjacent patternProperties', async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', type: 'object', patternProperties: { '^foo': { type: 'string' }, }, unevaluatedProperties: false, }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); (0, chai_1.expect)(await parseSetup(toContent({ foo: 'foo' }))).to.be.empty; const result = await parseSetup(toContent({ foo: 'foo', bar: 'bar' })); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Property bar is not allowed.'); }); it('unevaluatedProperties with adjacent additionalProperties', async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', type: 'object', properties: { foo: { type: 'string' }, }, additionalProperties: true, unevaluatedProperties: false, }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); (0, chai_1.expect)(await parseSetup(toContent({ foo: 'foo' }))).to.be.empty; (0, chai_1.expect)(await parseSetup(toContent({ foo: 'foo', bar: 'bar' }))).to.be.empty; }); it('unevaluatedProperties with nested properties', async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', type: 'object', properties: { foo: { type: 'string' }, }, allOf: [ { properties: { bar: { type: 'string' }, }, }, ], unevaluatedProperties: false, }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); (0, chai_1.expect)(await parseSetup(toContent({ foo: 'foo', bar: 'bar' }))).to.be.empty; const result = await parseSetup(toContent({ foo: 'foo', bar: 'bar', baz: 'baz' })); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Property baz is not allowed.'); }); it('unevaluatedProperties with nested patternProperties', async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', type: 'object', properties: { foo: { type: 'string' }, }, allOf: [ { patternProperties: { '^bar': { type: 'string' }, }, }, ], unevaluatedProperties: false, }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); (0, chai_1.expect)(await parseSetup(toContent({ foo: 'foo', bar: 'bar' }))).to.be.empty; const result = await parseSetup(toContent({ foo: 'foo', bar: 'bar', baz: 'baz' })); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Property baz is not allowed.'); }); it('unevaluatedProperties with nested additionalProperties', async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', type: 'object', properties: { foo: { type: 'string' }, }, allOf: [ { additionalProperties: true, }, ], unevaluatedProperties: false, }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); (0, chai_1.expect)(await parseSetup(toContent({ foo: 'foo' }))).to.be.empty; (0, chai_1.expect)(await parseSetup(toContent({ foo: 'foo', bar: 'bar' }))).to.be.empty; }); it('unevaluatedProperties with nested unevaluatedProperties', async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', type: 'object', properties: { foo: { type: 'string' }, }, allOf: [ { unevaluatedProperties: true, }, ], unevaluatedProperties: { type: 'string', maxLength: 2, }, }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); (0, chai_1.expect)(await parseSetup(toContent({ foo: 'foo' }))).to.be.empty; (0, chai_1.expect)(await parseSetup(toContent({ foo: 'foo', bar: 'bar' }))).to.be.empty; }); it('unevaluatedProperties with anyOf', async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', type: 'object', properties: { foo: { type: 'string' }, }, anyOf: [ { properties: { bar: { const: 'bar' }, }, required: ['bar'], }, { properties: { baz: { const: 'baz' }, }, required: ['baz'], }, { properties: { quux: { const: 'quux' }, }, required: ['quux'], }, ], unevaluatedProperties: false, }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); (0, chai_1.expect)(await parseSetup(toContent({ foo: 'foo', bar: 'bar' }))).to.be.empty; let result = await parseSetup(toContent({ foo: 'foo', bar: 'bar', baz: 'not-baz' })); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Property baz is not allowed.'); (0, chai_1.expect)(await parseSetup(toContent({ foo: 'foo', bar: 'bar', baz: 'baz' }))).to.be.empty; result = await parseSetup(toContent({ foo: 'foo', bar: 'bar', baz: 'baz', quux: 'not-quux' })); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Property quux is not allowed.'); }); it('unevaluatedProperties with oneOf', async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', type: 'object', properties: { foo: { type: 'string' }, }, oneOf: [ { properties: { bar: { const: 'bar' }, }, required: ['bar'], }, { properties: { baz: { const: 'baz' }, }, required: ['baz'], }, ], unevaluatedProperties: false, }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); (0, chai_1.expect)(await parseSetup(toContent({ foo: 'foo', bar: 'bar' }))).to.be.empty; const result = await parseSetup(toContent({ foo: 'foo', bar: 'bar', quux: 'quux' })); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Property quux is not allowed.'); }); it('unevaluatedProperties with not', async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', type: 'object', properties: { foo: { type: 'string' }, }, not: { not: { properties: { bar: { const: 'bar' }, }, required: ['bar'], }, }, unevaluatedProperties: false, }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); const result = await parseSetup(toContent({ foo: 'foo', bar: 'bar' })); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Property bar is not allowed.'); }); it('unevaluatedProperties with if/then/else', async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', type: 'object', if: { properties: { foo: { const: 'then' }, }, required: ['foo'], }, then: { properties: { bar: { type: 'string' }, }, required: ['bar'], }, else: { properties: { baz: { type: 'string' }, }, required: ['baz'], }, unevaluatedProperties: false, }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); (0, chai_1.expect)(await parseSetup(toContent({ foo: 'then', bar: 'bar' }))).to.be.empty; let result = await parseSetup(toContent({ foo: 'then', bar: 'bar', baz: 'baz' })); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Property baz is not allowed.'); (0, chai_1.expect)(await parseSetup(toContent({ baz: 'baz' }))).to.be.empty; result = await parseSetup(toContent({ foo: 'else', baz: 'baz' })); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Property foo is not allowed.'); }); it('unevaluatedProperties with if/then/else, then not defined', async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', type: 'object', if: { properties: { foo: { const: 'then' }, }, required: ['foo'], }, else: { properties: { baz: { type: 'string' }, }, required: ['baz'], }, unevaluatedProperties: false, }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); (0, chai_1.expect)(await parseSetup(toContent({ foo: 'then' }))).to.be.empty; let result = await parseSetup(toContent({ foo: 'then', bar: 'bar' })); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Property bar is not allowed.'); (0, chai_1.expect)(await parseSetup(toContent({ baz: 'baz' }))).to.be.empty; result = await parseSetup(toContent({ foo: 'else', baz: 'baz' })); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Property foo is not allowed.'); }); it('unevaluatedProperties with if/then/else, else not defined', async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', type: 'object', if: { properties: { foo: { const: 'then' }, }, required: ['foo'], }, then: { properties: { bar: { type: 'string' }, }, required: ['bar'], }, unevaluatedProperties: false, }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); (0, chai_1.expect)(await parseSetup(toContent({ foo: 'then', bar: 'bar' }))).to.be.empty; let result = await parseSetup(toContent({ foo: 'then', bar: 'bar', baz: 'baz' })); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Property baz is not allowed.'); result = await parseSetup(toContent({ baz: 'baz' })); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Property baz is not allowed.'); result = await parseSetup(toContent({ foo: 'else', baz: 'baz' })); (0, chai_1.expect)(result).to.have.length(2); const messages = result.map((entry) => entry.message).join(' | '); (0, chai_1.expect)(messages).to.include('Property foo is not allowed.'); (0, chai_1.expect)(messages).to.include('Property baz is not allowed.'); }); it('unevaluatedProperties with dependentSchemas', async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', type: 'object', properties: { foo: { type: 'string' }, }, dependentSchemas: { foo: { properties: { bar: { const: 'bar' }, }, required: ['bar'], }, }, unevaluatedProperties: false, }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); (0, chai_1.expect)(await parseSetup(toContent({ foo: 'foo', bar: 'bar' }))).to.be.empty; const result = await parseSetup(toContent({ bar: 'bar' })); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Property bar is not allowed.'); }); it('unevaluatedProperties with boolean schemas', async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', type: 'object', properties: { foo: { type: 'string' }, }, allOf: [true], unevaluatedProperties: false, }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); (0, chai_1.expect)(await parseSetup(toContent({ foo: 'foo' }))).to.be.empty; const result = await parseSetup(toContent({ bar: 'bar' })); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Property bar is not allowed.'); }); it('unevaluatedProperties with $ref', async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', type: 'object', $ref: '#/$defs/bar', properties: { foo: { type: 'string' }, }, unevaluatedProperties: false, $defs: { bar: { properties: { bar: { type: 'string' }, }, }, }, }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); (0, chai_1.expect)(await parseSetup(toContent({ foo: 'foo', bar: 'bar' }))).to.be.empty; const result = await parseSetup(toContent({ foo: 'foo', bar: 'bar', baz: 'baz' })); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Property baz is not allowed.'); }); it('unevaluatedProperties before $ref', async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', type: 'object', unevaluatedProperties: false, properties: { foo: { type: 'string' }, }, $ref: '#/$defs/bar', $defs: { bar: { properties: { bar: { type: 'string' }, }, }, }, }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); (0, chai_1.expect)(await parseSetup(toContent({ foo: 'foo', bar: 'bar' }))).to.be.empty; const result = await parseSetup(toContent({ foo: 'foo', bar: 'bar', baz: 'baz' })); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Property baz is not allowed.'); }); it('unevaluatedProperties with $recursiveRef', async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', $id: 'https://example.com/unevaluated-properties-with-recursive-ref/extended-tree', $recursiveAnchor: true, $ref: './tree', properties: { name: { type: 'string' }, }, $defs: { tree: { $id: './tree', $recursiveAnchor: true, type: 'object', properties: { node: true, branches: { $comment: "unevaluatedProperties comes first so it's more likely to bugs errors with implementations that are sensitive to keyword ordering", unevaluatedProperties: false, $recursiveRef: '#', }, }, required: ['node'], }, }, }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); (0, chai_1.expect)(await parseSetup(toContent({ name: 'a', node: 1, branches: { name: 'b', node: 2, }, }))).to.be.empty; const result = await parseSetup(toContent({ name: 'a', node: 1, branches: { foo: 'b', node: 2, }, })); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Property foo is not allowed.'); }); it("unevaluatedProperties can't see inside cousins", async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', allOf: [ { properties: { foo: true, }, }, { unevaluatedProperties: false, }, ], }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); const result = await parseSetup(toContent({ foo: 1 })); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Property foo is not allowed.'); }); it("unevaluatedProperties can't see inside cousins (reverse order)", async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', allOf: [ { unevaluatedProperties: false, }, { properties: { foo: true, }, }, ], }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); const result = await parseSetup(toContent({ foo: 1 })); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Property foo is not allowed.'); }); it('nested unevaluatedProperties, outer false, inner true, properties outside', async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', type: 'object', properties: { foo: { type: 'string' }, }, allOf: [ { unevaluatedProperties: true, }, ], unevaluatedProperties: false, }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); (0, chai_1.expect)(await parseSetup(toContent({ foo: 'foo' }))).to.be.empty; (0, chai_1.expect)(await parseSetup(toContent({ foo: 'foo', bar: 'bar' }))).to.be.empty; }); it('nested unevaluatedProperties, outer false, inner true, properties inside', async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', type: 'object', allOf: [ { properties: { foo: { type: 'string' }, }, unevaluatedProperties: true, }, ], unevaluatedProperties: false, }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); (0, chai_1.expect)(await parseSetup(toContent({ foo: 'foo' }))).to.be.empty; (0, chai_1.expect)(await parseSetup(toContent({ foo: 'foo', bar: 'bar' }))).to.be.empty; }); it('nested unevaluatedProperties, outer true, inner false, properties outside', async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', type: 'object', properties: { foo: { type: 'string' }, }, allOf: [ { unevaluatedProperties: false, }, ], unevaluatedProperties: true, }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); const result1 = await parseSetup(toContent({ foo: 'foo' })); (0, chai_1.expect)(result1).to.have.length(1); (0, chai_1.expect)(result1[0].message).to.include('Property foo is not allowed.'); const result2 = await parseSetup(toContent({ foo: 'foo', bar: 'bar' })); (0, chai_1.expect)(result2).to.have.length(2); const messages = result2.map((entry) => entry.message).join(' | '); (0, chai_1.expect)(messages).to.include('Property foo is not allowed.'); (0, chai_1.expect)(messages).to.include('Property bar is not allowed.'); }); it('nested unevaluatedProperties, outer true, inner false, properties inside', async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', type: 'object', allOf: [ { properties: { foo: { type: 'string' }, }, unevaluatedProperties: false, }, ], unevaluatedProperties: true, }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); (0, chai_1.expect)(await parseSetup(toContent({ foo: 'foo' }))).to.be.empty; const result = await parseSetup(toContent({ foo: 'foo', bar: 'bar' })); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Property bar is not allowed.'); }); it('cousin unevaluatedProperties, true and false, true with properties', async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', type: 'object', allOf: [ { properties: { foo: { type: 'string' }, }, unevaluatedProperties: true, }, { unevaluatedProperties: false, }, ], }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); const result1 = await parseSetup(toContent({ foo: 'foo' })); (0, chai_1.expect)(result1).to.have.length(1); (0, chai_1.expect)(result1[0].message).to.include('Property foo is not allowed.'); const result2 = await parseSetup(toContent({ foo: 'foo', bar: 'bar' })); (0, chai_1.expect)(result2).to.have.length(2); const messages = result2.map((entry) => entry.message).join(' | '); (0, chai_1.expect)(messages).to.include('Property foo is not allowed.'); (0, chai_1.expect)(messages).to.include('Property bar is not allowed.'); }); it('cousin unevaluatedProperties, true and false, false with properties', async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', type: 'object', allOf: [ { unevaluatedProperties: true, }, { properties: { foo: { type: 'string' }, }, unevaluatedProperties: false, }, ], }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); (0, chai_1.expect)(await parseSetup(toContent({ foo: 'foo' }))).to.be.empty; const result = await parseSetup(toContent({ foo: 'foo', bar: 'bar' })); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Property bar is not allowed.'); }); it('property is evaluated in an uncle schema to unevaluatedProperties', async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', type: 'object', properties: { foo: { type: 'object', properties: { bar: { type: 'string', }, }, unevaluatedProperties: false, }, }, anyOf: [ { properties: { foo: { properties: { faz: { type: 'string', }, }, }, }, }, ], }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); (0, chai_1.expect)(await parseSetup(toContent({ foo: { bar: 'test' } }))).to.be.empty; const result = await parseSetup(toContent({ foo: { bar: 'test', faz: 'test' } })); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Property faz is not allowed.'); }); describe('in-place applicator siblings, allOf has unevaluated', () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', type: 'object', allOf: [ { properties: { foo: true, }, unevaluatedProperties: false, }, ], anyOf: [ { properties: { bar: true, }, }, ], }; beforeEach(() => { schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); }); it('base case: both properties present', async () => { const result = await parseSetup(toContent({ foo: 1, bar: 1 })); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Property bar is not allowed.'); }); it('in place applicator siblings, bar is missing', async () => { (0, chai_1.expect)(await parseSetup(toContent({ foo: 1 }))).to.be.empty; }); it('in place applicator siblings, foo is missing', async () => { const result = await parseSetup(toContent({ bar: 1 })); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Property bar is not allowed.'); }); }); describe('in-place applicator siblings, anyOf has unevaluated', () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', type: 'object', allOf: [ { properties: { foo: true, }, }, ], anyOf: [ { properties: { bar: true, }, unevaluatedProperties: false, }, ], }; beforeEach(() => { schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); }); it('base case: both properties present', async () => { const result = await parseSetup(toContent({ foo: 1, bar: 1 })); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Property foo is not allowed.'); }); it('in place applicator siblings, bar is missing', async () => { const result = await parseSetup(toContent({ foo: 1 })); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Property foo is not allowed.'); }); it('in place applicator siblings, foo is missing', async () => { (0, chai_1.expect)(await parseSetup(toContent({ bar: 1 }))).to.be.empty; }); }); describe('unevaluatedProperties + single cyclic ref', () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', type: 'object', properties: { x: { $ref: '#' }, }, unevaluatedProperties: false, }; beforeEach(() => { schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); }); it('Empty is valid', async () => { (0, chai_1.expect)(await parseSetup(toContent({}))).to.be.empty; }); it('Single is valid', async () => { (0, chai_1.expect)(await parseSetup(toContent({ x: {} }))).to.be.empty; }); it('Unevaluated on 1st level is invalid', async () => { const result = await parseSetup(toContent({ x: {}, y: {} })); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Property y is not allowed.'); }); it('Nested is valid', async () => { (0, chai_1.expect)(await parseSetup(toContent({ x: { x: {} } }))).to.be.empty; }); it('Unevaluated on 2nd level is invalid', async () => { const result = await parseSetup(toContent({ x: { x: {}, y: {} } })); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Property y is not allowed.'); }); it('Deep nested is valid', async () => { (0, chai_1.expect)(await parseSetup(toContent({ x: { x: { x: {} } } }))).to.be.empty; }); it('Unevaluated on 3rd level is invalid', async () => { const result = await parseSetup(toContent({ x: { x: { x: {}, y: {} } } })); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Property y is not allowed.'); }); }); describe('unevaluatedProperties + ref inside allOf / oneOf', () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', $defs: { one: { properties: { a: true }, }, two: { required: ['x'], properties: { x: true }, }, }, allOf: [ { $ref: '#/$defs/one' }, { properties: { b: true } }, { oneOf: [ { $ref: '#/$defs/two' }, { required: ['y'], properties: { y: true }, }, ], }, ], unevaluatedProperties: false, }; beforeEach(() => { schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); }); it('Empty is invalid (no x or y)', async () => { const result = await parseSetup(toContent({})); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Missing property'); (0, chai_1.expect)(result[0].message).to.include('x'); }); it('a and b are invalid (no x or y)', async () => { const result = await parseSetup(toContent({ a: 1, b: 1 })); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Missing property'); (0, chai_1.expect)(result[0].message).to.include('x'); }); it('x and y are invalid', async () => { const result = await parseSetup(toContent({ x: 1, y: 1 })); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Matches multiple schemas when only one must validate.'); }); it('a and x are valid', async () => { (0, chai_1.expect)(await parseSetup(toContent({ a: 1, x: 1 }))).to.be.empty; }); it('a and y are valid', async () => { (0, chai_1.expect)(await parseSetup(toContent({ a: 1, y: 1 }))).to.be.empty; }); it('a and b and x are valid', async () => { (0, chai_1.expect)(await parseSetup(toContent({ a: 1, b: 1, x: 1 }))).to.be.empty; }); it('a and b and y are valid', async () => { (0, chai_1.expect)(await parseSetup(toContent({ a: 1, b: 1, y: 1 }))).to.be.empty; }); it('a and b and x and y are invalid', async () => { const result = await parseSetup(toContent({ a: 1, b: 1, x: 1, y: 1 })); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Matches multiple schemas when only one must validate.'); }); }); describe('dynamic evalation inside nested refs', () => { const schema = { $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, }; beforeEach(() => { schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); }); it('Empty is invalid', async () => { const result = await parseSetup(toContent({})); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Missing property'); }); it('a is valid', async () => { (0, chai_1.expect)(await parseSetup(toContent({ a: 1 }))).to.be.empty; }); it('b is valid', async () => { (0, chai_1.expect)(await parseSetup(toContent({ b: 1 }))).to.be.empty; }); it('c is valid', async () => { (0, chai_1.expect)(await parseSetup(toContent({ c: 1 }))).to.be.empty; }); it('d is valid', async () => { (0, chai_1.expect)(await parseSetup(toContent({ d: 1 }))).to.be.empty; }); it('a + b is invalid', async () => { const result = await parseSetup(toContent({ a: 1, b: 1 })); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Matches multiple schemas when only one must validate.'); }); it('a + c is invalid', async () => { const result = await parseSetup(toContent({ a: 1, c: 1 })); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Matches multiple schemas when only one must validate.'); }); it('a + d is invalid', async () => { const result = await parseSetup(toContent({ a: 1, d: 1 })); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Matches multiple schemas when only one must validate.'); }); it('b + c is invalid', async () => { const result = await parseSetup(toContent({ b: 1, c: 1 })); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Matches multiple schemas when only one must validate.'); }); it('b + d is invalid', async () => { const result = await parseSetup(toContent({ b: 1, d: 1 })); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Matches multiple schemas when only one must validate.'); }); it('c + d is invalid', async () => { const result = await parseSetup(toContent({ c: 1, d: 1 })); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Matches multiple schemas when only one must validate.'); }); it('xx is valid', async () => { (0, chai_1.expect)(await parseSetup(toContent({ xx: 1 }))).to.be.empty; }); it('xx + foox is valid', async () => { (0, chai_1.expect)(await parseSetup(toContent({ xx: 1, foox: 1 }))).to.be.empty; }); it('xx + foo is invalid', async () => { const result = await parseSetup(toContent({ xx: 1, foo: 1 })); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Property foo is not allowed.'); }); it('xx + a is invalid', async () => { const result = await parseSetup(toContent({ xx: 1, a: 1 })); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Matches multiple schemas when only one must validate.'); }); it('xx + b is invalid', async () => { const result = await parseSetup(toContent({ xx: 1, b: 1 })); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Matches multiple schemas when only one must validate.'); }); it('xx + c is invalid', async () => { const result = await parseSetup(toContent({ xx: 1, c: 1 })); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Matches multiple schemas when only one must validate.'); }); it('xx + d is invalid', async () => { const result = await parseSetup(toContent({ xx: 1, d: 1 })); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Matches multiple schemas when only one must validate.'); }); it('all is valid', async () => { (0, chai_1.expect)(await parseSetup(toContent({ all: 1 }))).to.be.empty; }); it('all + foo is valid', async () => { (0, chai_1.expect)(await parseSetup(toContent({ all: 1, foo: 1 }))).to.be.empty; }); it('all + a is invalid', async () => { const result = await parseSetup(toContent({ all: 1, a: 1 })); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Matches multiple schemas when only one must validate.'); }); }); it('non-object instances are valid', async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', unevaluatedProperties: false, }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); (0, chai_1.expect)(await parseSetup(toContent(true))).to.be.empty; (0, chai_1.expect)(await parseSetup(toContent(123))).to.be.empty; (0, chai_1.expect)(await parseSetup(toContent(1.0))).to.be.empty; (0, chai_1.expect)(await parseSetup(toContent([]))).to.be.empty; (0, chai_1.expect)(await parseSetup(toContent('foo'))).to.be.empty; (0, chai_1.expect)(await parseSetup(toContent(null))).to.be.empty; }); it('unevaluatedProperties with null valued instance properties', async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', unevaluatedProperties: { type: 'null', }, }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); (0, chai_1.expect)(await parseSetup(toContent({ foo: null }))).to.be.empty; }); it('unevaluatedProperties not affected by propertyNames', async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', propertyNames: { maxLength: 1 }, unevaluatedProperties: { type: 'number', }, }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); (0, chai_1.expect)(await parseSetup(toContent({ a: 1 }))).to.be.empty; const result = await parseSetup(toContent({ a: 'b' })); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Incorrect type'); (0, chai_1.expect)(result[0].message).to.include('number'); }); it('unevaluatedProperties can see annotations from if without then and else', async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', if: { patternProperties: { foo: { type: 'string', }, }, }, unevaluatedProperties: false, }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); (0, chai_1.expect)(await parseSetup(toContent({ foo: 'a' }))).to.be.empty; const result = await parseSetup(toContent({ bar: 'a' })); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Property bar is not allowed.'); }); it('dependentSchemas with unevaluatedProperties', async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', properties: { foo2: {} }, dependentSchemas: { foo: {}, foo2: { properties: { bar: {}, }, }, }, unevaluatedProperties: false, }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); const result1 = await parseSetup(toContent({ foo: '' })); (0, chai_1.expect)(result1).to.have.length(1); (0, chai_1.expect)(result1[0].message).to.include('Property foo is not allowed.'); const result2 = await parseSetup(toContent({ bar: '' })); (0, chai_1.expect)(result2).to.have.length(1); (0, chai_1.expect)(result2[0].message).to.include('Property bar is not allowed.'); (0, chai_1.expect)(await parseSetup(toContent({ foo2: '', bar: '' }))).to.be.empty; }); it('Evaluated properties collection needs to consider instance location', async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', properties: { foo: { properties: { bar: { type: 'string' }, }, }, }, unevaluatedProperties: false, }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); const result = await parseSetup(toContent({ foo: { bar: 'foo' }, bar: 'bar' })); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Property bar is not allowed.'); }); }); describe('keyword: unevaluatedItems', () => { it('unevaluatedItems true', async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', unevaluatedItems: true, }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); (0, chai_1.expect)(await parseSetup(`[]`)).to.be.empty; (0, chai_1.expect)(await parseSetup(`- foo`)).to.be.empty; }); it('unevaluatedItems false', async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', unevaluatedItems: false, }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); (0, chai_1.expect)(await parseSetup(`[]`)).to.be.empty; const content = `- foo`; const result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Array has too many items according to schema. Expected 0 or fewer.'); }); it('unevaluatedItems as schema', async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', unevaluatedItems: { type: 'string' }, }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); (0, chai_1.expect)(await parseSetup(`[]`)).to.be.empty; (0, chai_1.expect)(await parseSetup(`- foo`)).to.be.empty; const result = await parseSetup(`- 42`); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Incorrect type. Expected'); (0, chai_1.expect)(result[0].message).to.include('string'); }); it('unevaluatedItems with uniform items', async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', items: { type: 'string' }, unevaluatedItems: false, }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); const content = `- foo\n- bar`; const result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; }); it('unevaluatedItems with tuple', async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', items: [{ type: 'string' }], unevaluatedItems: false, }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); (0, chai_1.expect)(await parseSetup(`- foo`)).to.be.empty; const content = `- foo\n- bar`; const result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Array has too many items according to schema. Expected 1 or fewer.'); }); it('unevaluatedItems with items and additionalItems', async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', items: [{ type: 'string' }], additionalItems: true, unevaluatedItems: false, }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); const content = `- foo\n- 42`; const result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; }); it('unevaluatedItems with ignored additionalItems', async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', additionalItems: { type: 'number' }, unevaluatedItems: { type: 'string' }, }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); const content = `- foo\n- 1`; const result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Incorrect type. Expected'); (0, chai_1.expect)(result[0].message).to.include('string'); (0, chai_1.expect)(await parseSetup(`- foo\n- bar\n- baz`)).to.be.empty; }); it('unevaluatedItems with ignored applicator additionalItems', async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', allOf: [{ additionalItems: { type: 'number' } }], unevaluatedItems: { type: 'string' }, }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); const content = `- foo\n- 1`; const result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Incorrect type. Expected'); (0, chai_1.expect)(result[0].message).to.include('string'); (0, chai_1.expect)(await parseSetup(`- foo\n- bar\n- baz`)).to.be.empty; }); it('unevaluatedItems with nested tuple', async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', items: [{ type: 'string' }], allOf: [ { items: [true, { type: 'number' }], }, ], unevaluatedItems: false, }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); (0, chai_1.expect)(await parseSetup(`- foo\n- 42`)).to.be.empty; const content = `- foo\n- 42\n- true`; const result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Array has too many items according to schema. Expected 2 or fewer.'); }); it('unevaluatedItems with nested items', async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', unevaluatedItems: { type: 'boolean' }, anyOf: [{ items: { type: 'string' } }, true], }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); const content1 = `- true\n- false`; const result1 = await parseSetup(content1); (0, chai_1.expect)(result1).to.be.empty; const content2 = `- "yes"\n- false`; const result2 = await parseSetup(content2); (0, chai_1.expect)(result2).to.have.length(1); (0, chai_1.expect)(result2[0].message).to.include('Incorrect type.'); const content3 = `- "yes"\n- "no"`; const result3 = await parseSetup(content3); (0, chai_1.expect)(result3).to.be.empty; }); it('unevaluatedItems with nested items and additionalItems', async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', allOf: [ { items: [{ type: 'string' }], additionalItems: true, }, ], unevaluatedItems: false, }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); (0, chai_1.expect)(await parseSetup(`- foo`)).to.be.empty; (0, chai_1.expect)(await parseSetup(`- foo\n- 42\n- true`)).to.be.empty; }); it('unevaluatedItems with nested unevaluatedItems', async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', allOf: [ { items: [{ type: 'string' }], }, { unevaluatedItems: true }, ], unevaluatedItems: false, }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); (0, chai_1.expect)(await parseSetup(`- foo`)).to.be.empty; const content = `- foo\n- 42\n- true`; const result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; }); it('unevaluatedItems with anyOf', async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', items: [{ const: 'foo' }], anyOf: [ { items: [true, { const: 'bar' }], }, { items: [true, true, { const: 'baz' }], }, ], unevaluatedItems: false, }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); const content1 = `- foo\n- bar`; const result1 = await parseSetup(content1); (0, chai_1.expect)(result1).to.be.empty; const content2 = `- foo\n- bar\n- 42`; const result2 = await parseSetup(content2); (0, chai_1.expect)(result2).to.have.length(1); (0, chai_1.expect)(result2[0].message).to.include('Array has too many items according to schema. Expected 2 or fewer.'); const content3 = `- foo\n- bar\n- baz`; const result3 = await parseSetup(content3); (0, chai_1.expect)(result3).to.be.empty; const content4 = `- foo\n- bar\n- baz\n- 42`; const result4 = await parseSetup(content4); (0, chai_1.expect)(result4).to.have.length(1); (0, chai_1.expect)(result4[0].message).to.include('Array has too many items according to schema. Expected 3 or fewer.'); }); it('unevaluatedItems with oneOf', async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', items: [{ const: 'foo' }], oneOf: [ { items: [true, { const: 'bar' }], }, { items: [true, { const: 'baz' }], }, ], unevaluatedItems: false, }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); (0, chai_1.expect)(await parseSetup(`- foo\n- bar`)).to.be.empty; const content = `- foo\n- bar\n- 42`; const result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Array has too many items according to schema. Expected 2 or fewer.'); }); it('unevaluatedItems with not', async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', items: [{ const: 'foo' }], not: { not: { items: [true, { const: 'bar' }], }, }, unevaluatedItems: false, }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); const content = `- foo\n- bar`; const result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Array has too many items according to schema. Expected 1 or fewer.'); }); it('unevaluatedItems with if/then/else', async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', items: [{ const: 'foo' }], if: { items: [true, { const: 'bar' }], }, then: { items: [true, true, { const: 'then' }], }, else: { items: [true, true, true, { const: 'else' }], }, unevaluatedItems: false, }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); const result1 = await parseSetup(`- foo\n- bar\n- then`); (0, chai_1.expect)(result1).to.be.empty; const result2 = await parseSetup(`- foo\n- bar\n- then\n- else`); (0, chai_1.expect)(result2).to.have.length(1); (0, chai_1.expect)(result2[0].message).to.include('Array has too many items according to schema. Expected 3 or fewer.'); const result3 = await parseSetup(`- foo\n- 42\n- 42\n- else`); (0, chai_1.expect)(result3).to.be.empty; const result = await parseSetup(`- foo\n- 42\n- 42\n- else\n- 42`); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Array has too many items according to schema. Expected 4 or fewer.'); }); it('unevaluatedItems with boolean schemas', async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', allOf: [true], unevaluatedItems: false, }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); (0, chai_1.expect)(await parseSetup(`[]`)).to.be.empty; const content = `- foo`; const result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Array has too many items according to schema. Expected 0 or fewer.'); }); it('unevaluatedItems with $ref', async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', $ref: '#/$defs/bar', items: [{ type: 'string' }], unevaluatedItems: false, $defs: { bar: { items: [true, { type: 'string' }], }, }, }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); (0, chai_1.expect)(await parseSetup(`- foo\n- bar`)).to.be.empty; const content = `- foo\n- bar\n- baz`; const result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Array has too many items according to schema. Expected 2 or fewer.'); }); it('unevaluatedItems before $ref', async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', unevaluatedItems: false, items: [{ type: 'string' }], $ref: '#/$defs/bar', $defs: { bar: { items: [true, { type: 'string' }], }, }, }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); (0, chai_1.expect)(await parseSetup(`- foo\n- bar`)).to.be.empty; const content = `- foo\n- bar\n- baz`; const result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Array has too many items according to schema. Expected 2 or fewer.'); }); it('unevaluatedItems with $recursiveRef', async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', $id: 'https://example.com/unevaluated-items-with-recursive-ref/extended-tree', $recursiveAnchor: true, $ref: './tree', items: [true, true, { type: 'string' }], $defs: { tree: { $id: './tree', $recursiveAnchor: true, type: 'array', items: [ { type: 'number' }, { unevaluatedItems: false, $recursiveRef: '#', }, ], }, }, }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); (0, chai_1.expect)(await parseSetup(`- 1\n- - 2\n - []\n - b\n- a`)).to.be.empty; const content = `- 1\n- - 2\n - []\n - b\n - too many\n- a`; const result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Array has too many items according to schema. Expected 3 or fewer.'); }); it("unevaluatedItems can't see inside cousins", async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', allOf: [{ items: [true] }, { unevaluatedItems: false }], }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); const content = `- 1`; const result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Array has too many items according to schema. Expected 0 or fewer.'); }); it('item is evaluated in an uncle schema to unevaluatedItems', async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', properties: { foo: { items: [{ type: 'string' }], unevaluatedItems: false, }, }, anyOf: [ { properties: { foo: { items: [true, { type: 'string' }], }, }, }, ], }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); (0, chai_1.expect)(await parseSetup(`foo:\n - test`)).to.be.empty; const content = `foo:\n - test\n - test`; const result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Array has too many items according to schema. Expected 1 or fewer.'); }); describe('non-array instances are valid', () => { it('', async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', unevaluatedItems: false, }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); const content = `true`; const result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; }); it('ignores integers', async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', unevaluatedItems: false, }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); const content = `123`; const result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; }); it('ignores floats', async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', unevaluatedItems: false, }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); const content = `1.0`; const result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; }); it('ignores objects', async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', unevaluatedItems: false, }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); const content = `{}`; const result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; }); it('ignores strings', async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', unevaluatedItems: false, }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); const content = `foo`; const result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; }); it('ignores null', async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', unevaluatedItems: false, }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); const content = `null`; const result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; }); }); it('unevaluatedItems with null instance elements', async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', unevaluatedItems: { type: 'null', }, }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); const content = `- null`; const result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; }); it('unevaluatedItems can see annotations from if without then and else', async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', if: { items: [{ const: 'a' }], }, unevaluatedItems: false, }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); (0, chai_1.expect)(await parseSetup(`- a`)).to.be.empty; const content = `- b`; const result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Array has too many items according to schema. Expected 0 or fewer.'); }); it('Evaluated items collection needs to consider instance location', async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', items: [ { items: [true, { type: 'string' }], }, ], unevaluatedItems: false, }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); const content = `- - foo\n - bar\n- bar`; const result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Array has too many items according to schema. Expected 1 or fewer.'); }); }); describe('keyword: contains + minContains/maxContains', () => { it('minContains fails when too few items match contains subschema', async () => { schemaProvider.addSchema(testHelper_1.SCHEMA_ID, { $schema: 'https://json-schema.org/draft/2019-09/schema', type: 'array', contains: { type: 'object', properties: { kind: { const: 'ok' }, id: { type: 'number' }, }, required: ['kind', 'id'], }, minContains: 2, }); const content = `- kind: ok\n id: 1\n- kind: ok\n id: "2"\n- kind: nope\n id: 3`; const result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Array has too few items matching'); (0, chai_1.expect)(result[0].message).to.include('contains'); (0, chai_1.expect)(result[0].message).to.include('Expected 2 or more.'); }); it('maxContains fails when too many items match contains subschema', async () => { schemaProvider.addSchema(testHelper_1.SCHEMA_ID, { $schema: 'https://json-schema.org/draft/2019-09/schema', type: 'array', contains: { type: 'object', properties: { kind: { const: 'ok' }, id: { type: 'number' }, }, required: ['kind', 'id'], }, maxContains: 3, }); const content = `- kind: ok\n id: 1\n- kind: ok\n id: 2\n- kind: ok\n id: 3\n- kind: ok\n id: 4`; const result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Array has too many items matching'); (0, chai_1.expect)(result[0].message).to.include('contains'); (0, chai_1.expect)(result[0].message).to.include('Expected 3 or fewer.'); }); it('minContains/maxContains passes when match count is within bounds', async () => { schemaProvider.addSchema(testHelper_1.SCHEMA_ID, { $schema: 'https://json-schema.org/draft/2019-09/schema', type: 'array', contains: { type: 'object', properties: { kind: { const: 'ok' }, id: { type: 'number' }, }, required: ['kind', 'id'], }, minContains: 2, maxContains: 3, }); const content = ` - kind: ok id: 1 - kind: ok id: 2 - kind: nope id: 3 `; const result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; }); it('contains matching is based on subschema, not just item type', async () => { schemaProvider.addSchema(testHelper_1.SCHEMA_ID, { $schema: 'https://json-schema.org/draft/2019-09/schema', type: 'array', contains: { type: 'object', required: ['tag'], properties: { tag: { const: 'match' }, }, }, minContains: 1, }); const okYaml = ` - tag: other - tag: match - nope: 1 `; let result = await parseSetup(okYaml); (0, chai_1.expect)(result).to.be.empty; const badYaml = ` - tag: other - tag: nope - nope: 1`; result = await parseSetup(badYaml); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Array has too few items matching'); (0, chai_1.expect)(result[0].message).to.include('contains'); (0, chai_1.expect)(result[0].message).to.include('Expected 1 or more.'); }); }); describe('keyword: dependentRequired', () => { beforeEach(() => { schemaProvider.addSchema(testHelper_1.SCHEMA_ID, { $schema: 'https://json-schema.org/draft/2019-09/schema', type: 'object', properties: { billing_address: { type: 'string' }, credit_card: { type: 'string' }, }, dependentRequired: { billing_address: ['credit_card'], }, }); }); it('requires dependent properties when the trigger property is present', async () => { const content = `billing_address: "123 King St"`; const result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Object is missing property credit_card required by property billing_address.'); }); it('passes when required dependent properties are present', async () => { const content = `billing_address: "123 King St"\ncredit_card: "4111-1111"`; const result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; }); }); describe('keyword: dependentSchemas', () => { it('does not apply when the trigger property is absent', async () => { schemaProvider.addSchema(testHelper_1.SCHEMA_ID, { $schema: 'https://json-schema.org/draft/2019-09/schema', type: 'object', properties: { kind: { type: 'string' }, port: { type: 'number' }, }, dependentSchemas: { kind: { required: ['port'] }, }, }); const content = `port: 8080`; const result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; }); it('applies dependent schema when the trigger property is present', async () => { schemaProvider.addSchema(testHelper_1.SCHEMA_ID, { $schema: 'https://json-schema.org/draft/2019-09/schema', type: 'object', properties: { kind: { type: 'string' }, port: { type: 'number' }, }, dependentSchemas: { kind: { required: ['port'] }, }, }); const content = `kind: service`; const result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Missing property'); (0, chai_1.expect)(result[0].message).to.include('port'); }); it('can enforce additional constraints from the dependent schema', async () => { schemaProvider.addSchema(testHelper_1.SCHEMA_ID, { $schema: 'https://json-schema.org/draft/2019-09/schema', type: 'object', properties: { tls: { type: 'boolean' }, port: { type: 'number' }, }, dependentSchemas: { tls: { required: ['port'], properties: { port: { minimum: 1024 }, }, }, }, }); const content = `tls: true\nport: 80`; const result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Value is below the minimum of 1024.'); }); it('applies multiple dependentSchemas when multiple triggers are present', async () => { schemaProvider.addSchema(testHelper_1.SCHEMA_ID, { $schema: 'https://json-schema.org/draft/2019-09/schema', type: 'object', properties: { kind: { type: 'string' }, tls: { type: 'boolean' }, port: { type: 'number' }, }, dependentSchemas: { kind: { required: ['port'] }, tls: { required: ['port'], properties: { port: { minimum: 1024 } }, }, }, }); const content = `kind: service\ntls: true\nport: 80`; const result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Value is below the minimum of 1024.'); }); }); describe('keyword: dependencies (backward compatibility)', () => { describe('property dependencies tests', () => { beforeEach(() => { schemaProvider.addSchema(testHelper_1.SCHEMA_ID, { $schema: 'http://json-schema.org/draft-07/schema#', type: 'object', properties: { credit_card: { type: 'string' }, billing_address: { type: 'string' }, }, dependencies: { credit_card: ['billing_address'], }, }); }); it('requires dependent properties when the trigger property is present', async () => { const content = `credit_card: "4111-1111-1111-1111"`; const result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Object is missing property billing_address required by property credit_card.'); }); it('does not apply when the trigger property is absent', async () => { const content = `billing_address: "123 Main St"`; const result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; }); }); describe('schema dependencies tests', () => { beforeEach(() => { schemaProvider.addSchema(testHelper_1.SCHEMA_ID, { $schema: 'http://json-schema.org/draft-07/schema#', type: 'object', properties: { mode: { type: 'string' }, port: { type: 'number' }, }, dependencies: { mode: { required: ['port'], properties: { port: { minimum: 1024 }, }, }, }, }); }); it('enforces dependent schema constraints when trigger property is present', async () => { const content = `mode: "server"\nport: 80`; const result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Value is below the minimum of 1024.'); }); it('enforces dependent schema required properties when trigger property is present', async () => { const content = `mode: "server"`; const result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Missing property'); (0, chai_1.expect)(result[0].message).to.include('port'); }); it('does not apply the dependent schema when trigger property is absent', async () => { const content = `port: 80`; const result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; }); }); }); describe('$ref resolution should support sibling keywords', () => { it('should apply sibling keywords next to $ref', async () => { schemaProvider.addSchema(testHelper_1.SCHEMA_ID, { $schema: 'https://json-schema.org/draft/2019-09/schema', type: 'object', properties: { value: { $ref: '#/$defs/A', type: 'number', }, }, $defs: { A: { type: 'string' }, }, }); // both should fail: must be both string and number (0, chai_1.expect)((await parseSetup(`value: hello`)).length).to.be.greaterThan(0); (0, chai_1.expect)((await parseSetup(`value: 1`)).length).to.be.greaterThan(0); }); it('should apply sibling keywords next to $ref (top level)', async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', definitions: { obj1: { type: 'object', properties: { value: { type: 'string' }, }, required: ['value'], }, }, $ref: '#/definitions/obj1', additionalProperties: false, properties: { value: {}, extra: { type: 'number' }, }, }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); const content = `value: hello extra: notANumber unknown: 1 `; const result = await parseSetup(content); (0, chai_1.expect)(result[0].message).to.include('Incorrect type. Expected'); (0, chai_1.expect)(result[0].message).to.include('number'); (0, chai_1.expect)(result[1].message).to.include('Property unknown is not allowed.'); }); }); describe('$id resolution', () => { it('$id inside an enum is not a real identifier (the implementation must not be confused by an $id buried in the enum)', async () => { const root = { $schema: 'https://json-schema.org/draft/2019-09/schema', $defs: { id_in_enum: { enum: [ { $id: 'https://localhost:1234/draft2019-09/id/my_identifier.json', type: 'null', }, ], }, real_id_in_schema: { $id: 'https://localhost:1234/draft2019-09/id/my_identifier.json', type: 'string', }, zzz_id_in_const: { const: { $id: 'https://localhost:1234/draft2019-09/id/my_identifier.json', type: 'null', }, }, }, anyOf: [{ $ref: '#/$defs/id_in_enum' }, { $ref: 'https://localhost:1234/draft2019-09/id/my_identifier.json' }], }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, root); // exact match to enum, and type matches (0, chai_1.expect)(await parseSetup(toContent({ $id: 'https://localhost:1234/draft2019-09/id/my_identifier.json', type: 'null', }))).to.be.empty; // match $ref to $id (0, chai_1.expect)(await parseSetup('a string to match #/$defs/id_in_enum')).to.be.empty; // no match on enum or $ref to $id const result = await parseSetup(`1`); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Value is not accepted. Valid values:'); }); it('should resolve embedded resource $id for relative $ref without external load', async () => { const root = { $schema: 'https://json-schema.org/draft/2019-09/schema', type: 'object', properties: { x: { $ref: 'other.json#bar' }, }, required: ['x'], $defs: { B: { $id: 'other.json', $defs: { X: { $anchor: 'bar', type: 'string', minLength: 2, }, }, }, }, }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, root); const yaml = `x: A`; const result = await parseSetup(yaml); (0, chai_1.expect)(result.some((d) => /Problems loading reference/i.test(d.message))).to.eq(false); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('String is shorter than the minimum length of 2.'); }); it('should handle $id changing base URI for nested $anchor resolution', async () => { const root = { $schema: 'https://json-schema.org/draft/2019-09/schema', type: 'object', properties: { address: { $ref: 'schemas/address.json#USAddress' }, }, $defs: { addressSchema: { $id: 'schemas/address.json', $defs: { us: { $anchor: 'USAddress', type: 'object', properties: { zipCode: { type: 'string', minLength: 5 }, }, required: ['zipCode'], }, }, }, }, }; schemaProvider.addSchema(testHelper_1.SCHEMA_ID, root); const yaml = `address:\n zipCode: "123"`; const result = await parseSetup(yaml); (0, chai_1.expect)(result.some((d) => /Problems loading reference/i.test(d.message))).to.eq(false); (0, chai_1.expect)(result[0].message).to.include('String is shorter than the minimum length of 5.'); }); }); describe('$recursiveAnchor and $recursiveRef resolution', () => { describe('$recursiveRef without $recursiveAnchor works like $ref', () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', properties: { foo: { $recursiveRef: '#' }, }, additionalProperties: false, }; beforeEach(() => { schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); }); it('match', async () => { (0, chai_1.expect)(await parseSetup('foo: false')).to.be.empty; }); it('recursive match', async () => { (0, chai_1.expect)(await parseSetup(`foo: foo: false`)).to.be.empty; }); it('mismatch', async () => { const result = await parseSetup('bar: false'); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Property bar is not allowed.'); }); it('recursive mismatch', async () => { const result = await parseSetup(`foo: bar: false`); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Property bar is not allowed.'); }); }); describe('$recursiveRef without using nesting', () => { const schema = { $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' }], }; beforeEach(() => { schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); }); it('integer matches at the outer level', async () => { (0, chai_1.expect)(await parseSetup(`1`)).to.be.empty; }); it('single level match', async () => { (0, chai_1.expect)(await parseSetup(`foo: hi`)).to.be.empty; }); it('integer does not match as a property value', async () => { const result = await parseSetup(`foo: 1`); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Incorrect type.'); (0, chai_1.expect)(result[0].message).to.include('string | object'); }); it('two levels, properties match with inner definition', async () => { (0, chai_1.expect)(await parseSetup(`foo: bar: hi`)).to.be.empty; }); it('two levels, no match', async () => { const result = await parseSetup(`foo: bar: 1`); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Incorrect type.'); (0, chai_1.expect)(result[0].message).to.include('string | object'); }); }); describe('$recursiveRef with nesting', () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', $id: 'http://localhost:4242/draft2019-09/recursiveRef3/schema.json', $recursiveAnchor: true, $defs: { myobject: { $id: 'myobject.json', $recursiveAnchor: true, anyOf: [ { type: 'string' }, { type: 'object', additionalProperties: { $recursiveRef: '#' }, }, ], }, }, anyOf: [{ type: 'integer' }, { $ref: '#/$defs/myobject' }], }; beforeEach(() => { schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); }); it('integer matches at the outer level', async () => { (0, chai_1.expect)(await parseSetup(`1`)).to.be.empty; }); it('single level match', async () => { (0, chai_1.expect)(await parseSetup(`foo: hi`)).to.be.empty; }); it('integer now matches as a property value', async () => { (0, chai_1.expect)(await parseSetup(`foo: 1`)).to.be.empty; }); it('two levels, properties match with inner definition', async () => { (0, chai_1.expect)(await parseSetup(`foo: bar: hi`)).to.be.empty; }); it('two levels, properties match with $recursiveRef', async () => { (0, chai_1.expect)(await parseSetup(`foo: bar: 1`)).to.be.empty; }); }); describe('$recursiveRef with $recursiveAnchor: false works like $ref', () => { const schema = { $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' }], }; beforeEach(() => { schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); }); it('integer matches at the outer level', async () => { (0, chai_1.expect)(await parseSetup(`1`)).to.be.empty; }); it('single level match', async () => { (0, chai_1.expect)(await parseSetup(`foo: hi`)).to.be.empty; }); it('integer does not match as a property value', async () => { const result = await parseSetup(`foo: 1`); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Incorrect type.'); (0, chai_1.expect)(result[0].message).to.include('string | object'); }); it('two levels, properties match with inner definition', async () => { (0, chai_1.expect)(await parseSetup(`foo: bar: hi`)).to.be.empty; }); it('two levels, integer does not match as a property value', async () => { const result = await parseSetup(`foo: bar: 1`); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Incorrect type.'); (0, chai_1.expect)(result[0].message).to.include('string | object'); }); }); describe('$recursiveRef with no $recursiveAnchor works like $ref', () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', $id: 'http://localhost:4242/draft2019-09/recursiveRef5/schema.json', $defs: { myobject: { $id: 'myobject.json', $recursiveAnchor: false, anyOf: [ { type: 'string' }, { type: 'object', additionalProperties: { $recursiveRef: '#' }, }, ], }, }, anyOf: [{ type: 'integer' }, { $ref: '#/$defs/myobject' }], }; beforeEach(() => { schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); }); it('integer matches at the outer level', async () => { (0, chai_1.expect)(await parseSetup(`1`)).to.be.empty; }); it('single level match', async () => { (0, chai_1.expect)(await parseSetup(`foo: hi`)).to.be.empty; }); it('integer does not match as a property value', async () => { const result = await parseSetup(`foo: 1`); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Incorrect type.'); (0, chai_1.expect)(result[0].message).to.include('string | object'); }); it('two levels, properties match with inner definition', async () => { (0, chai_1.expect)(await parseSetup(`foo: bar: hi`)).to.be.empty; }); it('two levels, integer does not match as a property value', async () => { const result = await parseSetup(`foo: bar: 1`); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Incorrect type.'); (0, chai_1.expect)(result[0].message).to.include('string | object'); }); }); describe('$recursiveRef with no $recursiveAnchor in the initial target schema resource', () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', $id: 'http://localhost:4242/draft2019-09/recursiveRef6/base.json', $recursiveAnchor: true, anyOf: [ { type: 'boolean' }, { type: 'object', additionalProperties: { $id: 'http://localhost:4242/draft2019-09/recursiveRef6/inner.json', $comment: 'there is no $recursiveAnchor: true here, so we do NOT recurse to the base', anyOf: [{ type: 'integer' }, { type: 'object', additionalProperties: { $recursiveRef: '#' } }], }, }, ], }; beforeEach(() => { schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); }); it('leaf node does not match; no recursion', async () => { const result = await parseSetup(`foo: true`); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Incorrect type.'); (0, chai_1.expect)(result[0].message).to.include('integer | object'); }); it('leaf node matches: recursion uses the inner schema', async () => { (0, chai_1.expect)(await parseSetup(`foo: bar: 1`)).to.be.empty; }); it('leaf node does not match: recursion uses the inner schema', async () => { const result = await parseSetup(`foo: bar: true`); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Incorrect type.'); (0, chai_1.expect)(result[0].message).to.include('integer | object'); }); }); describe('$recursiveRef with no $recursiveAnchor in the outer schema resource', () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', $id: 'http://localhost:4242/draft2019-09/recursiveRef7/base.json', anyOf: [ { type: 'boolean' }, { type: 'object', additionalProperties: { $id: 'http://localhost:4242/draft2019-09/recursiveRef7/inner.json', $recursiveAnchor: true, anyOf: [{ type: 'integer' }, { type: 'object', additionalProperties: { $recursiveRef: '#' } }], }, }, ], }; beforeEach(() => { schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); }); it('leaf node does not match; no recursion', async () => { const result = await parseSetup(`foo: true`); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Incorrect type.'); (0, chai_1.expect)(result[0].message).to.include('integer | object'); }); it('leaf node matches: recursion only uses inner schema', async () => { (0, chai_1.expect)(await parseSetup(`foo: bar: 1`)).to.be.empty; }); it('leaf node does not match: recursion only uses inner schema', async () => { const result = await parseSetup(`foo: bar: true`); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Incorrect type.'); (0, chai_1.expect)(result[0].message).to.include('integer | object'); }); }); describe('multiple dynamic paths to the $recursiveRef keyword', () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', $id: 'https://example.com/recursiveRef8_main.json', $defs: { inner: { $id: 'recursiveRef8_inner.json', $recursiveAnchor: true, title: 'inner', additionalProperties: { $recursiveRef: '#', }, }, }, if: { propertyNames: { pattern: '^[a-m]', }, }, then: { title: 'any type of node', $id: 'recursiveRef8_anyLeafNode.json', $recursiveAnchor: true, $ref: 'recursiveRef8_inner.json', }, else: { title: 'integer node', $id: 'recursiveRef8_integerNode.json', $recursiveAnchor: true, type: ['object', 'integer'], $ref: 'recursiveRef8_inner.json', }, }; beforeEach(() => { schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); }); it('recurse to anyLeafNode - floats are allowed', async () => { (0, chai_1.expect)(await parseSetup(`alpha: 1.1`)).to.be.empty; }); it('recurse to integerNode - floats are not allowed', async () => { const result = await parseSetup(`november: 1.1`); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Incorrect type. Expected one of object, integer.'); }); }); describe('dynamic $recursiveRef destination (not predictable at schema compile time)', () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', $id: 'https://example.com/main.json', $defs: { inner: { $id: 'inner.json', $recursiveAnchor: true, title: 'inner', additionalProperties: { $recursiveRef: '#', }, }, }, if: { propertyNames: { pattern: '^[a-m]' } }, then: { title: 'any type of node', $id: 'anyLeafNode.json', $recursiveAnchor: true, $ref: 'main.json#/$defs/inner', }, else: { title: 'integer node', $id: 'integerNode.json', $recursiveAnchor: true, type: ['object', 'integer'], $ref: 'main.json#/$defs/inner', }, }; beforeEach(() => { schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema); }); it('numeric node', async () => { (0, chai_1.expect)(await parseSetup(`alpha: 1.1`)).to.be.empty; }); it('integer node', async () => { const result = await parseSetup(`november: 1.1`); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Incorrect type. Expected one of object, integer.'); }); }); it('does not infinite loop on self-recursive $recursiveRef', async () => { const schema = { $schema: 'https://json-schema.org/draft/2019-09/schema', $id: 'http://localhost:4242/recursive.json', $recursiveAnchor: true, anyOf: [ { type: 'string' }, { type: 'object', additionalProperties: { $recursiveRef: '#' }, }, ], }; schemaProvider.addSchemaWithUri(testHelper_1.SCHEMA_ID, 'http://localhost:4242/recursive.json', schema); const result = await parseSetup(`# yaml-language-server: $schema=http://localhost:4242/recursive.json foo: bar: baz: 1`); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Incorrect type.'); (0, chai_1.expect)(result[0].message).to.include('string | object'); }); describe('tree schema with unevaluatedProperties', () => { it('$recursiveRef resolves to outermost schema with $recursiveAnchor in dynamic scope', async () => { const treeSchema = { $schema: 'https://json-schema.org/draft/2019-09/schema', $id: 'http://example.com/tree.json', $recursiveAnchor: true, type: 'object', properties: { value: { type: 'integer' }, children: { type: 'array', items: { $recursiveRef: '#' } }, }, required: ['value'], }; const treeWithMetaSchema = { $schema: 'https://json-schema.org/draft/2019-09/schema', $id: 'http://example.com/tree-with-meta.json', $recursiveAnchor: true, allOf: [{ $ref: 'http://example.com/tree.json' }], properties: { meta: { type: 'string' }, }, required: ['meta'], unevaluatedProperties: false, }; schemaProvider.addSchemaWithUri(testHelper_1.SCHEMA_ID, 'http://example.com/tree.json', treeSchema); schemaProvider.addSchemaWithUri(testHelper_1.SCHEMA_ID, 'http://example.com/tree-with-meta.json', treeWithMetaSchema); const content = `# yaml-language-server: $schema=http://example.com/tree-with-meta.json value: 1 meta: root children: - value: 2 `; const result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Missing property'); (0, chai_1.expect)(result[0].message).to.include('meta'); }); it('$recursiveRef with all required properties should pass', async () => { const treeSchema = { $schema: 'https://json-schema.org/draft/2019-09/schema', $id: 'http://example.com/tree2.json', $recursiveAnchor: true, type: 'object', properties: { value: { type: 'integer' }, children: { type: 'array', items: { $recursiveRef: '#' } }, }, required: ['value'], }; const treeWithMetaSchema = { $schema: 'https://json-schema.org/draft/2019-09/schema', $id: 'http://example.com/tree-with-meta2.json', $recursiveAnchor: true, allOf: [{ $ref: 'http://example.com/tree2.json' }], properties: { meta: { type: 'string' }, }, required: ['meta'], unevaluatedProperties: false, }; schemaProvider.addSchemaWithUri(testHelper_1.SCHEMA_ID, 'http://example.com/tree2.json', treeSchema); schemaProvider.addSchemaWithUri(testHelper_1.SCHEMA_ID, 'http://example.com/tree-with-meta2.json', treeWithMetaSchema); const content = `# yaml-language-server: $schema=http://example.com/tree-with-meta2.json value: 1 meta: root children: - value: 2 meta: child `; (0, chai_1.expect)(await parseSetup(content)).to.be.empty; }); }); }); describe('keyword: contains', () => { it('contains keyword validation', async () => { schemaProvider.addSchema(testHelper_1.SCHEMA_ID, { $schema: 'https://json-schema.org/draft/2019-09/schema', contains: { minimum: 5 }, }); // array with item matching schema (5) is valid let content = toContent([3, 4, 5]); let result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; // array with item matching schema (6) is valid content = toContent([3, 4, 6]); result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; // array with two items matching schema (5, 6) is valid content = toContent([3, 4, 5, 6]); result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; // array without items matching schema is invalid content = toContent([2, 3, 4]); result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Array has too few items matching'); // empty array is invalid content = toContent([]); result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Array has too few items matching'); // not array is valid content = toContent({}); result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; }); it('contains keyword with const keyword', async () => { schemaProvider.addSchema(testHelper_1.SCHEMA_ID, { $schema: 'https://json-schema.org/draft/2019-09/schema', contains: { const: 5 }, }); // array with item 5 is valid let content = toContent([3, 4, 5]); let result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; // array with two items 5 is valid content = toContent([3, 4, 5, 5]); result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; // array without item 5 is invalid content = toContent([1, 2, 3, 4]); result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Array has too few items matching'); }); it('contains keyword with boolean schema true', async () => { schemaProvider.addSchema(testHelper_1.SCHEMA_ID, { $schema: 'https://json-schema.org/draft/2019-09/schema', contains: true, }); // any non-empty array is valid let content = toContent(['foo']); let result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; // empty array is invalid content = toContent([]); result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Array has too few items matching'); }); it('contains keyword with boolean schema false', async () => { schemaProvider.addSchema(testHelper_1.SCHEMA_ID, { $schema: 'https://json-schema.org/draft/2019-09/schema', contains: false, }); // any non-empty array is invalid let content = toContent(['foo']); let result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Array has too few items matching'); // empty array is invalid content = toContent([]); result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Array has too few items matching'); // non-arrays are valid content = toContent('contains does not apply to strings'); result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; }); it('items + contains', async () => { schemaProvider.addSchema(testHelper_1.SCHEMA_ID, { $schema: 'https://json-schema.org/draft/2019-09/schema', items: { multipleOf: 2 }, contains: { multipleOf: 3 }, }); // matches items, does not match contains let content = toContent([2, 4, 8]); let result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Array has too few items matching'); // does not match items, matches contains content = toContent([3, 6, 9]); result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(2); (0, chai_1.expect)(result[0].message).to.include('Value is not divisible by 2.'); (0, chai_1.expect)(result[1].message).to.include('Value is not divisible by 2.'); // matches both items and contains content = toContent([6, 12]); result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; // matches neither items nor contains content = toContent([1, 5]); result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(3); (0, chai_1.expect)(result[0].message).to.include('Value is not divisible by 2.'); (0, chai_1.expect)(result[1].message).to.include('Value is not divisible by 2.'); (0, chai_1.expect)(result[2].message).to.include('Array has too few items matching'); }); it('contains with false if subschema', async () => { schemaProvider.addSchema(testHelper_1.SCHEMA_ID, { $schema: 'https://json-schema.org/draft/2019-09/schema', contains: { if: false, else: true, }, }); // any non-empty array is valid let content = toContent(['foo']); let result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; // empty array is invalid content = toContent([]); result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Array has too few items matching'); }); it('contains with null instance elements', async () => { schemaProvider.addSchema(testHelper_1.SCHEMA_ID, { $schema: 'https://json-schema.org/draft/2019-09/schema', contains: { type: 'null', }, }); // allows null items const content = toContent([null]); const result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; }); }); describe('keyword: maxContains', () => { it('maxContains without contains is ignored', async () => { schemaProvider.addSchema(testHelper_1.SCHEMA_ID, { $schema: 'https://json-schema.org/draft/2019-09/schema', maxContains: 1, }); // one item valid against lone maxContains let content = toContent([1]); let result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; // two items still valid against lone maxContains content = toContent([1, 2]); result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; }); it('maxContains with contains', async () => { schemaProvider.addSchema(testHelper_1.SCHEMA_ID, { $schema: 'https://json-schema.org/draft/2019-09/schema', contains: { const: 1 }, maxContains: 1, }); // empty data let content = toContent([]); let result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Array has too few items matching'); // all elements match, valid maxContains content = toContent([1]); result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; // all elements match, invalid maxContains content = toContent([1, 1]); result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Array has too many items matching'); // some elements match, valid maxContains content = toContent([1, 2]); result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; // some elements match, invalid maxContains content = toContent([1, 2, 1]); result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Array has too many items matching'); }); it('maxContains with contains, value with a decimal', async () => { schemaProvider.addSchema(testHelper_1.SCHEMA_ID, { $schema: 'https://json-schema.org/draft/2019-09/schema', contains: { const: 1 }, maxContains: 1.0, }); // one element matches, valid maxContains let content = toContent([1]); let result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; // too many elements match, invalid maxContains content = toContent([1, 1]); result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Array has too many items matching'); }); it('minContains < maxContains', async () => { schemaProvider.addSchema(testHelper_1.SCHEMA_ID, { $schema: 'https://json-schema.org/draft/2019-09/schema', contains: { const: 1 }, minContains: 1, maxContains: 3, }); // actual < minContains < maxContains let content = toContent([]); let result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Array has too few items matching'); // minContains < actual < maxContains content = toContent([1, 1]); result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; // minContains < maxContains < actual content = toContent([1, 1, 1, 1]); result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Array has too many items matching'); }); }); describe('keyword: minContains', () => { it('minContains without contains is ignored', async () => { schemaProvider.addSchema(testHelper_1.SCHEMA_ID, { $schema: 'https://json-schema.org/draft/2019-09/schema', minContains: 1, }); // one item valid against lone minContains let content = toContent([1]); let result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; // zero items still valid against lone minContains content = toContent([]); result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; }); it('minContains=1 with contains', async () => { schemaProvider.addSchema(testHelper_1.SCHEMA_ID, { $schema: 'https://json-schema.org/draft/2019-09/schema', contains: { const: 1 }, minContains: 1, }); // empty data let content = toContent([]); let result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Array has too few items matching'); // no elements match content = toContent([2]); result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Array has too few items matching'); // single element matches, valid minContains content = toContent([1]); result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; // some elements match, valid minContains content = toContent([1, 2]); result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; // all elements match, valid minContains content = toContent([1, 1]); result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; }); it('minContains=2 with contains', async () => { schemaProvider.addSchema(testHelper_1.SCHEMA_ID, { $schema: 'https://json-schema.org/draft/2019-09/schema', contains: { const: 1 }, minContains: 2, }); // empty data let content = toContent([]); let result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Array has too few items matching'); // all elements match, invalid minContains content = toContent([1]); result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Array has too few items matching'); // some elements match, invalid minContains content = toContent([1, 2]); result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Array has too few items matching'); // all elements match, valid minContains (exactly as needed) content = toContent([1, 1]); result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; // all elements match, valid minContains (more than needed) content = toContent([1, 1, 1]); result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; // some elements match, valid minContains content = toContent([1, 2, 1]); result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; }); it('minContains=2 with contains with a decimal value', async () => { schemaProvider.addSchema(testHelper_1.SCHEMA_ID, { $schema: 'https://json-schema.org/draft/2019-09/schema', contains: { const: 1 }, minContains: 2.0, }); // one element matches, invalid minContains let content = toContent([1]); let result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Array has too few items matching'); // both elements match, valid minContains content = toContent([1, 1]); result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; }); it('maxContains = minContains', async () => { schemaProvider.addSchema(testHelper_1.SCHEMA_ID, { $schema: 'https://json-schema.org/draft/2019-09/schema', contains: { const: 1 }, maxContains: 2, minContains: 2, }); // empty data let content = toContent([]); let result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Array has too few items matching'); // all elements match, invalid minContains content = toContent([1]); result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Array has too few items matching'); // all elements match, invalid maxContains content = toContent([1, 1, 1]); result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Array has too many items matching'); // all elements match, valid maxContains and minContains content = toContent([1, 1]); result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; }); it('maxContains < minContains', async () => { schemaProvider.addSchema(testHelper_1.SCHEMA_ID, { $schema: 'https://json-schema.org/draft/2019-09/schema', contains: { const: 1 }, maxContains: 1, minContains: 3, }); // empty data let content = toContent([]); let result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Array has too few items matching'); // invalid minContains content = toContent([1]); result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Array has too few items matching'); // invalid maxContains content = toContent([1, 1, 1]); result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(2); (0, chai_1.expect)(result[0].message).to.include('Array has too few items matching'); (0, chai_1.expect)(result[1].message).to.include('Array has too many items matching'); // invalid maxContains and minContains content = toContent([1, 1]); result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(2); (0, chai_1.expect)(result[0].message).to.include('Array has too few items matching'); (0, chai_1.expect)(result[1].message).to.include('Array has too many items matching'); }); it('minContains = 0 with no maxContains', async () => { schemaProvider.addSchema(testHelper_1.SCHEMA_ID, { $schema: 'https://json-schema.org/draft/2019-09/schema', contains: { const: 1 }, minContains: 0, }); // empty data let content = toContent([]); let result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; // minContains = 0 makes contains always pass content = toContent([2]); result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; }); it('minContains = 0 with maxContains', async () => { schemaProvider.addSchema(testHelper_1.SCHEMA_ID, { $schema: 'https://json-schema.org/draft/2019-09/schema', contains: { const: 1 }, minContains: 0, maxContains: 1, }); // empty data let content = toContent([]); let result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; // not more than maxContains content = toContent([1]); result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; // too many content = toContent([1, 1]); result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Array has too many items matching'); }); }); describe('keyword: dependentSchemas', () => { it('single dependency', async () => { schemaProvider.addSchema(testHelper_1.SCHEMA_ID, { $schema: 'https://json-schema.org/draft/2019-09/schema', dependentSchemas: { bar: { properties: { foo: { type: 'integer' }, bar: { type: 'integer' }, }, }, }, }); // valid let content = toContent({ foo: 1, bar: 2 }); let result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; // no dependency content = toContent({ foo: 'quux' }); result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; // wrong type content = toContent({ foo: 'quux', bar: 2 }); result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Incorrect type'); // wrong type other content = toContent({ foo: 2, bar: 'quux' }); result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Incorrect type'); // wrong type both content = toContent({ foo: 'quux', bar: 'quux' }); result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(2); (0, chai_1.expect)(result[0].message).to.include('Incorrect type'); (0, chai_1.expect)(result[1].message).to.include('Incorrect type'); // ignores arrays content = toContent(['bar']); result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; // ignores strings content = toContent('foobar'); result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; // ignores other non-objects content = toContent(12); result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; }); it('boolean subschemas', async () => { schemaProvider.addSchema(testHelper_1.SCHEMA_ID, { $schema: 'https://json-schema.org/draft/2019-09/schema', dependentSchemas: { foo: true, bar: false, }, }); // object with property having schema true is valid let content = toContent({ foo: 1 }); let result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; // object with property having schema false is invalid content = toContent({ bar: 2 }); result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Matches a schema that is not allowed'); // object with both properties is invalid content = toContent({ foo: 1, bar: 2 }); result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Matches a schema that is not allowed'); // empty object is valid content = toContent({}); result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; }); it('dependencies with escaped characters', async () => { schemaProvider.addSchema(testHelper_1.SCHEMA_ID, { $schema: 'https://json-schema.org/draft/2019-09/schema', dependentSchemas: { 'foo\tbar': { minProperties: 4 }, "foo'bar": { required: ['foo"bar'] }, }, }); // quoted tab let content = toContent({ 'foo\tbar': 1, a: 2, b: 3, c: 4, }); let result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; // quoted quote content = toContent({ "foo'bar": { 'foo"bar': 1 }, }); result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Missing property'); // quoted tab invalid under dependent schema content = toContent({ 'foo\tbar': 1, a: 2, }); result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Object has fewer properties than the required number of 4'); // quoted quote invalid under dependent schema content = toContent({ "foo'bar": 1 }); result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Missing property'); }); it('dependent subschema incompatible with root', async () => { schemaProvider.addSchema(testHelper_1.SCHEMA_ID, { $schema: 'https://json-schema.org/draft/2019-09/schema', properties: { foo: {}, }, dependentSchemas: { foo: { properties: { bar: {}, }, additionalProperties: false, }, }, }); // matches root let content = toContent({ foo: 1 }); let result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('not allowed'); // matches dependency content = toContent({ bar: 1 }); result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; // matches both content = toContent({ foo: 1, bar: 2 }); result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('not allowed'); // no dependency content = toContent({ baz: 1 }); result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; }); }); describe('keyword: dependentRequired', () => { it('single dependency', async () => { schemaProvider.addSchema(testHelper_1.SCHEMA_ID, { $schema: 'https://json-schema.org/draft/2019-09/schema', dependentRequired: { bar: ['foo'] }, }); // neither let content = toContent({}); let result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; // nondependant content = toContent({ foo: 1 }); result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; // with dependency content = toContent({ foo: 1, bar: 2 }); result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; // missing dependency content = toContent({ bar: 2 }); result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Object is missing property foo required by property bar.'); // ignores arrays content = toContent(['bar']); result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; // ignores strings content = toContent('foobar'); result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; // ignores other non-objects content = toContent(12); result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; }); it('empty dependents', async () => { schemaProvider.addSchema(testHelper_1.SCHEMA_ID, { $schema: 'https://json-schema.org/draft/2019-09/schema', dependentRequired: { bar: [] }, }); // empty object let content = toContent({}); let result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; // object with one property content = toContent({ bar: 2 }); result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; // non-object is valid content = toContent(1); result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; }); it('multiple dependents required', async () => { schemaProvider.addSchema(testHelper_1.SCHEMA_ID, { $schema: 'https://json-schema.org/draft/2019-09/schema', dependentRequired: { quux: ['foo', 'bar'] }, }); // neither let content = toContent({}); let result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; // nondependants content = toContent({ foo: 1, bar: 2 }); result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; // with dependencies content = toContent({ foo: 1, bar: 2, quux: 3 }); result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; // missing dependency content = toContent({ foo: 1, quux: 2 }); result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Object is missing property bar required by property quux.'); // missing other dependency content = toContent({ bar: 1, quux: 2 }); result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Object is missing property foo required by property quux.'); // missing both dependencies content = toContent({ quux: 1 }); result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(2); (0, chai_1.expect)(result[0].message).to.include('Object is missing property foo required by property quux.'); (0, chai_1.expect)(result[1].message).to.include('Object is missing property bar required by property quux.'); }); it('dependencies with escaped characters', async () => { schemaProvider.addSchema(testHelper_1.SCHEMA_ID, { $schema: 'https://json-schema.org/draft/2019-09/schema', dependentRequired: { 'foo\nbar': ['foo\rbar'], 'foo"bar': ["foo'bar"], }, }); // CRLF let content = toContent({ 'foo\nbar': 1, 'foo\rbar': 2, }); let result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; // quoted quotes content = toContent({ "foo'bar": 1, 'foo"bar': 2, }); result = await parseSetup(content); (0, chai_1.expect)(result).to.be.empty; // CRLF missing dependent content = toContent({ 'foo\nbar': 1, foo: 2, }); result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Object is missing property foo\rbar required by property foo\nbar.'); // quoted quotes missing dependent content = toContent({ 'foo"bar': 2, }); result = await parseSetup(content); (0, chai_1.expect)(result).to.have.length(1); (0, chai_1.expect)(result[0].message).to.include('Object is missing property foo\'bar required by property foo"bar.'); }); }); }); //# sourceMappingURL=schema2019Validation.test.js.map