azure-resource-manager-schemas/tools/test/schemas.test.ts

212 строки
7.4 KiB
TypeScript

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import Ajv from 'ajv';
import * as url from 'url';
import path from 'path';
import fs from 'fs';
import { readFile } from 'fs/promises';
import { getLanguageService } from 'vscode-json-languageservice';
import { TextDocument } from 'vscode-languageserver-textdocument';
import draft4MetaSchema from 'ajv/lib/refs/json-schema-draft-04.json';
import { findCycle } from '../src/cycleCheck';
const schemasFolder = path.join(__dirname, '../../schemas/');
const testSchemasFolder = path.join(__dirname, '../testSchemas/');
const templateTestsFolder = path.join(__dirname, '../templateTests/');
const armSchemasPrefix = /^https?:\/\/schema\.management\.azure\.com\/schemas\//
const jsonSchemaDraft4Prefix = /^https?:\/\/json-schema\.org\/draft-04\/schema/
const ajvInstance = new Ajv({
loadSchema: loadSchema,
strictDefaults: true,
schemaId: 'id',
meta: true,
}).addMetaSchema(draft4MetaSchema)
.addFormat('int32', /.*/)
.addFormat('duration', /.*/)
.addFormat('password', /.*/);
async function loadRawSchema(uri: string): Promise<string> {
const hashIndex = uri.indexOf("#");
if (hashIndex !== -1) {
uri = uri.substring(0, hashIndex);
}
let jsonPath: string;
if (uri.match(armSchemasPrefix)) {
jsonPath = uri.replace(armSchemasPrefix, schemasFolder);
}
else if (uri.match(jsonSchemaDraft4Prefix)) {
return JSON.stringify(draft4MetaSchema);
}
else {
jsonPath = uri;
}
if (jsonPath.startsWith("http:") || jsonPath.startsWith("https:")) {
throw new Error(`Unsupported JSON path ${jsonPath}`);
}
return await readFile(jsonPath, { encoding: "utf8" });
}
async function loadSchema(uri: string): Promise<object> {
const rawSchema = await loadRawSchema(uri);
return JSON.parse(rawSchema);
}
function listSchemaPaths(basePath: string): string[] {
let results: string[] = [];
for (const subPathName of fs.readdirSync(basePath)) {
const subPath = path.resolve(`${basePath}/${subPathName}`);
const fileStat = fs.statSync(subPath);
if (fileStat.isDirectory()) {
const pathResults = listSchemaPaths(subPath);
results = results.concat(pathResults);
continue;
}
if (!fileStat.isFile() || path.extname(subPath).toLowerCase() !== '.json') {
continue;
}
results.push(subPath);
}
return results;
}
const metaSchemaPaths = [
'http://json-schema.org/draft-04/schema',
testSchemasFolder + 'ResourceMetaSchema.json',
];
// Cyclic schemas cause an issue for ARM export, but we have a few already
// 'known' bad schemas. Please do not add to this list unless you are sure
// this will not cause a problem in ARM.
const schemasToSkipForCyclicValidation = new Set([
'2017-09-01-preview/Microsoft.DataFactory.json',
'2018-06-01/Microsoft.DataFactory.json',
'2018-07-01/Microsoft.Media.json',
'2018-11-01-preview/Microsoft.Billing.json',
].map(p => path.resolve(`${schemasFolder}/${p}`)));
const schemasToSkip = [
'0.0.1-preview/CreateUIDefinition.CommonControl.json',
'0.0.1-preview/CreateUIDefinition.MultiVm.json',
'0.0.1-preview/CreateUIDefinition.ProviderControl.json',
'0.1.0-preview/CreateUIDefinition.CommonControl.json',
'0.1.0-preview/CreateUIDefinition.MultiVm.json',
'0.1.0-preview/CreateUIDefinition.ProviderControl.json',
'0.1.1-preview/CreateUIDefinition.CommonControl.json',
'0.1.1-preview/CreateUIDefinition.MultiVm.json',
'0.1.1-preview/CreateUIDefinition.ProviderControl.json',
'0.1.2-preview/CreateUIDefinition.CommonControl.json',
'0.1.2-preview/CreateUIDefinition.MultiVm.json',
'0.1.2-preview/CreateUIDefinition.ProviderControl.json',
'2014-04-01-preview/deploymentParameters.json',
'2014-04-01-preview/deploymentTemplate.json',
'2015-01-01/deploymentParameters.json',
'2015-01-01/deploymentTemplate.json',
'2015-10-01-preview/policyDefinition.json',
'2016-12-01/policyDefinition.json',
'2018-05-01/policyDefinition.json',
'2019-01-01/policyDefinition.json',
'2019-06-01/policyDefinition.json',
'2019-09-01/policyDefinition.json',
'2020-09-01/policyDefinition.json',
'2020-10-01/policyDefinition.json',
'2018-05-01/subscriptionDeploymentParameters.json',
'2018-05-01/subscriptionDeploymentTemplate.json',
'2019-04-01/deploymentParameters.json',
'2019-04-01/deploymentTemplate.json',
'2019-03-01-hybrid/deploymentTemplate.json',
'2019-03-01-hybrid/deploymentParameters.json',
'2019-08-01/managementGroupDeploymentParameters.json',
'2019-08-01/managementGroupDeploymentTemplate.json',
'2019-08-01/tenantDeploymentParameters.json',
'2019-08-01/tenantDeploymentTemplate.json',
'2021-09-09/uiFormDefinition.schema.json',
'common/definitions.json',
'common/manuallyAddedResources.json',
'common/autogeneratedResources.json',
'viewdefinition/0.0.1-preview/ViewDefinition.json',
].map(p => path.resolve(`${schemasFolder}/${p}`));
const schemaPaths = listSchemaPaths(schemasFolder).filter(path => schemasToSkip.indexOf(path) == -1);
const templateTestPaths = listSchemaPaths(templateTestsFolder);
const TIMEOUT_1_MINUTE = 60000;
describe('Validate individual resource schemas', () => {
it(`can be parsed with JSON.parse`, async function () {
for (const schemaPath of schemaPaths) {
const schema = await loadRawSchema(schemaPath);
expect(() => JSON.parse(schema)).not.toThrow();
}
}, TIMEOUT_1_MINUTE);
for (const metaSchemaPath of metaSchemaPaths) {
it(`validates against '${metaSchemaPath}'`, async function () {
for (const schemaPath of schemaPaths) {
const schema = await loadSchema(schemaPath);
const metaSchema = await loadSchema(metaSchemaPath);
const validate = await ajvInstance.compileAsync(metaSchema);
const result = await validate(schema);
if (!result) {
console.error(`Validating ${schemaPath} failed with errors ${JSON.stringify(validate.errors, null, 2)}`);
}
expect(result).toBeTruthy();
}
}, TIMEOUT_1_MINUTE);
}
it(`can be compiled`, async function () {
for (const schemaPath of schemaPaths) {
const schema = await loadSchema(schemaPath);
expect(() => ajvInstance.compile(schema)).not.toThrow();
}
}, TIMEOUT_1_MINUTE);
it(`does not contain any cycles`, async function () {
for (const schemaPath of schemaPaths) {
if (!schemasToSkipForCyclicValidation.has(schemaPath)) {
const schema = await loadSchema(schemaPath);
const cycle = findCycle(schema);
if (cycle) {
console.error(`Found ${schemaPath} cycle ${cycle?.join(' -> ')}`);
}
expect(cycle).toBeUndefined();
}
}
}, TIMEOUT_1_MINUTE);
});
describe('Validate test templates against VSCode language service', () => {
for (const templateTestFile of templateTestPaths) {
it(`running schema validation on '${templateTestFile}'`, async function () {
const service = getLanguageService({
schemaRequestService: loadRawSchema,
workspaceContext: {
resolveRelativePath: (relativePath, resource) => url.resolve(resource, relativePath)
},
});
const content = await readFile(templateTestFile, { encoding: 'utf8' });
const textDocument = TextDocument.create(templateTestFile, 'json', 0, content);
const jsonDocument = service.parseJSONDocument(textDocument);
const result = await service.doValidation(textDocument, jsonDocument);
expect(result).toEqual([]);
}, TIMEOUT_1_MINUTE);
}
});