From 2e73ed22bbf561c158ae04c5f24f61be4776af01 Mon Sep 17 00:00:00 2001 From: Luis Tejeda <46000487+LuisTejedaS@users.noreply.github.com> Date: Mon, 6 Jun 2022 11:23:39 -0500 Subject: [PATCH] Fix input validation in multi file APIs --- lib/schemaUtils.js | 43 +++++++++++++++++++++ lib/schemapack.js | 8 ++-- test/unit/bundle.test.js | 46 ++++++++++++++++++++++ test/unit/detectRelatedFiles.test.js | 58 ++++++++++++++++++++++++---- test/unit/detectRoot.test.js | 43 ++++++++++++++++++++- 5 files changed, 186 insertions(+), 12 deletions(-) diff --git a/lib/schemaUtils.js b/lib/schemaUtils.js index 80ff378..7239f54 100644 --- a/lib/schemaUtils.js +++ b/lib/schemaUtils.js @@ -4815,6 +4815,16 @@ module.exports = { }; }, + /** + * + * @description Takes in a the root files obtains the related files and + * generates the result object + * @param {object} parsedRootFiles - found parsed root files + * @param {array} inputData - file data information [{path, content}] + * @param {Array} origin - process origin (BROWSER or node) + * + * @returns {object} process result { rootFile, relatedFiles, missingRelatedFiles } + */ getRelatedFilesData(parsedRootFiles, inputData, origin) { const data = parsedRootFiles.map((root) => { let relatedData = getRelatedFiles(root, inputData, origin), @@ -4828,6 +4838,17 @@ module.exports = { return data; }, + /* + * + * @description Takes in parsed root files and bundle it + * @param {object} parsedRootFiles - found parsed root files + * @param {array} inputData - file data information [{path, content}] + * @param {Array} origin - process origin (BROWSER or node) + * @param {string} format - output format could be either YAML or JSON + * @param {string} version - specification version specified in the input + * + * @returns {object} process result { rootFile, bundledContent } + */ getBundledFileData(parsedRootFiles, inputData, origin, format) { const data = parsedRootFiles.map((root) => { let bundleData = getBundleContentAndComponents(root, inputData, origin); @@ -4923,6 +4944,28 @@ module.exports = { } }, + /** + * + * @description Validates the input for multi file APIs + * @param {string} processInput - Process input data + * + * @returns {undefined} - nothing + */ + validateInputMultiFileAPI(processInput) { + if (_.isEmpty(processInput)) { + throw new Error('Input object must have "type" and "data" information'); + } + if (!processInput.type) { + throw new Error('"Type" parameter should be provided'); + } + if (!processInput.data || processInput.data.length === 0) { + throw new Error('"Data" parameter should be provided'); + } + if (processInput.data[0].path === '') { + throw new Error('"Path" of the data element should be provided'); + } + }, + parseFileOrThrow(fileContent) { const result = parse.getOasObject(fileContent); if (result.result === false) { diff --git a/lib/schemapack.js b/lib/schemapack.js index 38bd0f3..beb70d1 100644 --- a/lib/schemapack.js +++ b/lib/schemapack.js @@ -629,10 +629,8 @@ class SchemaPack { */ async detectRootFiles() { const input = this.input; - if (input.data[0].path === '') { - throw new Error('undefined input'); - } + schemaUtils.validateInputMultiFileAPI(input); if (!this.hasDefinedVersion && ('content' in input.data[0])) { return schemaUtils.mapGetRootFilesOutputToDetectRootFilesOutput([], input.specificationVersion); } @@ -668,6 +666,8 @@ class SchemaPack { */ async detectRelatedFiles() { const input = this.input; + + schemaUtils.validateInputMultiFileAPI(input); if (!input.rootFiles || input.rootFiles.length === 0) { let rootFiles = await this.detectRootFiles(input); if (rootFiles.output.data) { @@ -698,6 +698,8 @@ class SchemaPack { */ async bundle() { const input = this.input; + + schemaUtils.validateInputMultiFileAPI(input); if (!input.rootFiles || input.rootFiles.length === 0) { let rootFiles = await this.detectRootFiles(input); if (rootFiles.output.data) { diff --git a/test/unit/bundle.test.js b/test/unit/bundle.test.js index 2cdb984..d7a9f46 100644 --- a/test/unit/bundle.test.js +++ b/test/unit/bundle.test.js @@ -806,4 +806,50 @@ describe('getReferences method when node does not have any reference', function( expect(result.referencesInNode[0].path).to.equal('./user.yaml'); expect(result.referencesInNode[0].newValue.$ref).to.equal('the/parent/user.yaml'); }); + + it('should return error when "type" parameter is not sent', async function () { + let input = { + rootFiles: [ + { + path: '/root.yaml', + content: '' + } + ], + data: [ + { + path: '/examples.yaml', + content: '' + } + ], + options: {}, + bundleFormat: 'JSON' + }; + try { + await Converter.bundle(input); + } + catch (error) { + expect(error).to.not.be.undefined; + expect(error.message).to.equal('"Type" parameter should be provided'); + } + }); + + it('should return error when input is an empty object', async function () { + try { + await Converter.bundle({}); + } + catch (error) { + expect(error).to.not.be.undefined; + expect(error.message).to.equal('Input object must have "type" and "data" information'); + } + }); + + it('should return error when input data is an empty array', async function () { + try { + await Converter.bundle({ type: 'folder', data: [] }); + } + catch (error) { + expect(error).to.not.be.undefined; + expect(error.message).to.equal('"Data" parameter should be provided'); + } + }); }); diff --git a/test/unit/detectRelatedFiles.test.js b/test/unit/detectRelatedFiles.test.js index d0b1286..5e34a20 100644 --- a/test/unit/detectRelatedFiles.test.js +++ b/test/unit/detectRelatedFiles.test.js @@ -139,8 +139,7 @@ describe('detectRelatedFiles method', function () { content: contentFileMissedRef } ], - data: [ - ] + data: [{}] }, res = await Converter.detectRelatedFiles(input); expect(res).to.not.be.empty; @@ -242,8 +241,7 @@ describe('detectRelatedFiles method', function () { content: contentFileHop } ], - data: [ - ] + data: [{}] }; const res = await Converter.detectRelatedFiles(input); expect(res).to.not.be.empty; @@ -266,8 +264,7 @@ describe('detectRelatedFiles method', function () { content: contentFileHop } ], - data: [ - ] + data: [{}] }; const res = await Converter.detectRelatedFiles(input); expect(res).to.not.be.empty; @@ -286,8 +283,7 @@ describe('detectRelatedFiles method', function () { content: contentFile } ], - data: [ - ] + data: [{}] }; const res = await Converter.detectRelatedFiles(input); expect(res).to.not.be.empty; @@ -368,4 +364,50 @@ describe('detectRelatedFiles method', function () { expect(res.output.data[0].missingRelatedFiles.length).to.equal(6); }); + it('should return error when "type" parameter is not sent', async function () { + let contentRootFile = fs.readFileSync(petstoreMultipleFiles, 'utf8'), + contentFileResPets = fs.readFileSync(resourcesPets, 'utf8'), + input = { + rootFiles: [ + { + path: '/openapi.yaml', + content: contentRootFile + } + ], + data: [ + { + path: '/resources/pets.yaml', + content: contentFileResPets + } + ] + }; + + try { + await Converter.detectRelatedFiles(input); + } + catch (error) { + expect(error).to.not.be.undefined; + expect(error.message).to.equal('"Type" parameter should be provided'); + } + }); + + it('should return error when input is an empty object', async function () { + try { + await Converter.detectRelatedFiles({}); + } + catch (error) { + expect(error).to.not.be.undefined; + expect(error.message).to.equal('Input object must have "type" and "data" information'); + } + }); + + it('should return error when input data is an empty array', async function () { + try { + await Converter.detectRelatedFiles({ type: 'folder', data: [] }); + } + catch (error) { + expect(error).to.not.be.undefined; + expect(error.message).to.equal('"Data" parameter should be provided'); + } + }); }); diff --git a/test/unit/detectRoot.test.js b/test/unit/detectRoot.test.js index f0d200a..95d5a96 100644 --- a/test/unit/detectRoot.test.js +++ b/test/unit/detectRoot.test.js @@ -218,7 +218,7 @@ describe('detectRoot method', function() { await Converter.detectRootFiles(input); } catch (ex) { - expect(ex.message).to.equal('undefined input'); + expect(ex.message).to.equal('"Path" of the data element should be provided'); } }); @@ -266,4 +266,45 @@ describe('detectRoot method', function() { }); + it('should return error when "type" parameter is not sent', async function () { + let input = { + data: [ + { + path: validPetstore + }, + { + path: validHopService31x + } + ] + }; + + try { + await Converter.detectRootFiles(input); + } + catch (error) { + expect(error).to.not.be.undefined; + expect(error.message).to.equal('"Type" parameter should be provided'); + } + }); + + it('should return error when input is an empty object', async function () { + try { + await Converter.detectRootFiles({}); + } + catch (error) { + expect(error).to.not.be.undefined; + expect(error.message).to.equal('Input object must have "type" and "data" information'); + } + }); + + it('should return error when input data is an empty array', async function () { + try { + await Converter.detectRootFiles({ type: 'folder', data: [] }); + } + catch (error) { + expect(error).to.not.be.undefined; + expect(error.message).to.equal('"Data" parameter should be provided'); + } + }); + });