yaml-language-server
Version:
1,150 lines (1,145 loc) • 111 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const testHelper_1 = require("./utils/testHelper");
const verifyError_1 = require("./utils/verifyError");
const serviceSetup_1 = require("./utils/serviceSetup");
const errorMessages_1 = require("./utils/errorMessages");
const assert_1 = __importDefault(require("assert"));
const path = __importStar(require("path"));
const vscode_languageserver_types_1 = require("vscode-languageserver-types");
const chai_1 = require("chai");
const yamlSettings_1 = require("../src/yamlSettings");
const jsonLanguageTypes_1 = require("../src/languageservice/jsonLanguageTypes");
const schemaUrls_1 = require("../src/languageservice/utils/schemaUrls");
const KUBERNETES_SCHEMA_URL = `https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/${schemaUrls_1.DEFAULT_KUBERNETES_SCHEMA_VERSION}-standalone-strict/all.json`;
describe('Validation Tests', () => {
let languageSettingsSetup;
let validationHandler;
let languageService;
let yamlSettings;
let telemetry;
let schemaProvider;
before(() => {
languageSettingsSetup = new serviceSetup_1.ServiceSetup()
.withValidate()
.withCompletion()
.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 { languageService: langService, validationHandler: valHandler, yamlSettings: settings, telemetry: testTelemetry, schemaProvider: testSchemaProvider, } = (0, testHelper_1.setupLanguageService)(languageSettingsSetup.languageSettings);
languageService = langService;
validationHandler = valHandler;
yamlSettings = settings;
telemetry = testTelemetry;
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);
}
function configureCustomTags(customTags) {
languageSettingsSetup.languageSettings.customTags = customTags;
languageService.configure(languageSettingsSetup.languageSettings);
}
afterEach(() => {
configureCustomTags([]);
schemaProvider.deleteSchema(testHelper_1.SCHEMA_ID);
});
describe('Boolean tests', () => {
it('Boolean true test', async () => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
type: 'object',
properties: {
analytics: {
type: 'boolean',
},
},
});
const content = 'analytics: true';
const result = await parseSetup(content);
assert_1.default.equal(result.length, 0);
});
it('Basic false test', async () => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
type: 'object',
properties: {
analytics: {
type: 'boolean',
},
},
});
const content = 'analytics: false';
const result = await parseSetup(content);
assert_1.default.equal(result.length, 0);
});
it('Test that boolean value without quotations is valid', async () => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
type: 'object',
properties: {
analytics: {
type: 'boolean',
},
},
});
const content = '%YAML 1.1\n---\nanalytics: no';
const result = await parseSetup(content);
assert_1.default.equal(result.length, 0);
});
it('Test that boolean value in quotations is interpreted as string not boolean', async () => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
type: 'object',
properties: {
analytics: {
type: 'boolean',
},
},
});
const content = 'analytics: "no"';
const result = await parseSetup(content);
assert_1.default.strictEqual(result.length, 1);
assert_1.default.deepStrictEqual(result[0], (0, verifyError_1.createDiagnosticWithData)(errorMessages_1.BooleanTypeError, 0, 11, 0, 15, vscode_languageserver_types_1.DiagnosticSeverity.Error, `yaml-schema: file:///${testHelper_1.SCHEMA_ID}`, `file:///${testHelper_1.SCHEMA_ID}`));
});
it('Error on incorrect value type (boolean)', async () => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
type: 'object',
properties: {
cwd: {
type: 'string',
},
},
});
const content = 'cwd: False';
const result = await parseSetup(content);
assert_1.default.equal(result.length, 1);
assert_1.default.deepEqual(result[0], (0, verifyError_1.createDiagnosticWithData)(errorMessages_1.StringTypeError, 0, 5, 0, 10, vscode_languageserver_types_1.DiagnosticSeverity.Error, `yaml-schema: file:///${testHelper_1.SCHEMA_ID}`, `file:///${testHelper_1.SCHEMA_ID}`));
});
it('Test that boolean value can be used in enum', async () => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
type: 'object',
properties: {
analytics: {
enum: [true, false],
},
},
});
const content = 'analytics: true';
const result = await parseSetup(content);
assert_1.default.deepStrictEqual(result, []);
});
it('Test that boolean value can be used in const', async () => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
type: 'object',
properties: {
analytics: {
const: true,
},
},
});
const content = 'analytics: true';
const result = await parseSetup(content);
assert_1.default.deepStrictEqual(result, []);
});
it('Test that YAML 1.1 boolean "True" can be used in enum', async () => {
// This test requires YAML 1.1 mode where "True" is parsed as a boolean
languageService.configure(languageSettingsSetup.withYamlVersion('1.1').languageSettings);
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
type: 'object',
properties: {
enabled: {
enum: [true, false],
},
},
});
const content = 'enabled: True';
const result = await parseSetup(content);
assert_1.default.deepStrictEqual(result, []);
// Reset to default YAML version
languageService.configure(languageSettingsSetup.withYamlVersion('1.2').languageSettings);
});
it('Test that YAML 1.1 boolean "False" can be used in enum', async () => {
languageService.configure(languageSettingsSetup.withYamlVersion('1.1').languageSettings);
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
type: 'object',
properties: {
enabled: {
enum: [true, false],
},
},
});
const content = 'enabled: False';
const result = await parseSetup(content);
assert_1.default.deepStrictEqual(result, []);
languageService.configure(languageSettingsSetup.withYamlVersion('1.2').languageSettings);
});
it('Test that YAML 1.1 boolean "yes" can be used with const', async () => {
languageService.configure(languageSettingsSetup.withYamlVersion('1.1').languageSettings);
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
type: 'object',
properties: {
confirmed: {
const: true,
},
},
});
const content = 'confirmed: yes';
const result = await parseSetup(content);
assert_1.default.deepStrictEqual(result, []);
languageService.configure(languageSettingsSetup.withYamlVersion('1.2').languageSettings);
});
it('Test that YAML 1.1 boolean "no" can be used with const', async () => {
languageService.configure(languageSettingsSetup.withYamlVersion('1.1').languageSettings);
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
type: 'object',
properties: {
disabled: {
const: false,
},
},
});
const content = 'disabled: no';
const result = await parseSetup(content);
assert_1.default.deepStrictEqual(result, []);
languageService.configure(languageSettingsSetup.withYamlVersion('1.2').languageSettings);
});
});
describe('String tests', () => {
it('Test that boolean inside of quotations is of type string', async () => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
type: 'object',
properties: {
analytics: {
type: 'string',
},
},
});
const content = 'analytics: "no"';
const result = await parseSetup(content);
assert_1.default.equal(result.length, 0);
});
it('Type string validates under children', async () => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
type: 'object',
properties: {
scripts: {
type: 'object',
properties: {
register: {
type: 'string',
},
},
},
},
});
const content = 'registry:\n register: file://test_url';
const result = await parseSetup(content);
assert_1.default.equal(result.length, 0);
});
it('Type String does not error on valid node', async () => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
type: 'object',
properties: {
cwd: {
type: 'string',
},
},
});
const content = 'cwd: this';
const result = await parseSetup(content);
assert_1.default.equal(result.length, 0);
});
it('Error on incorrect value type (string)', async () => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
type: 'object',
properties: {
analytics: {
type: 'boolean',
},
},
});
const content = 'analytics: hello';
const result = await parseSetup(content);
assert_1.default.equal(result.length, 1);
assert_1.default.deepEqual(result[0], (0, verifyError_1.createDiagnosticWithData)(errorMessages_1.BooleanTypeError, 0, 11, 0, 16, vscode_languageserver_types_1.DiagnosticSeverity.Error, `yaml-schema: file:///${testHelper_1.SCHEMA_ID}`, `file:///${testHelper_1.SCHEMA_ID}`));
});
it('Test that boolean is invalid when no strings present and schema wants string', async () => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
type: 'object',
properties: {
cwd: {
type: 'string',
},
},
});
const content = '%YAML 1.1\n---\ncwd: no';
const result = await parseSetup(content);
assert_1.default.equal(result.length, 1);
assert_1.default.deepEqual(result[0], (0, verifyError_1.createDiagnosticWithData)(errorMessages_1.StringTypeError, 2, 5, 2, 7, vscode_languageserver_types_1.DiagnosticSeverity.Error, `yaml-schema: file:///${testHelper_1.SCHEMA_ID}`, `file:///${testHelper_1.SCHEMA_ID}`));
});
});
describe('Pattern tests', () => {
it('Test a valid Unicode pattern', async () => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
type: 'object',
properties: {
prop: {
type: 'string',
pattern: '^tes\\p{Letter}$',
},
},
});
const result = await parseSetup('prop: "tesT"');
assert_1.default.equal(result.length, 0);
});
it('Test an invalid Unicode pattern', async () => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
type: 'object',
properties: {
prop: {
type: 'string',
pattern: '^tes\\p{Letter}$',
},
},
});
const result = await parseSetup('prop: "tes "');
assert_1.default.equal(result.length, 1);
assert_1.default.ok(result[0].message.startsWith('String does not match the pattern'));
assert_1.default.deepEqual(result[0], (0, verifyError_1.createDiagnosticWithData)(result[0].message, 0, 6, 0, 12, vscode_languageserver_types_1.DiagnosticSeverity.Error, `yaml-schema: file:///${testHelper_1.SCHEMA_ID}`, `file:///${testHelper_1.SCHEMA_ID}`));
});
it('Test a valid Unicode patternProperty', async () => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
type: 'object',
patternProperties: {
'^tes\\p{Letter}$': true,
},
additionalProperties: false,
});
const result = await parseSetup('tesT: true');
assert_1.default.equal(result.length, 0);
});
it('Test an invalid Unicode patternProperty', async () => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
type: 'object',
patternProperties: {
'^tes\\p{Letter}$': true,
},
additionalProperties: false,
});
const result = await parseSetup('tes9: true');
assert_1.default.equal(result.length, 1);
assert_1.default.deepEqual(result[0], (0, verifyError_1.createDiagnosticWithData)('Property tes9 is not allowed.', 0, 0, 0, 4, vscode_languageserver_types_1.DiagnosticSeverity.Error, `yaml-schema: file:///${testHelper_1.SCHEMA_ID}`, `file:///${testHelper_1.SCHEMA_ID}`, jsonLanguageTypes_1.ErrorCode.PropertyExpected));
});
it('Test inline pattern modifiers', async () => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
type: 'object',
properties: {
caseInsensitiveField: {
type: 'string',
pattern: '(?i)^(yes|no)$',
},
multilineOnly: {
type: 'string',
pattern: '(?m)^start.*end$',
},
dotallOnly: {
type: 'string',
pattern: '(?s)^start.*end$',
},
multilineAndDotallCombinedFlags: {
type: 'string',
pattern: '(?ms)^start.*end$',
},
dotallAndCaseInsensitiveFieldFlags: {
type: 'string',
pattern: '(?s)(?i)test.*content',
},
allFlags: {
type: 'string',
pattern: '(?ims)^START.*END$',
},
},
});
const content = [
'caseInsensitiveField: YES',
'',
'multilineOnly: |-',
' other text',
' start middle content end',
' more text',
'',
'dotallOnly: |-',
' start',
' middle content',
' end',
'',
'multilineAndDotallCombinedFlags: |-',
' other text',
' start',
' middle content',
' end',
' more text',
'',
'dotallAndCaseInsensitiveFieldFlags: |-',
' TEST',
' middle content',
' CONTENT',
'',
'allFlags: |-',
' other text',
' start',
' middle content',
' end',
' more text',
].join('\n');
const result = await parseSetup(content);
assert_1.default.equal(result.length, 0);
});
it('Test an unsupported pattern', async () => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
type: 'object',
properties: {
unsupportedPattern: {
type: 'string',
pattern: '(?x)text .*',
},
},
});
const result = await parseSetup('unsupportedPattern: text content');
assert_1.default.equal(result.length, 1);
assert_1.default.deepEqual(result[0], (0, verifyError_1.createDiagnosticWithData)('Invalid pattern: "(?x)text .*"', 0, 20, 0, 32, vscode_languageserver_types_1.DiagnosticSeverity.Error, `yaml-schema: file:///${testHelper_1.SCHEMA_ID}`, `file:///${testHelper_1.SCHEMA_ID}`));
});
});
describe('Number tests', () => {
it('Type Number does not error on valid node', async () => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
type: 'object',
properties: {
timeout: {
type: 'number',
},
},
});
const content = 'timeout: 60000';
const result = await parseSetup(content);
assert_1.default.equal(result.length, 0);
});
it('Error on incorrect value type (number)', async () => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
type: 'object',
properties: {
cwd: {
type: 'string',
},
},
});
const content = 'cwd: 100000';
const result = await parseSetup(content);
assert_1.default.equal(result.length, 1);
assert_1.default.deepEqual(result[0], (0, verifyError_1.createDiagnosticWithData)(errorMessages_1.StringTypeError, 0, 5, 0, 11, vscode_languageserver_types_1.DiagnosticSeverity.Error, `yaml-schema: file:///${testHelper_1.SCHEMA_ID}`, `file:///${testHelper_1.SCHEMA_ID}`));
});
});
describe('Null tests', () => {
it('Basic test on nodes with null', async () => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
type: 'object',
additionalProperties: false,
properties: {
columns: {
type: 'object',
patternProperties: {
'^[a-zA-Z]+$': {
type: 'object',
properties: {
int: {
type: 'null',
},
long: {
type: 'null',
},
id: {
type: 'null',
},
unique: {
type: 'null',
},
},
oneOf: [
{
required: ['int'],
},
{
required: ['long'],
},
],
},
},
},
},
});
const content = 'columns:\n ColumnA: { int, id }\n ColumnB: { long, unique }\n ColumnC: { long, unique }';
const result = await parseSetup(content);
assert_1.default.equal(result.length, 0);
});
});
describe('Object tests', () => {
it('Basic test on nodes with children', async () => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
type: 'object',
properties: {
scripts: {
type: 'object',
properties: {
preinstall: {
type: 'string',
},
postinstall: {
type: 'string',
},
},
},
},
});
const content = 'scripts:\n preinstall: test1\n postinstall: test2';
const result = await parseSetup(content);
assert_1.default.equal(result.length, 0);
});
it('Test with multiple nodes with children', async () => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
type: 'object',
properties: {
analytics: {
type: 'boolean',
},
cwd: {
type: 'string',
},
scripts: {
type: 'object',
properties: {
preinstall: {
type: 'string',
},
postinstall: {
type: 'string',
},
},
},
},
});
const content = 'analytics: true\ncwd: this\nscripts:\n preinstall: test1\n postinstall: test2';
const result = await parseSetup(content);
assert_1.default.equal(result.length, 0);
});
it('Type Object does not error on valid node', async () => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
type: 'object',
properties: {
registry: {
type: 'object',
properties: {
search: {
type: 'string',
},
},
},
},
});
const content = 'registry:\n search: file://test_url';
const result = await parseSetup(content);
assert_1.default.equal(result.length, 0);
});
it('Error on incorrect value type (object)', async () => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
type: 'object',
title: 'Object',
properties: {
scripts: {
type: 'object',
properties: {
search: {
type: 'string',
},
},
},
},
});
const content = 'scripts: test';
const result = await parseSetup(content);
assert_1.default.equal(result.length, 1);
assert_1.default.deepEqual(result[0], (0, verifyError_1.createDiagnosticWithData)('Incorrect type. Expected "object(Object)".', 0, 9, 0, 13, vscode_languageserver_types_1.DiagnosticSeverity.Error, `yaml-schema: Object`, `file:///${testHelper_1.SCHEMA_ID}`));
});
});
describe('Array tests', () => {
it('Type Array does not error on valid node', async () => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
type: 'object',
properties: {
resolvers: {
type: 'array',
items: {
type: 'string',
},
},
},
});
const content = 'resolvers:\n - test\n - test\n - test';
const result = await parseSetup(content);
assert_1.default.equal(result.length, 0);
});
it('Error on incorrect value type (array)', async () => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
type: 'object',
properties: {
resolvers: {
type: 'array',
},
},
});
const content = 'resolvers: test';
const result = await parseSetup(content);
assert_1.default.equal(result.length, 1);
assert_1.default.deepEqual(result[0], (0, verifyError_1.createDiagnosticWithData)(errorMessages_1.ArrayTypeError, 0, 11, 0, 15, vscode_languageserver_types_1.DiagnosticSeverity.Error, `yaml-schema: file:///${testHelper_1.SCHEMA_ID}`, `file:///${testHelper_1.SCHEMA_ID}`));
});
});
describe('Anchor tests', () => {
it('Anchor should not error', async () => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
type: 'object',
properties: {
default: {
type: 'object',
properties: {
name: {
type: 'string',
},
},
},
},
});
const content = 'default: &DEFAULT\n name: Anchor\nanchor_test:\n <<: *DEFAULT';
const result = await parseSetup(content);
assert_1.default.equal(result.length, 0);
});
it('Anchor with multiple references should not error', async () => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
type: 'object',
properties: {
default: {
type: 'object',
properties: {
name: {
type: 'string',
},
},
},
},
});
const content = 'default: &DEFAULT\n name: Anchor\nanchor_test:\n <<: *DEFAULT\nanchor_test2:\n <<: *DEFAULT';
const result = await parseSetup(content);
assert_1.default.equal(result.length, 0);
});
it('Multiple Anchor in array of references should not error', async () => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
type: 'object',
properties: {
default: {
type: 'object',
properties: {
name: {
type: 'string',
},
},
},
},
});
const content = 'default: &DEFAULT\n name: Anchor\ncustomname: &CUSTOMNAME\n custom_name: Anchor\nanchor_test:\n <<: [*DEFAULT, *CUSTOMNAME]';
const result = await parseSetup(content);
assert_1.default.equal(result.length, 0);
});
it('Multiple Anchors being referenced in same level at same time for yaml 1.1', async () => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
type: 'object',
properties: {
customize: {
type: 'object',
properties: {
register: {
type: 'string',
},
},
},
},
});
const content = '%YAML 1.1\n---\ndefault: &DEFAULT\n name: Anchor\ncustomname: &CUSTOMNAME\n custom_name: Anchor\nanchor_test:\n <<: *DEFAULT\n <<: *CUSTOMNAME\n';
const result = await parseSetup(content);
assert_1.default.strictEqual(result.length, 0);
});
it('Multiple Anchors being referenced in same level at same time for yaml generate error for 1.2', async () => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
type: 'object',
properties: {
customize: {
type: 'object',
properties: {
register: {
type: 'string',
},
},
},
},
});
const content = 'default: &DEFAULT\n name: Anchor\ncustomname: &CUSTOMNAME\n custom_name: Anchor\nanchor_test:\n <<: *DEFAULT\n <<: *CUSTOMNAME\n';
const result = await parseSetup(content);
assert_1.default.strictEqual(result.length, 1);
assert_1.default.deepStrictEqual(result[0], (0, verifyError_1.createExpectedError)('Map keys must be unique', 6, 2, 6, 18, vscode_languageserver_types_1.DiagnosticSeverity.Error));
});
it('Nested object anchors should expand properly', async () => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
type: 'object',
additionalProperties: {
type: 'object',
additionalProperties: false,
properties: {
akey: {
type: 'string',
},
},
required: ['akey'],
},
});
const content = `
l1: &l1
akey: avalue
l2: &l2
<<: *l1
l3: &l3
<<: *l2
l4:
<<: *l3
`;
const validator = await parseSetup(content);
assert_1.default.strictEqual(validator.length, 0);
});
it('Anchor reference with a validation error in a sub-object emits the error in the right location', async () => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
type: 'object',
properties: {
src: {},
dest: {
type: 'object',
properties: {
outer: {
type: 'object',
required: ['otherkey'],
},
},
},
},
required: ['src', 'dest'],
});
const content = `
src: &src
outer:
akey: avalue
dest:
<<: *src
`;
const result = await parseSetup(content);
assert_1.default.equal(result.length, 1);
// The key thing we're checking is *where* the validation error gets reported.
// "outer" isn't required to contain "otherkey" inside "src", but it is inside
// "dest". Since "outer" doesn't appear inside "dest" because of the alias, we
// need to move the error into "src".
assert_1.default.deepEqual(result[0], (0, verifyError_1.createDiagnosticWithData)(errorMessages_1.MissingRequiredPropWarning.replace('{0}', 'otherkey'), 2, 10, 2, 15, vscode_languageserver_types_1.DiagnosticSeverity.Error, `yaml-schema: file:///${testHelper_1.SCHEMA_ID}`, `file:///${testHelper_1.SCHEMA_ID}`));
});
it('Array Anchor merge', async () => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
type: 'object',
properties: {
arr: {
type: 'array',
items: {
type: 'number',
},
},
obj: {
properties: {
arr2: {
type: 'array',
items: {
type: 'string',
},
},
},
},
},
});
const content = `
arr: &a
- 1
- 2
obj:
<<: *a
arr2:
- << *a
`;
const result = await parseSetup(content);
assert_1.default.equal(result.length, 0);
});
});
describe('Custom tag tests', () => {
it('Custom Tags without type', async () => {
configureCustomTags(['!Test']);
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
type: 'object',
properties: {
analytics: {
type: 'boolean',
},
},
});
const content = 'analytics: !Test false';
const result = await parseSetup(content);
assert_1.default.equal(result.length, 1);
assert_1.default.deepStrictEqual(result[0], (0, verifyError_1.createDiagnosticWithData)(errorMessages_1.BooleanTypeError, 0, 17, 0, 22, vscode_languageserver_types_1.DiagnosticSeverity.Error, `yaml-schema: file:///${testHelper_1.SCHEMA_ID}`, `file:///${testHelper_1.SCHEMA_ID}`));
});
it('Custom Tags with type', async () => {
configureCustomTags(['!Ref sequence']);
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
type: 'object',
properties: {
resolvers: {
type: 'array',
items: {
type: 'string',
},
},
},
});
const content = 'resolvers: !Ref\n - test';
const result = await parseSetup(content);
assert_1.default.equal(result.length, 0);
});
it('Include with value should not error', async () => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
type: 'object',
properties: {
customize: {
type: 'string',
},
},
});
const content = 'customize: !include customize.yaml';
const result = await parseSetup(content);
assert_1.default.equal(result.length, 0);
});
it('Include without value should error', async () => {
const content = 'customize: !include';
const result = await parseSetup(content);
assert_1.default.equal(result.length, 1);
assert_1.default.deepEqual(result[0], (0, verifyError_1.createExpectedError)(errorMessages_1.IncludeWithoutValueError, 0, 11, 0, 19));
});
it('Custom Tags with return type validate against evaluated schema type', async () => {
configureCustomTags(['!Ref scalar:string', '!FindInMap sequence:string']);
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
type: 'object',
properties: {
ImageId: {
type: 'string',
},
},
});
const content = 'ImageId: !FindInMap [AWSRegionArch2AMI, !Ref "AWS::Region", HVM64]';
const result = await parseSetup(content);
assert_1.default.equal(result.length, 0);
});
it('Custom Tags without return type still validate against input node type', async () => {
configureCustomTags(['!Ref scalar:string', '!FindInMap sequence']);
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
type: 'object',
properties: {
ImageId: {
type: 'string',
},
},
});
const content = 'ImageId: !FindInMap [AWSRegionArch2AMI, !Ref "AWS::Region", HVM64]';
const result = await parseSetup(content);
assert_1.default.equal(result.length, 1);
assert_1.default.ok(result[0].message.includes('Incorrect type. Expected "string".'));
});
});
describe('Multiple type tests', function () {
it('Do not error when there are multiple types in schema and theyre valid', async () => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
type: 'object',
properties: {
license: {
type: ['string', 'boolean'],
},
},
});
const content = 'license: MIT';
const result = await parseSetup(content);
assert_1.default.equal(result.length, 0);
});
});
describe('Invalid YAML errors', function () {
it('Error when theres a finished untyped item', async () => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
type: 'object',
properties: {
cwd: {
type: 'string',
},
analytics: {
type: 'boolean',
},
},
});
const content = 'cwd: hello\nan';
const result = await parseSetup(content);
assert_1.default.equal(result.length, 1);
assert_1.default.deepEqual(result[0], (0, verifyError_1.createExpectedError)(errorMessages_1.BlockMappingEntryError, 1, 0, 1, 2));
});
it('Error when theres no value for a node', async () => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
type: 'object',
properties: {
cwd: {
type: 'string',
},
},
});
const content = 'cwd:';
const result = await parseSetup(content);
assert_1.default.equal(result.length, 1);
assert_1.default.deepEqual(result[0], (0, verifyError_1.createDiagnosticWithData)(errorMessages_1.StringTypeError, 0, 4, 0, 4, vscode_languageserver_types_1.DiagnosticSeverity.Error, `yaml-schema: file:///${testHelper_1.SCHEMA_ID}`, `file:///${testHelper_1.SCHEMA_ID}`));
});
});
describe('Test with no schemas', () => {
it('Duplicate properties are reported', async () => {
const content = 'kind: a\ncwd: b\nkind: c';
const result = await parseSetup(content);
assert_1.default.equal(result.length, 1);
assert_1.default.deepEqual(result[0], (0, verifyError_1.createExpectedError)(errorMessages_1.DuplicateKeyError, 2, 0, 2, 7));
});
});
describe('Test anchors', function () {
it('Test that anchors with a schema do not report Property << is not allowed', async () => {
const schema = {
type: 'object',
properties: {
sample: {
type: 'object',
properties: {
prop1: {
type: 'string',
},
prop2: {
type: 'string',
},
},
additionalProperties: false,
},
},
$schema: 'http://json-schema.org/draft-07/schema#',
};
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
const content = 'test: &test\n prop1: hello\nsample:\n <<: *test\n prop2: another_test';
const result = await parseSetup(content);
assert_1.default.equal(result.length, 0);
});
});
describe('Test with custom kubernetes schemas', function () {
after(() => {
languageSettingsSetup.languageSettings.schemas.pop();
languageService.configure(languageSettingsSetup.languageSettings);
});
it('Test that properties that match multiple enums get validated properly', async () => {
languageService.configure(languageSettingsSetup.withKubernetes().languageSettings);
yamlSettings.specificValidatorPaths = ['*.yml', '*.yaml'];
const schema = {
definitions: {
ImageStreamImport: {
type: 'object',
properties: {
kind: {
type: 'string',
enum: ['ImageStreamImport'],
},
},
},
ImageStreamLayers: {
type: 'object',
properties: {
kind: {
type: 'string',
enum: ['ImageStreamLayers'],
},
},
},
},
oneOf: [
{
$ref: '#/definitions/ImageStreamImport',
},
{
$ref: '#/definitions/ImageStreamLayers',
},
],
};
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
const content = 'kind: ';
const result = await parseSetup(content);
assert_1.default.equal(result.length, 2);
assert_1.default.equal(result[1].message, `Value is not accepted. Valid values: "ImageStreamImport", "ImageStreamLayers".`);
});
it('does not report error for direct Kubernetes standalone-strict/all.json schema associations', async () => {
const schemaUri = 'https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.32.9-standalone-strict/all.json';
languageService.configure(new serviceSetup_1.ServiceSetup().withValidate().withSchemaFileMatch({ uri: schemaUri, fileMatch: ['*.yaml'] }).languageSettings);
yamlSettings.specificValidatorPaths = ['*.yaml'];
try {
const result = await parseSetup(`apiVersion: v1
kind: Service
metadata:
name: longhorn-ui-nodeport
namespace: longhorn-system
spec:
type: NodePort
ports:
- port: 80
targetPort: 80
nodePort: 30080
selector:
app: longhorn-ui`, 'file://~/Desktop/vscode-yaml/service.yaml');
(0, chai_1.expect)(result.map((diagnostic) => diagnostic.message)).not.include('Matches multiple schemas when only one must validate.');
(0, chai_1.expect)(result.map((diagnostic) => diagnostic.message)).deep.equal([]);
}
finally {
yamlSettings.specificValidatorPaths = [];
}
});
it('does not report error for direct Kubernetes standalone-strict/all.json modelines', async () => {
const schemaUri = 'https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.32.9-standalone-strict/all.json';
languageService.configure(languageSettingsSetup.withKubernetes(false).languageSettings);
yamlSettings.specificValidatorPaths = [];
try {
const result = await parseSetup(`# yaml-language-server: $schema=${schemaUri}
apiVersion: v1
kind: Service
metadata:
name: longhorn-ui-nodeport
namespace: longhorn-system
spec:
type: NodePort
ports:
- port: 80
targetPort: 80
nodePort: 30080
selector:
app: longhorn-ui`, 'file://~/Desktop/vscode-yaml/service.yaml');
(0, chai_1.expect)(result.map((diagnostic) => diagnostic.message)).not.include('Matches multiple schemas when only one must validate.');
(0, chai_1.expect)(result.map((diagnostic) => diagnostic.message)).deep.equal([]);
}
finally {
languageService.configure(languageSettingsSetup.withKubernetes().languageSettings);
}
});
it('Test that it validates against the correct schema based on the GroupVersionKind', async () => {
languageService.configure(languageSettingsSetup.withKubernetes().withSchemaFileMatch({ uri: KUBERNETES_SCHEMA_URL, fileMatch: ['*.yml', '*.yaml'] })
.languageSettings);
yamlSettings.specificValidatorPaths = ['*.yml', '*.yaml'];
const content = `apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: foo
spec:
foo: bar
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: foo
minReplicas: 2
maxReplicas: 3
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 80`;
const result = await parseSetup(content);
assert_1.default.equal(result.length, 1);
assert_1.default.equal(result[0].message, `Property foo is not allowed.`);
});
});
// https://github.com/redhat-developer/yaml-language-server/issues/118
describe('Null literals', () => {
['NULL', 'Null', 'null', '~', ''].forEach((content) => {
it(`Test type null is parsed from [${content}]`, async () => {
const schema = {
type: 'object',
properties: {
nulltest: {
type: 'null',
},
},
};
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
const result = await parseSetup('nulltest: ' + content);
assert_1.default.equal(result.length, 0);
});
});
it('Test type null is working correctly in array', async () => {
const schema = {
properties: {
values: {
type: 'array',
items: {
type: 'null',
},
},
},
required: ['values'],