From 533a18d568a6b8cdfe10217677a3d57b21df73cd Mon Sep 17 00:00:00 2001 From: moander Date: Sat, 6 Aug 2016 01:31:38 +0200 Subject: [PATCH] mockModeNoCallback option and controller cache mockModeNoCallback config option makes it possible for mockMode to behave more like the real deal. swagger examples show res.end() in the controllers while mock mode use cb(). This makes a huge difference for projects using custom fitters after the router. --- fittings/swagger_router.js | 83 ++++++++++++++++++++++++++------------ lib/connect_middleware.js | 1 + 2 files changed, 58 insertions(+), 26 deletions(-) diff --git a/fittings/swagger_router.js b/fittings/swagger_router.js index 57d55df..0b1c5ce 100644 --- a/fittings/swagger_router.js +++ b/fittings/swagger_router.js @@ -5,6 +5,7 @@ var path = require('path'); var assert = require('assert'); var SWAGGER_ROUTER_CONTROLLER = 'x-swagger-router-controller'; var util = require('util'); +var translateResponse = require('../lib/connect_middleware').translateResponse; module.exports = function create(fittingDef, bagpipes) { @@ -20,14 +21,14 @@ module.exports = function create(fittingDef, bagpipes) { var controllersDirs = mockMode ? fittingDef.mockControllersDirs : fittingDef.controllersDirs; - controllersDirs = controllersDirs.map(function(dir) { + controllersDirs = controllersDirs.map(function (dir) { return path.resolve(appRoot, dir); }); var controllerFunctionsCache = {}; return function swagger_router(context, cb) { - debug('exec'); + debug('exec %s', context.request.swagger.operation.ptr || context.request.swagger.operation.pathObject.ptr); var operation = context.request.swagger.operation; var controllerName = operation[SWAGGER_ROUTER_CONTROLLER] || operation.pathObject[SWAGGER_ROUTER_CONTROLLER]; @@ -47,56 +48,86 @@ module.exports = function create(fittingDef, bagpipes) { try { controller = require(controllerPath); controllerFunctionsCache[controllerName] = controller; - debug('controller found', controllerPath); + debug('controller found', path.relative(appRoot, controllerPath)); break; } catch (err) { + debug('controller not in', path.relative(appRoot, controllerPath)); if (!mockMode && i === controllersDirs.length - 1) { return cb(err); } - debug('controller not in', controllerPath); } } } + if (!controller && mockMode) { + controller = controllerFunctionsCache[controllerName] = {}; + debug('created mock controller %s', controllerName); + } + if (controller) { var operationId = operation.definition['operationId'] || context.request.method.toLowerCase(); var controllerFunction = controller[operationId]; + if (!controllerFunction && mockMode) { + controllerFunction = controller[operationId] = createMockControllerFunction(fittingDef, bagpipes); + debug('created mock controller function %s.%s', controllerName, operationId); + } + if (controllerFunction && typeof controllerFunction === 'function') { - debug('running controller'); + debug('running controller %s.%s', controllerName, operationId); return controllerFunction(context.request, context.response, cb); } - var msg = util.format('Controller %s doesn\'t export handler function %s', controllerName, operationId); - if (mockMode) { - debug(msg); - } else { - return cb(new Error(msg)); - } + debug(util.format('Controller %s doesn\'t export handler function %s', controllerName, operationId)); + return cb(new Error(msg)); } - if (mockMode) { + // for completeness, we should never actually get here + cb(new Error(util.format('No controller found for %s in %j', controllerName, controllersDirs))); + } +}; + +function createMockControllerFunction(fittingDef, bagpipes) { + var swaggerNodeRunner = bagpipes.config.swaggerNodeRunner; + var appRoot = swaggerNodeRunner.config.swagger.appRoot; + + var mockModeNoCallback = !!fittingDef.mockModeNoCallback || !!swaggerNodeRunner.config.swagger.mockModeNoCallback; + + return function default_mock_controller(request, response, cb) { + debug('exec default_mock_controller') - var statusCode = parseInt(context.request.get('_mockreturnstatus')) || 200; + var operation = request.swagger.operation; + var controllerName = operation[SWAGGER_ROUTER_CONTROLLER] || operation.pathObject[SWAGGER_ROUTER_CONTROLLER]; - var mimetype = context.request.get('accept') || 'application/json'; - var mock = operation.getResponseExample(statusCode, mimetype); + var statusCode = parseInt(request.get('_mockreturnstatus')) || 200; - if (mock) { - debug('returning mock example value', mock); + var mimetype = request.get('accept'); + if (!mimetype || mimetype === '*/*') { + if (!operation.produces || !operation.produces.length || operation.produces.indexOf('application/json') >= 0) { + mimetype = 'application/json'; } else { - mock = operation.getResponseSample(statusCode); - debug('returning mock sample value', mock); + mimetype = operation.produces[0]; } + } - context.headers['Content-Type'] = mimetype; - context.statusCode = statusCode; + var mock = operation.getResponseExample(statusCode, mimetype); - return cb(null, mock); + if (mock) { + debug('returning mock example value', mock); + } else { + mock = operation.getResponseSample(statusCode); + debug('returning mock sample value', mock); } - // for completeness, we should never actually get here - cb(new Error(util.format('No controller found for %s in %j', controllerName, controllersDirs))); - } -}; + response.setHeader('Content-Type', mimetype); + + response.statusCode = statusCode; + + if (mockModeNoCallback) { + response.end(translateResponse(mock, mimetype)); + } else { + cb(null, mock); + } + }; +} \ No newline at end of file diff --git a/lib/connect_middleware.js b/lib/connect_middleware.js index f9e41f7..f2e5fae 100644 --- a/lib/connect_middleware.js +++ b/lib/connect_middleware.js @@ -130,6 +130,7 @@ function translate(output, mimeType) { return util.inspect(output) } } +module.exports.translateResponse = translate; function hookResponseForValidation(context, eventEmitter) {