UNPKG

@luminati-io/mountebank

Version:

Over the wire test doubles

216 lines (183 loc) 7.88 kB
'use strict'; const exceptions = require('../util/errors.js'), helpers = require('../util/helpers.js'), compatibility = require('../models/compatibility.js'), dryRunValidator = require('../models/dryRunValidator'); /** * The controller that manages the list of imposters * @module */ /** * Creates the imposters controller * @param {Object} protocols - the protocol implementations supported by mountebank * @param {Object} imposters - The imposters repository * @param {Object} logger - The logger * @param {Boolean} allowInjection - Whether injection is allowed or not * @returns {{get, post, del, put}} */ function create (protocols, imposters, logger, allowInjection) { function isFlagFalse (query, key) { return !helpers.defined(query[key]) || query[key].toLowerCase() !== 'false'; } function isFlagSet (query, key) { return helpers.defined(query[key]) && query[key].toLowerCase() === 'true'; } function validatePort (port, errors) { const portIsValid = !helpers.defined(port) || (port.toString().indexOf('.') === -1 && port > 0 && port < 65536); if (!portIsValid) { errors.push(exceptions.ValidationError("invalid value for 'port'")); } } function validateProtocol (protocol, errors) { const Protocol = protocols[protocol]; if (!helpers.defined(protocol)) { errors.push(exceptions.ValidationError("'protocol' is a required field")); } else if (!Protocol) { errors.push(exceptions.ValidationError(`the ${protocol} protocol is not yet supported`)); } } function validate (request) { const errors = []; compatibility.upcast(request); validatePort(request.port, errors); validateProtocol(request.protocol, errors); if (errors.length > 0) { return Promise.resolve({ isValid: false, errors }); } else { const Protocol = protocols[request.protocol], validator = dryRunValidator.create({ testRequest: Protocol.testRequest, testProxyResponse: Protocol.testProxyResponse, additionalValidation: Protocol.validate, allowInjection: allowInjection }); return validator.validate(request, logger); } } function respondWithValidationErrors (response, validationErrors) { logger.error(`error creating imposter: ${JSON.stringify(exceptions.details(validationErrors))}`); response.statusCode = 400; response.send({ errors: validationErrors }); } function respondWithCreationError (response, error) { logger.error(`error creating imposter: ${JSON.stringify(exceptions.details(error))}`); response.statusCode = (error.code === 'insufficient access') ? 403 : 400; response.send({ errors: [error] }); } function requestDetails (request) { return `${helpers.socketName(request.socket)} => ${JSON.stringify(request.body)}`; } async function getAllJSON (queryOptions) { const allImposters = await imposters.all(), promises = allImposters.map(imposter => imposter.toJSON(queryOptions)); return Promise.all(promises); } /** * The function responding to GET /imposters * @memberOf module:controllers/impostersController# * @param {Object} request - the HTTP request * @param {Object} response - the HTTP response * @returns {Object} - the promise */ async function get (request, response) { const options = { replayable: isFlagSet(request.query, 'replayable'), removeProxies: isFlagSet(request.query, 'removeProxies'), list: !(isFlagSet(request.query, 'replayable') || isFlagSet(request.query, 'removeProxies')) }, impostersJSON = await getAllJSON(options); response.format({ json: () => response.send({ imposters: impostersJSON }), html: () => response.render('imposters', { imposters: impostersJSON }) }); } /** * The function responding to POST /imposters * @memberOf module:controllers/impostersController# * @param {Object} request - the HTTP request * @param {Object} response - the HTTP response * @returns {Object} A promise for testing purposes */ async function post (request, response) { logger.debug(requestDetails(request)); const validation = await validate(request.body), protocol = request.body.protocol; if (validation.isValid) { try { const imposter = await protocols[protocol].createImposterFrom(request.body); await imposters.add(imposter); const json = await imposter.toJSON(); response.setHeader('Location', imposter.url); response.statusCode = 201; response.send(json); } catch (error) { respondWithCreationError(response, error); } } else { respondWithValidationErrors(response, validation.errors); } } /** * The function responding to DELETE /imposters * @memberOf module:controllers/impostersController# * @param {Object} request - the HTTP request * @param {Object} response - the HTTP response * @returns {Object} A promise for testing purposes */ async function del (request, response) { const options = { // default to replayable for backwards compatibility replayable: isFlagFalse(request.query, 'replayable'), removeProxies: isFlagSet(request.query, 'removeProxies') }, json = await getAllJSON(options); await imposters.deleteAll(); response.send({ imposters: json }); } /** * The function responding to PUT /imposters * @memberOf module:controllers/impostersController# * @param {Object} request - the HTTP request * @param {Object} response - the HTTP response * @returns {Object} A promise for testing purposes */ async function put (request, response) { const requestImposters = request.body.imposters || [], validationPromises = requestImposters.map(imposter => validate(imposter)); logger.debug(requestDetails(request)); if (!('imposters' in request.body)) { respondWithValidationErrors(response, [ exceptions.ValidationError("'imposters' is a required field") ]); return false; } const validations = await Promise.all(validationPromises), isValid = validations.every(validation => validation.isValid); if (!isValid) { const validationErrors = validations.reduce((accumulator, validation) => accumulator.concat(validation.errors), []); respondWithValidationErrors(response, validationErrors); return false; } await imposters.deleteAll(); try { const creationPromises = requestImposters.map(imposter => protocols[imposter.protocol].createImposterFrom(imposter) ), allImposters = await Promise.all(creationPromises); await Promise.all(allImposters.map(imposters.add)); const promises = allImposters.map(imposter => imposter.toJSON({ list: true })), json = await Promise.all(promises); response.send({ imposters: json }); } catch (error) { respondWithCreationError(response, error); } return true; } return { get, post, del, put }; } module.exports = { create };