UNPKG

@ply-ct/ply

Version:

REST API Automated Testing

469 lines 17.5 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.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