@ply-ct/ply
Version:
REST API Automated Testing
428 lines • 15.7 kB
JavaScript
"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