yaml-language-server
Version:
977 lines • 100 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
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 KUBERNETES_SCHEMA_URL = `https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/${schemaUrls_1.DEFAULT_KUBERNETES_SCHEMA_VERSION}-standalone-strict/all.json`;
const toContent = (data) => JSON.stringify(data, null, 2);
before(() => {
languageSettingsSetup = new serviceSetup_1.ServiceSetup()
.withValidate()
.withCompletion()
.withCustomTags(['!Test', '!Ref sequence'])
.withSchemaFileMatch({ uri: KUBERNETES_SCHEMA_URL, fileMatch: ['.drone.yml'] })
.withSchemaFileMatch({ uri: 'https://json.schemastore.org/drone', fileMatch: ['.drone.yml'] })
.withSchemaFileMatch({ uri: 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('keyword: prefixItems + items', () => {
describe('Open tuple', () => {
beforeEach(() => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
$schema: 'https://json-schema.org/draft/2020-12/schema',
type: 'array',
prefixItems: [{ type: 'string' }, { type: 'number' }],
});
});
it('allows extra items by default (items is unconstrained)', async () => {
const content = `- hello
- 123
- { totally: "anything" }`;
const result = await parseSetup(content);
(0, chai_1.expect)(result).to.be.empty;
});
it('fails when a prefixItems position has the wrong type', async () => {
const content = `- 123\n- hello`;
const 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[0].message).to.include('string');
(0, chai_1.expect)(result[1].message).to.include('Incorrect type');
(0, chai_1.expect)(result[1].message).to.include('number');
});
});
describe('Closed tuple', () => {
beforeEach(() => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
$schema: 'https://json-schema.org/draft/2020-12/schema',
type: 'array',
prefixItems: [{ type: 'string' }, { type: 'number' }],
items: false,
});
});
it('forbids extra items after prefixItems', async () => {
const content = `- hello\n- 123\n- extra`;
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('passes when length is within prefixItems', async () => {
const content = `- hello\n- 123`;
const result = await parseSetup(content);
(0, chai_1.expect)(result).to.be.empty;
});
});
describe('Tuple with constrained extra items', () => {
beforeEach(() => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
$schema: 'https://json-schema.org/draft/2020-12/schema',
type: 'array',
prefixItems: [{ type: 'string' }, { type: 'number' }],
items: { type: 'boolean' },
});
});
it('fails when an extra item does not match items schema', async () => {
const content = `- hello
- 123
- notBoolean`;
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('boolean');
});
it('passes when extra items match items schema', async () => {
const content = `- hello
- 123
- true
- false`;
const result = await parseSetup(content);
(0, chai_1.expect)(result).to.be.empty;
});
});
});
describe('keyword: prefixItems', () => {
it('a schema given for prefixItems', async () => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
$schema: 'https://json-schema.org/draft/2020-12/schema',
prefixItems: [{ type: 'integer' }, { type: 'string' }],
});
// correct types
let content = toContent([1, 'foo']);
let result = await parseSetup(content);
(0, chai_1.expect)(result).to.be.empty;
// wrong types
content = toContent(['foo', 1]);
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');
// incomplete array of items
content = toContent([1]);
result = await parseSetup(content);
(0, chai_1.expect)(result).to.be.empty;
// array with additional items
content = toContent([1, 'foo', true]);
result = await parseSetup(content);
(0, chai_1.expect)(result).to.be.empty;
// empty array
content = toContent([]);
result = await parseSetup(content);
(0, chai_1.expect)(result).to.be.empty;
// JavaScript pseudo-array is valid
content = toContent({
'0': 'invalid',
'1': 'valid',
length: 2,
});
result = await parseSetup(content);
(0, chai_1.expect)(result).to.be.empty;
});
it('prefixItems with boolean schemas', async () => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
$schema: 'https://json-schema.org/draft/2020-12/schema',
prefixItems: [true, false],
});
// array with one item is valid
let content = toContent([1]);
let result = await parseSetup(content);
(0, chai_1.expect)(result).to.be.empty;
// array with two items is invalid
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('Matches a schema that is not allowed');
// empty array is valid
content = toContent([]);
result = await parseSetup(content);
(0, chai_1.expect)(result).to.be.empty;
});
it('additional items are allowed by default', async () => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
$schema: 'https://json-schema.org/draft/2020-12/schema',
prefixItems: [{ type: 'integer' }],
});
// only the first item is validated
const content = toContent([1, 'foo', false]);
const result = await parseSetup(content);
(0, chai_1.expect)(result).to.be.empty;
});
it('prefixItems with null instance elements', async () => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
$schema: 'https://json-schema.org/draft/2020-12/schema',
prefixItems: [
{
type: 'null',
},
],
});
// allows null elements
const content = toContent([null]);
const result = await parseSetup(content);
(0, chai_1.expect)(result).to.be.empty;
});
});
describe('keyword: items', () => {
it('a schema given for items', async () => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
$schema: 'https://json-schema.org/draft/2020-12/schema',
items: { type: 'integer' },
});
// valid items
let content = toContent([1, 2, 3]);
let result = await parseSetup(content);
(0, chai_1.expect)(result).to.be.empty;
// wrong type of items
content = toContent([1, 'x']);
result = await parseSetup(content);
(0, chai_1.expect)(result).to.have.length(1);
(0, chai_1.expect)(result[0].message).to.include('Incorrect type');
// ignores non-arrays
content = toContent({ foo: 'bar' });
result = await parseSetup(content);
(0, chai_1.expect)(result).to.be.empty;
// JavaScript pseudo-array is valid
content = toContent({
'0': 'invalid',
length: 1,
});
result = await parseSetup(content);
(0, chai_1.expect)(result).to.be.empty;
});
it('items with boolean schema (true)', async () => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
$schema: 'https://json-schema.org/draft/2020-12/schema',
items: true,
});
// any array is valid
let content = toContent([1, 'foo', true]);
let result = await parseSetup(content);
(0, chai_1.expect)(result).to.be.empty;
// empty array is valid
content = toContent([]);
result = await parseSetup(content);
(0, chai_1.expect)(result).to.be.empty;
});
it('items with boolean schema (false)', async () => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
$schema: 'https://json-schema.org/draft/2020-12/schema',
items: false,
});
// any non-empty array is invalid
let content = toContent([1, 'foo', true]);
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 many items according to schema. Expected 0 or fewer.');
// empty array is valid
content = toContent([]);
result = await parseSetup(content);
(0, chai_1.expect)(result).to.be.empty;
});
it('items and subitems', async () => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
$schema: 'https://json-schema.org/draft/2020-12/schema',
$defs: {
item: {
type: 'array',
items: false,
prefixItems: [{ $ref: '#/$defs/sub-item' }, { $ref: '#/$defs/sub-item' }],
},
'sub-item': {
type: 'object',
required: ['foo'],
},
},
type: 'array',
items: false,
prefixItems: [{ $ref: '#/$defs/item' }, { $ref: '#/$defs/item' }, { $ref: '#/$defs/item' }],
});
// valid items
let content = toContent([
[{ foo: null }, { foo: null }],
[{ foo: null }, { foo: null }],
[{ foo: null }, { foo: null }],
]);
let result = await parseSetup(content);
(0, chai_1.expect)(result).to.be.empty;
// too many items
content = toContent([
[{ foo: null }, { foo: null }],
[{ foo: null }, { foo: null }],
[{ foo: null }, { foo: null }],
[{ foo: null }, { foo: null }],
]);
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');
// too many sub-items
content = toContent([
[{ foo: null }, { foo: null }, { foo: null }],
[{ foo: null }, { foo: null }],
[{ foo: null }, { foo: null }],
]);
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');
// wrong item
content = toContent([{ foo: null }, [{ foo: null }, { foo: null }], [{ foo: null }, { foo: null }]]);
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 sub-item
content = toContent([
[{}, { foo: null }],
[{ foo: null }, { foo: null }],
[{ foo: null }, { foo: null }],
]);
result = await parseSetup(content);
(0, chai_1.expect)(result).to.have.length(1);
(0, chai_1.expect)(result[0].message).to.include('Missing property');
// fewer items is valid
content = toContent([[{ foo: null }], [{ foo: null }]]);
result = await parseSetup(content);
(0, chai_1.expect)(result).to.be.empty;
});
it('nested items', async () => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
$schema: 'https://json-schema.org/draft/2020-12/schema',
type: 'array',
items: {
type: 'array',
items: {
type: 'array',
items: {
type: 'array',
items: {
type: 'number',
},
},
},
},
});
// valid nested array
let content = toContent([[[[1]], [[2], [3]]], [[[4], [5], [6]]]]);
let result = await parseSetup(content);
(0, chai_1.expect)(result).to.be.empty;
// nested array with invalid type
content = toContent([[[['1']], [[2], [3]]], [[[4], [5], [6]]]]);
result = await parseSetup(content);
(0, chai_1.expect)(result).to.have.length(1);
(0, chai_1.expect)(result[0].message).to.include('Incorrect type');
// not deep enough
content = toContent([
[[1], [2], [3]],
[[4], [5], [6]],
]);
result = await parseSetup(content);
(0, chai_1.expect)(result).to.have.length(6);
(0, chai_1.expect)(result[0].message).to.include('Incorrect type');
});
it('prefixItems with no additional items allowed', async () => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
$schema: 'https://json-schema.org/draft/2020-12/schema',
prefixItems: [{}, {}, {}],
items: false,
});
// empty array
let content = toContent([]);
let result = await parseSetup(content);
(0, chai_1.expect)(result).to.be.empty;
// fewer number of items present (1)
content = toContent([1]);
result = await parseSetup(content);
(0, chai_1.expect)(result).to.be.empty;
// fewer number of items present (2)
content = toContent([1, 2]);
result = await parseSetup(content);
(0, chai_1.expect)(result).to.be.empty;
// equal number of items present
content = toContent([1, 2, 3]);
result = await parseSetup(content);
(0, chai_1.expect)(result).to.be.empty;
// additional items are not permitted
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 many items');
});
it('items does not look in applicators, valid case', async () => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
$schema: 'https://json-schema.org/draft/2020-12/schema',
allOf: [{ prefixItems: [{ minimum: 3 }] }],
items: { minimum: 5 },
});
// prefixItems in allOf does not constrain items, invalid case
let content = toContent([3, 5]);
let 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 5.');
// prefixItems in allOf does not constrain items, valid case
content = toContent([5, 5]);
result = await parseSetup(content);
(0, chai_1.expect)(result).to.be.empty;
});
it('prefixItems validation adjusts the starting index for items', async () => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
$schema: 'https://json-schema.org/draft/2020-12/schema',
prefixItems: [{ type: 'string' }],
items: { type: 'integer' },
});
// valid items
let content = toContent(['x', 2, 3]);
let result = await parseSetup(content);
(0, chai_1.expect)(result).to.be.empty;
// wrong type of second item
content = toContent(['x', 'y']);
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('items with heterogeneous array', async () => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
$schema: 'https://json-schema.org/draft/2020-12/schema',
prefixItems: [{}],
items: false,
});
// heterogeneous invalid instance
let content = toContent(['foo', 'bar', 37]);
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 many items');
// valid instance
content = toContent([null]);
result = await parseSetup(content);
(0, chai_1.expect)(result).to.be.empty;
});
it('items with null instance elements', async () => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
$schema: 'https://json-schema.org/draft/2020-12/schema',
items: {
type: 'null',
},
});
// allows null elements
const content = toContent([null]);
const result = await parseSetup(content);
(0, chai_1.expect)(result).to.be.empty;
});
});
describe('keyword: unevaluatedItems', () => {
it('unevaluatedItems true', async () => {
const schema = {
$schema: 'https://json-schema.org/draft/2020-12/schema',
unevaluatedItems: 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']))).to.be.empty;
});
it('unevaluatedItems false', async () => {
const schema = {
$schema: 'https://json-schema.org/draft/2020-12/schema',
unevaluatedItems: false,
};
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
(0, chai_1.expect)(await parseSetup(toContent([]))).to.be.empty;
const result = await parseSetup(toContent(['foo']));
(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/2020-12/schema',
unevaluatedItems: { type: 'string' },
};
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']))).to.be.empty;
const result = await parseSetup(toContent([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/2020-12/schema',
items: { type: 'string' },
unevaluatedItems: false,
};
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
(0, chai_1.expect)(await parseSetup(toContent(['foo', 'bar']))).to.be.empty;
});
it('unevaluatedItems with tuple', async () => {
const schema = {
$schema: 'https://json-schema.org/draft/2020-12/schema',
prefixItems: [{ type: 'string' }],
unevaluatedItems: false,
};
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
(0, chai_1.expect)(await parseSetup(toContent(['foo']))).to.be.empty;
const result = await parseSetup(toContent(['foo', 'bar']));
(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 prefixItems', async () => {
const schema = {
$schema: 'https://json-schema.org/draft/2020-12/schema',
prefixItems: [{ type: 'string' }],
items: true,
unevaluatedItems: false,
};
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
(0, chai_1.expect)(await parseSetup(toContent(['foo', 42]))).to.be.empty;
});
it('unevaluatedItems with items', async () => {
const schema = {
$schema: 'https://json-schema.org/draft/2020-12/schema',
items: { type: 'number' },
unevaluatedItems: { type: 'string' },
};
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
(0, chai_1.expect)(await parseSetup(toContent([5, 6, 7, 8]))).to.be.empty;
const result = await parseSetup(toContent(['foo', 'bar', 'baz']));
(0, chai_1.expect)(result).to.have.length(3);
(0, chai_1.expect)(result[0].message).to.include('Incorrect type. Expected');
(0, chai_1.expect)(result[0].message).to.include('number');
});
it('unevaluatedItems with nested tuple', async () => {
const schema = {
$schema: 'https://json-schema.org/draft/2020-12/schema',
prefixItems: [{ type: 'string' }],
allOf: [
{
prefixItems: [true, { type: 'number' }],
},
],
unevaluatedItems: false,
};
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
(0, chai_1.expect)(await parseSetup(toContent(['foo', 42]))).to.be.empty;
const result = await parseSetup(toContent(['foo', 42, true]));
(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/2020-12/schema',
unevaluatedItems: { type: 'boolean' },
anyOf: [{ items: { type: 'string' } }, true],
};
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
(0, chai_1.expect)(await parseSetup(toContent([true, false]))).to.be.empty;
(0, chai_1.expect)(await parseSetup(toContent(['yes', 'no']))).to.be.empty;
const result = await parseSetup(toContent(['yes', false]));
(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('boolean');
});
it('unevaluatedItems with nested prefixItems and items', async () => {
const schema = {
$schema: 'https://json-schema.org/draft/2020-12/schema',
allOf: [
{
prefixItems: [{ type: 'string' }],
items: true,
},
],
unevaluatedItems: false,
};
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
(0, chai_1.expect)(await parseSetup(toContent(['foo']))).to.be.empty;
(0, chai_1.expect)(await parseSetup(toContent(['foo', 42, true]))).to.be.empty;
});
it('unevaluatedItems with nested unevaluatedItems', async () => {
const schema = {
$schema: 'https://json-schema.org/draft/2020-12/schema',
allOf: [
{
prefixItems: [{ type: 'string' }],
},
{ unevaluatedItems: true },
],
unevaluatedItems: false,
};
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
(0, chai_1.expect)(await parseSetup(toContent(['foo']))).to.be.empty;
(0, chai_1.expect)(await parseSetup(toContent(['foo', 42, true]))).to.be.empty;
});
it('unevaluatedItems with anyOf', async () => {
const schema = {
$schema: 'https://json-schema.org/draft/2020-12/schema',
prefixItems: [{ const: 'foo' }],
anyOf: [
{
prefixItems: [true, { const: 'bar' }],
},
{
prefixItems: [true, true, { const: 'baz' }],
},
],
unevaluatedItems: false,
};
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
(0, chai_1.expect)(await parseSetup(toContent(['foo', 'bar']))).to.be.empty;
const result1 = await parseSetup(toContent(['foo', 'bar', 42]));
(0, chai_1.expect)(result1).to.have.length(1);
(0, chai_1.expect)(result1[0].message).to.include('Array has too many items according to schema. Expected 2 or fewer.');
(0, chai_1.expect)(await parseSetup(toContent(['foo', 'bar', 'baz']))).to.be.empty;
const result2 = await parseSetup(toContent(['foo', 'bar', 'baz', 42]));
(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.');
});
it('unevaluatedItems with oneOf', async () => {
const schema = {
$schema: 'https://json-schema.org/draft/2020-12/schema',
prefixItems: [{ const: 'foo' }],
oneOf: [
{
prefixItems: [true, { const: 'bar' }],
},
{
prefixItems: [true, { const: 'baz' }],
},
],
unevaluatedItems: false,
};
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
(0, chai_1.expect)(await parseSetup(toContent(['foo', 'bar']))).to.be.empty;
const result = await parseSetup(toContent(['foo', 'bar', 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 2 or fewer.');
});
it('unevaluatedItems with not', async () => {
const schema = {
$schema: 'https://json-schema.org/draft/2020-12/schema',
prefixItems: [{ const: 'foo' }],
not: {
not: {
prefixItems: [true, { const: 'bar' }],
},
},
unevaluatedItems: false,
};
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
const result = await parseSetup(toContent(['foo', 'bar']));
(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/2020-12/schema',
prefixItems: [{ const: 'foo' }],
if: {
prefixItems: [true, { const: 'bar' }],
},
then: {
prefixItems: [true, true, { const: 'then' }],
},
else: {
prefixItems: [true, true, true, { const: 'else' }],
},
unevaluatedItems: false,
};
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
(0, chai_1.expect)(await parseSetup(toContent(['foo', 'bar', 'then']))).to.be.empty;
const result1 = await parseSetup(toContent(['foo', 'bar', 'then', 'else']));
(0, chai_1.expect)(result1).to.have.length(1);
(0, chai_1.expect)(result1[0].message).to.include('Array has too many items according to schema. Expected 3 or fewer.');
(0, chai_1.expect)(await parseSetup(toContent(['foo', 42, 42, 'else']))).to.be.empty;
const result2 = await parseSetup(toContent(['foo', 42, 42, 'else', 42]));
(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 4 or fewer.');
});
it('unevaluatedItems with boolean schemas', async () => {
const schema = {
$schema: 'https://json-schema.org/draft/2020-12/schema',
allOf: [true],
unevaluatedItems: false,
};
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
(0, chai_1.expect)(await parseSetup(toContent([]))).to.be.empty;
const result = await parseSetup(toContent(['foo']));
(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/2020-12/schema',
$ref: '#/$defs/bar',
prefixItems: [{ type: 'string' }],
unevaluatedItems: false,
$defs: {
bar: {
prefixItems: [true, { type: 'string' }],
},
},
};
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
(0, chai_1.expect)(await parseSetup(toContent(['foo', 'bar']))).to.be.empty;
const result = await parseSetup(toContent(['foo', 'bar', 'baz']));
(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/2020-12/schema',
unevaluatedItems: false,
prefixItems: [{ type: 'string' }],
$ref: '#/$defs/bar',
$defs: {
bar: {
prefixItems: [true, { type: 'string' }],
},
},
};
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
(0, chai_1.expect)(await parseSetup(toContent(['foo', 'bar']))).to.be.empty;
const result = await parseSetup(toContent(['foo', 'bar', 'baz']));
(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 $dynamicRef', async () => {
const schema = {
$schema: 'https://json-schema.org/draft/2020-12/schema',
$id: 'https://example.com/unevaluated-items-with-dynamic-ref/derived',
$ref: './baseSchema',
$defs: {
derived: {
$dynamicAnchor: 'addons',
prefixItems: [true, { type: 'string' }],
},
baseSchema: {
$id: './baseSchema',
$comment: "unevaluatedItems comes first so it's more likely to catch bugs with implementations that are sensitive to keyword ordering",
unevaluatedItems: false,
type: 'array',
prefixItems: [{ type: 'string' }],
$dynamicRef: '#addons',
$defs: {
defaultAddons: {
$comment: 'Needed to satisfy the bookending requirement',
$dynamicAnchor: 'addons',
},
},
},
},
};
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
(0, chai_1.expect)(await parseSetup(toContent(['foo', 'bar']))).to.be.empty;
const result = await parseSetup(toContent(['foo', 'bar', 'baz']));
(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 can't see inside cousins", async () => {
const schema = {
$schema: 'https://json-schema.org/draft/2020-12/schema',
allOf: [
{
prefixItems: [true],
},
{ unevaluatedItems: false },
],
};
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
const result = await parseSetup(toContent([1]));
(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/2020-12/schema',
properties: {
foo: {
prefixItems: [{ type: 'string' }],
unevaluatedItems: false,
},
},
anyOf: [
{
properties: {
foo: {
prefixItems: [true, { type: 'string' }],
},
},
},
],
};
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
(0, chai_1.expect)(await parseSetup(toContent({ foo: ['test'] }))).to.be.empty;
const result = await parseSetup(toContent({ foo: ['test', 'test'] }));
(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 depends on adjacent contains', async () => {
const schema = {
$schema: 'https://json-schema.org/draft/2020-12/schema',
prefixItems: [true],
contains: { type: 'string' },
unevaluatedItems: false,
};
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
(0, chai_1.expect)(await parseSetup(toContent([1, 'foo']))).to.be.empty;
const result1 = await parseSetup(toContent([1, 2]));
(0, chai_1.expect)(result1).to.have.length(2);
(0, chai_1.expect)(result1[0].message).to.include('Array has too few items matching');
(0, chai_1.expect)(result1[0].message).to.include('Expected 1 or more.');
(0, chai_1.expect)(result1[1].message).to.include('Array has too many items according to schema. Expected 1 or fewer.');
const result2 = await parseSetup(toContent([1, 2, 'foo']));
(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 1 or fewer.');
});
it('unevaluatedItems depends on multiple nested contains', async () => {
const schema = {
$schema: 'https://json-schema.org/draft/2020-12/schema',
allOf: [{ contains: { multipleOf: 2 } }, { contains: { multipleOf: 3 } }],
unevaluatedItems: { multipleOf: 5 },
};
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
(0, chai_1.expect)(await parseSetup(toContent([2, 3, 4, 5, 6]))).to.be.empty;
const result = await parseSetup(toContent([2, 3, 4, 7, 8]));
(0, chai_1.expect)(result).to.have.length(1);
(0, chai_1.expect)(result[0].message).to.include('Value is not divisible by 5.');
});
it('unevaluatedItems and contains interact to control item dependency relationship', async () => {
const schema = {
$schema: 'https://json-schema.org/draft/2020-12/schema',
if: {
contains: { const: 'a' },
},
then: {
if: {
contains: { const: 'b' },
},
then: {
if: {
contains: { const: 'c' },
},
},
},
unevaluatedItems: false,
};
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
(0, chai_1.expect)(await parseSetup(toContent([]))).to.be.empty;
(0, chai_1.expect)(await parseSetup(toContent(['a', 'a']))).to.be.empty;
(0, chai_1.expect)(await parseSetup(toContent(['a', 'b', 'a', 'b', 'a']))).to.be.empty;
(0, chai_1.expect)(await parseSetup(toContent(['c', 'a', 'c', 'c', 'b', 'a']))).to.be.empty;
const result1 = await parseSetup(toContent(['b', 'b']));
(0, chai_1.expect)(result1).to.have.length(2);
(0, chai_1.expect)(result1[0].message).to.include('Array has too many items according to schema. Expected 0 or fewer.');
(0, chai_1.expect)(result1[1].message).to.include('Array has too many items according to schema. Expected 1 or fewer.');
const result2 = await parseSetup(toContent(['c', 'c']));
(0, chai_1.expect)(result2).to.have.length(2);
(0, chai_1.expect)(result2[0].message).to.include('Array has too many items according to schema. Expected 0 or fewer.');
(0, chai_1.expect)(result2[1].message).to.include('Array has too many items according to schema. Expected 1 or fewer.');
const result3 = await parseSetup(toContent(['c', 'b', 'c', 'b', 'c']));
(0, chai_1.expect)(result3).to.have.length(5);
(0, chai_1.expect)(result3[0].message).to.include('Array has too many items according to schema. Expected 0 or fewer.');
(0, chai_1.expect)(result3[1].message).to.include('Array has too many items according to schema. Expected 1 or fewer.');
(0, chai_1.expect)(result3[2].message).to.include('Array has too many items according to schema. Expected 2 or fewer.');
(0, chai_1.expect)(result3[3].message).to.include('Array has too many items according to schema. Expected 3 or fewer.');
(0, chai_1.expect)(result3[4].message).to.include('Array has too many items according to schema. Expected 4 or fewer.');
const result4 = await parseSetup(toContent(['c', 'a', 'c', 'a', 'c']));
(0, chai_1.expect)(result4).to.have.length(3);
(0, chai_1.expect)(result4[0].message).to.include('Array has too many items according to schema. Expected 0 or fewer.');
(0, chai_1.expect)(result4[1].message).to.include('Array has too many items according to schema. Expected 2 or fewer.');
(0, chai_1.expect)(result4[2].message).to.include('Array has too many items according to schema. Expected 4 or fewer.');
});
it('unevaluatedItems with minContains = 0', async () => {
const schema = {
$schema: 'https://json-schema.org/draft/2020-12/schema',
contains: { type: 'string' },
minContains: 0,
unevaluatedItems: false,
};
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
(0, chai_1.expect)(await parseSetup(toContent([]))).to.be.empty;
const result1 = await parseSetup(toContent([0]));
(0, chai_1.expect)(result1).to.have.length(1);
(0, chai_1.expect)(result1[0].message).to.include('Array has too many items according to schema. Expected 0 or fewer.');
const result2 = await parseSetup(toContent(['foo', 0]));
(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 1 or fewer.');
(0, chai_1.expect)(await parseSetup(toContent(['foo', 'bar']))).to.be.empty;
});
it('non-array instances are valid', async () => {
const schema = {
$schema: 'https://json-schema.org/draft/2020-12/schema',
unevaluatedItems: 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('unevaluatedItems with null instance elements', async () => {
const schema = {
$schema: 'https://json-schema.org/draft/2020-12/schema',
unevaluatedItems: { type: 'null' },
};
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
(0, chai_1.expect)(await parseSetup(toContent([null]))).to.be.empty;
});
it('unevaluatedItems can see annotations from if without then and else', async () => {
const schema = {
$schema: 'https://json-schema.org/draft/2020-12/schema',
if: {
prefixItems: [{ const: 'a' }],
},
unevaluatedItems: false,
};
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
(0, chai_1.expect)(await parseSetup(toContent(['a']))).to.be.empty;
const result = await parseSetup(toContent(['b']));
(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/2020-12/schema',
prefixItems: [
{
prefixItems: [true, { type: 'string' }],
},
],
unevaluatedItems: false,
};
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
const result = await parseSetup(toContent([['foo', 'bar'], 'bar']));
(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', () => {
it('contains keyword validation', async () => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
$schema: 'https://json-schema.org/draft/2020-12/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/2020-12/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)(re