UNPKG

@ply-ct/ply

Version:

REST API Automated Testing

428 lines 15.7 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Config = exports.PLY_CONFIGS = exports.Defaults = void 0; const findUp = __importStar(require("find-up")); const yargs = __importStar(require("yargs")); const retrieval_1 = require("./retrieval"); const yaml = __importStar(require("./yaml")); const json_1 = require("./json"); /** * Locations are lazily inited to reflect bootstrapped testsLocation. */ class Defaults { constructor(testsLocation = '.') { this.testsLocation = testsLocation; this.requestFiles = '**/*.{ply,ply.yaml,ply.yml}'; this.caseFiles = '**/*.ply.ts'; this.flowFiles = '**/*.ply.flow'; this.ignore = '**/{node_modules,bin,dist,out}/**'; this.skip = '**/*.ply'; this.reporter = ''; this.resultFollowsRelativePath = true; this.valuesFiles = {}; this.verbose = false; this.quiet = false; this.bail = false; this.validate = true; this.parallel = false; this.batchRows = 1; this.batchDelay = 0; this.maxLoops = 10; this.responseBodySortedKeys = true; this.genExcludeResponseHeaders = [ 'cache-control', 'connection', 'content-length', 'date', 'etag', 'keep-alive', 'server', 'transfer-encoding', 'x-powered-by' ]; this.binaryMediaTypes = [ 'application/octet-stream', 'image/png', 'image/jpeg', 'image/gif', 'application/pdf' ]; this.prettyIndent = 2; } get expectedLocation() { if (!this._expectedLocation) { this._expectedLocation = this.testsLocation + '/results/expected'; } return this._expectedLocation; } get actualLocation() { if (!this._actualLocation) { this._actualLocation = this.testsLocation + '/results/actual'; } return this._actualLocation; } get logLocation() { if (!this._logLocation) { this._logLocation = this.actualLocation; } return this._logLocation; } } exports.Defaults = Defaults; exports.PLY_CONFIGS = ['plyconfig.yaml', 'plyconfig.yml', 'plyconfig.json']; class Config { constructor(defaults = new Defaults(), commandLine = false, configPath) { this.defaults = defaults; this.yargsOptions = { testsLocation: { describe: 'Tests base directory', alias: 't' }, requestFiles: { describe: 'Request files glob pattern' }, caseFiles: { describe: 'Case files glob pattern' }, flowFiles: { describe: 'Flow files glob pattern' }, ignore: { describe: 'File patterns to ignore' }, skip: { describe: 'File patterns to skip' }, submit: { describe: "Send requests but don't verify", alias: 's', type: 'boolean' }, create: { describe: 'Create expected result from actual', type: 'boolean' }, trusted: { describe: 'Expressions are from trusted source', type: 'boolean' }, expectedLocation: { describe: 'Expected results base dir', type: 'string' // avoid premature reading of default }, actualLocation: { describe: 'Actual results base dir', type: 'string' // avoid premature reading of default }, resultFollowsRelativePath: { describe: 'Results under similar subpath' }, logLocation: { describe: 'Test logs base dir', type: 'string' // avoid premature reading of default }, valuesFiles: { describe: 'Values files (comma-separated)', type: 'string' }, values: { describe: 'Runtime override values', type: 'array' }, outputFile: { describe: 'Report or summary json file path', alias: 'o', type: 'string' }, verbose: { describe: "Much output (supersedes 'quiet')" }, quiet: { describe: "Opposite of 'verbose'" }, bail: { describe: 'Stop on first failure' }, validate: { describe: 'Validate flows inputs' }, parallel: { describe: 'Run suites in parallel' }, batchRows: { describe: '(Rowwise values) rows per batch' }, batchDelay: { describe: '(Rowwise values) ms batch delay' }, reporter: { describe: 'Reporter output format' }, maxLoops: { describe: 'Flow step instance limit' }, import: { describe: 'Import requests/values from external', type: 'string' }, importToSuite: { describe: 'Import into .yaml suite files', type: 'boolean' }, report: { describe: 'Generate report from ply results', type: 'string' }, openapi: { describe: 'Augment OpenAPI 3 docs with examples', type: 'string' }, useDist: { describe: 'Load cases from compiled js', type: 'boolean' }, stepsBase: { describe: 'Base path for custom steps', type: 'string' }, responseBodySortedKeys: { describe: 'Sort response body JSON keys' }, genExcludeResponseHeaders: { describe: 'Exclude from generated results', type: 'string' }, binaryMediaTypes: { describe: 'Binary media types', type: 'string' }, prettyIndent: { describe: 'Format response JSON' } }; this.options = this.load(defaults, commandLine, configPath); this.defaults.testsLocation = this.options.testsLocation; // result locations may need priming if (!this.options.expectedLocation) { this.options.expectedLocation = defaults.expectedLocation; } if (!this.options.actualLocation) { this.options.actualLocation = defaults.actualLocation; } if (!this.options.logLocation) { this.options.logLocation = this.options.actualLocation; } } load(defaults, commandLine, configPath) { var _a; let opts; if (commandLine) { // help pre-check to avoid premature yargs parsing const needsHelp = process.argv.length > 2 && process.argv[2] === '--help'; if (!configPath && !needsHelp && yargs.argv.config) { configPath = '' + yargs.argv.config; console.debug(`Loading config from ${configPath}`); } if (!configPath) { configPath = findUp.sync(exports.PLY_CONFIGS, { cwd: defaults.testsLocation }); } const config = configPath ? this.read(configPath) : {}; if (process.argv.length > 2 && process.argv.find((arg) => arg.startsWith('--valuesFiles'))) { delete config.valuesFiles; // overridden by command-line option } let spec = yargs .usage('Usage: $0 <tests> [options]') .help('help') .alias('help', 'h') .version() .alias('version', 'v') .config(config) .option('config', { description: 'Ply config location', type: 'string', alias: 'c' }); for (const option of Object.keys(this.yargsOptions)) { const yargsOption = this.yargsOptions[option]; let type = yargsOption.type; if (!type) { // infer from default type = typeof defaults[option]; } spec = spec.option(option, { type, // default: val, // clutters help output ...yargsOption }); if (type === 'boolean') { spec = spec.boolean(option); } } opts = spec.argv; if (typeof config.valuesFiles === 'object') { // undo yargs messing this up due to dots opts.valuesFiles = config.valuesFiles; } if (typeof opts.valuesFiles === 'string') { opts.valuesFiles = opts.valuesFiles .split(',') .reduce((vfs, v) => { vfs[v.trim()] = true; return vfs; }, {}); } if (opts.genExcludeResponseHeaders) { if (typeof opts.genExcludeResponseHeaders === 'string') { opts.genExcludeResponseHeaders = opts.genExcludeResponseHeaders .split(',') .map((v) => v.trim()); } opts.genExcludeResponseHeaders = opts.genExcludeResponseHeaders.map((v) => v.toLowerCase()); } if (opts.binaryMediaTypes) { if (typeof opts.binaryMediaTypes === 'string') { opts.binaryMediaTypes = opts.binaryMediaTypes .split(',') .map((v) => v.trim()); } opts.binaryMediaTypes = opts.binaryMediaTypes.map((v) => v.toLowerCase()); } opts.args = opts._; delete opts._; } else { if (!configPath) { configPath = findUp.sync(exports.PLY_CONFIGS, { cwd: defaults.testsLocation }); } opts = configPath ? this.read(configPath) : {}; } let options = { ...defaults, ...opts }; if (((_a = opts.args) === null || _a === void 0 ? void 0 : _a.length) > 0 && !process.argv.find((arg) => arg.startsWith('--skip'))) { // command-line tests passed, and --skip option not supplied, override plyconfig skip delete options.skip; } // clean up garbage keys added by yargs, and private defaults options = Object.keys(options).reduce((obj, key) => { if (key.length > 1 && key.indexOf('_') === -1 && key.indexOf('-') === -1) { obj[key] = options[key]; } return obj; }, {}); // run options options.runOptions = {}; if (options.submit) { options.runOptions.submit = options.submit; delete options.submit; if (opts.validate === undefined) { options.validate = false; } } if (options.create) { options.runOptions.createExpected = options.create; delete options.create; } if (options.trusted) { options.runOptions.trusted = options.trusted; delete options.trusted; } if (options.useDist) { options.runOptions.useDist = options.useDist; delete options.useDist; } if (options.import) { options.runOptions.import = options.import; delete options.import; } if (options.importToSuite) { options.runOptions.importToSuite = options.importToSuite; delete options.importToSuite; } if (options.report) { options.runOptions.report = options.report; delete options.report; } if (options.openapi) { options.runOptions.openapi = options.openapi; delete options.openapi; } if (options.reporter || options.runOptions.report) { if (!process.argv.includes('-o') && !process.argv.find((av) => av.startsWith('--outputFile='))) { delete options.outputFile; } } if (options.values) { options.runOptions.values = options.values.reduce((values, value) => { const eq = value.indexOf('='); if (eq > 0 && eq < value.length - 1) { const exprKey = '${' + value.substring(0, eq) + '}'; values[exprKey] = value.substring(eq + 1); } return values; }, {}); delete options.values; } if (options.stepsBase) { options.runOptions.stepsBase = options.stepsBase; delete options.stepsBase; delete options['steps-base']; } return options; } read(configPath) { const retrieval = new retrieval_1.Retrieval(configPath); const contents = retrieval.sync(); if (typeof contents === 'string') { let config; if (retrieval.location.isYaml) { config = yaml.load(retrieval.location.path, contents); } else { config = (0, json_1.parseJsonc)(configPath, contents); } if (Array.isArray(config.valuesFiles)) { // covert all to enabled format config.valuesFiles = config.valuesFiles.reduce((obj, valFile) => { if (typeof valFile === 'object') { const file = Object.keys(valFile)[0]; obj[file] = valFile[file]; } else { obj[valFile] = true; } return obj; }, {}); } return config; } else { throw new Error('Cannot load config: ' + configPath); } } } exports.Config = Config; //# sourceMappingURL=options.js.map