Simon Priet e69a613a37 feat: Created a mini nodeJS server with NewMan for testing without PostMan GUI.
This will mimic a run in a CD/CI environment or docker container.
2021-09-08 14:01:19 +02:00

335 lines
10 KiB
JavaScript

/* istanbul ignore file */
// @todo
// 1. Return with annotations like (overridden headers, auth headers etc.)
// 2. Utilize requester (core.js) methods for dryRun
// 3. Add tests
const _ = require('lodash'),
async = require('async'),
mime = require('mime-types'),
urlEncoder = require('postman-url-encoder'),
Request = require('postman-collection').Request,
authorizeRequest = require('../authorizer').authorizeRequest,
authHandlers = require('../authorizer').AuthLoader.handlers,
version = require('../../package.json').version,
CALCULATED_AT_RUNTIME = '<calculated when request is sent>',
COOKIE = 'Cookie',
FUNCTION = 'function',
CONTENT_TYPE = 'Content-Type',
DEFAULT_MIME_TYPE = 'application/octet-stream',
CONTENT_TYPE_URLENCODED = 'application/x-www-form-urlencoded',
CONTENT_TYPE_FORMDATA = 'multipart/form-data; boundary=' + CALCULATED_AT_RUNTIME,
CONTENT_TYPE_LANGUAGE = {
'html': 'text/html',
'text': 'text/plain',
'json': 'application/json',
'javascript': 'application/javascript',
'xml': 'application/xml'
},
BODY_MODE = {
raw: 'raw',
file: 'file',
graphql: 'graphql',
formdata: 'formdata',
urlencoded: 'urlencoded'
};
/**
* Check if request body is empty and also handles disabled params for urlencoded
* and formdata bodies.
*
* @todo Update Collection SDK isEmpty method to account for this.
*
* @private
* @param {RequestBody} body
* @returns {Boolean}
*/
function bodyIsEmpty (body) {
if (!body || body.disabled || body.isEmpty()) {
return true;
}
var i,
param,
mode = body.mode;
if (!(mode === BODY_MODE.formdata || mode === BODY_MODE.urlencoded)) {
return false;
}
for (i = body[mode].members.length - 1; i >= 0; i--) {
param = body[mode].members[i];
// bail out if a single enabled param is present
if (param && !param.disabled) {
return false;
}
}
return true;
}
/**
* Add new System header.
*
* @param {object} headers
* @param {String} key
* @param {String} value
*/
function addSystemHeader (headers, key, value) {
headers.add({
key: key,
value: value,
system: true
});
}
/**
* Authorize the given request.
*
* @private
* @param {Request} request
* @param {Function} callback
*/
function setAuthorization (request, callback) {
authorizeRequest(request, function (err, clonedRequest) {
// @note authorizeRequest returns a cloned request.
!clonedRequest && (clonedRequest = new Request(request.toJSON()));
if (err) {
return callback(null, clonedRequest);
}
var auth = request.auth,
authType = auth && auth.type,
manifest = _.get(authHandlers, [authType, 'manifest']),
headers = _.get(clonedRequest, 'headers.reference') || {},
queryParams = _.get(clonedRequest, 'url.query.reference') || {},
bodyParams = _.get(clonedRequest, 'body.urlencoded.reference') || {},
propertyList,
propertyKey,
property;
if (authType === 'apikey' && (auth = auth.apikey)) {
propertyKey = String(auth.get('key')).toLowerCase();
propertyList = auth.get('in') === 'query' ? queryParams : headers;
if ((property = propertyList[propertyKey])) {
Array.isArray(property) && (property = property[property.length - 1]);
property.auth = true;
}
return callback(null, clonedRequest);
}
if (!(manifest && manifest.updates)) {
return callback(null, clonedRequest);
}
manifest.updates.forEach(function (update) {
propertyKey = update.property;
switch (update.type) {
case 'header':
propertyKey = propertyKey.toLowerCase();
propertyList = headers;
break;
case 'url.param':
propertyList = queryParams;
break;
case 'body.urlencoded':
propertyList = bodyParams;
break;
default: return;
}
if ((property = propertyList[propertyKey])) {
Array.isArray(property) && (property = property[property.length - 1]);
property.auth = true;
}
});
callback(null, clonedRequest);
});
}
/**
* Adds Content-Type header based on selected request body.
*
* @private
* @param {Request} request
* @param {Function} callback
*/
function setContentType (request, callback) {
// bail out if body is empty
if (bodyIsEmpty(request.body)) {
return callback(null, request);
}
var headers = request.headers,
contentLanguage;
switch (request.body.mode) {
case BODY_MODE.raw:
contentLanguage = _.get(request, 'body.options.raw.language', 'text');
addSystemHeader(headers, CONTENT_TYPE, CONTENT_TYPE_LANGUAGE[contentLanguage] ||
CONTENT_TYPE_LANGUAGE.text);
break;
case BODY_MODE.urlencoded:
addSystemHeader(headers, CONTENT_TYPE, CONTENT_TYPE_URLENCODED);
break;
case BODY_MODE.formdata:
addSystemHeader(headers, CONTENT_TYPE, CONTENT_TYPE_FORMDATA);
break;
case BODY_MODE.graphql:
addSystemHeader(headers, CONTENT_TYPE, CONTENT_TYPE_LANGUAGE.json);
break;
case BODY_MODE.file:
addSystemHeader(headers, CONTENT_TYPE,
mime.lookup(request.body.file && request.body.file.src) || DEFAULT_MIME_TYPE);
break;
default: break;
}
addSystemHeader(headers, 'Content-Length', CALCULATED_AT_RUNTIME);
callback(null, request);
}
/**
* Adds Cookie header for the given request url.
*
* @private
* @param {Request} request
* @param {Object} cookieJar
* @param {Function} callback
*/
function setCookie (request, cookieJar, callback) {
// bail out if not a valid instance of CookieJar
if (!(cookieJar && cookieJar.getCookieString)) {
return callback(null, request);
}
// @note don't pass request.url instance to force re-parsing of the URL
cookieJar.getCookieString(urlEncoder.toNodeUrl(request.url.toString()), function (err, cookies) {
if (err) {
return callback(null, request);
}
if (cookies && cookies.length) {
addSystemHeader(request.headers, COOKIE, cookies);
}
callback(null, request);
});
}
/**
* A helper method to dry run the given request instance.
* It returns the cloned request instance with the system added properties.
*
* @param {Request} request
* @param {Object} options
* @param {Object} options.cookieJar
* @param {Object} options.protocolProfileBehavior
* @param {Function} done
*/
function dryRun (request, options, done) {
if (!done && typeof options === FUNCTION) {
done = options;
options = {};
}
if (!Request.isRequest(request)) {
return done(new Error('Invalid Request instance'));
}
!options && (options = {});
var cookieJar = options.cookieJar,
implicitCacheControl = options.implicitCacheControl,
implicitTraceHeader = options.implicitTraceHeader,
disabledSystemHeaders = _.get(options.protocolProfileBehavior, 'disabledSystemHeaders') || {},
disableCookies = _.get(options.protocolProfileBehavior, 'disableCookies');
async.waterfall([
function setAuthorizationHeaders (next) {
setAuthorization(request, next);
},
function setContentTypeHeader (request, next) {
setContentType(request, next);
},
function setContentLength (request, next) {
var headers = request.headers,
header = headers.one('content-length');
// bail out if header added by body helper
if (header && header.system) {
return next(null, request);
}
switch (String(request.method).toUpperCase()) {
case 'GET':
case 'HEAD':
case 'TRACE':
case 'DELETE':
case 'CONNECT':
case 'OPTIONS':
break;
default:
addSystemHeader(headers, 'Content-Length', '0');
break;
}
next(null, request);
},
function setCookieHeader (request, next) {
if (disableCookies || !cookieJar) {
return next(null, request);
}
setCookie(request, cookieJar, next);
},
function setStaticHeaders (request, next) {
var headers = request.headers;
// remove header added by auth helpers
headers.remove(function (header) {
return header.system && header.key.toLowerCase() === 'host';
});
addSystemHeader(headers, 'User-Agent', 'PostmanRuntime/' + version);
addSystemHeader(headers, 'Accept', '*/*');
addSystemHeader(headers, 'Accept-Encoding', 'gzip, deflate, br');
addSystemHeader(headers, 'Host', CALCULATED_AT_RUNTIME);
addSystemHeader(headers, 'Connection', 'keep-alive');
implicitCacheControl && addSystemHeader(headers, 'Cache-Control', 'no-cache');
implicitTraceHeader && addSystemHeader(headers, 'Postman-Token', CALCULATED_AT_RUNTIME);
next(null, request);
},
function disableSystemHeaders (request, next) {
var headersReference = request.headers.reference,
header;
_.forEach(disabledSystemHeaders, function (disabled, headerKey) {
if (!disabled) { return; }
if ((header = headersReference[headerKey.toLowerCase()])) {
Array.isArray(header) && (header = header[header.length - 1]);
header.system && (header.disabled = true);
}
});
next(null, request);
}
], function (err, request) {
if (err) { return done(err); }
done(null, request);
});
}
module.exports = dryRun;