@ply-ct/ply
Version:
REST API Automated Testing
469 lines • 17.5 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.Plier = exports.Plyee = exports.Ply = void 0;
const path = __importStar(require("path"));
const rimraf_1 = require("rimraf");
const events_1 = require("events");
const options_1 = require("./options");
const cases_1 = require("./cases");
const requests_1 = require("./requests");
const compile_1 = require("./compile");
const log_1 = require("./log");
const logger_1 = require("./logger");
const util = __importStar(require("./util"));
const flows_1 = require("./flows");
const values_1 = require("./values");
const runner_1 = require("./runner");
const report_1 = require("./report/report");
class Ply {
constructor(options, logger) {
this.logger = logger;
this.options = Object.assign({}, new options_1.Defaults(), options || new options_1.Config().options);
}
/**
* Load request from .ply file
*/
async loadRequest(location) {
const suite = await this.loadRequestSuite(location);
if (suite.size() === 0)
throw new Error(`No request found in: ${suite.name}`);
return suite.all()[0];
}
loadRequestSync(location) {
const suite = this.loadRequestSuiteSync(location);
if (suite.size() === 0)
throw new Error(`No request found in: ${suite.name}`);
return suite.all()[0];
}
async loadRequests(locations, ...moreLocations) {
if (typeof locations === 'string') {
locations = [locations];
}
if (moreLocations) {
locations = [...locations, ...moreLocations];
}
const requestLoader = new requests_1.RequestLoader(locations, this.options, this.logger);
return await requestLoader.load();
}
/**
* Throws if location or suite not found
*/
async loadRequestSuite(location) {
const requestSuites = await this.loadRequests([location]);
if (requestSuites.length === 0) {
throw new Error(`No request suite found in: ${location}`);
}
return requestSuites[0];
}
loadRequestsSync(locations, ...moreLocations) {
if (typeof locations === 'string') {
locations = [locations];
}
if (moreLocations) {
locations = [...locations, ...moreLocations];
}
const requestLoader = new requests_1.RequestLoader(locations, this.options, this.logger);
return requestLoader.sync();
}
/**
* Throws if location or suite not found
*/
loadRequestSuiteSync(location) {
const requestSuites = this.loadRequestsSync([location]);
if (requestSuites.length === 0) {
throw new Error(`No request suite found in: ${location}`);
}
return requestSuites[0];
}
async loadSuite(location) {
return await this.loadRequestSuite(location);
}
loadSuiteSync(location) {
return this.loadRequestSuiteSync(location);
}
/**
* Throws if location or suite not found
*/
async loadCaseSuites(location) {
const caseSuites = await this.loadCases([location]);
if (caseSuites.length === 0) {
throw new Error(`No case suite found in: ${location}`);
}
return caseSuites;
}
async loadCases(files, ...moreFiles) {
if (typeof files === 'string') {
files = [files];
}
if (moreFiles) {
files = [...files, ...moreFiles];
}
const compileOptions = new compile_1.TsCompileOptions(this.options);
const caseLoader = new cases_1.CaseLoader(files, this.options, compileOptions, this.logger);
const suites = await caseLoader.load();
return suites;
}
/**
* Throws if location or suite not found
*/
async loadFlowSuites(location) {
const flowSuites = await this.loadFlows([location]);
if (flowSuites.length === 0) {
throw new Error(`No flow suite found in: ${location}`);
}
return flowSuites;
}
async loadFlow(file) {
const flows = await this.loadFlows(file);
if (flows.length === 0)
throw new Error(`No flow found in: ${file}`);
return flows[0];
}
async loadFlows(files, ...moreFiles) {
if (typeof files === 'string') {
files = [files];
}
if (moreFiles) {
files = [...files, ...moreFiles];
}
const flowLoader = new flows_1.FlowLoader(files, this.options, this.logger);
const suites = await flowLoader.load();
return suites;
}
}
exports.Ply = Ply;
/**
* A Plyee is a test (request/case), or a suite.
*
* Format: <absolute_suite_file_forward_slashes>#<optional_case_suite>^<test_name>
* eg: c:/ply/ply/test/ply/requests/movie-queries.ply.yaml#moviesByYearAndRating
* or: /Users/me/ply/ply/test/ply/cases/movieCrud.ply.ts#movie-crud^add new movie
* or for a suite: /Users/me/ply/ply/test/ply/requests/movie-queries.ply.yaml
* (TODO: handle caseFile#suite^case)
*/
class Plyee {
constructor(pathOrSuite, test) {
if (test) {
this.path =
util.fwdSlashes(path.normalize(path.resolve(pathOrSuite))) + `#${test.name}`;
}
else {
const hash = pathOrSuite.indexOf('#');
if (hash === 0 || hash > pathOrSuite.length - 2) {
throw new Error(`Invalid path: ${pathOrSuite}`);
}
if (hash === -1 && pathOrSuite.endsWith('.ply')) {
// path and test will be the same
this.path = util.fwdSlashes(path.normalize(path.resolve(pathOrSuite)));
}
else {
const base = pathOrSuite.substring(0, hash);
const frag = pathOrSuite.substring(hash + 1);
this.path = util.fwdSlashes(path.normalize(path.resolve(base))) + `#${frag}`;
}
}
this.hash = this.path.indexOf('#');
this.hat = this.path.lastIndexOf('^');
if (this.hat < this.hash || this.hat < this.path.length - 1) {
this.hat = -1;
}
}
get location() {
if (this.hash > 0) {
return this.path.substring(0, this.hash);
}
else {
return this.path;
}
}
get suite() {
if (this.hat > 0) {
return this.path.substring(this.hash + 1, this.hat);
}
else {
return this.location;
}
}
get test() {
if (this.hash > 0) {
if (this.hat > 0) {
return this.path.substring(this.hat + 1);
}
else {
return this.path.substring(this.hash + 1);
}
}
else if (this.path.endsWith('.ply')) {
return this.path;
}
}
toString() {
return this.path;
}
static isRequest(path) {
return path.endsWith('.ply') || path.endsWith('.yml') || path.endsWith('.yaml');
}
static isCase(path) {
return path.endsWith('.ts');
}
static isFlow(path) {
return path.endsWith('.flow');
}
/**
* Maps plyee paths to Plyee by Suite.
*/
static requests(paths) {
return this.collect(paths, (plyee) => Plyee.isRequest(plyee.location));
}
static cases(paths) {
return this.collect(paths, (plyee) => Plyee.isCase(plyee.location));
}
static flows(paths) {
return this.collect(paths, (plyee) => Plyee.isFlow(plyee.location));
}
/**
* Returns a map of unique suite location to Plyee[]
* @param paths test paths
* @param test (optional) for matching
*/
static collect(paths, test) {
const map = new Map();
for (const path of paths) {
const plyee = new Plyee(path);
if (!test || test(plyee)) {
let plyees = map.get(plyee.location);
if (!plyees) {
plyees = [];
map.set(plyee.location, plyees);
}
plyees.push(plyee);
}
}
return map;
}
}
exports.Plyee = Plyee;
/**
* Utility for executing multiple tests, organized into their respective suites.
* Used by both CLI and vscode-ply.
*/
class Plier extends events_1.EventEmitter {
constructor(options, logger) {
// @ts-ignore node 12 takes no params
super({ captureRejections: true });
const opts = Object.assign({}, new options_1.Defaults(), options || new options_1.Config().options);
this.logger =
logger ||
new logger_1.Logger({
level: opts.verbose ? log_1.LogLevel.debug : opts.quiet ? log_1.LogLevel.error : log_1.LogLevel.info,
prettyIndent: opts.prettyIndent
});
this.ply = new Ply(options, this.logger);
}
get options() {
return this.ply.options;
}
/**
* Plyees should be test paths (not suites).
*/
async run(plyees, runOptions, plyVersion) {
const version = plyVersion || (await util.plyVersion());
if (version)
this.logger.info('Ply version', version);
this.logger.debug('Options', this.options);
const plyValues = new values_1.ValuesBuilder(this.options.valuesFiles, this.logger);
let values = await plyValues.read();
if (runOptions === null || runOptions === void 0 ? void 0 : runOptions.values) {
// runOptions values override file files
values = { ...values, ...runOptions.values };
}
this.logger.debug('Values', values);
// remove all previous runs
await (0, rimraf_1.rimraf)(`${this.options.logLocation}/runs`);
let promises = []; // for parallel exec
const overall = {
Passed: 0,
Failed: 0,
Errored: 0,
Pending: 0,
Submitted: 0,
Waiting: 0
};
// requests
const requestTests = new Map();
for (const [loc, requestPlyee] of Plyee.requests(plyees)) {
const requestSuite = await this.ply.loadRequestSuite(loc);
const tests = requestPlyee.map((plyee) => {
if (!plyee.test) {
throw new Error(`Plyee is not a test: ${plyee}`);
}
if (plyee.test.endsWith('.ply') && requestSuite.size()) {
return requestSuite.all().values().next().value.name;
}
return plyee.test;
});
requestSuite.emitter = this;
requestTests.set(requestSuite, tests);
}
const requestRunner = new runner_1.PlyRunner(this.ply.options, requestTests, plyValues, this.logger);
await requestRunner.runSuiteTests(values, runOptions);
// TODO overall results should not count each request, but suites?
if (this.ply.options.parallel) {
promises = [...promises, ...requestRunner.promises];
}
else {
requestRunner.results.forEach((result) => overall[result.status]++);
}
// flows
const flowTests = new Map();
for (const [loc, flowPlyee] of Plyee.flows(plyees)) {
const tests = flowPlyee.map((plyee) => {
if (!plyee.test) {
throw new Error(`Plyee is not a test: ${plyee}`);
}
return plyee.test;
});
const flowSuites = await this.ply.loadFlowSuites(loc);
for (const flowSuite of flowSuites) {
// should only be one per loc
flowSuite.emitter = this;
flowTests.set(flowSuite, tests);
}
}
const flowRunner = new runner_1.PlyRunner(this.ply.options, flowTests, plyValues, this.logger);
await flowRunner.runSuiteTests(values, runOptions);
if (this.ply.options.parallel) {
promises = [...promises, ...flowRunner.promises];
}
else {
flowRunner.results.forEach((result) => overall[result.status]++);
}
// cases
const caseTests = new Map();
for (const [loc, casePlyee] of Plyee.cases(plyees)) {
const tests = casePlyee.map((plyee) => {
if (!plyee.test) {
throw new Error(`Plyee is not a test: ${plyee}`);
}
return plyee.test;
});
const caseSuites = await this.ply.loadCaseSuites(loc);
for (const caseSuite of caseSuites) {
// should only be one per loc
caseSuite.emitter = this;
caseTests.set(caseSuite, tests);
}
}
const caseRunner = new runner_1.PlyRunner(this.ply.options, caseTests, plyValues, this.logger);
await caseRunner.runSuiteTests(values, runOptions);
if (this.ply.options.parallel) {
promises = [...promises, ...caseRunner.promises];
}
else {
caseRunner.results.forEach((result) => overall[result.status]++);
}
if (this.ply.options.parallel) {
const allResults = await Promise.all(promises);
allResults.forEach((results) => results.forEach((res) => overall[res.status]++));
}
if (this.options.reporter) {
const factory = new report_1.ReporterFactory(this.options.reporter);
const reporter = await factory.createReporter();
await reporter.report({
format: factory.format,
output: this.options.outputFile ||
`${this.options.logLocation}/ply-runs.${factory.format}`,
runsLocation: `${this.options.logLocation}/runs`,
logger: this.logger,
indent: this.options.prettyIndent
});
}
return overall;
}
/**
* Finds plyees from suites and tests.
* @param paths suite/test paths
*/
async find(paths) {
const plyees = [];
for (const path of paths) {
if (path.indexOf('#') > 0) {
plyees.push(path);
}
else {
// suite
if (Plyee.isRequest(path)) {
const requestSuite = await this.ply.loadRequestSuite(path);
if (!requestSuite.skip) {
if (path.endsWith('.ply')) {
plyees.push(path);
}
else {
for (const request of requestSuite) {
plyees.push(this.ply.options.testsLocation +
'/' +
requestSuite.path +
'#' +
request.name);
}
}
}
}
else if (Plyee.isCase(path)) {
const caseSuites = await this.ply.loadCaseSuites(path);
for (const caseSuite of caseSuites) {
if (!caseSuite.skip) {
for (const testCase of caseSuite) {
plyees.push(this.ply.options.testsLocation +
'/' +
caseSuite.path +
'#' +
testCase.name);
}
}
}
}
else if (Plyee.isFlow(path)) {
const flowSuites = await this.ply.loadFlowSuites(path);
for (const flowSuite of flowSuites) {
if (!flowSuite.skip) {
for (const step of flowSuite) {
plyees.push(this.ply.options.testsLocation +
'/' +
flowSuite.path +
'#' +
step.name);
}
}
}
}
}
}
return plyees;
}
}
exports.Plier = Plier;
//# sourceMappingURL=ply.js.map