diff --git a/CHANGELOG.md b/CHANGELOG.md index c5859324..93a7c02d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ ## [Unreleased] +## [v4.22.0] - 2024-07-10 + +### Chore + +- Updated postman-collection to v4.4.0. + ## [v4.21.0] - 2024-05-17 ### Added @@ -620,7 +626,9 @@ Newer releases follow the [Keep a Changelog](https://keepachangelog.com/en/1.0.0 - Base release -[Unreleased]: https://github.com/postmanlabs/openapi-to-postman/compare/v4.21.0...HEAD +[Unreleased]: https://github.com/postmanlabs/openapi-to-postman/compare/v4.22.0...HEAD + +[v4.22.0]: https://github.com/postmanlabs/openapi-to-postman/compare/v4.21.0...v4.22.0 [v4.21.0]: https://github.com/postmanlabs/openapi-to-postman/compare/v4.20.1...v4.21.0 diff --git a/lib/common/versionUtils.js b/lib/common/versionUtils.js index 7ece46ac..cf90fd04 100644 --- a/lib/common/versionUtils.js +++ b/lib/common/versionUtils.js @@ -212,9 +212,10 @@ function getSpecVersion({ type, data, specificationVersion }) { const openapi30 = getVersionRegexp(VERSION_30), openapi31 = getVersionRegexp(VERSION_31), openapi20 = getVersionRegexp(VERSION_20), - is30 = data.match(openapi30), - is31 = data.match(openapi31), - is20 = data.match(openapi20); + is30 = typeof data === 'string' && data.match(openapi30), + is31 = typeof data === 'string' && data.match(openapi31), + is20 = typeof data === 'string' && data.match(openapi20); + let version = DEFAULT_SPEC_VERSION; if (is30) { diff --git a/lib/schemaUtils.js b/lib/schemaUtils.js index 5eb5e704..78a49461 100644 --- a/lib/schemaUtils.js +++ b/lib/schemaUtils.js @@ -2,13 +2,18 @@ * This file contains util functions that need OAS-awareness * utils.js contains other util functions */ - -const { ParseError } = require('./common/ParseError.js'); - const { formatDataPath, checkIsCorrectType, isKnownType } = require('./common/schemaUtilsCommon.js'), { getConcreteSchemaUtils, isSwagger, validateSupportedVersion } = require('./common/versionUtils.js'), async = require('async'), - sdk = require('postman-collection'), + { Variable } = require('postman-collection/lib/collection/variable'), + { QueryParam } = require('postman-collection/lib/collection/query-param'), + { Header } = require('postman-collection/lib/collection/header'), + { ItemGroup } = require('postman-collection/lib/collection/item-group'), + { Item } = require('postman-collection/lib/collection/item'), + { FormParam } = require('postman-collection/lib/collection/form-param'), + { RequestAuth } = require('postman-collection/lib/collection/request-auth'), + { Response } = require('postman-collection/lib/collection/response'), + { RequestBody } = require('postman-collection/lib/collection/request-body'), schemaFaker = require('../assets/json-schema-faker.js'), deref = require('./deref.js'), _ = require('lodash'), @@ -20,6 +25,7 @@ const { formatDataPath, checkIsCorrectType, isKnownType } = require('./common/sc { validateSchema } = require('./ajValidation/ajvValidation'), inputValidation = require('./30XUtils/inputValidation'), traverseUtility = require('traverse'), + { ParseError } = require('./common/ParseError.js'), SCHEMA_FORMATS = { DEFAULT: 'default', // used for non-request-body data and json XML: 'xml' // used for request-body XMLs @@ -511,7 +517,7 @@ module.exports = { if (serverVariables) { _.forOwn(serverVariables, (value, key) => { let description = this.getParameterDescription(value); - variables.push(new sdk.Variable({ + variables.push(new Variable({ key: key, value: value.default || '', description: description @@ -519,7 +525,7 @@ module.exports = { }); } if (keyName) { - variables.push(new sdk.Variable({ + variables.push(new Variable({ key: keyName, value: serverUrl, type: 'string' @@ -739,7 +745,7 @@ module.exports = { addCollectionItemsFromWebhooks: function(spec, generatedStore, components, options, schemaCache) { let webhooksObj = this.generateTrieFromPaths(spec, options, true), webhooksTree = webhooksObj.tree, - webhooksFolder = new sdk.ItemGroup({ name: 'Webhooks' }), + webhooksFolder = new ItemGroup({ name: 'Webhooks' }), variableStore = {}, webhooksVariables = []; @@ -752,7 +758,7 @@ module.exports = { webhooksTree.root.children.hasOwnProperty(child) && webhooksTree.root.children[child].requestCount > 0 ) { - webhooksVariables.push(new sdk.Variable({ + webhooksVariables.push(new Variable({ key: this.cleanWebhookName(child), value: '/', type: 'string' @@ -838,7 +844,7 @@ module.exports = { // variableStore contains all the kinds of variable created. // Add only the variables with type 'collection' to generatedStore.collection.variables if (variableStore[key].type === 'collection') { - const collectionVar = new sdk.Variable(variableStore[key]); + const collectionVar = new Variable(variableStore[key]); generatedStore.collection.variables.add(collectionVar); } } @@ -961,7 +967,7 @@ module.exports = { // Add all folders created from tags and corresponding operations // Iterate from bottom to top order to maintain tag order in spec _.forEachRight(tagFolders, (tagFolder, tagName) => { - var itemGroup = new sdk.ItemGroup({ + var itemGroup = new ItemGroup({ name: tagName, description: tagFolder.description }); @@ -981,7 +987,7 @@ module.exports = { // Add only the variables with type 'collection' to generatedStore.collection.variables _.forEach(variableStore, (variable) => { if (variable.type === 'collection') { - const collectionVar = new sdk.Variable(variable); + const collectionVar = new Variable(variable); generatedStore.collection.variables.add(collectionVar); } }); @@ -997,7 +1003,7 @@ module.exports = { * resolve references while generating params. * @param {object} options - a standard list of options that's globally passed around. Check options.js for more. * @param {object} schemaCache - object storing schemaFaker and schmeResolution caches - * @returns {Array} returns an array of sdk.Variable + * @returns {Array} returns an array of Collection SDK Variable */ convertPathVariables: function(type, providedPathVars, commonPathVars, components, options, schemaCache) { var variables = []; @@ -1067,7 +1073,7 @@ module.exports = { if (resource.requestCount > 1) { // only return a Postman folder if this folder has>1 children in its subtree // otherwise we can end up with 10 levels of folders with 1 request in the end - itemGroup = new sdk.ItemGroup({ + itemGroup = new ItemGroup({ name: resource.name // TODO: have to add auth here (but first, auth to be put into the openapi tree) }); @@ -1308,9 +1314,9 @@ module.exports = { */ generateSdkParam: function (param, location) { const sdkElementMap = { - 'query': sdk.QueryParam, - 'header': sdk.Header, - 'path': sdk.Variable + 'query': QueryParam, + 'header': Header, + 'path': Variable }; let generatedParam = { @@ -1913,7 +1919,7 @@ module.exports = { convertedHeader = _.get(this.convertParamsWithStyle(header, fakeData, parameterSource, components, schemaCache, options), '[0]'); - reqHeader = new sdk.Header(convertedHeader); + reqHeader = new Header(convertedHeader); reqHeader.description = this.getParameterDescription(header); return reqHeader; @@ -1936,7 +1942,7 @@ module.exports = { originalParam, paramArray = [], updateOptions = {}, - reqBody = new sdk.RequestBody(), + reqBody = new RequestBody(), contentHeader, contentTypes = {}, rDataMode, @@ -2021,7 +2027,7 @@ module.exports = { }; // add a content type header for each media type for the request body - contentHeader = new sdk.Header({ + contentHeader = new Header({ key: 'Content-Type', value: URLENCODED }); @@ -2088,14 +2094,14 @@ module.exports = { originalParam.type === 'string' && originalParam.format === 'binary' ) { - param = new sdk.FormParam({ + param = new FormParam({ key: key, value: '', type: 'file' }); } else { - param = new sdk.FormParam({ + param = new FormParam({ key: key, value: value, type: 'text' @@ -2112,7 +2118,7 @@ module.exports = { formdata: paramArray }; // add a content type header for the pertaining media type - contentHeader = new sdk.Header({ + contentHeader = new Header({ key: 'Content-Type', value: FORM_DATA }); @@ -2177,7 +2183,7 @@ module.exports = { }; } - contentHeader = new sdk.Header({ + contentHeader = new Header({ key: 'Content-Type', value: bodyType }); @@ -2246,7 +2252,7 @@ module.exports = { responseMediaTypes = _.keys(response.content); if (responseMediaTypes.length > 0) { - let acceptHeader = new sdk.Header({ + let acceptHeader = new Header({ key: 'Accept', value: responseMediaTypes[0] }); @@ -2256,7 +2262,7 @@ module.exports = { } } - sdkResponse = new sdk.Response({ + sdkResponse = new Response({ name: response.description, code: code || 500, header: responseHeaders, @@ -2654,7 +2660,7 @@ module.exports = { } // creating the request object - item = new sdk.Item({ + item = new Item({ name: reqName, request: { description: operation.description, @@ -2672,7 +2678,7 @@ module.exports = { }; thisAuthObject[authMap[authMeta.currentHelper]] = authMeta.helperAttributes; - item.request.auth = new sdk.RequestAuth(thisAuthObject); + item.request.auth = new RequestAuth(thisAuthObject); } else { item.request.auth = authHelper; diff --git a/lib/schemapack.js b/lib/schemapack.js index 07bc8a50..e3bd8d17 100644 --- a/lib/schemapack.js +++ b/lib/schemapack.js @@ -6,7 +6,8 @@ const { getConcreteSchemaUtils } = require('./common/versionUtils.js'), Ajv = require('ajv'), addFormats = require('ajv-formats'), async = require('async'), - sdk = require('postman-collection'), + { Collection } = require('postman-collection/lib/collection/collection'), + { Url } = require('postman-collection/lib/collection/url'), OasResolverOptions = { resolve: true, // Resolve external references jsonSchema: true // Treat $ref like JSON Schema and convert to OpenAPI Schema Objects @@ -340,7 +341,7 @@ class SchemaPack { // Creating a new instance of a Postman collection // All generated folders and requests will go inside this - generatedStore.collection = new sdk.Collection({ + generatedStore.collection = new Collection({ info: { name: utils.getCollectionName(_.get(openapi, 'info.title')) } @@ -530,7 +531,7 @@ class SchemaPack { }); // SDK URL object. Get raw string representation. - requestUrl = (new sdk.Url(requestUrl)).toString(); + requestUrl = (new Url(requestUrl)).toString(); } // 1. Look at transaction.request.URL + method, and find matching request from schema diff --git a/libV2/index.js b/libV2/index.js index b881d810..1c3ce5af 100644 --- a/libV2/index.js +++ b/libV2/index.js @@ -1,6 +1,6 @@ /* eslint-disable one-var */ const _ = require('lodash'), - sdk = require('postman-collection'), + { Collection } = require('postman-collection/lib/collection/collection'), GraphLib = require('graphlib'), generateSkeletonTreeFromOpenAPI = require('./helpers/collection/generateSkeletionTreeFromOpenAPI'), generateCollectionFromOpenAPI = require('./helpers/collection/generateCollectionFromOpenAPI'), @@ -45,7 +45,7 @@ module.exports = { case 'collection': { // dummy collection to be generated. const { data, variables } = generateCollectionFromOpenAPI(context, node); - collection = new sdk.Collection(data); + collection = new Collection(data); collection = collection.toJSON(); @@ -213,6 +213,11 @@ module.exports = { } }); + // Remove duplicate variables as different requests could end up creating same variables + if (!_.isEmpty(collection.variable)) { + collection.variable = _.uniqBy(collection.variable, 'key'); + } + return cb(null, { result: true, output: [{ diff --git a/libV2/schemaUtils.js b/libV2/schemaUtils.js index e0d62b7c..47c5217a 100644 --- a/libV2/schemaUtils.js +++ b/libV2/schemaUtils.js @@ -1086,7 +1086,13 @@ let QUERYPARAM = 'query', * * This matching between request bodies and response bodies are done in following order. * 1. Try matching keys from request and response examples + * + * (We'll also be considering any request body example with response code as key + * that's matching default response body example if present + * See fro example - test/data/valid_openapi/multiExampleResponseCodeMatching.json) + * * 2. If any key matching is found, we'll generate example from it and ignore non-matching keys + * * 3. If no matching key is found, we'll generate examples based on positional matching. * * Positional matching means first example in request body will be matched with first example @@ -1104,12 +1110,22 @@ let QUERYPARAM = 'query', const pmExamples = [], responseExampleKeys = _.map(responseExamples, 'key'), requestBodyExampleKeys = _.map(requestBodyExamples, 'key'), - matchedKeys = _.intersectionBy(responseExampleKeys, requestBodyExampleKeys, _.toLower), usedRequestExamples = _.fill(Array(requestBodyExamples.length), false), exampleKeyComparator = (example, key) => { return _.toLower(example.key) === _.toLower(key); }; + let matchedKeys = _.intersectionBy(responseExampleKeys, requestBodyExampleKeys, _.toLower), + isResponseCodeMatching = false; + + // Only match in case of default response example matching with any request body example + if (!matchedKeys.length && responseExamples.length === 1 && responseExamples[0].key === '_default') { + const responseCodes = _.map(responseExamples, 'responseCode'); + + matchedKeys = _.intersectionBy(responseCodes, requestBodyExampleKeys, _.toLower); + isResponseCodeMatching = matchedKeys.length > 0; + } + // Do keys matching first and ignore any leftover req/res body for which matching is not found if (matchedKeys.length) { _.forEach(matchedKeys, (key) => { @@ -1117,6 +1133,11 @@ let QUERYPARAM = 'query', return exampleKeyComparator(example, key); }), responseExample = _.find(responseExamples, (example) => { + // If there is a response code key-matching, then only match with keys based on response code + if (isResponseCodeMatching) { + return example.responseCode === key; + } + return exampleKeyComparator(example, key); }); @@ -1183,6 +1204,7 @@ let QUERYPARAM = 'query', }); }); + // eslint-disable-next-line one-var let responseExample, responseExampleData; @@ -1221,10 +1243,13 @@ let QUERYPARAM = 'query', * @param {Object} requestBodySchema - Schema of the request / response body * @param {String} bodyType - Content type of the body * @param {Boolean} isExampleBody - Whether the body is example body + * @param {String} responseCode - Response code * @param {Object} requestBodyExamples - Examples defined in the request body * @returns {Array} Request / Response body data */ - resolveBodyData = (context, requestBodySchema, bodyType, isExampleBody = false, requestBodyExamples) => { + resolveBodyData = (context, requestBodySchema, bodyType, isExampleBody = false, + responseCode = null, requestBodyExamples = {} + ) => { let { parametersResolution, indentCharacter } = context.computedOptions, headerFamily = getHeaderFamily(bodyType), bodyData = '', @@ -1366,7 +1391,8 @@ let QUERYPARAM = 'query', responseExamples = [{ key: '_default', value: bodyData, - contentType: bodyType + contentType: bodyType, + responseCode }]; if (!_.isEmpty(examples)) { @@ -1839,9 +1865,10 @@ let QUERYPARAM = 'query', * @param {Object} context - Global context object * @param {Object} responseBody - Response body schema * @param {Object} requestBodyExamples - Examples defined in the request body of corresponding operation + * @param {String} code - Response code * @returns {Array} - Postman examples */ - resolveResponseBody = (context, responseBody = {}, requestBodyExamples) => { + resolveResponseBody = (context, responseBody = {}, requestBodyExamples = {}, code = null) => { let responseContent, bodyType, allBodyData, @@ -1868,7 +1895,7 @@ let QUERYPARAM = 'query', bodyType = getRawBodyType(responseContent); headerFamily = getHeaderFamily(bodyType); - allBodyData = resolveBodyData(context, responseContent[bodyType], bodyType, true, requestBodyExamples); + allBodyData = resolveBodyData(context, responseContent[bodyType], bodyType, true, code, requestBodyExamples); return _.map(allBodyData, (bodyData) => { let requestBodyData = bodyData.request, @@ -2081,7 +2108,7 @@ let QUERYPARAM = 'query', let responseSchema = _.has(responseObj, '$ref') ? resolveSchema(context, responseObj) : responseObj, { includeAuthInfoInExample } = context.computedOptions, auth = request.auth, - resolvedExamples = resolveResponseBody(context, responseSchema, requestBodyExamples) || {}, + resolvedExamples = resolveResponseBody(context, responseSchema, requestBodyExamples, code) || {}, headers = resolveResponseHeaders(context, responseSchema.headers); _.forOwn(resolvedExamples, (resolvedExample = {}) => { diff --git a/libV2/utils.js b/libV2/utils.js index 00e952bd..66419302 100644 --- a/libV2/utils.js +++ b/libV2/utils.js @@ -1,5 +1,6 @@ -const sdk = require('postman-collection'), - _ = require('lodash'), +const _ = require('lodash'), + { Item } = require('postman-collection/lib/collection/item'), + { Response } = require('postman-collection/lib/collection/response'), // This is the default collection name if one can't be inferred from the OpenAPI spec COLLECTION_NAME = 'Imported from OpenAPI', @@ -30,7 +31,7 @@ const sdk = require('postman-collection'), response.code = response.code.replace(/X|x/g, '0'); response.code = response.code === 'default' ? 500 : _.toSafeInteger(response.code); - let sdkResponse = new sdk.Response({ + let sdkResponse = new Response({ name: response.name, code: response.code, header: response.headers, @@ -50,7 +51,7 @@ const sdk = require('postman-collection'), return sdkResponse; }, generateRequestItemObject = (requestObject) => { - const requestItem = new sdk.Item(requestObject), + const requestItem = new Item(requestObject), queryParams = _.get(requestObject, 'request.params.queryParams'), pathParams = _.get(requestObject, 'request.params.pathParams', []), headers = _.get(requestObject, 'request.headers', []), diff --git a/libV2/validationUtils.js b/libV2/validationUtils.js index 2efd431d..f5f43942 100644 --- a/libV2/validationUtils.js +++ b/libV2/validationUtils.js @@ -2,7 +2,10 @@ // TODO: REMOVE THIS ☝🏻 const _ = require('lodash'), - sdk = require('postman-collection'), + { Header } = require('postman-collection/lib/collection/header'), + { QueryParam } = require('postman-collection/lib/collection/query-param'), + { Url } = require('postman-collection/lib/collection/url'), + { Variable } = require('postman-collection/lib/collection/variable'), async = require('async'), crypto = require('crypto'), schemaFaker = require('../assets/json-schema-faker.js'), @@ -853,9 +856,9 @@ function checkContentTypeHeader (headers, transactionPathPrefix, schemaPathPrefi */ function generateSdkParam (param, location) { const sdkElementMap = { - 'query': sdk.QueryParam, - 'header': sdk.Header, - 'path': sdk.Variable + 'query': QueryParam, + 'header': Header, + 'path': Variable }; let generatedParam = { @@ -1012,7 +1015,7 @@ function convertToPmCollectionVariables (serverVariables, keyName, serverUrl = ' if (serverVariables) { _.forOwn(serverVariables, (value, key) => { let description = getParameterDescription(value); - variables.push(new sdk.Variable({ + variables.push(new Variable({ key: key, value: value.default || '', description: description @@ -1020,7 +1023,7 @@ function convertToPmCollectionVariables (serverVariables, keyName, serverUrl = ' }); } if (keyName) { - variables.push(new sdk.Variable({ + variables.push(new Variable({ key: keyName, value: serverUrl, type: 'string' @@ -2535,7 +2538,7 @@ module.exports = { queryParams = [...(requestUrl.query || [])]; // SDK URL object. Get raw string representation. - requestUrl = (new sdk.Url(requestUrl)).toString(); + requestUrl = (new Url(requestUrl)).toString(); } // 1. Look at transaction.request.URL + method, and find matching request from schema diff --git a/package-lock.json b/package-lock.json index 4489d797..ee461f23 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "openapi-to-postmanv2", - "version": "4.21.0", + "version": "4.22.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "openapi-to-postmanv2", - "version": "4.21.0", + "version": "4.22.0", "license": "Apache-2.0", "dependencies": { "ajv": "8.11.0", @@ -21,7 +21,7 @@ "oas-resolver-browser": "2.5.6", "object-hash": "3.0.0", "path-browserify": "1.0.1", - "postman-collection": "4.2.1", + "postman-collection": "^4.4.0", "swagger2openapi": "7.0.8", "traverse": "0.6.6", "yaml": "1.10.2" @@ -4285,9 +4285,9 @@ } }, "node_modules/postman-collection": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/postman-collection/-/postman-collection-4.2.1.tgz", - "integrity": "sha512-DFLt3/yu8+ldtOTIzmBUctoupKJBOVK4NZO0t68K2lIir9smQg7OdQTBjOXYy+PDh7u0pSDvD66tm93eBHEPHA==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/postman-collection/-/postman-collection-4.4.0.tgz", + "integrity": "sha512-2BGDFcUwlK08CqZFUlIC8kwRJueVzPjZnnokWPtJCd9f2J06HBQpGL7t2P1Ud1NEsK9NHq9wdipUhWLOPj5s/Q==", "dependencies": { "@faker-js/faker": "5.5.3", "file-type": "3.9.0", @@ -8837,9 +8837,9 @@ } }, "postman-collection": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/postman-collection/-/postman-collection-4.2.1.tgz", - "integrity": "sha512-DFLt3/yu8+ldtOTIzmBUctoupKJBOVK4NZO0t68K2lIir9smQg7OdQTBjOXYy+PDh7u0pSDvD66tm93eBHEPHA==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/postman-collection/-/postman-collection-4.4.0.tgz", + "integrity": "sha512-2BGDFcUwlK08CqZFUlIC8kwRJueVzPjZnnokWPtJCd9f2J06HBQpGL7t2P1Ud1NEsK9NHq9wdipUhWLOPj5s/Q==", "requires": { "@faker-js/faker": "5.5.3", "file-type": "3.9.0", diff --git a/package.json b/package.json index f326089c..281565f8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "openapi-to-postmanv2", - "version": "4.21.0", + "version": "4.22.0", "description": "Convert a given OpenAPI specification to Postman Collection v2.0", "homepage": "https://github.com/postmanlabs/openapi-to-postman", "bugs": "https://github.com/postmanlabs/openapi-to-postman/issues", @@ -128,7 +128,7 @@ "object-hash": "3.0.0", "graphlib": "2.1.8", "path-browserify": "1.0.1", - "postman-collection": "4.2.1", + "postman-collection": "^4.4.0", "swagger2openapi": "7.0.8", "traverse": "0.6.6", "yaml": "1.10.2" diff --git a/test/data/valid_openapi/duplicateCollectionVars.json b/test/data/valid_openapi/duplicateCollectionVars.json new file mode 100644 index 00000000..451f8022 --- /dev/null +++ b/test/data/valid_openapi/duplicateCollectionVars.json @@ -0,0 +1,56 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "MyTitle", + "description": "My Description", + "version": "1.0.0", + "x-ms-generated-by": { + "toolName": "Microsoft.OpenApi.OData", + "toolVersion": "1.0.9.0" + } + }, + "paths": { + "/Path1({MyParam})": { + "description": "My path1 description", + "get": { + "tags": [ + "MyTag" + ], + "summary": "does path1", + "operationId": "Path1", + "parameters": [ + { + "name": "MyParam", + "in": "path", + "description": "My Param", + "schema": { + "type": "string", + "nullable": true + } + } + ] + } + }, + "/Path2({MyParam})": { + "description": "My path2 description", + "get": { + "tags": [ + "MyTag" + ], + "summary": "does path2", + "operationId": "Path2", + "parameters": [ + { + "name": "MyParam", + "in": "path", + "description": "My Param", + "schema": { + "type": "string", + "nullable": true + } + } + ] + } + } + } +} diff --git a/test/data/valid_openapi/multiExampleResponseCodeMatching.json b/test/data/valid_openapi/multiExampleResponseCodeMatching.json new file mode 100644 index 00000000..3b0292c4 --- /dev/null +++ b/test/data/valid_openapi/multiExampleResponseCodeMatching.json @@ -0,0 +1,282 @@ +{ + "x-generator": "NSwag v13.19.0.0 (NJsonSchema v10.9.0.0 (Newtonsoft.Json v13.0.0.0))", + "openapi": "3.0.0", + "info": { + "title": "Postman Example API", + "description": "postman Test. \r\n\r\n © Copyright 2024.", + "version": "v1" + }, + "servers": [ + { + "url": "https://localhost:1234" + } + ], + "paths": { + "/addUser": { + "post": { + "tags": [ + "PostmanExample" + ], + "summary": "Add User", + "description": "Add new user to system and define his access.", + "operationId": "PostmanExample_AddUser", + "requestBody": { + "x-name": "command", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AddUserCommand" + }, + "examples": { + "200": { + "value": { + "userDetail": { + "roleId": 1, + "department": "Admin 1", + "email": "123@gmail.com" + } + } + }, + "400": { + "value": { + "userDetail": { + "roleId": null, + "department": "Admin 1", + "email": "" + } + } + }, + "404": { + "value": { + "userDetail": { + "roleId": 0, + "department": "Admin 0", + "email": "123@gmail.com" + } + } + }, + "409": { + "value": { + "userDetail": { + "roleId": 1, + "department": "Admin 1", + "email": "123@gmail.com" + } + } + }, + "500": { + "value": { + "userDetail": { + "roleId": 0, + "department": null, + "email": null + } + } + } + } + } + }, + "required": true, + "x-position": 1 + }, + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserResponse" + }, + "example": { + "userId": 12 + } + } + } + }, + "400": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestResponse" + }, + "example": { + "hasErrorMessage": true, + "errorMessage": "Bad Request", + "validationsErrors": [ + { + "propertyName": "RoleID", + "errorMessage": "Can not be null" + } + ] + } + } + } + }, + "404": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundResponse" + }, + "example": { + "message": "AddUserDetailsCommand : User Role Not Found" + } + } + } + }, + "409": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConflictErrorResponse" + }, + "example": { + "message": "AddUserDetailsCommand : Duplicate" + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnexpectedErrorResponse" + }, + "example": { + "message": "AddUserDetailsCommand : System Error message" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "UserResponse": { + "allOf": [ + { + "$ref": "#/components/schemas/Response" + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "userId": { + "type": "integer", + "format": "int32", + "nullable": true + } + } + } + ] + }, + "Response": { + "type": "object", + "additionalProperties": false + }, + "BadRequestResponse": { + "type": "object", + "additionalProperties": false, + "properties": { + "hasErrorMessage": { + "type": "boolean" + }, + "errorMessage": { + "type": "string", + "nullable": true + }, + "validationsErrors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + } + } + } + }, + "ValidationError": { + "type": "object", + "additionalProperties": false, + "properties": { + "propertyName": { + "type": "string" + }, + "errorMessage": { + "type": "string" + } + } + }, + "NotFoundResponse": { + "type": "object", + "additionalProperties": false, + "properties": { + "message": { + "type": "string" + } + } + }, + "ConflictErrorResponse": { + "type": "object", + "additionalProperties": false, + "properties": { + "message": { + "type": "string" + } + } + }, + "UnexpectedErrorResponse": { + "type": "object", + "additionalProperties": false, + "properties": { + "message": { + "type": "string" + } + } + }, + "AddUserCommand": { + "allOf": [ + { + "$ref": "#/components/schemas/AddUserCommandResponse" + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "userDetail": { + "$ref": "#/components/schemas/UserInformationDto" + } + } + } + ] + }, + "UserInformationDto": { + "type": "object", + "additionalProperties": false, + "properties": { + "roleId": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "department": { + "type": "string", + "nullable": true + }, + "email": { + "type": "string", + "nullable": true + } + } + }, + "AddUserCommandResponse": { + "type": "object", + "additionalProperties": false + } + } + } +} diff --git a/test/system/repository.test.js b/test/system/repository.test.js index 9c15b46e..2d2fe4d1 100644 --- a/test/system/repository.test.js +++ b/test/system/repository.test.js @@ -77,13 +77,10 @@ describe('project repository', function () { expect(json.dependencies).to.be.a('object'); }); - // Unskip before merging - it('must point to a valid and precise (no * or ^) semver', function () { - json.dependencies && Object.keys(json.dependencies).forEach(function (item) { - expect(json.dependencies[item]).to.match(new RegExp('(^((\\d+)\\.(\\d+)\\.(\\d+)|' + - '(^npm:[\\dA-Za-z\\-]+@(\\d+)\\.(\\d+)\\.(\\d+)))(?:-([\\dA-Za-z\\-]+(?:\\.[\\dA-Za-z\\-]+)*))?' + - '(?:\\+([\\dA-Za-z\\-]+(?:\\.[\\dA-Za-z\\-]+)*))?)$')); - }); + it('should have a valid version string in form of ..', function () { + expect(json.version) + // eslint-disable-next-line max-len, security/detect-unsafe-regex + .to.match(/^((\d+)\.(\d+)\.(\d+))(?:-([\dA-Za-z-]+(?:\.[\dA-Za-z-]+)*))?(?:\+([\dA-Za-z-]+(?:\.[\dA-Za-z-]+)*))?$/); }); }); @@ -92,11 +89,11 @@ describe('project repository', function () { expect(json.devDependencies).to.be.a('object'); }); - it('must point to a valid and precise (no * or ^) semver', function () { - json.devDependencies && Object.keys(json.devDependencies).forEach(function (item) { - expect(json.devDependencies[item]).to.match(new RegExp('(^((\\d+)\\.(\\d+)\\.(\\d+)|' + - '(^npm:[\\dA-Za-z\\-]+@(\\d+)\\.(\\d+)\\.(\\d+)))(?:-([\\dA-Za-z\\-]+(?:\\.[\\dA-Za-z\\-]+)*))?' + - '(?:\\+([\\dA-Za-z\\-]+(?:\\.[\\dA-Za-z\\-]+)*))?)$')); + it('should point to a valid semver', function () { + Object.keys(json.devDependencies).forEach(function (dependencyName) { + // eslint-disable-next-line security/detect-non-literal-regexp + expect(json.devDependencies[dependencyName]).to.match(new RegExp('((\\d+)\\.(\\d+)\\.(\\d+))(?:-' + + '([\\dA-Za-z\\-]+(?:\\.[\\dA-Za-z\\-]+)*))?(?:\\+([\\dA-Za-z\\-]+(?:\\.[\\dA-Za-z\\-]+)*))?$')); }); }); diff --git a/test/unit/convertV2.test.js b/test/unit/convertV2.test.js index 44aad990..8666139e 100644 --- a/test/unit/convertV2.test.js +++ b/test/unit/convertV2.test.js @@ -106,7 +106,11 @@ const expect = require('chai').expect, multiContentTypesMultiExample = path.join(__dirname, VALID_OPENAPI_PATH, '/multiContentTypesMultiExample.json'), multiExampleRequestVariousResponse = - path.join(__dirname, VALID_OPENAPI_PATH, '/multiExampleRequestVariousResponse.yaml'); + path.join(__dirname, VALID_OPENAPI_PATH, '/multiExampleRequestVariousResponse.yaml'), + multiExampleResponseCodeMatching = + path.join(__dirname, VALID_OPENAPI_PATH, '/multiExampleResponseCodeMatching.json'), + duplicateCollectionVars = + path.join(__dirname, VALID_OPENAPI_PATH, '/duplicateCollectionVars.json'); describe('The convert v2 Function', function() { @@ -2727,5 +2731,92 @@ describe('The convert v2 Function', function() { done(); }); }); + + it('request body and responses contain examples with matching keys as response codes', function(done) { + const openapi = fs.readFileSync(multiExampleResponseCodeMatching, 'utf8'); + Converter.convertV2({ type: 'string', data: openapi }, { parametersResolution: 'Example' }, + (err, conversionResult) => { + expect(err).to.be.null; + expect(conversionResult.result).to.equal(true); + expect(conversionResult.output.length).to.equal(1); + expect(conversionResult.output[0].type).to.equal('collection'); + expect(conversionResult.output[0].data).to.have.property('info'); + expect(conversionResult.output[0].data).to.have.property('item'); + expect(conversionResult.output[0].data.item[0].item.length).to.equal(1); + + const item = conversionResult.output[0].data.item[0].item[0], + defaultReqBody = { + userDetail: { + roleId: 1, + department: 'Admin 1', + email: '123@gmail.com' + } + }; + + expect(JSON.parse(item.request.body.raw)).to.eql(defaultReqBody); + expect(item.response).to.have.lengthOf(5); + + expect(item.response[0].name).to.eql('200'); + expect(item.response[0].code).to.eql(200); + expect(item.response[0]._postman_previewlanguage).to.eql('json'); + expect(JSON.parse(item.response[0].originalRequest.body.raw)).to.eql(defaultReqBody); + expect(JSON.parse(item.response[0].body)).to.eql({ userId: 12 }); + + expect(item.response[1].name).to.eql('400'); + expect(item.response[1].code).to.eql(400); + expect(item.response[1]._postman_previewlanguage).to.eql('json'); + expect(JSON.parse(item.response[1].originalRequest.body.raw)).to.eql({ + userDetail: { roleId: null, department: 'Admin 1', email: '' } + }); + expect(JSON.parse(item.response[1].body)).to.eql({ + hasErrorMessage: true, + errorMessage: 'Bad Request', + validationsErrors: [{ propertyName: 'RoleID', errorMessage: 'Can not be null' }] + }); + + expect(item.response[2].name).to.eql('404'); + expect(item.response[2].code).to.eql(404); + expect(item.response[2]._postman_previewlanguage).to.eql('json'); + expect(JSON.parse(item.response[2].originalRequest.body.raw)).to.eql({ + userDetail: { roleId: 0, department: 'Admin 0', email: '123@gmail.com' } + }); + expect(JSON.parse(item.response[2].body)).to.eql({ message: 'AddUserDetailsCommand : User Role Not Found' }); + + expect(item.response[3].name).to.eql('409'); + expect(item.response[3].code).to.eql(409); + expect(item.response[3]._postman_previewlanguage).to.eql('json'); + expect(JSON.parse(item.response[3].originalRequest.body.raw)).to.eql({ + userDetail: { roleId: 1, department: 'Admin 1', email: '123@gmail.com' } + }); + expect(JSON.parse(item.response[3].body)).to.eql({ message: 'AddUserDetailsCommand : Duplicate' }); + + expect(item.response[4].name).to.eql('500'); + expect(item.response[4].code).to.eql(500); + expect(item.response[4]._postman_previewlanguage).to.eql('json'); + expect(JSON.parse(item.response[4].originalRequest.body.raw)).to.eql({ + userDetail: { roleId: 0, department: null, email: null } + }); + expect(JSON.parse(item.response[4].body)).to.eql({ message: 'AddUserDetailsCommand : System Error message' }); + done(); + }); + }); + }); + + it('[Github #11884] Should not contain duplicate variables created from requests path', function (done) { + const openapi = fs.readFileSync(duplicateCollectionVars, 'utf8'); + Converter.convertV2({ type: 'string', data: openapi }, { parametersResolution: 'Example' }, + (err, conversionResult) => { + expect(err).to.be.null; + expect(conversionResult.result).to.equal(true); + expect(conversionResult.output.length).to.equal(1); + expect(conversionResult.output[0].type).to.equal('collection'); + expect(conversionResult.output[0].data).to.have.property('info'); + expect(conversionResult.output[0].data).to.have.property('item'); + expect(conversionResult.output[0].data).to.have.property('variable'); + expect(conversionResult.output[0].data.variable).to.have.lengthOf(2); + expect(conversionResult.output[0].data.variable[0]).to.have.property('key', 'baseUrl'); + expect(conversionResult.output[0].data.variable[1]).to.have.property('key', 'MyParam'); + done(); + }); }); });