UNPKG

openapi-examples-validator

Version:
448 lines (444 loc) 22.6 kB
const path = require('path'), structuredClone = require('core-js-pure/actual/structured-clone'), { loadTestData, normalizeValidationResultPaths } = require('../../../util/setup-tests'), { validateExample, 'default': validateExamples, validateExamplesByMap } = require('../../../../src/index'), { ApplicationError, ErrorType } = require('../../../../src/application-error'); const { prepare } = require('../../../../src/impl/v2'); const { validateFile } = require('../../../../src'); const PATH__SCHEMA_EXTERNAL_EXAMPLE = '$.paths./.get.responses.200.schema', PATH__SCHEMA_EXTERNAL_EXAMPLE_INVALID = '$.hmm.what.am.i.gonna.get.for.lunch', FILE_PATH__DATA = path.join(__dirname, '..', '..', '..', 'data', 'v2'), FILE_PATH__EXTERNAL_EXAMPLES_SCHEMA = path.join(FILE_PATH__DATA, 'external-examples-schema.json'), FILE_PATH__EXTERNAL_EXAMPLE1_VALID = path.join(FILE_PATH__DATA, 'external-examples-valid-example1.json'), FILE_PATH__EXTERNAL_EXAMPLE2_VALID = path.join(FILE_PATH__DATA, 'external-examples-valid-example2.json'), FILE_PATH__EXTERNAL_EXAMPLES_GLOB = path.join(FILE_PATH__DATA, 'map-external-examples-glob-invalid*.json'), FILE_PATH__EXTERNAL_EXAMPLES_GLOB_INVALID1 = path.join(FILE_PATH__DATA, 'map-external-examples-glob-invalid1.json'), FILE_PATH__EXTERNAL_EXAMPLES_GLOB_INVALID2 = path.join(FILE_PATH__DATA, 'map-external-examples-glob-invalid2.json'), FILE_PATH__EXTERNAL_EXAMPLES_MAP = path.join(FILE_PATH__DATA, 'map-external-examples.json'), FILE_PATH__EXTERNAL_EXAMPLES_MAP_RELATIVE = path.join(FILE_PATH__DATA, 'map-external-examples-relative.json'), FILE_PATH__EXTERNAL_EXAMPLES_MAP_WITH_WILDCARDS = path.join(FILE_PATH__DATA, 'map-external-examples-with-wildcards.json'), FILE_PATH__EXTERNAL_EXAMPLES_MAP_WITHOUT_WILDCARDS = path.join(FILE_PATH__DATA, 'map-external-examples-without-wildcards.json'), FILE_PATH__EXTERNAL_EXAMPLES_MAP_WITH_WRONG_SCHEMA_PATH = path.join(FILE_PATH__DATA, 'map-external-examples-map-with-wrong-schema-path.json'), FILE_PATH__EXTERNAL_EXAMPLES_MAP_WITH_MISSING_EXAMPLE = path.join(FILE_PATH__DATA, 'map-external-examples-map-with-missing-examples.json'), FILE_PATH__NOT_EXISTS = 'there is no spoon', FILE_PATH__EXTERNAL_EXAMPLE_INVALID_TYPE = path.join('test', 'data', 'v2', 'external-examples-invalid-type.json'), FILE_PATH__EXTERNAL_EXAMPLE_INVALID_MISSING_LINK = path.join('test', 'data', 'v2', 'external-examples-invalid-missing-link.json'), FILE_PATH__VALID__RESPONSE__EXAMPLES_CONTENT_ARRAY = path.join(__dirname, '../../../data/v2/response-valid-content-array.yaml'); describe('Main-module, for v2 should', () => { describe('recognize', () => { it('valid single example', async() => { (await validateExamples(loadTestData('v2/valid-single-example'))).valid.should.equal(true); }); it('valid multiple examples', async() => { (await validateExamples(loadTestData('v2/valid-multiple-examples'))).valid.should.equal(true); }); it('valid array-example', async() => { (await validateExamples(loadTestData('v2/valid-array-response'))).valid.should.equal(true); }); }); describe('ignore', () => { it('responses without schema', async() => { (await validateExamples(loadTestData('v2/valid-without-schema'))).valid.should.equal(true); }); it('responses without examples', async() => { (await validateExamples(loadTestData('v2/valid-without-examples'))).valid.should.equal(true); }); }); describe('find error:', () => { it('invalid type', async() => { const result = await validateExamples(loadTestData('v2/invalid-type')); result.valid.should.equal(false); result.errors.should.deep.equal([new ApplicationError(ErrorType.validation, { instancePath: '/versions/0/id', keyword: 'type', message: 'must be string', params: { type: 'string' }, schemaPath: '#/properties/versions/items/properties/id/type', examplePath: '/paths/~1/get/responses/200/examples/application~1json' })]); }); it('multiple errors', async() => { const result = await validateExamples(loadTestData('v2/multiple-errors')); result.valid.should.equal(false); result.errors.should.deep.equal([ new ApplicationError(ErrorType.validation, { keyword: 'type', instancePath: '/versions/0/id', schemaPath: '#/properties/versions/items/properties/id/type', params: { type: 'string' }, message: 'must be string', examplePath: '/paths/~1/get/responses/200/examples/application~1json' }), new ApplicationError(ErrorType.validation, { keyword: 'required', instancePath: '/versions/0', schemaPath: '#/properties/versions/items/required', params: { missingProperty: 'links' }, message: "must have required property 'links'", examplePath: '/paths/~1/get/responses/300/examples/application~1json' }), new ApplicationError(ErrorType.validation, { keyword: 'type', instancePath: '/versions/1/id', schemaPath: '#/properties/versions/items/properties/id/type', params: { type: 'string' }, message: 'must be string', examplePath: '/paths/~1/get/responses/200/examples/application~1json' }) ]); }); describe('In array-response:', () => { it('multiple errors', async() => { const result = await validateExamples(loadTestData('v2/invalid-array-response')); result.valid.should.equal(false); result.errors.should.deep.equal([ new ApplicationError(ErrorType.validation, { keyword: 'required', instancePath: '/0', schemaPath: '#/items/required', params: { missingProperty: 'id' }, message: "must have required property 'id'", examplePath: '/paths/~1/get/responses/200/examples/application~1json' }), new ApplicationError(ErrorType.validation, { keyword: 'type', instancePath: '/1/links', schemaPath: '#/items/properties/links/type', params: { type: 'array' }, message: 'must be array', examplePath: '/paths/~1/get/responses/200/examples/application~1json' }) ]); }); }); }); describe('should be able to validate external examples', () => { it('without errors', async() => { (await validateExample( FILE_PATH__EXTERNAL_EXAMPLES_SCHEMA, PATH__SCHEMA_EXTERNAL_EXAMPLE, FILE_PATH__EXTERNAL_EXAMPLE1_VALID )).valid.should.equal(true); (await validateExample( FILE_PATH__EXTERNAL_EXAMPLES_SCHEMA, PATH__SCHEMA_EXTERNAL_EXAMPLE, FILE_PATH__EXTERNAL_EXAMPLE2_VALID )).valid.should.equal(true); }); describe('with errors', () => { it('(type error)', async() => { const result = await validateExample( FILE_PATH__EXTERNAL_EXAMPLES_SCHEMA, PATH__SCHEMA_EXTERNAL_EXAMPLE, FILE_PATH__EXTERNAL_EXAMPLE_INVALID_TYPE ); result.valid.should.equal(false); result.errors.should.deep.equal([new ApplicationError(ErrorType.validation, { instancePath: '/versions/0/id', keyword: 'type', message: 'must be string', params: { type: 'string' }, schemaPath: '#/properties/versions/items/properties/id/type', exampleFilePath: FILE_PATH__EXTERNAL_EXAMPLE_INVALID_TYPE })]); }); }); it('with an example-map', async() => { const result = await validateExamplesByMap(FILE_PATH__EXTERNAL_EXAMPLES_SCHEMA, FILE_PATH__EXTERNAL_EXAMPLES_MAP); normalizeValidationResultPaths(result); result.valid.should.equal(false); result.errors.should.deep.equal([ new ApplicationError(ErrorType.validation, { instancePath: '/versions/0/id', keyword: 'type', message: 'must be string', params: { type: 'string' }, schemaPath: '#/properties/versions/items/properties/id/type', mapFilePath: FILE_PATH__EXTERNAL_EXAMPLES_MAP, exampleFilePath: path.normalize('test/data/v2/external-examples-invalid-type.json') }), new ApplicationError(ErrorType.validation, { instancePath: '/versions/0', keyword: 'required', message: "must have required property 'links'", params: { missingProperty: 'links' }, schemaPath: '#/properties/versions/items/required', mapFilePath: FILE_PATH__EXTERNAL_EXAMPLES_MAP, exampleFilePath: FILE_PATH__EXTERNAL_EXAMPLE_INVALID_MISSING_LINK }) ]); }); it('should be able to expand examples wildcards', async() => { const result = structuredClone( await validateExamplesByMap(FILE_PATH__EXTERNAL_EXAMPLES_SCHEMA, FILE_PATH__EXTERNAL_EXAMPLES_MAP_WITH_WILDCARDS) ); result.valid.should.equal(false); result.statistics.should.deep.equal({ examplesTotal: 7, examplesWithoutSchema: 0, matchingFilePathsMapping: 1, schemasWithExamples: 2 }); result.errors.should.deep.equal([{ type: 'Validation', message: "must have required property 'links'", instancePath: '/versions/0', schemaPath: '#/properties/versions/items/required', keyword: 'required', exampleFilePath: path.normalize('test/data/v2/external-examples-invalid-missing-link.json'), mapFilePath: path.normalize( path.join(__dirname, '../../../../test/data/v2/map-external-examples-with-wildcards.json')), params: { missingProperty: 'links' } }, { type: 'Validation', message: 'must be string', instancePath: '/versions/0/id', schemaPath: '#/properties/versions/items/properties/id/type', keyword: 'type', exampleFilePath: path.normalize('test/data/v2/external-examples-invalid-type.json'), mapFilePath: path.normalize( path.join(__dirname, '../../../../test/data/v2/map-external-examples-with-wildcards.json')), params: { type: 'string' } }]); }); it('map of examples with wildcards should be equal to without wildcards', async() => { const resultWithWildcards = await validateExamplesByMap(FILE_PATH__EXTERNAL_EXAMPLES_SCHEMA, FILE_PATH__EXTERNAL_EXAMPLES_MAP_WITH_WILDCARDS); const resultWithoutWildcards = await validateExamplesByMap(FILE_PATH__EXTERNAL_EXAMPLES_SCHEMA, FILE_PATH__EXTERNAL_EXAMPLES_MAP_WITHOUT_WILDCARDS); resultWithWildcards.valid.should.equal(resultWithoutWildcards.valid); resultWithWildcards.statistics.should.deep.equal(resultWithoutWildcards.statistics); removeMapFilePath(resultWithWildcards.errors).should.deep.equal( removeMapFilePath(resultWithoutWildcards.errors)); }); }); describe('should throw errors', () => { describe("when files can't be found:", () => { it('the mapping-file', async() => { const result = await validateExamplesByMap(FILE_PATH__EXTERNAL_EXAMPLES_SCHEMA, FILE_PATH__NOT_EXISTS); result.valid.should.equal(false); result.errors.should.deep.equal([ new ApplicationError(ErrorType.jsENOENT, { message: `ENOENT: no such file or directory, open '${ FILE_PATH__NOT_EXISTS }'`, params: { path: FILE_PATH__NOT_EXISTS } }) ]); }); it('referenced example-file in the mapping-file', async() => { const result = await validateExamplesByMap(FILE_PATH__EXTERNAL_EXAMPLES_SCHEMA, FILE_PATH__EXTERNAL_EXAMPLES_MAP_WITH_MISSING_EXAMPLE); normalizeValidationResultPaths(result); result.valid.should.equal(false); result.errors.should.deep.equal([ new ApplicationError(ErrorType.jsENOENT, { mapFilePath: FILE_PATH__EXTERNAL_EXAMPLES_MAP_WITH_MISSING_EXAMPLE, message: "No such file or directory: 'test/data/v2/blegh forgot the sugar in the" + " coffee'", params: { path: 'test/data/v2/blegh forgot the sugar in the coffee' } }), new ApplicationError(ErrorType.validation, { instancePath: '/versions/0', keyword: 'required', message: "must have required property 'links'", params: { missingProperty: 'links' }, schemaPath: '#/properties/versions/items/required', mapFilePath: FILE_PATH__EXTERNAL_EXAMPLES_MAP_WITH_MISSING_EXAMPLE, exampleFilePath: FILE_PATH__EXTERNAL_EXAMPLE_INVALID_MISSING_LINK }) ]); }); it('the example-file', async() => { const result = (await validateExample( FILE_PATH__EXTERNAL_EXAMPLES_SCHEMA, PATH__SCHEMA_EXTERNAL_EXAMPLE, FILE_PATH__NOT_EXISTS )); result.valid.should.equal(false); result.errors.should.deep.equal([ new ApplicationError(ErrorType.jsENOENT, { message: `ENOENT: no such file or directory, open '${ FILE_PATH__NOT_EXISTS }'`, params: { path: FILE_PATH__NOT_EXISTS } }) ]); }); }); describe("when the response-schema can't be found", () => { it('while validating a single external example', async() => { const result = await validateExample( FILE_PATH__EXTERNAL_EXAMPLES_SCHEMA, PATH__SCHEMA_EXTERNAL_EXAMPLE_INVALID, FILE_PATH__EXTERNAL_EXAMPLE1_VALID ); result.valid.should.equal(false); result.errors.should.deep.equal([ new ApplicationError(ErrorType.jsonPathNotFound, { message: "Path to schema can't be found: " + `'${ PATH__SCHEMA_EXTERNAL_EXAMPLE_INVALID }'`, params: { path: PATH__SCHEMA_EXTERNAL_EXAMPLE_INVALID }, type: 'JsonPathNotFound' }) ]); }); it('while validating a map of external examples', async() => { const result = await validateExamplesByMap(FILE_PATH__EXTERNAL_EXAMPLES_SCHEMA, FILE_PATH__EXTERNAL_EXAMPLES_MAP_WITH_WRONG_SCHEMA_PATH); normalizeValidationResultPaths(result); result.valid.should.equal(false); result.errors.should.deep.equal([ new ApplicationError(ErrorType.jsonPathNotFound, { mapFilePath: FILE_PATH__EXTERNAL_EXAMPLES_MAP_WITH_WRONG_SCHEMA_PATH, message: "Path to schema can't be found: " + `'${ PATH__SCHEMA_EXTERNAL_EXAMPLE_INVALID }'`, params: { path: PATH__SCHEMA_EXTERNAL_EXAMPLE_INVALID } }) ]); }); }); }); describe('should be able to resolve globs for mapping-files', () => { it('and collect the errors for all mapping-files', async() => { const result = await validateExamplesByMap(FILE_PATH__EXTERNAL_EXAMPLES_SCHEMA, FILE_PATH__EXTERNAL_EXAMPLES_GLOB); normalizeValidationResultPaths(result); result.valid.should.equal(false); result.errors.should.deep.equal([ new ApplicationError(ErrorType.validation, { message: "must have required property 'links'", keyword: 'required', instancePath: '/versions/0', schemaPath: '#/properties/versions/items/required', params: { missingProperty: 'links' }, exampleFilePath: FILE_PATH__EXTERNAL_EXAMPLE_INVALID_MISSING_LINK, mapFilePath: FILE_PATH__EXTERNAL_EXAMPLES_GLOB_INVALID1 }), new ApplicationError(ErrorType.validation, { message: 'must be string', keyword: 'type', instancePath: '/versions/0/id', schemaPath: '#/properties/versions/items/properties/id/type', params: { type: 'string' }, exampleFilePath: FILE_PATH__EXTERNAL_EXAMPLE_INVALID_TYPE, mapFilePath: FILE_PATH__EXTERNAL_EXAMPLES_GLOB_INVALID1 }), new ApplicationError(ErrorType.validation, { message: "must have required property 'links'", keyword: 'required', instancePath: '/versions/0', schemaPath: '#/properties/versions/items/required', params: { missingProperty: 'links' }, exampleFilePath: FILE_PATH__EXTERNAL_EXAMPLE_INVALID_MISSING_LINK, mapFilePath: FILE_PATH__EXTERNAL_EXAMPLES_GLOB_INVALID2 }) ]); }); it('should collect the statistics over all mapping-files', async() => { structuredClone( await validateExamplesByMap(FILE_PATH__EXTERNAL_EXAMPLES_SCHEMA, FILE_PATH__EXTERNAL_EXAMPLES_GLOB) ).statistics.should.deep.equal({ schemasWithExamples: 4, examplesWithoutSchema: 0, examplesTotal: 7, matchingFilePathsMapping: 2 }); }); }); describe('with set `cwd-to-mapping-file`-flag', () => { it('resolve the relative paths in the mapping-files', async() => { const result = structuredClone( await validateExamplesByMap(FILE_PATH__EXTERNAL_EXAMPLES_SCHEMA, FILE_PATH__EXTERNAL_EXAMPLES_MAP_RELATIVE, { cwdToMappingFile: true }) ); result.valid.should.equal(true); result.statistics.should.deep.equal({ schemasWithExamples: 2, examplesWithoutSchema: 0, examplesTotal: 3, matchingFilePathsMapping: 1 }); }); }); describe('prepare', () => { it('should set noAdditionalProperties in the openapiSpec, if flag provided', async() => { const preparedOpenapi = prepare( loadTestData('v2/valid-without-examples'), { noAdditionalProperties: true } ); preparedOpenapi.should.deep.equal(loadTestData('v2/valid-with-no-additional-properties')); }); it('should set allPropertiesRequired in the openapiSpec, if flag provided', async() => { const preparedOpenapi = prepare( loadTestData('v2/valid-without-examples'), { allPropertiesRequired: true } ); preparedOpenapi.should.deep.equal(loadTestData('v2/valid-with-all-properties-required')); }); }); describe('handle elements named "content" which are Arrays', function() { it('should validate successfully', async function() { const { valid, statistics } = structuredClone( await validateFile(FILE_PATH__VALID__RESPONSE__EXAMPLES_CONTENT_ARRAY) ); valid.should.equal(true); statistics.should.deep.equal({ schemasWithExamples: 1, examplesWithoutSchema: 0, examplesTotal: 1 }); }); }); }); function removeMapFilePath(errors) { return errors.map(error => { delete error.mapFilePath; return error; }); }