UNPKG

@luminati-io/mountebank

Version:

Over the wire test doubles

329 lines (291 loc) 12.2 kB
'use strict'; const exceptions = require('../util/errors.js'), helpers = require('../util/helpers.js'), compatibility = require('../models/compatibility.js'), dryRunValidator = require('../models/dryRunValidator.js'); /** * The controller that gets and deletes single imposters * @module */ /** * Creates the imposter controller * @param {Object} protocols - the protocol implementations supported by mountebank * @param {Object} imposters - The map of ports to imposters * @param {Object} logger - The logger * @param {Boolean} allowInjection - Whether injection is allowed or not * @returns {{get, del}} */ function create (protocols, imposters, logger, allowInjection) { function isFlagSet (query, key) { if (!helpers.defined(query[key])) { return false; } return query[key].toLowerCase() === 'true'; } /** * The function responding to GET /imposters/:id * @memberOf module:controllers/imposterController# * @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') }, imposter = await imposters.get(request.params.id), json = await imposter.toJSON(options); response.format({ json: () => response.send(json), html: () => response.render('imposter', { imposter: json }) }); } /** * Corresponds to DELETE /imposters/:id/savedProxyResponses * Removes all saved proxy responses * @memberOf module:controllers/imposterController# * @param {Object} request - the HTTP request * @param {Object} response - the HTTP response * @returns {Object} A promise for testing */ async function resetProxies (request, response) { const options = { replayable: false, removeProxies: false }, imposter = await imposters.get(request.params.id); await imposters.stubsFor(request.params.id).deleteSavedProxyResponses(); const json = await imposter.toJSON(options); response.format({ json: () => response.send(json), html: () => response.render('imposter', { imposter: json }) }); } /** * Corresponds to DELETE /imposters/:id/savedRequests * Removes all saved requests * @memberOf module:controllers/imposterController# * @param {Object} request - the HTTP request * @param {Object} response - the HTTP response * @returns {Object} A promise for testing */ async function resetRequests (request, response) { const imposter = await imposters.get(request.params.id); await imposter.resetRequests(); const json = await imposter.toJSON(); response.format({ json: () => response.send(json), html: () => response.render('imposter', { imposter: json }) }); } /** * The function responding to DELETE /imposters/:id * @memberOf module:controllers/imposterController# * @param {Object} request - the HTTP request * @param {Object} response - the HTTP response * @returns {Object} A promise for testing */ async function del (request, response) { const options = { replayable: isFlagSet(request.query, 'replayable'), removeProxies: isFlagSet(request.query, 'removeProxies') }, imposter = await imposters.get(request.params.id); if (imposter) { const json = await imposter.toJSON(options); await imposters.del(request.params.id); response.send(json); } else { response.send({}); } } /** * The function responding to POST /imposters/:id/_requests * This is what protocol implementations call to send the JSON request * structure to mountebank, which responds with the JSON response structure * @memberOf module:controllers/imposterController# * @param {Object} request - the HTTP request * @param {Object} response - the HTTP response * @returns {Object} - the promise */ async function postRequest (request, response) { const imposter = await imposters.get(request.params.id), jsonResponse = await imposter.getResponseFor(request.body.request); response.send(jsonResponse); } /** * The function responding to POST /imposters/:id/_requests/:proxyResolutionKey * This is what protocol implementations call after proxying a request so * mountebank can record the response and add behaviors to * @memberOf module:controllers/imposterController# * @param {Object} request - the HTTP request * @param {Object} response - the HTTP response * @returns {Object} - the promise */ async function postProxyResponse (request, response) { const proxyResolutionKey = request.params.proxyResolutionKey, proxyResponse = request.body.proxyResponse, imposter = await imposters.get(request.params.id), json = await imposter.getProxyResponseFor(proxyResponse, proxyResolutionKey); response.send(json); } async function validateStubs (imposter, newStubs) { const errors = []; if (!helpers.defined(newStubs)) { errors.push(exceptions.ValidationError("'stubs' is a required field")); } else if (!Array.isArray(newStubs)) { errors.push(exceptions.ValidationError("'stubs' must be an array")); } if (errors.length > 0) { return Promise.resolve({ isValid: false, errors }); } const request = await imposter.toJSON(), Protocol = protocols[request.protocol], validator = dryRunValidator.create({ testRequest: Protocol.testRequest, testProxyResponse: Protocol.testProxyResponse, additionalValidation: Protocol.validate, allowInjection: allowInjection }); request.stubs = newStubs; compatibility.upcast(request); return validator.validate(request, logger); } function respondWithValidationErrors (response, validationErrors, statusCode = 400) { logger.error(`error changing stubs: ${JSON.stringify(exceptions.details(validationErrors))}`); response.statusCode = statusCode; response.send({ errors: validationErrors }); } /** * The function responding to PUT /imposters/:id/stubs * Overwrites the stubs list without restarting the imposter * @memberOf module:controllers/imposterController# * @param {Object} request - the HTTP request * @param {Object} response - the HTTP response * @returns {Object} - promise for testing */ async function putStubs (request, response) { const imposter = await imposters.get(request.params.id), stubs = imposters.stubsFor(request.params.id), newStubs = request.body.stubs, result = await validateStubs(imposter, newStubs); if (result.isValid) { await stubs.overwriteAll(newStubs); const json = await imposter.toJSON(); response.send(json); } else { respondWithValidationErrors(response, result.errors); } } async function validateStubIndex (stubs, index) { const allStubs = await stubs.toJSON(); const errors = []; if (typeof allStubs[index] === 'undefined') { errors.push(exceptions.ValidationError("'stubIndex' must be a valid integer, representing the array index position of the stub to replace")); } return { isValid: errors.length === 0, errors }; } /** * The function responding to PUT /imposters/:id/stubs/:stubIndex * Overwrites a single stub without restarting the imposter * @memberOf module:controllers/imposterController# * @param {Object} request - the HTTP request * @param {Object} response - the HTTP response * @returns {Object} - promise for testing */ async function putStub (request, response) { const imposter = await imposters.get(request.params.id), stubs = imposters.stubsFor(request.params.id), validation = await validateStubIndex(stubs, request.params.stubIndex); if (validation.isValid) { const newStub = request.body, result = await validateStubs(imposter, [newStub]); if (result.isValid) { await stubs.overwriteAtIndex(newStub, request.params.stubIndex); const json = await imposter.toJSON(); response.send(json); } else { respondWithValidationErrors(response, result.errors); } } else { respondWithValidationErrors(response, validation.errors, 404); } } function validateNewStub (index, allStubs, newStub) { const errors = []; if (typeof index !== 'number' || index < 0 || index > allStubs.length) { errors.push(exceptions.ValidationError("'index' must be between 0 and the length of the stubs array")); } if (typeof newStub === 'undefined') { errors.push(exceptions.ValidationError("must contain 'stub' field")); } return { isValid: errors.length === 0, errors }; } /** * The function responding to POST /imposters/:port/stubs * Creates a single stub without restarting the imposter * @memberOf module:controllers/imposterController# * @param {Object} request - the HTTP request * @param {Object} response - the HTTP response * @returns {Object} - promise for testing */ async function postStub (request, response) { const imposter = await imposters.get(request.params.id), stubs = imposters.stubsFor(request.params.id), allStubs = await stubs.toJSON(), newStub = request.body.stub, index = typeof request.body.index === 'undefined' ? allStubs.length : request.body.index, validation = validateNewStub(index, allStubs, newStub); if (validation.isValid) { const result = await validateStubs(imposter, [newStub]); if (result.isValid) { await stubs.insertAtIndex(newStub, index); const json = await imposter.toJSON(); response.send(json); } else { respondWithValidationErrors(response, result.errors); } } else { respondWithValidationErrors(response, validation.errors); } } /** * The function responding to DELETE /imposters/:port/stubs/:stubIndex * Removes a single stub without restarting the imposter * @memberOf module:controllers/imposterController# * @param {Object} request - the HTTP request * @param {Object} response - the HTTP response * @returns {Object} - promise for testing */ async function deleteStub (request, response) { const imposter = await imposters.get(request.params.id), stubs = imposters.stubsFor(request.params.id), validation = await validateStubIndex(stubs, request.params.stubIndex); if (validation.isValid) { await stubs.deleteAtIndex(request.params.stubIndex); const json = await imposter.toJSON(); response.send(json); } else { respondWithValidationErrors(response, validation.errors, 404); } } return { get, del, resetProxies, resetRequests, postRequest, postProxyResponse, putStubs, putStub, postStub, deleteStub }; } module.exports = { create };