@apica-io/url-xi
Version:
URL Check for integrations and API monitoring
585 lines • 34.8 kB
JavaScript
"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