Skip to content

Commit

Permalink
mockModeNoCallback option and controller cache
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
moander committed Aug 5, 2016
1 parent cc89cb2 commit 533a18d
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 26 deletions.
83 changes: 57 additions & 26 deletions fittings/swagger_router.js
Expand Up @@ -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) {

Expand All @@ -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];
Expand All @@ -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);
}
};
}
1 change: 1 addition & 0 deletions lib/connect_middleware.js
Expand Up @@ -130,6 +130,7 @@ function translate(output, mimeType) {
return util.inspect(output)
}
}
module.exports.translateResponse = translate;

function hookResponseForValidation(context, eventEmitter) {

Expand Down

0 comments on commit 533a18d

Please sign in to comment.