UNPKG

@apica-io/url-xi

Version:

URL Check for integrations and API monitoring

585 lines 34.8 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); }) : (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.TestRunner = void 0; const ITestConfig_1 = require("../model/ITestConfig"); const testResultProcessor_1 = require("../processor/testResultProcessor"); const prePostProcessors_1 = require("../util/prePostProcessors"); const testbase_1 = require("../lib/testbase"); const helpers = __importStar(require("../lib/helpers")); const api_1 = require("../lib/api"); const qs_1 = __importDefault(require("qs")); const https = __importStar(require("https")); const http = __importStar(require("http")); class TestRunner extends testbase_1.TestBase { constructor(config, debug = false) { super(debug, 'TestRunner'); this._httpsAgentString = ''; this._httpsAgent = {}; this._testConfig = config; this._prePostProcessors = new prePostProcessors_1.PrePostProcessors(config, debug); } setConfigValues(config, keepMustache) { const regex = /{{(\$?[A-Za-z_][\w\\.]*\w)}}/; let jsonStr = JSON.stringify(config); while (regex.test(jsonStr)) { jsonStr = this._testConfig.replaceWithVarValue(jsonStr, keepMustache ? true : false); if (keepMustache) break; } const ret = JSON.parse(jsonStr); return ret; } saveAssertionAsError(requestResult, assertionResults) { let foundFailure = false; if (!requestResult.error) { assertionResults.forEach((assertion) => { if (!foundFailure && assertion.status === 'failure') { const value = assertion.value.toString().substring(0, 40); foundFailure = true; requestResult.error = new Error(); requestResult.error.message = `${assertion.description} value=${value}`; requestResult.error.name = 'AssertionFailure'; requestResult.error.stack = ''; } }); } } runTestStep(results, step, api, options, idleBetweenRequest, apiConfig) { var _a, _b, _c, _d, _e, _f, _g, _h, _j; return __awaiter(this, void 0, void 0, function* () { let maxContentLength = options.maxContentLength && !isNaN(Number(options.maxContentLength)) ? Number(options.maxContentLength) : 0; maxContentLength = Math.max(ITestConfig_1.ResultConfig.contentLength.maxRequestLength, maxContentLength); const stepResult = {}; this._prePostProcessors.runBeforeScripts(this._testConfig.configData, step); stepResult.name = step.name; testResultProcessor_1.TestResultProcessor.initBaseResultItem(stepResult); stepResult.ignoreDuration = (step === null || step === void 0 ? void 0 : step.ignoreDuration) || false; stepResult.requests = []; if (step.idleBetweenRequests) { let idleTime = step.idleBetweenRequests; if (isNaN(idleTime)) idleTime = this._testConfig.replaceWithVarValue(step.idleBetweenRequests); if (!isNaN(idleTime)) { idleBetweenRequest = idleTime; this._logger.debug('Idle between request in step =%d', idleBetweenRequest); } } this._testConfig.setVariableValue('$stepName', step.name); try { let foundError = false; let iterator = { varName: '', value: 1, waitForValidResponse: false, }; let laps = 1; let orgIterValue = ''; if (step.iterator) { stepResult.success = true; iterator = step.iterator; orgIterValue = iterator.value; if (!Array.isArray(iterator.value)) { iterator.value = this._testConfig.replaceWithVarValue(iterator.value); } if (iterator.value && Array.isArray(iterator.value)) { let maxLaps = -1; if (iterator.maxLaps) { switch (typeof iterator.maxLaps) { case 'string': // eslint-disable-next-line no-case-declarations const ml = this._testConfig.replaceWithVarValue(iterator.maxLaps) || iterator.maxLaps; // eslint-disable-next-line no-case-declarations const num = Number(ml); if (!isNaN(num)) { maxLaps = num; } break; case 'number': maxLaps = iterator.maxLaps; } } laps = maxLaps >= 0 ? Math.min(iterator.value.length, maxLaps) : iterator.value.length; } else { const num = Number(iterator.value); laps = isNaN(num) ? 0 : num; } } else { laps = iterator.value || (orgIterValue ? 0 : 1); } const varName = iterator.varName || ''; for (let lap = 0; !foundError && lap < laps; lap++) { this._testConfig.setVariableValue('$lap', lap); this._testConfig.setVariableValue('$lapIdx1', lap + 1); if (varName) { const val = ((_a = iterator.value) === null || _a === void 0 ? void 0 : _a.length) ? iterator.value[lap] : Number(lap + 1); this._testConfig.setVariableValue(varName, val); } let poll = iterator.waitForValidResponse && lap < laps - 1 ? true : false; for (let idx = 0; (!foundError && idx < (step === null || step === void 0 ? void 0 : step.requests.length)) || 0; idx++) { const requestIdleBetweenRequests = idleBetweenRequest; const request = step.requests[idx]; if (request.disabled || (request.condition && !this._prePostProcessors.saveJavaScriptEval(request.condition.value, request.condition.expression, true))) continue; let authMethod = request.authMethod || step.authMethod || undefined; if (authMethod) { authMethod = this._testConfig.replaceFromJSON(authMethod); this._prePostProcessors.setupAuthMethod(api, authMethod); } let httpsAgent = request.httpsAgent || step.httpsAgent || this._testConfig.configData.httpsAgent || undefined; if (httpsAgent) { httpsAgent = this._testConfig.setupHttpsAgent(httpsAgent); } const config = helpers.clone(request.config); const requestResult = {}; stepResult.onRequestError = requestResult.onRequestError = request.onRequestError || step.onRequestError || 'stopTest'; let requestContentType = ''; if ((_b = request === null || request === void 0 ? void 0 : request.config) === null || _b === void 0 ? void 0 : _b.headers) requestContentType = ((_c = request === null || request === void 0 ? void 0 : request.config) === null || _c === void 0 ? void 0 : _c.headers['Content-type']) || ((_d = request === null || request === void 0 ? void 0 : request.config) === null || _d === void 0 ? void 0 : _d.headers['Content-Type']) || ''; if (request.assertions && !stepResult.assertions) stepResult.assertions = []; if (config.data) { if (Array.isArray(config.data) && typeof (config === null || config === void 0 ? void 0 : config.data[0]) === 'string') config.data = config.data.join(''); if (typeof config.data === 'string') { const data = this._testConfig.replaceWithVarValue(config.data); const json = helpers.toJson(data); config.data = json || data; } else if (requestContentType === 'application/x-www-form-urlencoded') { const formData = this._testConfig.replaceFromJSON(config.data); config.data = qs_1.default.stringify(formData); // eslint-disable-next-line no-constant-condition } else if (typeof (config.data === 'object')) { config.data = this._testConfig.replaceFromJSON(config.data); } } let response = JSON.parse('{}'); let requestConfig = this.setConfigValues(config, true); this._prePostProcessors.runBeforeScripts(null, step, request, requestConfig); requestConfig = this.setConfigValues(requestConfig, false); if (httpsAgent) { httpsAgent.keepAlive = options.noKeepAlive ? false : true; const newConfig = JSON.stringify(httpsAgent); if (this._httpsAgentString === newConfig) { requestConfig.httpsAgent = this._httpsAgent; } else { requestConfig.httpsAgent = new https.Agent(httpsAgent); this._httpsAgentString = newConfig; this._httpsAgent = requestConfig.httpsAgent; } } const requestNum = idx + 1; const lapNum = lap + 1; requestResult.name = request.name ? this._testConfig.replaceWithVarValue(request.name) : `Request [${lapNum}_${requestNum}]`; requestResult.url = ''; requestResult.method = (requestConfig === null || requestConfig === void 0 ? void 0 : requestConfig.method) || 'get'; requestResult.requestHeaders = Object.assign({}, requestConfig.headers, apiConfig.headers); this._logger.debug('Step [%s]. Executing request: %s:%s, headers=%s', step.name, requestResult.method, requestConfig === null || requestConfig === void 0 ? void 0 : requestConfig.url, requestConfig.headers || ''); testResultProcessor_1.TestResultProcessor.initBaseResultItem(requestResult); try { response = yield api.request(requestConfig); } catch (error) { if (!error.response) { const status = error.errno || -1; const statusText = error.message; this._logger.fatal('%s %s', error.message, error.isAxiosError); requestResult.error = new Error(); requestResult.error.message = error.message; requestResult.error.name = error.name; requestResult.error.stack = ''; response = JSON.parse('{}'); response.status = status; response.statusText = statusText; } else { response = error.response; requestResult.error = new Error(error.message); requestResult.error.name = error.name; requestResult.error.stack = ''; } } if (request.expectedStatus) { let statusOK = true; if (Array.isArray(request.expectedStatus)) { statusOK = request.expectedStatus.find((status) => { return status === response.status; }) != undefined; } else if (!isNaN(request.expectedStatus)) { statusOK = response.status === request.expectedStatus; } foundError = !statusOK; if (statusOK) { requestResult.error = undefined; } } else { foundError = response.status < 200 || response.status > 299; } if (response.status) { requestResult.status = response.status; this._testConfig.setVariableValue('$status', response.status); const timings = testResultProcessor_1.TestResultProcessor.getTimings(response); if (timings && timings.totalTime > 0) { requestResult.timings = timings; const dollar = '$'; for (const key in timings) { const value = timings[key]; this._testConfig.setVariableValue(`${dollar}timings.${key}`, value); } } requestResult.statusText = response === null || response === void 0 ? void 0 : response.statusText; requestResult.headers = response.headers; if (!foundError && requestResult.status > 0) { const cl = response.headers['content-length']; if (cl !== undefined) requestResult.contentLength = Number(cl); else if (response.data) { if (typeof response.data === 'string') requestResult.contentLength = response.data.length; else { requestResult.contentLength = ((_e = JSON.stringify(response.data)) === null || _e === void 0 ? void 0 : _e.length) || 0; } } } this._testConfig.setVariableValue('$contentLength', requestResult.contentLength); requestResult.durationMs = timings && timings.totalTime > 0 ? timings.totalTime : Date.now() - requestResult.startTimestamp; this._testConfig.setVariableValue('$durationMs', requestResult.durationMs); if (request.extractors) { const results = this._prePostProcessors.extractValues(request.extractors, response); if (results.length) { if (!stepResult.assertions) stepResult.assertions = []; stepResult.assertions = [...stepResult.assertions, ...results]; foundError = true; this.saveAssertionAsError(requestResult, results); } } if (request.transformers) { this._prePostProcessors.transform(request.transformers); } if (!foundError) { const scriptResults = this._prePostProcessors.runAfterScripts(null, step, request, response, requestResult.timings); const scriptAssertions = this.addScriptAssertions(scriptResults, stepResult, requestResult); if (request.assertions) { const assertions = this._prePostProcessors.validate(requestResult, request.assertions); if (assertions.length) { if (!stepResult.assertions) stepResult.assertions = []; const failStep = assertions.find((res) => { return res.status === 'failure'; }); /** Stop polling when result found */ if (poll && !failStep) { poll = false; lap = laps; } assertions.forEach((assertion) => { var _a; if (!poll) (_a = stepResult.assertions) === null || _a === void 0 ? void 0 : _a.push(assertion); }); foundError = !poll && failStep !== undefined; if (foundError) { this.saveAssertionAsError(requestResult, assertions); } } } if (!helpers.isEmpty(scriptAssertions) && !foundError && !poll) { foundError = true; } } } if ((_f = response === null || response === void 0 ? void 0 : response.config) === null || _f === void 0 ? void 0 : _f.headers) requestResult.requestHeaders = (_g = response === null || response === void 0 ? void 0 : response.config) === null || _g === void 0 ? void 0 : _g.headers; requestResult.url = ((_j = (_h = response === null || response === void 0 ? void 0 : response.request) === null || _h === void 0 ? void 0 : _h._redirectable) === null || _j === void 0 ? void 0 : _j._currentUrl) || requestConfig.url || ''; if (options.proxy) { const m = /\/((http|https):.*)/.exec(requestResult.url); if (m && m[1]) { requestResult.url = m[1]; } } if (options.mask) { testResultProcessor_1.TestResultProcessor.maskRequestResults(this._testConfig.getVars(), requestResult); } else if (!options.noData && requestConfig.data) { requestResult.requestBody = requestConfig.data; } requestResult.success = !foundError; stepResult.success = !foundError; if (authMethod) { this._prePostProcessors.teardownAuthMethod(api, authMethod); } const bigContent = requestResult.contentLength > maxContentLength; if (bigContent) this._logger.debug('Content length [%d] is greater than max length to save [%d]. Result not saved ', requestResult.contentLength, maxContentLength); if (!bigContent && response.data) { if (request.alwaysSaveResponse || (!options.noData && !request.notSaveData)) { requestResult.responseBody = response.data; } } if (!poll) stepResult.requests.push(requestResult); stepResult.durationMs += requestResult.durationMs; stepResult.contentLength += requestResult.contentLength; stepResult.timings = testResultProcessor_1.TestResultProcessor.incrementTimings(stepResult.timings, requestResult.timings); this._logger.debug('Request success=%s, status=%d statusText=%s, has Request Body %s', !foundError, requestResult.status, requestResult.statusText, requestResult.requestBody ? true : false); if (foundError) { stepResult.success = false; switch (requestResult.onRequestError) { case 'nextRequest': foundError = false; break; case 'nextStep': foundError = true; break; } } if (request.message) { requestResult.message = this._testConfig.replaceWithVarValue(request.message); } if (requestIdleBetweenRequests && (lap === 0 || lap < laps - 1)) { this._logger.debug('Start idle %d ms between requests. Lap=%d of laps=%d', requestIdleBetweenRequests, lap, laps); yield helpers.sleep(requestIdleBetweenRequests); } requestResult.endTimestamp = Date.now(); if (requestResult.error) { if (!results.errors) results.errors = []; results.errors.push(requestResult.error); } } } } catch (error) { this._logger.error(error); } stepResult.endTimestamp = Date.now(); return stepResult; }); } run(options) { return __awaiter(this, void 0, void 0, function* () { const results = JSON.parse('{}'); results.name = this._testConfig.configData.name; this._testConfig.setVariableValue('$testName', results.name); results.baseURL = this._testConfig.configData.baseURL; results.resultVersion = results.producer = results.type = ''; results.flowControl = this._testConfig.configData.flowControl; if (this._testConfig.configData.message) { results.message = ''; } results.returnValue = 0; results.unit = 'ms'; testResultProcessor_1.TestResultProcessor.initBaseResultItem(results); results.variables = []; results.customMetrics = {}; this._testConfig.setResultVariables(results.variables); let idleBetweenRequest = 0; results.steps = []; if (!results.errors) { if (this._testConfig.configData.idleBetweenRequests) { let idleTime = this._testConfig.configData.idleBetweenRequests; if (isNaN(idleTime)) idleTime = this._testConfig.replaceWithVarValue(this._testConfig.configData.idleBetweenRequests); if (!isNaN(idleTime)) { idleBetweenRequest = idleTime; this._logger.debug('Idle between request=%d', idleBetweenRequest); } } try { let config = {}; if (this._testConfig.configData.config) { config = this.setConfigValues(this._testConfig.configData.config); api_1.Api.fixBasicAuth(config); } if (!config.headers) config.headers = { 'Content-Type': 'application/json', }; if (!options.noKeepAlive) { const httpAgent = new http.Agent({ keepAlive: true, }); const httpsAgent = new https.Agent({ keepAlive: true, }); config.httpAgent = httpAgent; config.httpsAgent = httpsAgent; } config.baseURL = this._testConfig.replaceWithVarValue(this._testConfig.configData.baseURL); if (options.proxy) { const proxyRegExp = /^(http|https):\/\/([\w\\.-]+)-?(:(\d+))?/; const proxyMatcher = proxyRegExp.exec(options.proxy); if (!proxyMatcher || proxyMatcher.length < 3) { throw `Invalid proxy settings ${config.proxy}`; } const protocol = proxyMatcher[1]; const host = proxyMatcher[2]; let port = protocol === 'http' ? 80 : 443; if (proxyMatcher[4]) { port = Number(proxyMatcher[4]); if (isNaN(port)) { throw `Invalid proxy settings. Port is not numeric ${config.proxy}`; } } config.proxy = { protocol: protocol, host: host, port: port, }; } const api = new api_1.Api(config, true); let testError = false; this._prePostProcessors.runBeforeScripts(this._testConfig.configData); for (let idx = 0; !testError && idx < this._testConfig.configData.steps.length; idx++) { const testStep = this._testConfig.configData.steps[idx]; if (!testStep.disabled) { this._logger.debug('Running step %s ', testStep.name); const stepResult = yield this.runTestStep(results, testStep, api, options, idleBetweenRequest, config); const scriptResults = this._prePostProcessors.runAfterScripts(this._testConfig.configData, testStep, undefined, undefined, stepResult.timings); const scriptError = this.addScriptAssertions(scriptResults, stepResult); if (stepResult.success && !helpers.isEmpty(scriptError)) { stepResult.success = false; if (!results.errors) results.errors = []; const m = `AssertionError: source=${scriptError.source}, ${scriptError.description}`; results.errors.push(m); } testError = !stepResult.success && results.flowControl !== 'Individual Tests'; if (testError && stepResult.onRequestError && stepResult.onRequestError === 'nextStep') { testError = false; } this._logger.debug('Step success=%s, durationMs=%d', stepResult.success, stepResult.durationMs); results.steps.push(stepResult); if (!stepResult.ignoreDuration) results.returnValue += stepResult.durationMs; results.durationMs += stepResult.durationMs; stepResult.durationMs = Number(stepResult.durationMs.toFixed(0)); results.contentLength += stepResult.contentLength; results.timings = testResultProcessor_1.TestResultProcessor.incrementTimings(results.timings, stepResult.timings); } else { this._logger.debug('Test step %s is disabled %s', testStep.name); } } results.success = !testError; } catch (error) { this._logger.error(error); } this._prePostProcessors.runAfterScripts(this._testConfig.configData, undefined, undefined, undefined, results.timings); if (isNaN(results.durationMs) === false) results.durationMs = Number(results.durationMs.toFixed(0)); testResultProcessor_1.TestResultProcessor.setCustomReturnValue(this._testConfig.configData, results); if (results.success) { if (this._testConfig.configData.message) { results.message = this._testConfig.replaceWithVarValue(this._testConfig.configData.message); } else { const numSteps = results.steps.length; let numRequests = 0; results.steps.forEach((step) => { numRequests += step.requests.length; }); results.message = `Number of steps executed=${numSteps}, number of requests=${numRequests}, duration=${results.durationMs} ms`; } } else if (!results.success && results.errors && results.errors[0]) { const error = results.errors[0]; results.message = error.toString() || ''; } } results.endTimestamp = Date.now(); this._logger.debug('Test success=%s, durationMs=%d real time=%d ', results.success, results.durationMs, results.endTimestamp - results.startTimestamp); const customMetrics = this._testConfig.setCustomMetrics(); if (customMetrics) { results.customMetrics = customMetrics; } else { delete results.customMetrics; } if (this._testConfig.configData.hideSteps) { results.steps = []; } return results; }); } addScriptAssertions(scriptResults, stepResult, requestResult) { let retAss = {}; const scriptError = scriptResults.find((result) => !result.ok); if (scriptError) { if (!stepResult.assertions) stepResult.assertions = []; scriptResults.forEach((result) => { var _a; if (!result.ok && result.error) { const assertion = { source: requestResult ? requestResult.name : stepResult.name, expression: `Script ${result.script.name} failed`, status: 'failure', description: result.error.message, value: result.error.name, }; retAss = assertion; const assertionResults = [assertion]; if (requestResult) { this.saveAssertionAsError(requestResult, assertionResults); } (_a = stepResult.assertions) === null || _a === void 0 ? void 0 : _a.push(assertion); } }); } return retAss; } } exports.TestRunner = TestRunner; //# sourceMappingURL=testRunner.js.map