diff --git a/lib/common/versionUtils.js b/lib/common/versionUtils.js index 9a50afe..5d98924 100644 --- a/lib/common/versionUtils.js +++ b/lib/common/versionUtils.js @@ -32,9 +32,67 @@ function getFileByContent(data) { return element.content.match(version2RegExp) || element.content.match(version3RegExp); }); if (!file) { - return { hasDefinedVersion: false }; + throw new Error('Not files with version'); } - return { hasDefinedVersion: true, content: file.content }; + return file.content; +} + +/** + * compares a version with an input + * @param {string} input The input to compare + * @param {string} version The version that will be used + * @returns {boolean} wheter the input corresponds to the version + */ +function compareVersion(input, version) { + let numberInput, + numberVersion; + numberInput = parseFloat(input); + numberVersion = parseFloat(version); + if (!isNaN(numberInput) && !isNaN(numberVersion)) { + return numberInput === numberVersion; + } + return false; +} + +/** + * Determins the version regex according to the specificationVersion + * @param {string} specificationVersion the string of the desired version + * @returns {object} the resultant regular expresion using the provided data + */ +function getVersionRegexBySpecificationVersion(specificationVersion) { + let versionRegExp; + if (compareVersion(specificationVersion, '2.0')) { + versionRegExp = getVersionRegexp(VERSION_20); + } + else if (compareVersion(specificationVersion, '3.1')) { + versionRegExp = getVersionRegexp(VERSION_31); + } + else { + versionRegExp = getVersionRegexp(VERSION_30); + } + return versionRegExp; +} + +/** + * When the array of files is provided as a list of parsed objects + * it returns the content from file that contains the version data + * specified in the specificationVersion parameter + * @param {array} data An array of the provided file's content parsed + * @param {string} specificationVersion the string of the desired version + * @returns {object} object with hasDefinedVersion property and + * The content of the file that contains the version data + */ +function getFileByContentSpecificationVersion(data, specificationVersion) { + let versionRegExp, + file; + versionRegExp = getVersionRegexBySpecificationVersion(specificationVersion); + file = data.find((element) => { + return element.content.match(versionRegExp); + }); + if (!file) { + throw new Error('Not files with version'); + } + return file.content; } /** @@ -57,14 +115,20 @@ function getFileByFileName(data) { /** When the user provides a folder, this function returns the file * that contains the version data * @param {array} data An array of file's paths + * @param {string} specificationVersion the string of the desired version * @returns {object} object with hasDefinedVersion property and * The content of the file that contains the version data */ -function getFileWithVersion(data) { +function getFileWithVersion(data, specificationVersion) { let file; if (data[0].hasOwnProperty('content')) { - file = getFileByContent(data); + if (specificationVersion) { + file = getFileByContentSpecificationVersion(data, specificationVersion); + } + else { + file = getFileByContent(data, specificationVersion); + } } else if (data[0].hasOwnProperty('fileName')) { file = getFileByFileName(data); @@ -77,13 +141,13 @@ function getFileWithVersion(data) { * @param {string} spec Data from input file * @returns {string} version of specification */ -function getSpecVersion({ type, data }) { +function getSpecVersion({ type, data, specificationVersion }) { if (!data) { return DEFAULT_SPEC_VERSION; } if (['folder'].includes(type)) { - data = getFileWithVersion(data); + data = getFileWithVersion(data, specificationVersion); } else if (['file'].includes(type)) { try { @@ -93,12 +157,6 @@ function getSpecVersion({ type, data }) { return DEFAULT_SPEC_VERSION; // If path is invalid it will follow the OAS 3.0 way } } - if (!data.hasDefinedVersion) { - return; - } - else { - data = data.content; - } if (type === 'json') { data = JSON.stringify(data); @@ -128,8 +186,8 @@ function getSpecVersion({ type, data }) { * @param {string} specVersion - the OAS specification version * @returns {NodeRequire} the schema utils according to version */ -function getConcreteSchemaUtils({ type, data }) { - const specVersion = getSpecVersion({ type, data }); +function getConcreteSchemaUtils({ type, data, specificationVersion }) { + const specVersion = getSpecVersion({ type, data, specificationVersion }); if (!specVersion) { return; } @@ -176,5 +234,7 @@ module.exports = { getSpecVersion, getConcreteSchemaUtils, filterOptionsByVersion, - isSwagger + isSwagger, + compareVersion, + getVersionRegexBySpecificationVersion }; diff --git a/lib/parse.js b/lib/parse.js index 2f337e0..8b75255 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -5,7 +5,8 @@ var yaml = require('js-yaml'), path = require('path'), pathBrowserify = require('path-browserify'), resolver = require('oas-resolver-browser'), - yamlParse = require('yaml'); + yamlParse = require('yaml'), + { compareVersion } = require('./common/versionUtils.js'); const BROWSER = 'browser'; module.exports = { @@ -79,9 +80,10 @@ module.exports = { * @param {Object} inputValidation Validator according to version * @param {Object} options computed process options * @param {Object} files Files map + * @param {string} specificationVersion the string of the desired version * @return {String} rootFile */ - getRootFiles: function (input, inputValidation, options, files = {}) { + getRootFiles: function (input, inputValidation, options, files = {}, specificationVersion) { let rootFilesArray = [], filesPathArray = input.data, origin = input.origin || ''; @@ -110,7 +112,14 @@ module.exports = { throw new Error(obj.reason); } if (inputValidation.validateSpec(oasObject, options).result) { - rootFilesArray.push(filePath.fileName); + if (specificationVersion) { + if (compareVersion(specificationVersion, oasObject.openapi)) { + rootFilesArray.push(filePath.fileName); + } + } + else { + rootFilesArray.push(filePath.fileName); + } } } catch (e) { diff --git a/lib/schemaUtils.js b/lib/schemaUtils.js index e5d53b8..9882317 100644 --- a/lib/schemaUtils.js +++ b/lib/schemaUtils.js @@ -4788,6 +4788,11 @@ module.exports = { }, inputValidation, + /** + * Maps the input from detect root files to get root files + * @param {object} input - input schema + * @returns {Array} - Array of all MISSING_ENDPOINT objects + */ mapDetectRootFilesInputToGetRootFilesInput(input) { let adaptedData = input.data.map((file) => { return { fileName: file.path }; @@ -4795,6 +4800,12 @@ module.exports = { return { data: adaptedData }; }, + /** + * Maps the output from get root files to detect root files + * @param {object} output - output schema + * @param {string} version - specified version of the process + * @returns {object} - Detect root files result object + */ mapGetRootFilesOutputToDetectRootFilesOutput(output, version) { let adaptedData = output.map((file) => { return { path: file }; diff --git a/lib/schemapack.js b/lib/schemapack.js index 9767cd3..524e716 100644 --- a/lib/schemapack.js +++ b/lib/schemapack.js @@ -52,9 +52,13 @@ class SchemaPack { this.computedOptions.schemaFaker = true; let indentCharacter = this.computedOptions.indentCharacter; this.computedOptions.indentCharacter = indentCharacter === 'tab' ? '\t' : ' '; - concreteUtils = getConcreteSchemaUtils(input); - this.hasDefinedVersion = concreteUtils !== undefined; - + try { + this.hasDefinedVersion = true; + concreteUtils = getConcreteSchemaUtils(input); + } + catch (error) { + this.hasDefinedVersion = false; + } this.validate(); } @@ -631,7 +635,8 @@ class SchemaPack { }); } adaptedInput = schemaUtils.mapDetectRootFilesInputToGetRootFilesInput(input); - rootFiles = parse.getRootFiles(adaptedInput, concreteUtils.inputValidation, this.computedOptions, files); + rootFiles = parse.getRootFiles(adaptedInput, concreteUtils.inputValidation, this.computedOptions, files, + input.specificationVersion); res = schemaUtils.mapGetRootFilesOutputToDetectRootFilesOutput(rootFiles, input.specificationVersion); return res; } diff --git a/test/unit/detectRoot.test.js b/test/unit/detectRoot.test.js index 3623352..cc8bdfd 100644 --- a/test/unit/detectRoot.test.js +++ b/test/unit/detectRoot.test.js @@ -3,11 +3,37 @@ var expect = require('chai').expect, fs = require('fs'), path = require('path'), VALID_OPENAPI_PATH = '../data/valid_openapi', + VALID_OPENAPI_31_PATH = '../data/valid_openapi31X', + PET_STORE_SEPARATED = '../data/petstore separate yaml/spec', + PET_STORE_SEPARATED_JSON = '../data/petstore-separate/spec', validPetstore = path.join(__dirname, VALID_OPENAPI_PATH + '/petstore.yaml'), - noauth = path.join(__dirname, VALID_OPENAPI_PATH + '/noauth.yaml'); + noauth = path.join(__dirname, VALID_OPENAPI_PATH + '/noauth.yaml'), + petstoreSeparated = path.join(__dirname, PET_STORE_SEPARATED + '/swagger.yaml'), + petstoreSeparatedPet = path.join(__dirname, PET_STORE_SEPARATED + '/pet.yaml'), + petstoreSeparatedJson = path.join(__dirname, PET_STORE_SEPARATED_JSON + '/swagger.json'), + petstoreSeparatedPetJson = path.join(__dirname, PET_STORE_SEPARATED_JSON + '/Pet.json'), + validHopService31x = path.join(__dirname, VALID_OPENAPI_31_PATH + '/yaml/hopService.yaml'); -describe('requestNameSource option', function() { +describe('detectRoot method', function() { + + it('should return one root 3.0 correctly no specific version', async function() { + let contentFile = fs.readFileSync(validPetstore, 'utf8'), + input = { + type: 'folder', + data: [ + { + path: '/petstore.yaml', + content: contentFile + } + ] + }; + const res = await Converter.detectRootFiles(input); + expect(res).to.not.be.empty; + expect(res.result).to.be.true; + expect(res.output.data[0].path).to.equal('/petstore.yaml'); + }); + it('should return one root 3.0 correctly', async function() { let contentFile = fs.readFileSync(validPetstore, 'utf8'), input = { @@ -26,10 +52,118 @@ describe('requestNameSource option', function() { expect(res.output.data[0].path).to.equal('/petstore.yaml'); }); + it('should return no root when specific version is not present', async function() { + let contentFile = fs.readFileSync(validPetstore, 'utf8'), + input = { + type: 'folder', + specificationVersion: '3.1', + data: [ + { + path: '/petstore.yaml', + content: contentFile + } + ] + }; + const res = await Converter.detectRootFiles(input); + expect(res).to.not.be.empty; + expect(res.result).to.be.true; + expect(res.output.data.length).to.equal(0); + }); + + it('should return one root 3.0 correctly with other files in folder', async function() { + let petRoot = fs.readFileSync(petstoreSeparated, 'utf8'), + petSchema = fs.readFileSync(petstoreSeparatedPet, 'utf8'), + input = { + type: 'folder', + specificationVersion: '3.0', + data: [ + { + path: '/swagger.yaml', + content: petRoot + }, + { + path: '/Pet.yaml', + content: petSchema + } + ] + }; + const res = await Converter.detectRootFiles(input); + expect(res).to.not.be.empty; + expect(res.result).to.be.true; + expect(res.output.data.length).to.equal(1); + expect(res.output.data[0].path).to.equal('/swagger.yaml'); + }); + + it('should return one root 3.1 correctly', async function() { + let contentFile = fs.readFileSync(validHopService31x, 'utf8'), + input = { + type: 'folder', + specificationVersion: '3.1', + data: [ + { + path: '/hopService.yaml', + content: contentFile + } + ] + }; + const res = await Converter.detectRootFiles(input); + expect(res).to.not.be.empty; + expect(res.result).to.be.true; + expect(res.output.data[0].path).to.equal('/hopService.yaml'); + }); + + it('should return one root when multiple versions are present correctly', async function() { + let petstoreContent = fs.readFileSync(validPetstore, 'utf8'), + hopService31x = fs.readFileSync(validHopService31x, 'utf8'), + input = { + type: 'folder', + specificationVersion: '3.0.0', + data: [ + { + path: '/petstore.yaml', + content: petstoreContent + }, + { + path: '/hopService.yaml', + content: hopService31x + } + ] + }; + const res = await Converter.detectRootFiles(input); + expect(res).to.not.be.empty; + expect(res.result).to.be.true; + expect(res.output.data.length).to.equal(1); + expect(res.output.data[0].path).to.equal('/petstore.yaml'); + }); + + it('should return one root when multiple versions are present correctly 3.1', async function() { + let petstoreContent = fs.readFileSync(validPetstore, 'utf8'), + hopService31x = fs.readFileSync(validHopService31x, 'utf8'), + input = { + type: 'folder', + specificationVersion: '3.1.0', + data: [ + { + path: '/petstore.yaml', + content: petstoreContent + }, + { + path: '/hopService.yaml', + content: hopService31x + } + ] + }; + const res = await Converter.detectRootFiles(input); + expect(res).to.not.be.empty; + expect(res.result).to.be.true; + expect(res.output.data.length).to.equal(1); + expect(res.output.data[0].path).to.equal('/hopService.yaml'); + }); + it('should return no root file when there is not a root file present', async function() { let input = { type: 'folder', - specificationVersion: '3.0', + specificationVersion: '3.0.0', data: [ { path: '/petstore.yaml', @@ -48,7 +182,7 @@ describe('requestNameSource option', function() { noAuthContent = fs.readFileSync(noauth, 'utf8'), input = { type: 'folder', - specificationVersion: '3.0', + specificationVersion: '3.0.0', data: [ { path: '/petstore.yaml', @@ -63,6 +197,7 @@ describe('requestNameSource option', function() { const res = await Converter.detectRootFiles(input); expect(res).to.not.be.empty; expect(res.result).to.be.true; + expect(res.output.data.length).to.equal(2); expect(res.output.data[0].path).to.equal('/petstore.yaml'); expect(res.output.data[1].path).to.equal('/noauth.yaml'); }); @@ -70,7 +205,7 @@ describe('requestNameSource option', function() { it('should propagate one error correctly', async function () { let input = { type: 'folder', - specificationVersion: '3.0', + specificationVersion: '3.0.0', data: [ { path: '', @@ -86,4 +221,28 @@ describe('requestNameSource option', function() { } }); + it('should return one root 3.0 correctly with other files in folder json', async function() { + let petRoot = fs.readFileSync(petstoreSeparatedJson, 'utf8'), + petSchema = fs.readFileSync(petstoreSeparatedPetJson, 'utf8'), + input = { + type: 'folder', + specificationVersion: '3.0', + data: [ + { + path: '/swagger.json', + content: petRoot + }, + { + path: '/Pet.json', + content: petSchema + } + ] + }; + const res = await Converter.detectRootFiles(input); + expect(res).to.not.be.empty; + expect(res.result).to.be.true; + expect(res.output.data.length).to.equal(1); + expect(res.output.data[0].path).to.equal('/swagger.json'); + }); + }); diff --git a/test/unit/parse.test.js b/test/unit/parse.test.js index f923d66..11cfb35 100644 --- a/test/unit/parse.test.js +++ b/test/unit/parse.test.js @@ -21,6 +21,22 @@ describe('PARSE FUNCTION TESTS', function() { expect(result[0]).to.equal(folderPath + '/index.yaml'); }); + it('getRootFiles should exclude files when the version is not the one in files', function() { + let folderPath = path.join(__dirname, '../data/multiFile_with_one_root'), + array = [ + { fileName: folderPath + '/index.yaml' }, + { fileName: folderPath + '/definitions/index.yaml' }, + { fileName: folderPath + '/definitions/User.yaml' }, + { fileName: folderPath + '/info/index.yaml' }, + { fileName: folderPath + '/paths/index.yaml' }, + { fileName: folderPath + '/paths/foo.yaml' }, + { fileName: folderPath + '/paths/bar.yaml' } + ], + result = parse.getRootFiles({ data: array, type: 'folder' }, inputValidation, {}, {}, + '2.0'); + expect(result.length).to.equal(0); + }); + it('getOasObject function should return a valid oas object from a yaml file', function() { let filePath = path.join(__dirname, '../data/multiFile_with_one_root/index.yaml'), file = fs.readFileSync(filePath, 'utf8'), diff --git a/test/unit/versionUtils.test.js b/test/unit/versionUtils.test.js index 358893b..c5d5942 100644 --- a/test/unit/versionUtils.test.js +++ b/test/unit/versionUtils.test.js @@ -1,4 +1,7 @@ -const { getSpecVersion, filterOptionsByVersion } = require('../../lib/common/versionUtils'), +const { getSpecVersion, + filterOptionsByVersion, + compareVersion, + getVersionRegexBySpecificationVersion } = require('../../lib/common/versionUtils'), expect = require('chai').expect; describe('getSpecVersion', function() { @@ -274,3 +277,54 @@ describe('filterOptionsByVersion method', function() { })).to.include.members(['optionC', 'optionA']); }); }); + +describe('compareVersion method', function () { + it('should return true when input and version are equal', function () { + const result = compareVersion('3.0.0', '3.0.0'); + expect(result).to.be.true; + }); + it('should return false when input and version are different', function () { + const result = compareVersion('3.1.0', '3.0.0'); + expect(result).to.be.false; + }); + it('should return true when input and version are semantically equal', function () { + const result = compareVersion('3.0', '3.0.0'); + expect(result).to.be.true; + }); + it('should return false when input is not a valid version string', function () { + const result = compareVersion('invalid', '3.0.0'); + expect(result).to.be.false; + }); + it('should return false when version is not a valid version string', function () { + const result = compareVersion('3.0.0', 'invalid'); + expect(result).to.be.false; + }); + it('should return false when version and input are not valid', function () { + const result = compareVersion('invalid', 'invalid'); + expect(result).to.be.false; + }); +}); + +describe('getVersionRegexBySpecificationVersion method', function () { + it('should return regex for 3.0', function () { + const result = getVersionRegexBySpecificationVersion('3.0'); + expect(result.toString()).to.equal('/openapi[\'|\"]?:\\s?[\\]?[\'|\"]?3.0/'); + }); + it('should return regex for 3.0.0', function () { + const result = getVersionRegexBySpecificationVersion('3.0.0'); + expect(result.toString()).to.equal('/openapi[\'|\"]?:\\s?[\\]?[\'|\"]?3.0/'); + }); + it('should return regex for 3.1', function () { + const result = getVersionRegexBySpecificationVersion('3.1'); + expect(result.toString()).to.equal('/openapi[\'|\"]?:\\s?[\\]?[\'|\"]?3.1/'); + }); + it('should return regex for 2.0', function () { + const result = getVersionRegexBySpecificationVersion('2.0'); + expect(result.toString()).to.equal('/swagger[\'|\"]?:\\s?[\\]?[\'|\"]?2.0/'); + }); + it('should return regex for 3.0 as default', function () { + const result = getVersionRegexBySpecificationVersion('invalid'); + expect(result.toString()).to.equal('/openapi[\'|\"]?:\\s?[\\]?[\'|\"]?3.0/'); + }); +}); +