From 7804629197bd21f1a77dbfe2deb43ab4527063b3 Mon Sep 17 00:00:00 2001 From: Natarajan Kannan Date: Sat, 29 Jul 2023 21:53:19 +0530 Subject: [PATCH 01/25] feat: support collections as local directories --- bin/newman.js | 77 +---- lib/commands/dir-add-test/index.js | 12 + lib/commands/dir-export/index.js | 28 ++ lib/commands/dir-import/index.js | 31 ++ lib/commands/dir-run/index.js | 42 +++ lib/commands/dir-utils.js | 295 ++++++++++++++++++ lib/commands/index.js | 11 + .../run/collection-runner.js} | 34 +- lib/{ => commands}/run/export-file.js | 0 lib/commands/run/index.js | 29 ++ lib/{ => commands}/run/options.js | 4 +- lib/commands/run/program-options.js | 51 +++ lib/{ => commands}/run/secure-fs.js | 0 lib/{ => commands}/run/summary.js | 0 lib/index.js | 4 +- package.json | 2 + test/unit/options.test.js | 2 +- test/unit/run-summary.test.js | 4 +- test/unit/run.test.js | 2 +- test/unit/secure-fs.test.js | 2 +- 20 files changed, 541 insertions(+), 89 deletions(-) create mode 100644 lib/commands/dir-add-test/index.js create mode 100644 lib/commands/dir-export/index.js create mode 100644 lib/commands/dir-import/index.js create mode 100644 lib/commands/dir-run/index.js create mode 100644 lib/commands/dir-utils.js create mode 100644 lib/commands/index.js rename lib/{run/index.js => commands/run/collection-runner.js} (94%) rename lib/{ => commands}/run/export-file.js (100%) create mode 100644 lib/commands/run/index.js rename lib/{ => commands}/run/options.js (99%) create mode 100644 lib/commands/run/program-options.js rename lib/{ => commands}/run/secure-fs.js (100%) rename lib/{ => commands}/run/summary.js (100%) diff --git a/bin/newman.js b/bin/newman.js index 234052c6c..8ecfca152 100755 --- a/bin/newman.js +++ b/bin/newman.js @@ -2,12 +2,11 @@ require('../lib/node-version-check'); // @note that this should not respect CLI --silent -const _ = require('lodash'), - waterfall = require('async/waterfall'), +const waterfall = require('async/waterfall'), { Command } = require('commander'), program = new Command(), version = require('../package.json').version, - newman = require('../'), + commands = require('../'), util = require('./util'); program @@ -15,75 +14,9 @@ program .addHelpCommand(false) .version(version, '-v, --version'); -// The `run` command allows you to specify a collection to be run with the provided options. -program - .command('run ') - .description('Initiate a Postman Collection run from a given URL or path') - .usage(' [options]') - .option('-e, --environment ', 'Specify a URL or path to a Postman Environment') - .option('-g, --globals ', 'Specify a URL or path to a file containing Postman Globals') - .option('-r, --reporters [reporters]', 'Specify the reporters to use for this run', util.cast.csvParse, ['cli']) - .option('-n, --iteration-count ', 'Define the number of iterations to run', util.cast.integer) - .option('-d, --iteration-data ', 'Specify a data file to use for iterations (either JSON or CSV)') - .option('--folder ', - 'Specify the folder to run from a collection. Can be specified multiple times to run multiple folders', - util.cast.memoize, []) - .option('--global-var ', - 'Allows the specification of global variables via the command line, in a key=value format', - util.cast.memoizeKeyVal, []) - .option('--env-var ', - 'Allows the specification of environment variables via the command line, in a key=value format', - util.cast.memoizeKeyVal, []) - .option('--export-environment ', 'Exports the final environment to a file after completing the run') - .option('--export-globals ', 'Exports the final globals to a file after completing the run') - .option('--export-collection ', 'Exports the executed collection to a file after completing the run') - .option('--postman-api-key ', 'API Key used to load the resources from the Postman API') - .option('--bail [modifiers]', - 'Specify whether or not to gracefully stop a collection run on encountering an error' + - ' and whether to end the run with an error based on the optional modifier', util.cast.csvParse) - .option('--ignore-redirects', 'Prevents Newman from automatically following 3XX redirect responses') - .option('-x , --suppress-exit-code', 'Specify whether or not to override the default exit code for the current run') - .option('--silent', 'Prevents Newman from showing output to CLI') - .option('--disable-unicode', 'Forces Unicode compliant symbols to be replaced by their plain text equivalents') - .option('--color ', 'Enable/Disable colored output (auto|on|off)', util.cast.colorOptions, 'auto') - .option('--delay-request [n]', 'Specify the extent of delay between requests (milliseconds)', util.cast.integer, 0) - .option('--timeout [n]', 'Specify a timeout for collection run (milliseconds)', util.cast.integer, 0) - .option('--timeout-request [n]', 'Specify a timeout for requests (milliseconds)', util.cast.integer, 0) - .option('--timeout-script [n]', 'Specify a timeout for scripts (milliseconds)', util.cast.integer, 0) - .option('--working-dir ', 'Specify the path to the working directory') - .option('--no-insecure-file-read', 'Prevents reading the files situated outside of the working directory') - .option('-k, --insecure', 'Disables SSL validations') - .option('--ssl-client-cert-list ', 'Specify the path to a client certificates configurations (JSON)') - .option('--ssl-client-cert ', 'Specify the path to a client certificate (PEM)') - .option('--ssl-client-key ', 'Specify the path to a client certificate private key') - .option('--ssl-client-passphrase ', 'Specify the client certificate passphrase (for protected key)') - .option('--ssl-extra-ca-certs ', 'Specify additionally trusted CA certificates (PEM)') - .option('--cookie-jar ', 'Specify the path to a custom cookie jar (serialized tough-cookie JSON) ') - .option('--export-cookie-jar ', 'Exports the cookie jar to a file after completing the run') - .option('--verbose', 'Show detailed information of collection run and each request sent') - .action((collection, command) => { - let options = util.commanderToObject(command), - - // parse custom reporter options - reporterOptions = util.parseNestedOptions(program._originalArgs, '--reporter-', options.reporters); - - // Inject additional properties into the options object - options.collection = collection; - options.reporterOptions = reporterOptions._generic; - options.reporter = _.transform(_.omit(reporterOptions, '_generic'), (acc, value, key) => { - acc[key] = _.assignIn(value, reporterOptions._generic); // overrides reporter options with _generic - }, {}); - - newman.run(options, function (err, summary) { - const runError = err || summary.run.error || summary.run.failures.length; - - if (err) { - console.error(`error: ${err.message || err}\n`); - err.friendly && console.error(` ${err.friendly}\n`); - } - runError && !_.get(options, 'suppressExitCode') && (process.exitCode = 1); - }); - }); +Object.keys(commands).forEach(function (commandSetupFunction) { + commands[commandSetupFunction](program); +}); program.addHelpText('after', ` To get available options for a command: diff --git a/lib/commands/dir-add-test/index.js b/lib/commands/dir-add-test/index.js new file mode 100644 index 000000000..57331ea35 --- /dev/null +++ b/lib/commands/dir-add-test/index.js @@ -0,0 +1,12 @@ +/* + @param {Command} - An commander Command instance to which this command is added +*/ +module.exports = function (program) { + program + .command('dir-add-test ') + .description('add a test to directory based postman collection') + .usage(' [options]') + .action((collection, command) => { + console.error(`${command} for ${collection} not implemented yet`); + }); +}; diff --git a/lib/commands/dir-export/index.js b/lib/commands/dir-export/index.js new file mode 100644 index 000000000..16a8c899a --- /dev/null +++ b/lib/commands/dir-export/index.js @@ -0,0 +1,28 @@ +const path = require('path'); +const dirUtils = require('../dir-utils'); + +/* + @param {Command} - An commander Command instance to which this command is added +*/ +module.exports = function (program) { + program + .command('dir-export ') + .description('convert a postman collection file into its directory representation') + .usage(' [options]') + .action((collectionFile) => { + let collectionFilePath = collectionFile.startsWith('/') ? + collectionFile : path.join(process.cwd(), collectionFile); + + dirUtils.assertFileExistence(collectionFilePath); + + let collection = require(collectionFilePath); + + // handle different format postman collection file + if (collection.collection) { + collection = collection.collection; + } + + dirUtils.traverse(collection, []); + process.exit(0); + }); +}; diff --git a/lib/commands/dir-import/index.js b/lib/commands/dir-import/index.js new file mode 100644 index 000000000..680a5f86d --- /dev/null +++ b/lib/commands/dir-import/index.js @@ -0,0 +1,31 @@ +const dirUtils = require('../dir-utils'); +const commandUtil = require('../../../bin/util'); + +/* + @param {Command} - An commander Command instance to which this command is added +*/ +module.exports = function (program) { + program + .command('dir-import ') + .description('convert a postman directory representation into a postman collection') + .usage(' [options]') + .option('-o, --output-file ', 'output collection file, default is collection.json') + .action((collectionDir, command) => { + dirUtils.assertDirectoryExistence(collectionDir); + + let collectionJson = {}, + content, + options = commandUtil.commanderToObject(command), + outputFile = 'collection.json'; + + if (typeof (options.outputFile) === 'string') { + outputFile = options.outputFile; + } + collectionJson = dirUtils.dirTreeToCollectionJson(collectionDir); + + content = JSON.stringify(collectionJson, null, 2); + + dirUtils.createFile(outputFile, content); + process.exit(0); + }); +}; diff --git a/lib/commands/dir-run/index.js b/lib/commands/dir-run/index.js new file mode 100644 index 000000000..68ecc9385 --- /dev/null +++ b/lib/commands/dir-run/index.js @@ -0,0 +1,42 @@ +const _ = require('lodash'), + fs = require('fs'), + dirUtils = require('../dir-utils'), + { cliOptions } = require('../run/program-options'), + { optionCollector, run } = require('../run/collection-runner'); + + +/* + @param {Command} - An commander Command instance to which this command is added +*/ +module.exports = function (program) { + let prg = program + .command('dir-run ') + .description('Runs the tests in collection-dir, with all the provided options') + .usage(' [options]'); + + cliOptions.forEach(function (value) { + prg = prg.option(...value); + }); + + prg.action(function (collectionDir, command) { + let collectionJson = dirUtils.dirTreeToCollectionJson(collectionDir), + + tempDir = dirUtils.createTempDir(), + collectionFile = `${tempDir}/collection.json`; + + dirUtils.createFile(collectionFile, JSON.stringify(collectionJson, null, 2)); + + const options = optionCollector(program, collectionFile, command); + + run(options, function (err, summary) { + const runError = err || summary.run.error || summary.run.failures.length; + + if (err) { + console.error(`error: ${err.message || err}\n`); + err.friendly && console.error(` ${err.friendly}\n`); + } + runError && !_.get(options, 'suppressExitCode') && (process.exitCode = 1); + fs.rmSync(tempDir, { recursive: true }); + }); + }); +}; diff --git a/lib/commands/dir-utils.js b/lib/commands/dir-utils.js new file mode 100644 index 000000000..9666c848f --- /dev/null +++ b/lib/commands/dir-utils.js @@ -0,0 +1,295 @@ +const fs = require('fs'); +const pathLib = require('path'); +const dirTree = require('directory-tree'); +const os = require('os'), + + assertDirectoryExistence = (dir) => { + try { + fs.readdirSync(dir); + } + catch (e) { + console.error(`Unable to open directory ${dir} for implode:\n${e}\n`); + process.exit(-1); + } + }, + + assertFileExistence = (file) => { + try { + fs.accessSync(file, fs.constants.R_OK); + } + catch (e) { + console.error(`Unable to open file ${file}\n`); + process.exit(-1); + } + }, + + // create directory + createDir = (dir) => { + if (fs.existsSync(dir)) { + throw new + Error(`directory ${dir} already exists, use unique names for your requests files within a directory`); + } + else { + fs.mkdirSync(dir, { recursive: true }); + } + }, + + // create file + createFile = (fileName, content, opts) => { + // fs.writeFileSync(fileName, content + '\n'); + fs.writeFileSync(fileName, content + ((typeof (opts) === 'object' && opts.skipTrailingNewLine) ? '' : '\n')); + }, + + // remove forward slashes in name + sanitizePathName = (name) => { + return name.replace(/\//g, '_slash_'); + }, + + topLevelFileKeys = { + variable: 1, + event: 1, + info: 1, + auth: 1 + }, + + traverse = (thing, ancestors) => { + let parent = './' + ancestors.join('/'), + elementOrder, + itemDir, + meta, + name = '', + newParent; + + if (ancestors.length === 0) { + // this is top level set directory name from info + name = thing.info.name; + } + else { + // set directory name to thing['name'] + name = thing.name; + } + name = sanitizePathName(name); + + itemDir = `${parent}/${name}`; + + if (thing.item) { + // TODO - handle directory creation error + createDir(itemDir); + + // top-level files to be created after top-level directory is created + if (ancestors.length === 0) { + Object.keys(topLevelFileKeys).forEach((element) => { + if (thing[element]) { + let obj = {}; + + obj[element] = thing[element]; + // TODO - handle file creation error + createFile(`${parent}/${name}/.${element}.json`, JSON.stringify(obj, null, 2)); + } + }); + } + + // walk-through items + newParent = ancestors.map((x) => { return x; }); + newParent.push(name); + + elementOrder = []; + + thing.item.forEach((element) => { + traverse(element, newParent); + elementOrder.push(element.name); + }); + + meta = { + childrenOrder: elementOrder + }; + + if (thing.description) { + meta.description = thing.description; + } + + // TODO save order of folders as meta + createFile(`${parent}/${name}/.meta.json`, JSON.stringify(meta, null, 2)); + } + + if (thing.request) { + /* + - save request under ancestors dir + - save tests that is part of event + */ + createDir(itemDir); + let requestFileName = `${itemDir}/request.json`; + + createFile(requestFileName, JSON.stringify(thing.request, null, 2)); + + if (thing.event) { + thing.event.forEach((element) => { + let eventFileName = `${itemDir}/event.${element.listen}.js`; + + // createFile(eventFileName, element['script']['exec'].join('\n'), {}); + createFile(eventFileName, element.script.exec.join('\n'), { skipTrailingNewLine: true }); + }); + } + + if (thing.response) { + let responseFileName = `${itemDir}/response.json`; + + createFile(responseFileName, JSON.stringify(thing.response, null, 2)); + } + } + }, + + walkDirTree = (dirTreeJson, level) => { + // console.log(JSON.stringify(dirTreeJson, null, 2)); + const { path, name, children, type } = dirTreeJson; + let eventMatches, + items, + others, + result = {}; + + if (level === 0) { + result.collection = {}; + result = result.collection; + } + + if (level === 1) { + // collect following top level keys + // info + // auth + // event + // variable + let matches = name.match(/\.([^./]*)\.json$/); + + if (matches && topLevelFileKeys[matches[1]]) { + let item = matches[1]; + + // console.log(`@@@ reading item ${path}`); + result[item] = JSON.parse(fs.readFileSync(path))[item]; + + return result; + } + } + // console.log(`processing name:${name} with type:${type} in path:${path}`); + + switch (type) { + case 'file': + switch (name) { + case '.meta.json': + break; + case 'request.json': + result = { + request: JSON.parse(fs.readFileSync(path)) + }; + break; + case 'response.json': + result = { + response: JSON.parse(fs.readFileSync(path)) + }; + break; + default: + eventMatches = name.match(/^event\.([^.]+)\.js$/); + + if (eventMatches) { + // console.log(`@@@@ eventMatches ${eventMatches[1]}`); + let fileContent = fs.readFileSync(path).toString(); + + // console.log(`@@@@ typeof fileContent is ${typeof fileContent}`); + // console.log(`@@@@ fileContent is ${fileContent}`); + result = { + listen: eventMatches[1], + script: { + exec: fileContent.split('\n'), + type: 'text/javascript' + } + }; + } + break; + } + break; + case 'directory': + items = []; + others = {}; + + // top level name is part of info key + if (level !== 0) { + result.name = name; + } + if (children instanceof Array && children.length > 0) { + let metaFilePath = pathLib.join(path, '.meta.json'); + + try { + fs.accessSync(metaFilePath, fs.constants.R_OK); + let meta = JSON.parse(fs.readFileSync(metaFilePath)), + childrenOrder = meta.childrenOrder; + + // console.log(`childrenOrder: ${childrenOrder}`); + // console.log(`children filtered: ${JSON.stringify(children)}`); + children.sort((a, b) => { + // console.log(`a.name: :${a.name}: b.name: :${b.name}:`); + let aIndex = childrenOrder.findIndex((e) => { return (e === a.name); }), + bIndex = childrenOrder.findIndex((e) => { return (e === b.name); }); + + // console.log(`aIndex: ${aIndex} bIndex: ${bIndex}`); + return (aIndex < bIndex) ? -1 : 1; + }); + // add description from meta + result.description = meta.description; + } + catch (e) { + // ignore if .meta.json does not exist + } + children.forEach((child) => { + let output = walkDirTree(child, level + 1); + + if (child.type === 'file') { + // handle event files outside of top-level directory separately + if (child.name.match(/event/) && level > 1) { + if (!result.event) { + result.event = []; + } + result.event.push(output); + } + else { + Object.assign(others, output); + } + } + else { + items.push(output); + } + }); + if (items.length > 0) { + result.item = items; + } + } + Object.assign(result, others); + break; + default: + break; + } + + return result; + }, + + dirTreeToCollectionJson = function (collectionDir) { + let collectionJson = {}; const tree = dirTree(collectionDir, { attributes: ['type'] }); + + collectionJson.collection = walkDirTree(tree, 0); + + return collectionJson; + }, + + createTempDir = function () { + return fs.mkdtempSync(pathLib.join(os.tmpdir(), 'newman-')); + }; + +module.exports = { + assertDirectoryExistence, + assertFileExistence, + createFile, + createDir, + createTempDir, + dirTreeToCollectionJson, + sanitizePathName, + traverse, + walkDirTree +}; diff --git a/lib/commands/index.js b/lib/commands/index.js new file mode 100644 index 000000000..4939f481f --- /dev/null +++ b/lib/commands/index.js @@ -0,0 +1,11 @@ +const commands = [ + 'run', + 'dir-add-test', + 'dir-export', + 'dir-import', + 'dir-run' +]; + +commands.forEach(function (value) { + module.exports[value] = require('./' + value); +}); diff --git a/lib/run/index.js b/lib/commands/run/collection-runner.js similarity index 94% rename from lib/run/index.js rename to lib/commands/run/collection-runner.js index b63409691..e9663112f 100644 --- a/lib/run/index.js +++ b/lib/commands/run/collection-runner.js @@ -8,7 +8,8 @@ var _ = require('lodash'), RunSummary = require('./summary'), getOptions = require('./options'), exportFile = require('./export-file'), - util = require('../util'), + util = require('../../util'), + commandUtil = require('../../../bin/util'), /** * This object describes the various events raised by Newman, and what each event argument contains. @@ -38,11 +39,11 @@ var _ = require('lodash'), * @type {Object} */ defaultReporters = { - cli: require('../reporters/cli'), - json: require('../reporters/json'), - junit: require('../reporters/junit'), - progress: require('../reporters/progress'), - emojitrain: require('../reporters/emojitrain') + cli: require('../../reporters/cli'), + json: require('../../reporters/json'), + junit: require('../../reporters/junit'), + progress: require('../../reporters/progress'), + emojitrain: require('../../reporters/emojitrain') }, /** @@ -84,7 +85,7 @@ var _ = require('lodash'), * @param {Function} callback - The callback function invoked to mark the end of the collection run. * @returns {EventEmitter} - An EventEmitter instance with done and error event attachments. */ -module.exports = function (options, callback) { +function run (options, callback) { // validate all options. it is to be noted that `options` parameter is option and is polymorphic (!callback && _.isFunction(options)) && ( (callback = options), @@ -438,4 +439,23 @@ module.exports = function (options, callback) { }); return emitter; +} + +module.exports = { + optionCollector (program, collection, command) { + let options = commandUtil.commanderToObject(command), + + // parse custom reporter options + reporterOptions = commandUtil.parseNestedOptions(program._originalArgs, '--reporter-', options.reporters); + + // Inject additional properties into the options object + options.collection = collection; + options.reporterOptions = reporterOptions._generic; + options.reporter = _.transform(_.omit(reporterOptions, '_generic'), (acc, value, key) => { + acc[key] = _.assignIn(value, reporterOptions._generic); // overrides reporter options with _generic + }, {}); + + return options; + }, + run }; diff --git a/lib/run/export-file.js b/lib/commands/run/export-file.js similarity index 100% rename from lib/run/export-file.js rename to lib/commands/run/export-file.js diff --git a/lib/commands/run/index.js b/lib/commands/run/index.js new file mode 100644 index 000000000..a011f2dd5 --- /dev/null +++ b/lib/commands/run/index.js @@ -0,0 +1,29 @@ +const _ = require('lodash'), + { optionCollector, run } = require('./collection-runner'), + { cliOptions } = require('../run/program-options'); + +module.exports = function (program) { + // The `run` command allows you to specify a collection to be run with the provided options. + let prg = program + .command('run ') + .description('Initiate a Postman Collection run from a given URL or path') + .usage(' [options]'); + + cliOptions.forEach(function (value) { + prg = prg.option(...value); + }); + + prg.action(function (collection, command) { + const options = optionCollector(program, collection, command); + + run(options, function (err, summary) { + const runError = err || summary.run.error || summary.run.failures.length; + + if (err) { + console.error(`error: ${err.message || err}\n`); + err.friendly && console.error(` ${err.friendly}\n`); + } + runError && !_.get(options, 'suppressExitCode') && (process.exitCode = 1); + }); + }); +}; diff --git a/lib/run/options.js b/lib/commands/run/options.js similarity index 99% rename from lib/run/options.js rename to lib/commands/run/options.js index 19c876ce2..c2fd1c1e2 100644 --- a/lib/run/options.js +++ b/lib/commands/run/options.js @@ -7,8 +7,8 @@ var _ = require('lodash'), transformer = require('postman-collection-transformer'), liquidJSON = require('liquid-json'), parseCsv = require('csv-parse'), - util = require('../util'), - config = require('../config'), + util = require('../../util'), + config = require('../../config'), /** * The message displayed when the specified collection file can't be loaded. diff --git a/lib/commands/run/program-options.js b/lib/commands/run/program-options.js new file mode 100644 index 000000000..641816fad --- /dev/null +++ b/lib/commands/run/program-options.js @@ -0,0 +1,51 @@ +const commandUtil = require('../../../bin/util'), + + cliOptions = [ + ['-e, --environment ', 'Specify a URL or path to a Postman Environment'], + ['-g, --globals ', 'Specify a URL or path to a file containing Postman Globals'], + ['-r, --reporters [reporters]', 'Specify the reporters to use for this run', + commandUtil.cast.csvParse, ['cli']], + ['-n, --iteration-count ', 'Define the number of iterations to run', commandUtil.cast.integer], + ['-d, --iteration-data ', 'Specify a data file to use for iterations (either JSON or CSV)'], + ['--folder ', + 'Specify the folder to run from a collection. Can be specified multiple times to run multiple folders', + commandUtil.cast.memoize, []], + ['--global-var ', + 'Allows the specification of global variables via the command line, in a key=value format', + commandUtil.cast.memoizeKeyVal, []], + ['--env-var ', + 'Allows the specification of environment variables via the command line, in a key=value format', + commandUtil.cast.memoizeKeyVal, []], + ['--export-environment ', 'Exports the final environment to a file after completing the run'], + ['--export-globals ', 'Exports the final globals to a file after completing the run'], + ['--export-collection ', 'Exports the executed collection to a file after completing the run'], + ['--postman-api-key ', 'API Key used to load the resources from the Postman API'], + ['--bail [modifiers]', + 'Specify whether or not to gracefully stop a collection run on encountering an error' + + ' and whether to end the run with an error based on the optional modifier', commandUtil.cast.csvParse], + ['--ignore-redirects', 'Prevents Newman from automatically following 3XX redirect responses'], + ['-x , --suppress-exit-code', 'Specify whether or not to override the default exit code for the current run'], + ['--silent', 'Prevents Newman from showing output to CLI'], + ['--disable-unicode', 'Forces Unicode compliant symbols to be replaced by their plain text equivalents'], + ['--color ', 'Enable/Disable colored output (auto|on|off)', commandUtil.cast.colorOptions, 'auto'], + ['--delay-request [n]', 'Specify the extent of delay between requests (milliseconds)', + commandUtil.cast.integer, 0], + ['--timeout [n]', 'Specify a timeout for collection run (milliseconds)', commandUtil.cast.integer, 0], + ['--timeout-request [n]', 'Specify a timeout for requests (milliseconds)', commandUtil.cast.integer, 0], + ['--timeout-script [n]', 'Specify a timeout for scripts (milliseconds)', commandUtil.cast.integer, 0], + ['--working-dir ', 'Specify the path to the working directory'], + ['--no-insecure-file-read', 'Prevents reading the files situated outside of the working directory'], + ['-k, --insecure', 'Disables SSL validations'], + ['--ssl-client-cert-list ', 'Specify the path to a client certificates configurations (JSON)'], + ['--ssl-client-cert ', 'Specify the path to a client certificate (PEM)'], + ['--ssl-client-key ', 'Specify the path to a client certificate private key'], + ['--ssl-client-passphrase ', 'Specify the client certificate passphrase (for protected key)'], + ['--ssl-extra-ca-certs ', 'Specify additionally trusted CA certificates (PEM)'], + ['--cookie-jar ', 'Specify the path to a custom cookie jar (serialized tough-cookie JSON) '], + ['--export-cookie-jar ', 'Exports the cookie jar to a file after completing the run'], + ['--verbose', 'Show detailed information of collection run and each request sent'] + ]; + +module.exports = { + cliOptions +}; diff --git a/lib/run/secure-fs.js b/lib/commands/run/secure-fs.js similarity index 100% rename from lib/run/secure-fs.js rename to lib/commands/run/secure-fs.js diff --git a/lib/run/summary.js b/lib/commands/run/summary.js similarity index 100% rename from lib/run/summary.js rename to lib/commands/run/summary.js diff --git a/lib/index.js b/lib/index.js index abb378da1..d7a8eba14 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,3 +1 @@ -module.exports = { - run: require('./run') -}; +module.exports = require('./commands'); diff --git a/package.json b/package.json index 102dfd66c..6a5bc0983 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,8 @@ "colors": "1.4.0", "commander": "11.0.0", "csv-parse": "4.16.3", + "directory-tree": "3.5.1", + "eventemitter3": "4.0.7", "filesize": "10.0.12", "liquid-json": "0.3.1", "lodash": "4.17.21", diff --git a/test/unit/options.test.js b/test/unit/options.test.js index d77c60e5a..508a852e9 100644 --- a/test/unit/options.test.js +++ b/test/unit/options.test.js @@ -1,7 +1,7 @@ const _ = require('lodash'), expect = require('chai').expect, { VariableScope } = require('postman-collection'), - options = require('../../lib/run/options'); + options = require('../../lib/commands/run/options'); describe('options', function () { describe('JSON with spaces', function () { diff --git a/test/unit/run-summary.test.js b/test/unit/run-summary.test.js index 44e2ec257..f0a298b43 100644 --- a/test/unit/run-summary.test.js +++ b/test/unit/run-summary.test.js @@ -3,8 +3,8 @@ const _ = require('lodash'), describe('run summary', function () { // @todo add test for computation of timings, transfer sizes and average response time - var Summary = require('../../lib/run/summary'), - EventEmitter = require('events'), + var Summary = require('../../lib/commands/run/summary'), + EventEmitter = require('eventemitter3'), sdk = require('postman-collection'), TRACKED_EVENTS = ['iteration', 'item', 'script', 'prerequest', 'request', 'test', 'assertion', diff --git a/test/unit/run.test.js b/test/unit/run.test.js index 9b359af35..7e56417e0 100644 --- a/test/unit/run.test.js +++ b/test/unit/run.test.js @@ -7,7 +7,7 @@ const _ = require('lodash'), runtime = require('postman-runtime'); describe('run module', function () { - var run = require('../../lib/run'); + var run = require('../../lib/commands/run'); it('should export a function', function () { expect(run).to.be.a('function'); diff --git a/test/unit/secure-fs.test.js b/test/unit/secure-fs.test.js index 1519ae7f2..fa93be1d6 100644 --- a/test/unit/secure-fs.test.js +++ b/test/unit/secure-fs.test.js @@ -1,6 +1,6 @@ const path = require('path'), expect = require('chai').expect, - SecureFs = require('../../lib/run/secure-fs'), + SecureFs = require('../../lib/commands/run/secure-fs'), POSIX_WORKING_DIR = '/Postman/files', WIN32_WORKING_DIR = 'C:\\Postman\\files'; From 18649a045cf05150e2f20d464470b27f05962afe Mon Sep 17 00:00:00 2001 From: Natarajan Kannan Date: Mon, 31 Jul 2023 13:42:38 +0530 Subject: [PATCH 02/25] feat: support collections as local directories From 76fd92df71e7306e0baaaaaf913ff228c7047b43 Mon Sep 17 00:00:00 2001 From: Natarajan Kannan Date: Fri, 11 Aug 2023 22:18:00 +0530 Subject: [PATCH 03/25] fix: fix top level formatting --- lib/commands/dir-utils.js | 16 +++++----------- test/cli/dir-commands.test.js | 5 +++++ 2 files changed, 10 insertions(+), 11 deletions(-) create mode 100644 test/cli/dir-commands.test.js diff --git a/lib/commands/dir-utils.js b/lib/commands/dir-utils.js index 9666c848f..8e958641b 100644 --- a/lib/commands/dir-utils.js +++ b/lib/commands/dir-utils.js @@ -114,9 +114,9 @@ const os = require('os'), if (thing.request) { /* - - save request under ancestors dir - - save tests that is part of event - */ + - save request under ancestors dir + - save tests that is part of event + */ createDir(itemDir); let requestFileName = `${itemDir}/request.json`; @@ -126,7 +126,6 @@ const os = require('os'), thing.event.forEach((element) => { let eventFileName = `${itemDir}/event.${element.listen}.js`; - // createFile(eventFileName, element['script']['exec'].join('\n'), {}); createFile(eventFileName, element.script.exec.join('\n'), { skipTrailingNewLine: true }); }); } @@ -147,11 +146,6 @@ const os = require('os'), others, result = {}; - if (level === 0) { - result.collection = {}; - result = result.collection; - } - if (level === 1) { // collect following top level keys // info @@ -243,7 +237,7 @@ const os = require('os'), if (child.type === 'file') { // handle event files outside of top-level directory separately - if (child.name.match(/event/) && level > 1) { + if (child.name.match(/event/) && level > 0) { if (!result.event) { result.event = []; } @@ -273,7 +267,7 @@ const os = require('os'), dirTreeToCollectionJson = function (collectionDir) { let collectionJson = {}; const tree = dirTree(collectionDir, { attributes: ['type'] }); - collectionJson.collection = walkDirTree(tree, 0); + collectionJson = walkDirTree(tree, 0); return collectionJson; }, diff --git a/test/cli/dir-commands.test.js b/test/cli/dir-commands.test.js new file mode 100644 index 000000000..8cd66f0df --- /dev/null +++ b/test/cli/dir-commands.test.js @@ -0,0 +1,5 @@ +describe('CLI dir command options', function () { + it('should work correctly without any extra options', function (done) { + exec('node ./bin/newman.js dir-export examples/sample-collection.json', done); + }); +}); From ba0702711ef0af76639354d75e6c7074da2855ca Mon Sep 17 00:00:00 2001 From: Natarajan Kannan Date: Sat, 12 Aug 2023 17:52:52 +0530 Subject: [PATCH 04/25] feat: add export-import-test command + fixes --- lib/commands/dir-export-import-test/index.js | 54 ++++++++++++++++++++ lib/commands/dir-export/index.js | 23 +++++++-- lib/commands/dir-utils.js | 28 ++++++---- lib/commands/index.js | 1 + 4 files changed, 94 insertions(+), 12 deletions(-) create mode 100644 lib/commands/dir-export-import-test/index.js diff --git a/lib/commands/dir-export-import-test/index.js b/lib/commands/dir-export-import-test/index.js new file mode 100644 index 000000000..6fd9e6cdb --- /dev/null +++ b/lib/commands/dir-export-import-test/index.js @@ -0,0 +1,54 @@ +const path = require('path'); +const dirUtils = require('../dir-utils'); +const assert = require('assert'); +const fs = require('fs'); +const commandUtil = require('../../../bin/util'); + +/* + @param {Command} - An commander Command instance to which this command is added +*/ +module.exports = function (program) { + program + .command('dir-export-import-test ') + .description('check if an export followed by import results in same collection') + .usage('') + .option('-s, --substitute-slashes', 'if slashes are found in name field - substitute them') + .action((collectionFile, command) => { + const collectionFilePath = collectionFile.startsWith('/') ? + collectionFile : path.join(process.cwd(), collectionFile), + options = commandUtil.commanderToObject(command); + + let inputCollection = require(collectionFilePath), + outputCollection = {}, + tempDir = dirUtils.createTempDir(), + collectionDir; + + dirUtils.assertFileExistence(collectionFilePath); + + // handle different format postman collection file + if (inputCollection.collection) { + inputCollection = inputCollection.collection; + } + collectionDir = inputCollection.info.name; + + try { + // create export directory under a temporary directory + process.chdir(tempDir); + dirUtils.traverse(inputCollection, [], options); + } + catch (e) { + console.error(e); + fs.rmSync(tempDir, { recursive: true, force: true }); + process.exit(1); + } + + dirUtils.assertDirectoryExistence(collectionDir); + outputCollection = dirUtils.dirTreeToCollectionJson(collectionDir); + // clean-up temp directory + fs.rmSync(`./${collectionDir}`, { recursive: true, force: true }); + // console.log(`${JSON.stringify(inputCollection, null, 2)}`); + // console.log(`${JSON.stringify(outputCollection, null, 2)}`); + + assert.deepStrictEqual(inputCollection, outputCollection); + }); +}; diff --git a/lib/commands/dir-export/index.js b/lib/commands/dir-export/index.js index 16a8c899a..582204d1b 100644 --- a/lib/commands/dir-export/index.js +++ b/lib/commands/dir-export/index.js @@ -1,5 +1,7 @@ const path = require('path'); const dirUtils = require('../dir-utils'); +const commandUtil = require('../../../bin/util'); +const fs = require('fs'); /* @param {Command} - An commander Command instance to which this command is added @@ -9,9 +11,12 @@ module.exports = function (program) { .command('dir-export ') .description('convert a postman collection file into its directory representation') .usage(' [options]') - .action((collectionFile) => { + .option('-s, --substitute-slashes', 'if slashes are found in name field - substitute them') + .option('-f, --force-overwrite', 'overwrite if directory already exists') + .action((collectionFile, command) => { let collectionFilePath = collectionFile.startsWith('/') ? - collectionFile : path.join(process.cwd(), collectionFile); + collectionFile : path.join(process.cwd(), collectionFile), + options = commandUtil.commanderToObject(command); dirUtils.assertFileExistence(collectionFilePath); @@ -22,7 +27,19 @@ module.exports = function (program) { collection = collection.collection; } - dirUtils.traverse(collection, []); + if (options.forceOverwrite) { + fs.rmSync(`./${collection.info.name}`, { recursive: true, force: true }); + } + + try { + dirUtils.traverse(collection, [], options); + } + catch (e) { + console.error(e); + fs.rmSync(`./${collection.info.name}`, { recursive: true, force: true }); + process.exit(1); + } + process.exit(0); }); }; diff --git a/lib/commands/dir-utils.js b/lib/commands/dir-utils.js index 8e958641b..fc5a89694 100644 --- a/lib/commands/dir-utils.js +++ b/lib/commands/dir-utils.js @@ -52,7 +52,7 @@ const os = require('os'), auth: 1 }, - traverse = (thing, ancestors) => { + traverse = (thing, ancestors, options) => { let parent = './' + ancestors.join('/'), elementOrder, itemDir, @@ -68,6 +68,13 @@ const os = require('os'), // set directory name to thing['name'] name = thing.name; } + + if (name.includes('/') && !options.substituteSlashes) { + throw new + Error(`collection with names containing slash ${name} cannot be processed - consider renaming them`, + 'slash'); + } + name = sanitizePathName(name); itemDir = `${parent}/${name}`; @@ -96,8 +103,8 @@ const os = require('os'), elementOrder = []; thing.item.forEach((element) => { - traverse(element, newParent); - elementOrder.push(element.name); + traverse(element, newParent, options); + elementOrder.push(sanitizePathName(element.name)); }); meta = { @@ -216,18 +223,21 @@ const os = require('os'), let meta = JSON.parse(fs.readFileSync(metaFilePath)), childrenOrder = meta.childrenOrder; - // console.log(`childrenOrder: ${childrenOrder}`); + // console.log(`childrenOrder: ${JSON.stringify(childrenOrder)}`); // console.log(`children filtered: ${JSON.stringify(children)}`); children.sort((a, b) => { - // console.log(`a.name: :${a.name}: b.name: :${b.name}:`); + // console.log(`a.name: :${a.name}: b.name: :${b.name}:`); let aIndex = childrenOrder.findIndex((e) => { return (e === a.name); }), bIndex = childrenOrder.findIndex((e) => { return (e === b.name); }); // console.log(`aIndex: ${aIndex} bIndex: ${bIndex}`); return (aIndex < bIndex) ? -1 : 1; }); + // console.log(`childrenOrder after sort: ${JSON.stringify(children)}`); // add description from meta - result.description = meta.description; + if (meta.description) { + result.description = meta.description; + } } catch (e) { // ignore if .meta.json does not exist @@ -265,9 +275,9 @@ const os = require('os'), }, dirTreeToCollectionJson = function (collectionDir) { - let collectionJson = {}; const tree = dirTree(collectionDir, { attributes: ['type'] }); - - collectionJson = walkDirTree(tree, 0); + const tree = dirTree(collectionDir, + { attributes: ['type'], exclude: /\.meta\.json/ }), + collectionJson = walkDirTree(tree, 0); return collectionJson; }, diff --git a/lib/commands/index.js b/lib/commands/index.js index 4939f481f..3636eba10 100644 --- a/lib/commands/index.js +++ b/lib/commands/index.js @@ -3,6 +3,7 @@ const commands = [ 'dir-add-test', 'dir-export', 'dir-import', + 'dir-export-import-test', 'dir-run' ]; From 9c0a41e9626b17b410ec312ed07258a508fbfecd Mon Sep 17 00:00:00 2001 From: Natarajan Kannan Date: Sat, 12 Aug 2023 22:33:58 +0530 Subject: [PATCH 05/25] feat: support event ordering --- lib/commands/dir-export-import-test/index.js | 5 ++- lib/commands/dir-utils.js | 47 +++++++++++++++++--- 2 files changed, 46 insertions(+), 6 deletions(-) diff --git a/lib/commands/dir-export-import-test/index.js b/lib/commands/dir-export-import-test/index.js index 6fd9e6cdb..cacaee071 100644 --- a/lib/commands/dir-export-import-test/index.js +++ b/lib/commands/dir-export-import-test/index.js @@ -19,6 +19,7 @@ module.exports = function (program) { options = commandUtil.commanderToObject(command); let inputCollection = require(collectionFilePath), + inputCollectionCloned, outputCollection = {}, tempDir = dirUtils.createTempDir(), collectionDir; @@ -29,6 +30,8 @@ module.exports = function (program) { if (inputCollection.collection) { inputCollection = inputCollection.collection; } + inputCollectionCloned = JSON.parse(JSON.stringify(inputCollection)); + collectionDir = inputCollection.info.name; try { @@ -49,6 +52,6 @@ module.exports = function (program) { // console.log(`${JSON.stringify(inputCollection, null, 2)}`); // console.log(`${JSON.stringify(outputCollection, null, 2)}`); - assert.deepStrictEqual(inputCollection, outputCollection); + assert.deepStrictEqual(inputCollectionCloned, outputCollection); }); }; diff --git a/lib/commands/dir-utils.js b/lib/commands/dir-utils.js index fc5a89694..98f688913 100644 --- a/lib/commands/dir-utils.js +++ b/lib/commands/dir-utils.js @@ -54,7 +54,10 @@ const os = require('os'), traverse = (thing, ancestors, options) => { let parent = './' + ancestors.join('/'), - elementOrder, + elementOrder = [], + elementMap = {}, + eventOrder = [], + eventMeta, itemDir, meta, name = '', @@ -100,11 +103,16 @@ const os = require('os'), newParent = ancestors.map((x) => { return x; }); newParent.push(name); - elementOrder = []; thing.item.forEach((element) => { - traverse(element, newParent, options); + // dis-ambiguate duplicate folder names + if (elementMap[element.name]) { + element.name += '-copy'; + } + elementMap[element.name] = true; + elementOrder.push(sanitizePathName(element.name)); + traverse(element, newParent, options); }); meta = { @@ -130,11 +138,21 @@ const os = require('os'), createFile(requestFileName, JSON.stringify(thing.request, null, 2)); if (thing.event) { + eventOrder = []; thing.event.forEach((element) => { - let eventFileName = `${itemDir}/event.${element.listen}.js`; + let eventFileName = `event.${element.listen}.js`, + eventFilePath = `${itemDir}/${eventFileName}`; - createFile(eventFileName, element.script.exec.join('\n'), { skipTrailingNewLine: true }); + eventOrder.push(eventFileName); + + createFile(eventFilePath, element.script.exec.join('\n'), { skipTrailingNewLine: true }); }); + + eventMeta = { + eventOrder: eventOrder + }; + + createFile(`${itemDir}/.event.meta.json`, JSON.stringify(eventMeta, null, 2)); } if (thing.response) { @@ -261,6 +279,25 @@ const os = require('os'), items.push(output); } }); + // sort event entries using event meta + if (result.event) { + let eventMetaFilePath = pathLib.join(path, '.event.meta.json'), + eventOrder, + eventMeta; + fs.accessSync(eventMetaFilePath, fs.constants.R_OK); + eventMeta = JSON.parse(fs.readFileSync(eventMetaFilePath)); + eventOrder = eventMeta.eventOrder; + + result.event.sort((a, b) => { + // console.log(`a.name: :${a.name}: b.name: :${b.name}:`); + let aIndex = eventOrder.findIndex((e) => { return (e === `event.${a.listen}.js`); }), + bIndex = eventOrder.findIndex((e) => { return (e === `event.${b.listen}.js`); }); + + // console.log(`aIndex: ${aIndex} bIndex: ${bIndex}`); + return (aIndex < bIndex) ? -1 : 1; + }); + } + if (items.length > 0) { result.item = items; } From a34f272a3494fa655289d76448d15f9324a40fb1 Mon Sep 17 00:00:00 2001 From: Natarajan Kannan Date: Sun, 13 Aug 2023 22:58:43 +0530 Subject: [PATCH 06/25] feat: support new test addition --- lib/commands/dir-add-test/index.js | 68 +++++++++++++++++-- .../templates/GET template/.event.meta.json | 6 ++ .../GET template/event.prerequest.js | 1 + .../templates/GET template/event.test.js | 3 + .../templates/GET template/request.json | 4 ++ .../POST body template/.event.meta.json | 6 ++ .../POST body template/event.prerequest.js | 1 + .../POST body template/event.test.js | 3 + .../templates/POST body template/request.json | 14 ++++ .../templates/POST template/.event.meta.json | 6 ++ .../POST template/event.prerequest.js | 1 + .../templates/POST template/event.test.js | 3 + .../templates/POST template/request.json | 14 ++++ lib/commands/dir-export/index.js | 2 +- lib/commands/dir-utils.js | 29 ++++++++ 15 files changed, 155 insertions(+), 6 deletions(-) create mode 100644 lib/commands/dir-add-test/templates/GET template/.event.meta.json create mode 100644 lib/commands/dir-add-test/templates/GET template/event.prerequest.js create mode 100644 lib/commands/dir-add-test/templates/GET template/event.test.js create mode 100644 lib/commands/dir-add-test/templates/GET template/request.json create mode 100644 lib/commands/dir-add-test/templates/POST body template/.event.meta.json create mode 100644 lib/commands/dir-add-test/templates/POST body template/event.prerequest.js create mode 100644 lib/commands/dir-add-test/templates/POST body template/event.test.js create mode 100644 lib/commands/dir-add-test/templates/POST body template/request.json create mode 100644 lib/commands/dir-add-test/templates/POST template/.event.meta.json create mode 100644 lib/commands/dir-add-test/templates/POST template/event.prerequest.js create mode 100644 lib/commands/dir-add-test/templates/POST template/event.test.js create mode 100644 lib/commands/dir-add-test/templates/POST template/request.json diff --git a/lib/commands/dir-add-test/index.js b/lib/commands/dir-add-test/index.js index 57331ea35..b369b1565 100644 --- a/lib/commands/dir-add-test/index.js +++ b/lib/commands/dir-add-test/index.js @@ -1,12 +1,70 @@ +const commandUtil = require('../../../bin/util'), + path = require('path'), + fs = require('fs'), + dirUtils = require('../dir-utils'), + { Option } = require('commander'); + /* @param {Command} - An commander Command instance to which this command is added */ module.exports = function (program) { program - .command('dir-add-test ') - .description('add a test to directory based postman collection') - .usage(' [options]') - .action((collection, command) => { - console.error(`${command} for ${collection} not implemented yet`); + .command('dir-add-test ') + .description('Add a test to directory based postman collection in the given path') + .usage(' [options]') + .addOption(new Option('-t, --type ', 'HTTP Method template').choices(['GET', 'POST']).default('GET')) + .option('-f, --force-overwrite', 'overwrite if test already exists', false) + .action((testPath, command) => { + const options = commandUtil.commanderToObject(command), + methodTemplateMap = { + GET: './lib/commands/dir-add-test/templates/GET template', + POST: './lib/commands/dir-add-test/templates/POST template' + }, + parentDir = path.dirname(testPath), + trimmedTestPath = testPath.replace(/\/+$/, ''), + testPathBaseName = path.basename(testPath), + metaFilePath = path.join(parentDir, '.meta.json'); + + + // check if test with same name exists when forceOverwrite is false + if (!options.forceOverwrite) { + dirUtils.assertDirectoryAbsence(testPath); + } + + // check if testPath's parent is already a collection folder + dirUtils.assertCollectionDir(parentDir); + + // copy request, response, event files + try { + fs.cpSync(methodTemplateMap[options.type], + trimmedTestPath, + { recursive: true } + ); + } catch (e) { + console.error(`Could not copy from template at ${methodTemplateMap[type]}`); + fs.rmSync(`${testPath}`, { recursive: true, force: true }); + process.exit(-1); + } + + // add new test to parent's .meta.json + try { + fs.accessSync(metaFilePath, fs.constants.R_OK); + let meta = JSON.parse(fs.readFileSync(metaFilePath)), + childrenOrder = meta.childrenOrder; + + if (!childrenOrder.includes(testPathBaseName)) { + childrenOrder.push(testPathBaseName); + } + + meta = { + childrenOrder: childrenOrder + }; + + dirUtils.createFile(metaFilePath, JSON.stringify(meta, null, 2)); + } catch (e) { + console.error(`Could not update ${metaFilePath} with new request ${testPath}: ${e}`); + fs.rmSync(`${testPath}`, { recursive: true, force: true }); + process.exit(-1); + } }); }; diff --git a/lib/commands/dir-add-test/templates/GET template/.event.meta.json b/lib/commands/dir-add-test/templates/GET template/.event.meta.json new file mode 100644 index 000000000..2df9d47d9 --- /dev/null +++ b/lib/commands/dir-add-test/templates/GET template/.event.meta.json @@ -0,0 +1,6 @@ +{ + "eventOrder": [ + "event.prerequest.js", + "event.test.js" + ] +} diff --git a/lib/commands/dir-add-test/templates/GET template/event.prerequest.js b/lib/commands/dir-add-test/templates/GET template/event.prerequest.js new file mode 100644 index 000000000..9944ca812 --- /dev/null +++ b/lib/commands/dir-add-test/templates/GET template/event.prerequest.js @@ -0,0 +1 @@ +// any prerequest js code goes here diff --git a/lib/commands/dir-add-test/templates/GET template/event.test.js b/lib/commands/dir-add-test/templates/GET template/event.test.js new file mode 100644 index 000000000..11bd605cc --- /dev/null +++ b/lib/commands/dir-add-test/templates/GET template/event.test.js @@ -0,0 +1,3 @@ +pm.test('expect response be 200', function () { + pm.response.to.be.ok +}) diff --git a/lib/commands/dir-add-test/templates/GET template/request.json b/lib/commands/dir-add-test/templates/GET template/request.json new file mode 100644 index 000000000..90660d2d0 --- /dev/null +++ b/lib/commands/dir-add-test/templates/GET template/request.json @@ -0,0 +1,4 @@ +{ + "url": "https://postman-echo.com/get?source=newman-sample-github-collection", + "method": "GET" +} diff --git a/lib/commands/dir-add-test/templates/POST body template/.event.meta.json b/lib/commands/dir-add-test/templates/POST body template/.event.meta.json new file mode 100644 index 000000000..2df9d47d9 --- /dev/null +++ b/lib/commands/dir-add-test/templates/POST body template/.event.meta.json @@ -0,0 +1,6 @@ +{ + "eventOrder": [ + "event.prerequest.js", + "event.test.js" + ] +} diff --git a/lib/commands/dir-add-test/templates/POST body template/event.prerequest.js b/lib/commands/dir-add-test/templates/POST body template/event.prerequest.js new file mode 100644 index 000000000..9944ca812 --- /dev/null +++ b/lib/commands/dir-add-test/templates/POST body template/event.prerequest.js @@ -0,0 +1 @@ +// any prerequest js code goes here diff --git a/lib/commands/dir-add-test/templates/POST body template/event.test.js b/lib/commands/dir-add-test/templates/POST body template/event.test.js new file mode 100644 index 000000000..11bd605cc --- /dev/null +++ b/lib/commands/dir-add-test/templates/POST body template/event.test.js @@ -0,0 +1,3 @@ +pm.test('expect response be 200', function () { + pm.response.to.be.ok +}) diff --git a/lib/commands/dir-add-test/templates/POST body template/request.json b/lib/commands/dir-add-test/templates/POST body template/request.json new file mode 100644 index 000000000..c4758493f --- /dev/null +++ b/lib/commands/dir-add-test/templates/POST body template/request.json @@ -0,0 +1,14 @@ +{ + "url": "https://postman-echo.com/post", + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\"text\":\"Duis posuere augue vel cursus pharetra. In luctus a ex nec pretium...\"}" + } +} diff --git a/lib/commands/dir-add-test/templates/POST template/.event.meta.json b/lib/commands/dir-add-test/templates/POST template/.event.meta.json new file mode 100644 index 000000000..2df9d47d9 --- /dev/null +++ b/lib/commands/dir-add-test/templates/POST template/.event.meta.json @@ -0,0 +1,6 @@ +{ + "eventOrder": [ + "event.prerequest.js", + "event.test.js" + ] +} diff --git a/lib/commands/dir-add-test/templates/POST template/event.prerequest.js b/lib/commands/dir-add-test/templates/POST template/event.prerequest.js new file mode 100644 index 000000000..9944ca812 --- /dev/null +++ b/lib/commands/dir-add-test/templates/POST template/event.prerequest.js @@ -0,0 +1 @@ +// any prerequest js code goes here diff --git a/lib/commands/dir-add-test/templates/POST template/event.test.js b/lib/commands/dir-add-test/templates/POST template/event.test.js new file mode 100644 index 000000000..11bd605cc --- /dev/null +++ b/lib/commands/dir-add-test/templates/POST template/event.test.js @@ -0,0 +1,3 @@ +pm.test('expect response be 200', function () { + pm.response.to.be.ok +}) diff --git a/lib/commands/dir-add-test/templates/POST template/request.json b/lib/commands/dir-add-test/templates/POST template/request.json new file mode 100644 index 000000000..07e823215 --- /dev/null +++ b/lib/commands/dir-add-test/templates/POST template/request.json @@ -0,0 +1,14 @@ +{ + "url": "https://postman-echo.com/post", + "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..." + } +} diff --git a/lib/commands/dir-export/index.js b/lib/commands/dir-export/index.js index 582204d1b..610244ec9 100644 --- a/lib/commands/dir-export/index.js +++ b/lib/commands/dir-export/index.js @@ -14,7 +14,7 @@ module.exports = function (program) { .option('-s, --substitute-slashes', 'if slashes are found in name field - substitute them') .option('-f, --force-overwrite', 'overwrite if directory already exists') .action((collectionFile, command) => { - let collectionFilePath = collectionFile.startsWith('/') ? + let collectionFilePath = path.isAbsolute(collectionFile) ? collectionFile : path.join(process.cwd(), collectionFile), options = commandUtil.commanderToObject(command); diff --git a/lib/commands/dir-utils.js b/lib/commands/dir-utils.js index 98f688913..67956b336 100644 --- a/lib/commands/dir-utils.js +++ b/lib/commands/dir-utils.js @@ -13,6 +13,16 @@ const os = require('os'), } }, + assertDirectoryAbsence = (dir) => { + try { + fs.readdirSync(dir); + console.error(`Directory ${dir} already exists, exiting`); + process.exit(-1); + } + catch (e) { + } + }, + assertFileExistence = (file) => { try { fs.accessSync(file, fs.constants.R_OK); @@ -23,6 +33,23 @@ const os = require('os'), } }, + assertCollectionDir = (dir) => { + try { + assertDirectoryExistence(dir); + } + catch (e) { + console.error(`Unable to open directory ${dir} for creating test:\n${e}\n`); + process.exit(-1); + } + try { + assertFileExistence(`${dir}/.meta.json`); + } + catch (e) { + console.error(`${dir} is not a collection dir. .meta.json file not-readable :\n${e}\n`); + process.exit(-1); + } + }, + // create directory createDir = (dir) => { if (fs.existsSync(dir)) { @@ -324,6 +351,8 @@ const os = require('os'), }; module.exports = { + assertCollectionDir, + assertDirectoryAbsence, assertDirectoryExistence, assertFileExistence, createFile, From bf77f3b24eb0934f314ef18480172a8f2f1562a2 Mon Sep 17 00:00:00 2001 From: Natarajan Kannan Date: Mon, 14 Aug 2023 15:11:37 +0530 Subject: [PATCH 07/25] feat: support test removal --- lib/commands/dir-add-test/index.js | 17 ++++--- .../templates/GET template/event.test.js | 6 ++- .../POST body template/event.test.js | 6 ++- .../templates/POST template/.event.meta.json | 6 --- .../POST template/event.prerequest.js | 1 - .../templates/POST template/event.test.js | 3 -- .../templates/POST template/request.json | 14 ------ lib/commands/dir-export-import-test/index.js | 2 +- lib/commands/dir-export/index.js | 2 +- lib/commands/dir-import/index.js | 2 +- lib/commands/dir-remove-test/index.js | 50 +++++++++++++++++++ lib/commands/dir-utils.js | 5 +- lib/commands/index.js | 3 +- 13 files changed, 76 insertions(+), 41 deletions(-) delete mode 100644 lib/commands/dir-add-test/templates/POST template/.event.meta.json delete mode 100644 lib/commands/dir-add-test/templates/POST template/event.prerequest.js delete mode 100644 lib/commands/dir-add-test/templates/POST template/event.test.js delete mode 100644 lib/commands/dir-add-test/templates/POST template/request.json create mode 100644 lib/commands/dir-remove-test/index.js diff --git a/lib/commands/dir-add-test/index.js b/lib/commands/dir-add-test/index.js index b369b1565..446f55f80 100644 --- a/lib/commands/dir-add-test/index.js +++ b/lib/commands/dir-add-test/index.js @@ -10,7 +10,7 @@ const commandUtil = require('../../../bin/util'), module.exports = function (program) { program .command('dir-add-test ') - .description('Add a test to directory based postman collection in the given path') + .description('Add a test to directory based Postman collection in the given path') .usage(' [options]') .addOption(new Option('-t, --type ', 'HTTP Method template').choices(['GET', 'POST']).default('GET')) .option('-f, --force-overwrite', 'overwrite if test already exists', false) @@ -18,7 +18,7 @@ module.exports = function (program) { const options = commandUtil.commanderToObject(command), methodTemplateMap = { GET: './lib/commands/dir-add-test/templates/GET template', - POST: './lib/commands/dir-add-test/templates/POST template' + POST: './lib/commands/dir-add-test/templates/POST body template' }, parentDir = path.dirname(testPath), trimmedTestPath = testPath.replace(/\/+$/, ''), @@ -38,10 +38,10 @@ module.exports = function (program) { try { fs.cpSync(methodTemplateMap[options.type], trimmedTestPath, - { recursive: true } - ); - } catch (e) { - console.error(`Could not copy from template at ${methodTemplateMap[type]}`); + { recursive: true }); + } + catch (e) { + console.error(`Could not copy from template at ${methodTemplateMap[options.type]}`); fs.rmSync(`${testPath}`, { recursive: true, force: true }); process.exit(-1); } @@ -57,11 +57,12 @@ module.exports = function (program) { } meta = { - childrenOrder: childrenOrder + childrenOrder }; dirUtils.createFile(metaFilePath, JSON.stringify(meta, null, 2)); - } catch (e) { + } + catch (e) { console.error(`Could not update ${metaFilePath} with new request ${testPath}: ${e}`); fs.rmSync(`${testPath}`, { recursive: true, force: true }); process.exit(-1); diff --git a/lib/commands/dir-add-test/templates/GET template/event.test.js b/lib/commands/dir-add-test/templates/GET template/event.test.js index 11bd605cc..2f88e5882 100644 --- a/lib/commands/dir-add-test/templates/GET template/event.test.js +++ b/lib/commands/dir-add-test/templates/GET template/event.test.js @@ -1,3 +1,5 @@ +/* global pm */ + pm.test('expect response be 200', function () { - pm.response.to.be.ok -}) + pm.response.to.be.ok; +}); diff --git a/lib/commands/dir-add-test/templates/POST body template/event.test.js b/lib/commands/dir-add-test/templates/POST body template/event.test.js index 11bd605cc..2f88e5882 100644 --- a/lib/commands/dir-add-test/templates/POST body template/event.test.js +++ b/lib/commands/dir-add-test/templates/POST body template/event.test.js @@ -1,3 +1,5 @@ +/* global pm */ + pm.test('expect response be 200', function () { - pm.response.to.be.ok -}) + pm.response.to.be.ok; +}); diff --git a/lib/commands/dir-add-test/templates/POST template/.event.meta.json b/lib/commands/dir-add-test/templates/POST template/.event.meta.json deleted file mode 100644 index 2df9d47d9..000000000 --- a/lib/commands/dir-add-test/templates/POST template/.event.meta.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "eventOrder": [ - "event.prerequest.js", - "event.test.js" - ] -} diff --git a/lib/commands/dir-add-test/templates/POST template/event.prerequest.js b/lib/commands/dir-add-test/templates/POST template/event.prerequest.js deleted file mode 100644 index 9944ca812..000000000 --- a/lib/commands/dir-add-test/templates/POST template/event.prerequest.js +++ /dev/null @@ -1 +0,0 @@ -// any prerequest js code goes here diff --git a/lib/commands/dir-add-test/templates/POST template/event.test.js b/lib/commands/dir-add-test/templates/POST template/event.test.js deleted file mode 100644 index 11bd605cc..000000000 --- a/lib/commands/dir-add-test/templates/POST template/event.test.js +++ /dev/null @@ -1,3 +0,0 @@ -pm.test('expect response be 200', function () { - pm.response.to.be.ok -}) diff --git a/lib/commands/dir-add-test/templates/POST template/request.json b/lib/commands/dir-add-test/templates/POST template/request.json deleted file mode 100644 index 07e823215..000000000 --- a/lib/commands/dir-add-test/templates/POST template/request.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "url": "https://postman-echo.com/post", - "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..." - } -} diff --git a/lib/commands/dir-export-import-test/index.js b/lib/commands/dir-export-import-test/index.js index cacaee071..2e2f0bc6b 100644 --- a/lib/commands/dir-export-import-test/index.js +++ b/lib/commands/dir-export-import-test/index.js @@ -10,7 +10,7 @@ const commandUtil = require('../../../bin/util'); module.exports = function (program) { program .command('dir-export-import-test ') - .description('check if an export followed by import results in same collection') + .description('Check if an export followed by import results in same collection') .usage('') .option('-s, --substitute-slashes', 'if slashes are found in name field - substitute them') .action((collectionFile, command) => { diff --git a/lib/commands/dir-export/index.js b/lib/commands/dir-export/index.js index 610244ec9..4977396b3 100644 --- a/lib/commands/dir-export/index.js +++ b/lib/commands/dir-export/index.js @@ -9,7 +9,7 @@ const fs = require('fs'); module.exports = function (program) { program .command('dir-export ') - .description('convert a postman collection file into its directory representation') + .description('Convert a Postman collection file into its directory representation') .usage(' [options]') .option('-s, --substitute-slashes', 'if slashes are found in name field - substitute them') .option('-f, --force-overwrite', 'overwrite if directory already exists') diff --git a/lib/commands/dir-import/index.js b/lib/commands/dir-import/index.js index 680a5f86d..42428512b 100644 --- a/lib/commands/dir-import/index.js +++ b/lib/commands/dir-import/index.js @@ -7,7 +7,7 @@ const commandUtil = require('../../../bin/util'); module.exports = function (program) { program .command('dir-import ') - .description('convert a postman directory representation into a postman collection') + .description('Convert a Postman directory representation into a postman collection') .usage(' [options]') .option('-o, --output-file ', 'output collection file, default is collection.json') .action((collectionDir, command) => { diff --git a/lib/commands/dir-remove-test/index.js b/lib/commands/dir-remove-test/index.js new file mode 100644 index 000000000..219f8f2e6 --- /dev/null +++ b/lib/commands/dir-remove-test/index.js @@ -0,0 +1,50 @@ +const path = require('path'), + fs = require('fs'), + dirUtils = require('../dir-utils'); + +/* + @param {Command} - An commander Command instance to which this command is added +*/ +module.exports = function (program) { + program + .command('dir-remove-test ') + .description('Remove test at given path from directory based Postman collection') + .usage(' [options]') + .action((testPath) => { + const parentDir = path.dirname(testPath), + testPathBaseName = path.basename(testPath), + metaFilePath = path.join(parentDir, '.meta.json'); + + // check if testPath's parent is already a collection folder + dirUtils.assertCollectionDir(parentDir); + + // copy request, response, event files + try { + fs.rmSync(testPath, { recursive: true, force: true }); + } + catch (e) { + console.error(`Could not delete test at ${testPath}, please check permissions`); + process.exit(-1); + } + + // remove test from parent's .meta.json + try { + fs.accessSync(metaFilePath, fs.constants.R_OK); + let meta = JSON.parse(fs.readFileSync(metaFilePath)), + childrenOrder = meta.childrenOrder; + + childrenOrder = childrenOrder.filter((item) => { return item !== testPathBaseName; }); + + meta = { + childrenOrder + }; + + dirUtils.createFile(metaFilePath, JSON.stringify(meta, null, 2)); + } + catch (e) { + console.error(`Could not update ${metaFilePath} with ${testPath} removed: ${e}`); + fs.rmSync(testPath, { recursive: true, force: true }); + process.exit(-1); + } + }); +}; diff --git a/lib/commands/dir-utils.js b/lib/commands/dir-utils.js index 67956b336..50c3c0648 100644 --- a/lib/commands/dir-utils.js +++ b/lib/commands/dir-utils.js @@ -20,6 +20,8 @@ const os = require('os'), process.exit(-1); } catch (e) { + // do nothing, directory is absent + } }, @@ -176,7 +178,7 @@ const os = require('os'), }); eventMeta = { - eventOrder: eventOrder + eventOrder }; createFile(`${itemDir}/.event.meta.json`, JSON.stringify(eventMeta, null, 2)); @@ -311,6 +313,7 @@ const os = require('os'), let eventMetaFilePath = pathLib.join(path, '.event.meta.json'), eventOrder, eventMeta; + fs.accessSync(eventMetaFilePath, fs.constants.R_OK); eventMeta = JSON.parse(fs.readFileSync(eventMetaFilePath)); eventOrder = eventMeta.eventOrder; diff --git a/lib/commands/index.js b/lib/commands/index.js index 3636eba10..ac24817fe 100644 --- a/lib/commands/index.js +++ b/lib/commands/index.js @@ -2,8 +2,9 @@ const commands = [ 'run', 'dir-add-test', 'dir-export', - 'dir-import', 'dir-export-import-test', + 'dir-import', + 'dir-remove-test', 'dir-run' ]; From 72d511e13214850a3b586f76df94595e50c0f266 Mon Sep 17 00:00:00 2001 From: Natarajan Kannan Date: Mon, 21 Aug 2023 11:20:50 +0530 Subject: [PATCH 08/25] fix: fix existing tests --- bin/newman.js | 63 ++++++++---- lib/commands/dir-add-test/index.js | 103 ++++++++++--------- lib/commands/dir-export-import-test/index.js | 94 +++++++++-------- lib/commands/dir-export/index.js | 69 +++++++------ lib/commands/dir-import/index.js | 40 ++++--- lib/commands/dir-remove-test/index.js | 86 +++++++++------- lib/commands/dir-run/index.js | 44 ++++---- lib/commands/index.js | 4 +- lib/commands/run/index.js | 35 ++++--- lib/commands/run/program-options.js | 92 +++++++++-------- test/unit/cli.test.js | 28 +++-- test/unit/defaultReporter.test.js | 12 +-- test/unit/dir-utils.test.js | 12 +++ test/unit/externalReporter.test.js | 4 +- test/unit/run.test.js | 6 +- 15 files changed, 394 insertions(+), 298 deletions(-) create mode 100644 test/unit/dir-utils.test.js diff --git a/bin/newman.js b/bin/newman.js index 8ecfca152..f26ab417b 100755 --- a/bin/newman.js +++ b/bin/newman.js @@ -3,39 +3,61 @@ require('../lib/node-version-check'); // @note that this should not respect CLI --silent const waterfall = require('async/waterfall'), - { Command } = require('commander'), - program = new Command(), version = require('../package.json').version, + { Command } = require('commander'), commands = require('../'), util = require('./util'); -program - .name('newman') - .addHelpCommand(false) - .version(version, '-v, --version'); -Object.keys(commands).forEach(function (commandSetupFunction) { - commands[commandSetupFunction](program); -}); +/** + * Creates a new command instance - useful for testing + * + * @param {Command} Command - Command type from commander library + * @param {Object} commands - list of supported commands - each defining cliSetup and action + */ +function getProgram (Command, commands) { + const program = new Command(); + + program + .name('newman') + .addHelpCommand(false) + .version(version, '-v, --version'); -program.addHelpText('after', ` -To get available options for a command: - newman -h`); + Object.keys(commands).forEach((commandSetupFunction) => { + let cliSetup = commands[commandSetupFunction].cliSetup, + action = commands[commandSetupFunction].action; -// Warn on invalid command and then exits. -program.on('command:*', (command) => { - console.error(`error: invalid command \`${command}\`\n`); - program.help(); -}); + cliSetup(program).action((args, command) => { + action(args, command, program); + }); + }); + + program.addHelpText('after', ` + To get available options for a command: + newman -h`); + + // Warn on invalid command and then exits. + program.on('command:*', (command) => { + console.error(`error: invalid command \`${command}\`\n`); + program.help(); + }); + + return program; +} /** * Starts the script execution. * callback is required when this is required as a module in tests. * * @param {String[]} argv - Argument vector. + * @param {Command} program - Commander command instance to run * @param {?Function} callback - The callback function invoked on the completion of execution. */ -function run (argv, callback) { +function run (argv, program, callback) { + if (!program) { + program = getProgram(Command, commands); + } + waterfall([ (next) => { // cache original argv, required to parse nested options later. @@ -74,4 +96,7 @@ function run (argv, callback) { !module.parent && run(process.argv); // Export to allow debugging and testing. -module.exports = run; +module.exports = { + run, + getProgram +}; diff --git a/lib/commands/dir-add-test/index.js b/lib/commands/dir-add-test/index.js index 446f55f80..ec3556f9b 100644 --- a/lib/commands/dir-add-test/index.js +++ b/lib/commands/dir-add-test/index.js @@ -7,65 +7,70 @@ const commandUtil = require('../../../bin/util'), /* @param {Command} - An commander Command instance to which this command is added */ -module.exports = function (program) { - program +function cliSetup (program) { + return program .command('dir-add-test ') .description('Add a test to directory based Postman collection in the given path') .usage(' [options]') .addOption(new Option('-t, --type ', 'HTTP Method template').choices(['GET', 'POST']).default('GET')) - .option('-f, --force-overwrite', 'overwrite if test already exists', false) - .action((testPath, command) => { - const options = commandUtil.commanderToObject(command), - methodTemplateMap = { - GET: './lib/commands/dir-add-test/templates/GET template', - POST: './lib/commands/dir-add-test/templates/POST body template' - }, - parentDir = path.dirname(testPath), - trimmedTestPath = testPath.replace(/\/+$/, ''), - testPathBaseName = path.basename(testPath), - metaFilePath = path.join(parentDir, '.meta.json'); + .option('-f, --force-overwrite', 'overwrite if test already exists', false); +} +function action (testPath, command) { + const options = commandUtil.commanderToObject(command), + methodTemplateMap = { + GET: './lib/commands/dir-add-test/templates/GET template', + POST: './lib/commands/dir-add-test/templates/POST body template' + }, + parentDir = path.dirname(testPath), + trimmedTestPath = testPath.replace(/\/+$/, ''), + testPathBaseName = path.basename(testPath), + metaFilePath = path.join(parentDir, '.meta.json'); - // check if test with same name exists when forceOverwrite is false - if (!options.forceOverwrite) { - dirUtils.assertDirectoryAbsence(testPath); - } + // check if test with same name exists when forceOverwrite is false + if (!options.forceOverwrite) { + dirUtils.assertDirectoryAbsence(testPath); + } - // check if testPath's parent is already a collection folder - dirUtils.assertCollectionDir(parentDir); + // check if testPath's parent is already a collection folder + dirUtils.assertCollectionDir(parentDir); - // copy request, response, event files - try { - fs.cpSync(methodTemplateMap[options.type], - trimmedTestPath, - { recursive: true }); - } - catch (e) { - console.error(`Could not copy from template at ${methodTemplateMap[options.type]}`); - fs.rmSync(`${testPath}`, { recursive: true, force: true }); - process.exit(-1); - } + // copy request, response, event files + try { + fs.cpSync(methodTemplateMap[options.type], + trimmedTestPath, + { recursive: true }); + } + catch (e) { + console.error(`Could not copy from template at ${methodTemplateMap[options.type]}`); + fs.rmSync(`${testPath}`, { recursive: true, force: true }); + process.exit(-1); + } - // add new test to parent's .meta.json - try { - fs.accessSync(metaFilePath, fs.constants.R_OK); - let meta = JSON.parse(fs.readFileSync(metaFilePath)), - childrenOrder = meta.childrenOrder; + // add new test to parent's .meta.json + try { + fs.accessSync(metaFilePath, fs.constants.R_OK); + let meta = JSON.parse(fs.readFileSync(metaFilePath)), + childrenOrder = meta.childrenOrder; - if (!childrenOrder.includes(testPathBaseName)) { - childrenOrder.push(testPathBaseName); - } + if (!childrenOrder.includes(testPathBaseName)) { + childrenOrder.push(testPathBaseName); + } - meta = { - childrenOrder - }; + meta = { + childrenOrder + }; - dirUtils.createFile(metaFilePath, JSON.stringify(meta, null, 2)); - } - catch (e) { - console.error(`Could not update ${metaFilePath} with new request ${testPath}: ${e}`); - fs.rmSync(`${testPath}`, { recursive: true, force: true }); - process.exit(-1); - } - }); + dirUtils.createFile(metaFilePath, JSON.stringify(meta, null, 2)); + } + catch (e) { + console.error(`Could not update ${metaFilePath} with new request ${testPath}: ${e}`); + fs.rmSync(`${testPath}`, { recursive: true, force: true }); + process.exit(-1); + } +} + +module.exports = { + cliSetup, + action }; diff --git a/lib/commands/dir-export-import-test/index.js b/lib/commands/dir-export-import-test/index.js index 2e2f0bc6b..effadb0ce 100644 --- a/lib/commands/dir-export-import-test/index.js +++ b/lib/commands/dir-export-import-test/index.js @@ -7,51 +7,57 @@ const commandUtil = require('../../../bin/util'); /* @param {Command} - An commander Command instance to which this command is added */ -module.exports = function (program) { - program +function cliSetup (program) { + return program .command('dir-export-import-test ') .description('Check if an export followed by import results in same collection') .usage('') - .option('-s, --substitute-slashes', 'if slashes are found in name field - substitute them') - .action((collectionFile, command) => { - const collectionFilePath = collectionFile.startsWith('/') ? - collectionFile : path.join(process.cwd(), collectionFile), - options = commandUtil.commanderToObject(command); - - let inputCollection = require(collectionFilePath), - inputCollectionCloned, - outputCollection = {}, - tempDir = dirUtils.createTempDir(), - collectionDir; - - dirUtils.assertFileExistence(collectionFilePath); - - // handle different format postman collection file - if (inputCollection.collection) { - inputCollection = inputCollection.collection; - } - inputCollectionCloned = JSON.parse(JSON.stringify(inputCollection)); - - collectionDir = inputCollection.info.name; - - try { - // create export directory under a temporary directory - process.chdir(tempDir); - dirUtils.traverse(inputCollection, [], options); - } - catch (e) { - console.error(e); - fs.rmSync(tempDir, { recursive: true, force: true }); - process.exit(1); - } - - dirUtils.assertDirectoryExistence(collectionDir); - outputCollection = dirUtils.dirTreeToCollectionJson(collectionDir); - // clean-up temp directory - fs.rmSync(`./${collectionDir}`, { recursive: true, force: true }); - // console.log(`${JSON.stringify(inputCollection, null, 2)}`); - // console.log(`${JSON.stringify(outputCollection, null, 2)}`); - - assert.deepStrictEqual(inputCollectionCloned, outputCollection); - }); + .option('-s, --substitute-slashes', 'if slashes are found in name field - substitute them'); +} + +function action (collectionFile, command) { + const collectionFilePath = collectionFile.startsWith('/') ? + collectionFile : path.join(process.cwd(), collectionFile), + options = commandUtil.commanderToObject(command); + + let inputCollection = require(collectionFilePath), + inputCollectionCloned, + outputCollection = {}, + tempDir = dirUtils.createTempDir(), + collectionDir; + + dirUtils.assertFileExistence(collectionFilePath); + + // handle different format postman collection file + if (inputCollection.collection) { + inputCollection = inputCollection.collection; + } + inputCollectionCloned = JSON.parse(JSON.stringify(inputCollection)); + + collectionDir = inputCollection.info.name; + + try { + // create export directory under a temporary directory + process.chdir(tempDir); + dirUtils.traverse(inputCollection, [], options); + } + catch (e) { + console.error(e); + fs.rmSync(tempDir, { recursive: true, force: true }); + process.exit(1); + } + + dirUtils.assertDirectoryExistence(collectionDir); + outputCollection = dirUtils.dirTreeToCollectionJson(collectionDir); + // clean-up temp directory + fs.rmSync(`./${collectionDir}`, { recursive: true, force: true }); + // console.log(`${JSON.stringify(inputCollection, null, 2)}`); + // console.log(`${JSON.stringify(outputCollection, null, 2)}`); + + assert.deepStrictEqual(inputCollectionCloned, outputCollection); +} + +module.exports = { + cliSetup, + action }; diff --git a/lib/commands/dir-export/index.js b/lib/commands/dir-export/index.js index 4977396b3..928c2b2e0 100644 --- a/lib/commands/dir-export/index.js +++ b/lib/commands/dir-export/index.js @@ -6,40 +6,45 @@ const fs = require('fs'); /* @param {Command} - An commander Command instance to which this command is added */ -module.exports = function (program) { - program +function cliSetup (program) { + return program .command('dir-export ') .description('Convert a Postman collection file into its directory representation') .usage(' [options]') .option('-s, --substitute-slashes', 'if slashes are found in name field - substitute them') - .option('-f, --force-overwrite', 'overwrite if directory already exists') - .action((collectionFile, command) => { - let collectionFilePath = path.isAbsolute(collectionFile) ? - collectionFile : path.join(process.cwd(), collectionFile), - options = commandUtil.commanderToObject(command); - - dirUtils.assertFileExistence(collectionFilePath); - - let collection = require(collectionFilePath); - - // handle different format postman collection file - if (collection.collection) { - collection = collection.collection; - } - - if (options.forceOverwrite) { - fs.rmSync(`./${collection.info.name}`, { recursive: true, force: true }); - } - - try { - dirUtils.traverse(collection, [], options); - } - catch (e) { - console.error(e); - fs.rmSync(`./${collection.info.name}`, { recursive: true, force: true }); - process.exit(1); - } - - process.exit(0); - }); + .option('-f, --force-overwrite', 'overwrite if directory already exists'); +} + +function action (collectionFile, command) { + let collectionFilePath = path.isAbsolute(collectionFile) ? + collectionFile : path.join(process.cwd(), collectionFile), + options = commandUtil.commanderToObject(command); + + dirUtils.assertFileExistence(collectionFilePath); + + let collection = require(collectionFilePath); + + // handle different format postman collection file + if (collection.collection) { + collection = collection.collection; + } + + if (options.forceOverwrite) { + fs.rmSync(`./${collection.info.name}`, { recursive: true, force: true }); + } + + try { + dirUtils.traverse(collection, [], options); + } + catch (e) { + console.error(e); + process.exit(1); + } + + process.exit(0); +} + +module.exports = { + cliSetup, + action }; diff --git a/lib/commands/dir-import/index.js b/lib/commands/dir-import/index.js index 42428512b..7e3d42928 100644 --- a/lib/commands/dir-import/index.js +++ b/lib/commands/dir-import/index.js @@ -4,28 +4,34 @@ const commandUtil = require('../../../bin/util'); /* @param {Command} - An commander Command instance to which this command is added */ -module.exports = function (program) { - program +function cliSetup (program) { + return program .command('dir-import ') .description('Convert a Postman directory representation into a postman collection') .usage(' [options]') - .option('-o, --output-file ', 'output collection file, default is collection.json') - .action((collectionDir, command) => { - dirUtils.assertDirectoryExistence(collectionDir); + .option('-o, --output-file ', 'output collection file, default is collection.json'); +} - let collectionJson = {}, - content, - options = commandUtil.commanderToObject(command), - outputFile = 'collection.json'; +function action (collectionDir, command) { + dirUtils.assertDirectoryExistence(collectionDir); - if (typeof (options.outputFile) === 'string') { - outputFile = options.outputFile; - } - collectionJson = dirUtils.dirTreeToCollectionJson(collectionDir); + let collectionJson = {}, + content, + options = commandUtil.commanderToObject(command), + outputFile = 'collection.json'; - content = JSON.stringify(collectionJson, null, 2); + if (typeof (options.outputFile) === 'string') { + outputFile = options.outputFile; + } + collectionJson = dirUtils.dirTreeToCollectionJson(collectionDir); - dirUtils.createFile(outputFile, content); - process.exit(0); - }); + content = JSON.stringify(collectionJson, null, 2); + + dirUtils.createFile(outputFile, content); + process.exit(0); +} + +module.exports = { + cliSetup, + action }; diff --git a/lib/commands/dir-remove-test/index.js b/lib/commands/dir-remove-test/index.js index 219f8f2e6..fd3f960f3 100644 --- a/lib/commands/dir-remove-test/index.js +++ b/lib/commands/dir-remove-test/index.js @@ -5,46 +5,52 @@ const path = require('path'), /* @param {Command} - An commander Command instance to which this command is added */ -module.exports = function (program) { - program +function cliSetup (program) { + return program .command('dir-remove-test ') .description('Remove test at given path from directory based Postman collection') - .usage(' [options]') - .action((testPath) => { - const parentDir = path.dirname(testPath), - testPathBaseName = path.basename(testPath), - metaFilePath = path.join(parentDir, '.meta.json'); - - // check if testPath's parent is already a collection folder - dirUtils.assertCollectionDir(parentDir); - - // copy request, response, event files - try { - fs.rmSync(testPath, { recursive: true, force: true }); - } - catch (e) { - console.error(`Could not delete test at ${testPath}, please check permissions`); - process.exit(-1); - } - - // remove test from parent's .meta.json - try { - fs.accessSync(metaFilePath, fs.constants.R_OK); - let meta = JSON.parse(fs.readFileSync(metaFilePath)), - childrenOrder = meta.childrenOrder; - - childrenOrder = childrenOrder.filter((item) => { return item !== testPathBaseName; }); - - meta = { - childrenOrder - }; - - dirUtils.createFile(metaFilePath, JSON.stringify(meta, null, 2)); - } - catch (e) { - console.error(`Could not update ${metaFilePath} with ${testPath} removed: ${e}`); - fs.rmSync(testPath, { recursive: true, force: true }); - process.exit(-1); - } - }); + .usage(' [options]'); +} + +function action (testPath) { + const parentDir = path.dirname(testPath), + testPathBaseName = path.basename(testPath), + metaFilePath = path.join(parentDir, '.meta.json'); + + // check if testPath's parent is already a collection folder + dirUtils.assertCollectionDir(parentDir); + + // copy request, response, event files + try { + fs.rmSync(testPath, { recursive: true, force: true }); + } + catch (e) { + console.error(`Could not delete test at ${testPath}, please check permissions`); + process.exit(-1); + } + + // remove test from parent's .meta.json + try { + fs.accessSync(metaFilePath, fs.constants.R_OK); + let meta = JSON.parse(fs.readFileSync(metaFilePath)), + childrenOrder = meta.childrenOrder; + + childrenOrder = childrenOrder.filter((item) => { return item !== testPathBaseName; }); + + meta = { + childrenOrder + }; + + dirUtils.createFile(metaFilePath, JSON.stringify(meta, null, 2)); + } + catch (e) { + console.error(`Could not update ${metaFilePath} with ${testPath} removed: ${e}`); + fs.rmSync(testPath, { recursive: true, force: true }); + process.exit(-1); + } +} + +module.exports = { + cliSetup, + action }; diff --git a/lib/commands/dir-run/index.js b/lib/commands/dir-run/index.js index 68ecc9385..3a3977339 100644 --- a/lib/commands/dir-run/index.js +++ b/lib/commands/dir-run/index.js @@ -1,42 +1,46 @@ const _ = require('lodash'), fs = require('fs'), dirUtils = require('../dir-utils'), - { cliOptions } = require('../run/program-options'), + { addRunOptions } = require('../run/program-options'), { optionCollector, run } = require('../run/collection-runner'); /* @param {Command} - An commander Command instance to which this command is added */ -module.exports = function (program) { +function cliSetup (program) { let prg = program .command('dir-run ') .description('Runs the tests in collection-dir, with all the provided options') .usage(' [options]'); - cliOptions.forEach(function (value) { - prg = prg.option(...value); - }); + prg = addRunOptions(prg); - prg.action(function (collectionDir, command) { - let collectionJson = dirUtils.dirTreeToCollectionJson(collectionDir), + return prg; +} - tempDir = dirUtils.createTempDir(), - collectionFile = `${tempDir}/collection.json`; +function action (collectionDir, command, program) { + let collectionJson = dirUtils.dirTreeToCollectionJson(collectionDir), + tempDir = dirUtils.createTempDir(), + collectionFile = `${tempDir}/collection.json`; - dirUtils.createFile(collectionFile, JSON.stringify(collectionJson, null, 2)); + dirUtils.createFile(collectionFile, JSON.stringify(collectionJson, null, 2)); - const options = optionCollector(program, collectionFile, command); + const options = optionCollector(program, collectionFile, command); - run(options, function (err, summary) { - const runError = err || summary.run.error || summary.run.failures.length; + run(options, function (err, summary) { + const runError = err || summary.run.error || summary.run.failures.length; - if (err) { - console.error(`error: ${err.message || err}\n`); - err.friendly && console.error(` ${err.friendly}\n`); - } - runError && !_.get(options, 'suppressExitCode') && (process.exitCode = 1); - fs.rmSync(tempDir, { recursive: true }); - }); + if (err) { + console.error(`error: ${err.message || err}\n`); + err.friendly && console.error(` ${err.friendly}\n`); + } + runError && !_.get(options, 'suppressExitCode') && (process.exitCode = 1); + fs.rmSync(tempDir, { recursive: true }); }); +} + +module.exports = { + cliSetup, + action }; diff --git a/lib/commands/index.js b/lib/commands/index.js index ac24817fe..472a93a08 100644 --- a/lib/commands/index.js +++ b/lib/commands/index.js @@ -1,11 +1,11 @@ const commands = [ - 'run', 'dir-add-test', 'dir-export', 'dir-export-import-test', 'dir-import', 'dir-remove-test', - 'dir-run' + 'dir-run', + 'run' ]; commands.forEach(function (value) { diff --git a/lib/commands/run/index.js b/lib/commands/run/index.js index a011f2dd5..a8d9464ce 100644 --- a/lib/commands/run/index.js +++ b/lib/commands/run/index.js @@ -1,29 +1,40 @@ const _ = require('lodash'), { optionCollector, run } = require('./collection-runner'), - { cliOptions } = require('../run/program-options'); + { addRunOptions } = require('../run/program-options'); -module.exports = function (program) { +function cliSetup (program) { // The `run` command allows you to specify a collection to be run with the provided options. let prg = program .command('run ') .description('Initiate a Postman Collection run from a given URL or path') .usage(' [options]'); + /* cliOptions.forEach(function (value) { + console.log(`@@@@ creation option in cliSetup: ${value}`); prg = prg.option(...value); }); + */ + prg = addRunOptions(prg); - prg.action(function (collection, command) { - const options = optionCollector(program, collection, command); + return prg; +} - run(options, function (err, summary) { - const runError = err || summary.run.error || summary.run.failures.length; +function action (collection, command, program) { + const options = optionCollector(program, collection, command); - if (err) { - console.error(`error: ${err.message || err}\n`); - err.friendly && console.error(` ${err.friendly}\n`); - } - runError && !_.get(options, 'suppressExitCode') && (process.exitCode = 1); - }); + run(options, function (err, summary) { + const runError = err || summary.run.error || summary.run.failures.length; + + if (err) { + console.error(`error: ${err.message || err}\n`); + err.friendly && console.error(` ${err.friendly}\n`); + } + runError && !_.get(options, 'suppressExitCode') && (process.exitCode = 1); }); +} + +module.exports = { + cliSetup, + action }; diff --git a/lib/commands/run/program-options.js b/lib/commands/run/program-options.js index 641816fad..b72cce5d6 100644 --- a/lib/commands/run/program-options.js +++ b/lib/commands/run/program-options.js @@ -1,51 +1,57 @@ -const commandUtil = require('../../../bin/util'), +const commandUtil = require('../../../bin/util'); - cliOptions = [ - ['-e, --environment ', 'Specify a URL or path to a Postman Environment'], - ['-g, --globals ', 'Specify a URL or path to a file containing Postman Globals'], - ['-r, --reporters [reporters]', 'Specify the reporters to use for this run', - commandUtil.cast.csvParse, ['cli']], - ['-n, --iteration-count ', 'Define the number of iterations to run', commandUtil.cast.integer], - ['-d, --iteration-data ', 'Specify a data file to use for iterations (either JSON or CSV)'], - ['--folder ', +/* + @param {Command} - An commander Command instance to which options are added +*/ +function addRunOptions (program) { + program.option('-e, --environment ', 'Specify a URL or path to a Postman Environment') + .option('-g, --globals ', 'Specify a URL or path to a file containing Postman Globals') + .option('-r, --reporters [reporters]', 'Specify the reporters to use for this run', + commandUtil.cast.csvParse, ['cli']) + .option('-n, --iteration-count ', 'Define the number of iterations to run', commandUtil.cast.integer) + .option('-d, --iteration-data ', 'Specify a data file to use for iterations (either JSON or CSV)') + .option('--folder ', 'Specify the folder to run from a collection. Can be specified multiple times to run multiple folders', - commandUtil.cast.memoize, []], - ['--global-var ', + commandUtil.cast.memoize, []) + .option('--global-var ', 'Allows the specification of global variables via the command line, in a key=value format', - commandUtil.cast.memoizeKeyVal, []], - ['--env-var ', + commandUtil.cast.memoizeKeyVal, []) + .option('--env-var ', 'Allows the specification of environment variables via the command line, in a key=value format', - commandUtil.cast.memoizeKeyVal, []], - ['--export-environment ', 'Exports the final environment to a file after completing the run'], - ['--export-globals ', 'Exports the final globals to a file after completing the run'], - ['--export-collection ', 'Exports the executed collection to a file after completing the run'], - ['--postman-api-key ', 'API Key used to load the resources from the Postman API'], - ['--bail [modifiers]', + commandUtil.cast.memoizeKeyVal, []) + .option('--export-environment ', 'Exports the final environment to a file after completing the run') + .option('--export-globals ', 'Exports the final globals to a file after completing the run') + .option('--export-collection ', 'Exports the executed collection to a file after completing the run') + .option('--postman-api-key ', 'API Key used to load the resources from the Postman API') + .option('--bail [modifiers]', 'Specify whether or not to gracefully stop a collection run on encountering an error' + - ' and whether to end the run with an error based on the optional modifier', commandUtil.cast.csvParse], - ['--ignore-redirects', 'Prevents Newman from automatically following 3XX redirect responses'], - ['-x , --suppress-exit-code', 'Specify whether or not to override the default exit code for the current run'], - ['--silent', 'Prevents Newman from showing output to CLI'], - ['--disable-unicode', 'Forces Unicode compliant symbols to be replaced by their plain text equivalents'], - ['--color ', 'Enable/Disable colored output (auto|on|off)', commandUtil.cast.colorOptions, 'auto'], - ['--delay-request [n]', 'Specify the extent of delay between requests (milliseconds)', - commandUtil.cast.integer, 0], - ['--timeout [n]', 'Specify a timeout for collection run (milliseconds)', commandUtil.cast.integer, 0], - ['--timeout-request [n]', 'Specify a timeout for requests (milliseconds)', commandUtil.cast.integer, 0], - ['--timeout-script [n]', 'Specify a timeout for scripts (milliseconds)', commandUtil.cast.integer, 0], - ['--working-dir ', 'Specify the path to the working directory'], - ['--no-insecure-file-read', 'Prevents reading the files situated outside of the working directory'], - ['-k, --insecure', 'Disables SSL validations'], - ['--ssl-client-cert-list ', 'Specify the path to a client certificates configurations (JSON)'], - ['--ssl-client-cert ', 'Specify the path to a client certificate (PEM)'], - ['--ssl-client-key ', 'Specify the path to a client certificate private key'], - ['--ssl-client-passphrase ', 'Specify the client certificate passphrase (for protected key)'], - ['--ssl-extra-ca-certs ', 'Specify additionally trusted CA certificates (PEM)'], - ['--cookie-jar ', 'Specify the path to a custom cookie jar (serialized tough-cookie JSON) '], - ['--export-cookie-jar ', 'Exports the cookie jar to a file after completing the run'], - ['--verbose', 'Show detailed information of collection run and each request sent'] - ]; + ' and whether to end the run with an error based on the optional modifier', commandUtil.cast.csvParse) + .option('--ignore-redirects', 'Prevents Newman from automatically following 3XX redirect responses') + .option('-x , --suppress-exit-code', + 'Specify whether or not to override the default exit code for the current run') + .option('--silent', 'Prevents Newman from showing output to CLI') + .option('--disable-unicode', 'Forces Unicode compliant symbols to be replaced by their plain text equivalents') + .option('--color ', 'Enable/Disable colored output (auto|on|off)', commandUtil.cast.colorOptions, 'auto') + .option('--delay-request [n]', 'Specify the extent of delay between requests (milliseconds)', + commandUtil.cast.integer, 0) + .option('--timeout [n]', 'Specify a timeout for collection run (milliseconds)', commandUtil.cast.integer, 0) + .option('--timeout-request [n]', 'Specify a timeout for requests (milliseconds)', commandUtil.cast.integer, 0) + .option('--timeout-script [n]', 'Specify a timeout for scripts (milliseconds)', commandUtil.cast.integer, 0) + .option('--working-dir ', 'Specify the path to the working directory') + .option('--no-insecure-file-read', 'Prevents reading the files situated outside of the working directory') + .option('-k, --insecure', 'Disables SSL validations') + .option('--ssl-client-cert-list ', 'Specify the path to a client certificates configurations (JSON)') + .option('--ssl-client-cert ', 'Specify the path to a client certificate (PEM)') + .option('--ssl-client-key ', 'Specify the path to a client certificate private key') + .option('--ssl-client-passphrase ', 'Specify the client certificate passphrase (for protected key)') + .option('--ssl-extra-ca-certs ', 'Specify additionally trusted CA certificates (PEM)') + .option('--cookie-jar ', 'Specify the path to a custom cookie jar (serialized tough-cookie JSON) ') + .option('--export-cookie-jar ', 'Exports the cookie jar to a file after completing the run') + .option('--verbose', 'Show detailed information of collection run and each request sent'); + + return program; +} module.exports = { - cliOptions + addRunOptions }; diff --git a/test/unit/cli.test.js b/test/unit/cli.test.js index e445d05fd..4b6a552be 100644 --- a/test/unit/cli.test.js +++ b/test/unit/cli.test.js @@ -1,11 +1,15 @@ const _ = require('lodash'), - sinon = require('sinon'), expect = require('chai').expect, - newman = require('../../'); - describe('cli parser', function () { - let newmanCLI, + var _ = require('lodash'), + sinon = require('sinon'), + { Command } = require('commander'), + newman = require('../../lib/commands/run/collection-runner'), + newmanCLI, + commands = { + run: require('../../lib/commands/run') + }, /** * Wrap newmanCLI callback to extract options passed to sinon `newman` stub. @@ -15,8 +19,10 @@ describe('cli parser', function () { * @param {Function} callback - The callback function invoked on the completion of commander parsing. */ cli = (argv, command, callback) => { - newmanCLI(argv, (err) => { - callback(err, _.get(newman, [command, 'lastCall', 'returnValue'])); + let program = newmanCLI.getProgram(Command, commands); + + newmanCLI.run(argv, program, (err) => { + callback(err, _.get(commands, [command, 'action', 'lastCall', 'returnValue'])); }); }; @@ -27,16 +33,18 @@ describe('cli parser', function () { }); describe('Run Command', function () { - // stub `newman.run`, directly return options passed to `newman.run` in newmanCLI. + // stub `run.action`, directly return options passed to `run.action` in newmanCLI. before(function () { - sinon.stub(newman, 'run').callsFake((options) => { - return options; + sinon.stub(commands.run, 'action').callsFake(function (collection, command, program) { + let opts = newman.optionCollector(program, collection, command); + + return opts; }); }); // restore original `newman.run` function. after(function () { - newman.run.restore(); + commands.run.action.restore(); }); it('should pass default options correctly', function (done) { diff --git a/test/unit/defaultReporter.test.js b/test/unit/defaultReporter.test.js index a213a5da0..41722d5b4 100644 --- a/test/unit/defaultReporter.test.js +++ b/test/unit/defaultReporter.test.js @@ -1,6 +1,6 @@ const sinon = require('sinon'), expect = require('chai').expect, - newman = require('../../'); + { run } = require('../../lib/commands/run/collection-runner'); describe('Default reporter', function () { beforeEach(function () { @@ -12,7 +12,7 @@ describe('Default reporter', function () { }); it('cli can be loaded', function (done) { - newman.run({ + run({ collection: 'test/fixtures/run/single-get-request.json', reporters: ['cli'] }, function (err) { @@ -24,7 +24,7 @@ describe('Default reporter', function () { }); it('json can be loaded', function (done) { - newman.run({ + run({ collection: 'test/fixtures/run/single-get-request.json', reporters: ['json'] }, function (err) { @@ -36,7 +36,7 @@ describe('Default reporter', function () { }); it('junit can be loaded', function (done) { - newman.run({ + run({ collection: 'test/fixtures/run/single-get-request.json', reporters: ['junit'] }, function (err) { @@ -48,7 +48,7 @@ describe('Default reporter', function () { }); it('progress can be loaded', function (done) { - newman.run({ + run({ collection: 'test/fixtures/run/single-get-request.json', reporters: ['progress'] }, function (err) { @@ -60,7 +60,7 @@ describe('Default reporter', function () { }); it('emojitrain can be loaded', function (done) { - newman.run({ + run({ collection: 'test/fixtures/run/single-get-request.json', reporters: ['emojitrain'] }, function (err) { diff --git a/test/unit/dir-utils.test.js b/test/unit/dir-utils.test.js new file mode 100644 index 000000000..ab9266320 --- /dev/null +++ b/test/unit/dir-utils.test.js @@ -0,0 +1,12 @@ +const dirUtils = require('../../lib/commands/dir-utils'), + fs = require('fs'); + + +describe('dir-utils tests', function () { + it('should create a temp dir', function (done) { + const dir = dirUtils.createTempDir(); + + fs.rmSync(dir, { recursive: true, force: true }); + done(); + }); +}); diff --git a/test/unit/externalReporter.test.js b/test/unit/externalReporter.test.js index 511237234..a4c34170c 100644 --- a/test/unit/externalReporter.test.js +++ b/test/unit/externalReporter.test.js @@ -1,6 +1,6 @@ const sinon = require('sinon'), expect = require('chai').expect, - newman = require('../../'); + { run } = require('../../lib/commands/run/collection-runner'); describe('External reporter', function () { beforeEach(function () { @@ -12,7 +12,7 @@ describe('External reporter', function () { }); it('warns when not found', function (done) { - newman.run({ + run({ collection: 'test/fixtures/run/single-get-request.json', reporters: ['unknownreporter'] }, function (err) { diff --git a/test/unit/run.test.js b/test/unit/run.test.js index 7e56417e0..680f25fc7 100644 --- a/test/unit/run.test.js +++ b/test/unit/run.test.js @@ -7,10 +7,12 @@ const _ = require('lodash'), runtime = require('postman-runtime'); describe('run module', function () { - var run = require('../../lib/commands/run'); + var runCommand = require('../../lib/commands/run'); + var { run } = require('../../lib/commands/run/collection-runner'); it('should export a function', function () { - expect(run).to.be.a('function'); + expect(runCommand).to.have.property('cliSetup'); + expect(runCommand).to.have.property('action'); }); it('should start a run with no options and return error in callback', function (done) { From 7b776a784c9ff8bd980491b2f7a760c0605dd1a1 Mon Sep 17 00:00:00 2001 From: Natarajan Kannan Date: Tue, 22 Aug 2023 11:24:27 +0530 Subject: [PATCH 09/25] fix: fix integration/library test executions --- npm/test-integration.js | 2 +- npm/test-library.js | 6 ++---- test/cli/dir-commands.test.js | 4 ++++ 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/npm/test-integration.js b/npm/test-integration.js index ccc1caa17..2fd50ae69 100755 --- a/npm/test-integration.js +++ b/npm/test-integration.js @@ -10,7 +10,7 @@ const fs = require('fs'), async = require('async'), colors = require('colors/safe'), recursive = require('recursive-readdir'), - newman = require(path.join(__dirname, '..', 'index')), + newman = require('../lib/commands/run/collection-runner'); echoServer = require('./server').createRawEchoServer(), redirectServer = require('./server').createRedirectServer(), diff --git a/npm/test-library.js b/npm/test-library.js index a0fca81b3..fbf8c7971 100755 --- a/npm/test-library.js +++ b/npm/test-library.js @@ -3,10 +3,8 @@ // This script is intended to execute all library tests. // --------------------------------------------------------------------------------------------------------------------- -const path = require('path'), - - colors = require('colors/safe'), - Mocha = require('mocha'), +const Mocha = require('mocha'), + path = require('path'), recursive = require('recursive-readdir'), SPEC_SOURCE_DIR = path.join('test', 'library'); diff --git a/test/cli/dir-commands.test.js b/test/cli/dir-commands.test.js index 8cd66f0df..73ef62149 100644 --- a/test/cli/dir-commands.test.js +++ b/test/cli/dir-commands.test.js @@ -2,4 +2,8 @@ describe('CLI dir command options', function () { it('should work correctly without any extra options', function (done) { exec('node ./bin/newman.js dir-export examples/sample-collection.json', done); }); + + it('should be able to run dir-export-import-test correctly without any extra options', function (done) { + exec('node ./bin/newman.js dir-export-import-test examples/sample-collection.json', done); + }); }); From 91d14bf595f218ec04c617957cfded9508a0eb0e Mon Sep 17 00:00:00 2001 From: Natarajan Kannan Date: Tue, 22 Aug 2023 22:09:26 +0530 Subject: [PATCH 10/25] fix: format raw request json for easier editing --- lib/commands/dir-utils.js | 15 ++++++++++++++- npm/test-integration.js | 2 +- test/cli/dir-commands.test.js | 6 +++++- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/lib/commands/dir-utils.js b/lib/commands/dir-utils.js index 50c3c0648..e5fff75dc 100644 --- a/lib/commands/dir-utils.js +++ b/lib/commands/dir-utils.js @@ -162,7 +162,16 @@ const os = require('os'), - save tests that is part of event */ createDir(itemDir); - let requestFileName = `${itemDir}/request.json`; + let requestFileName = `${itemDir}/request.json`, + isJsonRequest = thing.request.header ? thing.request.header.findIndex((element) => { + return (element.key === 'Content-Type' && element.value === 'application/json') + }) : -1; + + // we have a body in the request + if (isJsonRequest > -1 && thing.request.body && thing.request.body.raw) { + // add raw post body as formatted JSON, will be removed in the import + thing.request.body.raw_json_formatted = JSON.parse(thing.request.body.raw); + } createFile(requestFileName, JSON.stringify(thing.request, null, 2)); @@ -228,6 +237,10 @@ const os = require('os'), result = { request: JSON.parse(fs.readFileSync(path)) }; + if (result.request.body && result.request.body.raw_json_formatted) { + result.request.body.raw = JSON.stringify(result.request.body.raw_json_formatted); + delete result.request.body.raw_json_formatted; + } break; case 'response.json': result = { diff --git a/npm/test-integration.js b/npm/test-integration.js index 2fd50ae69..0b559fecf 100755 --- a/npm/test-integration.js +++ b/npm/test-integration.js @@ -10,7 +10,7 @@ const fs = require('fs'), async = require('async'), colors = require('colors/safe'), recursive = require('recursive-readdir'), - newman = require('../lib/commands/run/collection-runner'); + newman = require('../lib/commands/run/collection-runner'), echoServer = require('./server').createRawEchoServer(), redirectServer = require('./server').createRedirectServer(), diff --git a/test/cli/dir-commands.test.js b/test/cli/dir-commands.test.js index 73ef62149..590d4ea98 100644 --- a/test/cli/dir-commands.test.js +++ b/test/cli/dir-commands.test.js @@ -1,6 +1,10 @@ +const fs = require('fs'); + describe('CLI dir command options', function () { it('should work correctly without any extra options', function (done) { - exec('node ./bin/newman.js dir-export examples/sample-collection.json', done); + exec('node ./bin/newman.js dir-export examples/sample-collection.json'); + // TODO - move to temp dir + exec('rm -rf ./Sample\ Postman\ Collection', done); }); it('should be able to run dir-export-import-test correctly without any extra options', function (done) { From 8ad4a687d3feb3bf3bfdd28c1592aeee40ec04e3 Mon Sep 17 00:00:00 2001 From: Natarajan Kannan Date: Wed, 23 Aug 2023 10:55:30 +0530 Subject: [PATCH 11/25] fix: handle raw body parse errors gracefully --- lib/commands/dir-utils.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/commands/dir-utils.js b/lib/commands/dir-utils.js index e5fff75dc..495a51f69 100644 --- a/lib/commands/dir-utils.js +++ b/lib/commands/dir-utils.js @@ -170,7 +170,11 @@ const os = require('os'), // we have a body in the request if (isJsonRequest > -1 && thing.request.body && thing.request.body.raw) { // add raw post body as formatted JSON, will be removed in the import - thing.request.body.raw_json_formatted = JSON.parse(thing.request.body.raw); + try { + thing.request.body.raw_json_formatted = JSON.parse(thing.request.body.raw); + } catch(e) { + console.warn(`Unable to parse raw body for ${name}`); + } } createFile(requestFileName, JSON.stringify(thing.request, null, 2)); From 8af8bf228da8ca0a8c68f945157df500ff33c1dc Mon Sep 17 00:00:00 2001 From: Natarajan Kannan Date: Wed, 23 Aug 2023 15:29:43 +0530 Subject: [PATCH 12/25] build: remove raw body from exported collection --- lib/commands/dir-utils.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/commands/dir-utils.js b/lib/commands/dir-utils.js index 495a51f69..97360bb3e 100644 --- a/lib/commands/dir-utils.js +++ b/lib/commands/dir-utils.js @@ -172,6 +172,7 @@ const os = require('os'), // add raw post body as formatted JSON, will be removed in the import try { thing.request.body.raw_json_formatted = JSON.parse(thing.request.body.raw); + delete thing.request.body.raw; } catch(e) { console.warn(`Unable to parse raw body for ${name}`); } @@ -224,7 +225,6 @@ const os = require('os'), if (matches && topLevelFileKeys[matches[1]]) { let item = matches[1]; - // console.log(`@@@ reading item ${path}`); result[item] = JSON.parse(fs.readFileSync(path))[item]; return result; @@ -255,11 +255,8 @@ const os = require('os'), eventMatches = name.match(/^event\.([^.]+)\.js$/); if (eventMatches) { - // console.log(`@@@@ eventMatches ${eventMatches[1]}`); let fileContent = fs.readFileSync(path).toString(); - // console.log(`@@@@ typeof fileContent is ${typeof fileContent}`); - // console.log(`@@@@ fileContent is ${fileContent}`); result = { listen: eventMatches[1], script: { From 230ae42c32f5f59f5cbfadea4352dd719a4e6a49 Mon Sep 17 00:00:00 2001 From: Natarajan Kannan Date: Sat, 26 Aug 2023 22:56:47 +0530 Subject: [PATCH 13/25] doc: add rationale documentation for dir-* commands --- DIR_CMDS.md | 156 ++++++++++++++++++ examples/Sample Postman Collection/.info.json | 7 + examples/Sample Postman Collection/.meta.json | 7 + .../A simple GET request/.event.meta.json | 5 + .../A simple GET request/event.test.js | 7 + .../A simple GET request/request.json | 4 + .../request.json | 16 ++ .../A simple POST request/request.json | 14 ++ 8 files changed, 216 insertions(+) create mode 100644 DIR_CMDS.md create mode 100644 examples/Sample Postman Collection/.info.json create mode 100644 examples/Sample Postman Collection/.meta.json create mode 100644 examples/Sample Postman Collection/A simple GET request/.event.meta.json create mode 100644 examples/Sample Postman Collection/A simple GET request/event.test.js create mode 100644 examples/Sample Postman Collection/A simple GET request/request.json create mode 100644 examples/Sample Postman Collection/A simple POST request with JSON body/request.json create mode 100644 examples/Sample Postman Collection/A simple POST request/request.json diff --git a/DIR_CMDS.md b/DIR_CMDS.md new file mode 100644 index 000000000..fbce9404d --- /dev/null +++ b/DIR_CMDS.md @@ -0,0 +1,156 @@ +This document introduces the user to the `dir-*` commands that are added in +this fork to facilitate easier maintenance and review of Postman collections. + +## Challenges in maintaining Postman collection in code repositories +Postman collections are maintained as a [JSON +file](https://learning.postman.com/collection-format/getting-started/structure-of-a-collection/) +by the Postman UI application. It is a single file that contains all the +requests/tests/variables etc. It is designed to be edited with the Postman UI. +The `newman` tool allows teams to run Postman collections as part of their CI +pipelines by supplying the Postman collection file as an argument. Typically, +teams that rely on Postman for their API testing in CI pipelines maintain a copy +of the collection file in their source repository. When teams maintain the +Postman collection in their repository, they will have the following workflow to +make changes to it and push is upstream: + +* Import the collection json file from the repo into Postman UI +* Make changes to the collection in Postman UI +* Export the collection to a file using Postman UI +* Commit and push the changes + +There are two primary challenges with the above workflow: + +* Collection file diffs are very hard to review in pull requests + * e.g. [PR containing payload + diffs](https://github.com/juspay/hyperswitch/pull/1948/files) + * e.g. test javascript code is maintained as an array of lines in the + collection JSON format which makes test changes hard to review +* Developers cannot use their favourite editor to make the changes + +As a consequence of the above test maintenance suffers and tests often become stale. + +## Solution to the above challenges + +The core reason for challenges presented above is the file format in which the +Postman tests are maintained. A single JSON file that encapsulates the entire +test code and sequencing does not lend itself for easy reviewing and be an +all-editor friendly format. + +This fork of newman attempts to address the challenge by representing the +Postman collection as a set of directories/files. It then allows developers +to run tests stored as directories/files. It achieves this by re-constructing +the collection json from the directory/file structure and leveraging the +existing newman `run` implementation. + +For example, this is the [directory equivalent +representation](examples/Sample%20Postman%20Collection) of a [sample +collection](examples/sample-collection.json) in the examples directory as +generated by the `dir-export` command. + +One can install the newman executable in this fork using the command: + +``` +npm install -g 'git+ssh://git@github.com:knutties/newman.git#feature/newman-dir' +``` + +The following no-arg run shows the new commands added to newman to help manage Postman +tests maintained as directories/files instead of a single json file. + +``` +$ newman +Usage: newman [options] [command] + +Options: + -v, --version output the version number + -h, --help display help for command + +Commands: + dir-add-test [options] Add a test to directory based Postman collection in the given path + dir-export [options] Convert a Postman collection file into its directory representation + dir-export-import-test [options] Check if an export followed by import results in same collection + dir-import [options] Convert a Postman directory representation into a postman collection + dir-remove-test Remove test at given path from directory based Postman collection + dir-run [options] Runs the tests in collection-dir, with all the provided options + run [options] Initiate a Postman Collection run from a given URL or path + + To get available options for a command: + newman -h +``` + +The following sections show the invocation of the different commands added in +this fork of newman + +### Generating directory representation for an existing collection +``` +newman dir-export examples/sample-collection.json +``` + +### Convert a directory representation back to a Postman collection json file +``` +newman dir-import examples/Sample Postman Collection/ -o examples/sample-collection.json +``` + +### Diff the collection generated by export followed by its import +This command is typically used to test the tool itself to ensure it can handle +all collection json use-cases. +``` +newman dir-export-import-test examples/sample-collection.json +``` + +### Execute a Postman collection stored in the directory format +``` +newman dir-run examples/Sample Postman Collection/ +``` + +output of `dir-run` +``` +newman + +Sample Postman Collection + +→ A simple GET request + GET https://postman-echo.com/get?source=newman-sample-github-collection [200 OK, 847B, 1054ms] + ✓ expect response be 200 + ✓ expect response json contain args + +→ A simple POST request + POST https://postman-echo.com/post [200 OK, 1.06kB, 317ms] + +→ A simple POST request with JSON body + POST https://postman-echo.com/post [200 OK, 1.17kB, 233ms] + +┌─────────────────────────┬─────────────────────┬─────────────────────┐ +│ │ executed │ failed │ +├─────────────────────────┼─────────────────────┼─────────────────────┤ +│ iterations │ 1 │ 0 │ +├─────────────────────────┼─────────────────────┼─────────────────────┤ +│ requests │ 3 │ 0 │ +├─────────────────────────┼─────────────────────┼─────────────────────┤ +│ test-scripts │ 1 │ 0 │ +├─────────────────────────┼─────────────────────┼─────────────────────┤ +│ prerequest-scripts │ 0 │ 0 │ +├─────────────────────────┼─────────────────────┼─────────────────────┤ +│ assertions │ 2 │ 0 │ +├─────────────────────────┴─────────────────────┴─────────────────────┤ +│ total run duration: 1647ms │ +├─────────────────────────────────────────────────────────────────────┤ +│ total data received: 2.13kB (approx) │ +├─────────────────────────────────────────────────────────────────────┤ +│ average response time: 534ms [min: 233ms, max: 1054ms, s.d.: 368ms] │ +└─────────────────────────────────────────────────────────────────────┘ +``` + +### Add a test under a directory +``` +newman dir-add-test examples/Sample Postman Collection/test4 +``` + +This command adds the test to the end of the tests already present in the +folder. The order of the tests is stored in a separate file called +[.meta.json](examples/Sample Postman Collection/.meta.json). The order of tests +can be changed by re-ordering the tests in this file. + +### Remove a test +``` +newman dir-remove-test examples/Sample Postman Collection/test4 +``` diff --git a/examples/Sample Postman Collection/.info.json b/examples/Sample Postman Collection/.info.json new file mode 100644 index 000000000..8bedbe2fd --- /dev/null +++ b/examples/Sample Postman Collection/.info.json @@ -0,0 +1,7 @@ +{ + "info": { + "name": "Sample Postman Collection", + "schema": "https://schema.getpostman.com/json/collection/v2.0.0/collection.json", + "description": "A sample collection to demonstrate collections as a set of related requests" + } +} diff --git a/examples/Sample Postman Collection/.meta.json b/examples/Sample Postman Collection/.meta.json new file mode 100644 index 000000000..6984bf8cf --- /dev/null +++ b/examples/Sample Postman Collection/.meta.json @@ -0,0 +1,7 @@ +{ + "childrenOrder": [ + "A simple GET request", + "A simple POST request", + "A simple POST request with JSON body" + ] +} diff --git a/examples/Sample Postman Collection/A simple GET request/.event.meta.json b/examples/Sample Postman Collection/A simple GET request/.event.meta.json new file mode 100644 index 000000000..688c85746 --- /dev/null +++ b/examples/Sample Postman Collection/A simple GET request/.event.meta.json @@ -0,0 +1,5 @@ +{ + "eventOrder": [ + "event.test.js" + ] +} diff --git a/examples/Sample Postman Collection/A simple GET request/event.test.js b/examples/Sample Postman Collection/A simple GET request/event.test.js new file mode 100644 index 000000000..a2d0fcb3f --- /dev/null +++ b/examples/Sample Postman Collection/A simple GET request/event.test.js @@ -0,0 +1,7 @@ +pm.test('expect response be 200', function () { + pm.response.to.be.ok +}) +pm.test('expect response json contain args', function () { + pm.expect(pm.response.json().args).to.have.property('source') + .and.equal('newman-sample-github-collection') +}) \ No newline at end of file diff --git a/examples/Sample Postman Collection/A simple GET request/request.json b/examples/Sample Postman Collection/A simple GET request/request.json new file mode 100644 index 000000000..90660d2d0 --- /dev/null +++ b/examples/Sample Postman Collection/A simple GET request/request.json @@ -0,0 +1,4 @@ +{ + "url": "https://postman-echo.com/get?source=newman-sample-github-collection", + "method": "GET" +} diff --git a/examples/Sample Postman Collection/A simple POST request with JSON body/request.json b/examples/Sample Postman Collection/A simple POST request with JSON body/request.json new file mode 100644 index 000000000..5b87e25f9 --- /dev/null +++ b/examples/Sample Postman Collection/A simple POST request with JSON body/request.json @@ -0,0 +1,16 @@ +{ + "url": "https://postman-echo.com/post", + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw_json_formatted": { + "text": "Duis posuere augue vel cursus pharetra. In luctus a ex nec pretium..." + } + } +} diff --git a/examples/Sample Postman Collection/A simple POST request/request.json b/examples/Sample Postman Collection/A simple POST request/request.json new file mode 100644 index 000000000..07e823215 --- /dev/null +++ b/examples/Sample Postman Collection/A simple POST request/request.json @@ -0,0 +1,14 @@ +{ + "url": "https://postman-echo.com/post", + "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..." + } +} From 327ea92e7f864a23d9ffff289a490d6edc855e6b Mon Sep 17 00:00:00 2001 From: Natarajan Kannan Date: Sun, 27 Aug 2023 12:12:38 +0530 Subject: [PATCH 14/25] feat: add dir-create/dir-add-folder/dir-remove-folder commands --- DIR_CMDS.md => DIR_COMMANDS.md | 92 ++++++++++++++----- lib/commands/dir-add-folder/index.js | 85 +++++++++++++++++ lib/commands/dir-add-test/index.js | 4 +- lib/commands/dir-create/index.js | 40 ++++++++ .../Sample Postman Collection/.info.json | 7 ++ .../Sample Postman Collection/.meta.json | 7 ++ .../A simple GET request/.event.meta.json | 5 + .../A simple GET request/event.test.js | 10 ++ .../A simple GET request/request.json | 4 + .../request.json | 16 ++++ .../A simple POST request/request.json | 14 +++ lib/commands/dir-remove-folder/index.js | 56 +++++++++++ lib/commands/dir-remove-test/index.js | 2 +- lib/commands/dir-utils.js | 7 +- lib/commands/index.js | 3 + test/cli/dir-commands.test.js | 4 +- 16 files changed, 324 insertions(+), 32 deletions(-) rename DIR_CMDS.md => DIR_COMMANDS.md (65%) create mode 100644 lib/commands/dir-add-folder/index.js create mode 100644 lib/commands/dir-create/index.js create mode 100644 lib/commands/dir-create/templates/Sample Postman Collection/.info.json create mode 100644 lib/commands/dir-create/templates/Sample Postman Collection/.meta.json create mode 100644 lib/commands/dir-create/templates/Sample Postman Collection/A simple GET request/.event.meta.json create mode 100644 lib/commands/dir-create/templates/Sample Postman Collection/A simple GET request/event.test.js create mode 100644 lib/commands/dir-create/templates/Sample Postman Collection/A simple GET request/request.json create mode 100644 lib/commands/dir-create/templates/Sample Postman Collection/A simple POST request with JSON body/request.json create mode 100644 lib/commands/dir-create/templates/Sample Postman Collection/A simple POST request/request.json create mode 100644 lib/commands/dir-remove-folder/index.js diff --git a/DIR_CMDS.md b/DIR_COMMANDS.md similarity index 65% rename from DIR_CMDS.md rename to DIR_COMMANDS.md index fbce9404d..b9036e0cc 100644 --- a/DIR_CMDS.md +++ b/DIR_COMMANDS.md @@ -1,31 +1,32 @@ This document introduces the user to the `dir-*` commands that are added in -this fork to facilitate easier maintenance and review of Postman collections. +this fork to facilitate CLI based creation, maintenance and review of Postman collections. ## Challenges in maintaining Postman collection in code repositories -Postman collections are maintained as a [JSON +Postman collections are created using the Postman UI application. These +collections are represented as a [JSON file](https://learning.postman.com/collection-format/getting-started/structure-of-a-collection/) -by the Postman UI application. It is a single file that contains all the -requests/tests/variables etc. It is designed to be edited with the Postman UI. -The `newman` tool allows teams to run Postman collections as part of their CI -pipelines by supplying the Postman collection file as an argument. Typically, -teams that rely on Postman for their API testing in CI pipelines maintain a copy -of the collection file in their source repository. When teams maintain the -Postman collection in their repository, they will have the following workflow to -make changes to it and push is upstream: +by the Postman UI application. It is a single file that contains all the +requests/tests/variables etc of the collection. It is designed to be edited +within the Postman UI. The `newman` tool allows teams to run Postman collections +as part of their CI pipelines by supplying the Postman collection file as an +argument. Typically, teams that rely on Postman for their API testing in CI +pipelines maintain a copy of the collection file in their source repository. +When teams maintain the Postman collection in their repository, they will have +the following workflow to make changes to it and push is upstream: * Import the collection json file from the repo into Postman UI * Make changes to the collection in Postman UI * Export the collection to a file using Postman UI * Commit and push the changes -There are two primary challenges with the above workflow: +There are a few challenges with the above workflow: * Collection file diffs are very hard to review in pull requests * e.g. [PR containing payload diffs](https://github.com/juspay/hyperswitch/pull/1948/files) * e.g. test javascript code is maintained as an array of lines in the collection JSON format which makes test changes hard to review -* Developers cannot use their favourite editor to make the changes +* Developers cannot use their favourite editor to make changes, add/remove new tests As a consequence of the above test maintenance suffers and tests often become stale. @@ -37,16 +38,38 @@ test code and sequencing does not lend itself for easy reviewing and be an all-editor friendly format. This fork of newman attempts to address the challenge by representing the -Postman collection as a set of directories/files. It then allows developers -to run tests stored as directories/files. It achieves this by re-constructing -the collection json from the directory/file structure and leveraging the -existing newman `run` implementation. +Postman collection as a set of directories/files. It allows developers to +create Postman collection using the directory representation and also run them. +It achieves this by re-constructing the collection json from the directory/file +structure and leveraging the existing newman `run` implementation. For example, this is the [directory equivalent representation](examples/Sample%20Postman%20Collection) of a [sample collection](examples/sample-collection.json) in the examples directory as generated by the `dir-export` command. +### Directory representation of collection + +The following is a tree structure of the directory that represents the Postman +collection with annotations of what each file corresponds to in a Postman +collection. +``` +examples/Sample Postman Collection +├── .info.json # contains the info element from the top of collection json +├── .meta.json # contains the ordering of the test folders +├── .auth.json # contains the auth element from the top of the collection json +├── .variable.json # contains the variable element from the top of the collection json +├── .event.json # contains the event element from the top of the collection json +└── A simple GET request +    ├── .event.meta.json # contains the ordering of the scripts - prerequest / test +    ├── event.test.js # the test script that runs post the request +    ├── event.prerequest.js # the test script that runs before the request +    ├── request.json # the request json from the Postman collection +    └── response.json # the response json +``` + +### Installing newman from this fork + One can install the newman executable in this fork using the command: ``` @@ -58,6 +81,7 @@ tests maintained as directories/files instead of a single json file. ``` $ newman + Usage: newman [options] [command] Options: @@ -65,10 +89,13 @@ Options: -h, --help display help for command Commands: + dir-add-folder [options] Add a folder to directory based Postman collection in the given path dir-add-test [options] Add a test to directory based Postman collection in the given path + dir-create [options] Create a directory based Postman collection in the given path dir-export [options] Convert a Postman collection file into its directory representation dir-export-import-test [options] Check if an export followed by import results in same collection dir-import [options] Convert a Postman directory representation into a postman collection + dir-remove-folder Remove test at given path from directory based Postman collection dir-remove-test Remove test at given path from directory based Postman collection dir-run [options] Runs the tests in collection-dir, with all the provided options run [options] Initiate a Postman Collection run from a given URL or path @@ -78,7 +105,12 @@ Commands: ``` The following sections show the invocation of the different commands added in -this fork of newman +this fork of newman. + +### Create a new directory based Postman collection +``` +newman dir-create new-dir-collection +``` ### Generating directory representation for an existing collection ``` @@ -87,7 +119,7 @@ newman dir-export examples/sample-collection.json ### Convert a directory representation back to a Postman collection json file ``` -newman dir-import examples/Sample Postman Collection/ -o examples/sample-collection.json +newman dir-import examples/Sample\ Postman\ Collection/ -o examples/sample-collection.json ``` ### Diff the collection generated by export followed by its import @@ -99,10 +131,13 @@ newman dir-export-import-test examples/sample-collection.json ### Execute a Postman collection stored in the directory format ``` -newman dir-run examples/Sample Postman Collection/ +newman dir-run examples/Sample\ Postman\ Collection/ ``` +The `dir-run` command supports all the options that the stock `run` command of +newman supports. This is achieved by re-using the same set of command options +for both the commands. -output of `dir-run` +Sample output of `dir-run` shown below ``` newman @@ -140,17 +175,28 @@ Sample Postman Collection └─────────────────────────────────────────────────────────────────────┘ ``` +### Add a folder under a existing directory +``` +newman dir-add-folder examples/Sample\ Postman\ Collection/folder1 +``` + + ### Add a test under a directory ``` -newman dir-add-test examples/Sample Postman Collection/test4 +newman dir-add-test examples/Sample\ Postman\ Collection/test4 ``` This command adds the test to the end of the tests already present in the folder. The order of the tests is stored in a separate file called -[.meta.json](examples/Sample Postman Collection/.meta.json). The order of tests +[.meta.json](examples/Sample%20Postman%20Collection/.meta.json). The order of tests can be changed by re-ordering the tests in this file. +### Remove a folder +``` +newman dir-remove-folder examples/Sample\ Postman\ Collection/folder1 +``` + ### Remove a test ``` -newman dir-remove-test examples/Sample Postman Collection/test4 +newman dir-remove-test examples/Sample\ Postman\ Collection/test4 ``` diff --git a/lib/commands/dir-add-folder/index.js b/lib/commands/dir-add-folder/index.js new file mode 100644 index 000000000..b90a2d622 --- /dev/null +++ b/lib/commands/dir-add-folder/index.js @@ -0,0 +1,85 @@ +const commandUtil = require('../../../bin/util'), + path = require('path'), + fs = require('fs'), + dirUtils = require('../dir-utils'); + +/* + @param {Command} - An commander Command instance to which this command is added +*/ +function cliSetup (program) { + return program + .command('dir-add-folder ') + .description('Add a folder to directory based Postman collection in the given path') + .usage(' [options]') + .option('-f, --force-overwrite', 'overwrite if test already exists', false); +} + +function action (folderPath, command) { + const options = commandUtil.commanderToObject(command), + parentDir = path.dirname(folderPath), + trimmedFolderPath = folderPath.replace(/\/+$/, ''), + folderPathBaseName = path.basename(folderPath), + parentMetaFilePath = path.join(parentDir, '.meta.json'), + metaFilePath = path.join(folderPath, '.meta.json'); + + // check if test with same name exists when forceOverwrite is false + if (!options.forceOverwrite) { + dirUtils.assertDirectoryAbsence(folderPath); + } + + // clean-up if directory already exists + fs.rmSync(`${folderPath}`, { recursive: true, force: true }); + + // check if folderPath's parent is already a collection folder + dirUtils.assertCollectionDir(parentDir); + + // copy request, response, event files + try { + dirUtils.createDir(trimmedFolderPath); + } + catch (e) { + console.error(`Could not create folder at ${folderPath}`); + process.exit(-1); + } + + // add new test to parent's .meta.json + try { + fs.accessSync(parentMetaFilePath, fs.constants.R_OK); + let meta = JSON.parse(fs.readFileSync(parentMetaFilePath)), + childrenOrder = meta.childrenOrder; + + if (!childrenOrder.includes(folderPathBaseName)) { + childrenOrder.push(folderPathBaseName); + } + + meta = { + childrenOrder + }; + + dirUtils.createFile(parentMetaFilePath, JSON.stringify(meta, null, 2)); + } + catch (e) { + console.error(`Could not update ${parentMetaFilePath} with new request ${folderPath}: ${e}`); + fs.rmSync(`${folderPath}`, { recursive: true, force: true }); + process.exit(-1); + } + + // add .meta.json to new folder + try { + let meta = { + childrenOrder: [] + }; + + dirUtils.createFile(metaFilePath, JSON.stringify(meta, null, 2)); + } + catch (e) { + console.error(`Could not create ${metaFilePath} under new folder ${folderPath}: ${e}`); + fs.rmSync(`${folderPath}`, { recursive: true, force: true }); + process.exit(-1); + } +} + +module.exports = { + cliSetup, + action +}; diff --git a/lib/commands/dir-add-test/index.js b/lib/commands/dir-add-test/index.js index ec3556f9b..ee54851e0 100644 --- a/lib/commands/dir-add-test/index.js +++ b/lib/commands/dir-add-test/index.js @@ -19,8 +19,8 @@ function cliSetup (program) { function action (testPath, command) { const options = commandUtil.commanderToObject(command), methodTemplateMap = { - GET: './lib/commands/dir-add-test/templates/GET template', - POST: './lib/commands/dir-add-test/templates/POST body template' + GET: __dirname + '/templates/GET template', + POST: __dirname + '/templates/POST body template' }, parentDir = path.dirname(testPath), trimmedTestPath = testPath.replace(/\/+$/, ''), diff --git a/lib/commands/dir-create/index.js b/lib/commands/dir-create/index.js new file mode 100644 index 000000000..11b576df9 --- /dev/null +++ b/lib/commands/dir-create/index.js @@ -0,0 +1,40 @@ +const commandUtil = require('../../../bin/util'), + fs = require('fs'), + dirUtils = require('../dir-utils'); + +/* + @param {Command} - An commander Command instance to which this command is added +*/ +function cliSetup (program) { + return program + .command('dir-create ') + .description('Create a directory based Postman collection in the given path') + .usage(' [options]') + .option('-f, --force-overwrite', 'overwrite if collection directory already exists', false); +} + +function action (collectionPath, command) { + const options = commandUtil.commanderToObject(command), + trimmedCollectionPath = collectionPath.replace(/\/+$/, ''), + collectionTemplate = __dirname + '/templates/Sample Postman Collection'; + + // check if test with same name exists when forceOverwrite is false + if (!options.forceOverwrite) { + dirUtils.assertDirectoryAbsence(collectionPath); + } + + // copy from template + try { + fs.cpSync(collectionTemplate, trimmedCollectionPath, { recursive: true }); + } + catch (e) { + console.error(`Could not copy from template at ${collectionTemplate}`); + fs.rmSync(`${collectionPath}`, { recursive: true, force: true }); + process.exit(-1); + } +} + +module.exports = { + cliSetup, + action +}; diff --git a/lib/commands/dir-create/templates/Sample Postman Collection/.info.json b/lib/commands/dir-create/templates/Sample Postman Collection/.info.json new file mode 100644 index 000000000..8bedbe2fd --- /dev/null +++ b/lib/commands/dir-create/templates/Sample Postman Collection/.info.json @@ -0,0 +1,7 @@ +{ + "info": { + "name": "Sample Postman Collection", + "schema": "https://schema.getpostman.com/json/collection/v2.0.0/collection.json", + "description": "A sample collection to demonstrate collections as a set of related requests" + } +} diff --git a/lib/commands/dir-create/templates/Sample Postman Collection/.meta.json b/lib/commands/dir-create/templates/Sample Postman Collection/.meta.json new file mode 100644 index 000000000..6984bf8cf --- /dev/null +++ b/lib/commands/dir-create/templates/Sample Postman Collection/.meta.json @@ -0,0 +1,7 @@ +{ + "childrenOrder": [ + "A simple GET request", + "A simple POST request", + "A simple POST request with JSON body" + ] +} diff --git a/lib/commands/dir-create/templates/Sample Postman Collection/A simple GET request/.event.meta.json b/lib/commands/dir-create/templates/Sample Postman Collection/A simple GET request/.event.meta.json new file mode 100644 index 000000000..688c85746 --- /dev/null +++ b/lib/commands/dir-create/templates/Sample Postman Collection/A simple GET request/.event.meta.json @@ -0,0 +1,5 @@ +{ + "eventOrder": [ + "event.test.js" + ] +} diff --git a/lib/commands/dir-create/templates/Sample Postman Collection/A simple GET request/event.test.js b/lib/commands/dir-create/templates/Sample Postman Collection/A simple GET request/event.test.js new file mode 100644 index 000000000..9c3bee193 --- /dev/null +++ b/lib/commands/dir-create/templates/Sample Postman Collection/A simple GET request/event.test.js @@ -0,0 +1,10 @@ +/* global pm */ + +pm.test('expect response be 200', function () { + pm.response.to.be.ok; +}); + +pm.test('expect response json contain args', function () { + pm.expect(pm.response.json().args).to.have.property('source') + .and.equal('newman-sample-github-collection'); +}); diff --git a/lib/commands/dir-create/templates/Sample Postman Collection/A simple GET request/request.json b/lib/commands/dir-create/templates/Sample Postman Collection/A simple GET request/request.json new file mode 100644 index 000000000..90660d2d0 --- /dev/null +++ b/lib/commands/dir-create/templates/Sample Postman Collection/A simple GET request/request.json @@ -0,0 +1,4 @@ +{ + "url": "https://postman-echo.com/get?source=newman-sample-github-collection", + "method": "GET" +} diff --git a/lib/commands/dir-create/templates/Sample Postman Collection/A simple POST request with JSON body/request.json b/lib/commands/dir-create/templates/Sample Postman Collection/A simple POST request with JSON body/request.json new file mode 100644 index 000000000..5b87e25f9 --- /dev/null +++ b/lib/commands/dir-create/templates/Sample Postman Collection/A simple POST request with JSON body/request.json @@ -0,0 +1,16 @@ +{ + "url": "https://postman-echo.com/post", + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw_json_formatted": { + "text": "Duis posuere augue vel cursus pharetra. In luctus a ex nec pretium..." + } + } +} diff --git a/lib/commands/dir-create/templates/Sample Postman Collection/A simple POST request/request.json b/lib/commands/dir-create/templates/Sample Postman Collection/A simple POST request/request.json new file mode 100644 index 000000000..07e823215 --- /dev/null +++ b/lib/commands/dir-create/templates/Sample Postman Collection/A simple POST request/request.json @@ -0,0 +1,14 @@ +{ + "url": "https://postman-echo.com/post", + "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..." + } +} diff --git a/lib/commands/dir-remove-folder/index.js b/lib/commands/dir-remove-folder/index.js new file mode 100644 index 000000000..720575d4a --- /dev/null +++ b/lib/commands/dir-remove-folder/index.js @@ -0,0 +1,56 @@ +const path = require('path'), + fs = require('fs'), + dirUtils = require('../dir-utils'); + +/* + @param {Command} - An commander Command instance to which this command is added +*/ +function cliSetup (program) { + return program + .command('dir-remove-folder ') + .description('Remove test at given path from directory based Postman collection') + .usage(''); +} + +function action (folderPath) { + const parentDir = path.dirname(folderPath), + folderPathBaseName = path.basename(folderPath), + metaFilePath = path.join(parentDir, '.meta.json'); + + // check if folderPath's parent is already a collection folder + dirUtils.assertCollectionDir(parentDir); + + // remove directory + try { + fs.rmSync(folderPath, { recursive: true, force: true }); + } + catch (e) { + console.error(`Could not delete folder at ${folderPath}, please check permissions`); + process.exit(-1); + } + + // remove folder from parent's .meta.json + try { + fs.accessSync(metaFilePath, fs.constants.R_OK); + let meta = JSON.parse(fs.readFileSync(metaFilePath)), + childrenOrder = meta.childrenOrder; + + childrenOrder = childrenOrder.filter((item) => { return item !== folderPathBaseName; }); + + meta = { + childrenOrder + }; + + dirUtils.createFile(metaFilePath, JSON.stringify(meta, null, 2)); + } + catch (e) { + console.error(`Could not update ${metaFilePath} with ${folderPath} removed: ${e}`); + fs.rmSync(folderPath, { recursive: true, force: true }); + process.exit(-1); + } +} + +module.exports = { + cliSetup, + action +}; diff --git a/lib/commands/dir-remove-test/index.js b/lib/commands/dir-remove-test/index.js index fd3f960f3..bcd725818 100644 --- a/lib/commands/dir-remove-test/index.js +++ b/lib/commands/dir-remove-test/index.js @@ -20,7 +20,7 @@ function action (testPath) { // check if testPath's parent is already a collection folder dirUtils.assertCollectionDir(parentDir); - // copy request, response, event files + // remove directory try { fs.rmSync(testPath, { recursive: true, force: true }); } diff --git a/lib/commands/dir-utils.js b/lib/commands/dir-utils.js index 97360bb3e..04547e804 100644 --- a/lib/commands/dir-utils.js +++ b/lib/commands/dir-utils.js @@ -164,8 +164,8 @@ const os = require('os'), createDir(itemDir); let requestFileName = `${itemDir}/request.json`, isJsonRequest = thing.request.header ? thing.request.header.findIndex((element) => { - return (element.key === 'Content-Type' && element.value === 'application/json') - }) : -1; + return (element.key === 'Content-Type' && element.value === 'application/json'); + }) : -1; // we have a body in the request if (isJsonRequest > -1 && thing.request.body && thing.request.body.raw) { @@ -173,7 +173,8 @@ const os = require('os'), try { thing.request.body.raw_json_formatted = JSON.parse(thing.request.body.raw); delete thing.request.body.raw; - } catch(e) { + } + catch (e) { console.warn(`Unable to parse raw body for ${name}`); } } diff --git a/lib/commands/index.js b/lib/commands/index.js index 472a93a08..ce3db47cc 100644 --- a/lib/commands/index.js +++ b/lib/commands/index.js @@ -1,8 +1,11 @@ const commands = [ + 'dir-add-folder', 'dir-add-test', + 'dir-create', 'dir-export', 'dir-export-import-test', 'dir-import', + 'dir-remove-folder', 'dir-remove-test', 'dir-run', 'run' diff --git a/test/cli/dir-commands.test.js b/test/cli/dir-commands.test.js index 590d4ea98..af1a75709 100644 --- a/test/cli/dir-commands.test.js +++ b/test/cli/dir-commands.test.js @@ -1,10 +1,8 @@ -const fs = require('fs'); - describe('CLI dir command options', function () { it('should work correctly without any extra options', function (done) { exec('node ./bin/newman.js dir-export examples/sample-collection.json'); // TODO - move to temp dir - exec('rm -rf ./Sample\ Postman\ Collection', done); + exec('rm -rf ./Sample Postman Collection', done); }); it('should be able to run dir-export-import-test correctly without any extra options', function (done) { From 347e03a42d8931c218193579d7eedacf06262865 Mon Sep 17 00:00:00 2001 From: Natarajan Kannan Date: Tue, 29 Aug 2023 14:28:00 +0530 Subject: [PATCH 15/25] fix: handle errors gracefully when executing dir-run --- lib/commands/dir-run/index.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/commands/dir-run/index.js b/lib/commands/dir-run/index.js index 3a3977339..deb3c175a 100644 --- a/lib/commands/dir-run/index.js +++ b/lib/commands/dir-run/index.js @@ -20,9 +20,17 @@ function cliSetup (program) { } function action (collectionDir, command, program) { - let collectionJson = dirUtils.dirTreeToCollectionJson(collectionDir), - tempDir = dirUtils.createTempDir(), - collectionFile = `${tempDir}/collection.json`; + let tempDir = dirUtils.createTempDir(), + collectionFile = `${tempDir}/collection.json`, + collectionJson; + + try { + collectionJson = dirUtils.dirTreeToCollectionJson(collectionDir); + } catch (e) { + console.error(`error: Unable to convert directory to collection: ${e.message}`); + fs.rmSync(tempDir, { recursive: true }); + process.exit(-1); + } dirUtils.createFile(collectionFile, JSON.stringify(collectionJson, null, 2)); From 0d4ece2575c8c56a52173dfb5557d92af972882e Mon Sep 17 00:00:00 2001 From: Natarajan Kannan Date: Wed, 30 Aug 2023 17:27:07 +0530 Subject: [PATCH 16/25] fix: handle top level event js files --- lib/commands/dir-add-test/index.js | 4 +- lib/commands/dir-create/index.js | 3 +- lib/commands/dir-run/index.js | 3 +- lib/commands/dir-utils.js | 87 ++++++++++++++++++------------ 4 files changed, 58 insertions(+), 39 deletions(-) diff --git a/lib/commands/dir-add-test/index.js b/lib/commands/dir-add-test/index.js index ee54851e0..be3065590 100644 --- a/lib/commands/dir-add-test/index.js +++ b/lib/commands/dir-add-test/index.js @@ -19,8 +19,8 @@ function cliSetup (program) { function action (testPath, command) { const options = commandUtil.commanderToObject(command), methodTemplateMap = { - GET: __dirname + '/templates/GET template', - POST: __dirname + '/templates/POST body template' + GET: path.join(__dirname, '/templates/GET template'), + POST: path.join(__dirname, '/templates/POST body template') }, parentDir = path.dirname(testPath), trimmedTestPath = testPath.replace(/\/+$/, ''), diff --git a/lib/commands/dir-create/index.js b/lib/commands/dir-create/index.js index 11b576df9..d596d2477 100644 --- a/lib/commands/dir-create/index.js +++ b/lib/commands/dir-create/index.js @@ -1,5 +1,6 @@ const commandUtil = require('../../../bin/util'), fs = require('fs'), + path = require('path'), dirUtils = require('../dir-utils'); /* @@ -16,7 +17,7 @@ function cliSetup (program) { function action (collectionPath, command) { const options = commandUtil.commanderToObject(command), trimmedCollectionPath = collectionPath.replace(/\/+$/, ''), - collectionTemplate = __dirname + '/templates/Sample Postman Collection'; + collectionTemplate = path.join(__dirname, '/templates/Sample Postman Collection'); // check if test with same name exists when forceOverwrite is false if (!options.forceOverwrite) { diff --git a/lib/commands/dir-run/index.js b/lib/commands/dir-run/index.js index deb3c175a..4e3ac1ff1 100644 --- a/lib/commands/dir-run/index.js +++ b/lib/commands/dir-run/index.js @@ -26,7 +26,8 @@ function action (collectionDir, command, program) { try { collectionJson = dirUtils.dirTreeToCollectionJson(collectionDir); - } catch (e) { + } + catch (e) { console.error(`error: Unable to convert directory to collection: ${e.message}`); fs.rmSync(tempDir, { recursive: true }); process.exit(-1); diff --git a/lib/commands/dir-utils.js b/lib/commands/dir-utils.js index 04547e804..cd50d09f0 100644 --- a/lib/commands/dir-utils.js +++ b/lib/commands/dir-utils.js @@ -74,9 +74,47 @@ const os = require('os'), return name.replace(/\//g, '_slash_'); }, + // break down event attribute into individual JS code files + createEventFiles = (itemDir, event) => { + let eventOrder = [], + eventMeta; + + event.forEach((element) => { + let eventFileName = `event.${element.listen}.js`, + eventFilePath = `${itemDir}/${eventFileName}`; + + eventOrder.push(eventFileName); + createFile(eventFilePath, element.script.exec.join('\n'), { skipTrailingNewLine: true }); + }); + + eventMeta = { + eventOrder + }; + + createFile(`${itemDir}/.event.meta.json`, JSON.stringify(eventMeta, null, 2)); + }, + + checkAndCreateEventEntry = (name, path) => { + const eventMatches = name.match(/^event\.([^.]+)\.js$/); + let result = {}, + fileContent; + + if (eventMatches) { + fileContent = fs.readFileSync(path).toString(); + result = { + listen: eventMatches[1], + script: { + exec: fileContent.split('\n'), + type: 'text/javascript' + } + }; + } + + return result; + }, + topLevelFileKeys = { variable: 1, - event: 1, info: 1, auth: 1 }, @@ -85,8 +123,6 @@ const os = require('os'), let parent = './' + ancestors.join('/'), elementOrder = [], elementMap = {}, - eventOrder = [], - eventMeta, itemDir, meta, name = '', @@ -126,6 +162,11 @@ const os = require('os'), createFile(`${parent}/${name}/.${element}.json`, JSON.stringify(obj, null, 2)); } }); + + // create top level event javascript files + if (thing.event) { + createEventFiles(itemDir, thing.event); + } } // walk-through items @@ -182,21 +223,7 @@ const os = require('os'), createFile(requestFileName, JSON.stringify(thing.request, null, 2)); if (thing.event) { - eventOrder = []; - thing.event.forEach((element) => { - let eventFileName = `event.${element.listen}.js`, - eventFilePath = `${itemDir}/${eventFileName}`; - - eventOrder.push(eventFileName); - - createFile(eventFilePath, element.script.exec.join('\n'), { skipTrailingNewLine: true }); - }); - - eventMeta = { - eventOrder - }; - - createFile(`${itemDir}/.event.meta.json`, JSON.stringify(eventMeta, null, 2)); + createEventFiles(itemDir, thing.event); } if (thing.response) { @@ -210,11 +237,12 @@ const os = require('os'), walkDirTree = (dirTreeJson, level) => { // console.log(JSON.stringify(dirTreeJson, null, 2)); const { path, name, children, type } = dirTreeJson; - let eventMatches, - items, + let items, others, result = {}; + // console.log(`@@@@ Walking directory ${name}:${level}:${type}`); + if (level === 1) { // collect following top level keys // info @@ -235,6 +263,7 @@ const os = require('os'), switch (type) { case 'file': + // console.log(`@@@@ Leaf node ${name}:${level}:${type}`); switch (name) { case '.meta.json': break; @@ -253,19 +282,7 @@ const os = require('os'), }; break; default: - eventMatches = name.match(/^event\.([^.]+)\.js$/); - - if (eventMatches) { - let fileContent = fs.readFileSync(path).toString(); - - result = { - listen: eventMatches[1], - script: { - exec: fileContent.split('\n'), - type: 'text/javascript' - } - }; - } + result = checkAndCreateEventEntry(name, path); break; } break; @@ -273,6 +290,7 @@ const os = require('os'), items = []; others = {}; + // console.log(`@@@@ Getting into directory ${name}:${level}:${type}`); // top level name is part of info key if (level !== 0) { result.name = name; @@ -308,8 +326,7 @@ const os = require('os'), let output = walkDirTree(child, level + 1); if (child.type === 'file') { - // handle event files outside of top-level directory separately - if (child.name.match(/event/) && level > 0) { + if (child.name.match(/event/)) { if (!result.event) { result.event = []; } From 5332ceaf1879e4239839fba16555ab1778b0f6f7 Mon Sep 17 00:00:00 2001 From: Natarajan Kannan Date: Wed, 30 Aug 2023 17:43:23 +0530 Subject: [PATCH 17/25] fix: handle body lang type for json formatting --- lib/commands/dir-utils.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/commands/dir-utils.js b/lib/commands/dir-utils.js index cd50d09f0..90b41be95 100644 --- a/lib/commands/dir-utils.js +++ b/lib/commands/dir-utils.js @@ -204,12 +204,14 @@ const os = require('os'), */ createDir(itemDir); let requestFileName = `${itemDir}/request.json`, - isJsonRequest = thing.request.header ? thing.request.header.findIndex((element) => { + contentTypeJsonHeaderIndex = thing.request.header ? thing.request.header.findIndex((element) => { return (element.key === 'Content-Type' && element.value === 'application/json'); - }) : -1; + }) : -1, + isBodyLangJson = (thing.request.body && thing.request.body.options && + thing.request.body.options.raw && thing.request.body.options.raw.language === 'json'); // we have a body in the request - if (isJsonRequest > -1 && thing.request.body && thing.request.body.raw) { + if ((contentTypeJsonHeaderIndex > -1 || isBodyLangJson) && (thing.request.body && thing.request.body.raw)) { // add raw post body as formatted JSON, will be removed in the import try { thing.request.body.raw_json_formatted = JSON.parse(thing.request.body.raw); From 2e3527259cffabed91376bd47474dcc597f2a035 Mon Sep 17 00:00:00 2001 From: Natarajan Kannan Date: Wed, 13 Sep 2023 11:36:48 +0530 Subject: [PATCH 18/25] ci: fix json payload in template for add test --- .../dir-add-test/templates/POST body template/request.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/commands/dir-add-test/templates/POST body template/request.json b/lib/commands/dir-add-test/templates/POST body template/request.json index c4758493f..5b87e25f9 100644 --- a/lib/commands/dir-add-test/templates/POST body template/request.json +++ b/lib/commands/dir-add-test/templates/POST body template/request.json @@ -9,6 +9,8 @@ ], "body": { "mode": "raw", - "raw": "{\"text\":\"Duis posuere augue vel cursus pharetra. In luctus a ex nec pretium...\"}" + "raw_json_formatted": { + "text": "Duis posuere augue vel cursus pharetra. In luctus a ex nec pretium..." + } } } From 7106e194c15d49d066fa09d9a2f18b2238f3dba8 Mon Sep 17 00:00:00 2001 From: Natarajan Kannan Date: Fri, 6 Oct 2023 16:19:25 +0530 Subject: [PATCH 19/25] refactor: rename test to request across --- DIR_COMMANDS.md | 12 +- .../index.js | 34 +- .../templates/GET template/.event.meta.json | 0 .../GET template/event.prerequest.js | 0 .../templates/GET template/event.test.js | 0 .../templates/GET template/request.json | 0 .../POST body template/.event.meta.json | 0 .../POST body template/event.prerequest.js | 0 .../POST body template/event.test.js | 0 .../templates/POST body template/request.json | 0 .../index.js | 24 +- lib/commands/index.js | 4 +- package-lock.json | 306 ++++++++++++++++-- 13 files changed, 325 insertions(+), 55 deletions(-) rename lib/commands/{dir-add-test => dir-add-request}/index.js (61%) rename lib/commands/{dir-add-test => dir-add-request}/templates/GET template/.event.meta.json (100%) rename lib/commands/{dir-add-test => dir-add-request}/templates/GET template/event.prerequest.js (100%) rename lib/commands/{dir-add-test => dir-add-request}/templates/GET template/event.test.js (100%) rename lib/commands/{dir-add-test => dir-add-request}/templates/GET template/request.json (100%) rename lib/commands/{dir-add-test => dir-add-request}/templates/POST body template/.event.meta.json (100%) rename lib/commands/{dir-add-test => dir-add-request}/templates/POST body template/event.prerequest.js (100%) rename lib/commands/{dir-add-test => dir-add-request}/templates/POST body template/event.test.js (100%) rename lib/commands/{dir-add-test => dir-add-request}/templates/POST body template/request.json (100%) rename lib/commands/{dir-remove-test => dir-remove-request}/index.js (55%) diff --git a/DIR_COMMANDS.md b/DIR_COMMANDS.md index b9036e0cc..f798a4678 100644 --- a/DIR_COMMANDS.md +++ b/DIR_COMMANDS.md @@ -90,13 +90,13 @@ Options: Commands: dir-add-folder [options] Add a folder to directory based Postman collection in the given path - dir-add-test [options] Add a test to directory based Postman collection in the given path + dir-add-request [options] Add a test to directory based Postman collection in the given path dir-create [options] Create a directory based Postman collection in the given path dir-export [options] Convert a Postman collection file into its directory representation dir-export-import-test [options] Check if an export followed by import results in same collection dir-import [options] Convert a Postman directory representation into a postman collection dir-remove-folder Remove test at given path from directory based Postman collection - dir-remove-test Remove test at given path from directory based Postman collection + dir-remove-request Remove test at given path from directory based Postman collection dir-run [options] Runs the tests in collection-dir, with all the provided options run [options] Initiate a Postman Collection run from a given URL or path @@ -181,9 +181,9 @@ newman dir-add-folder examples/Sample\ Postman\ Collection/folder1 ``` -### Add a test under a directory +### Add a request under a directory ``` -newman dir-add-test examples/Sample\ Postman\ Collection/test4 +newman dir-add-request examples/Sample\ Postman\ Collection/test4 ``` This command adds the test to the end of the tests already present in the @@ -196,7 +196,7 @@ can be changed by re-ordering the tests in this file. newman dir-remove-folder examples/Sample\ Postman\ Collection/folder1 ``` -### Remove a test +### Remove a request ``` -newman dir-remove-test examples/Sample\ Postman\ Collection/test4 +newman dir-remove-request examples/Sample\ Postman\ Collection/test4 ``` diff --git a/lib/commands/dir-add-test/index.js b/lib/commands/dir-add-request/index.js similarity index 61% rename from lib/commands/dir-add-test/index.js rename to lib/commands/dir-add-request/index.js index be3065590..044f00422 100644 --- a/lib/commands/dir-add-test/index.js +++ b/lib/commands/dir-add-request/index.js @@ -9,30 +9,30 @@ const commandUtil = require('../../../bin/util'), */ function cliSetup (program) { return program - .command('dir-add-test ') - .description('Add a test to directory based Postman collection in the given path') - .usage(' [options]') + .command('dir-add-request ') + .description('Add a request to directory based Postman collection in the given path') + .usage(' [options]') .addOption(new Option('-t, --type ', 'HTTP Method template').choices(['GET', 'POST']).default('GET')) - .option('-f, --force-overwrite', 'overwrite if test already exists', false); + .option('-f, --force-overwrite', 'overwrite if request already exists', false); } -function action (testPath, command) { +function action (requestPath, command) { const options = commandUtil.commanderToObject(command), methodTemplateMap = { GET: path.join(__dirname, '/templates/GET template'), POST: path.join(__dirname, '/templates/POST body template') }, - parentDir = path.dirname(testPath), - trimmedTestPath = testPath.replace(/\/+$/, ''), - testPathBaseName = path.basename(testPath), + parentDir = path.dirname(requestPath), + trimmedTestPath = requestPath.replace(/\/+$/, ''), + requestPathBaseName = path.basename(requestPath), metaFilePath = path.join(parentDir, '.meta.json'); - // check if test with same name exists when forceOverwrite is false + // check if request with same name exists when forceOverwrite is false if (!options.forceOverwrite) { - dirUtils.assertDirectoryAbsence(testPath); + dirUtils.assertDirectoryAbsence(requestPath); } - // check if testPath's parent is already a collection folder + // check if requestPath's parent is already a collection folder dirUtils.assertCollectionDir(parentDir); // copy request, response, event files @@ -43,18 +43,18 @@ function action (testPath, command) { } catch (e) { console.error(`Could not copy from template at ${methodTemplateMap[options.type]}`); - fs.rmSync(`${testPath}`, { recursive: true, force: true }); + fs.rmSync(`${requestPath}`, { recursive: true, force: true }); process.exit(-1); } - // add new test to parent's .meta.json + // add new request to parent's .meta.json try { fs.accessSync(metaFilePath, fs.constants.R_OK); let meta = JSON.parse(fs.readFileSync(metaFilePath)), childrenOrder = meta.childrenOrder; - if (!childrenOrder.includes(testPathBaseName)) { - childrenOrder.push(testPathBaseName); + if (!childrenOrder.includes(requestPathBaseName)) { + childrenOrder.push(requestPathBaseName); } meta = { @@ -64,8 +64,8 @@ function action (testPath, command) { dirUtils.createFile(metaFilePath, JSON.stringify(meta, null, 2)); } catch (e) { - console.error(`Could not update ${metaFilePath} with new request ${testPath}: ${e}`); - fs.rmSync(`${testPath}`, { recursive: true, force: true }); + console.error(`Could not update ${metaFilePath} with new request ${requestPath}: ${e}`); + fs.rmSync(`${requestPath}`, { recursive: true, force: true }); process.exit(-1); } } diff --git a/lib/commands/dir-add-test/templates/GET template/.event.meta.json b/lib/commands/dir-add-request/templates/GET template/.event.meta.json similarity index 100% rename from lib/commands/dir-add-test/templates/GET template/.event.meta.json rename to lib/commands/dir-add-request/templates/GET template/.event.meta.json diff --git a/lib/commands/dir-add-test/templates/GET template/event.prerequest.js b/lib/commands/dir-add-request/templates/GET template/event.prerequest.js similarity index 100% rename from lib/commands/dir-add-test/templates/GET template/event.prerequest.js rename to lib/commands/dir-add-request/templates/GET template/event.prerequest.js diff --git a/lib/commands/dir-add-test/templates/GET template/event.test.js b/lib/commands/dir-add-request/templates/GET template/event.test.js similarity index 100% rename from lib/commands/dir-add-test/templates/GET template/event.test.js rename to lib/commands/dir-add-request/templates/GET template/event.test.js diff --git a/lib/commands/dir-add-test/templates/GET template/request.json b/lib/commands/dir-add-request/templates/GET template/request.json similarity index 100% rename from lib/commands/dir-add-test/templates/GET template/request.json rename to lib/commands/dir-add-request/templates/GET template/request.json diff --git a/lib/commands/dir-add-test/templates/POST body template/.event.meta.json b/lib/commands/dir-add-request/templates/POST body template/.event.meta.json similarity index 100% rename from lib/commands/dir-add-test/templates/POST body template/.event.meta.json rename to lib/commands/dir-add-request/templates/POST body template/.event.meta.json diff --git a/lib/commands/dir-add-test/templates/POST body template/event.prerequest.js b/lib/commands/dir-add-request/templates/POST body template/event.prerequest.js similarity index 100% rename from lib/commands/dir-add-test/templates/POST body template/event.prerequest.js rename to lib/commands/dir-add-request/templates/POST body template/event.prerequest.js diff --git a/lib/commands/dir-add-test/templates/POST body template/event.test.js b/lib/commands/dir-add-request/templates/POST body template/event.test.js similarity index 100% rename from lib/commands/dir-add-test/templates/POST body template/event.test.js rename to lib/commands/dir-add-request/templates/POST body template/event.test.js diff --git a/lib/commands/dir-add-test/templates/POST body template/request.json b/lib/commands/dir-add-request/templates/POST body template/request.json similarity index 100% rename from lib/commands/dir-add-test/templates/POST body template/request.json rename to lib/commands/dir-add-request/templates/POST body template/request.json diff --git a/lib/commands/dir-remove-test/index.js b/lib/commands/dir-remove-request/index.js similarity index 55% rename from lib/commands/dir-remove-test/index.js rename to lib/commands/dir-remove-request/index.js index bcd725818..92cca64da 100644 --- a/lib/commands/dir-remove-test/index.js +++ b/lib/commands/dir-remove-request/index.js @@ -7,25 +7,25 @@ const path = require('path'), */ function cliSetup (program) { return program - .command('dir-remove-test ') - .description('Remove test at given path from directory based Postman collection') - .usage(' [options]'); + .command('dir-remove-request ') + .description('Remove request at given path from directory based Postman collection') + .usage(' [options]'); } -function action (testPath) { - const parentDir = path.dirname(testPath), - testPathBaseName = path.basename(testPath), +function action (requestPath) { + const parentDir = path.dirname(requestPath), + requestPathBaseName = path.basename(requestPath), metaFilePath = path.join(parentDir, '.meta.json'); - // check if testPath's parent is already a collection folder + // check if requestPath's parent is already a collection folder dirUtils.assertCollectionDir(parentDir); // remove directory try { - fs.rmSync(testPath, { recursive: true, force: true }); + fs.rmSync(requestPath, { recursive: true, force: true }); } catch (e) { - console.error(`Could not delete test at ${testPath}, please check permissions`); + console.error(`Could not delete test at ${requestPath}, please check permissions`); process.exit(-1); } @@ -35,7 +35,7 @@ function action (testPath) { let meta = JSON.parse(fs.readFileSync(metaFilePath)), childrenOrder = meta.childrenOrder; - childrenOrder = childrenOrder.filter((item) => { return item !== testPathBaseName; }); + childrenOrder = childrenOrder.filter((item) => { return item !== requestPathBaseName; }); meta = { childrenOrder @@ -44,8 +44,8 @@ function action (testPath) { dirUtils.createFile(metaFilePath, JSON.stringify(meta, null, 2)); } catch (e) { - console.error(`Could not update ${metaFilePath} with ${testPath} removed: ${e}`); - fs.rmSync(testPath, { recursive: true, force: true }); + console.error(`Could not update ${metaFilePath} with ${requestPath} removed: ${e}`); + fs.rmSync(requestPath, { recursive: true, force: true }); process.exit(-1); } } diff --git a/lib/commands/index.js b/lib/commands/index.js index ce3db47cc..47385a22b 100644 --- a/lib/commands/index.js +++ b/lib/commands/index.js @@ -1,12 +1,12 @@ const commands = [ 'dir-add-folder', - 'dir-add-test', + 'dir-add-request', 'dir-create', 'dir-export', 'dir-export-import-test', 'dir-import', 'dir-remove-folder', - 'dir-remove-test', + 'dir-remove-request', 'dir-run', 'run' ]; diff --git a/package-lock.json b/package-lock.json index da6a03924..03cbfb9a5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,8 @@ "colors": "1.4.0", "commander": "11.0.0", "csv-parse": "4.16.3", + "directory-tree": "3.5.1", + "eventemitter3": "4.0.7", "filesize": "10.0.12", "liquid-json": "0.3.1", "lodash": "4.17.21", @@ -982,7 +984,6 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, "dependencies": { "color-convert": "^1.9.0" }, @@ -1039,6 +1040,14 @@ "sprintf-js": "~1.0.2" } }, + "node_modules/array-back": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz", + "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==", + "engines": { + "node": ">=6" + } + }, "node_modules/array-buffer-byte-length": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", @@ -1403,7 +1412,6 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -1526,7 +1534,6 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, "dependencies": { "color-name": "1.1.3" } @@ -1534,8 +1541,7 @@ "node_modules/color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, "node_modules/colors": { "version": "1.4.0", @@ -1556,6 +1562,50 @@ "node": ">= 0.8" } }, + "node_modules/command-line-args": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-5.2.1.tgz", + "integrity": "sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg==", + "dependencies": { + "array-back": "^3.1.0", + "find-replace": "^3.0.0", + "lodash.camelcase": "^4.3.0", + "typical": "^4.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/command-line-usage": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/command-line-usage/-/command-line-usage-6.1.3.tgz", + "integrity": "sha512-sH5ZSPr+7UStsloltmDh7Ce5fb8XPlHyoPzTpyyMuYCtervL65+ubVZ6Q61cFtFl62UyJlc8/JwERRbAFPUqgw==", + "dependencies": { + "array-back": "^4.0.2", + "chalk": "^2.4.2", + "table-layout": "^1.0.2", + "typical": "^5.2.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/command-line-usage/node_modules/array-back": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-4.0.2.tgz", + "integrity": "sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/command-line-usage/node_modules/typical": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", + "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", + "engines": { + "node": ">=8" + } + }, "node_modules/commander": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/commander/-/commander-11.0.0.tgz", @@ -1727,6 +1777,14 @@ "node": ">=6" } }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -1880,6 +1938,21 @@ "node": ">=8" } }, + "node_modules/directory-tree": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/directory-tree/-/directory-tree-3.5.1.tgz", + "integrity": "sha512-HqjZ49fDzUnKYUhHxVw9eKBqbQ+lL0v4kSBInlDlaktmLtGoV9tC54a6A0ZfYeIrkMHWTE6MwwmUXP477+UEKQ==", + "dependencies": { + "command-line-args": "^5.2.0", + "command-line-usage": "^6.1.1" + }, + "bin": { + "directory-tree": "bin/index.js" + }, + "engines": { + "node": ">=10.0" + } + }, "node_modules/docker-modem": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-1.0.9.tgz", @@ -2165,7 +2238,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true, "engines": { "node": ">=0.8.0" } @@ -2566,6 +2638,11 @@ "node": ">=0.10.0" } }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -2686,6 +2763,17 @@ "url": "https://github.com/avajs/find-cache-dir?sponsor=1" } }, + "node_modules/find-replace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-3.0.0.tgz", + "integrity": "sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==", + "dependencies": { + "array-back": "^3.0.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -3088,7 +3176,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true, "engines": { "node": ">=4" } @@ -4084,6 +4171,11 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" + }, "node_modules/lodash.flattendeep": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", @@ -5552,6 +5644,14 @@ "node": ">=6.0.0" } }, + "node_modules/reduce-flatten": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-2.0.0.tgz", + "integrity": "sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w==", + "engines": { + "node": ">=6" + } + }, "node_modules/regexp-tree": { "version": "0.1.27", "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.27.tgz", @@ -6143,7 +6243,6 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, "dependencies": { "has-flag": "^3.0.0" }, @@ -6151,6 +6250,36 @@ "node": ">=4" } }, + "node_modules/table-layout": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-1.0.2.tgz", + "integrity": "sha512-qd/R7n5rQTRFi+Zf2sk5XVVd9UQl6ZkduPFC3S7WEGJAmetDTjY3qPN50eSKzwuzEyQKy5TN2TiZdkIjos2L6A==", + "dependencies": { + "array-back": "^4.0.1", + "deep-extend": "~0.6.0", + "typical": "^5.2.0", + "wordwrapjs": "^4.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/table-layout/node_modules/array-back": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-4.0.2.tgz", + "integrity": "sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/table-layout/node_modules/typical": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", + "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", + "engines": { + "node": ">=8" + } + }, "node_modules/tar-fs": { "version": "1.16.3", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-1.16.3.tgz", @@ -6406,6 +6535,14 @@ "is-typedarray": "^1.0.0" } }, + "node_modules/typical": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-4.0.0.tgz", + "integrity": "sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==", + "engines": { + "node": ">=8" + } + }, "node_modules/uglify-js": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.0.tgz", @@ -6632,6 +6769,26 @@ "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=" }, + "node_modules/wordwrapjs": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-4.0.1.tgz", + "integrity": "sha512-kKlNACbvHrkpIw6oPeYDSmdCTu2hdMHoyXLTcUKala++lx5Y+wjJ/e474Jqv5abnVmwxw08DiTuHmw69lJGksA==", + "dependencies": { + "reduce-flatten": "^2.0.0", + "typical": "^5.2.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/wordwrapjs/node_modules/typical": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", + "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", + "engines": { + "node": ">=8" + } + }, "node_modules/workerpool": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", @@ -7657,7 +7814,6 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, "requires": { "color-convert": "^1.9.0" } @@ -7702,6 +7858,11 @@ "sprintf-js": "~1.0.2" } }, + "array-back": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz", + "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==" + }, "array-buffer-byte-length": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", @@ -7998,7 +8159,6 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, "requires": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -8087,7 +8247,6 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, "requires": { "color-name": "1.1.3" } @@ -8095,8 +8254,7 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, "colors": { "version": "1.4.0", @@ -8111,6 +8269,40 @@ "delayed-stream": "~1.0.0" } }, + "command-line-args": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-5.2.1.tgz", + "integrity": "sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg==", + "requires": { + "array-back": "^3.1.0", + "find-replace": "^3.0.0", + "lodash.camelcase": "^4.3.0", + "typical": "^4.0.0" + } + }, + "command-line-usage": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/command-line-usage/-/command-line-usage-6.1.3.tgz", + "integrity": "sha512-sH5ZSPr+7UStsloltmDh7Ce5fb8XPlHyoPzTpyyMuYCtervL65+ubVZ6Q61cFtFl62UyJlc8/JwERRbAFPUqgw==", + "requires": { + "array-back": "^4.0.2", + "chalk": "^2.4.2", + "table-layout": "^1.0.2", + "typical": "^5.2.0" + }, + "dependencies": { + "array-back": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-4.0.2.tgz", + "integrity": "sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg==" + }, + "typical": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", + "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==" + } + } + }, "commander": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/commander/-/commander-11.0.0.tgz", @@ -8260,6 +8452,11 @@ "type-detect": "^4.0.0" } }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" + }, "deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -8371,6 +8568,15 @@ "path-type": "^4.0.0" } }, + "directory-tree": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/directory-tree/-/directory-tree-3.5.1.tgz", + "integrity": "sha512-HqjZ49fDzUnKYUhHxVw9eKBqbQ+lL0v4kSBInlDlaktmLtGoV9tC54a6A0ZfYeIrkMHWTE6MwwmUXP477+UEKQ==", + "requires": { + "command-line-args": "^5.2.0", + "command-line-usage": "^6.1.1" + } + }, "docker-modem": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-1.0.9.tgz", @@ -8609,8 +8815,7 @@ "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "eslint": { "version": "8.49.0", @@ -8882,6 +9087,11 @@ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true }, + "eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + }, "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -8975,6 +9185,14 @@ "pkg-dir": "^4.1.0" } }, + "find-replace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-3.0.0.tgz", + "integrity": "sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==", + "requires": { + "array-back": "^3.0.1" + } + }, "find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -9269,8 +9487,7 @@ "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" }, "has-property-descriptors": { "version": "1.0.0", @@ -9996,6 +10213,11 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" + }, "lodash.flattendeep": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", @@ -11131,6 +11353,11 @@ "minimatch": "^3.0.5" } }, + "reduce-flatten": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-2.0.0.tgz", + "integrity": "sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w==" + }, "regexp-tree": { "version": "0.1.27", "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.27.tgz", @@ -11586,11 +11813,33 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, "requires": { "has-flag": "^3.0.0" } }, + "table-layout": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-1.0.2.tgz", + "integrity": "sha512-qd/R7n5rQTRFi+Zf2sk5XVVd9UQl6ZkduPFC3S7WEGJAmetDTjY3qPN50eSKzwuzEyQKy5TN2TiZdkIjos2L6A==", + "requires": { + "array-back": "^4.0.1", + "deep-extend": "~0.6.0", + "typical": "^5.2.0", + "wordwrapjs": "^4.0.0" + }, + "dependencies": { + "array-back": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-4.0.2.tgz", + "integrity": "sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg==" + }, + "typical": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", + "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==" + } + } + }, "tar-fs": { "version": "1.16.3", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-1.16.3.tgz", @@ -11808,6 +12057,11 @@ "is-typedarray": "^1.0.0" } }, + "typical": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-4.0.0.tgz", + "integrity": "sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==" + }, "uglify-js": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.0.tgz", @@ -11989,6 +12243,22 @@ "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=" }, + "wordwrapjs": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-4.0.1.tgz", + "integrity": "sha512-kKlNACbvHrkpIw6oPeYDSmdCTu2hdMHoyXLTcUKala++lx5Y+wjJ/e474Jqv5abnVmwxw08DiTuHmw69lJGksA==", + "requires": { + "reduce-flatten": "^2.0.0", + "typical": "^5.2.0" + }, + "dependencies": { + "typical": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", + "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==" + } + } + }, "workerpool": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", From 74a3901215864326136ce59d76fad2e0b08e3719 Mon Sep 17 00:00:00 2001 From: Natarajan K Date: Fri, 6 Oct 2023 17:27:27 +0530 Subject: [PATCH 20/25] doc: Update DIR_COMMANDS.md --- DIR_COMMANDS.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/DIR_COMMANDS.md b/DIR_COMMANDS.md index f798a4678..7fc89e5d7 100644 --- a/DIR_COMMANDS.md +++ b/DIR_COMMANDS.md @@ -21,12 +21,12 @@ the following workflow to make changes to it and push is upstream: There are a few challenges with the above workflow: -* Collection file diffs are very hard to review in pull requests - * e.g. [PR containing payload - diffs](https://github.com/juspay/hyperswitch/pull/1948/files) - * e.g. test javascript code is maintained as an array of lines in the - collection JSON format which makes test changes hard to review -* Developers cannot use their favourite editor to make changes, add/remove new tests +1. Collection file diffs are very hard to review in pull requests + * Old format collection diff: https://github.com/juspay/hyperswitch/commit/7e29adb5c9dee8b03ef58ccbd85b07b106459380 + ![Screenshot 2023-10-06 at 5 24 35 PM](https://github.com/knutties/newman/assets/77204/d4c67bb2-59fa-4938-be4a-68f8159584a1) + * New format collection diff: https://github.com/juspay/hyperswitch/pull/2117/files + ![Screenshot 2023-10-06 at 5 25 47 PM](https://github.com/knutties/newman/assets/77204/1c3798c6-84c0-4589-9e5e-ad067d62bb9f) +1. Developers cannot use their favourite editor to make changes, add/remove new tests As a consequence of the above test maintenance suffers and tests often become stale. From d706aa8b838ea9a23d1ea6a18fb6e93e7f14ab0a Mon Sep 17 00:00:00 2001 From: Natarajan K Date: Fri, 6 Oct 2023 18:36:46 +0530 Subject: [PATCH 21/25] ci: Update DIR_COMMANDS.md --- DIR_COMMANDS.md | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/DIR_COMMANDS.md b/DIR_COMMANDS.md index 7fc89e5d7..7a33566b8 100644 --- a/DIR_COMMANDS.md +++ b/DIR_COMMANDS.md @@ -1,7 +1,5 @@ -This document introduces the user to the `dir-*` commands that are added in -this fork to facilitate CLI based creation, maintenance and review of Postman collections. +# Splitting Postman collection into directories - a better storage format -## Challenges in maintaining Postman collection in code repositories Postman collections are created using the Postman UI application. These collections are represented as a [JSON file](https://learning.postman.com/collection-format/getting-started/structure-of-a-collection/) @@ -28,7 +26,7 @@ There are a few challenges with the above workflow: ![Screenshot 2023-10-06 at 5 25 47 PM](https://github.com/knutties/newman/assets/77204/1c3798c6-84c0-4589-9e5e-ad067d62bb9f) 1. Developers cannot use their favourite editor to make changes, add/remove new tests -As a consequence of the above test maintenance suffers and tests often become stale. +As a consequence of the above, test maintenance suffers and tests often become stale. ## Solution to the above challenges @@ -68,6 +66,17 @@ examples/Sample Postman Collection    └── response.json # the response json ``` +### Next steps +This concept of representing Postman collections as a directory opens up programmatic +pre-processing of test data before running tests. This could include things like the following: + +* Re-using same data across tests +* Using other node libraries in testing code + +The instructions for using the tool are given below. Please give it spin and let me know it you if it is useful. + +## Usage + ### Installing newman from this fork One can install the newman executable in this fork using the command: @@ -90,13 +99,13 @@ Options: Commands: dir-add-folder [options] Add a folder to directory based Postman collection in the given path - dir-add-request [options] Add a test to directory based Postman collection in the given path + dir-add-request [options] Add a request to directory based Postman collection in the given path dir-create [options] Create a directory based Postman collection in the given path dir-export [options] Convert a Postman collection file into its directory representation dir-export-import-test [options] Check if an export followed by import results in same collection dir-import [options] Convert a Postman directory representation into a postman collection dir-remove-folder Remove test at given path from directory based Postman collection - dir-remove-request Remove test at given path from directory based Postman collection + dir-remove-request Remove request at given path from directory based Postman collection dir-run [options] Runs the tests in collection-dir, with all the provided options run [options] Initiate a Postman Collection run from a given URL or path From 1376f3535c869b1da612ecaf62dd393fc936c35f Mon Sep 17 00:00:00 2001 From: Natarajan Kannan Date: Sat, 7 Oct 2023 10:11:53 +0530 Subject: [PATCH 22/25] refactor: rename test to request across --- DIR_COMMANDS.md | 6 +++--- lib/commands/dir-remove-folder/index.js | 2 +- lib/commands/dir-remove-request/index.js | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/DIR_COMMANDS.md b/DIR_COMMANDS.md index 7a33566b8..710528f36 100644 --- a/DIR_COMMANDS.md +++ b/DIR_COMMANDS.md @@ -67,8 +67,8 @@ examples/Sample Postman Collection ``` ### Next steps -This concept of representing Postman collections as a directory opens up programmatic -pre-processing of test data before running tests. This could include things like the following: +This concept of representing Postman collections as a directory opens up programmatic +pre-processing of test data before running tests. This could include things like the following: * Re-using same data across tests * Using other node libraries in testing code @@ -104,7 +104,7 @@ Commands: dir-export [options] Convert a Postman collection file into its directory representation dir-export-import-test [options] Check if an export followed by import results in same collection dir-import [options] Convert a Postman directory representation into a postman collection - dir-remove-folder Remove test at given path from directory based Postman collection + dir-remove-folder Remove folder at given path from directory based Postman collection dir-remove-request Remove request at given path from directory based Postman collection dir-run [options] Runs the tests in collection-dir, with all the provided options run [options] Initiate a Postman Collection run from a given URL or path diff --git a/lib/commands/dir-remove-folder/index.js b/lib/commands/dir-remove-folder/index.js index 720575d4a..81b19a94f 100644 --- a/lib/commands/dir-remove-folder/index.js +++ b/lib/commands/dir-remove-folder/index.js @@ -8,7 +8,7 @@ const path = require('path'), function cliSetup (program) { return program .command('dir-remove-folder ') - .description('Remove test at given path from directory based Postman collection') + .description('Remove folder at given path from directory based Postman collection') .usage(''); } diff --git a/lib/commands/dir-remove-request/index.js b/lib/commands/dir-remove-request/index.js index 92cca64da..7b505e2e3 100644 --- a/lib/commands/dir-remove-request/index.js +++ b/lib/commands/dir-remove-request/index.js @@ -25,11 +25,11 @@ function action (requestPath) { fs.rmSync(requestPath, { recursive: true, force: true }); } catch (e) { - console.error(`Could not delete test at ${requestPath}, please check permissions`); + console.error(`Could not delete request at ${requestPath}, please check permissions`); process.exit(-1); } - // remove test from parent's .meta.json + // remove request from parent's .meta.json try { fs.accessSync(metaFilePath, fs.constants.R_OK); let meta = JSON.parse(fs.readFileSync(metaFilePath)), From 297601ecf67036c8c2d6ec54867cd38e3317bd62 Mon Sep 17 00:00:00 2001 From: Natarajan Kannan Date: Sat, 7 Oct 2023 11:30:06 +0530 Subject: [PATCH 23/25] refactor: rename dir-export-import-test to dir-export-import-check --- DIR_COMMANDS.md | 30 +++++++++---------- .../index.js | 2 +- lib/commands/index.js | 2 +- test/cli/dir-commands.test.js | 4 +-- 4 files changed, 19 insertions(+), 19 deletions(-) rename lib/commands/{dir-export-import-test => dir-export-import-check}/index.js (96%) diff --git a/DIR_COMMANDS.md b/DIR_COMMANDS.md index 710528f36..9d6b902c0 100644 --- a/DIR_COMMANDS.md +++ b/DIR_COMMANDS.md @@ -26,13 +26,13 @@ There are a few challenges with the above workflow: ![Screenshot 2023-10-06 at 5 25 47 PM](https://github.com/knutties/newman/assets/77204/1c3798c6-84c0-4589-9e5e-ad067d62bb9f) 1. Developers cannot use their favourite editor to make changes, add/remove new tests -As a consequence of the above, test maintenance suffers and tests often become stale. +As a consequence of the above, collection maintenance suffers and often become stale. ## Solution to the above challenges The core reason for challenges presented above is the file format in which the -Postman tests are maintained. A single JSON file that encapsulates the entire -test code and sequencing does not lend itself for easy reviewing and be an +Postman requests are maintained. A single JSON file that encapsulates the entire +request code and sequencing does not lend itself for easy reviewing and be an all-editor friendly format. This fork of newman attempts to address the challenge by representing the @@ -54,7 +54,7 @@ collection. ``` examples/Sample Postman Collection ├── .info.json # contains the info element from the top of collection json -├── .meta.json # contains the ordering of the test folders +├── .meta.json # contains the ordering of the request folders ├── .auth.json # contains the auth element from the top of the collection json ├── .variable.json # contains the variable element from the top of the collection json ├── .event.json # contains the event element from the top of the collection json @@ -68,10 +68,10 @@ examples/Sample Postman Collection ### Next steps This concept of representing Postman collections as a directory opens up programmatic -pre-processing of test data before running tests. This could include things like the following: +pre-processing of request data before running the requests. This could include things like the following: -* Re-using same data across tests -* Using other node libraries in testing code +* Re-using same data across requests +* Using other javascript libraries in testing code The instructions for using the tool are given below. Please give it spin and let me know it you if it is useful. @@ -86,7 +86,7 @@ npm install -g 'git+ssh://git@github.com:knutties/newman.git#feature/newman-dir' ``` The following no-arg run shows the new commands added to newman to help manage Postman -tests maintained as directories/files instead of a single json file. +requests maintained as directories/files instead of a single json file. ``` $ newman @@ -102,11 +102,11 @@ Commands: dir-add-request [options] Add a request to directory based Postman collection in the given path dir-create [options] Create a directory based Postman collection in the given path dir-export [options] Convert a Postman collection file into its directory representation - dir-export-import-test [options] Check if an export followed by import results in same collection + dir-export-import-check [options] Check if an export followed by import results in same collection dir-import [options] Convert a Postman directory representation into a postman collection dir-remove-folder Remove folder at given path from directory based Postman collection dir-remove-request Remove request at given path from directory based Postman collection - dir-run [options] Runs the tests in collection-dir, with all the provided options + dir-run [options] Runs the requests in collection-dir, with all the provided options run [options] Initiate a Postman Collection run from a given URL or path To get available options for a command: @@ -135,7 +135,7 @@ newman dir-import examples/Sample\ Postman\ Collection/ -o examples/sample-colle This command is typically used to test the tool itself to ensure it can handle all collection json use-cases. ``` -newman dir-export-import-test examples/sample-collection.json +newman dir-export-import-check examples/sample-collection.json ``` ### Execute a Postman collection stored in the directory format @@ -195,10 +195,10 @@ newman dir-add-folder examples/Sample\ Postman\ Collection/folder1 newman dir-add-request examples/Sample\ Postman\ Collection/test4 ``` -This command adds the test to the end of the tests already present in the -folder. The order of the tests is stored in a separate file called -[.meta.json](examples/Sample%20Postman%20Collection/.meta.json). The order of tests -can be changed by re-ordering the tests in this file. +This command adds the request to the end of the requests already present in the +folder. The order of the requests is stored in a separate file called +[.meta.json](examples/Sample%20Postman%20Collection/.meta.json). The order of requests +can be changed by re-ordering the requests in this file. ### Remove a folder ``` diff --git a/lib/commands/dir-export-import-test/index.js b/lib/commands/dir-export-import-check/index.js similarity index 96% rename from lib/commands/dir-export-import-test/index.js rename to lib/commands/dir-export-import-check/index.js index effadb0ce..cf6eb42be 100644 --- a/lib/commands/dir-export-import-test/index.js +++ b/lib/commands/dir-export-import-check/index.js @@ -9,7 +9,7 @@ const commandUtil = require('../../../bin/util'); */ function cliSetup (program) { return program - .command('dir-export-import-test ') + .command('dir-export-import-check ') .description('Check if an export followed by import results in same collection') .usage('') .option('-s, --substitute-slashes', 'if slashes are found in name field - substitute them'); diff --git a/lib/commands/index.js b/lib/commands/index.js index 47385a22b..9d8097e15 100644 --- a/lib/commands/index.js +++ b/lib/commands/index.js @@ -3,7 +3,7 @@ const commands = [ 'dir-add-request', 'dir-create', 'dir-export', - 'dir-export-import-test', + 'dir-export-import-check', 'dir-import', 'dir-remove-folder', 'dir-remove-request', diff --git a/test/cli/dir-commands.test.js b/test/cli/dir-commands.test.js index af1a75709..e1b43ad44 100644 --- a/test/cli/dir-commands.test.js +++ b/test/cli/dir-commands.test.js @@ -5,7 +5,7 @@ describe('CLI dir command options', function () { exec('rm -rf ./Sample Postman Collection', done); }); - it('should be able to run dir-export-import-test correctly without any extra options', function (done) { - exec('node ./bin/newman.js dir-export-import-test examples/sample-collection.json', done); + it('should be able to run dir-export-import-check correctly without any extra options', function (done) { + exec('node ./bin/newman.js dir-export-import-check examples/sample-collection.json', done); }); }); From f71f965365fb1d997fdda4f768c4fe74a5692c8d Mon Sep 17 00:00:00 2001 From: Natarajan Kannan Date: Sat, 7 Oct 2023 11:58:04 +0530 Subject: [PATCH 24/25] fix: fix lint issues --- npm/test-library.js | 6 ++++-- test/unit/cli.test.js | 3 +-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/npm/test-library.js b/npm/test-library.js index fbf8c7971..a0fca81b3 100755 --- a/npm/test-library.js +++ b/npm/test-library.js @@ -3,8 +3,10 @@ // This script is intended to execute all library tests. // --------------------------------------------------------------------------------------------------------------------- -const Mocha = require('mocha'), - path = require('path'), +const path = require('path'), + + colors = require('colors/safe'), + Mocha = require('mocha'), recursive = require('recursive-readdir'), SPEC_SOURCE_DIR = path.join('test', 'library'); diff --git a/test/unit/cli.test.js b/test/unit/cli.test.js index 4b6a552be..c45388cb9 100644 --- a/test/unit/cli.test.js +++ b/test/unit/cli.test.js @@ -1,5 +1,4 @@ -const _ = require('lodash'), - expect = require('chai').expect, +const expect = require('chai').expect; describe('cli parser', function () { var _ = require('lodash'), From 7bdb3067b77b39a04520411c6cd89fb998a33534 Mon Sep 17 00:00:00 2001 From: Natarajan Kannan Date: Sun, 8 Oct 2023 16:57:11 +0530 Subject: [PATCH 25/25] test: address test coverage --- .nycrc.js | 20 +++- DIR_COMMANDS.md | 4 +- lib/commands/dir-add-folder/index.js | 60 +----------- .../index.js | 2 +- .../Sample Postman Collection/.info.json | 0 .../Sample Postman Collection/.meta.json | 0 .../A simple GET request/.event.meta.json | 0 .../A simple GET request/event.test.js | 0 .../A simple GET request/request.json | 0 .../request.json | 0 .../A simple POST request/request.json | 0 lib/commands/dir-remove-folder/index.js | 40 +------- lib/commands/dir-utils.js | 98 +++++++++++++++++++ lib/commands/index.js | 2 +- test/cli/dir-commands.test.js | 58 +++++++++-- test/library/cookie-jar.test.js | 2 +- test/library/export-environment.test.js | 2 +- test/library/export-globals.test.js | 2 +- test/library/folder-variants.test.js | 2 +- test/library/iteration-count.test.js | 2 +- test/library/postman-api-key.test.js | 2 +- test/library/requestAgents.test.js | 2 +- test/library/run-options.test.js | 2 +- test/library/shallow-junit-reporter.test.js | 2 +- test/library/ssl-client-cert.test.js | 2 +- test/library/suppress-exit-code.test.js | 2 +- test/library/working-directory.test.js | 2 +- test/system/npm-publish.test.js | 4 +- test/unit/cli.test.js | 4 +- test/unit/dir-utils.test.js | 87 +++++++++++++++- 30 files changed, 276 insertions(+), 127 deletions(-) rename lib/commands/{dir-create => dir-collection-create}/index.js (95%) rename lib/commands/{dir-create => dir-collection-create}/templates/Sample Postman Collection/.info.json (100%) rename lib/commands/{dir-create => dir-collection-create}/templates/Sample Postman Collection/.meta.json (100%) rename lib/commands/{dir-create => dir-collection-create}/templates/Sample Postman Collection/A simple GET request/.event.meta.json (100%) rename lib/commands/{dir-create => dir-collection-create}/templates/Sample Postman Collection/A simple GET request/event.test.js (100%) rename lib/commands/{dir-create => dir-collection-create}/templates/Sample Postman Collection/A simple GET request/request.json (100%) rename lib/commands/{dir-create => dir-collection-create}/templates/Sample Postman Collection/A simple POST request with JSON body/request.json (100%) rename lib/commands/{dir-create => dir-collection-create}/templates/Sample Postman Collection/A simple POST request/request.json (100%) diff --git a/.nycrc.js b/.nycrc.js index 414da9e57..62ba72e2e 100644 --- a/.nycrc.js +++ b/.nycrc.js @@ -2,7 +2,17 @@ const TEST_TYPE = ((argv) => { let match = argv[argv.length - 1].match(/npm\/test-(\w+).js/); return match && match[1] || ''; -})(process.argv); +})(process.argv), + RUN_TEST_FILES = [ + 'lib/commands/run/*.js', + 'lib/reporter/*.js', + 'lib/config/*.js', + 'lib/print/*.js', + 'lib/*.js', + 'bin/**/*.js' + ]; + + function configOverrides (testType) { switch (testType) { @@ -12,20 +22,22 @@ function configOverrides (testType) { branches: 65, functions: 85, lines: 80 - }; + }; case 'integration': return { statements: 40, branches: 20, functions: 40, - lines: 40 + lines: 40, + include: RUN_TEST_FILES }; case 'library': return { statements: 55, branches: 40, functions: 55, - lines: 55 + lines: 55, + include: RUN_TEST_FILES }; case 'unit': return { diff --git a/DIR_COMMANDS.md b/DIR_COMMANDS.md index 9d6b902c0..4fde71a74 100644 --- a/DIR_COMMANDS.md +++ b/DIR_COMMANDS.md @@ -100,7 +100,7 @@ Options: Commands: dir-add-folder [options] Add a folder to directory based Postman collection in the given path dir-add-request [options] Add a request to directory based Postman collection in the given path - dir-create [options] Create a directory based Postman collection in the given path + dir-collection-create [options] Create a directory based Postman collection in the given path dir-export [options] Convert a Postman collection file into its directory representation dir-export-import-check [options] Check if an export followed by import results in same collection dir-import [options] Convert a Postman directory representation into a postman collection @@ -118,7 +118,7 @@ this fork of newman. ### Create a new directory based Postman collection ``` -newman dir-create new-dir-collection +newman dir-collection-create new-dir-collection ``` ### Generating directory representation for an existing collection diff --git a/lib/commands/dir-add-folder/index.js b/lib/commands/dir-add-folder/index.js index b90a2d622..c5ce96065 100644 --- a/lib/commands/dir-add-folder/index.js +++ b/lib/commands/dir-add-folder/index.js @@ -1,6 +1,4 @@ const commandUtil = require('../../../bin/util'), - path = require('path'), - fs = require('fs'), dirUtils = require('../dir-utils'); /* @@ -15,68 +13,14 @@ function cliSetup (program) { } function action (folderPath, command) { - const options = commandUtil.commanderToObject(command), - parentDir = path.dirname(folderPath), - trimmedFolderPath = folderPath.replace(/\/+$/, ''), - folderPathBaseName = path.basename(folderPath), - parentMetaFilePath = path.join(parentDir, '.meta.json'), - metaFilePath = path.join(folderPath, '.meta.json'); + const options = commandUtil.commanderToObject(command); // check if test with same name exists when forceOverwrite is false if (!options.forceOverwrite) { dirUtils.assertDirectoryAbsence(folderPath); } - // clean-up if directory already exists - fs.rmSync(`${folderPath}`, { recursive: true, force: true }); - - // check if folderPath's parent is already a collection folder - dirUtils.assertCollectionDir(parentDir); - - // copy request, response, event files - try { - dirUtils.createDir(trimmedFolderPath); - } - catch (e) { - console.error(`Could not create folder at ${folderPath}`); - process.exit(-1); - } - - // add new test to parent's .meta.json - try { - fs.accessSync(parentMetaFilePath, fs.constants.R_OK); - let meta = JSON.parse(fs.readFileSync(parentMetaFilePath)), - childrenOrder = meta.childrenOrder; - - if (!childrenOrder.includes(folderPathBaseName)) { - childrenOrder.push(folderPathBaseName); - } - - meta = { - childrenOrder - }; - - dirUtils.createFile(parentMetaFilePath, JSON.stringify(meta, null, 2)); - } - catch (e) { - console.error(`Could not update ${parentMetaFilePath} with new request ${folderPath}: ${e}`); - fs.rmSync(`${folderPath}`, { recursive: true, force: true }); - process.exit(-1); - } - - // add .meta.json to new folder - try { - let meta = { - childrenOrder: [] - }; - - dirUtils.createFile(metaFilePath, JSON.stringify(meta, null, 2)); - } - catch (e) { - console.error(`Could not create ${metaFilePath} under new folder ${folderPath}: ${e}`); - fs.rmSync(`${folderPath}`, { recursive: true, force: true }); - process.exit(-1); - } + dirUtils.createPostmanFolder(folderPath); } module.exports = { diff --git a/lib/commands/dir-create/index.js b/lib/commands/dir-collection-create/index.js similarity index 95% rename from lib/commands/dir-create/index.js rename to lib/commands/dir-collection-create/index.js index d596d2477..a8858e438 100644 --- a/lib/commands/dir-create/index.js +++ b/lib/commands/dir-collection-create/index.js @@ -8,7 +8,7 @@ const commandUtil = require('../../../bin/util'), */ function cliSetup (program) { return program - .command('dir-create ') + .command('dir-collection-create ') .description('Create a directory based Postman collection in the given path') .usage(' [options]') .option('-f, --force-overwrite', 'overwrite if collection directory already exists', false); diff --git a/lib/commands/dir-create/templates/Sample Postman Collection/.info.json b/lib/commands/dir-collection-create/templates/Sample Postman Collection/.info.json similarity index 100% rename from lib/commands/dir-create/templates/Sample Postman Collection/.info.json rename to lib/commands/dir-collection-create/templates/Sample Postman Collection/.info.json diff --git a/lib/commands/dir-create/templates/Sample Postman Collection/.meta.json b/lib/commands/dir-collection-create/templates/Sample Postman Collection/.meta.json similarity index 100% rename from lib/commands/dir-create/templates/Sample Postman Collection/.meta.json rename to lib/commands/dir-collection-create/templates/Sample Postman Collection/.meta.json diff --git a/lib/commands/dir-create/templates/Sample Postman Collection/A simple GET request/.event.meta.json b/lib/commands/dir-collection-create/templates/Sample Postman Collection/A simple GET request/.event.meta.json similarity index 100% rename from lib/commands/dir-create/templates/Sample Postman Collection/A simple GET request/.event.meta.json rename to lib/commands/dir-collection-create/templates/Sample Postman Collection/A simple GET request/.event.meta.json diff --git a/lib/commands/dir-create/templates/Sample Postman Collection/A simple GET request/event.test.js b/lib/commands/dir-collection-create/templates/Sample Postman Collection/A simple GET request/event.test.js similarity index 100% rename from lib/commands/dir-create/templates/Sample Postman Collection/A simple GET request/event.test.js rename to lib/commands/dir-collection-create/templates/Sample Postman Collection/A simple GET request/event.test.js diff --git a/lib/commands/dir-create/templates/Sample Postman Collection/A simple GET request/request.json b/lib/commands/dir-collection-create/templates/Sample Postman Collection/A simple GET request/request.json similarity index 100% rename from lib/commands/dir-create/templates/Sample Postman Collection/A simple GET request/request.json rename to lib/commands/dir-collection-create/templates/Sample Postman Collection/A simple GET request/request.json diff --git a/lib/commands/dir-create/templates/Sample Postman Collection/A simple POST request with JSON body/request.json b/lib/commands/dir-collection-create/templates/Sample Postman Collection/A simple POST request with JSON body/request.json similarity index 100% rename from lib/commands/dir-create/templates/Sample Postman Collection/A simple POST request with JSON body/request.json rename to lib/commands/dir-collection-create/templates/Sample Postman Collection/A simple POST request with JSON body/request.json diff --git a/lib/commands/dir-create/templates/Sample Postman Collection/A simple POST request/request.json b/lib/commands/dir-collection-create/templates/Sample Postman Collection/A simple POST request/request.json similarity index 100% rename from lib/commands/dir-create/templates/Sample Postman Collection/A simple POST request/request.json rename to lib/commands/dir-collection-create/templates/Sample Postman Collection/A simple POST request/request.json diff --git a/lib/commands/dir-remove-folder/index.js b/lib/commands/dir-remove-folder/index.js index 81b19a94f..60edadc58 100644 --- a/lib/commands/dir-remove-folder/index.js +++ b/lib/commands/dir-remove-folder/index.js @@ -1,6 +1,4 @@ -const path = require('path'), - fs = require('fs'), - dirUtils = require('../dir-utils'); +const dirUtils = require('../dir-utils'); /* @param {Command} - An commander Command instance to which this command is added @@ -13,41 +11,7 @@ function cliSetup (program) { } function action (folderPath) { - const parentDir = path.dirname(folderPath), - folderPathBaseName = path.basename(folderPath), - metaFilePath = path.join(parentDir, '.meta.json'); - - // check if folderPath's parent is already a collection folder - dirUtils.assertCollectionDir(parentDir); - - // remove directory - try { - fs.rmSync(folderPath, { recursive: true, force: true }); - } - catch (e) { - console.error(`Could not delete folder at ${folderPath}, please check permissions`); - process.exit(-1); - } - - // remove folder from parent's .meta.json - try { - fs.accessSync(metaFilePath, fs.constants.R_OK); - let meta = JSON.parse(fs.readFileSync(metaFilePath)), - childrenOrder = meta.childrenOrder; - - childrenOrder = childrenOrder.filter((item) => { return item !== folderPathBaseName; }); - - meta = { - childrenOrder - }; - - dirUtils.createFile(metaFilePath, JSON.stringify(meta, null, 2)); - } - catch (e) { - console.error(`Could not update ${metaFilePath} with ${folderPath} removed: ${e}`); - fs.rmSync(folderPath, { recursive: true, force: true }); - process.exit(-1); - } + dirUtils.removePostmanFolder(folderPath); } module.exports = { diff --git a/lib/commands/dir-utils.js b/lib/commands/dir-utils.js index 90b41be95..68bceb2a8 100644 --- a/lib/commands/dir-utils.js +++ b/lib/commands/dir-utils.js @@ -383,6 +383,102 @@ const os = require('os'), return collectionJson; }, + createPostmanFolder = function (folderPath) { + const parentDir = pathLib.dirname(folderPath), + trimmedFolderPath = folderPath.replace(/\/+$/, ''), + folderPathBaseName = pathLib.basename(folderPath), + parentMetaFilePath = pathLib.join(parentDir, '.meta.json'), + metaFilePath = pathLib.join(folderPath, '.meta.json'); + + // clean-up if directory already exists + fs.rmSync(`${folderPath}`, { recursive: true, force: true }); + + // check if folderPath's parent is already a collection folder + assertCollectionDir(parentDir); + + try { + createDir(trimmedFolderPath); + } + catch (e) { + console.error(`Could not create folder at ${folderPath}`); + process.exit(-1); + } + + // add new folder to parent's .meta.json + try { + fs.accessSync(parentMetaFilePath, fs.constants.R_OK); + let meta = JSON.parse(fs.readFileSync(parentMetaFilePath)), + childrenOrder = meta.childrenOrder; + + if (!childrenOrder.includes(folderPathBaseName)) { + childrenOrder.push(folderPathBaseName); + } + + meta = { + childrenOrder + }; + + createFile(parentMetaFilePath, JSON.stringify(meta, null, 2)); + } + catch (e) { + console.error(`Could not update ${parentMetaFilePath} with new request ${folderPath}: ${e}`); + fs.rmSync(`${folderPath}`, { recursive: true, force: true }); + process.exit(-1); + } + + // add .meta.json to new folder + try { + let meta = { + childrenOrder: [] + }; + + createFile(metaFilePath, JSON.stringify(meta, null, 2)); + } + catch (e) { + console.error(`Could not create ${metaFilePath} under new folder ${folderPath}: ${e}`); + fs.rmSync(`${folderPath}`, { recursive: true, force: true }); + process.exit(-1); + } + }, + + removePostmanFolder = function (folderPath) { + const parentDir = pathLib.dirname(folderPath), + folderPathBaseName = pathLib.basename(folderPath), + metaFilePath = pathLib.join(parentDir, '.meta.json'); + + // check if folderPath's parent is already a collection folder + assertCollectionDir(parentDir); + + // remove directory + try { + fs.rmSync(folderPath, { recursive: true, force: true }); + } + catch (e) { + console.error(`Could not delete folder at ${folderPath}, please check permissions`); + process.exit(-1); + } + + // remove folder from parent's .meta.json + try { + fs.accessSync(metaFilePath, fs.constants.R_OK); + let meta = JSON.parse(fs.readFileSync(metaFilePath)), + childrenOrder = meta.childrenOrder; + + childrenOrder = childrenOrder.filter((item) => { return item !== folderPathBaseName; }); + + meta = { + childrenOrder + }; + + createFile(metaFilePath, JSON.stringify(meta, null, 2)); + } + catch (e) { + console.error(`Could not update ${metaFilePath} with ${folderPath} removed: ${e}`); + fs.rmSync(folderPath, { recursive: true, force: true }); + process.exit(-1); + } + }, + createTempDir = function () { return fs.mkdtempSync(pathLib.join(os.tmpdir(), 'newman-')); }; @@ -394,8 +490,10 @@ module.exports = { assertFileExistence, createFile, createDir, + createPostmanFolder, createTempDir, dirTreeToCollectionJson, + removePostmanFolder, sanitizePathName, traverse, walkDirTree diff --git a/lib/commands/index.js b/lib/commands/index.js index 9d8097e15..c584c7bc0 100644 --- a/lib/commands/index.js +++ b/lib/commands/index.js @@ -1,7 +1,7 @@ const commands = [ 'dir-add-folder', 'dir-add-request', - 'dir-create', + 'dir-collection-create', 'dir-export', 'dir-export-import-check', 'dir-import', diff --git a/test/cli/dir-commands.test.js b/test/cli/dir-commands.test.js index e1b43ad44..48c08821b 100644 --- a/test/cli/dir-commands.test.js +++ b/test/cli/dir-commands.test.js @@ -1,11 +1,57 @@ +const fs = require('fs'), + expect = require('chai').expect; + +/* + statusCodeCheck = function (code) { + expect(code).to.equal(0); + }*/ + describe('CLI dir command options', function () { - it('should work correctly without any extra options', function (done) { - exec('node ./bin/newman.js dir-export examples/sample-collection.json'); - // TODO - move to temp dir - exec('rm -rf ./Sample Postman Collection', done); + it('should export a collection file to directory', function (done) { + exec('node ./bin/newman.js dir-export examples/sample-collection.json', function (code) { + expect(code).to.equal(0); + fs.rmSync('./Sample Postman Collection', { recursive: true, force: true }); + done(); + }); + }); + + it('should import a directory into a collection file', function (done) { + exec('node ./bin/newman.js dir-import "examples/Sample Postman Collection" -o dir-import-test-collection.json', + function (code) { + expect(code).to.equal(0); + fs.rmSync('./dir-import-test-collection.json', { force: true }); + done(); + }); + }); + + it('should be able to run dir-export-import-check', function (done) { + exec('node ./bin/newman.js dir-export-import-check examples/sample-collection.json', function (code) { + expect(code).to.equal(0); + done(); + }); + }); + + it('should be able to run directory based collection', function (done) { + exec('node ./bin/newman.js dir-run "examples/Sample Postman Collection"', function (code) { + expect(code).to.equal(0); + done(); + }); + }); + + it('should be able to create new directory based collection', function (done) { + exec('node ./bin/newman.js dir-collection-create create-collection-test', + function (code) { + expect(code).to.equal(0); + fs.rmSync('./create-collection-test', { recursive: true, force: true }); + done(); + }); }); - it('should be able to run dir-export-import-check correctly without any extra options', function (done) { - exec('node ./bin/newman.js dir-export-import-check examples/sample-collection.json', done); + it('should be able to add a new folder and a request under it and remove them', function (done) { + exec('node ./bin/newman.js dir-add-folder "examples/Sample Postman Collection/foo"'); + exec('node ./bin/newman.js dir-add-request "examples/Sample Postman Collection/foo/test"'); + exec('node ./bin/newman.js dir-remove-request "examples/Sample Postman Collection/foo/test"'); + exec('node ./bin/newman.js dir-remove-folder "examples/Sample Postman Collection/foo"'); + done(); }); }); diff --git a/test/library/cookie-jar.test.js b/test/library/cookie-jar.test.js index 2aede9bb5..16b7acd71 100644 --- a/test/library/cookie-jar.test.js +++ b/test/library/cookie-jar.test.js @@ -4,7 +4,7 @@ const path = require('path'), expect = require('chai').expect, CookieJar = require('@postman/tough-cookie').CookieJar, - newman = require('../../'); + newman = require('../../lib/commands/run/collection-runner'); describe('newman.run cookieJar', function () { var cookieJar = new CookieJar(), diff --git a/test/library/export-environment.test.js b/test/library/export-environment.test.js index 1f8c27359..4b3ba49a9 100644 --- a/test/library/export-environment.test.js +++ b/test/library/export-environment.test.js @@ -4,7 +4,7 @@ const fs = require('fs'), sh = require('shelljs'), expect = require('chai').expect, - newman = require('../../'); + newman = require('../../lib/commands/run/collection-runner'); describe('newman.run exportEnvironment', function () { var outDir = 'out', diff --git a/test/library/export-globals.test.js b/test/library/export-globals.test.js index f531c87d0..63091c468 100644 --- a/test/library/export-globals.test.js +++ b/test/library/export-globals.test.js @@ -4,7 +4,7 @@ const fs = require('fs'), sh = require('shelljs'), expect = require('chai').expect, - newman = require('../../'); + newman = require('../../lib/commands/run/collection-runner'); describe('newman.run exportGlobals', function () { var outDir = 'out', diff --git a/test/library/folder-variants.test.js b/test/library/folder-variants.test.js index d36e6c3fe..8ee85738a 100644 --- a/test/library/folder-variants.test.js +++ b/test/library/folder-variants.test.js @@ -1,6 +1,6 @@ const expect = require('chai').expect, - newman = require('../../'); + newman = require('../../lib/commands/run/collection-runner'); describe('folder variants', function () { var collection = { diff --git a/test/library/iteration-count.test.js b/test/library/iteration-count.test.js index d21753c93..622937a09 100644 --- a/test/library/iteration-count.test.js +++ b/test/library/iteration-count.test.js @@ -4,7 +4,7 @@ var fs = require('fs'), _ = require('lodash'), expect = require('chai').expect, - newman = require('../../'); + newman = require('../../lib/commands/run/collection-runner'); describe('iterationCount vs iterationData.length conflicts', function () { var iterationProperty = 'run.stats.iterations.total', diff --git a/test/library/postman-api-key.test.js b/test/library/postman-api-key.test.js index 214390420..329043f1e 100644 --- a/test/library/postman-api-key.test.js +++ b/test/library/postman-api-key.test.js @@ -4,7 +4,7 @@ const fs = require('fs'), request = require('postman-request'), expect = require('chai').expect, - newman = require('../../'), + newman = require('../../lib/commands/run/collection-runner'), COLLECTION = { id: 'C1', diff --git a/test/library/requestAgents.test.js b/test/library/requestAgents.test.js index c067ed06c..69b8fc85a 100644 --- a/test/library/requestAgents.test.js +++ b/test/library/requestAgents.test.js @@ -3,7 +3,7 @@ const sinon = require('sinon'), https = require('https'), expect = require('chai').expect, - newman = require('../../'); + newman = require('../../lib/commands/run/collection-runner'); describe('newman.run requestAgents', function () { let httpAgent = new http.Agent(), diff --git a/test/library/run-options.test.js b/test/library/run-options.test.js index 0fa90e4ad..05f68e32c 100644 --- a/test/library/run-options.test.js +++ b/test/library/run-options.test.js @@ -1,7 +1,7 @@ const _ = require('lodash'), expect = require('chai').expect, - newman = require('../../'), + newman = require('../../lib/commands/run/collection-runner'), runtimeVersion = require('../../package.json').dependencies['postman-runtime']; describe('Newman run options', function () { diff --git a/test/library/shallow-junit-reporter.test.js b/test/library/shallow-junit-reporter.test.js index e209bc670..c62b5e497 100644 --- a/test/library/shallow-junit-reporter.test.js +++ b/test/library/shallow-junit-reporter.test.js @@ -5,7 +5,7 @@ const fs = require('fs'), parseXml = require('xml2js').parseString, expect = require('chai').expect, - newman = require('../../'); + newman = require('../../lib/commands/run/collection-runner'); describe('JUnit reporter', function () { var outDir = 'out', diff --git a/test/library/ssl-client-cert.test.js b/test/library/ssl-client-cert.test.js index 36ee4a855..6686e228e 100644 --- a/test/library/ssl-client-cert.test.js +++ b/test/library/ssl-client-cert.test.js @@ -3,7 +3,7 @@ const fs = require('fs'), expect = require('chai').expect, https = require('https'), - newman = require('../../'); + newman = require('../../lib/commands/run/collection-runner'); describe('SSL Client certificates', function () { var server1, server2, server3; diff --git a/test/library/suppress-exit-code.test.js b/test/library/suppress-exit-code.test.js index 5e0c961fc..c6c82e806 100644 --- a/test/library/suppress-exit-code.test.js +++ b/test/library/suppress-exit-code.test.js @@ -1,5 +1,5 @@ const expect = require('chai').expect, - newman = require('../../'); + newman = require('../../lib/commands/run/collection-runner'); describe('newman.run suppressExitCode', function () { it('should accept the suppressExitCode parameter', function (done) { diff --git a/test/library/working-directory.test.js b/test/library/working-directory.test.js index b09c6ce5f..6811f51f3 100644 --- a/test/library/working-directory.test.js +++ b/test/library/working-directory.test.js @@ -1,7 +1,7 @@ const path = require('path'), expect = require('chai').expect, - newman = require('../../'), + newman = require('../../lib/commands/run/collection-runner'), workingDir = path.resolve(__dirname, '../fixtures/files/work-dir'); describe('newman.run workingDir', function () { diff --git a/test/system/npm-publish.test.js b/test/system/npm-publish.test.js index b0b3c2c84..8c4662ad0 100644 --- a/test/system/npm-publish.test.js +++ b/test/system/npm-publish.test.js @@ -5,7 +5,9 @@ const expect = require('chai').expect, describe('npm publish', function () { const stdout = exec('npm pack --dry-run --json').toString(), packageInfo = JSON.parse(stdout.substring(stdout.indexOf('[')))[0], - packagedFiles = packageInfo.files.map(function (file) { return file.path; }); + packagedFiles = packageInfo.files + .map(function (file) { return file.path; }) + .filter(function (item) { return (item !== 'DIR_COMMANDS.md'); }); it('should have a valid package name', function () { expect(packageInfo.name).to.equal('newman'); diff --git a/test/unit/cli.test.js b/test/unit/cli.test.js index c45388cb9..c9c7dafee 100644 --- a/test/unit/cli.test.js +++ b/test/unit/cli.test.js @@ -6,9 +6,7 @@ describe('cli parser', function () { { Command } = require('commander'), newman = require('../../lib/commands/run/collection-runner'), newmanCLI, - commands = { - run: require('../../lib/commands/run') - }, + commands = require('../../lib/commands'), /** * Wrap newmanCLI callback to extract options passed to sinon `newman` stub. diff --git a/test/unit/dir-utils.test.js b/test/unit/dir-utils.test.js index ab9266320..c76dc896e 100644 --- a/test/unit/dir-utils.test.js +++ b/test/unit/dir-utils.test.js @@ -1,7 +1,8 @@ const dirUtils = require('../../lib/commands/dir-utils'), + path = require('path'), + expect = require('chai').expect, fs = require('fs'); - describe('dir-utils tests', function () { it('should create a temp dir', function (done) { const dir = dirUtils.createTempDir(); @@ -9,4 +10,88 @@ describe('dir-utils tests', function () { fs.rmSync(dir, { recursive: true, force: true }); done(); }); + + it('should assert directory absence', function (done) { + const dir = dirUtils.createTempDir(); + + dirUtils.assertDirectoryAbsence(path.join(dir, 'barbaz')); + fs.rmSync(dir, { recursive: true, force: true }); + done(); + }); + + it('should assert directory existence', function (done) { + const dir = dirUtils.createTempDir(); + + dirUtils.assertDirectoryExistence(dir); + fs.rmSync(dir, { recursive: true, force: true }); + done(); + }); + + it('should assert file create and existence', function (done) { + const dir = dirUtils.createTempDir(), + file = path.join(dir, 'foo.txt'); + + dirUtils.createFile(file, ''); + dirUtils.assertFileExistence(file); + fs.rmSync(dir, { recursive: true, force: true }); + done(); + }); + + it('should be able to create directory', function (done) { + const dir = dirUtils.createTempDir(), + newDir = path.join(dir, 'foo'); + + dirUtils.createDir(newDir); + fs.rmSync(dir, { recursive: true, force: true }); + done(); + }); + + it('should assert collection directory', function (done) { + dirUtils.assertCollectionDir('examples/Sample Postman Collection'); + done(); + }); + + it('should sanitize slashes', function (done) { + const foo = dirUtils.sanitizePathName('/foo/'); + + expect(foo).to.equal('_slash_foo_slash_'); + done(); + }); + + it('should return same string if no slashes present', function (done) { + const foo = dirUtils.sanitizePathName('foo'); + + expect(foo).to.equal('foo'); + done(); + }); + + it('should convert directory tree to collection', function (done) { + const collectionJSON = dirUtils.dirTreeToCollectionJson('examples/Sample Postman Collection'); + + expect(collectionJSON).to.deep.equal(JSON.parse(fs.readFileSync('examples/sample-collection.json'))); + done(); + }); + + it('should convert collection to directory tree', function (done) { + const dir = dirUtils.createTempDir(), + collectionJSON = JSON.parse(fs.readFileSync('examples/sample-collection.json')), + currentDir = process.cwd(); + + process.chdir(dir); + dirUtils.traverse(collectionJSON, [], {}); + process.chdir(currentDir); + + let recreatedCollectionJSON = dirUtils.dirTreeToCollectionJson(path.join(dir, 'Sample Postman Collection')); + + expect(recreatedCollectionJSON).to.deep.equal(JSON.parse(fs.readFileSync('examples/sample-collection.json'))); + + fs.rmSync(dir, { recursive: true, force: true }); + done(); + }); + + it('should create and remove postman folders under an collection', function (done) { + dirUtils.createPostmanFolder('examples/Sample Postman Collection/foo'); + dirUtils.removePostmanFolder('examples/Sample Postman Collection/foo'); + done(); + }); });