UNPKG

@apica-io/asm-pm-runner

Version:

Run a postman collection in Apica ASM.

532 lines 22.8 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; }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.NewmanRunner = void 0; const _ = __importStar(require("lodash")); const postman_collection_1 = require("postman-collection"); const newman = __importStar(require("newman")); const crs = __importStar(require("../model/crsformat")); const chalk_1 = __importDefault(require("chalk")); const Apica_ReturnValue = "_Apica_ReturnValue"; const Apica_ReturnUnit = "_Apica_ReturnUnit"; const Apica_Message = "_Apica_Message"; const helpers = __importStar(require("../lib/helpers")); const fs = __importStar(require("fs")); const path = __importStar(require("path")); const os = __importStar(require("os")); const cryptify_1 = __importDefault(require("cryptify")); const log4js_1 = require("log4js"); const VAR_Blacklist_Patterns = [ "secret", "token", "client_id", "password", "username", "api", "key", ]; class NewmanRunner { constructor(config) { this.config = {}; this.collection = {}; this.lastItemGroup = ""; this.successFulRequests = 0; this.failedRequests = 0; this.tempResult = []; this.tempFiles = []; this.result = {}; this.changedVars = new Map(); this.error = {}; this.config = config; this.logger = (0, log4js_1.getLogger)(NewmanRunner.name); } init() { let content = fs.readFileSync(this.config.collection).toString(); this.collection = new postman_collection_1.Collection(JSON.parse(content)); } run() { return __awaiter(this, void 0, void 0, function* () { let options = { collection: this.collection, bail: true, timeout: 180000, timeoutRequest: 45000, }; try { options["verbose"] = true; if (this.config.envVars) { let envVars = []; this.config.envVars.forEach((element) => { let pos = element.indexOf("="); let envVar = {}; if (pos > 0) { envVar.value = element.substring(pos + 1); envVar.key = element.substring(0, pos); } else { envVar.key = element; envVar.value = undefined; } envVars.push(envVar); }); options.envVar = envVars; this.logger.debug("env variables=%s", this.config.envVars); } if (this.config.environment) { let env_path = this.config.environment; options.environment = yield this.getDataFile(env_path); } if (this.config.sslConfig.cert && this.config.sslConfig.key) { let ssl_key_path, ssl_cert_path; if (this.config.dataDir) { ssl_cert_path = path.resolve(this.config.dataDir, this.config.sslConfig.cert); ssl_key_path = path.resolve(this.config.dataDir, this.config.sslConfig.key); } else { ssl_cert_path = this.config.sslConfig.cert; ssl_key_path = this.config.sslConfig.key; } options.sslClientCert = yield this.getDataFile(ssl_cert_path); options.sslClientKey = yield this.getDataFile(ssl_key_path); options.sslClientPassphrase = this.config.sslConfig.passPhrase; } yield new Promise((resolve, reject) => { newman .run(options, (err, data) => { if (err) return reject(err); resolve(data); }) .on("beforeItem", (err, args) => this.beforeItem(err, args)) .on("request", (err, args) => this.request(err, args)) .on("test", (err, args) => this.test(err, args)) .on("exception", (err, args) => this.exception(err, args)) .on("assertion", (err, args) => this.assertion(err, args)) .on("beforeDone", (err, args) => this.beforeDone(err, args)); }); } catch (err) { this.logger.error(err.message || err); throw err; } finally { this.cleanUpTempFiles(); } this.log("Result: ", this.result.success, this.result.message); }); } getDataFile(pathName) { return __awaiter(this, void 0, void 0, function* () { if (fs.existsSync(pathName)) { if (this.config.decryptKey) { let tmpFile = os.tmpdir() + "/" + process.pid + "_" + path.basename(pathName); fs.copyFileSync(pathName, tmpFile); let crypto = new cryptify_1.default(tmpFile, this.config.decryptKey, undefined, undefined, true); let files = yield crypto.decrypt(); if (files && files.length > 0) { this.logger.debug("Decrypting %s", tmpFile); this.tempFiles.push(tmpFile); } return tmpFile; } return pathName; } else { let err = new Error(`File ${pathName} not found`); err.name = " ERR_FILE_NOT_FOUND"; throw err; } }); } cleanUpTempFiles() { this.logger.debug("Clean up temporary files", this.tempFiles); this.tempFiles.forEach((file) => { if (fs.existsSync(file)) { fs.unlinkSync(file); } }); } saveResult() { if (this.config.resultDir) { let jobId = process.env.APICA_JOB_ID || ""; let fileName = jobId || this.collection.name + "_res"; let resultFile = path.resolve(this.config.resultDir, fileName + ".json"); let res = JSON.stringify(this.result, null, 2); fs.writeFileSync(resultFile, res); this.logger.debug("Results written to %s", resultFile); } } log(message, ...optionalParams) { if (this.config.verbose) { console.log((0, chalk_1.default) `{green ${message}}`, ...optionalParams); } } toResultHeaders(list) { let headers = {}; list.each((header) => { headers[header.key] = header.value; }); return headers; } getStepName(item, collection) { let names = []; item.forEachParent((item) => { names.push(item.name); }); if (names.length > 0) return _.reverse(names).join("/"); return collection.name; } createResult(collection, runResult) { let packInfo = helpers.getPackageInfo(); let result = this.result; result.name = collection.name; result.type = "Postman.Collection"; result.result_version = packInfo.version || "1.0"; result.producer = packInfo.name; if (packInfo.name) { result.producer = packInfo.name + " " + packInfo.version; } result.value = 0; result.unit = "ms"; result.returncode = 0; result.success = true; if (this.error.message) { result.message = `Exception ${this.error.message}, ${this.error.name} `; } else { result.message = `Successful requests ${this.successFulRequests}, Failed requests ${this.failedRequests} `; } crs.initBaseResult(result, collection.name); if (collection.description) result.description = collection.description.toString(); let lastItemGroup = undefined; result.variables = []; let step = undefined; result.steps = []; if (!result.errors && this.error.message) { result.errors = []; result.errors.push(this.error); } this.tempResult.forEach((tempResult) => { let requestResult = tempResult.result; let itemGroup = tempResult.item.parent() || undefined; if (!lastItemGroup || !_.isEqual(itemGroup, lastItemGroup)) { step = {}; crs.initBaseResult(step, this.getStepName(tempResult.item, collection)); step.start_timestamp_ms = requestResult.start_timestamp_ms; step.requests = []; result.steps.push(step); lastItemGroup = itemGroup; } step.requests.push(requestResult); step.success = true; step.end_timestamp_ms = requestResult.end_timestamp_ms; step.content_length += requestResult.content_length; step.duration_ms += requestResult.duration_ms; result.duration_ms += requestResult.duration_ms; result.content_length += step.content_length; if (requestResult.metrics) { crs.incrementTimingPhases(step.metrics, requestResult.metrics); crs.incrementTimingPhases(result.metrics, requestResult.metrics); crs.roundTimingPhases(requestResult.metrics); } if (requestResult.error) { if (!result.errors) result.errors = []; result.errors.push(requestResult.error); step.success = result.success = requestResult.success = false; } if (tempResult.assertions && tempResult.assertions.length > 0) { if (!step.assertions) step.assertions = []; if (step.assertions.length === 0) { _.assign(step.assertions, tempResult.assertions); } else { step.assertions = _.concat(step.assertions, tempResult.assertions); } if (tempResult.assertions.find((assertion) => assertion.status === "failure")) { step.success = result.success = false; } } }); let firstRequest = _.first(this.tempResult) || {}; if (firstRequest.result) { result.start_timestamp_ms = firstRequest.result.start_timestamp_ms; let lastRequest = _.last(this.tempResult) || {}; if (lastRequest.result) result.end_timestamp_ms = lastRequest.result.end_timestamp_ms; } else { result.success = false; } result.returncode = result.success ? 0 : 1; result.value = result.duration_ms; let custom_return_value = undefined; let custom_return_unit = undefined; let custom_message = ''; collection.variables.each((variable) => { var _a; let key = variable.key || ""; let newValue = this.changedVars.get(key); let value = (newValue === undefined) ? variable.value : newValue; switch (key) { case Apica_ReturnValue: if (result.success && _.isNumber(value)) { custom_return_value = Number(value); } break; case Apica_ReturnUnit: if (result.success && value && _.isString(value)) { custom_return_unit = value; } break; case Apica_Message: if (value && _.isString(value)) { custom_message = value; } break; default: if (!key.startsWith("_") && (value !== undefined && value !== '') && !VAR_Blacklist_Patterns.find((name) => key.toLocaleLowerCase().includes(name))) { let s = variable.value; let link = s.startsWith("http://") || s.startsWith("https://"); let _var = { key: key || "", type: link ? "url" : isNaN(value) ? "string" : "number", usage: newValue ? "inResponse" : "input", value: value, }; (_a = result.variables) === null || _a === void 0 ? void 0 : _a.push(_var); } } }); if (custom_return_value != undefined) { result.value = Number(custom_return_value); if (custom_return_unit) result.unit = custom_return_unit; } result.steps.forEach((step) => { crs.roundTimingPhases(step.metrics); step.requests.forEach((request) => { let authHeader = request.request_headers && request.request_headers["Authorization"]; if (authHeader) { const elem = authHeader.split(" "); if (elem.length > 0) { request.request_headers["Authorization"] = elem[0] + " {secret}"; } } }); }); crs.roundTimingPhases(result.metrics); if (result.success) { result.message = custom_message ? custom_message : `Requests:${runResult.stats.requests.total}, tests:${runResult.stats.tests.total}, assertions:${runResult.stats.assertions.total}`; } else if (result.errors && result.errors[0]) { let err = result.errors[0]; result.message = custom_message ? `${custom_message}` : `${err.name} ${err.message}`; } } timingPhases(timings) { // bail out if timing information is not provided if (!(timings && timings.offset)) { return undefined; } let offset = timings.offset; // REFER: https://github.com/postmanlabs/postman-request/blob/v2.88.1-postman.5/request.js#L996 let phases = { secure_handshake_ms: 0, prepare: offset.request, socket_wait_ms: offset.socket - offset.request, dns_time_ms: offset.lookup - offset.socket, tcp_connect_ms: offset.connect - offset.lookup, time_to_first_byte_ms: offset.response - offset.connect, download_time_ms: offset.end - offset.response, process_time_ms: offset.done - offset.end, total_time_ms: offset.done, }; if (offset.secureConnect) { phases.secure_handshake_ms = offset.secureConnect - offset.connect; phases.time_to_first_byte_ms = offset.response - offset.secureConnect; } return phases; } beforeDone(err, args) { //this.log("beforeDone", args) let summary = args.summary; let collection = summary.collection; this.log("Collection", collection.name); //this.log("Variables", summary.collection.variables.members) let run = summary.run; this.log("run.stats", run.stats); this.createResult(collection, run); } beforeItem(err, args) { let item = args.item; let itemGroup = item.parent() || undefined; if (itemGroup && itemGroup.name != this.lastItemGroup) { this.log("itemGroup", itemGroup.name); this.lastItemGroup = itemGroup.name || ""; } } request(err, args) { let item = args.item; let request = args.request; let url = request.url.toString(); let response = args.response; this.log("item", item.name); if (err) this.log("request error", err); else { this.log("request", request.method, url); this.log("response", response.status, response.code, response.reason(), response.responseTime, response.size()); } let timings = _.last(_.get(args, "history.execution.data")); timings = timings && timings.timings; let tmpResult = {}; tmpResult.request = request; tmpResult.item = item; let result = {}; crs.initBaseResult(result, item.name); result.url = url; result.method = request.method; result.success = true; if (!err) { this.successFulRequests++; result.start_timestamp_ms -= response.responseTime; result.duration_ms = response.responseTime; let size = response.size(); result.content_length = size.body ? size.body : 0; result.status = response.code; result.status_text = response.reason(); if (timings) { result.metrics = this.timingPhases(timings); this.log("timingPhases", result.metrics); } result.request_headers = this.toResultHeaders(request.headers); result.headers = this.toResultHeaders(response.headers); } else { this.failedRequests++; result.success = false; result.error = new Error(); result.error.message = err.message; result.error.name = err.name; result.error.stack = ""; result.duration_ms = result.content_length = 0; result.status = -1; result.status_text = err.message; } tmpResult.result = result; this.tempResult.push(tmpResult); } exception(err, args) { if (args.error) { this.log("error", args.error); this.error = args.error; } } test(err, args) { let item = args.item; let _error = undefined; args.executions.forEach((execution) => { var _a, _b, _c, _d, _e, _f; if (execution.error) { _error = execution.error; let compacted = (_c = (_b = (_a = execution.result) === null || _a === void 0 ? void 0 : _a.collectionVariables) === null || _b === void 0 ? void 0 : _b.mutations) === null || _c === void 0 ? void 0 : _c.compacted; if (compacted && !_.isEmpty(compacted)) { let changedVar = compacted[Apica_Message]; if (changedVar && !_.isEmpty(changedVar)) { this.changedVars.set(changedVar[0], changedVar[1]); this.log(Apica_Message + " value changed", changedVar); } } } else { let compacted = (_f = (_e = (_d = execution.result) === null || _d === void 0 ? void 0 : _d.collectionVariables) === null || _e === void 0 ? void 0 : _e.mutations) === null || _f === void 0 ? void 0 : _f.compacted; if (compacted && !_.isEmpty(compacted)) { Object.keys(compacted).forEach((key) => { let changedVar = compacted[key]; if (changedVar.length > 1) { this.changedVars.set(changedVar[0], changedVar[1]); this.log("changed variable", changedVar); } }); } } }); if (_error) { let error = new Error(); error.name = _.clone(_error.name); error.message = _.clone(_error.message) + " in [" + item.name + "]"; error.stack = ""; let last = _.last(this.tempResult) || {}; if (last.result) last.result.error = error; this.log("error", error); } } assertion(err, args) { let item = args.item; this.log("assertion", args.assertion, args.error ? false : true); let last = _.last(this.tempResult) || {}; if (last.result) { if (!last.assertions) last.assertions = []; let assertion = { source: item.name, description: args.assertion, status: args.error ? "failure" : "info", expression: args.assertion, value: args.error ? false : true, }; last.assertions.push(assertion); } } } exports.NewmanRunner = NewmanRunner; //# sourceMappingURL=newmanRunner.js.map