UNPKG

yaml-language-server

Version:
1,150 lines (1,145 loc) 111 kB
"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'],