From a67b59d40891973c0e95128d28cd18f8ac94c38b Mon Sep 17 00:00:00 2001 From: Khuda Dad Nomani Date: Tue, 4 Nov 2025 15:15:18 +0000 Subject: [PATCH 01/13] duplicate curl for postman cli codegen --- codegens/postman-cli/.gitignore | 72 + codegens/postman-cli/.jsdoc-config.json | 34 + codegens/postman-cli/.npmignore | 74 + codegens/postman-cli/README.md | 73 + codegens/postman-cli/index.js | 1 + codegens/postman-cli/lib/index.js | 298 ++++ codegens/postman-cli/lib/lodash.js | 456 ++++++ codegens/postman-cli/lib/util.js | 379 +++++ codegens/postman-cli/npm-shrinkwrap.json | 5 + codegens/postman-cli/npm/test-lint.js | 56 + codegens/postman-cli/npm/test-newman.js | 59 + codegens/postman-cli/npm/test-unit.js | 59 + codegens/postman-cli/npm/test.js | 16 + codegens/postman-cli/package.json | 30 + codegens/postman-cli/test/.eslintrc | 29 + codegens/postman-cli/test/ci-install.sh | 5 + .../postman-cli/test/newman/newman.test.js | 31 + codegens/postman-cli/test/unit/.gitkeep | 0 .../postman-cli/test/unit/convert.test.js | 1224 ++++++++++++++ .../fixtures/testcollection/collection.json | 1453 +++++++++++++++++ .../postman-cli/test/unit/validation.test.js | 30 + 21 files changed, 4384 insertions(+) create mode 100644 codegens/postman-cli/.gitignore create mode 100644 codegens/postman-cli/.jsdoc-config.json create mode 100644 codegens/postman-cli/.npmignore create mode 100644 codegens/postman-cli/README.md create mode 100644 codegens/postman-cli/index.js create mode 100644 codegens/postman-cli/lib/index.js create mode 100644 codegens/postman-cli/lib/lodash.js create mode 100644 codegens/postman-cli/lib/util.js create mode 100644 codegens/postman-cli/npm-shrinkwrap.json create mode 100644 codegens/postman-cli/npm/test-lint.js create mode 100644 codegens/postman-cli/npm/test-newman.js create mode 100755 codegens/postman-cli/npm/test-unit.js create mode 100755 codegens/postman-cli/npm/test.js create mode 100644 codegens/postman-cli/package.json create mode 100644 codegens/postman-cli/test/.eslintrc create mode 100755 codegens/postman-cli/test/ci-install.sh create mode 100644 codegens/postman-cli/test/newman/newman.test.js create mode 100644 codegens/postman-cli/test/unit/.gitkeep create mode 100644 codegens/postman-cli/test/unit/convert.test.js create mode 100644 codegens/postman-cli/test/unit/fixtures/testcollection/collection.json create mode 100644 codegens/postman-cli/test/unit/validation.test.js diff --git a/codegens/postman-cli/.gitignore b/codegens/postman-cli/.gitignore new file mode 100644 index 000000000..bfb38cf93 --- /dev/null +++ b/codegens/postman-cli/.gitignore @@ -0,0 +1,72 @@ +.DS_Store +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Package manager lock files +yarn.lock +package-lock.json +pnpm-lock.yaml +bun.lockb + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Prevent IDE stuff +.idea +.vscode +*.sublime-* + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +.coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Typescript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + +out/ diff --git a/codegens/postman-cli/.jsdoc-config.json b/codegens/postman-cli/.jsdoc-config.json new file mode 100644 index 000000000..90e6d5d44 --- /dev/null +++ b/codegens/postman-cli/.jsdoc-config.json @@ -0,0 +1,34 @@ +{ + "tags": { + "allowUnknownTags": true, + "dictionaries": ["jsdoc", "closure"] + }, + "source": { + "include": [ ], + "includePattern": ".+\\.js(doc)?$", + "excludePattern": "(^|\\/|\\\\)_" + }, + + "plugins": [ + "plugins/markdown" + ], + + "templates": { + "cleverLinks": false, + "monospaceLinks": false, + "highlightTutorialCode" : true + }, + + "opts": { + "template": "./node_modules/postman-jsdoc-theme", + "encoding": "utf8", + "destination": "./out/docs", + "recurse": true, + "readme": "README.md" + }, + + "markdown": { + "parser": "gfm", + "hardwrap": false + } +} diff --git a/codegens/postman-cli/.npmignore b/codegens/postman-cli/.npmignore new file mode 100644 index 000000000..17156c3bc --- /dev/null +++ b/codegens/postman-cli/.npmignore @@ -0,0 +1,74 @@ +### NPM Specific: Disregard recursive project files +### =============================================== +/.editorconfig +/.gitmodules +/test + +### Borrowed from .gitignore +### ======================== + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Prevent IDE stuff +.idea +.vscode +*.sublime-* + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +.coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Typescript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + +out/ diff --git a/codegens/postman-cli/README.md b/codegens/postman-cli/README.md new file mode 100644 index 000000000..a9962c100 --- /dev/null +++ b/codegens/postman-cli/README.md @@ -0,0 +1,73 @@ +# codegen-curl + +> Converts Postman-SDK Request into code snippet for cURL. + +#### Prerequisites +To run Code-Gen, ensure that you have NodeJS >= v8. A copy of the NodeJS installable can be downloaded from + +## Using the Module +The module will expose an object which will have property `convert` which is the function for converting the Postman-SDK request to cURL code snippet and `getOptions` function which returns an array of supported options. + +### convert function +Convert function takes three parameters + +* `request` - Postman-SDK Request Object + +* `options` - options is an object which has following properties + * `indentType` - String denoting type of indentation for code snippet. eg: 'Space', 'Tab' + * `indentCount` - The number of indentation characters to add per code level + * `trimRequestBody` - Trim request body fields + * `followRedirect` - Boolean denoting whether to redirect a request + * `requestTimeoutInSeconds` - Integer denoting time after which the request will bail out in seconds + * `multiLine` - Boolean denoting whether to output code snippet with multi line breaks + * `longFormat` - Boolean denoting whether to use longform cURL options in snippet + * `quoteType` - String denoting the quote type to use (single or double) for URL + +* `callback` - callback function with first parameter as error and second parameter as string for code snippet + +##### Example: +```js +var request = new sdk.Request('www.google.com'), //using postman sdk to create request + options = { + indentCount: 3, + indentType: 'Space', + requestTimeout: 200, + trimRequestBody: true, + multiLine: true, + followRedirect: true, + longFormat: true, + quoteType: 'single' + }; +convert(request, options, function(error, snippet) { + if (error) { + // handle error + } + // handle snippet +}); +``` +### getOptions function + +This function returns a list of options supported by this codegen. + +#### Example +```js +var options = getOptions(); + +console.log(options); +// output +// [ +// { +// name: 'Set indentation count', +// id: 'indentCount', +// type: 'positiveInteger', +// default: 2, +// description: 'Set the number of indentation characters to add per code level' +// }, +// ... +// ] +``` +### Guidelines for using generated snippet + +* Since Postman-SDK Request object doesn't provide complete path of the file, it needs to be manually inserted in case of uploading a file. + +* This module doesn't support cookies. diff --git a/codegens/postman-cli/index.js b/codegens/postman-cli/index.js new file mode 100644 index 000000000..bb0a047c4 --- /dev/null +++ b/codegens/postman-cli/index.js @@ -0,0 +1 @@ +module.exports = require('./lib'); diff --git a/codegens/postman-cli/lib/index.js b/codegens/postman-cli/lib/index.js new file mode 100644 index 000000000..af57ca4d3 --- /dev/null +++ b/codegens/postman-cli/lib/index.js @@ -0,0 +1,298 @@ +const { + sanitize, + sanitizeOptions, + getUrlStringfromUrlObject, + getNtlmAuthInfo, + addFormParam, + form, + shouldAddHttpMethod + } = require('./util'), + _ = require('./lodash'); + +var self; + +self = module.exports = { + convert: function (request, options, callback) { + + if (!_.isFunction(callback)) { + throw new Error('Curl-Converter: callback is not valid function'); + } + options = sanitizeOptions(options, self.getOptions()); + + var indent, trim, headersData, body, redirect, timeout, multiLine, + format, snippet, silent, url, quoteType, ntlmAuth; + + redirect = options.followRedirect; + timeout = options.requestTimeoutInSeconds; + multiLine = options.multiLine; + format = options.longFormat; + trim = options.trimRequestBody; + silent = options.silent; + quoteType = options.quoteType === 'single' ? '\'' : '"'; + url = getUrlStringfromUrlObject(request.url, quoteType); + ntlmAuth = getNtlmAuthInfo(request.auth, quoteType, format); + + snippet = 'curl'; + + if (ntlmAuth) { + snippet += ntlmAuth; + } + if (silent) { + snippet += ` ${form('-s', format)}`; + } + if (redirect) { + snippet += ` ${form('-L', format)}`; + } + if (timeout > 0) { + snippet += ` ${form('-m', format)} ${timeout}`; + } + if ((url.match(/[{[}\]]/g) || []).length > 0) { + snippet += ` ${form('-g', format)}`; + } + if (multiLine) { + indent = options.indentType === 'Tab' ? '\t' : ' '; + indent = ' ' + options.lineContinuationCharacter + '\n' + indent.repeat(options.indentCount); // eslint-disable-line max-len + } + else { + indent = ' '; + } + + if (request.method === 'HEAD') { + snippet += ` ${form('-I', format)}`; + } + if (shouldAddHttpMethod(request, options)) { + snippet += ` ${form('-X', format)} ${request.method}`; + } + snippet += ` ${quoteType + url + quoteType}`; + + if (request.body && !request.headers.has('Content-Type')) { + if (request.body.mode === 'file') { + request.addHeader({ + key: 'Content-Type', + value: 'text/plain' + }); + } + else if (request.body.mode === 'graphql') { + request.addHeader({ + key: 'Content-Type', + value: 'application/json' + }); + } + } + headersData = request.toJSON().header; + if (headersData) { + headersData = _.reject(headersData, 'disabled'); + _.forEach(headersData, (header) => { + if (!header.key) { + return; + } + snippet += indent + `${form('-H', format)} ${quoteType}${sanitize(header.key, true, quoteType)}`; + // If the header value is an empty string then add a semicolon after key + // otherwise the header would be ignored by curl + if (header.value) { + snippet += `: ${sanitize(header.value, false, quoteType)}${quoteType}`; + } + else { + snippet += ';' + quoteType; + } + }); + } + + // The following code handles multiple files in the same formdata param. + // It removes the form data params where the src property is an array of filepath strings + // Splits that array into different form data params with src set as a single filepath string + if (request.body && request.body.mode === 'formdata') { + let formdata = request.body.formdata, + formdataArray = []; + formdata.members.forEach((param) => { + let key = param.key, + type = param.type, + disabled = param.disabled, + contentType = param.contentType; + if (type === 'file') { + if (typeof param.src !== 'string') { + if (Array.isArray(param.src) && param.src.length) { + param.src.forEach((filePath) => { + addFormParam(formdataArray, key, param.type, filePath, disabled, contentType); + }); + } + else { + addFormParam(formdataArray, key, param.type, '/path/to/file', disabled, contentType); + } + } + else { + addFormParam(formdataArray, key, param.type, param.src, disabled, contentType); + } + } + else { + addFormParam(formdataArray, key, param.type, param.value, disabled, contentType); + } + }); + request.body.update({ + mode: 'formdata', + formdata: formdataArray + }); + } + if (request.body) { + body = request.body.toJSON(); + + if (!_.isEmpty(body)) { + switch (body.mode) { + case 'urlencoded': + _.forEach(body.urlencoded, function (data) { + if (!data.disabled) { + snippet += indent + (format ? '--data-urlencode' : '-d'); + snippet += ` ${quoteType}${sanitize(data.key, trim, quoteType, false, true)}=` + + `${sanitize(data.value, trim, quoteType, false, !format)}${quoteType}`; + } + }); + break; + case 'raw': { + let rawBody = body.raw.toString(), + isAsperandPresent = _.includes(rawBody, '@'), + // Use the long option if `@` is present in the request body otherwise follow user setting + optionName = isAsperandPresent ? '--data-raw' : form('-d', format), + sanitizedBody = sanitize(rawBody, trim, quoteType); + + if (!multiLine) { + try { + sanitizedBody = JSON.stringify(JSON.parse(sanitizedBody)); + } + catch (e) { + // Do nothing + } + } + + snippet += indent + `${optionName} ${quoteType}${sanitizedBody}${quoteType}`; + + break; + } + + case 'graphql': { + // eslint-disable-next-line no-case-declarations + let query = body.graphql ? body.graphql.query : '', + graphqlVariables, requestBody, isAsperandPresent, optionName; + try { + graphqlVariables = JSON.parse(body.graphql.variables); + } + catch (e) { + graphqlVariables = {}; + } + + requestBody = JSON.stringify({ + query: query, + variables: graphqlVariables + }); + + isAsperandPresent = _.includes(requestBody, '@'); + // Use the long option if `@` is present in the request body otherwise follow user setting + optionName = isAsperandPresent ? '--data-raw' : form('-d', format); + snippet += indent + `${optionName} ${quoteType}${sanitize(requestBody, trim, quoteType)}${quoteType}`; + break; + } + case 'formdata': + _.forEach(body.formdata, function (data) { + if (!(data.disabled)) { + if (data.type === 'file') { + snippet += indent + `${form('-F', format)}`; + snippet += ` ${quoteType}${sanitize(data.key, trim, quoteType)}=` + + `${sanitize(`@"${sanitize(data.src, trim, '"', true)}"`, trim, quoteType, quoteType === '"')}`; + snippet += quoteType; + } + else { + snippet += indent + `${form('-F', format)}`; + snippet += ` ${quoteType}${sanitize(data.key, trim, quoteType)}=` + + sanitize(`"${sanitize(data.value, trim, '"', true)}"`, trim, quoteType, quoteType === '"'); + if (data.contentType) { + snippet += `;type=${data.contentType}`; + } + snippet += quoteType; + } + } + }); + break; + case 'file': + snippet += indent + (format ? '--data-binary' : '-d'); + snippet += ` ${quoteType}@${sanitize(body[body.mode].src, trim)}${quoteType}`; + break; + default: + snippet += `${form('-d', format)} ${quoteType}${quoteType}`; + } + } + } + + callback(null, snippet); + }, + getOptions: function () { + return [ + { + name: 'Generate multiline snippet', + id: 'multiLine', + type: 'boolean', + default: true, + description: 'Split cURL command across multiple lines' + }, + { + name: 'Use long form options', + id: 'longFormat', + type: 'boolean', + default: true, + description: 'Use the long form for cURL options (--header instead of -H)' + }, + { + name: 'Line continuation character', + id: 'lineContinuationCharacter', + availableOptions: ['\\', '^', '`'], + type: 'enum', + default: '\\', + description: 'Set a character used to mark the continuation of a statement on the next line ' + + '(generally, \\ for OSX/Linux, ^ for Windows cmd and ` for Powershell)' + }, + { + name: 'Quote Type', + id: 'quoteType', + availableOptions: ['single', 'double'], + type: 'enum', + default: 'single', + description: 'String denoting the quote type to use (single or double) for URL ' + + '(Use double quotes when running curl in cmd.exe and single quotes for the rest)' + }, + { + name: 'Set request timeout (in seconds)', + id: 'requestTimeoutInSeconds', + type: 'positiveInteger', + default: 0, + description: 'Set number of seconds the request should wait for a response before ' + + 'timing out (use 0 for infinity)' + }, + { + name: 'Follow redirects', + id: 'followRedirect', + type: 'boolean', + default: true, + description: 'Automatically follow HTTP redirects' + }, + { + name: 'Follow original HTTP method', + id: 'followOriginalHttpMethod', + type: 'boolean', + default: false, + description: 'Redirect with the original HTTP method instead of the default behavior of redirecting with GET' + }, + { + name: 'Trim request body fields', + id: 'trimRequestBody', + type: 'boolean', + default: false, + description: 'Remove white space and additional lines that may affect the server\'s response' + }, + { + name: 'Use Silent Mode', + id: 'silent', + type: 'boolean', + default: false, + description: 'Display the requested data without showing the cURL progress meter or error messages' + } + ]; + } +}; diff --git a/codegens/postman-cli/lib/lodash.js b/codegens/postman-cli/lib/lodash.js new file mode 100644 index 000000000..55ea6666d --- /dev/null +++ b/codegens/postman-cli/lib/lodash.js @@ -0,0 +1,456 @@ +/* istanbul ignore next */ +module.exports = { + + /** + * Checks if `value` is an empty object, array or string. + * + * Objects are considered empty if they have no own enumerable string keyed + * properties. + * + * Values such as strings, arrays are considered empty if they have a `length` of `0`. + * + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is empty, else `false`. + * @example + * + * isEmpty(null) + * // => true + * + * isEmpty(true) + * // => true + * + * isEmpty(1) + * // => true + * + * isEmpty([1, 2, 3]) + * // => false + * + * isEmpty('abc') + * // => false + * + * isEmpty({ 'a': 1 }) + * // => false + */ + isEmpty: function (value) { + // eslint-disable-next-line lodash/prefer-is-nil + if (value === null || value === undefined) { + return true; + } + if (Array.isArray(value) || typeof value === 'string' || typeof value.splice === 'function') { + return !value.length; + } + + for (const key in value) { + if (Object.prototype.hasOwnProperty.call(value, key)) { + return false; + } + } + + return true; + }, + + /** + * Checks if `value` is `undefined`. + * + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is `undefined`, else `false`. + * @example + * + * isUndefined(void 0) + * // => true + * + * isUndefined(null) + * // => false + */ + isUndefined: function (value) { + return value === undefined; + }, + + /** + * Checks if `func` is classified as a `Function` object. + * + * @param {*} func The value to check. + * @returns {boolean} Returns `true` if `func` is a function, else `false`. + * @example + * + * isFunction(self.isEmpty) + * // => true + * + * isFunction(/abc/) + * // => false + */ + isFunction: function (func) { + return typeof func === 'function'; + }, + + /** + * Converts the first character of `string` to upper case and the remaining + * to lower case. + * + * @param {string} [string=''] The string to capitalize. + * @returns {string} Returns the capitalized string. + * @example + * + * capitalize('FRED') + * // => 'Fred' + * + * capitalize('john') + * // => 'John' + */ + + capitalize: function (string) { + return string.charAt(0).toUpperCase() + string.slice(1).toLowerCase(); + }, + + /** + * Reduces `array` to a value which is the accumulated result of running + * each element in `array` thru `iteratee`, where each successive + * invocation is supplied the return value of the previous. If `accumulator` + * is not given, the first element of `array` is used as the initial + * value. The iteratee is invoked with four arguments: + * (accumulator, value, index|key, array). + * + * @param {Array} array The Array to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @param {*} [accumulator] The initial value. + * @returns {*} Returns the accumulated value. + * @example + * + * reduce([1, 2], (sum, n) => sum + n, 0) + * // => 3 + * + */ + reduce: function (array, iteratee, accumulator) { + return array.reduce(iteratee, accumulator); + }, + + /** + * Iterates over elements of `array`, returning an array of all elements + * `predicate` returns truthy for. The predicate is invoked with three + * arguments: (value, index, array). + * + * @param {Array} array The array to iterate over. + * @param {Function|object} predicate The function/object invoked per iteration. + * @returns {Array} Returns the new filtered array. + * @example + * + * const users = [ + * { 'user': 'barney', 'active': true }, + * { 'user': 'fred', 'active': false } + * ] + * + * filter(users, ({ active }) => active) + * // => object for ['barney'] + */ + filter: function (array, predicate) { + if (typeof predicate === 'function') { + return array.filter(predicate); + } + var key = Object.keys(predicate), + val = predicate[key], + res = []; + array.forEach(function (item) { + if (item[key] && item[key] === val) { + res.push(item); + } + }); + return res; + }, + + /** + * The opposite of `filter` this method returns the elements of `array` + * that `predicate` does **not** return truthy for. + * + * @param {Array} array collection to iterate over. + * @param {String} predicate The String that needs to have truthy value, invoked per iteration. + * @returns {Array} Returns the new filtered array. + * @example + * + * const users = [ + * { 'user': 'barney', 'active': true }, + * { 'user': 'fred', 'active': false } + * ] + * + * reject(users, 'active') + * // => object for ['fred'] + */ + reject: function (array, predicate) { + var res = []; + array.forEach((object) => { + if (!object[predicate]) { + res.push(object); + } + }); + return res; + }, + + /** + * Creates an array of values by running each element of `array` thru `iteratee`. + * The iteratee is invoked with three arguments: (value, index, array). + * + * @param {Array} array The array to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @returns {Array} Returns the new mapped array. + * @example + * + * function square(n) { + * return n * n + * } + * + * map([4, 8], square) + * // => [16, 64] + */ + map: function (array, iteratee) { + return array.map(iteratee); + }, + + /** + * Iterates over elements of `collection` and invokes `iteratee` for each element. + * The iteratee is invoked with three arguments: (value, index|key, collection). + * + * @param {Array|Object} collection The collection to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @returns {Array|Object} Returns `collection`. + * @example + * + * forEach([1, 2], value => console.log(value)) + * // => Logs `1` then `2`. + * + * forEach({ 'a': 1, 'b': 2 }, (value, key) => console.log(key)) + * // => Logs 'a' then 'b' + */ + + forEach: function (collection, iteratee) { + if (collection === null) { + return null; + } + + if (Array.isArray(collection)) { + return collection.forEach(iteratee); + } + const iterable = Object(collection), + props = Object.keys(collection); + var index = -1, + key, i; + + for (i = 0; i < props.length; i++) { + key = props[++index]; + iteratee(iterable[key], key, iterable); + } + return collection; + }, + + /** + * Checks if `value` is in `collection`. If `collection` is a string, it's + * checked for a substring of `value`, otherwise it checks if the `value` is present + * as a key in a `collection` object. + * + * @param {Array|Object|string} collection The collection to inspect. + * @param {*} value The value to search for. + * @returns {boolean} Returns `true` if `value` is found, else `false`. + * @example + * + * _.includes([1, 2, 3], 1); + * // => true + * + * _.includes({ 'a': 1, 'b': 2 }, 1); + * // => true + * + * _.includes('abcd', 'bc'); + * // => true + */ + includes: function (collection, value) { + if (Array.isArray(collection) || typeof collection === 'string') { + return collection.includes(value); + } + for (var key in collection) { + if (collection.hasOwnProperty(key)) { + if (collection[key] === value) { + return true; + } + } + } + return false; + }, + + /** + * Gets the size of `collection` by returning its length for array and strings. + * For objects it returns the number of enumerable string keyed + * properties. + * + * @param {Array|Object|string} collection The collection to inspect. + * @returns {number} Returns the collection size. + * @example + * + * size([1, 2, 3]) + * // => 3 + * + * size({ 'a': 1, 'b': 2 }) + * // => 2 + * + * size('pebbles') + * // => 7 + */ + size: function (collection) { + // eslint-disable-next-line lodash/prefer-is-nil + if (collection === null || collection === undefined) { + return 0; + } + if (Array.isArray(collection) || typeof collection === 'string') { + return collection.length; + } + + return Object.keys(collection).length; + }, + + /** + * Converts all elements in `array` into a string separated by `separator`. + * + * @param {Array} array The array to convert. + * @param {string} [separator=','] The element separator. + * @returns {string} Returns the joined string. + * @example + * + * _.join(['a', 'b', 'c'], '~'); + * // => 'a~b~c' + */ + join: function (array, separator) { + if (array === null) { + return ''; + } + return array.join(separator); + }, + + /** + * Removes trailing whitespace or specified characters from `string`. + * + * @param {string} [string=''] The string to trim. + * @param {string} [chars=whitespace] The characters to trim. + * @returns {string} Returns the trimmed string. + * @example + * + * trimEnd(' abc ') + * // => ' abc' + * + * trimEnd('-_-abc-_-', '_-') + * // => '-_-abc' + */ + trimEnd: function (string, chars) { + if (!string) { + return ''; + } + if (string && !chars) { + return string.replace(/\s*$/, ''); + } + chars += '$'; + return string.replace(new RegExp(chars, 'g'), ''); + }, + + /** + * Returns the index of the first + * element `predicate` returns truthy for. + * + * @param {Array} array The array to inspect. + * @param {Object} predicate The exact object to be searched for in the array. + * @returns {number} Returns the index of the found element, else `-1`. + * @example + * + * var users = [ + * { 'user': 'barney', 'active': false }, + * { 'user': 'fred', 'active': false }, + * { 'user': 'pebbles', 'active': true } + * ]; + * + * _.findIndex(users, { 'user': 'fred', 'active': false }); + * // => 1 + * + * _.findIndex(users, {'active' : false}); + * // => 0 + * + */ + findIndex: function (array, predicate) { + var length = array === null ? 0 : array.length, + index = -1, + keys = Object.keys(predicate), + found, i; + if (!length) { + return -1; + } + for (i = 0; i < array.length; i++) { + found = true; + // eslint-disable-next-line no-loop-func + keys.forEach((key) => { + if (!(array[i][key] && array[i][key] === predicate[key])) { + found = false; + } + }); + if (found) { + index = i; + break; + } + } + return index; + }, + + /** + * Gets the value at `path` of `object`. If the resolved value is + * `undefined`, the `defaultValue` is returned in its place. + * + * @param {Object} object The object to query. + * @param {string} path The path of the property to get. + * @param {*} [defaultValue] The value returned for `undefined` resolved values. + * @returns {*} Returns the resolved value. + * @example + * + * const object = { a: {b : 'c'} } + * + * + * get(object, 'a.b.c', 'default') + * // => 'default' + * + * get(object, 'a.b', 'default') + * // => 'c' + */ + get: function (object, path, defaultValue) { + if (object === null) { + return undefined; + } + var arr = path.split('.'), + res = object, + i; + for (i = 0; i < arr.length; i++) { + res = res[arr[i]]; + if (res === undefined) { + return defaultValue; + } + } + return res; + }, + + /** + * Checks if `predicate` returns truthy for **all** elements of `array`. + * Iteration is stopped once `predicate` returns falsey. The predicate is + * invoked with three arguments: (value, index, array). + * + * @param {Array} array The array to iterate over. + * @param {Function} predicate The function invoked per iteration. + * @returns {boolean} Returns `true` if all elements pass the predicate check, + * else `false`. + * @example + * + * every([true, 1, null, 'yes'], Boolean) + * // => false + */ + every: function (array, predicate) { + var index = -1, + length = array === null ? 0 : array.length; + + while (++index < length) { + if (!predicate(array[index], index, array)) { + return false; + } + } + return true; + } + +}; diff --git a/codegens/postman-cli/lib/util.js b/codegens/postman-cli/lib/util.js new file mode 100644 index 000000000..a3a5d4020 --- /dev/null +++ b/codegens/postman-cli/lib/util.js @@ -0,0 +1,379 @@ +const _ = require('./lodash'); + +var self = module.exports = { + /** + * sanitizes input string by handling escape characters eg: converts '''' to '\'\'', (" to \" and \ to \\ ) + * and trim input if required + * + * @param {String} inputString + * @param {Boolean} [trim] - indicates whether to trim string or not + * @param {String} [quoteType] - indicates which quoteType has to be escaped + * @param {Boolean} [backSlash] - indicates whether to escape backslash(\\) + * @param {Boolean} [urlEncode] - indicates whether to url-encode inputString + * @returns {String} + */ + sanitize: function (inputString, trim, quoteType, backSlash = false, urlEncode = false) { + if (typeof inputString !== 'string') { + return ''; + } + + if (urlEncode) { + inputString = encodeURIComponent(inputString); + } + + if (backSlash) { + inputString = inputString.replace(/\\/g, '\\\\'); + } + + if (quoteType === '"') { + inputString = inputString.replace(/"/g, '\\"'); + // Escape backslash if double quote was already escaped before call to sanitize + inputString = inputString.replace(/(? { + defaultOptions[option.id] = { + default: option.default, + type: option.type + }; + if (option.type === 'enum') { + defaultOptions[option.id].availableOptions = option.availableOptions; + } + }); + + for (id in options) { + if (options.hasOwnProperty(id)) { + if (defaultOptions[id] === undefined) { + continue; + } + switch (defaultOptions[id].type) { + case 'boolean': + if (typeof options[id] !== 'boolean') { + result[id] = defaultOptions[id].default; + } + else { + result[id] = options[id]; + } + break; + case 'positiveInteger': + if (typeof options[id] !== 'number' || options[id] < 0) { + result[id] = defaultOptions[id].default; + } + else { + result[id] = options[id]; + } + break; + case 'enum': + if (!defaultOptions[id].availableOptions.includes(options[id])) { + result[id] = defaultOptions[id].default; + } + else { + result[id] = options[id]; + } + break; + default: + result[id] = options[id]; + } + } + } + + for (id in defaultOptions) { + if (defaultOptions.hasOwnProperty(id)) { + if (result[id] === undefined) { + result[id] = defaultOptions[id].default; + } + } + } + return result; + }, + + /** + * Generates args required for NTLM authentication to happen + * + * @param {*} auth - The request sdk request.auth object + * @param {string} quoteType - user provided option to decide whether to use single or double quotes + * @param {string} format - user provided option to decide whether to use long format or not + * @returns {string} - The string to be added if NTLM auth is required + */ + getNtlmAuthInfo: function (auth, quoteType, format) { + const ntlmAuth = auth && auth.ntlm; + + if (!auth || auth.type !== 'ntlm' || !ntlmAuth || !ntlmAuth.count || !ntlmAuth.count()) { + return ''; + } + + const username = ntlmAuth.has('username') && ntlmAuth.get('username'), + password = ntlmAuth.has('password') && ntlmAuth.get('password'), + domain = ntlmAuth.has('domain') && ntlmAuth.get('domain'); + + if (!username && !password) { + return ''; + } + + var userArg = format ? '--user ' : '-u ', + ntlmString = ' --ntlm ' + userArg + quoteType; + + if (domain) { + ntlmString += self.sanitize(domain, true, quoteType) + '\\'; + } + ntlmString += self.sanitize(username, true, quoteType) + ':' + self.sanitize(password, true, quoteType); + ntlmString += quoteType; + + return ntlmString; + }, + + /** + * + * @param {*} urlObject The request sdk request.url object + * @param {boolean} quoteType The user given quoteType + * @returns {String} The final string after parsing all the parameters of the url including + * protocol, auth, host, port, path, query, hash + * This will be used because the url.toString() method returned the URL with non encoded query string + * and hence a manual call is made to getQueryString() method with encode option set as true. + */ + getUrlStringfromUrlObject: function (urlObject, quoteType) { + var url = ''; + if (!urlObject) { + return url; + } + if (urlObject.protocol) { + url += (urlObject.protocol.endsWith('://') ? urlObject.protocol : urlObject.protocol + '://'); + } + if (urlObject.auth && urlObject.auth.user) { + url = url + ((urlObject.auth.password) ? + // ==> username:password@ + urlObject.auth.user + ':' + urlObject.auth.password : urlObject.auth.user) + '@'; + } + if (urlObject.host) { + url += urlObject.getHost(); + } + if (urlObject.port) { + url += ':' + urlObject.port.toString(); + } + if (urlObject.path) { + url += urlObject.getPath(); + } + if (urlObject.query && urlObject.query.count()) { + let queryString = self.getQueryString(urlObject); + queryString && (url += '?' + queryString); + } + if (urlObject.hash) { + url += '#' + urlObject.hash; + } + + return self.sanitize(url, false, quoteType); + }, + + /** + * @param {Object} urlObject + * @returns {String} + */ + getQueryString: function (urlObject) { + let isFirstParam = true, + params = _.get(urlObject, 'query.members'), + result = ''; + if (Array.isArray(params)) { + result = _.reduce(params, function (result, param) { + if (param.disabled === true) { + return result; + } + + if (isFirstParam) { + isFirstParam = false; + } + else { + result += '&'; + } + + return result + self.encodeParam(param.key) + '=' + self.encodeParam(param.value); + }, result); + } + + return result; + }, + + /** + * Encode param except the following characters- [,{,},],%,+ + * + * @param {String} param + * @returns {String} + */ + encodeParam: function (param) { + return encodeURIComponent(param) + .replace(/%5B/g, '[') + .replace(/%7B/g, '{') + .replace(/%5D/g, ']') + .replace(/%7D/g, '}') + .replace(/%2B/g, '+') + .replace(/%25/g, '%') + .replace(/'/g, '%27'); + }, + + /** + * + * @param {Array} array - form data array + * @param {String} key - key of form data param + * @param {String} type - type of form data param(file/text) + * @param {String} val - value/src property of form data param + * @param {String} disabled - Boolean denoting whether the param is disabled or not + * @param {String} contentType - content type header of the param + * + * Appends a single param to form data array + */ + addFormParam: function (array, key, type, val, disabled, contentType) { + if (type === 'file') { + array.push({ + key: key, + type: type, + src: val, + disabled: disabled, + contentType: contentType + }); + } + else { + array.push({ + key: key, + type: type, + value: val, + disabled: disabled, + contentType: contentType + }); + } + }, + + /** + * @param {Object} body + * @returns {boolean} + * + * Determines if a request body is actually empty. + * This is needed because body.isEmpty() returns false for formdata + * and urlencoded when they contain only disabled params which will not + * be a part of the curl request. + */ + isBodyEmpty (body) { + if (!body) { + return true; + } + + if (body.isEmpty()) { + return true; + } + + if (body.mode === 'formdata' || body.mode === 'urlencoded') { + let memberCount = 0; + body[body.mode] && body[body.mode].members && body[body.mode].members.forEach((param) => { + if (!param.disabled) { + memberCount += 1; + } + }); + + return memberCount === 0; + } + + return false; + }, + + /** + * Decide whether we should add the HTTP method explicitly to the cURL command. + * + * @param {Object} request + * @param {Object} options + * + * @returns {Boolean} + */ + shouldAddHttpMethod: function (request, options) { + let followRedirect = options.followRedirect, + followOriginalHttpMethod = options.followOriginalHttpMethod, + disableBodyPruning = true, + isBodyEmpty = self.isBodyEmpty(request.body); + + // eslint-disable-next-line lodash/prefer-is-nil + if (request.protocolProfileBehavior !== null && request.protocolProfileBehavior !== undefined) { + followRedirect = _.get(request, 'protocolProfileBehavior.followRedirects', followRedirect); + followOriginalHttpMethod = + _.get(request, 'protocolProfileBehavior.followOriginalHttpMethod', followOriginalHttpMethod); + disableBodyPruning = _.get(request, 'protocolProfileBehavior.disableBodyPruning', true); + } + + if (followRedirect && followOriginalHttpMethod) { + return true; + } + + switch (request.method) { + case 'HEAD': + return false; + case 'GET': + // disableBodyPruning will generally not be present in the request + // the only time it will be present, its value will be _false_ + // i.e. the user wants to prune the request body despite it being present + if (!isBodyEmpty && disableBodyPruning) { + return true; + } + + return false; + case 'POST': + return isBodyEmpty; + case 'DELETE': + case 'PUT': + case 'PATCH': + default: + return true; + } + } +}; diff --git a/codegens/postman-cli/npm-shrinkwrap.json b/codegens/postman-cli/npm-shrinkwrap.json new file mode 100644 index 000000000..7dfc60f5f --- /dev/null +++ b/codegens/postman-cli/npm-shrinkwrap.json @@ -0,0 +1,5 @@ +{ + "name": "@postman/codegen-curl", + "version": "0.1.6", + "lockfileVersion": 1 +} diff --git a/codegens/postman-cli/npm/test-lint.js b/codegens/postman-cli/npm/test-lint.js new file mode 100644 index 000000000..2f4db0cb8 --- /dev/null +++ b/codegens/postman-cli/npm/test-lint.js @@ -0,0 +1,56 @@ +#!/usr/bin/env node +var shell = require('shelljs'), + chalk = require('chalk'), + async = require('async'), + ESLintCLIEngine = require('eslint').CLIEngine, + + /** + * The list of source code files / directories to be linted. + * + * @type {Array} + */ + LINT_SOURCE_DIRS = [ + './lib', + './test', + './npm/*.js', + './index.js' + ]; + +module.exports = function (exit) { + // banner line + console.info(chalk.yellow.bold('\nLinting files using eslint...')); + + async.waterfall([ + + /** + * Instantiates an ESLint CLI engine and runs it in the scope defined within LINT_SOURCE_DIRS. + * + * @param {Function} next - The callback function whose invocation marks the end of the lint test run. + * @returns {*} + */ + function (next) { + next(null, (new ESLintCLIEngine()).executeOnFiles(LINT_SOURCE_DIRS)); + }, + + /** + * Processes a test report from the Lint test runner, and displays meaningful results. + * + * @param {Object} report - The overall test report for the current lint test. + * @param {Object} report.results - The set of test results for the current lint run. + * @param {Function} next - The callback whose invocation marks the completion of the post run tasks. + * @returns {*} + */ + function (report, next) { + var errorReport = ESLintCLIEngine.getErrorResults(report.results); + // log the result to CLI + console.info(ESLintCLIEngine.getFormatter()(report.results)); + // log the success of the parser if it has no errors + (errorReport && !errorReport.length) && console.info(chalk.green('eslint ok!')); + // ensure that the exit code is non zero in case there was an error + next(Number(errorReport && errorReport.length) || 0); + } + ], exit); +}; + +// ensure we run this script exports if this is a direct stdin.tty run +!module.parent && module.exports(shell.exit); diff --git a/codegens/postman-cli/npm/test-newman.js b/codegens/postman-cli/npm/test-newman.js new file mode 100644 index 000000000..ae7d2afe1 --- /dev/null +++ b/codegens/postman-cli/npm/test-newman.js @@ -0,0 +1,59 @@ +#!/usr/bin/env node +/* eslint-env node, es6 */ +// --------------------------------------------------------------------------------------------------------------------- +// This script is intended to execute all newman tests. +// --------------------------------------------------------------------------------------------------------------------- + +var shell = require('shelljs'), + + // set directories and files for test and coverage report + path = require('path'), + + NYC = require('nyc'), + chalk = require('chalk'), + recursive = require('recursive-readdir'), + + COV_REPORT_PATH = '.coverage', + SPEC_SOURCE_DIR = path.join(__dirname, '..', 'test', 'newman'); + +module.exports = function (exit) { + // banner line + console.info(chalk.yellow.bold('Running newman tests using mocha on node...')); + + shell.test('-d', COV_REPORT_PATH) && shell.rm('-rf', COV_REPORT_PATH); + shell.mkdir('-p', COV_REPORT_PATH); + + var Mocha = require('mocha'), + nyc = new NYC({ + reportDir: COV_REPORT_PATH, + tempDirectory: COV_REPORT_PATH, + reporter: ['text', 'lcov', 'text-summary'], + exclude: ['config', 'test'], + hookRunInContext: true, + hookRunInThisContext: true + }); + + nyc.wrap(); + // add all spec files to mocha + recursive(SPEC_SOURCE_DIR, function (err, files) { + if (err) { console.error(err); return exit(1); } + + var mocha = new Mocha({ timeout: 1000 * 60 }); + + files.filter(function (file) { // extract all test files + return (file.substr(-8) === '.test.js'); + }).forEach(mocha.addFile.bind(mocha)); + + mocha.run(function (runError) { + runError && console.error(runError.stack || runError); + + nyc.reset(); + nyc.writeCoverageFile(); + nyc.report(); + exit(runError ? 1 : 0); + }); + }); +}; + +// ensure we run this script exports if this is a direct stdin.tty run +!module.parent && module.exports(shell.exit); diff --git a/codegens/postman-cli/npm/test-unit.js b/codegens/postman-cli/npm/test-unit.js new file mode 100755 index 000000000..0de7fd529 --- /dev/null +++ b/codegens/postman-cli/npm/test-unit.js @@ -0,0 +1,59 @@ +#!/usr/bin/env node +/* eslint-env node, es6 */ +// --------------------------------------------------------------------------------------------------------------------- +// This script is intended to execute all unit tests. +// --------------------------------------------------------------------------------------------------------------------- + +var shell = require('shelljs'), + + // set directories and files for test and coverage report + path = require('path'), + + NYC = require('nyc'), + chalk = require('chalk'), + recursive = require('recursive-readdir'), + + COV_REPORT_PATH = '.coverage', + SPEC_SOURCE_DIR = path.join(__dirname, '..', 'test', 'unit'); + +module.exports = function (exit) { + // banner line + console.info(chalk.yellow.bold('Running unit tests using mocha on node...')); + + shell.test('-d', COV_REPORT_PATH) && shell.rm('-rf', COV_REPORT_PATH); + shell.mkdir('-p', COV_REPORT_PATH); + + var Mocha = require('mocha'), + nyc = new NYC({ + reportDir: COV_REPORT_PATH, + tempDirectory: COV_REPORT_PATH, + reporter: ['text', 'lcov', 'text-summary'], + exclude: ['config', 'test'], + hookRunInContext: true, + hookRunInThisContext: true + }); + + nyc.wrap(); + // add all spec files to mocha + recursive(SPEC_SOURCE_DIR, function (err, files) { + if (err) { console.error(err); return exit(1); } + + var mocha = new Mocha({ timeout: 1000 * 60 }); + + files.filter(function (file) { // extract all test files + return (file.substr(-8) === '.test.js'); + }).forEach(mocha.addFile.bind(mocha)); + + mocha.run(function (runError) { + runError && console.error(runError.stack || runError); + + nyc.reset(); + nyc.writeCoverageFile(); + nyc.report(); + exit(runError ? 1 : 0); + }); + }); +}; + +// ensure we run this script exports if this is a direct stdin.tty run +!module.parent && module.exports(shell.exit); diff --git a/codegens/postman-cli/npm/test.js b/codegens/postman-cli/npm/test.js new file mode 100755 index 000000000..2c454fb5d --- /dev/null +++ b/codegens/postman-cli/npm/test.js @@ -0,0 +1,16 @@ +#!/usr/bin/env node +var chalk = require('chalk'), + exit = require('shelljs').exit, + prettyms = require('pretty-ms'), + startedAt = Date.now(), + name = require('../package.json').name; + +require('async').series([ + require('./test-lint'), + require('./test-unit'), + require('./test-newman') +], function (code) { + // eslint-disable-next-line max-len + console.info(chalk[code ? 'red' : 'green'](`\n${name}: duration ${prettyms(Date.now() - startedAt)}\n${name}: ${code ? 'not ok' : 'ok'}!`)); + exit(code && (typeof code === 'number' ? code : 1) || 0); +}); diff --git a/codegens/postman-cli/package.json b/codegens/postman-cli/package.json new file mode 100644 index 000000000..7e0317129 --- /dev/null +++ b/codegens/postman-cli/package.json @@ -0,0 +1,30 @@ +{ + "name": "@postman/codegen-curl", + "version": "0.1.6", + "description": "Converter plugin to convert from postman sdk request to cURL code snippet", + "com_postman_plugin": { + "type": "code_generator", + "lang": "curl", + "variant": "cURL", + "syntax_mode": "powershell" + }, + "main": "index.js", + "scripts": { + "test": "node npm/test.js", + "test-lint": "node npm/test-lint.js", + "test-newman": "node npm/test-newman.js", + "test-unit": "node npm/test-unit.js" + }, + "repository": { + "type": "git", + "url": "" + }, + "author": "Postman Labs ", + "license": "Apache-2.0", + "homepage": "https://github.com/postmanlabs/code-generators/tree/master/codegens/curl", + "dependencies": {}, + "devDependencies": {}, + "engines": { + "node": ">=8" + } +} diff --git a/codegens/postman-cli/test/.eslintrc b/codegens/postman-cli/test/.eslintrc new file mode 100644 index 000000000..e8db1afa8 --- /dev/null +++ b/codegens/postman-cli/test/.eslintrc @@ -0,0 +1,29 @@ +{ + "plugins": [ + "mocha" + ], + "env": { + "mocha": true, + "node": true, + "es6": true + }, + "rules": { + "mocha/handle-done-callback": "error", + "mocha/max-top-level-suites": "error", + "mocha/no-exclusive-tests": "error", + "mocha/no-global-tests": "error", + "mocha/no-hooks-for-single-case": "off", + "mocha/no-hooks": "off", + "mocha/no-identical-title": "error", + "mocha/no-mocha-arrows": "error", + "mocha/no-nested-tests": "error", + "mocha/no-pending-tests": "error", + "mocha/no-return-and-callback": "error", + "mocha/no-sibling-hooks": "error", + "mocha/no-skipped-tests": "warn", + "mocha/no-synchronous-tests": "off", + "mocha/no-top-level-hooks": "warn", + "mocha/valid-test-description": "off", + "mocha/valid-suite-description": "off" + } +} diff --git a/codegens/postman-cli/test/ci-install.sh b/codegens/postman-cli/test/ci-install.sh new file mode 100755 index 000000000..881fb5bac --- /dev/null +++ b/codegens/postman-cli/test/ci-install.sh @@ -0,0 +1,5 @@ +#!/bin/bash +set -ev; # stop on error + +echo "Installing curl" +sudo apt-get install -y curl diff --git a/codegens/postman-cli/test/newman/newman.test.js b/codegens/postman-cli/test/newman/newman.test.js new file mode 100644 index 000000000..3cf1fb9f5 --- /dev/null +++ b/codegens/postman-cli/test/newman/newman.test.js @@ -0,0 +1,31 @@ +var runNewmanTest = require('../../../../test/codegen/newman/newmanTestUtil').runNewmanTest, + convert = require('../../index').convert; + +describe('Convert for different types of request', function () { + var testConfig = {compileScript: null, runScript: null, fileName: null}, + options1 = { + indentCount: 3, + indentType: 'Space', + requestTimeout: 200, + multiLine: true, + followRedirect: true, + longFormat: true, + silent: true, + lineContinuationCharacter: '\\', + quoteType: 'single' + }, + options2 = { + indentCount: 3, + indentType: 'Space', + requestTimeout: 200, + multiLine: true, + followRedirect: true, + longFormat: false, + silent: true, + lineContinuationCharacter: '\\', + quoteType: 'single' + }; + + runNewmanTest(convert, options1, testConfig); + runNewmanTest(convert, options2, testConfig); +}); diff --git a/codegens/postman-cli/test/unit/.gitkeep b/codegens/postman-cli/test/unit/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/codegens/postman-cli/test/unit/convert.test.js b/codegens/postman-cli/test/unit/convert.test.js new file mode 100644 index 000000000..d3c4c5061 --- /dev/null +++ b/codegens/postman-cli/test/unit/convert.test.js @@ -0,0 +1,1224 @@ +var _ = require('lodash'), + expect = require('chai').expect, + { Request } = require('postman-collection/lib/collection/request'), + { Url } = require('postman-collection/lib/collection/url'), + convert = require('../../index').convert, + getUrlStringfromUrlObject = require('../../lib/util').getUrlStringfromUrlObject; + +describe('curl convert function', function () { + describe('Convert function', function () { + var request, options, snippetArray, line; + + it('should return snippet with carat(^) as line continuation ' + + 'character for multiline code generation', function () { + request = new Request({ + 'method': 'POST', + 'header': [], + 'body': { + 'mode': 'raw', + 'raw': '' + } + }); + options = { + multiLine: true, + lineContinuationCharacter: '^' + }; + convert(request, options, function (error, snippet) { + if (error) { + expect.fail(null, null, error); + } + snippetArray = snippet.split('\n'); + // Ignoring the last line as there is no line continuation character at last line + for (var i = 0; i < snippetArray.length - 1; i++) { + line = snippetArray[i]; + expect(line.charAt(line.length - 1)).to.equal('^'); + } + }); + }); + + it('should return snippet with url in single quote(\')', function () { + request = new Request({ + 'method': 'POST', + 'header': [], + 'body': { + 'mode': 'raw', + 'raw': '' + } + }); + options = { + quoteType: 'single' + }; + convert(request, options, function (error, snippet) { + if (error) { + expect.fail(null, null, error); + } + + snippetArray = snippet.split(' '); + expect(snippetArray[4][0]).to.equal('\''); + }); + }); + + it('should return snippet with url in double quote(")', function () { + request = new Request({ + 'method': 'POST', + 'header': [], + 'body': { + 'mode': 'raw', + 'raw': '' + } + }); + options = { + quoteType: 'double' + }; + convert(request, options, function (error, snippet) { + if (error) { + expect.fail(null, null, error); + } + + snippetArray = snippet.split(' '); + expect(snippetArray[4][0]).to.equal('"'); + }); + }); + + it('should add semicolon after header key, if the value is empty string', function () { + request = new Request({ + 'method': 'GET', + 'header': [ + { + 'key': 'hello', + 'value': '' + } + ], + 'url': { + 'raw': 'https://postman-echo.com/get', + 'protocol': 'https', + 'host': [ + 'postman-echo', + 'com' + ], + 'path': [ + 'get' + ] + } + }); + convert(request, {}, function (error, snippet) { + if (error) { + expect.fail(null, null, error); + } + expect(snippet).to.be.a('string'); + expect(snippet).to.contain('--header \'hello;\''); + }); + }); + + it('should return snippet with backslash(\\) as line continuation ' + + 'character for multiline code generation by default', function () { + request = new Request({ + 'method': 'POST', + 'header': [], + 'body': { + 'mode': 'raw', + 'raw': '' + } + }); + options = { + multiLine: true + }; + convert(request, options, function (error, snippet) { + if (error) { + expect.fail(null, null, error); + } + snippetArray = snippet.split('\n'); + // Ignoring the last line as there is no line continuation character at last line + for (var i = 0; i < snippetArray.length - 1; i++) { + line = snippetArray[i]; + expect(line.charAt(line.length - 1)).to.equal('\\'); + } + }); + }); + + it('should return snippet with backtick(`) as line continuation ' + + 'character for multiline code generation', function () { + request = new Request({ + 'method': 'POST', + 'header': [], + 'body': { + 'mode': 'raw', + 'raw': '' + } + }); + options = { + multiLine: true, + lineContinuationCharacter: '`' + }; + convert(request, options, function (error, snippet) { + if (error) { + expect.fail(null, null, error); + } + snippetArray = snippet.split('\n'); + // Ignoring the last line as there is no line continuation character at last line + for (var i = 0; i < snippetArray.length - 1; i++) { + line = snippetArray[i]; + expect(line.charAt(line.length - 1)).to.equal('`'); + } + }); + }); + + it('should add content type if formdata field contains a content-type', function () { + request = new Request({ + 'method': 'POST', + 'body': { + 'mode': 'formdata', + 'formdata': [ + { + 'key': 'json', + 'value': '{"hello": "world"}', + 'contentType': 'application/json', + 'type': 'text' + } + ] + }, + 'url': { + 'raw': 'http://postman-echo.com/post', + 'host': [ + 'postman-echo', + 'com' + ], + 'path': [ + 'post' + ] + } + }); + + convert(request, {}, function (error, snippet) { + if (error) { + expect.fail(null, null, error); + } + expect(snippet).to.be.a('string'); + expect(snippet).to.contain('--form \'json="{\\"hello\\": \\"world\\"}";type=application/json\''); + + }); + }); + + it('should parse header with string value properly', function () { + request = new Request({ + 'method': 'POST', + 'header': [ + { + 'key': 'foo', + 'value': '"bar"' + } + ], + 'body': { + 'mode': 'raw', + 'raw': '' + } + }); + options = { + longFormat: false + }; + convert(request, options, function (error, snippet) { + if (error) { + expect.fail(null, null, error); + } + expect(snippet).to.include("-H 'foo: \"bar\"'"); // eslint-disable-line quotes + }); + }); + + it('should generate snippet with -g parameter when either of {,[,},] are present in url parameter', function () { + [ + '{world}', + '{{world', + '[world]', + ']world', + 'world}' + ].forEach(function (value) { + const request = new Request({ + 'method': 'GET', + 'url': { + 'raw': `http://example.com?hello=${value}`, + 'protocol': 'http', + 'host': [ + 'example', + 'com' + ], + 'query': [ + { + 'key': 'hello', + 'value': value + } + ] + } + }); + convert(request, {}, function (error, snippet) { + if (error) { + expect.fail(null, null, error); + } + expect(snippet).to.include('-g'); + }); + convert(request, { longFormat: true }, function (error, snippet) { + if (error) { + expect.fail(null, null, error); + } + expect(snippet).to.include('--globoff'); + }); + }); + }); + + it('should return snippet without errors when request object has no body property', function () { + request = new Request({ + 'method': 'GET', + 'header': [], + 'url': { + 'raw': 'https://google.com', + 'protocol': 'https', + 'host': [ + 'google', + 'com' + ] + } + }); + options = { + longFormat: false + }; + convert(request, options, function (error, snippet) { + if (error) { + expect.fail(null, null, error); + } + expect(snippet).to.be.a('string'); + expect(snippet).to.include("'https://google.com'"); // eslint-disable-line quotes + }); + }); + + it('should return snippet with JSON body in single line if multiline option is false', function () { + request = new Request({ + 'method': 'POST', + 'header': [], + 'body': { + 'mode': 'raw', + 'raw': '{\n "name": "John",\n "type": "names",\n "id": "123sdaw"\n}', + 'options': { + 'raw': { + 'language': 'json' + } + } + }, + 'url': { + 'raw': 'https://postman-echo.com/post', + 'protocol': 'https', + 'host': [ + 'postman-echo', + 'com' + ], + 'path': [ + 'post' + ] + } + }); + options = { + multiLine: false, + longFormat: false, + lineContinuationCharacter: '\\', + quoteType: 'single', + requestTimeoutInSeconds: 0, + followRedirect: true, + followOriginalHttpMethod: false + }; + + convert(request, options, function (error, snippet) { + if (error) { + expect.fail(null, null, error); + } + expect(snippet).to.be.a('string'); + expect(snippet).to.contain('-d \'{"name":"John","type":"names","id":"123sdaw"}\''); + }); + }); + + it('should return snippet with backslash(\\) character as line continuation ' + + 'character for multiline code generation', function () { + request = new Request({ + 'method': 'POST', + 'header': [], + 'body': { + 'mode': 'raw', + 'raw': '' + } + }); + options = { + multiLine: true, + lineContinuationCharacter: '\\' + }; + convert(request, options, function (error, snippet) { + if (error) { + expect.fail(null, null, error); + } + snippetArray = snippet.split('\n'); + // Ignoring the last line as there is no line continuation character at last line + for (var i = 0; i < snippetArray.length - 1; i++) { + line = snippetArray[i]; + expect(line.charAt(line.length - 1)).to.equal('\\'); + } + }); + }); + + it('should not encode queryParam unresolved variables and ' + + 'leave it inside double parenthesis {{xyz}}', function () { + request = new Request({ + 'method': 'POST', + 'header': [], + 'url': { + 'raw': 'http://postman-echo.com/post?a={{xyz}}', + 'protocol': 'http', + 'host': [ + 'postman-echo', + 'com' + ], + 'path': [ + 'post' + ], + 'query': [ + { + 'key': 'a', + 'value': '{{xyz}}' + } + ] + } + }); + options = {}; + convert(request, options, function (error, snippet) { + if (error) { + expect.fail(null, null, error); + } + expect(snippet).to.be.a('string'); + expect(snippet).to.include('http://postman-echo.com/post?a={{xyz}}'); + expect(snippet).to.not.include('http://postman-echo.com/post?a=%7B%7Bxyz%7D%7D'); + }); + }); + + it('should encode queryParams other than unresolved variables', function () { + request = new Request({ + 'method': 'POST', + 'header': [], + 'url': { + 'raw': 'http://postman-echo.com/post?a=b c', + 'protocol': 'http', + 'host': [ + 'postman-echo', + 'com' + ], + 'path': [ + 'post' + ], + 'query': [ + { + 'key': 'a', + 'value': 'b c' + } + ] + } + }); + options = {}; + convert(request, options, function (error, snippet) { + if (error) { + expect.fail(null, null, error); + } + expect(snippet).to.be.a('string'); + expect(snippet).to.include('http://postman-echo.com/post?a=b%20c'); + expect(snippet).to.not.include('http://postman-echo.com/post?a=b c'); + }); + }); + + it('should trim header keys and not trim header values', function () { + var request = new Request({ + 'method': 'GET', + 'header': [ + { + 'key': ' key_containing_whitespaces ', + 'value': ' value_containing_whitespaces ' + } + ], + 'url': { + 'raw': 'https://google.com', + 'protocol': 'https', + 'host': [ + 'google', + 'com' + ] + } + }); + convert(request, { longFormat: true }, function (error, snippet) { + if (error) { + expect.fail(null, null, error); + } + expect(snippet).to.be.a('string'); + // one extra space in matching the output because we add key:value in the snippet + expect(snippet).to.include( + `--header 'key_containing_whitespaces: value_containing_whitespaces '`); // eslint-disable-line quotes + }); + }); + + it('should generate snippets for no files in form data', function () { + var request = new Request({ + 'method': 'POST', + 'header': [], + 'body': { + 'mode': 'formdata', + 'formdata': [ + { + 'key': 'no file', + 'value': '', + 'type': 'file', + 'src': [] + }, + { + 'key': 'no src', + 'value': '', + 'type': 'file' + }, + { + 'key': 'invalid src', + 'value': '', + 'type': 'file', + 'src': {} + } + ] + }, + 'url': { + 'raw': 'https://postman-echo.com/post', + 'protocol': 'https', + 'host': [ + 'postman-echo', + 'com' + ], + 'path': [ + 'post' + ] + } + }); + convert(request, {}, function (error, snippet) { + if (error) { + expect.fail(null, null, error); + } + expect(snippet).to.be.a('string'); + expect(snippet).to.include('no file=@"/path/to/file"'); + expect(snippet).to.include('no src=@"/path/to/file"'); + expect(snippet).to.include('invalid src=@"/path/to/file"'); + }); + }); + + it('should generate valid snippets for single/double quotes in URL', function () { + // url = https://a"b'c.com/'d/"e + var request = new Request("https://a\"b'c.com/'d/\"e"); // eslint-disable-line quotes + convert(request, {}, function (error, snippet) { + if (error) { + expect.fail(null, null, error); + } + // for curl escaping of single quotes inside single quotes involves changing of ' to '\'' + // expect => 'https://a"b'\''c.com/'\''d/"e' + expect(snippet).to.include("'https://a\"b'\\''c.com/'\\''d/\"e'"); // eslint-disable-line quotes + }); + }); + + it('should generate valid snippets when quoteType is "double"', function () { + // url = https://a"b'c.com/'d/"e + var request = new Request({ + 'method': 'POST', + 'body': { + 'mode': 'formdata', + 'formdata': [ + { + 'key': 'json', + 'value': '{"hello": "world"}', + 'contentType': 'application/json', + 'type': 'text' + } + ] + }, + 'url': { + 'raw': "https://a\"b'c.com/'d/\"e", // eslint-disable-line quotes + 'host': [ + 'a"b\'c', + 'com' + ] + } + }); + convert(request, {quoteType: 'double'}, function (error, snippet) { + if (error) { + expect.fail(null, null, error); + } + + expect(snippet).to.include('"a\\"b\'c.com"'); + expect(snippet).to.include('"json=\\"{\\\\\\"hello\\\\\\": \\\\\\"world\\\\\\"}\\";type=application/json"'); + }); + }); + + it('should not add appropriate escaping characters when quote type is "double"', function () { + var request = new Request({ + 'method': 'POST', + 'header': [], + 'body': { + 'mode': 'graphql', + 'graphql': { + 'query': '{\n findScenes(\n filter: {per_page: 0}\n scene_filter: {is_missing: "performers"}){\n count\n scenes {\n id\n title\n path\n }\n }\n}', // eslint-disable-line + 'variables': '{\n\t"variable_key": "variable_value"\n}' + } + }, + 'url': { + 'raw': 'https://postman-echo.com/post', + 'protocol': 'https', + 'host': [ + 'postman-echo', + 'com' + ], + 'path': [ + 'post' + ] + } + }); + convert(request, { quoteType: 'double', lineContinuationCharacter: '^' }, function (error, snippet) { + if (error) { + expect.fail(null, null, error); + } + + expect(snippet).to.include('{\\"query\\":\\"{\\n findScenes(\\n filter: {per_page: 0}\\n scene_filter: {is_missing: \\\\\\"performers\\\\\\"})'); // eslint-disable-line + }); + }); + + it('should escape special characters when quoteType is "double"', function () { + var request = new Request({ + 'method': 'POST', + 'header': [], + 'body': { + 'mode': 'raw', + 'raw': '{\r\n "hello": "$(whoami)"\r\n}', + 'options': { + 'raw': { + 'language': 'json' + } + } + }, + 'url': { + 'raw': 'https://postman-echo.com/post', + 'protocol': 'https', + 'host': [ + 'postman-echo', + 'com' + ], + 'path': [ + 'post' + ] + } + }); + convert(request, { quoteType: 'double', lineContinuationCharacter: '^' }, function (error, snippet) { + if (error) { + expect.fail(null, null, error); + } + + expect(snippet.includes('\\"hello\\": \\"\\$(whoami)\\"')).to.be.true; // eslint-disable-line + }); + }); + + it('should longer option for body even if longFormat is disabled if @ character is present', function () { + let request = new Request({ + 'method': 'POST', + 'header': [], + 'body': { + 'mode': 'raw', + 'raw': '@hello' + }, + 'url': { + 'raw': 'https://postman-echo.com/post', + 'protocol': 'https', + 'host': [ + 'postman-echo', + 'com' + ], + 'path': [ + 'post' + ] + } + }); + + convert(request, { longFormat: false }, function (error, snippet) { + if (error) { + expect.fail(null, null, error); + } + + expect(snippet).include('--data-raw'); + }); + }); + + describe('getUrlStringfromUrlObject function', function () { + var rawUrl, urlObject, outputUrlString; + + it('should return empty string for an url object for an empty url or if no url object is passed', function () { + rawUrl = ''; + urlObject = new Url(rawUrl); + outputUrlString = getUrlStringfromUrlObject(urlObject); + expect(outputUrlString).to.be.empty; + outputUrlString = getUrlStringfromUrlObject(); + expect(outputUrlString).to.be.empty; + }); + + it('should add protocol if present in the url object', function () { + rawUrl = 'https://postman-echo.com'; + urlObject = new Url(rawUrl); + outputUrlString = getUrlStringfromUrlObject(urlObject); + expect(outputUrlString).to.equal(rawUrl); + }); + + it('should add the auth information if present in the url object', function () { + rawUrl = 'https://user:password@postman-echo.com'; + urlObject = new Url(rawUrl); + outputUrlString = getUrlStringfromUrlObject(urlObject); + expect(outputUrlString).to.equal(rawUrl); + }); + + it('should not add the auth information if user isn\'t present but' + + ' password is present in the url object', function () { + rawUrl = 'https://:password@postman-echo.com'; + urlObject = new Url(rawUrl); + outputUrlString = getUrlStringfromUrlObject(urlObject); + expect(outputUrlString).to.not.include(':password'); + }); + + it('should add host if present in the url object', function () { + rawUrl = 'https://postman-echo.com'; + urlObject = new Url(rawUrl); + outputUrlString = getUrlStringfromUrlObject(urlObject); + expect(outputUrlString).to.equal(rawUrl); + }); + + it('should add port if present in the url object', function () { + rawUrl = 'https://postman-echo.com:8080'; + urlObject = new Url(rawUrl); + outputUrlString = getUrlStringfromUrlObject(urlObject); + expect(outputUrlString).to.equal(rawUrl); + }); + + it('should add path if present in the url object', function () { + rawUrl = 'https://postman-echo.com/get'; + urlObject = new Url(rawUrl); + outputUrlString = getUrlStringfromUrlObject(urlObject); + expect(outputUrlString).to.equal(rawUrl); + }); + + describe('queryParams', function () { + + it('should not encode unresolved query params', function () { + rawUrl = 'https://postman-echo.com/get?key={{value}}'; + urlObject = new Url(rawUrl); + outputUrlString = getUrlStringfromUrlObject(urlObject); + expect(outputUrlString).to.not.include('key=%7B%7Bvalue%7B%7B'); + expect(outputUrlString).to.equal(rawUrl); + }); + + it('should encode query params other than unresolved variables', function () { + rawUrl = 'https://postman-echo.com/get?key=\'a b c\''; + urlObject = new Url(rawUrl); + outputUrlString = getUrlStringfromUrlObject(urlObject); + expect(outputUrlString).to.not.include('key=\'a b c\''); + expect(outputUrlString).to.equal('https://postman-echo.com/get?key=%27a%20b%20c%27'); + }); + + it('should not encode unresolved query params and ' + + 'encode every other query param, both present together', function () { + rawUrl = 'https://postman-echo.com/get?key1={{value}}&key2=\'a b+c\''; + urlObject = new Url(rawUrl); + outputUrlString = getUrlStringfromUrlObject(urlObject); + expect(outputUrlString).to.not.include('key1=%7B%7Bvalue%7B%7B'); + expect(outputUrlString).to.not.include('key2=\'a b+c\''); + expect(outputUrlString).to.equal('https://postman-echo.com/get?key1={{value}}&key2=%27a%20b+c%27'); + }); + + it('should not encode query params that are already encoded', function () { + rawUrl = 'https://postman-echo.com/get?query=urn%3Ali%3Afoo%3A62324'; + urlObject = new Url(rawUrl); + outputUrlString = getUrlStringfromUrlObject(urlObject); + expect(outputUrlString).to.equal('https://postman-echo.com/get?query=urn%3Ali%3Afoo%3A62324'); + }); + + it('should discard disabled query params', function () { + urlObject = new Url({ + protocol: 'https', + host: 'postman-echo.com', + query: [ + { key: 'foo', value: 'bar' }, + { key: 'alpha', value: 'beta', disabled: true } + ] + }); + outputUrlString = getUrlStringfromUrlObject(urlObject); + expect(outputUrlString).to.equal('https://postman-echo.com?foo=bar'); + }); + }); + + it('should add hash if present in the url object', function () { + rawUrl = 'https://postmanm-echo.com/get#hash'; + urlObject = new Url(rawUrl); + outputUrlString = getUrlStringfromUrlObject(urlObject); + expect(outputUrlString).to.equal(rawUrl); + }); + }); + + it('should not add --request parameter in POST request if body is present', function () { + var request = new Request({ + 'method': 'POST', + 'header': [], + 'body': { + 'mode': 'graphql', + 'graphql': { + 'query': '{\n findScenes(\n filter: {per_page: 0}\n scene_filter: {is_missing: "performers"}){\n count\n scenes {\n id\n title\n path\n }\n }\n}', // eslint-disable-line + 'variables': '{\n\t"variable_key": "variable_value"\n}' + } + }, + 'url': { + 'raw': 'https://postman-echo.com/post', + 'protocol': 'https', + 'host': [ + 'postman-echo', + 'com' + ], + 'path': [ + 'post' + ] + } + }); + + convert(request, { followRedirect: true }, function (error, snippet) { + if (error) { + expect.fail(null, null, error); + } + expect(snippet).to.be.a('string'); + expect(snippet).to.not.include('--request POST'); + }); + }); + + it('should add --request parameter in POST request if body is not present', function () { + var request = new Request({ + 'method': 'POST', + 'header': [], + 'url': { + 'raw': 'https://postman-echo.com/post', + 'protocol': 'https', + 'host': [ + 'postman-echo', + 'com' + ], + 'path': [ + 'post' + ] + } + }); + + convert(request, { followRedirect: true }, function (error, snippet) { + if (error) { + expect.fail(null, null, error); + } + expect(snippet).to.be.a('string'); + expect(snippet).to.include('--request POST'); + }); + }); + + it('should add --request parameter in GET request if body is present', function () { + var request = new Request({ + 'method': 'GET', + 'header': [], + 'body': { + 'mode': 'graphql', + 'graphql': { + 'query': '{\n findScenes(\n filter: {per_page: 0}\n scene_filter: {is_missing: "performers"}){\n count\n scenes {\n id\n title\n path\n }\n }\n}', // eslint-disable-line + 'variables': '{\n\t"variable_key": "variable_value"\n}' + } + }, + 'url': { + 'raw': 'https://postman-echo.com/get', + 'protocol': 'https', + 'host': [ + 'postman-echo', + 'com' + ], + 'path': [ + 'get' + ] + } + }); + + convert(request, { followRedirect: true }, function (error, snippet) { + if (error) { + expect.fail(null, null, error); + } + expect(snippet).to.be.a('string'); + expect(snippet).to.include('--request GET'); + }); + }); + + it('should not add --request parameter in GET request if body is present ' + + 'but disableBodyPruning is false', function () { + const request = new Request({ + 'method': 'GET', + 'header': [], + 'body': { + 'mode': 'graphql', + 'graphql': { + 'query': '{\n findScenes(\n filter: {per_page: 0}\n scene_filter: {is_missing: "performers"}){\n count\n scenes {\n id\n title\n path\n }\n }\n}', // eslint-disable-line + 'variables': '{\n\t"variable_key": "variable_value"\n}' + } + }, + 'url': { + 'raw': 'https://postman-echo.com/get', + 'protocol': 'https', + 'host': [ + 'postman-echo', + 'com' + ], + 'path': [ + 'get' + ] + } + }); + + // this needs to be done here because protocolProfileBehavior is not in collections SDK + request.protocolProfileBehavior = { + disableBodyPruning: false + }; + + convert(request, { followRedirect: true }, function (error, snippet) { + if (error) { + expect.fail(null, null, error); + } + expect(snippet).to.be.a('string'); + expect(snippet).to.not.include('--request GET'); + }); + }); + + describe('followRedirect and followOriginalHttpMethod', function () { + it('should add --request parameter when passed true via options', function () { + const request = new Request({ + 'method': 'POST', + 'header': [], + 'body': { + 'mode': 'graphql', + 'graphql': { + 'query': '{\n findScenes(\n filter: {per_page: 0}\n scene_filter: {is_missing: "performers"}){\n count\n scenes {\n id\n title\n path\n }\n }\n}', // eslint-disable-line + 'variables': '{\n\t"variable_key": "variable_value"\n}' + } + }, + 'url': { + 'raw': 'https://postman-echo.com/post', + 'protocol': 'https', + 'host': [ + 'postman-echo', + 'com' + ], + 'path': [ + 'post' + ] + } + }); + + convert(request, { followRedirect: true, followOriginalHttpMethod: true }, function (error, snippet) { + if (error) { + expect.fail(null, null, error); + } + expect(snippet).to.be.a('string'); + expect(snippet).to.include('--request POST'); + }); + }); + + it('should not add --request parameter when passed false via options', function () { + const request = new Request({ + 'method': 'POST', + 'header': [], + 'body': { + 'mode': 'graphql', + 'graphql': { + 'query': '{\n findScenes(\n filter: {per_page: 0}\n scene_filter: {is_missing: "performers"}){\n count\n scenes {\n id\n title\n path\n }\n }\n}', // eslint-disable-line + 'variables': '{\n\t"variable_key": "variable_value"\n}' + } + }, + 'url': { + 'raw': 'https://postman-echo.com/post', + 'protocol': 'https', + 'host': [ + 'postman-echo', + 'com' + ], + 'path': [ + 'post' + ] + } + }); + + convert(request, { followRedirect: false, followOriginalHttpMethod: false }, function (error, snippet) { + if (error) { + expect.fail(null, null, error); + } + expect(snippet).to.be.a('string'); + expect(snippet).to.not.include('--request POST'); + }); + }); + + it('should add --request parameter when passed false via options but true in request settings', function () { + const request = new Request({ + 'method': 'POST', + 'header': [], + 'body': { + 'mode': 'graphql', + 'graphql': { + 'query': '{\n findScenes(\n filter: {per_page: 0}\n scene_filter: {is_missing: "performers"}){\n count\n scenes {\n id\n title\n path\n }\n }\n}', // eslint-disable-line + 'variables': '{\n\t"variable_key": "variable_value"\n}' + } + }, + 'url': { + 'raw': 'https://postman-echo.com/post', + 'protocol': 'https', + 'host': [ + 'postman-echo', + 'com' + ], + 'path': [ + 'post' + ] + } + }); + + // this needs to be done here because protocolProfileBehavior is not in collections SDK + request.protocolProfileBehavior = { + followRedirects: true, + followOriginalHttpMethod: true + }; + + convert(request, { followRedirect: false, followOriginalHttpMethod: false }, function (error, snippet) { + if (error) { + expect.fail(null, null, error); + } + expect(snippet).to.be.a('string'); + expect(snippet).to.include('--request POST'); + }); + }); + + it('should not add --request parameter when passed true via options but false in request settings', function () { + const request = new Request({ + 'method': 'POST', + 'header': [], + 'body': { + 'mode': 'graphql', + 'graphql': { + 'query': '{\n findScenes(\n filter: {per_page: 0}\n scene_filter: {is_missing: "performers"}){\n count\n scenes {\n id\n title\n path\n }\n }\n}', // eslint-disable-line + 'variables': '{\n\t"variable_key": "variable_value"\n}' + } + }, + 'url': { + 'raw': 'https://postman-echo.com/post', + 'protocol': 'https', + 'host': [ + 'postman-echo', + 'com' + ], + 'path': [ + 'post' + ] + } + }); + + // this needs to be done here because protocolProfileBehavior is not in collections SDK + request.protocolProfileBehavior = { + followRedirects: false, + followOriginalHttpMethod: false + }; + + convert(request, { followRedirect: true, followOriginalHttpMethod: true }, function (error, snippet) { + if (error) { + expect.fail(null, null, error); + } + expect(snippet).to.be.a('string'); + expect(snippet).to.not.include('--request POST'); + }); + }); + + it('should work when protocolProfileBehavior is null in request settings', function () { + const request = new Request({ + 'method': 'POST', + 'header': [], + 'body': { + 'mode': 'graphql', + 'graphql': { + 'query': '{\n findScenes(\n filter: {per_page: 0}\n scene_filter: {is_missing: "performers"}){\n count\n scenes {\n id\n title\n path\n }\n }\n}', // eslint-disable-line + 'variables': '{\n\t"variable_key": "variable_value"\n}' + } + }, + 'url': { + 'raw': 'https://postman-echo.com/post', + 'protocol': 'https', + 'host': [ + 'postman-echo', + 'com' + ], + 'path': [ + 'post' + ] + } + }); + + // this needs to be done here because protocolProfileBehavior is not in collections SDK + request.protocolProfileBehavior = null; + + convert(request, { followRedirect: true, followOriginalHttpMethod: true }, function (error, snippet) { + if (error) { + expect.fail(null, null, error); + } + expect(snippet).to.be.a('string'); + expect(snippet).to.include('--request POST'); + }); + }); + }); + + describe('should correctly handle NTLM auth', function () { + const sampleRequest = { + 'method': 'POST', + 'header': [], + 'auth': { + 'type': 'ntlm', + 'ntlm': [] + }, + 'url': { + 'raw': 'https://postman-echo.com/post', + 'protocol': 'https', + 'host': [ + 'postman-echo', + 'com' + ], + 'path': [ + 'post' + ] + } + }; + + it('when no username or password is present', function () { + const request = new Request(sampleRequest); + + convert(request, {}, function (error, snippet) { + if (error) { + expect.fail(null, null, error); + } + expect(snippet).to.be.a('string'); + expect(snippet).to.not.include('--ntlm'); + }); + }); + + it('when empty username and password is present', function () { + const request = new Request(Object.assign({ auth: { + 'type': 'ntlm', + 'ntlm': [ + {key: 'username', value: ''}, + {key: 'password', value: ''} + ] + }}, sampleRequest)); + + convert(request, {}, function (error, snippet) { + if (error) { + expect.fail(null, null, error); + } + expect(snippet).to.be.a('string'); + expect(snippet).to.not.include('--ntlm'); + }); + }); + + it('when correct username and password is present with single quotes as option', function () { + const request = new Request(_.set(sampleRequest, 'auth.ntlm', [ + {key: 'username', value: 'joh\'n'}, + {key: 'password', value: 'tennesse"e'} + ])); + + convert(request, { quoteType: 'single' }, function (error, snippet) { + if (error) { + expect.fail(null, null, error); + } + expect(snippet).to.be.a('string'); + expect(snippet).to.equal('curl --ntlm --user \'joh\'\\\'\'n:tennesse"e\' --location' + + ' --request POST \'https://postman-echo.com/post\''); + }); + }); + + it('when correct username and password is present with double as option', function () { + const request = new Request(_.set(sampleRequest, 'auth.ntlm', [ + {key: 'username', value: 'joh\'n'}, + {key: 'password', value: 'tennesse"e'} + ])); + + convert(request, { quoteType: 'double' }, function (error, snippet) { + if (error) { + expect.fail(null, null, error); + } + expect(snippet).to.be.a('string'); + expect(snippet).to.equal('curl --ntlm --user "joh\'n:tennesse\\"e" --location' + + ' --request POST "https://postman-echo.com/post"'); + }); + }); + + it('when correct username and password is present with long format option disabled', function () { + const request = new Request(_.set(sampleRequest, 'auth.ntlm', [ + {key: 'username', value: 'joh\'n'}, + {key: 'password', value: 'tennesse"e'} + ])); + + convert(request, { longFormat: false }, function (error, snippet) { + if (error) { + expect.fail(null, null, error); + } + expect(snippet).to.be.a('string'); + expect(snippet).to.equal('curl --ntlm -u \'joh\'\\\'\'n:tennesse"e\' -L' + + ' -X POST \'https://postman-echo.com/post\''); + }); + }); + + it('when username and password is present with domain as well', function () { + const request = new Request(_.set(sampleRequest, 'auth.ntlm', [ + {key: 'username', value: 'joh\'n'}, + {key: 'password', value: 'tennesse"e'}, + {key: 'domain', value: 'radio'} + ])); + + convert(request, {}, function (error, snippet) { + if (error) { + expect.fail(null, null, error); + } + expect(snippet).to.be.a('string'); + expect(snippet).to.equal('curl --ntlm --user \'radio\\joh\'\\\'\'n:tennesse"e\' --location' + + ' --request POST \'https://postman-echo.com/post\''); + }); + }); + }); + + it('should use --data-binary when request body type is binary', function () { + var request = new Request({ + 'method': 'POST', + 'header': [], + 'body': { + 'mode': 'file', + 'file': { + 'src': 'file-path/collection123.json' + } + }, + 'url': { + 'raw': 'https://postman-echo.com/get', + 'protocol': 'https', + 'host': [ + 'postman-echo', + 'com' + ], + 'path': [ + 'get' + ] + } + }); + + convert(request, { longFormat: true }, function (error, snippet) { + if (error) { + expect.fail(null, null, error); + } + expect(snippet).to.be.a('string'); + expect(snippet).to.include('--data-binary \'@file-path/collection123.json\''); + }); + }); + }); +}); diff --git a/codegens/postman-cli/test/unit/fixtures/testcollection/collection.json b/codegens/postman-cli/test/unit/fixtures/testcollection/collection.json new file mode 100644 index 000000000..363346323 --- /dev/null +++ b/codegens/postman-cli/test/unit/fixtures/testcollection/collection.json @@ -0,0 +1,1453 @@ +{ + "info": { + "name": "Code-Gen Test Cases", + "_postman_id": "41182fad-912e-6bc9-d6b9-dfb6f5bf5ffb", + "description": "This collection contains requests that will be used to test validity of plugin created to convert postman request into code snippet of particular language.", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "item": [ + { + "name": "Request Headers with disabled headers", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "var responseJSON;", + "try {", + " tests[\"Body contains headers\"] = responseBody.has(\"headers\");", + " responseJSON = JSON.parse(responseBody);", + " tests[\"Header contains host\"] = \"host\" in responseJSON.headers;", + " tests[\"Header contains test parameter sent as part of request header\"] = \"my-sample-header\" in responseJSON.headers;", + "}", + "catch (e) { }", + "", + "", + "", + "" + ] + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "my-sample-header", + "value": "Lorem ipsum dolor sit amet" + }, + { + "key": "not-disabled-header", + "value": "ENABLED" + }, + { + "key": "disabled header", + "value": "DISABLED", + "disabled": true + } + ], + "body": {}, + "url": { + "raw": "https://postman-echo.com/headers", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + "headers" + ] + }, + "description": "A `GET` request to this endpoint returns the list of all request headers as part of the response JSON.\nIn Postman, sending your own set of headers through the [Headers tab](https://www.getpostman.com/docs/requests#headers?source=echo-collection-app-onboarding) will reveal the headers as part of the response." + }, + "response": [] + }, + { + "name": "GET Request with disabled query", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "var responseJSON;", + "", + "try { ", + " responseJSON = JSON.parse(responseBody); ", + " tests['response is valid JSON'] = true;", + "}", + "catch (e) { ", + " responseJSON = {}; ", + " tests['response is valid JSON'] = false;", + "}", + "", + "tests['response json contains headers'] = _.has(responseJSON, 'headers');", + "tests['response json contains args'] = _.has(responseJSON, 'args');", + "tests['response json contains url'] = _.has(responseJSON, 'url');", + "", + "tests['args key contains argument passed as url parameter'] = ('test' in responseJSON.args);", + "tests['args passed via request url params has value \"123\"'] = (_.get(responseJSON, 'args.test') === \"123\");" + ] + } + } + ], + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "https://postman-echo.com/get?test=123&anotherone=232", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + "get" + ], + "query": [ + { + "key": "test", + "value": "123", + "equals": true + }, + { + "key": "anotherone", + "value": "232", + "equals": true + }, + { + "key": "anotheroneone", + "value": "sdfsdf", + "equals": true, + "disabled": true + } + ] + }, + "description": "The HTTP `GET` request method is meant to retrieve data from a server. The data\nis identified by a unique URI (Uniform Resource Identifier). \n\nA `GET` request can pass parameters to the server using \"Query String \nParameters\". For example, in the following request,\n\n> http://example.com/hi/there?hand=wave\n\nThe parameter \"hand\" has the value \"wave\".\n\nThis endpoint echoes the HTTP headers, request parameters and the complete\nURI requested." + }, + "response": [] + }, + { + "name": "POST Raw Text", + "event": [ + { + "listen": "test", + "script": { + "id": "753f8a33-adb6-402f-8d19-386c1981ecb6", + "type": "text/javascript", + "exec": [ + "var responseJSON;", + "", + "try { ", + " responseJSON = JSON.parse(responseBody); ", + " tests['response is valid JSON'] = true;", + "}", + "catch (e) { ", + " responseJSON = {}; ", + " tests['response is valid JSON'] = false;", + "}", + "", + "", + "tests['response has post data'] = _.has(responseJSON, 'data');", + "tests['response matches the data posted'] = (responseJSON.data && responseJSON.data.length === 256);", + "", + "tests[\"content-type equals text/plain\"] = responseJSON && responseJSON.headers && (responseJSON.headers[\"content-type\"] === 'text/plain');" + ] + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "text/plain" + } + ], + "body": { + "mode": "raw", + "raw": "\"'Duis posuere augue vel cursus pharetra. In luctus a ex nec pretium. Praesent neque quam, tincidunt nec leo eget, rutrum vehicula magna.\nMaecenas consequat elementum elit, \"id\" \"se\\\"mper\" sem tristique et. Integer pulvinar enim quis consectetur interdum volutpat.'\"" + }, + "url": { + "raw": "https://postman-echo.com/post", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + "post" + ] + }, + "description": "The HTTP `POST` request method is meant to transfer data to a server \n(and elicit a response). What data is returned depends on the implementation\nof the server.\n\nA `POST` request can pass parameters to the server using \"Query String \nParameters\", as well as the Request Body. For example, in the following request,\n\n> POST /hi/there?hand=wave\n>\n> \n\nThe parameter \"hand\" has the value \"wave\". The request body can be in multiple\nformats. These formats are defined by the MIME type of the request. The MIME \nType can be set using the ``Content-Type`` HTTP header. The most commonly used \nMIME types are:\n\n* `multipart/form-data`\n* `application/x-www-form-urlencoded`\n* `application/json`\n\nThis endpoint echoes the HTTP headers, request parameters, the contents of\nthe request body and the complete URI requested." + }, + "response": [] + }, + { + "name": "POST form data with file", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "var responseJSON;", + "", + "try { ", + " responseJSON = JSON.parse(responseBody); ", + " tests['response is valid JSON'] = true;", + "}", + "catch (e) { ", + " responseJSON = {}; ", + " tests['response is valid JSON'] = false;", + "}", + "", + "", + "tests['response has post data'] = _.has(responseJSON, 'data');", + "tests['response matches the data posted'] = (responseJSON.data && responseJSON.data.length === 256);", + "", + "tests[\"content-type equals text/plain\"] = responseJSON && responseJSON.headers && (responseJSON.headers[\"content-type\"] === 'text/plain');" + ] + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/x-www-form-urlencoded", + "disabled": true + }, + { + "key": "content-type", + "value": "application/json", + "disabled": true + } + ], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "fdjks", + "value": "dsf", + "type": "text" + }, + { + "key": "&^%", + "value": "helo", + "type": "text" + }, + { + "key": "12", + "value": "\"23\"", + "description": "", + "type": "text" + }, + { + "key": "'123'", + "value": "'\"23\\\"4\\\"\"'", + "description": "", + "type": "text" + }, + { + "key": "", + "value": "", + "description": "", + "type": "text", + "disabled": true + } + ] + }, + "url": { + "raw": "https://postman-echo.com/post", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + "post" + ] + }, + "description": "The HTTP `POST` request method is meant to transfer data to a server \n(and elicit a response). What data is returned depends on the implementation\nof the server.\n\nA `POST` request can pass parameters to the server using \"Query String \nParameters\", as well as the Request Body. For example, in the following request,\n\n> POST /hi/there?hand=wave\n>\n> \n\nThe parameter \"hand\" has the value \"wave\". The request body can be in multiple\nformats. These formats are defined by the MIME type of the request. The MIME \nType can be set using the ``Content-Type`` HTTP header. The most commonly used \nMIME types are:\n\n* `multipart/form-data`\n* `application/x-www-form-urlencoded`\n* `application/json`\n\nThis endpoint echoes the HTTP headers, request parameters, the contents of\nthe request body and the complete URI requested." + }, + "response": [] + }, + { + "name": "POST urlencoded data with disabled entries", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "var responseJSON;", + "", + "try { ", + " responseJSON = JSON.parse(responseBody); ", + " tests['response is valid JSON'] = true;", + "}", + "catch (e) { ", + " responseJSON = {}; ", + " tests['response is valid JSON'] = false;", + "}", + "", + "", + "tests['response has post data'] = _.has(responseJSON, 'data');", + "tests['response matches the data posted'] = (responseJSON.data && responseJSON.data.length === 256);", + "", + "tests[\"content-type equals text/plain\"] = responseJSON && responseJSON.headers && (responseJSON.headers[\"content-type\"] === 'text/plain');" + ] + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/x-www-form-urlencoded" + } + ], + "body": { + "mode": "urlencoded", + "urlencoded": [ + { + "key": "1", + "value": "a", + "description": "", + "type": "text" + }, + { + "key": "2", + "value": "b", + "description": "", + "type": "text" + }, + { + "key": "\"\"12\"\"", + "value": "\"23\"", + "description": "", + "type": "text" + }, + { + "key": "'1\"2\\\"\"3'", + "value": "'1\"23\"4'", + "description": "", + "type": "text" + } + ] + }, + "url": { + "raw": "https://postman-echo.com/post/?hardik=\"me\"", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + "post", + "" + ], + "query": [ + { + "key": "hardik", + "value": "\"me\"", + "equals": true + } + ] + }, + "description": "The HTTP `POST` request method is meant to transfer data to a server \n(and elicit a response). What data is returned depends on the implementation\nof the server.\n\nA `POST` request can pass parameters to the server using \"Query String \nParameters\", as well as the Request Body. For example, in the following request,\n\n> POST /hi/there?hand=wave\n>\n> \n\nThe parameter \"hand\" has the value \"wave\". The request body can be in multiple\nformats. These formats are defined by the MIME type of the request. The MIME \nType can be set using the ``Content-Type`` HTTP header. The most commonly used \nMIME types are:\n\n* `multipart/form-data`\n* `application/x-www-form-urlencoded`\n* `application/json`\n\nThis endpoint echoes the HTTP headers, request parameters, the contents of\nthe request body and the complete URI requested." + }, + "response": [] + }, + { + "name": "POST json with raw", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "var responseJSON;", + "", + "try { ", + " responseJSON = JSON.parse(responseBody); ", + " tests['response is valid JSON'] = true;", + "}", + "catch (e) { ", + " responseJSON = {}; ", + " tests['response is valid JSON'] = false;", + "}", + "", + "", + "tests['response has post data'] = _.has(responseJSON, 'data');", + "tests['response matches the data posted'] = (responseJSON.data && responseJSON.data.length === 256);", + "", + "tests[\"content-type equals text/plain\"] = responseJSON && responseJSON.headers && (responseJSON.headers[\"content-type\"] === 'text/plain');" + ] + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"json\": \"Test-Test\"\n}" + }, + "url": { + "raw": "https://postman-echo.com/post", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + "post" + ] + }, + "description": "The HTTP `POST` request method is meant to transfer data to a server \n(and elicit a response). What data is returned depends on the implementation\nof the server.\n\nA `POST` request can pass parameters to the server using \"Query String \nParameters\", as well as the Request Body. For example, in the following request,\n\n> POST /hi/there?hand=wave\n>\n> \n\nThe parameter \"hand\" has the value \"wave\". The request body can be in multiple\nformats. These formats are defined by the MIME type of the request. The MIME \nType can be set using the ``Content-Type`` HTTP header. The most commonly used \nMIME types are:\n\n* `multipart/form-data`\n* `application/x-www-form-urlencoded`\n* `application/json`\n\nThis endpoint echoes the HTTP headers, request parameters, the contents of\nthe request body and the complete URI requested." + }, + "response": [ + { + "id": "db02f994-5ac4-41e1-835a-f49a14acbb6e", + "name": "POST json with raw", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"json\": \"Test-Test\"\n}" + }, + "url": { + "raw": "https://postman-echo.com/post", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + "post" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Access-Control-Allow-Credentials", + "value": "", + "name": "Access-Control-Allow-Credentials", + "description": "Indicates whether or not the response to the request can be exposed when the credentials flag is true. When used as part of a response to a preflight request, this indicates whether or not the actual request can be made using credentials." + }, + { + "key": "Access-Control-Allow-Headers", + "value": "", + "name": "Access-Control-Allow-Headers", + "description": "Used in response to a preflight request to indicate which HTTP headers can be used when making the actual request." + }, + { + "key": "Access-Control-Allow-Methods", + "value": "", + "name": "Access-Control-Allow-Methods", + "description": "Specifies the method or methods allowed when accessing the resource. This is used in response to a preflight request." + }, + { + "key": "Access-Control-Allow-Origin", + "value": "", + "name": "Access-Control-Allow-Origin", + "description": "Specifies a URI that may access the resource. For requests without credentials, the server may specify '*' as a wildcard, thereby allowing any origin to access the resource." + }, + { + "key": "Access-Control-Expose-Headers", + "value": "", + "name": "Access-Control-Expose-Headers", + "description": "Lets a server whitelist headers that browsers are allowed to access." + }, + { + "key": "Connection", + "value": "keep-alive", + "name": "Connection", + "description": "Options that are desired for the connection" + }, + { + "key": "Content-Encoding", + "value": "gzip", + "name": "Content-Encoding", + "description": "The type of encoding used on the data." + }, + { + "key": "Content-Length", + "value": "385", + "name": "Content-Length", + "description": "The length of the response body in octets (8-bit bytes)" + }, + { + "key": "Content-Type", + "value": "application/json; charset=utf-8", + "name": "Content-Type", + "description": "The mime type of this content" + }, + { + "key": "Date", + "value": "Wed, 07 Feb 2018 10:06:15 GMT", + "name": "Date", + "description": "The date and time that the message was sent" + }, + { + "key": "ETag", + "value": "W/\"215-u7EU1nFtauIn0/aVifjuXA\"", + "name": "ETag", + "description": "An identifier for a specific version of a resource, often a message digest" + }, + { + "key": "Server", + "value": "nginx", + "name": "Server", + "description": "A name for the server" + }, + { + "key": "Vary", + "value": "X-HTTP-Method-Override, Accept-Encoding", + "name": "Vary", + "description": "Tells downstream proxies how to match future request headers to decide whether the cached response can be used rather than requesting a fresh one from the origin server." + }, + { + "key": "set-cookie", + "value": "sails.sid=s%3AxRBxgrc9M-jKK_l1mX3y3rM_ry8wYLz4.Of4qpOzd9hi6uO0sAQIj%2Bxs2VeppWxYjJa4OpIW3PKg; Path=/; HttpOnly", + "name": "set-cookie", + "description": "an HTTP cookie" + } + ], + "cookie": [ + { + "expires": "Tue Jan 19 2038 08:44:07 GMT+0530 (IST)", + "httpOnly": true, + "domain": "postman-echo.com", + "path": "/", + "secure": false, + "value": "s%3AxRBxgrc9M-jKK_l1mX3y3rM_ry8wYLz4.Of4qpOzd9hi6uO0sAQIj%2Bxs2VeppWxYjJa4OpIW3PKg", + "key": "sails.sid" + } + ], + "body": "{\"args\":{},\"data\":\"{\\n \\\"json\\\": \\\"Test-Test\\\"\\n}\",\"files\":{},\"form\":{},\"headers\":{\"host\":\"postman-echo.com\",\"content-length\":\"25\",\"accept\":\"*/*\",\"accept-encoding\":\"gzip, deflate\",\"cache-control\":\"no-cache\",\"content-type\":\"text/plain\",\"cookie\":\"sails.sid=s%3AkOgtF1XmXtVFx-Eg3S7-37BKKaMqMDPe.hnwldNwyvsaASUiRR0Y0vcowadkMXO4HMegTeVIPgqo\",\"postman-token\":\"2ced782f-a141-428e-8af6-04ce954a77d5\",\"user-agent\":\"PostmanRuntime/7.1.1\",\"x-forwarded-port\":\"443\",\"x-forwarded-proto\":\"https\"},\"json\":null,\"url\":\"https://postman-echo.com/post\"}" + } + ] + }, + { + "name": "POST javascript with raw", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "var responseJSON;", + "", + "try { ", + " responseJSON = JSON.parse(responseBody); ", + " tests['response is valid JSON'] = true;", + "}", + "catch (e) { ", + " responseJSON = {}; ", + " tests['response is valid JSON'] = false;", + "}", + "", + "", + "tests['response has post data'] = _.has(responseJSON, 'data');", + "tests['response matches the data posted'] = (responseJSON.data && responseJSON.data.length === 256);", + "", + "tests[\"content-type equals text/plain\"] = responseJSON && responseJSON.headers && (responseJSON.headers[\"content-type\"] === 'text/plain');" + ] + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/javascript" + } + ], + "body": { + "mode": "raw", + "raw": "var val = 6;\nconsole.log(val);" + }, + "url": { + "raw": "https://postman-echo.com/post", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + "post" + ] + }, + "description": "The HTTP `POST` request method is meant to transfer data to a server \n(and elicit a response). What data is returned depends on the implementation\nof the server.\n\nA `POST` request can pass parameters to the server using \"Query String \nParameters\", as well as the Request Body. For example, in the following request,\n\n> POST /hi/there?hand=wave\n>\n> \n\nThe parameter \"hand\" has the value \"wave\". The request body can be in multiple\nformats. These formats are defined by the MIME type of the request. The MIME \nType can be set using the ``Content-Type`` HTTP header. The most commonly used \nMIME types are:\n\n* `multipart/form-data`\n* `application/x-www-form-urlencoded`\n* `application/json`\n\nThis endpoint echoes the HTTP headers, request parameters, the contents of\nthe request body and the complete URI requested." + }, + "response": [] + }, + { + "name": "POST text/xml with raw", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "var responseJSON;", + "", + "try { ", + " responseJSON = JSON.parse(responseBody); ", + " tests['response is valid JSON'] = true;", + "}", + "catch (e) { ", + " responseJSON = {}; ", + " tests['response is valid JSON'] = false;", + "}", + "", + "", + "tests['response has post data'] = _.has(responseJSON, 'data');", + "tests['response matches the data posted'] = (responseJSON.data && responseJSON.data.length === 256);", + "", + "tests[\"content-type equals text/plain\"] = responseJSON && responseJSON.headers && (responseJSON.headers[\"content-type\"] === 'text/plain');" + ] + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "text/xml" + } + ], + "body": { + "mode": "raw", + "raw": "\n Test Test\n" + }, + "url": { + "raw": "https://postman-echo.com/post", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + "post" + ] + }, + "description": "The HTTP `POST` request method is meant to transfer data to a server \n(and elicit a response). What data is returned depends on the implementation\nof the server.\n\nA `POST` request can pass parameters to the server using \"Query String \nParameters\", as well as the Request Body. For example, in the following request,\n\n> POST /hi/there?hand=wave\n>\n> \n\nThe parameter \"hand\" has the value \"wave\". The request body can be in multiple\nformats. These formats are defined by the MIME type of the request. The MIME \nType can be set using the ``Content-Type`` HTTP header. The most commonly used \nMIME types are:\n\n* `multipart/form-data`\n* `application/x-www-form-urlencoded`\n* `application/json`\n\nThis endpoint echoes the HTTP headers, request parameters, the contents of\nthe request body and the complete URI requested." + }, + "response": [] + }, + { + "name": "POST text/html with raw", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "var responseJSON;", + "", + "try { ", + " responseJSON = JSON.parse(responseBody); ", + " tests['response is valid JSON'] = true;", + "}", + "catch (e) { ", + " responseJSON = {}; ", + " tests['response is valid JSON'] = false;", + "}", + "", + "", + "tests['response has post data'] = _.has(responseJSON, 'data');", + "tests['response matches the data posted'] = (responseJSON.data && responseJSON.data.length === 256);", + "", + "tests[\"content-type equals text/plain\"] = responseJSON && responseJSON.headers && (responseJSON.headers[\"content-type\"] === 'text/plain');" + ] + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "text/html" + } + ], + "body": { + "mode": "raw", + "raw": "\n Test Test\n" + }, + "url": { + "raw": "https://postman-echo.com/post", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + "post" + ] + }, + "description": "The HTTP `POST` request method is meant to transfer data to a server \n(and elicit a response). What data is returned depends on the implementation\nof the server.\n\nA `POST` request can pass parameters to the server using \"Query String \nParameters\", as well as the Request Body. For example, in the following request,\n\n> POST /hi/there?hand=wave\n>\n> \n\nThe parameter \"hand\" has the value \"wave\". The request body can be in multiple\nformats. These formats are defined by the MIME type of the request. The MIME \nType can be set using the ``Content-Type`` HTTP header. The most commonly used \nMIME types are:\n\n* `multipart/form-data`\n* `application/x-www-form-urlencoded`\n* `application/json`\n\nThis endpoint echoes the HTTP headers, request parameters, the contents of\nthe request body and the complete URI requested." + }, + "response": [] + }, + { + "name": "Resolve URL", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/x-www-form-urlencoded" + } + ], + "body": { + "mode": "raw", + "raw": "Duis posuere augue vel cursus pharetra. In luctus a ex nec pretium. Praesent neque quam, tincidunt nec leo eget, rutrum vehicula magna.\nMaecenas consequat elementum elit, id semper sem tristique et. Integer pulvinar enim quis consectetur interdum volutpat." + }, + "url": { + "raw": "https://postman-echo.com/:action", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + ":action" + ], + "variable": [ + { + "key": "action", + "value": "post" + } + ] + }, + "description": null + }, + "response": [] + }, + { + "name": "PUT Request", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "var responseJSON;", + "", + "try { ", + " responseJSON = JSON.parse(responseBody); ", + " tests['response is valid JSON'] = true;", + "}", + "catch (e) { ", + " responseJSON = {}; ", + " tests['response is valid JSON'] = false;", + "}", + "", + "", + "tests['response has PUT data'] = _.has(responseJSON, 'data');", + "tests['response matches the data sent in request'] = (responseJSON.data && responseJSON.data.length === 256);" + ] + } + } + ], + "request": { + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "value": "text/plain" + } + ], + "body": { + "mode": "raw", + "raw": "Etiam mi lacus, cursus vitae felis et, blandit pellentesque neque. Vestibulum eget nisi a tortor commodo dignissim.\nQuisque ipsum ligula, faucibus a felis a, commodo elementum nisl. Mauris vulputate sapien et tincidunt viverra. Donec vitae velit nec metus." + }, + "url": { + "raw": "https://postman-echo.com/put", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + "put" + ] + }, + "description": "The HTTP `PUT` request method is similar to HTTP `POST`. It too is meant to \ntransfer data to a server (and elicit a response). What data is returned depends on the implementation\nof the server.\n\nA `PUT` request can pass parameters to the server using \"Query String \nParameters\", as well as the Request Body. For example, in the following \nraw HTTP request,\n\n> PUT /hi/there?hand=wave\n>\n> \n\n\n" + }, + "response": [] + }, + { + "name": "PATCH Request", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "var responseJSON;", + "", + "try { ", + " responseJSON = JSON.parse(responseBody); ", + " tests['response is valid JSON'] = true;", + "}", + "catch (e) { ", + " responseJSON = {}; ", + " tests['response is valid JSON'] = false;", + "}", + "", + "", + "tests['response has PUT data'] = _.has(responseJSON, 'data');", + "tests['response matches the data sent in request'] = (responseJSON.data && responseJSON.data.length === 256);" + ] + } + } + ], + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "text/plain" + } + ], + "body": { + "mode": "raw", + "raw": "Curabitur auctor, elit nec pulvinar porttitor, ex augue condimentum enim, eget suscipit urna felis quis neque.\nSuspendisse sit amet luctus massa, nec venenatis mi. Suspendisse tincidunt massa at nibh efficitur fringilla. Nam quis congue mi. Etiam volutpat." + }, + "url": { + "raw": "https://postman-echo.com/patch", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + "patch" + ] + }, + "description": "The HTTP `PATCH` method is used to update resources on a server. The exact\nuse of `PATCH` requests depends on the server in question. There are a number\nof server implementations which handle `PATCH` differently. Technically, \n`PATCH` supports both Query String parameters and a Request Body.\n\nThis endpoint accepts an HTTP `PATCH` request and provides debug information\nsuch as the HTTP headers, Query String arguments, and the Request Body." + }, + "response": [] + }, + { + "name": "DELETE Request", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "var responseJSON;", + "", + "try { ", + " responseJSON = JSON.parse(responseBody); ", + " tests['response is valid JSON'] = true;", + "}", + "catch (e) { ", + " responseJSON = {}; ", + " tests['response is valid JSON'] = false;", + "}", + "", + "", + "tests['response has PUT data'] = _.has(responseJSON, 'data');", + "tests['response matches the data sent in request'] = (responseJSON.data && responseJSON.data.length === 256);" + ] + } + } + ], + "request": { + "method": "DELETE", + "header": [ + { + "key": "Content-Type", + "value": "application/x-www-form-urlencoded" + }, + { + "key": "Content-Length", + "value": "1000", + "disabled": true + } + ], + "body": { + "mode": "urlencoded", + "urlencoded": [ + { + "key": "dsfs", + "value": "sfdds", + "description": "", + "type": "text" + }, + { + "key": "sfdsdf", + "value": "sdf", + "description": "", + "type": "text" + } + ] + }, + "url": { + "raw": "https://postman-echo.com/delete", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + "delete" + ] + }, + "description": "The HTTP `DELETE` method is used to delete resources on a server. The exact\nuse of `DELETE` requests depends on the server implementation. In general, \n`DELETE` requests support both, Query String parameters as well as a Request \nBody.\n\nThis endpoint accepts an HTTP `DELETE` request and provides debug information\nsuch as the HTTP headers, Query String arguments, and the Request Body." + }, + "response": [] + }, + { + "name": "OPTIONS to postman echo", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "var responseJSON;", + "", + "try { ", + " responseJSON = JSON.parse(responseBody); ", + " tests['response is valid JSON'] = true;", + "}", + "catch (e) { ", + " responseJSON = {}; ", + " tests['response is valid JSON'] = false;", + "}", + "", + "", + "tests['response has post data'] = _.has(responseJSON, 'data');", + "tests['response matches the data posted'] = (responseJSON.data && responseJSON.data.length === 256);", + "", + "tests[\"content-type equals text/plain\"] = responseJSON && responseJSON.headers && (responseJSON.headers[\"content-type\"] === 'text/plain');" + ] + } + } + ], + "request": { + "method": "OPTIONS", + "header": [ + { + "key": "Content-Type", + "value": "application/x-www-form-urlencoded" + } + ], + "body": {}, + "url": { + "raw": "https://postman-echo.com/post", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + "post" + ] + }, + "description": "The HTTP `POST` request method is meant to transfer data to a server \n(and elicit a response). What data is returned depends on the implementation\nof the server.\n\nA `POST` request can pass parameters to the server using \"Query String \nParameters\", as well as the Request Body. For example, in the following request,\n\n> POST /hi/there?hand=wave\n>\n> \n\nThe parameter \"hand\" has the value \"wave\". The request body can be in multiple\nformats. These formats are defined by the MIME type of the request. The MIME \nType can be set using the ``Content-Type`` HTTP header. The most commonly used \nMIME types are:\n\n* `multipart/form-data`\n* `application/x-www-form-urlencoded`\n* `application/json`\n\nThis endpoint echoes the HTTP headers, request parameters, the contents of\nthe request body and the complete URI requested." + }, + "response": [] + }, + { + "name": "HEAD request", + "request": { + "method": "HEAD", + "header": [ + { + "key": "hello", + "value": "helloagain", + "disabled": true + } + ], + "body": {}, + "url": { + "raw": "https://www.postman-echo.com/head", + "protocol": "https", + "host": [ + "www", + "postman-echo", + "com" + ], + "path": [ + "head" + ] + }, + "description": null + }, + "response": [] + }, + { + "name": "LINK request", + "request": { + "method": "LINK", + "header": [ + { + "key": "Content-Type", + "value": "text/plain" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "https://postman-echo.com/request", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + "request" + ] + }, + "description": "" + }, + "response": [] + }, + { + "name": "UNLINK request", + "request": { + "method": "UNLINK", + "header": [ + { + "key": "Content-Type", + "value": "text/plain" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "https://postman-echo.com/request", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + "request" + ] + }, + "description": "" + }, + "response": [] + }, + { + "name": "LOCK request", + "request": { + "method": "LOCK", + "header": [ + { + "key": "Content-Type", + "value": "text/plain" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "https://postman-echo.com/request", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + "request" + ] + }, + "description": "" + }, + "response": [] + }, + { + "name": "UNLOCK request", + "request": { + "method": "UNLOCK", + "header": [], + "body": {}, + "url": { + "raw": "https://postman-echo.com/request", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + "request" + ] + }, + "description": "" + }, + "response": [] + }, + { + "name": "PROPFIND request", + "request": { + "method": "PROPFIND", + "header": [ + { + "key": "Content-Type", + "value": "text/plain" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "https://postman-echo.com/request", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + "request" + ] + }, + "description": "" + }, + "response": [] + }, + { + "name": "VIEW request", + "request": { + "method": "VIEW", + "header": [ + { + "key": "Content-Type", + "value": "text/plain" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "https://postman-echo.com/request", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + "request" + ] + }, + "description": "" + }, + "response": [] + }, + { + "name": "PURGE Request", + "request": { + "method": "PURGE", + "header": [], + "body": {}, + "url": { + "raw": "https://postman-echo.com", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ] + }, + "description": null + }, + "response": [ + { + "id": "c0d81a4f-46ee-4ce4-a602-37b7d55b9983", + "name": "PURGE Request", + "originalRequest": { + "method": "PURGE", + "header": [], + "body": {}, + "url": { + "raw": "https://postman-echo.com", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ] + } + }, + "status": "Not Found", + "code": 404, + "_postman_previewlanguage": "text", + "header": [ + { + "key": "Access-Control-Allow-Credentials", + "value": "", + "name": "Access-Control-Allow-Credentials", + "description": "" + }, + { + "key": "Access-Control-Allow-Headers", + "value": "", + "name": "Access-Control-Allow-Headers", + "description": "" + }, + { + "key": "Access-Control-Allow-Methods", + "value": "", + "name": "Access-Control-Allow-Methods", + "description": "" + }, + { + "key": "Access-Control-Allow-Origin", + "value": "*", + "name": "Access-Control-Allow-Origin", + "description": "" + }, + { + "key": "Access-Control-Expose-Headers", + "value": "", + "name": "Access-Control-Expose-Headers", + "description": "" + }, + { + "key": "Connection", + "value": "keep-alive", + "name": "Connection", + "description": "" + }, + { + "key": "Content-Encoding", + "value": "gzip", + "name": "Content-Encoding", + "description": "" + }, + { + "key": "Content-Length", + "value": "152", + "name": "Content-Length", + "description": "" + }, + { + "key": "Content-Type", + "value": "application/json; charset=utf-8", + "name": "Content-Type", + "description": "" + }, + { + "key": "Date", + "value": "Tue, 13 Feb 2018 13:58:56 GMT", + "name": "Date", + "description": "" + }, + { + "key": "ETag", + "value": "W/\"a7-kIxN5L9H0YwilUQPUUio9A\"", + "name": "ETag", + "description": "" + }, + { + "key": "Server", + "value": "nginx", + "name": "Server", + "description": "" + }, + { + "key": "Vary", + "value": "Accept-Encoding", + "name": "Vary", + "description": "" + } + ], + "cookie": [], + "responseTime": "375", + "body": "{\n \"args\": {},\n \"data\": \"Curabitur auctor, elit nec pulvinar porttitor, ex augue condimentum enim, eget suscipit urna felis quis neque.\\nSuspendisse sit amet luctus massa, nec venenatis mi. Suspendisse tincidunt massa at nibh efficitur fringilla. Nam quis congue mi. Etiam volutpat.\",\n \"files\": {},\n \"form\": {},\n \"headers\": {\n \"host\": \"postman-echo.com\",\n \"content-length\": \"256\",\n \"accept\": \"*/*\",\n \"accept-encoding\": \"gzip, deflate\",\n \"content-type\": \"text/plain\",\n \"cookie\": \"sails.sid=s%3A1wOi4AdoZEbqBjGi6oSUC5Vlfje8wJvs.DHQfRLXfIBvZ%2Bv0KhLAThMDz%2FXvxh9gyxWYa0u1EZOU\",\n \"user-agent\": \"PostmanRuntime/7.1.1\",\n \"x-forwarded-port\": \"443\",\n \"x-forwarded-proto\": \"https\"\n },\n \"json\": null,\n \"url\": \"https://postman-echo.com\"\n}" + } + ] + }, + { + "name": "COPY Request", + "request": { + "method": "COPY", + "header": [], + "body": {}, + "url": { + "raw": "https://postman-echo.com/request", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + "request" + ] + }, + "description": null + }, + "response": [ + { + "id": "6367d22f-0cf7-48f2-a41d-5b9ed11cbff5", + "name": "COPY Request", + "originalRequest": { + "method": "COPY", + "header": [], + "body": {}, + "url": { + "raw": "https://postman-echo.com/request", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + "request" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Access-Control-Allow-Credentials", + "value": "true", + "name": "Access-Control-Allow-Credentials", + "description": "Indicates whether or not the response to the request can be exposed when the credentials flag is true. When used as part of a response to a preflight request, this indicates whether or not the actual request can be made using credentials." + }, + { + "key": "Access-Control-Allow-Headers", + "value": "host,connection,accept-encoding,x-forwarded-for,cf-ray,x-forwarded-proto,cf-visitor,cache-control,postman-token,user-agent,accept,cookie,cf-connecting-ip,x-request-id,x-forwarded-port,via,connect-time,x-request-start,total-route-time,content-length", + "name": "Access-Control-Allow-Headers", + "description": "Used in response to a preflight request to indicate which HTTP headers can be used when making the actual request." + }, + { + "key": "Access-Control-Allow-Methods", + "value": "COPY", + "name": "Access-Control-Allow-Methods", + "description": "Specifies the method or methods allowed when accessing the resource. This is used in response to a preflight request." + }, + { + "key": "Access-Control-Allow-Origin", + "value": "*", + "name": "Access-Control-Allow-Origin", + "description": "Specifies a URI that may access the resource. For requests without credentials, the server may specify '*' as a wildcard, thereby allowing any origin to access the resource." + }, + { + "key": "CF-RAY", + "value": "3fb595d5facaa302-HKG", + "name": "CF-RAY", + "description": "Custom header" + }, + { + "key": "Connection", + "value": "keep-alive", + "name": "Connection", + "description": "Options that are desired for the connection" + }, + { + "key": "Content-Encoding", + "value": "gzip", + "name": "Content-Encoding", + "description": "The type of encoding used on the data." + }, + { + "key": "Content-Type", + "value": "application/json; charset=utf-8", + "name": "Content-Type", + "description": "The mime type of this content" + }, + { + "key": "Date", + "value": "Wed, 14 Mar 2018 09:06:37 GMT", + "name": "Date", + "description": "The date and time that the message was sent" + }, + { + "key": "Etag", + "value": "W/\"4ac-YP9NIoQ5TiGJRPuQSZMKtA\"", + "name": "Etag", + "description": "An identifier for a specific version of a resource, often a message digest" + }, + { + "key": "Expect-CT", + "value": "max-age=604800, report-uri=\"https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct\"", + "name": "Expect-CT", + "description": "Custom header" + }, + { + "key": "Server", + "value": "cloudflare", + "name": "Server", + "description": "A name for the server" + }, + { + "key": "Transfer-Encoding", + "value": "chunked", + "name": "Transfer-Encoding", + "description": "The form of encoding used to safely transfer the entity to the user. Currently defined methods are: chunked, compress, deflate, gzip, identity." + }, + { + "key": "Vary", + "value": "Accept, Accept-Encoding", + "name": "Vary", + "description": "Tells downstream proxies how to match future request headers to decide whether the cached response can be used rather than requesting a fresh one from the origin server." + }, + { + "key": "Via", + "value": "1.1 vegur", + "name": "Via", + "description": "Informs the client of proxies through which the response was sent." + }, + { + "key": "X-Powered-By", + "value": "mockbin", + "name": "X-Powered-By", + "description": "Specifies the technology (ASP.NET, PHP, JBoss, e.g.) supporting the web application (version details are often in X-Runtime, X-Version, or X-AspNet-Version)" + } + ], + "cookie": [ + { + "expires": "Thu Mar 14 2019 13:12:10 GMT+0530 (IST)", + "httpOnly": true, + "domain": "postman-echo.com", + "path": "/", + "secure": false, + "value": "dfb94a3e1f3f8a9956138e4896847caf21521013330", + "key": "__cfduid" + } + ], + "body": "{\n \"startedDateTime\": \"2018-03-14T09:06:37.443Z\",\n \"clientIPAddress\": \"106.51.70.154\",\n \"method\": \"COPY\",\n \"url\": \"https://postman-echo.com/request\",\n \"httpVersion\": \"HTTP/1.1\",\n \"cookies\": {\n \"__cfduid\": \"dfb94a3e1f3f8a9956138e4896847caf21521013330\"\n },\n \"headers\": {\n \"host\": \"postman-echo.com\",\n \"connection\": \"close\",\n \"accept-encoding\": \"gzip\",\n \"x-forwarded-for\": \"106.51.70.154, 172.68.255.127\",\n \"cf-ray\": \"3fb595d5facaa302-HKG\",\n \"x-forwarded-proto\": \"http\",\n \"cf-visitor\": \"{\\\"scheme\\\":\\\"https\\\"}\",\n \"cache-control\": \"no-cache\",\n \"postman-token\": \"8d5b9832-75df-432f-90a3-284dacef0478\",\n \"user-agent\": \"PostmanRuntime/7.1.1\",\n \"accept\": \"*/*\",\n \"cookie\": \"__cfduid=dfb94a3e1f3f8a9956138e4896847caf21521013330\",\n \"cf-connecting-ip\": \"106.51.70.154\",\n \"x-request-id\": \"0e41473d-5130-4a6e-968d-b2a16cda3364\",\n \"x-forwarded-port\": \"80\",\n \"via\": \"1.1 vegur\",\n \"connect-time\": \"2\",\n \"x-request-start\": \"1521018397437\",\n \"total-route-time\": \"0\",\n \"content-length\": \"0\"\n },\n \"queryString\": {},\n \"postData\": {\n \"mimeType\": \"application/octet-stream\",\n \"text\": \"\",\n \"params\": []\n },\n \"headersSize\": 637,\n \"bodySize\": 0\n}" + } + ] + } + ] +} \ No newline at end of file diff --git a/codegens/postman-cli/test/unit/validation.test.js b/codegens/postman-cli/test/unit/validation.test.js new file mode 100644 index 000000000..23050f424 --- /dev/null +++ b/codegens/postman-cli/test/unit/validation.test.js @@ -0,0 +1,30 @@ +var expect = require('chai').expect, + path = require('path'), + + package = require(path.resolve('.', 'package.json')); + + +describe('package.json', function () { + it('should have com_postman_plugin object with valid properties', function () { + expect(package).to.have.property('com_postman_plugin'); + + expect(package.com_postman_plugin.type).to.equal('code_generator'); + expect(package.com_postman_plugin.lang).to.be.a('string'); + expect(package.com_postman_plugin.variant).to.be.a('string'); + expect(package.com_postman_plugin.syntax_mode).to.be.equal('powershell'); + }); + it('should have main property with relative path to object with convert property', function () { + var languageModule; + + expect(package.main).to.be.a('string'); + + try { + languageModule = require(path.resolve('.', package.main)); + } + catch (error) { + console.error(error); + } + expect(languageModule).to.be.a('object'); + expect(languageModule.convert).to.be.a('function'); + }); +}); From ee821c7c6f9a37714b6b685efdd56b2697b88079 Mon Sep 17 00:00:00 2001 From: Khuda Dad Nomani Date: Wed, 5 Nov 2025 10:00:03 +0000 Subject: [PATCH 02/13] Refactor Postman CLI code generator: rename package, update README, and enhance functionality. The package is now named "@postman/codegen-postman-cli" with version 0.1.0. Updated descriptions and options in README and code to reflect Postman CLI usage. Added new options for request handling and improved error messages. --- codegens/postman-cli/README.md | 114 ++++++++++++++++++++++++------ codegens/postman-cli/lib/index.js | 102 ++++++++++---------------- codegens/postman-cli/lib/util.js | 103 +++++++++++++-------------- codegens/postman-cli/package.json | 13 ++-- 4 files changed, 189 insertions(+), 143 deletions(-) diff --git a/codegens/postman-cli/README.md b/codegens/postman-cli/README.md index a9962c100..632ce8d65 100644 --- a/codegens/postman-cli/README.md +++ b/codegens/postman-cli/README.md @@ -1,12 +1,12 @@ -# codegen-curl +# codegen-postman-cli -> Converts Postman-SDK Request into code snippet for cURL. +> Converts Postman-SDK Request into code snippet for Postman CLI. #### Prerequisites -To run Code-Gen, ensure that you have NodeJS >= v8. A copy of the NodeJS installable can be downloaded from +To run Code-Gen, ensure that you have NodeJS >= v8. A copy of the NodeJS installable can be downloaded from https://nodejs.org/en/download/package-manager. ## Using the Module -The module will expose an object which will have property `convert` which is the function for converting the Postman-SDK request to cURL code snippet and `getOptions` function which returns an array of supported options. +The module will expose an object which will have property `convert` which is the function for converting the Postman-SDK request to Postman CLI code snippet and `getOptions` function which returns an array of supported options. ### convert function Convert function takes three parameters @@ -14,14 +14,18 @@ Convert function takes three parameters * `request` - Postman-SDK Request Object * `options` - options is an object which has following properties + * `multiLine` - Boolean denoting whether to split command across multiple lines + * `longFormat` - Boolean denoting whether to use long form options (--header instead of -H) + * `lineContinuationCharacter` - Character used to mark continuation of statement on next line (\\, ^, or `) + * `quoteType` - String denoting the quote type to use (single or double) for URL + * `requestTimeoutInSeconds` - Integer denoting time after which the request will timeout in seconds + * `followRedirect` - Boolean denoting whether to automatically follow HTTP redirects + * `followOriginalHttpMethod` - Boolean denoting whether to redirect with original HTTP method + * `maxRedirects` - Integer denoting maximum number of redirects to follow + * `trimRequestBody` - Boolean denoting whether to trim request body fields + * `quiet` - Boolean denoting whether to display requested data without extra output * `indentType` - String denoting type of indentation for code snippet. eg: 'Space', 'Tab' * `indentCount` - The number of indentation characters to add per code level - * `trimRequestBody` - Trim request body fields - * `followRedirect` - Boolean denoting whether to redirect a request - * `requestTimeoutInSeconds` - Integer denoting time after which the request will bail out in seconds - * `multiLine` - Boolean denoting whether to output code snippet with multi line breaks - * `longFormat` - Boolean denoting whether to use longform cURL options in snippet - * `quoteType` - String denoting the quote type to use (single or double) for URL * `callback` - callback function with first parameter as error and second parameter as string for code snippet @@ -31,12 +35,16 @@ var request = new sdk.Request('www.google.com'), //using postman sdk to create options = { indentCount: 3, indentType: 'Space', - requestTimeout: 200, + requestTimeoutInSeconds: 200, trimRequestBody: true, multiLine: true, followRedirect: true, + followOriginalHttpMethod: false, + maxRedirects: 0, longFormat: true, - quoteType: 'single' + lineContinuationCharacter: '\\', + quoteType: 'single', + quiet: false }; convert(request, options, function(error, snippet) { if (error) { @@ -56,18 +64,82 @@ var options = getOptions(); console.log(options); // output // [ -// { -// name: 'Set indentation count', -// id: 'indentCount', -// type: 'positiveInteger', -// default: 2, -// description: 'Set the number of indentation characters to add per code level' -// }, -// ... +// { +// name: 'Generate multiline snippet', +// id: 'multiLine', +// type: 'boolean', +// default: true, +// description: 'Split Postman CLI command across multiple lines' +// }, +// { +// name: 'Use long form options', +// id: 'longFormat', +// type: 'boolean', +// default: true, +// description: 'Use the long form for Postman CLI options (--header instead of -H)' +// }, +// { +// name: 'Line continuation character', +// id: 'lineContinuationCharacter', +// availableOptions: ['\\', '^', '`'], +// type: 'enum', +// default: '\\', +// description: 'Set a character used to mark the continuation of a statement on the next line' +// }, +// { +// name: 'Quote Type', +// id: 'quoteType', +// availableOptions: ['single', 'double'], +// type: 'enum', +// default: 'single', +// description: 'String denoting the quote type to use (single or double) for URL' +// }, +// { +// name: 'Set request timeout (in seconds)', +// id: 'requestTimeoutInSeconds', +// type: 'positiveInteger', +// default: 0, +// description: 'Set number of seconds the request should wait for a response before timing out (use 0 for infinity)' +// }, +// { +// name: 'Follow redirects', +// id: 'followRedirect', +// type: 'boolean', +// default: true, +// description: 'Automatically follow HTTP redirects' +// }, +// { +// name: 'Follow original HTTP method', +// id: 'followOriginalHttpMethod', +// type: 'boolean', +// default: false, +// description: 'Redirect with the original HTTP method instead of the default behavior of redirecting with GET' +// }, +// { +// name: 'Maximum number of redirects', +// id: 'maxRedirects', +// type: 'positiveInteger', +// default: 0, +// description: 'Set the maximum number of redirects to follow, defaults to 0 (unlimited)' +// }, +// { +// name: 'Trim request body fields', +// id: 'trimRequestBody', +// type: 'boolean', +// default: false, +// description: 'Remove white space and additional lines that may affect the server\'s response' +// }, +// { +// name: 'Use Quiet Mode', +// id: 'quiet', +// type: 'boolean', +// default: false, +// description: 'Display the requested data without showing any extra output.' +// } // ] ``` ### Guidelines for using generated snippet * Since Postman-SDK Request object doesn't provide complete path of the file, it needs to be manually inserted in case of uploading a file. -* This module doesn't support cookies. +* The generated snippet uses the `postman request` command from the Postman CLI. Make sure you have the Postman CLI installed to run the generated commands. diff --git a/codegens/postman-cli/lib/index.js b/codegens/postman-cli/lib/index.js index af57ca4d3..8b5106055 100644 --- a/codegens/postman-cli/lib/index.js +++ b/codegens/postman-cli/lib/index.js @@ -2,7 +2,6 @@ const { sanitize, sanitizeOptions, getUrlStringfromUrlObject, - getNtlmAuthInfo, addFormParam, form, shouldAddHttpMethod @@ -15,39 +14,35 @@ self = module.exports = { convert: function (request, options, callback) { if (!_.isFunction(callback)) { - throw new Error('Curl-Converter: callback is not valid function'); + throw new Error('Postman-CLI-Converter: callback is not valid function'); } options = sanitizeOptions(options, self.getOptions()); var indent, trim, headersData, body, redirect, timeout, multiLine, - format, snippet, silent, url, quoteType, ntlmAuth; + format, snippet, quiet, url, quoteType, maxRedirects, followOriginalHttpMethod; redirect = options.followRedirect; + maxRedirects = options.maxRedirects; timeout = options.requestTimeoutInSeconds; multiLine = options.multiLine; format = options.longFormat; trim = options.trimRequestBody; - silent = options.silent; + quiet = options.quiet; + followOriginalHttpMethod = options.followOriginalHttpMethod; quoteType = options.quoteType === 'single' ? '\'' : '"'; url = getUrlStringfromUrlObject(request.url, quoteType); - ntlmAuth = getNtlmAuthInfo(request.auth, quoteType, format); - snippet = 'curl'; + snippet = 'postman request'; - if (ntlmAuth) { - snippet += ntlmAuth; - } - if (silent) { - snippet += ` ${form('-s', format)}`; + if (shouldAddHttpMethod(request, options)) { + snippet += ` ${request.method}`; } - if (redirect) { - snippet += ` ${form('-L', format)}`; + + if (quiet) { + snippet += ` ${form('-q', format)}`; } if (timeout > 0) { - snippet += ` ${form('-m', format)} ${timeout}`; - } - if ((url.match(/[{[}\]]/g) || []).length > 0) { - snippet += ` ${form('-g', format)}`; + snippet += ` --timeout ${timeout}`; } if (multiLine) { indent = options.indentType === 'Tab' ? '\t' : ' '; @@ -57,12 +52,6 @@ self = module.exports = { indent = ' '; } - if (request.method === 'HEAD') { - snippet += ` ${form('-I', format)}`; - } - if (shouldAddHttpMethod(request, options)) { - snippet += ` ${form('-X', format)} ${request.method}`; - } snippet += ` ${quoteType + url + quoteType}`; if (request.body && !request.headers.has('Content-Type')) { @@ -87,14 +76,7 @@ self = module.exports = { return; } snippet += indent + `${form('-H', format)} ${quoteType}${sanitize(header.key, true, quoteType)}`; - // If the header value is an empty string then add a semicolon after key - // otherwise the header would be ignored by curl - if (header.value) { - snippet += `: ${sanitize(header.value, false, quoteType)}${quoteType}`; - } - else { - snippet += ';' + quoteType; - } + snippet += `: ${sanitize(header.value, false, quoteType)}${quoteType}`; }); } @@ -149,9 +131,6 @@ self = module.exports = { break; case 'raw': { let rawBody = body.raw.toString(), - isAsperandPresent = _.includes(rawBody, '@'), - // Use the long option if `@` is present in the request body otherwise follow user setting - optionName = isAsperandPresent ? '--data-raw' : form('-d', format), sanitizedBody = sanitize(rawBody, trim, quoteType); if (!multiLine) { @@ -163,44 +142,20 @@ self = module.exports = { } } - snippet += indent + `${optionName} ${quoteType}${sanitizedBody}${quoteType}`; + snippet += indent + `${form('-d', format)} ${quoteType}${sanitizedBody}${quoteType}`; break; } - case 'graphql': { - // eslint-disable-next-line no-case-declarations - let query = body.graphql ? body.graphql.query : '', - graphqlVariables, requestBody, isAsperandPresent, optionName; - try { - graphqlVariables = JSON.parse(body.graphql.variables); - } - catch (e) { - graphqlVariables = {}; - } - - requestBody = JSON.stringify({ - query: query, - variables: graphqlVariables - }); - - isAsperandPresent = _.includes(requestBody, '@'); - // Use the long option if `@` is present in the request body otherwise follow user setting - optionName = isAsperandPresent ? '--data-raw' : form('-d', format); - snippet += indent + `${optionName} ${quoteType}${sanitize(requestBody, trim, quoteType)}${quoteType}`; - break; - } case 'formdata': _.forEach(body.formdata, function (data) { if (!(data.disabled)) { if (data.type === 'file') { - snippet += indent + `${form('-F', format)}`; snippet += ` ${quoteType}${sanitize(data.key, trim, quoteType)}=` + `${sanitize(`@"${sanitize(data.src, trim, '"', true)}"`, trim, quoteType, quoteType === '"')}`; snippet += quoteType; } else { - snippet += indent + `${form('-F', format)}`; snippet += ` ${quoteType}${sanitize(data.key, trim, quoteType)}=` + sanitize(`"${sanitize(data.value, trim, '"', true)}"`, trim, quoteType, quoteType === '"'); if (data.contentType) { @@ -212,8 +167,7 @@ self = module.exports = { }); break; case 'file': - snippet += indent + (format ? '--data-binary' : '-d'); - snippet += ` ${quoteType}@${sanitize(body[body.mode].src, trim)}${quoteType}`; + snippet += indent + form('-d', format) + ` ${quoteType}@${sanitize(body[body.mode].src, trim)}${quoteType}`; break; default: snippet += `${form('-d', format)} ${quoteType}${quoteType}`; @@ -221,6 +175,18 @@ self = module.exports = { } } + if (!redirect) { + snippet += `${indent}--redirects-ignore`; + } + + if (followOriginalHttpMethod) { + snippet += `${indent}--redirect-follow-method`; + } + + if (maxRedirects > 0) { + snippet += `${indent}--max-redirects ${maxRedirects}`; + } + callback(null, snippet); }, getOptions: function () { @@ -279,6 +245,14 @@ self = module.exports = { default: false, description: 'Redirect with the original HTTP method instead of the default behavior of redirecting with GET' }, + + { + name: 'Maximum number of redirects', + id: 'maxRedirects', + type: 'positiveInteger', + default: 0, + description: 'Set the maximum number of redirects to follow, defaults to 0 (unlimited)' + }, { name: 'Trim request body fields', id: 'trimRequestBody', @@ -287,11 +261,11 @@ self = module.exports = { description: 'Remove white space and additional lines that may affect the server\'s response' }, { - name: 'Use Silent Mode', - id: 'silent', + name: 'Use Quiet Mode', + id: 'quiet', type: 'boolean', default: false, - description: 'Display the requested data without showing the cURL progress meter or error messages' + description: 'Display the requested data without showing any extra output.' } ]; } diff --git a/codegens/postman-cli/lib/util.js b/codegens/postman-cli/lib/util.js index a3a5d4020..fa777512f 100644 --- a/codegens/postman-cli/lib/util.js +++ b/codegens/postman-cli/lib/util.js @@ -48,26 +48,20 @@ var self = module.exports = { form: function (option, format) { if (format) { switch (option) { - case '-s': - return '--silent'; - case '-L': - return '--location'; - case '-m': - return '--max-time'; - case '-I': - return '--head'; - case '-X': - return '--request'; case '-H': return '--header'; case '-d': - return '--data'; - case '-F': + return '--body'; + case '-f': return '--form'; - case '-g': - return '--globoff'; + case '-e': + return '--environment'; + case '-o': + return '--output'; + case '-q': + return '--quiet'; default: - return ''; + return option; } } else { @@ -330,50 +324,55 @@ var self = module.exports = { }, /** - * Decide whether we should add the HTTP method explicitly to the cURL command. + * Decide whether we should add the HTTP method explicitly to the Postman CLI command. * * @param {Object} request - * @param {Object} options * * @returns {Boolean} */ - shouldAddHttpMethod: function (request, options) { - let followRedirect = options.followRedirect, - followOriginalHttpMethod = options.followOriginalHttpMethod, - disableBodyPruning = true, - isBodyEmpty = self.isBodyEmpty(request.body); - - // eslint-disable-next-line lodash/prefer-is-nil - if (request.protocolProfileBehavior !== null && request.protocolProfileBehavior !== undefined) { - followRedirect = _.get(request, 'protocolProfileBehavior.followRedirects', followRedirect); - followOriginalHttpMethod = - _.get(request, 'protocolProfileBehavior.followOriginalHttpMethod', followOriginalHttpMethod); - disableBodyPruning = _.get(request, 'protocolProfileBehavior.disableBodyPruning', true); - } + shouldAddHttpMethod: function (request) { - if (followRedirect && followOriginalHttpMethod) { - return true; + if (request.method === 'GET') { + return false; } - switch (request.method) { - case 'HEAD': - return false; - case 'GET': - // disableBodyPruning will generally not be present in the request - // the only time it will be present, its value will be _false_ - // i.e. the user wants to prune the request body despite it being present - if (!isBodyEmpty && disableBodyPruning) { - return true; - } - - return false; - case 'POST': - return isBodyEmpty; - case 'DELETE': - case 'PUT': - case 'PATCH': - default: - return true; - } + return true; + // let followRedirect = options.followRedirect, + // followOriginalHttpMethod = options.followOriginalHttpMethod, + // disableBodyPruning = true, + // isBodyEmpty = self.isBodyEmpty(request.body); + + // // eslint-disable-next-line lodash/prefer-is-nil + // if (request.protocolProfileBehavior !== null && request.protocolProfileBehavior !== undefined) { + // followRedirect = _.get(request, 'protocolProfileBehavior.followRedirects', followRedirect); + // followOriginalHttpMethod = + // _.get(request, 'protocolProfileBehavior.followOriginalHttpMethod', followOriginalHttpMethod); + // disableBodyPruning = _.get(request, 'protocolProfileBehavior.disableBodyPruning', true); + // } + + // if (followRedirect && followOriginalHttpMethod) { + // return true; + // } + + // switch (request.method) { + // case 'HEAD': + // return false; + // case 'GET': + // // disableBodyPruning will generally not be present in the request + // // the only time it will be present, its value will be _false_ + // // i.e. the user wants to prune the request body despite it being present + // if (!isBodyEmpty && disableBodyPruning) { + // return true; + // } + + // return false; + // case 'POST': + // return isBodyEmpty; + // case 'DELETE': + // case 'PUT': + // case 'PATCH': + // default: + // return true; + // } } }; diff --git a/codegens/postman-cli/package.json b/codegens/postman-cli/package.json index 7e0317129..78880c8f3 100644 --- a/codegens/postman-cli/package.json +++ b/codegens/postman-cli/package.json @@ -1,15 +1,16 @@ { - "name": "@postman/codegen-curl", - "version": "0.1.6", - "description": "Converter plugin to convert from postman sdk request to cURL code snippet", + "name": "@postman/codegen-postman-cli", + "version": "0.1.0", + "description": "Converter plugin to convert from postman sdk request to Postman CLI code snippet", "com_postman_plugin": { "type": "code_generator", - "lang": "curl", - "variant": "cURL", + "lang": "postman-cli", + "variant": "Postman CLI", "syntax_mode": "powershell" }, "main": "index.js", "scripts": { + "dev": "node dev-watch.js", "test": "node npm/test.js", "test-lint": "node npm/test-lint.js", "test-newman": "node npm/test-newman.js", @@ -21,7 +22,7 @@ }, "author": "Postman Labs ", "license": "Apache-2.0", - "homepage": "https://github.com/postmanlabs/code-generators/tree/master/codegens/curl", + "homepage": "https://github.com/postmanlabs/code-generators/tree/master/codegens/postman-cli", "dependencies": {}, "devDependencies": {}, "engines": { From 848670fb3a0eec05b016e0dd9d4525e943bfb38c Mon Sep 17 00:00:00 2001 From: Khuda Dad Nomani Date: Wed, 5 Nov 2025 10:34:06 +0000 Subject: [PATCH 03/13] relocate some options for better visibility --- codegens/postman-cli/lib/index.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/codegens/postman-cli/lib/index.js b/codegens/postman-cli/lib/index.js index 8b5106055..440665715 100644 --- a/codegens/postman-cli/lib/index.js +++ b/codegens/postman-cli/lib/index.js @@ -38,12 +38,6 @@ self = module.exports = { snippet += ` ${request.method}`; } - if (quiet) { - snippet += ` ${form('-q', format)}`; - } - if (timeout > 0) { - snippet += ` --timeout ${timeout}`; - } if (multiLine) { indent = options.indentType === 'Tab' ? '\t' : ' '; indent = ' ' + options.lineContinuationCharacter + '\n' + indent.repeat(options.indentCount); // eslint-disable-line max-len @@ -54,6 +48,13 @@ self = module.exports = { snippet += ` ${quoteType + url + quoteType}`; + if (quiet) { + snippet += `${indent}${form('-q', format)}`; + } + if (timeout > 0) { + snippet += `${indent}--timeout ${timeout}`; + } + if (request.body && !request.headers.has('Content-Type')) { if (request.body.mode === 'file') { request.addHeader({ From 78bc331d67b541967573eb58de63211a9c62cbb4 Mon Sep 17 00:00:00 2001 From: Khuda Dad Nomani Date: Wed, 5 Nov 2025 11:12:33 +0000 Subject: [PATCH 04/13] support some auth options including basic, oauth, hawk etc... --- codegens/postman-cli/README.md | 98 +++++++++++ codegens/postman-cli/lib/index.js | 8 +- codegens/postman-cli/lib/util.js | 280 ++++++++++++++++++++++++++++++ 3 files changed, 385 insertions(+), 1 deletion(-) diff --git a/codegens/postman-cli/README.md b/codegens/postman-cli/README.md index 632ce8d65..ce0c7302d 100644 --- a/codegens/postman-cli/README.md +++ b/codegens/postman-cli/README.md @@ -8,6 +8,19 @@ To run Code-Gen, ensure that you have NodeJS >= v8. A copy of the NodeJS install ## Using the Module The module will expose an object which will have property `convert` which is the function for converting the Postman-SDK request to Postman CLI code snippet and `getOptions` function which returns an array of supported options. +## Supported Features + +### Authentication +The codegen supports all authentication types available in Postman CLI: +- **Basic Auth**: Username and password authentication +- **Bearer Token**: Token-based authentication +- **Digest Auth**: Digest authentication with realm, nonce, qop, etc. +- **OAuth 1.0**: OAuth 1.0 authentication with consumer key, token, signatures +- **OAuth 2.0**: OAuth 2.0 with access tokens +- **API Key**: API key in header or query parameters +- **Hawk**: Hawk authentication +- **NTLM**: NTLM authentication with domain support + ### convert function Convert function takes three parameters @@ -53,6 +66,91 @@ convert(request, options, function(error, snippet) { // handle snippet }); ``` + +### Authentication Examples + +#### Basic Authentication +```js +var request = new sdk.Request({ + url: 'https://postman-echo.com/basic-auth', + method: 'GET', + auth: { + type: 'basic', + basic: [ + { key: 'username', value: 'postman' }, + { key: 'password', value: 'password' } + ] + } +}); + +convert(request, {}, function(error, snippet) { + console.log(snippet); + // Output: postman request 'https://postman-echo.com/basic-auth' --auth-basic-username 'postman' --auth-basic-password 'password' +}); +``` + +#### Bearer Token +```js +var request = new sdk.Request({ + url: 'https://api.example.com/data', + method: 'GET', + auth: { + type: 'bearer', + bearer: [ + { key: 'token', value: 'your-token-here' } + ] + } +}); + +convert(request, {}, function(error, snippet) { + console.log(snippet); + // Output: postman request 'https://api.example.com/data' --auth-bearer-token 'your-token-here' +}); +``` + +#### API Key +```js +var request = new sdk.Request({ + url: 'https://api.example.com/data', + method: 'GET', + auth: { + type: 'apikey', + apikey: [ + { key: 'key', value: 'X-API-Key' }, + { key: 'value', value: 'my-secret-key' }, + { key: 'in', value: 'header' } + ] + } +}); + +convert(request, {}, function(error, snippet) { + console.log(snippet); + // Output: postman request 'https://api.example.com/data' --auth-apikey-key 'X-API-Key' --auth-apikey-value 'my-secret-key' --auth-apikey-in 'header' +}); +``` + +#### AWS Signature +```js +var request = new sdk.Request({ + url: 'https://s3.amazonaws.com/bucket/file', + method: 'GET', + auth: { + type: 'awsv4', + awsv4: [ + { key: 'accessKey', value: 'AKIAIOSFODNN7EXAMPLE' }, + { key: 'secretKey', value: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY' }, + { key: 'region', value: 'us-east-1' }, + { key: 'service', value: 's3' } + ] + } +}); + +convert(request, {}, function(error, snippet) { + console.log(snippet); + // Output includes: --auth-aws-accessKey 'AKIAIOSFODNN7EXAMPLE' --auth-aws-secretKey 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY' ... +}); +``` + ### getOptions function This function returns a list of options supported by this codegen. diff --git a/codegens/postman-cli/lib/index.js b/codegens/postman-cli/lib/index.js index 440665715..dd94d2d6c 100644 --- a/codegens/postman-cli/lib/index.js +++ b/codegens/postman-cli/lib/index.js @@ -4,7 +4,8 @@ const { getUrlStringfromUrlObject, addFormParam, form, - shouldAddHttpMethod + shouldAddHttpMethod, + getAuthFlags } = require('./util'), _ = require('./lodash'); @@ -176,6 +177,11 @@ self = module.exports = { } } + // Add authentication flags if auth is present + if (request.auth) { + snippet += getAuthFlags(request.auth, quoteType, indent); + } + if (!redirect) { snippet += `${indent}--redirects-ignore`; } diff --git a/codegens/postman-cli/lib/util.js b/codegens/postman-cli/lib/util.js index fa777512f..1a9e7e0e7 100644 --- a/codegens/postman-cli/lib/util.js +++ b/codegens/postman-cli/lib/util.js @@ -323,6 +323,286 @@ var self = module.exports = { return false; }, + /** + * Generates authentication flags for Postman CLI based on request auth configuration + * + * @param {Object} auth - The request.auth object from Postman SDK + * @param {String} quoteType - User provided option to decide whether to use single or double quotes + * @param {String} indent - Indentation string + * @returns {String} - The authentication flags to be added to the CLI command + */ + /** + * Generates authentication flags for Postman CLI based on request auth configuration + * + * @param {Object} auth - The request.auth object from Postman SDK + * @param {String} quoteType - User provided option to decide whether to use single or double quotes + * @param {String} indent - Indentation string + * @returns {String} - The authentication flags to be added to the CLI command + */ + getAuthFlags: function (auth, quoteType, indent) { + if (!auth || !auth.type) { + return ''; + } + + var authType = auth.type, + authData = auth[authType], + authFlags = '', + getAuthParam = function (paramName) { + if (!authData || !authData.members) { + return ''; + } + var param = authData.members.find(function (item) { return item.key === paramName; }); + return param ? param.value : ''; + }, + username, password, realm, nonce, algorithm, qop, nc, cnonce, opaque, token, tokenSecret, + consumerKey, consumerSecret, signatureMethod, timestamp, version, addParamsToHeader, + addEmptyParamsToSign, accessToken, addTokenTo, accessKey, secretKey, region, service, + sessionToken, authId, authKey, user, extraData, app, delegation, domain, workstation, + key, value, inParam; + + switch (authType) { + case 'basic': + username = getAuthParam('username'); + password = getAuthParam('password'); + if (username || password) { + authFlags += indent + '--auth-basic-username ' + quoteType + + self.sanitize(username, true, quoteType) + quoteType; + authFlags += indent + '--auth-basic-password ' + quoteType + + self.sanitize(password, true, quoteType) + quoteType; + } + break; + + case 'bearer': + token = getAuthParam('token'); + if (token) { + authFlags += indent + '--auth-bearer-token ' + quoteType + + self.sanitize(token, true, quoteType) + quoteType; + } + break; + + case 'digest': + username = getAuthParam('username'); + password = getAuthParam('password'); + realm = getAuthParam('realm'); + nonce = getAuthParam('nonce'); + algorithm = getAuthParam('algorithm'); + qop = getAuthParam('qop'); + nc = getAuthParam('nc'); + cnonce = getAuthParam('cnonce'); + opaque = getAuthParam('opaque'); + + if (username) { + authFlags += indent + '--auth-digest-username ' + quoteType + + self.sanitize(username, true, quoteType) + quoteType; + } + if (password) { + authFlags += indent + '--auth-digest-password ' + quoteType + + self.sanitize(password, true, quoteType) + quoteType; + } + if (realm) { + authFlags += indent + '--auth-digest-realm ' + quoteType + + self.sanitize(realm, true, quoteType) + quoteType; + } + if (nonce) { + authFlags += indent + '--auth-digest-nonce ' + quoteType + + self.sanitize(nonce, true, quoteType) + quoteType; + } + if (algorithm) { + authFlags += indent + '--auth-digest-algorithm ' + quoteType + + self.sanitize(algorithm, true, quoteType) + quoteType; + } + if (qop) { + authFlags += indent + '--auth-digest-qop ' + quoteType + + self.sanitize(qop, true, quoteType) + quoteType; + } + if (nc) { + authFlags += indent + '--auth-digest-nc ' + quoteType + + self.sanitize(nc, true, quoteType) + quoteType; + } + if (cnonce) { + authFlags += indent + '--auth-digest-cnonce ' + quoteType + + self.sanitize(cnonce, true, quoteType) + quoteType; + } + if (opaque) { + authFlags += indent + '--auth-digest-opaque ' + quoteType + + self.sanitize(opaque, true, quoteType) + quoteType; + } + break; + + case 'oauth1': + consumerKey = getAuthParam('consumerKey'); + consumerSecret = getAuthParam('consumerSecret'); + token = getAuthParam('token'); + tokenSecret = getAuthParam('tokenSecret'); + signatureMethod = getAuthParam('signatureMethod'); + timestamp = getAuthParam('timestamp'); + nonce = getAuthParam('nonce'); + version = getAuthParam('version'); + realm = getAuthParam('realm'); + addParamsToHeader = getAuthParam('addParamsToHeader'); + addEmptyParamsToSign = getAuthParam('addEmptyParamsToSign'); + + if (consumerKey) { + authFlags += indent + '--auth-oauth1-consumerKey ' + quoteType + + self.sanitize(consumerKey, true, quoteType) + quoteType; + } + if (consumerSecret) { + authFlags += indent + '--auth-oauth1-consumerSecret ' + quoteType + + self.sanitize(consumerSecret, true, quoteType) + quoteType; + } + if (token) { + authFlags += indent + '--auth-oauth1-token ' + quoteType + + self.sanitize(token, true, quoteType) + quoteType; + } + if (tokenSecret) { + authFlags += indent + '--auth-oauth1-tokenSecret ' + quoteType + + self.sanitize(tokenSecret, true, quoteType) + quoteType; + } + if (signatureMethod) { + authFlags += indent + '--auth-oauth1-signatureMethod ' + quoteType + + self.sanitize(signatureMethod, true, quoteType) + quoteType; + } + if (timestamp) { + authFlags += indent + '--auth-oauth1-timestamp ' + quoteType + + self.sanitize(timestamp, true, quoteType) + quoteType; + } + if (nonce) { + authFlags += indent + '--auth-oauth1-nonce ' + quoteType + + self.sanitize(nonce, true, quoteType) + quoteType; + } + if (version) { + authFlags += indent + '--auth-oauth1-version ' + quoteType + + self.sanitize(version, true, quoteType) + quoteType; + } + if (realm) { + authFlags += indent + '--auth-oauth1-realm ' + quoteType + + self.sanitize(realm, true, quoteType) + quoteType; + } + if (addParamsToHeader) { + authFlags += indent + '--auth-oauth1-addParamsToHeader ' + quoteType + + self.sanitize(addParamsToHeader, true, quoteType) + quoteType; + } + if (addEmptyParamsToSign) { + authFlags += indent + '--auth-oauth1-addEmptyParamsToSign ' + quoteType + + self.sanitize(addEmptyParamsToSign, true, quoteType) + quoteType; + } + break; + + case 'oauth2': + accessToken = getAuthParam('accessToken'); + addTokenTo = getAuthParam('addTokenTo'); + + if (accessToken) { + authFlags += indent + '--auth-oauth2-accessToken ' + quoteType + + self.sanitize(accessToken, true, quoteType) + quoteType; + } + if (addTokenTo) { + authFlags += indent + '--auth-oauth2-addTokenTo ' + quoteType + + self.sanitize(addTokenTo, true, quoteType) + quoteType; + } + break; + + case 'hawk': + authId = getAuthParam('authId'); + authKey = getAuthParam('authKey'); + algorithm = getAuthParam('algorithm'); + user = getAuthParam('user'); + nonce = getAuthParam('nonce'); + extraData = getAuthParam('extraData'); + app = getAuthParam('app'); + delegation = getAuthParam('delegation'); + timestamp = getAuthParam('timestamp'); + + if (authId) { + authFlags += indent + '--auth-hawk-authId ' + quoteType + + self.sanitize(authId, true, quoteType) + quoteType; + } + if (authKey) { + authFlags += indent + '--auth-hawk-authKey ' + quoteType + + self.sanitize(authKey, true, quoteType) + quoteType; + } + if (algorithm) { + authFlags += indent + '--auth-hawk-algorithm ' + quoteType + + self.sanitize(algorithm, true, quoteType) + quoteType; + } + if (user) { + authFlags += indent + '--auth-hawk-user ' + quoteType + + self.sanitize(user, true, quoteType) + quoteType; + } + if (nonce) { + authFlags += indent + '--auth-hawk-nonce ' + quoteType + + self.sanitize(nonce, true, quoteType) + quoteType; + } + if (extraData) { + authFlags += indent + '--auth-hawk-extraData ' + quoteType + + self.sanitize(extraData, true, quoteType) + quoteType; + } + if (app) { + authFlags += indent + '--auth-hawk-app ' + quoteType + + self.sanitize(app, true, quoteType) + quoteType; + } + if (delegation) { + authFlags += indent + '--auth-hawk-delegation ' + quoteType + + self.sanitize(delegation, true, quoteType) + quoteType; + } + if (timestamp) { + authFlags += indent + '--auth-hawk-timestamp ' + quoteType + + self.sanitize(timestamp, true, quoteType) + quoteType; + } + break; + + case 'ntlm': + username = getAuthParam('username'); + password = getAuthParam('password'); + domain = getAuthParam('domain'); + workstation = getAuthParam('workstation'); + + if (username) { + authFlags += indent + '--auth-ntlm-username ' + quoteType + + self.sanitize(username, true, quoteType) + quoteType; + } + if (password) { + authFlags += indent + '--auth-ntlm-password ' + quoteType + + self.sanitize(password, true, quoteType) + quoteType; + } + if (domain) { + authFlags += indent + '--auth-ntlm-domain ' + quoteType + + self.sanitize(domain, true, quoteType) + quoteType; + } + if (workstation) { + authFlags += indent + '--auth-ntlm-workstation ' + quoteType + + self.sanitize(workstation, true, quoteType) + quoteType; + } + break; + + case 'apikey': + key = getAuthParam('key'); + value = getAuthParam('value'); + inParam = getAuthParam('in'); + + if (key) { + authFlags += indent + '--auth-apikey-key ' + quoteType + + self.sanitize(key, true, quoteType) + quoteType; + } + if (value) { + authFlags += indent + '--auth-apikey-value ' + quoteType + + self.sanitize(value, true, quoteType) + quoteType; + } + if (inParam) { + authFlags += indent + '--auth-apikey-in ' + quoteType + + self.sanitize(inParam, true, quoteType) + quoteType; + } + break; + + default: + // Unsupported auth type, return empty string + break; + } + + return authFlags; + }, + + /** * Decide whether we should add the HTTP method explicitly to the Postman CLI command. * From bd44618342866f02dc474e0bedddb5e26ff730d8 Mon Sep 17 00:00:00 2001 From: Khuda Dad Nomani Date: Wed, 5 Nov 2025 11:29:21 +0000 Subject: [PATCH 05/13] fix --form option and remove urlincoded support (needs to be added back later on) --- codegens/postman-cli/lib/index.js | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/codegens/postman-cli/lib/index.js b/codegens/postman-cli/lib/index.js index dd94d2d6c..b1a757029 100644 --- a/codegens/postman-cli/lib/index.js +++ b/codegens/postman-cli/lib/index.js @@ -122,15 +122,6 @@ self = module.exports = { if (!_.isEmpty(body)) { switch (body.mode) { - case 'urlencoded': - _.forEach(body.urlencoded, function (data) { - if (!data.disabled) { - snippet += indent + (format ? '--data-urlencode' : '-d'); - snippet += ` ${quoteType}${sanitize(data.key, trim, quoteType, false, true)}=` + - `${sanitize(data.value, trim, quoteType, false, !format)}${quoteType}`; - } - }); - break; case 'raw': { let rawBody = body.raw.toString(), sanitizedBody = sanitize(rawBody, trim, quoteType); @@ -158,11 +149,8 @@ self = module.exports = { snippet += quoteType; } else { - snippet += ` ${quoteType}${sanitize(data.key, trim, quoteType)}=` + - sanitize(`"${sanitize(data.value, trim, '"', true)}"`, trim, quoteType, quoteType === '"'); - if (data.contentType) { - snippet += `;type=${data.contentType}`; - } + snippet += `${indent} ${form('-f', format)} ${quoteType}${sanitize(data.key, trim, quoteType)}=` + + sanitize(`${sanitize(data.value, trim, '"', true)}`, trim, quoteType, quoteType === '"'); snippet += quoteType; } } @@ -172,7 +160,7 @@ self = module.exports = { snippet += indent + form('-d', format) + ` ${quoteType}@${sanitize(body[body.mode].src, trim)}${quoteType}`; break; default: - snippet += `${form('-d', format)} ${quoteType}${quoteType}`; + snippet += `${indent}${form('-d', format)} ${quoteType}${quoteType}`; } } } From f04c2bf711106426fc7577f923a0d9e5dd6cc352 Mon Sep 17 00:00:00 2001 From: Khuda Dad Nomani Date: Wed, 5 Nov 2025 12:23:10 +0000 Subject: [PATCH 06/13] migrate tests for postman-cli --- .../postman-cli/test/newman/newman.test.js | 31 -- .../postman-cli/test/unit/convert.test.js | 323 +++++------------- 2 files changed, 80 insertions(+), 274 deletions(-) delete mode 100644 codegens/postman-cli/test/newman/newman.test.js diff --git a/codegens/postman-cli/test/newman/newman.test.js b/codegens/postman-cli/test/newman/newman.test.js deleted file mode 100644 index 3cf1fb9f5..000000000 --- a/codegens/postman-cli/test/newman/newman.test.js +++ /dev/null @@ -1,31 +0,0 @@ -var runNewmanTest = require('../../../../test/codegen/newman/newmanTestUtil').runNewmanTest, - convert = require('../../index').convert; - -describe('Convert for different types of request', function () { - var testConfig = {compileScript: null, runScript: null, fileName: null}, - options1 = { - indentCount: 3, - indentType: 'Space', - requestTimeout: 200, - multiLine: true, - followRedirect: true, - longFormat: true, - silent: true, - lineContinuationCharacter: '\\', - quoteType: 'single' - }, - options2 = { - indentCount: 3, - indentType: 'Space', - requestTimeout: 200, - multiLine: true, - followRedirect: true, - longFormat: false, - silent: true, - lineContinuationCharacter: '\\', - quoteType: 'single' - }; - - runNewmanTest(convert, options1, testConfig); - runNewmanTest(convert, options2, testConfig); -}); diff --git a/codegens/postman-cli/test/unit/convert.test.js b/codegens/postman-cli/test/unit/convert.test.js index d3c4c5061..73b7b756b 100644 --- a/codegens/postman-cli/test/unit/convert.test.js +++ b/codegens/postman-cli/test/unit/convert.test.js @@ -5,7 +5,7 @@ var _ = require('lodash'), convert = require('../../index').convert, getUrlStringfromUrlObject = require('../../lib/util').getUrlStringfromUrlObject; -describe('curl convert function', function () { +describe('postman-cli convert function', function () { describe('Convert function', function () { var request, options, snippetArray, line; @@ -43,6 +43,17 @@ describe('curl convert function', function () { 'body': { 'mode': 'raw', 'raw': '' + }, + 'url': { + 'raw': 'https://postman-echo.com/post', + 'protocol': 'https', + 'host': [ + 'postman-echo', + 'com' + ], + 'path': [ + 'post' + ] } }); options = { @@ -53,8 +64,7 @@ describe('curl convert function', function () { expect.fail(null, null, error); } - snippetArray = snippet.split(' '); - expect(snippetArray[4][0]).to.equal('\''); + expect(snippet).to.contain('\'https://postman-echo.com/post\''); }); }); @@ -65,6 +75,17 @@ describe('curl convert function', function () { 'body': { 'mode': 'raw', 'raw': '' + }, + 'url': { + 'raw': 'https://postman-echo.com/post', + 'protocol': 'https', + 'host': [ + 'postman-echo', + 'com' + ], + 'path': [ + 'post' + ] } }); options = { @@ -75,12 +96,11 @@ describe('curl convert function', function () { expect.fail(null, null, error); } - snippetArray = snippet.split(' '); - expect(snippetArray[4][0]).to.equal('"'); + expect(snippet).to.contain('"https://postman-echo.com/post"'); }); }); - it('should add semicolon after header key, if the value is empty string', function () { + it('should add colon after header key, if the value is empty string', function () { request = new Request({ 'method': 'GET', 'header': [ @@ -106,7 +126,7 @@ describe('curl convert function', function () { expect.fail(null, null, error); } expect(snippet).to.be.a('string'); - expect(snippet).to.contain('--header \'hello;\''); + expect(snippet).to.contain('--header \'hello: \''); }); }); @@ -194,8 +214,8 @@ describe('curl convert function', function () { expect.fail(null, null, error); } expect(snippet).to.be.a('string'); - expect(snippet).to.contain('--form \'json="{\\"hello\\": \\"world\\"}";type=application/json\''); - + expect(snippet).to.contain('--form'); + expect(snippet).to.contain('json='); }); }); @@ -224,46 +244,6 @@ describe('curl convert function', function () { }); }); - it('should generate snippet with -g parameter when either of {,[,},] are present in url parameter', function () { - [ - '{world}', - '{{world', - '[world]', - ']world', - 'world}' - ].forEach(function (value) { - const request = new Request({ - 'method': 'GET', - 'url': { - 'raw': `http://example.com?hello=${value}`, - 'protocol': 'http', - 'host': [ - 'example', - 'com' - ], - 'query': [ - { - 'key': 'hello', - 'value': value - } - ] - } - }); - convert(request, {}, function (error, snippet) { - if (error) { - expect.fail(null, null, error); - } - expect(snippet).to.include('-g'); - }); - convert(request, { longFormat: true }, function (error, snippet) { - if (error) { - expect.fail(null, null, error); - } - expect(snippet).to.include('--globoff'); - }); - }); - }); - it('should return snippet without errors when request object has no body property', function () { request = new Request({ 'method': 'GET', @@ -499,9 +479,9 @@ describe('curl convert function', function () { expect.fail(null, null, error); } expect(snippet).to.be.a('string'); - expect(snippet).to.include('no file=@"/path/to/file"'); - expect(snippet).to.include('no src=@"/path/to/file"'); - expect(snippet).to.include('invalid src=@"/path/to/file"'); + expect(snippet).to.include('\'no file=@"/path/to/file"\''); + expect(snippet).to.include('\'no src=@"/path/to/file"\''); + expect(snippet).to.include('\'invalid src=@"/path/to/file"\''); }); }); @@ -512,7 +492,7 @@ describe('curl convert function', function () { if (error) { expect.fail(null, null, error); } - // for curl escaping of single quotes inside single quotes involves changing of ' to '\'' + // for single quote escaping inside single quotes involves changing of ' to '\'' // expect => 'https://a"b'\''c.com/'\''d/"e' expect(snippet).to.include("'https://a\"b'\\''c.com/'\\''d/\"e'"); // eslint-disable-line quotes }); @@ -547,19 +527,21 @@ describe('curl convert function', function () { } expect(snippet).to.include('"a\\"b\'c.com"'); - expect(snippet).to.include('"json=\\"{\\\\\\"hello\\\\\\": \\\\\\"world\\\\\\"}\\";type=application/json"'); + expect(snippet).to.include('"json='); }); }); - it('should not add appropriate escaping characters when quote type is "double"', function () { + it('should add appropriate escaping characters when quote type is "double"', function () { var request = new Request({ 'method': 'POST', 'header': [], 'body': { - 'mode': 'graphql', - 'graphql': { - 'query': '{\n findScenes(\n filter: {per_page: 0}\n scene_filter: {is_missing: "performers"}){\n count\n scenes {\n id\n title\n path\n }\n }\n}', // eslint-disable-line - 'variables': '{\n\t"variable_key": "variable_value"\n}' + 'mode': 'raw', + 'raw': '{"query":"test"}', + 'options': { + 'raw': { + 'language': 'json' + } } }, 'url': { @@ -574,12 +556,13 @@ describe('curl convert function', function () { ] } }); - convert(request, { quoteType: 'double', lineContinuationCharacter: '^' }, function (error, snippet) { + convert(request, { quoteType: 'double', longFormat: true }, function (error, snippet) { if (error) { expect.fail(null, null, error); } - expect(snippet).to.include('{\\"query\\":\\"{\\n findScenes(\\n filter: {per_page: 0}\\n scene_filter: {is_missing: \\\\\\"performers\\\\\\"})'); // eslint-disable-line + expect(snippet).to.include('--body'); + expect(snippet).to.include('query'); }); }); @@ -608,42 +591,14 @@ describe('curl convert function', function () { ] } }); - convert(request, { quoteType: 'double', lineContinuationCharacter: '^' }, function (error, snippet) { - if (error) { - expect.fail(null, null, error); - } - - expect(snippet.includes('\\"hello\\": \\"\\$(whoami)\\"')).to.be.true; // eslint-disable-line - }); - }); - - it('should longer option for body even if longFormat is disabled if @ character is present', function () { - let request = new Request({ - 'method': 'POST', - 'header': [], - 'body': { - 'mode': 'raw', - 'raw': '@hello' - }, - 'url': { - 'raw': 'https://postman-echo.com/post', - 'protocol': 'https', - 'host': [ - 'postman-echo', - 'com' - ], - 'path': [ - 'post' - ] - } - }); - - convert(request, { longFormat: false }, function (error, snippet) { + // eslint-disable-next-line max-len + convert(request, { quoteType: 'double', lineContinuationCharacter: '^', longFormat: true }, function (error, snippet) { if (error) { expect.fail(null, null, error); } - expect(snippet).include('--data-raw'); + expect(snippet).to.include('--body'); + expect(snippet).to.include('hello'); }); }); @@ -759,7 +714,7 @@ describe('curl convert function', function () { }); }); - it('should not add --request parameter in POST request if body is present', function () { + it('should always add HTTP method in POST request', function () { var request = new Request({ 'method': 'POST', 'header': [], @@ -788,11 +743,11 @@ describe('curl convert function', function () { expect.fail(null, null, error); } expect(snippet).to.be.a('string'); - expect(snippet).to.not.include('--request POST'); + expect(snippet).to.match(/^postman request POST /); }); }); - it('should add --request parameter in POST request if body is not present', function () { + it('should add HTTP method in POST request even if body is not present', function () { var request = new Request({ 'method': 'POST', 'header': [], @@ -814,55 +769,14 @@ describe('curl convert function', function () { expect.fail(null, null, error); } expect(snippet).to.be.a('string'); - expect(snippet).to.include('--request POST'); + expect(snippet).to.match(/^postman request POST /); }); }); - it('should add --request parameter in GET request if body is present', function () { + it('should not add HTTP method in GET request', function () { var request = new Request({ 'method': 'GET', 'header': [], - 'body': { - 'mode': 'graphql', - 'graphql': { - 'query': '{\n findScenes(\n filter: {per_page: 0}\n scene_filter: {is_missing: "performers"}){\n count\n scenes {\n id\n title\n path\n }\n }\n}', // eslint-disable-line - 'variables': '{\n\t"variable_key": "variable_value"\n}' - } - }, - 'url': { - 'raw': 'https://postman-echo.com/get', - 'protocol': 'https', - 'host': [ - 'postman-echo', - 'com' - ], - 'path': [ - 'get' - ] - } - }); - - convert(request, { followRedirect: true }, function (error, snippet) { - if (error) { - expect.fail(null, null, error); - } - expect(snippet).to.be.a('string'); - expect(snippet).to.include('--request GET'); - }); - }); - - it('should not add --request parameter in GET request if body is present ' + - 'but disableBodyPruning is false', function () { - const request = new Request({ - 'method': 'GET', - 'header': [], - 'body': { - 'mode': 'graphql', - 'graphql': { - 'query': '{\n findScenes(\n filter: {per_page: 0}\n scene_filter: {is_missing: "performers"}){\n count\n scenes {\n id\n title\n path\n }\n }\n}', // eslint-disable-line - 'variables': '{\n\t"variable_key": "variable_value"\n}' - } - }, 'url': { 'raw': 'https://postman-echo.com/get', 'protocol': 'https', @@ -876,22 +790,18 @@ describe('curl convert function', function () { } }); - // this needs to be done here because protocolProfileBehavior is not in collections SDK - request.protocolProfileBehavior = { - disableBodyPruning: false - }; - convert(request, { followRedirect: true }, function (error, snippet) { if (error) { expect.fail(null, null, error); } expect(snippet).to.be.a('string'); - expect(snippet).to.not.include('--request GET'); + expect(snippet).to.match(/^postman request '/); + expect(snippet).to.not.match(/^postman request GET /); }); }); describe('followRedirect and followOriginalHttpMethod', function () { - it('should add --request parameter when passed true via options', function () { + it('should add --redirect-follow-method flag when followOriginalHttpMethod is true', function () { const request = new Request({ 'method': 'POST', 'header': [], @@ -920,11 +830,12 @@ describe('curl convert function', function () { expect.fail(null, null, error); } expect(snippet).to.be.a('string'); - expect(snippet).to.include('--request POST'); + expect(snippet).to.match(/^postman request POST /); + expect(snippet).to.include('--redirect-follow-method'); }); }); - it('should not add --request parameter when passed false via options', function () { + it('should add --redirects-ignore flag when followRedirect is false', function () { const request = new Request({ 'method': 'POST', 'header': [], @@ -953,21 +864,15 @@ describe('curl convert function', function () { expect.fail(null, null, error); } expect(snippet).to.be.a('string'); - expect(snippet).to.not.include('--request POST'); + expect(snippet).to.include('--redirects-ignore'); + expect(snippet).to.not.include('--redirect-follow-method'); }); }); - it('should add --request parameter when passed false via options but true in request settings', function () { + it('should not add --redirects-ignore when followRedirect is true', function () { const request = new Request({ 'method': 'POST', 'header': [], - 'body': { - 'mode': 'graphql', - 'graphql': { - 'query': '{\n findScenes(\n filter: {per_page: 0}\n scene_filter: {is_missing: "performers"}){\n count\n scenes {\n id\n title\n path\n }\n }\n}', // eslint-disable-line - 'variables': '{\n\t"variable_key": "variable_value"\n}' - } - }, 'url': { 'raw': 'https://postman-echo.com/post', 'protocol': 'https', @@ -981,32 +886,19 @@ describe('curl convert function', function () { } }); - // this needs to be done here because protocolProfileBehavior is not in collections SDK - request.protocolProfileBehavior = { - followRedirects: true, - followOriginalHttpMethod: true - }; - - convert(request, { followRedirect: false, followOriginalHttpMethod: false }, function (error, snippet) { + convert(request, { followRedirect: true, followOriginalHttpMethod: false }, function (error, snippet) { if (error) { expect.fail(null, null, error); } expect(snippet).to.be.a('string'); - expect(snippet).to.include('--request POST'); + expect(snippet).to.not.include('--redirects-ignore'); }); }); - it('should not add --request parameter when passed true via options but false in request settings', function () { + it('should add --max-redirects flag when maxRedirects is set', function () { const request = new Request({ 'method': 'POST', 'header': [], - 'body': { - 'mode': 'graphql', - 'graphql': { - 'query': '{\n findScenes(\n filter: {per_page: 0}\n scene_filter: {is_missing: "performers"}){\n count\n scenes {\n id\n title\n path\n }\n }\n}', // eslint-disable-line - 'variables': '{\n\t"variable_key": "variable_value"\n}' - } - }, 'url': { 'raw': 'https://postman-echo.com/post', 'protocol': 'https', @@ -1020,54 +912,12 @@ describe('curl convert function', function () { } }); - // this needs to be done here because protocolProfileBehavior is not in collections SDK - request.protocolProfileBehavior = { - followRedirects: false, - followOriginalHttpMethod: false - }; - - convert(request, { followRedirect: true, followOriginalHttpMethod: true }, function (error, snippet) { + convert(request, { followRedirect: true, maxRedirects: 5 }, function (error, snippet) { if (error) { expect.fail(null, null, error); } expect(snippet).to.be.a('string'); - expect(snippet).to.not.include('--request POST'); - }); - }); - - it('should work when protocolProfileBehavior is null in request settings', function () { - const request = new Request({ - 'method': 'POST', - 'header': [], - 'body': { - 'mode': 'graphql', - 'graphql': { - 'query': '{\n findScenes(\n filter: {per_page: 0}\n scene_filter: {is_missing: "performers"}){\n count\n scenes {\n id\n title\n path\n }\n }\n}', // eslint-disable-line - 'variables': '{\n\t"variable_key": "variable_value"\n}' - } - }, - 'url': { - 'raw': 'https://postman-echo.com/post', - 'protocol': 'https', - 'host': [ - 'postman-echo', - 'com' - ], - 'path': [ - 'post' - ] - } - }); - - // this needs to be done here because protocolProfileBehavior is not in collections SDK - request.protocolProfileBehavior = null; - - convert(request, { followRedirect: true, followOriginalHttpMethod: true }, function (error, snippet) { - if (error) { - expect.fail(null, null, error); - } - expect(snippet).to.be.a('string'); - expect(snippet).to.include('--request POST'); + expect(snippet).to.include('--max-redirects 5'); }); }); }); @@ -1101,7 +951,8 @@ describe('curl convert function', function () { expect.fail(null, null, error); } expect(snippet).to.be.a('string'); - expect(snippet).to.not.include('--ntlm'); + expect(snippet).to.not.include('--auth-ntlm-username'); + expect(snippet).to.not.include('--auth-ntlm-password'); }); }); @@ -1119,7 +970,8 @@ describe('curl convert function', function () { expect.fail(null, null, error); } expect(snippet).to.be.a('string'); - expect(snippet).to.not.include('--ntlm'); + expect(snippet).to.not.include('--auth-ntlm-username'); + expect(snippet).to.not.include('--auth-ntlm-password'); }); }); @@ -1134,8 +986,8 @@ describe('curl convert function', function () { expect.fail(null, null, error); } expect(snippet).to.be.a('string'); - expect(snippet).to.equal('curl --ntlm --user \'joh\'\\\'\'n:tennesse"e\' --location' + - ' --request POST \'https://postman-echo.com/post\''); + expect(snippet).to.include('--auth-ntlm-username \'joh\'\\\'\'n\''); + expect(snippet).to.include('--auth-ntlm-password \'tennesse"e\''); }); }); @@ -1150,24 +1002,8 @@ describe('curl convert function', function () { expect.fail(null, null, error); } expect(snippet).to.be.a('string'); - expect(snippet).to.equal('curl --ntlm --user "joh\'n:tennesse\\"e" --location' + - ' --request POST "https://postman-echo.com/post"'); - }); - }); - - it('when correct username and password is present with long format option disabled', function () { - const request = new Request(_.set(sampleRequest, 'auth.ntlm', [ - {key: 'username', value: 'joh\'n'}, - {key: 'password', value: 'tennesse"e'} - ])); - - convert(request, { longFormat: false }, function (error, snippet) { - if (error) { - expect.fail(null, null, error); - } - expect(snippet).to.be.a('string'); - expect(snippet).to.equal('curl --ntlm -u \'joh\'\\\'\'n:tennesse"e\' -L' + - ' -X POST \'https://postman-echo.com/post\''); + expect(snippet).to.include('--auth-ntlm-username "joh\'n"'); + expect(snippet).to.include('--auth-ntlm-password "tennesse\\"e"'); }); }); @@ -1183,13 +1019,14 @@ describe('curl convert function', function () { expect.fail(null, null, error); } expect(snippet).to.be.a('string'); - expect(snippet).to.equal('curl --ntlm --user \'radio\\joh\'\\\'\'n:tennesse"e\' --location' + - ' --request POST \'https://postman-echo.com/post\''); + expect(snippet).to.include('--auth-ntlm-username \'joh\'\\\'\'n\''); + expect(snippet).to.include('--auth-ntlm-password \'tennesse"e\''); + expect(snippet).to.include('--auth-ntlm-domain \'radio\''); }); }); }); - it('should use --data-binary when request body type is binary', function () { + it('should use --body when request body type is file', function () { var request = new Request({ 'method': 'POST', 'header': [], @@ -1217,7 +1054,7 @@ describe('curl convert function', function () { expect.fail(null, null, error); } expect(snippet).to.be.a('string'); - expect(snippet).to.include('--data-binary \'@file-path/collection123.json\''); + expect(snippet).to.include('--body \'@file-path/collection123.json\''); }); }); }); From b472bd1182d53aaf570653faa9b928ede56890ef Mon Sep 17 00:00:00 2001 From: Khuda Dad Nomani Date: Wed, 5 Nov 2025 12:23:59 +0000 Subject: [PATCH 07/13] fix test pipeline --- codegens/postman-cli/lib/index.js | 13 ++++++++++--- codegens/postman-cli/lib/util.js | 13 ++----------- codegens/postman-cli/test/ci-install.sh | 10 +++++++++- 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/codegens/postman-cli/lib/index.js b/codegens/postman-cli/lib/index.js index b1a757029..4a739a358 100644 --- a/codegens/postman-cli/lib/index.js +++ b/codegens/postman-cli/lib/index.js @@ -186,19 +186,26 @@ self = module.exports = { }, getOptions: function () { return [ + { + name: 'Indentation count', + id: 'indentCount', + type: 'positiveInteger', + default: 2, + description: 'Set the number of indentation characters to add per code level' + }, { name: 'Generate multiline snippet', id: 'multiLine', type: 'boolean', default: true, - description: 'Split cURL command across multiple lines' + description: 'Split Postman CLI command across multiple lines' }, { name: 'Use long form options', id: 'longFormat', type: 'boolean', default: true, - description: 'Use the long form for cURL options (--header instead of -H)' + description: 'Use the long form for Postman CLI options (--header instead of -H)' }, { name: 'Line continuation character', @@ -216,7 +223,7 @@ self = module.exports = { type: 'enum', default: 'single', description: 'String denoting the quote type to use (single or double) for URL ' + - '(Use double quotes when running curl in cmd.exe and single quotes for the rest)' + '(Use double quotes when running Postman CLI in cmd.exe and single quotes for the rest)' }, { name: 'Set request timeout (in seconds)', diff --git a/codegens/postman-cli/lib/util.js b/codegens/postman-cli/lib/util.js index 1a9e7e0e7..07059ec26 100644 --- a/codegens/postman-cli/lib/util.js +++ b/codegens/postman-cli/lib/util.js @@ -323,14 +323,6 @@ var self = module.exports = { return false; }, - /** - * Generates authentication flags for Postman CLI based on request auth configuration - * - * @param {Object} auth - The request.auth object from Postman SDK - * @param {String} quoteType - User provided option to decide whether to use single or double quotes - * @param {String} indent - Indentation string - * @returns {String} - The authentication flags to be added to the CLI command - */ /** * Generates authentication flags for Postman CLI based on request auth configuration * @@ -356,9 +348,8 @@ var self = module.exports = { }, username, password, realm, nonce, algorithm, qop, nc, cnonce, opaque, token, tokenSecret, consumerKey, consumerSecret, signatureMethod, timestamp, version, addParamsToHeader, - addEmptyParamsToSign, accessToken, addTokenTo, accessKey, secretKey, region, service, - sessionToken, authId, authKey, user, extraData, app, delegation, domain, workstation, - key, value, inParam; + addEmptyParamsToSign, accessToken, addTokenTo, authId, authKey, user, extraData, app, + delegation, domain, workstation, key, value, inParam; switch (authType) { case 'basic': diff --git a/codegens/postman-cli/test/ci-install.sh b/codegens/postman-cli/test/ci-install.sh index 881fb5bac..7cdd5e022 100755 --- a/codegens/postman-cli/test/ci-install.sh +++ b/codegens/postman-cli/test/ci-install.sh @@ -1,5 +1,13 @@ #!/bin/bash set -ev; # stop on error -echo "Installing curl" +echo "Installing dependencies required for tests in codegens/postman-cli" + +# Install curl if not already available +echo "Installing curl..." +sudo apt-get update sudo apt-get install -y curl + +# Install Postman CLI +echo "Installing Postman CLI..." +curl -o- "https://dl-cli.pstmn.io/install/linux64.sh" | sh From 3a98b2001c0b7248f8914e6e11adb34d760a9636 Mon Sep 17 00:00:00 2001 From: Khuda Dad Nomani Date: Wed, 5 Nov 2025 12:24:09 +0000 Subject: [PATCH 08/13] remove newman test --- codegens/postman-cli/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/codegens/postman-cli/package.json b/codegens/postman-cli/package.json index 78880c8f3..91aeacee5 100644 --- a/codegens/postman-cli/package.json +++ b/codegens/postman-cli/package.json @@ -13,7 +13,6 @@ "dev": "node dev-watch.js", "test": "node npm/test.js", "test-lint": "node npm/test-lint.js", - "test-newman": "node npm/test-newman.js", "test-unit": "node npm/test-unit.js" }, "repository": { From 523684fedd1ec79600ece7d6420bb94a87356400 Mon Sep 17 00:00:00 2001 From: Khuda Dad Nomani Date: Wed, 5 Nov 2025 12:30:27 +0000 Subject: [PATCH 09/13] fix some missed options in different files --- codegens/postman-cli/README.md | 22 ------------- codegens/postman-cli/lib/util.js | 4 +-- codegens/postman-cli/npm-shrinkwrap.json | 4 +-- codegens/postman-cli/test/ci-install.sh | 2 +- .../postman-cli/test/newman/newman.test.js | 32 +++++++++++++++++++ 5 files changed, 37 insertions(+), 27 deletions(-) create mode 100644 codegens/postman-cli/test/newman/newman.test.js diff --git a/codegens/postman-cli/README.md b/codegens/postman-cli/README.md index ce0c7302d..fa7df7b53 100644 --- a/codegens/postman-cli/README.md +++ b/codegens/postman-cli/README.md @@ -129,28 +129,6 @@ convert(request, {}, function(error, snippet) { }); ``` -#### AWS Signature -```js -var request = new sdk.Request({ - url: 'https://s3.amazonaws.com/bucket/file', - method: 'GET', - auth: { - type: 'awsv4', - awsv4: [ - { key: 'accessKey', value: 'AKIAIOSFODNN7EXAMPLE' }, - { key: 'secretKey', value: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY' }, - { key: 'region', value: 'us-east-1' }, - { key: 'service', value: 's3' } - ] - } -}); - -convert(request, {}, function(error, snippet) { - console.log(snippet); - // Output includes: --auth-aws-accessKey 'AKIAIOSFODNN7EXAMPLE' --auth-aws-secretKey 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY' ... -}); -``` - ### getOptions function This function returns a list of options supported by this codegen. diff --git a/codegens/postman-cli/lib/util.js b/codegens/postman-cli/lib/util.js index 07059ec26..8604c3e1c 100644 --- a/codegens/postman-cli/lib/util.js +++ b/codegens/postman-cli/lib/util.js @@ -38,7 +38,7 @@ var self = module.exports = { .replace(/!/g, '\\!'); } else if (quoteType === '\'') { - // for curl escaping of single quotes inside single quotes involves changing of ' to '\'' + // for shell escaping of single quotes inside single quotes involves changing of ' to '\'' inputString = inputString.replace(/'/g, "'\\''"); // eslint-disable-line quotes } @@ -298,7 +298,7 @@ var self = module.exports = { * Determines if a request body is actually empty. * This is needed because body.isEmpty() returns false for formdata * and urlencoded when they contain only disabled params which will not - * be a part of the curl request. + * be a part of the CLI request. */ isBodyEmpty (body) { if (!body) { diff --git a/codegens/postman-cli/npm-shrinkwrap.json b/codegens/postman-cli/npm-shrinkwrap.json index 7dfc60f5f..d8e84ce5c 100644 --- a/codegens/postman-cli/npm-shrinkwrap.json +++ b/codegens/postman-cli/npm-shrinkwrap.json @@ -1,5 +1,5 @@ { - "name": "@postman/codegen-curl", - "version": "0.1.6", + "name": "@postman/codegen-postman-cli", + "version": "0.0.1", "lockfileVersion": 1 } diff --git a/codegens/postman-cli/test/ci-install.sh b/codegens/postman-cli/test/ci-install.sh index 7cdd5e022..0be0c8c74 100755 --- a/codegens/postman-cli/test/ci-install.sh +++ b/codegens/postman-cli/test/ci-install.sh @@ -3,7 +3,7 @@ set -ev; # stop on error echo "Installing dependencies required for tests in codegens/postman-cli" -# Install curl if not already available +# Install curl command-line tool (needed to download Postman CLI) echo "Installing curl..." sudo apt-get update sudo apt-get install -y curl diff --git a/codegens/postman-cli/test/newman/newman.test.js b/codegens/postman-cli/test/newman/newman.test.js new file mode 100644 index 000000000..f8f6304c4 --- /dev/null +++ b/codegens/postman-cli/test/newman/newman.test.js @@ -0,0 +1,32 @@ +var runNewmanTest = require('../../../../test/codegen/newman/newmanTestUtil').runNewmanTest, + convert = require('../../index').convert; + +describe('Convert for different types of request', function () { + var testConfig = {compileScript: null, runScript: null, fileName: null}, + options1 = { + indentCount: 3, + indentType: 'Space', + requestTimeoutInSeconds: 200, + multiLine: true, + followRedirect: true, + longFormat: true, + quiet: true, + lineContinuationCharacter: '\\', + quoteType: 'single' + }, + options2 = { + indentCount: 3, + indentType: 'Space', + requestTimeoutInSeconds: 200, + multiLine: true, + followRedirect: true, + longFormat: false, + quiet: true, + lineContinuationCharacter: '\\', + quoteType: 'single' + }; + + runNewmanTest(convert, options1, testConfig); + runNewmanTest(convert, options2, testConfig); +}); + From a8243c706d874e66186ac79840b124e8c23755c1 Mon Sep 17 00:00:00 2001 From: Khuda Dad Nomani Date: Thu, 6 Nov 2025 10:59:33 +0000 Subject: [PATCH 10/13] remove an un-used script and fix a few bugs --- codegens/postman-cli/lib/index.js | 6 +++--- codegens/postman-cli/package.json | 1 - lib/assets/languageLabels.json | 3 ++- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/codegens/postman-cli/lib/index.js b/codegens/postman-cli/lib/index.js index 4a739a358..180faf405 100644 --- a/codegens/postman-cli/lib/index.js +++ b/codegens/postman-cli/lib/index.js @@ -157,7 +157,7 @@ self = module.exports = { }); break; case 'file': - snippet += indent + form('-d', format) + ` ${quoteType}@${sanitize(body[body.mode].src, trim)}${quoteType}`; + snippet += indent + form('-d', format) + ` ${quoteType}@${sanitize(body[body.mode].src, trim, quoteType)}${quoteType}`; break; default: snippet += `${indent}${form('-d', format)} ${quoteType}${quoteType}`; @@ -175,11 +175,11 @@ self = module.exports = { } if (followOriginalHttpMethod) { - snippet += `${indent}--redirect-follow-method`; + snippet += `${indent}--redirects-follow-method`; } if (maxRedirects > 0) { - snippet += `${indent}--max-redirects ${maxRedirects}`; + snippet += `${indent}--redirects-max ${maxRedirects}`; } callback(null, snippet); diff --git a/codegens/postman-cli/package.json b/codegens/postman-cli/package.json index 91aeacee5..2b21eb4cf 100644 --- a/codegens/postman-cli/package.json +++ b/codegens/postman-cli/package.json @@ -10,7 +10,6 @@ }, "main": "index.js", "scripts": { - "dev": "node dev-watch.js", "test": "node npm/test.js", "test-lint": "node npm/test-lint.js", "test-unit": "node npm/test-unit.js" diff --git a/lib/assets/languageLabels.json b/lib/assets/languageLabels.json index 8078f4d65..1cbed6bb9 100644 --- a/lib/assets/languageLabels.json +++ b/lib/assets/languageLabels.json @@ -21,5 +21,6 @@ "shell": "Shell", "dart": "Dart", "r": "R", - "rust": "Rust" + "rust": "Rust", + "postman-cli": "Postman CLI" } From 11f6a6b82255a8686d95d2afb51e7dde90b06086 Mon Sep 17 00:00:00 2001 From: Khuda Dad Nomani Date: Thu, 6 Nov 2025 11:20:59 +0000 Subject: [PATCH 11/13] refactore index.js --- codegens/postman-cli/lib/index.js | 455 ++++++++++++++++++++---------- 1 file changed, 312 insertions(+), 143 deletions(-) diff --git a/codegens/postman-cli/lib/index.js b/codegens/postman-cli/lib/index.js index 180faf405..8df40a1b8 100644 --- a/codegens/postman-cli/lib/index.js +++ b/codegens/postman-cli/lib/index.js @@ -11,181 +11,350 @@ const { var self; -self = module.exports = { - convert: function (request, options, callback) { +/** + * Initialize options and extract values + * + * @param {Object} options - Sanitized options object + * @param {Object} request - The request object + * @returns {Object} Extracted option values + */ +function initializeOptions (options, request) { + const quoteType = options.quoteType === 'single' ? '\'' : '"', + url = getUrlStringfromUrlObject(request.url, quoteType); - if (!_.isFunction(callback)) { - throw new Error('Postman-CLI-Converter: callback is not valid function'); - } - options = sanitizeOptions(options, self.getOptions()); + let indent; + if (options.multiLine) { + indent = options.indentType === 'Tab' ? '\t' : ' '; + indent = ' ' + options.lineContinuationCharacter + '\n' + indent.repeat(options.indentCount); // eslint-disable-line max-len + } + else { + indent = ' '; + } - var indent, trim, headersData, body, redirect, timeout, multiLine, - format, snippet, quiet, url, quoteType, maxRedirects, followOriginalHttpMethod; - - redirect = options.followRedirect; - maxRedirects = options.maxRedirects; - timeout = options.requestTimeoutInSeconds; - multiLine = options.multiLine; - format = options.longFormat; - trim = options.trimRequestBody; - quiet = options.quiet; - followOriginalHttpMethod = options.followOriginalHttpMethod; - quoteType = options.quoteType === 'single' ? '\'' : '"'; - url = getUrlStringfromUrlObject(request.url, quoteType); + return { + redirect: options.followRedirect, + maxRedirects: options.maxRedirects, + timeout: options.requestTimeoutInSeconds, + multiLine: options.multiLine, + format: options.longFormat, + trim: options.trimRequestBody, + quiet: options.quiet, + followOriginalHttpMethod: options.followOriginalHttpMethod, + quoteType, + url, + indent + }; +} - snippet = 'postman request'; +/** + * Build the base command with method and URL + * + * @param {Object} request - The request object + * @param {Object} opts - Extracted options + * @param {Object} options - Original options object + * @returns {string} Base snippet + */ +function buildBaseCommand (request, opts, options) { + let snippet = 'postman request'; - if (shouldAddHttpMethod(request, options)) { - snippet += ` ${request.method}`; - } + if (shouldAddHttpMethod(request, options)) { + snippet += ` ${request.method}`; + } - if (multiLine) { - indent = options.indentType === 'Tab' ? '\t' : ' '; - indent = ' ' + options.lineContinuationCharacter + '\n' + indent.repeat(options.indentCount); // eslint-disable-line max-len - } - else { - indent = ' '; - } + snippet += ` ${opts.quoteType + opts.url + opts.quoteType}`; - snippet += ` ${quoteType + url + quoteType}`; + return snippet; +} - if (quiet) { - snippet += `${indent}${form('-q', format)}`; - } - if (timeout > 0) { - snippet += `${indent}--timeout ${timeout}`; - } +/** + * Add quiet and timeout flags to snippet + * + * @param {string} snippet - Current snippet + * @param {Object} opts - Extracted options + * @returns {string} Updated snippet + */ +function addQuietAndTimeout (snippet, opts) { + if (opts.quiet) { + snippet += `${opts.indent}${form('-q', opts.format)}`; + } + if (opts.timeout > 0) { + snippet += `${opts.indent}--timeout ${opts.timeout}`; + } + return snippet; +} - if (request.body && !request.headers.has('Content-Type')) { - if (request.body.mode === 'file') { - request.addHeader({ - key: 'Content-Type', - value: 'text/plain' - }); - } - else if (request.body.mode === 'graphql') { - request.addHeader({ - key: 'Content-Type', - value: 'application/json' - }); - } +/** + * Set default Content-Type header if needed + * + * @param {Object} request - The request object + */ +function setDefaultContentType (request) { + if (request.body && !request.headers.has('Content-Type')) { + if (request.body.mode === 'file') { + request.addHeader({ + key: 'Content-Type', + value: 'text/plain' + }); } - headersData = request.toJSON().header; - if (headersData) { - headersData = _.reject(headersData, 'disabled'); - _.forEach(headersData, (header) => { - if (!header.key) { - return; - } - snippet += indent + `${form('-H', format)} ${quoteType}${sanitize(header.key, true, quoteType)}`; - snippet += `: ${sanitize(header.value, false, quoteType)}${quoteType}`; + else if (request.body.mode === 'graphql') { + request.addHeader({ + key: 'Content-Type', + value: 'application/json' }); } + } +} + +/** + * Add headers to snippet + * + * @param {string} snippet - Current snippet + * @param {Object} request - The request object + * @param {Object} opts - Extracted options + * @returns {string} Updated snippet + */ +function addHeaders (snippet, request, opts) { + let headersData = request.toJSON().header; + if (headersData) { + headersData = _.reject(headersData, 'disabled'); + _.forEach(headersData, (header) => { + if (!header.key) { + return; + } + snippet += opts.indent + + `${form('-H', opts.format)} ${opts.quoteType}${sanitize(header.key, true, opts.quoteType)}`; + snippet += `: ${sanitize(header.value, false, opts.quoteType)}${opts.quoteType}`; + }); + } + return snippet; +} - // The following code handles multiple files in the same formdata param. - // It removes the form data params where the src property is an array of filepath strings - // Splits that array into different form data params with src set as a single filepath string - if (request.body && request.body.mode === 'formdata') { - let formdata = request.body.formdata, - formdataArray = []; - formdata.members.forEach((param) => { - let key = param.key, - type = param.type, - disabled = param.disabled, - contentType = param.contentType; - if (type === 'file') { - if (typeof param.src !== 'string') { - if (Array.isArray(param.src) && param.src.length) { - param.src.forEach((filePath) => { - addFormParam(formdataArray, key, param.type, filePath, disabled, contentType); - }); - } - else { - addFormParam(formdataArray, key, param.type, '/path/to/file', disabled, contentType); - } +/** + * Process formdata to handle multiple files + * + * @param {Object} request - The request object + */ +function processFormData (request) { + // The following code handles multiple files in the same formdata param. + // It removes the form data params where the src property is an array of filepath strings + // Splits that array into different form data params with src set as a single filepath string + if (request.body && request.body.mode === 'formdata') { + let formdata = request.body.formdata, + formdataArray = []; + formdata.members.forEach((param) => { + let key = param.key, + type = param.type, + disabled = param.disabled, + contentType = param.contentType; + if (type === 'file') { + if (typeof param.src !== 'string') { + if (Array.isArray(param.src) && param.src.length) { + param.src.forEach((filePath) => { + addFormParam(formdataArray, key, param.type, filePath, disabled, contentType); + }); } else { - addFormParam(formdataArray, key, param.type, param.src, disabled, contentType); + addFormParam(formdataArray, key, param.type, '/path/to/file', disabled, contentType); } } else { - addFormParam(formdataArray, key, param.type, param.value, disabled, contentType); - } - }); - request.body.update({ - mode: 'formdata', - formdata: formdataArray - }); - } - if (request.body) { - body = request.body.toJSON(); - - if (!_.isEmpty(body)) { - switch (body.mode) { - case 'raw': { - let rawBody = body.raw.toString(), - sanitizedBody = sanitize(rawBody, trim, quoteType); - - if (!multiLine) { - try { - sanitizedBody = JSON.stringify(JSON.parse(sanitizedBody)); - } - catch (e) { - // Do nothing - } - } - - snippet += indent + `${form('-d', format)} ${quoteType}${sanitizedBody}${quoteType}`; - - break; - } - - case 'formdata': - _.forEach(body.formdata, function (data) { - if (!(data.disabled)) { - if (data.type === 'file') { - snippet += ` ${quoteType}${sanitize(data.key, trim, quoteType)}=` + - `${sanitize(`@"${sanitize(data.src, trim, '"', true)}"`, trim, quoteType, quoteType === '"')}`; - snippet += quoteType; - } - else { - snippet += `${indent} ${form('-f', format)} ${quoteType}${sanitize(data.key, trim, quoteType)}=` + - sanitize(`${sanitize(data.value, trim, '"', true)}`, trim, quoteType, quoteType === '"'); - snippet += quoteType; - } - } - }); - break; - case 'file': - snippet += indent + form('-d', format) + ` ${quoteType}@${sanitize(body[body.mode].src, trim, quoteType)}${quoteType}`; - break; - default: - snippet += `${indent}${form('-d', format)} ${quoteType}${quoteType}`; + addFormParam(formdataArray, key, param.type, param.src, disabled, contentType); } } - } + else { + addFormParam(formdataArray, key, param.type, param.value, disabled, contentType); + } + }); + request.body.update({ + mode: 'formdata', + formdata: formdataArray + }); + } +} + +/** + * Add raw body to snippet + * + * @param {string} snippet - Current snippet + * @param {Object} body - Body data + * @param {Object} opts - Extracted options + * @returns {string} Updated snippet + */ +function addRawBody (snippet, body, opts) { + let rawBody = body.raw.toString(), + sanitizedBody = sanitize(rawBody, opts.trim, opts.quoteType); - // Add authentication flags if auth is present - if (request.auth) { - snippet += getAuthFlags(request.auth, quoteType, indent); + if (!opts.multiLine) { + try { + sanitizedBody = JSON.stringify(JSON.parse(sanitizedBody)); + } + catch (e) { + // Do nothing } + } + + snippet += opts.indent + `${form('-d', opts.format)} ${opts.quoteType}${sanitizedBody}${opts.quoteType}`; - if (!redirect) { - snippet += `${indent}--redirects-ignore`; + return snippet; +} + +/** + * Add formdata body to snippet + * + * @param {string} snippet - Current snippet + * @param {Object} body - Body data + * @param {Object} opts - Extracted options + * @returns {string} Updated snippet + */ +function addFormDataBody (snippet, body, opts) { + _.forEach(body.formdata, function (data) { + if (data.disabled) { + return; } - if (followOriginalHttpMethod) { - snippet += `${indent}--redirects-follow-method`; + if (data.type === 'file') { + const sanitizedSrc = sanitize(data.src, opts.trim, '"', true), + wrappedSrc = `@"${sanitizedSrc}"`, + finalSrc = sanitize(wrappedSrc, opts.trim, opts.quoteType, opts.quoteType === '"'); + snippet += ` ${opts.quoteType}${sanitize(data.key, opts.trim, opts.quoteType)}=${finalSrc}`; + snippet += opts.quoteType; + } + else { + const sanitizedValue = sanitize(data.value, opts.trim, '"', true), + finalValue = sanitize(sanitizedValue, opts.trim, opts.quoteType, opts.quoteType === '"'); + snippet += `${opts.indent} ${form('-f', opts.format)} ` + + `${opts.quoteType}${sanitize(data.key, opts.trim, opts.quoteType)}=${finalValue}`; + snippet += opts.quoteType; } + }); + return snippet; +} + +/** + * Add body data to snippet based on body mode + * + * @param {string} snippet - Current snippet + * @param {Object} request - The request object + * @param {Object} opts - Extracted options + * @returns {string} Updated snippet + */ +function addBodyData (snippet, request, opts) { + if (!request.body) { + return snippet; + } + + const body = request.body.toJSON(); + + if (_.isEmpty(body)) { + return snippet; + } + + switch (body.mode) { + case 'raw': + snippet = addRawBody(snippet, body, opts); + break; + + case 'formdata': + snippet = addFormDataBody(snippet, body, opts); + break; + + case 'file': + snippet += opts.indent + form('-d', opts.format) + + ` ${opts.quoteType}@${sanitize(body[body.mode].src, opts.trim, opts.quoteType)}${opts.quoteType}`; + break; - if (maxRedirects > 0) { - snippet += `${indent}--redirects-max ${maxRedirects}`; + default: + snippet += `${opts.indent}${form('-d', opts.format)} ${opts.quoteType}${opts.quoteType}`; + } + + return snippet; +} + +/** + * Add authentication flags to snippet + * + * @param {string} snippet - Current snippet + * @param {Object} request - The request object + * @param {Object} opts - Extracted options + * @returns {string} Updated snippet + */ +function addAuthentication (snippet, request, opts) { + if (request.auth) { + snippet += getAuthFlags(request.auth, opts.quoteType, opts.indent); + } + return snippet; +} + +/** + * Add redirect options to snippet + * + * @param {string} snippet - Current snippet + * @param {Object} opts - Extracted options + * @returns {string} Updated snippet + */ +function addRedirectOptions (snippet, opts) { + if (!opts.redirect) { + snippet += `${opts.indent}--redirects-ignore`; + } + + if (opts.followOriginalHttpMethod) { + snippet += `${opts.indent}--redirects-follow-method`; + } + + if (opts.maxRedirects > 0) { + snippet += `${opts.indent}--redirects-max ${opts.maxRedirects}`; + } + + return snippet; +} + +self = module.exports = { + convert: function (request, options, callback) { + + if (!_.isFunction(callback)) { + throw new Error('Postman-CLI-Converter: callback is not valid function'); } + options = sanitizeOptions(options, self.getOptions()); + + // Initialize options + const opts = initializeOptions(options, request); + + // Build base command + let snippet = buildBaseCommand(request, opts, options); + + // Add quiet and timeout flags + snippet = addQuietAndTimeout(snippet, opts); + + // Set default Content-Type if needed + setDefaultContentType(request); + + // Add headers + snippet = addHeaders(snippet, request, opts); + + // Process formdata + processFormData(request); + + // Add body data + snippet = addBodyData(snippet, request, opts); + + // Add authentication + snippet = addAuthentication(snippet, request, opts); + + // Add redirect options + snippet = addRedirectOptions(snippet, opts); callback(null, snippet); }, getOptions: function () { return [ + { + name: 'Set indentation type', + id: 'indentType', + type: 'enum', + availableOptions: ['Tab', 'Space'], + default: 'Space', + description: 'Select the character used to indent lines of code' + }, { name: 'Indentation count', id: 'indentCount', From 42abbced387bc93014caeb10dcd00f0b62368823 Mon Sep 17 00:00:00 2001 From: Khuda Dad Nomani Date: Thu, 6 Nov 2025 11:28:30 +0000 Subject: [PATCH 12/13] add the debug option and tests for it. --- codegens/postman-cli/lib/index.js | 13 +- .../postman-cli/test/unit/convert.test.js | 355 +++++++++++++++++- 2 files changed, 364 insertions(+), 4 deletions(-) diff --git a/codegens/postman-cli/lib/index.js b/codegens/postman-cli/lib/index.js index 8df40a1b8..dca65f38c 100644 --- a/codegens/postman-cli/lib/index.js +++ b/codegens/postman-cli/lib/index.js @@ -39,6 +39,7 @@ function initializeOptions (options, request) { format: options.longFormat, trim: options.trimRequestBody, quiet: options.quiet, + debug: options.debug, followOriginalHttpMethod: options.followOriginalHttpMethod, quoteType, url, @@ -67,7 +68,7 @@ function buildBaseCommand (request, opts, options) { } /** - * Add quiet and timeout flags to snippet + * Add quiet, debug and timeout flags to snippet * * @param {string} snippet - Current snippet * @param {Object} opts - Extracted options @@ -77,6 +78,9 @@ function addQuietAndTimeout (snippet, opts) { if (opts.quiet) { snippet += `${opts.indent}${form('-q', opts.format)}`; } + if (opts.debug) { + snippet += `${opts.indent}--debug`; + } if (opts.timeout > 0) { snippet += `${opts.indent}--timeout ${opts.timeout}`; } @@ -437,6 +441,13 @@ self = module.exports = { type: 'boolean', default: false, description: 'Display the requested data without showing any extra output.' + }, + { + name: 'Use Debug Mode', + id: 'debug', + type: 'boolean', + default: false, + description: 'Show detailed execution information including retry attempts, redirects, and timing breakdowns.' } ]; } diff --git a/codegens/postman-cli/test/unit/convert.test.js b/codegens/postman-cli/test/unit/convert.test.js index 73b7b756b..7a02584aa 100644 --- a/codegens/postman-cli/test/unit/convert.test.js +++ b/codegens/postman-cli/test/unit/convert.test.js @@ -831,7 +831,7 @@ describe('postman-cli convert function', function () { } expect(snippet).to.be.a('string'); expect(snippet).to.match(/^postman request POST /); - expect(snippet).to.include('--redirect-follow-method'); + expect(snippet).to.include('--redirects-follow-method'); }); }); @@ -865,7 +865,7 @@ describe('postman-cli convert function', function () { } expect(snippet).to.be.a('string'); expect(snippet).to.include('--redirects-ignore'); - expect(snippet).to.not.include('--redirect-follow-method'); + expect(snippet).to.not.include('--redirects-follow-method'); }); }); @@ -917,7 +917,7 @@ describe('postman-cli convert function', function () { expect.fail(null, null, error); } expect(snippet).to.be.a('string'); - expect(snippet).to.include('--max-redirects 5'); + expect(snippet).to.include('--redirects-max 5'); }); }); }); @@ -1057,5 +1057,354 @@ describe('postman-cli convert function', function () { expect(snippet).to.include('--body \'@file-path/collection123.json\''); }); }); + + describe('indentType option', function () { + it('should use spaces for indentation when indentType is "Space"', function () { + request = new Request({ + 'method': 'POST', + 'header': [ + { + 'key': 'Content-Type', + 'value': 'application/json' + } + ], + 'body': { + 'mode': 'raw', + 'raw': '{"test": "data"}' + }, + 'url': { + 'raw': 'https://postman-echo.com/post', + 'protocol': 'https', + 'host': [ + 'postman-echo', + 'com' + ], + 'path': [ + 'post' + ] + } + }); + options = { + multiLine: true, + indentType: 'Space', + indentCount: 2, + lineContinuationCharacter: '\\' + }; + convert(request, options, function (error, snippet) { + if (error) { + expect.fail(null, null, error); + } + expect(snippet).to.be.a('string'); + // Check that lines are indented with spaces (the pattern should be: space + backslash + newline + spaces) + snippetArray = snippet.split('\n'); + // Second line onwards should have space indentation + for (var i = 1; i < snippetArray.length; i++) { + line = snippetArray[i]; + if (line.length > 0) { + // Line should start with spaces (indentCount = 2) + expect(line).to.match(/^ {2}/); + } + } + }); + }); + + it('should use tabs for indentation when indentType is "Tab"', function () { + request = new Request({ + 'method': 'POST', + 'header': [ + { + 'key': 'Content-Type', + 'value': 'application/json' + } + ], + 'body': { + 'mode': 'raw', + 'raw': '{"test": "data"}' + }, + 'url': { + 'raw': 'https://postman-echo.com/post', + 'protocol': 'https', + 'host': [ + 'postman-echo', + 'com' + ], + 'path': [ + 'post' + ] + } + }); + options = { + multiLine: true, + indentType: 'Tab', + indentCount: 1, + lineContinuationCharacter: '\\' + }; + convert(request, options, function (error, snippet) { + if (error) { + expect.fail(null, null, error); + } + expect(snippet).to.be.a('string'); + // Check that lines are indented with tabs + snippetArray = snippet.split('\n'); + // Second line onwards should have tab indentation + for (var i = 1; i < snippetArray.length; i++) { + line = snippetArray[i]; + if (line.length > 0) { + // Line should start with tab character + expect(line).to.match(/^\t/); + } + } + }); + }); + + it('should respect indentCount when using Space indentation', function () { + request = new Request({ + 'method': 'POST', + 'header': [ + { + 'key': 'X-Custom-Header', + 'value': 'test' + } + ], + 'url': { + 'raw': 'https://postman-echo.com/post', + 'protocol': 'https', + 'host': [ + 'postman-echo', + 'com' + ], + 'path': [ + 'post' + ] + } + }); + options = { + multiLine: true, + indentType: 'Space', + indentCount: 4, + lineContinuationCharacter: '\\' + }; + convert(request, options, function (error, snippet) { + if (error) { + expect.fail(null, null, error); + } + expect(snippet).to.be.a('string'); + snippetArray = snippet.split('\n'); + // Second line onwards should have 4 spaces indentation + for (var i = 1; i < snippetArray.length; i++) { + line = snippetArray[i]; + if (line.length > 0) { + expect(line).to.match(/^ {4}/); + } + } + }); + }); + + it('should use Space indentation by default', function () { + request = new Request({ + 'method': 'POST', + 'header': [ + { + 'key': 'Content-Type', + 'value': 'application/json' + } + ], + 'url': { + 'raw': 'https://postman-echo.com/post', + 'protocol': 'https', + 'host': [ + 'postman-echo', + 'com' + ], + 'path': [ + 'post' + ] + } + }); + options = { + multiLine: true, + lineContinuationCharacter: '\\' + }; + convert(request, options, function (error, snippet) { + if (error) { + expect.fail(null, null, error); + } + expect(snippet).to.be.a('string'); + snippetArray = snippet.split('\n'); + // Should use spaces by default (2 spaces based on default indentCount) + for (var i = 1; i < snippetArray.length; i++) { + line = snippetArray[i]; + if (line.length > 0) { + expect(line).to.match(/^ {2}/); + expect(line).to.not.match(/^\t/); + } + } + }); + }); + }); + + describe('debug option', function () { + it('should add --debug flag when debug option is true', function () { + request = new Request({ + 'method': 'GET', + 'header': [], + 'url': { + 'raw': 'https://postman-echo.com/get', + 'protocol': 'https', + 'host': [ + 'postman-echo', + 'com' + ], + 'path': [ + 'get' + ] + } + }); + options = { + debug: true + }; + convert(request, options, function (error, snippet) { + if (error) { + expect.fail(null, null, error); + } + expect(snippet).to.be.a('string'); + expect(snippet).to.include('--debug'); + }); + }); + + it('should not add --debug flag when debug option is false', function () { + request = new Request({ + 'method': 'GET', + 'header': [], + 'url': { + 'raw': 'https://postman-echo.com/get', + 'protocol': 'https', + 'host': [ + 'postman-echo', + 'com' + ], + 'path': [ + 'get' + ] + } + }); + options = { + debug: false + }; + convert(request, options, function (error, snippet) { + if (error) { + expect.fail(null, null, error); + } + expect(snippet).to.be.a('string'); + expect(snippet).to.not.include('--debug'); + }); + }); + + it('should not add --debug flag by default', function () { + request = new Request({ + 'method': 'GET', + 'header': [], + 'url': { + 'raw': 'https://postman-echo.com/get', + 'protocol': 'https', + 'host': [ + 'postman-echo', + 'com' + ], + 'path': [ + 'get' + ] + } + }); + options = {}; + convert(request, options, function (error, snippet) { + if (error) { + expect.fail(null, null, error); + } + expect(snippet).to.be.a('string'); + expect(snippet).to.not.include('--debug'); + }); + }); + + it('should add --debug flag with multiline format', function () { + request = new Request({ + 'method': 'POST', + 'header': [ + { + 'key': 'Content-Type', + 'value': 'application/json' + } + ], + 'body': { + 'mode': 'raw', + 'raw': '{"test": "data"}' + }, + 'url': { + 'raw': 'https://postman-echo.com/post', + 'protocol': 'https', + 'host': [ + 'postman-echo', + 'com' + ], + 'path': [ + 'post' + ] + } + }); + options = { + debug: true, + multiLine: true, + longFormat: true + }; + convert(request, options, function (error, snippet) { + if (error) { + expect.fail(null, null, error); + } + expect(snippet).to.be.a('string'); + expect(snippet).to.include('--debug'); + // Verify it appears on its own line in multiline format + snippetArray = snippet.split('\n'); + var foundDebug = false; + for (var i = 0; i < snippetArray.length; i++) { + if (snippetArray[i].includes('--debug')) { + foundDebug = true; + break; + } + } + expect(foundDebug).to.be.true; + }); + }); + + it('should work with both quiet and debug flags together', function () { + request = new Request({ + 'method': 'GET', + 'header': [], + 'url': { + 'raw': 'https://postman-echo.com/get', + 'protocol': 'https', + 'host': [ + 'postman-echo', + 'com' + ], + 'path': [ + 'get' + ] + } + }); + options = { + quiet: true, + debug: true, + longFormat: true + }; + convert(request, options, function (error, snippet) { + if (error) { + expect.fail(null, null, error); + } + expect(snippet).to.be.a('string'); + expect(snippet).to.include('--quiet'); + expect(snippet).to.include('--debug'); + }); + }); + }); }); }); From fe5c049ae34501b56c60357ed5c72d6c085b563c Mon Sep 17 00:00:00 2001 From: Khuda Dad Nomani Date: Wed, 12 Nov 2025 12:14:40 +0000 Subject: [PATCH 13/13] fix failing tests --- codegens/postman-cli/lib/index.js | 8 ++++---- test/codegen/structure.test.js | 30 +++++++++++++++++++++++++++++- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/codegens/postman-cli/lib/index.js b/codegens/postman-cli/lib/index.js index dca65f38c..6f38121b6 100644 --- a/codegens/postman-cli/lib/index.js +++ b/codegens/postman-cli/lib/index.js @@ -360,7 +360,7 @@ self = module.exports = { description: 'Select the character used to indent lines of code' }, { - name: 'Indentation count', + name: 'Set indentation count', id: 'indentCount', type: 'positiveInteger', default: 2, @@ -371,14 +371,14 @@ self = module.exports = { id: 'multiLine', type: 'boolean', default: true, - description: 'Split Postman CLI command across multiple lines' + description: 'Split cURL command across multiple lines' }, { name: 'Use long form options', id: 'longFormat', type: 'boolean', default: true, - description: 'Use the long form for Postman CLI options (--header instead of -H)' + description: 'Use the long form for cURL options (--header instead of -H)' }, { name: 'Line continuation character', @@ -396,7 +396,7 @@ self = module.exports = { type: 'enum', default: 'single', description: 'String denoting the quote type to use (single or double) for URL ' + - '(Use double quotes when running Postman CLI in cmd.exe and single quotes for the rest)' + '(Use double quotes when running curl in cmd.exe and single quotes for the rest)' }, { name: 'Set request timeout (in seconds)', diff --git a/test/codegen/structure.test.js b/test/codegen/structure.test.js index 4553c829f..84af756c1 100644 --- a/test/codegen/structure.test.js +++ b/test/codegen/structure.test.js @@ -94,6 +94,31 @@ const expectedOptions = { default: 'single', description: 'String denoting the quote type to use (single or double) for URL ' + '(Use double quotes when running curl in cmd.exe and single quotes for the rest)' + }, + maxRedirects: { + name: 'Maximum number of redirects', + type: 'positiveInteger', + default: 0, + description: 'Set the maximum number of redirects to follow, defaults to 0 (unlimited)' + }, + quiet: { + name: 'Use Quiet Mode', + type: 'boolean', + default: false, + description: 'Display the requested data without showing any extra output.' + }, + debug: { + name: 'Use Debug Mode', + type: 'boolean', + default: false, + description: 'Show detailed execution information including retry attempts, redirects, and timing breakdowns.' + }, + lineContinuationCharacter: { + name: 'Line continuation character', + type: 'enum', + default: '\\', + description: 'Set a character used to mark the continuation of a statement on the next line ' + + '(generally, \\ for OSX/Linux, ^ for Windows cmd and ` for Powershell)' } }, // Standard array of ids that should be used for options ids. Any new option should be updated here. @@ -116,7 +141,10 @@ const expectedOptions = { 'asyncAwaitEnabled', 'quoteType', 'asyncType', - 'ignoreWarnings' + 'ignoreWarnings', + 'maxRedirects', + 'quiet', + 'debug' ], CODEGEN_ABS_PATH = `./codegens/${codegen}`; describe('Code-gen repository ' + codegen, function () {