@ply-ct/ply
Version:
REST API Automated Testing
233 lines • 10.1 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;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.PlyRequest = void 0;
const cross_fetch_1 = __importDefault(require("cross-fetch"));
const response_1 = require("./response");
const log_1 = require("./log");
const result_1 = require("./result");
const form_1 = require("./form");
const util = __importStar(require("./util"));
const replace_1 = require("./replace");
const names_1 = require("./names");
class PlyRequest {
/**
* @param name test name
* @param obj object to parse for contents
*/
constructor(name, obj, logger, retrieval) {
this.name = name;
this.logger = logger;
this.type = 'request';
if (!obj.url) {
throw new Error(`Request '${name}' in ${retrieval} is missing 'url'`);
}
this.url = obj.url.trim();
if (!obj.method) {
throw new Error(`Request '${name}' in ${retrieval} is missing 'method'`);
}
this.method = obj.method.trim();
this.headers = obj.headers || {};
this.body = obj.body;
this.start = obj.start || 0;
this.end = obj.end;
}
getSupportedMethod(method) {
const upperCase = method.toUpperCase().trim();
if (upperCase === 'GET' ||
upperCase === 'HEAD' ||
upperCase === 'POST' ||
upperCase === 'PUT' ||
upperCase === 'DELETE' ||
upperCase === 'CONNECT' ||
upperCase === 'OPTIONS' ||
upperCase === 'TRACE' ||
upperCase === 'PATCH') {
return upperCase;
}
}
get isGraphQl() {
if (this.body) {
return this.body.startsWith('query') || this.body.startsWith('mutation');
}
return false;
}
getRunId(values) {
return values[names_1.RUN_ID];
}
/**
* Call submit() to send the request without producing actual results
* or comparing with expected. Useful for cleaning up or restoring
* REST resources before/after testing (see Case.before()/after()).
*/
async submit(values, options, runOptions) {
return await this.doSubmit(this.getRunId(values), this.getRequest(values, options, runOptions, true), options, runOptions);
}
async doSubmit(runId, requestObj, options, runOptions) {
const before = new Date().getTime();
const { Authorization: _auth, ...loggedHeaders } = requestObj.headers;
const loggedRequest = { ...requestObj, runId, headers: loggedHeaders };
if (runOptions === null || runOptions === void 0 ? void 0 : runOptions.submit) {
this.logger.info('Request', loggedRequest);
}
else {
this.logger.debug('Request', loggedRequest);
}
const ctHeader = util.header(requestObj.headers, 'content-type');
if (ctHeader && ctHeader[1].startsWith('multipart/form-data')) {
requestObj = new form_1.MultipartForm(requestObj).getRequest();
}
const { url: _url, ...fetchRequest } = requestObj;
fetchRequest.headers = { ...(fetchRequest.headers || {}) };
if (!Object.keys(fetchRequest.headers).find((k) => k.toLowerCase() === 'user-agent')) {
fetchRequest.headers['User-Agent'] = `Ply-CT/${await util.plyVersion()}`;
}
const response = await (0, cross_fetch_1.default)(requestObj.url, fetchRequest);
const status = { code: response.status, message: response.statusText };
const headers = this.responseHeaders(response.headers);
let body;
if (util.isBinary(headers, options)) {
body = await response.arrayBuffer();
}
else {
body = await response.text();
}
const time = new Date().getTime() - before;
const plyResponse = new response_1.PlyResponse(runId, status, headers, body, time);
if (runOptions === null || runOptions === void 0 ? void 0 : runOptions.submit) {
this.logger.info('Response', plyResponse);
}
else {
this.logger.debug('Response', plyResponse);
}
return plyResponse;
}
/**
* Request object with substituted values
*/
getRequest(values, options, runOptions, includeAuthHeader = false) {
const replaceOptions = { logger: this.logger, trusted: runOptions === null || runOptions === void 0 ? void 0 : runOptions.trusted };
const url = (0, replace_1.replace)(this.url, values, replaceOptions);
if (!url.startsWith('http://') && !url.startsWith('https://')) {
throw new Error('Invalid url: ' + url);
}
const method = (0, replace_1.replace)(this.method, values, replaceOptions).toUpperCase();
if (!this.getSupportedMethod(method)) {
throw new Error('Unsupported method: ' + method);
}
const headers = {};
for (const key of Object.keys(this.headers)) {
headers[key] = (0, replace_1.replace)(this.headers[key], values, replaceOptions);
}
if (!includeAuthHeader) {
delete headers.Authorization;
}
let body = this.body;
if (body) {
body = (0, replace_1.replace)(body, values, { logger: this.logger, trusted: runOptions === null || runOptions === void 0 ? void 0 : runOptions.trusted });
if (this.isGraphQl) {
// graphql
this.graphQl = body;
body = JSON.stringify({ query: body }, null, options === null || options === void 0 ? void 0 : options.prettyIndent);
}
}
return {
name: this.name,
type: this.type,
url,
method,
headers,
body,
submitted: this.submitted,
submit: () => {
throw new Error('Not implemented');
}
};
}
responseHeaders(headers) {
const obj = {};
headers.forEach((value, name) => {
obj[name] = value;
});
return obj;
}
/**
* Only to be called in the context of a Suite (hence 'runtime').
* To execute a test programmatically, call one of the Suite.run() overloads.
* Or to send a request without testing, call submit().
* @returns result with request invocation and status of 'Pending'
*/
async run(runtime, values, runOptions, runNum) {
var _a;
this.submitted = new Date();
const requestObject = this.getRequest(values, runtime.options, runOptions, true);
const id = this.logger.level === log_1.LogLevel.debug ? ` (${this.getRunId(values)})` : '';
this.logger.info(`Request '${this.name}'${id} submitted at ${util.timestamp(this.submitted, this.logger.level === log_1.LogLevel.debug)}`);
const runOpts = { ...runOptions };
const expectedExists = await runtime.results.expected.exists;
if ((runOptions === null || runOptions === void 0 ? void 0 : runOptions.submitIfExpectedMissing) && !expectedExists) {
runOpts.submit = true;
}
const runId = this.getRunId(values);
try {
const response = await this.doSubmit(runId, requestObject, runtime.options, runOpts);
if (response.headers &&
((runOptions === null || runOptions === void 0 ? void 0 : runOptions.createExpected) ||
((runOptions === null || runOptions === void 0 ? void 0 : runOptions.createExpectedIfMissing) && !expectedExists)) &&
((_a = runtime.options.genExcludeResponseHeaders) === null || _a === void 0 ? void 0 : _a.length)) {
for (const key of Object.keys(response.headers)) {
if (runtime.options.genExcludeResponseHeaders.includes(key)) {
delete response.headers[key];
}
}
}
const result = new result_1.PlyResult(this.name, requestObject, response.getResponse(runId, runtime.options, (runOptions === null || runOptions === void 0 ? void 0 : runOptions.submit) ? undefined : runtime.responseMassagers, true));
if (this.graphQl) {
result.graphQl = this.graphQl;
}
return result;
}
catch (err) {
this.logger.error(err.message, err);
let errMsg = err.message;
if (runNum)
errMsg += ` (run ${runNum})`;
const requestError = new Error(errMsg);
requestError.request = { ...requestObject };
requestError.request.headers = {};
Object.keys(requestObject.headers).forEach((key) => {
if (key !== 'Authorization') {
requestError.request.headers[key] = requestObject.headers[key];
}
});
throw requestError;
}
}
}
exports.PlyRequest = PlyRequest;
//# sourceMappingURL=request.js.map