Skip to content

Commit

Permalink
Pull req and res mocking into core router's route() method
Browse files Browse the repository at this point in the history
  • Loading branch information
mikermcneil committed May 12, 2014
1 parent bae740d commit 678e1c0
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 206 deletions.
50 changes: 13 additions & 37 deletions lib/app/request.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
*/

var _ = require('lodash');
var QS = require('querystring');
var util = require('util');
var MockReq = require('mock-req');
var MockRes = require('mock-res');
var QS = require('querystring');
var buildReq = require('../router/req');
var buildRes = require('../router/res');
var Transform = require('stream').Transform;


Expand Down Expand Up @@ -130,46 +130,21 @@ module.exports = function request( /* address, body, cb */ ) {
});


// Build HTTP Server Response
var res = new MockRes();

// Build HTTP Server Request
var req = new MockReq({
method: opts.method,
url: opts.url,
headers: opts.headers
});
var req = buildReq(opts, {});

// Parse query string (`req.query`)
var queryStringPos = opts.url.indexOf('?');
req.query = {};
if (queryStringPos !== -1) {
req.query = QS.parse(opts.url.substr(queryStringPos + 1));
}
// console.log(req.query);

/// TODO: merge w/ lib/router/req.js and lib/hooks/sockets/lib/interpreter/*.js
req.body = '';
req.on('readable', function() {
var chunk;
while (null !== (chunk = req.read())) {
req.body += chunk;
}
});
req.on('end', function() {
try {
req.body = JSON.parse(req.body);
} catch (e) {}
// Build HTTP Server Response
var res = buildRes(req, {});

// Set up all things pushed to `res` on the server
// to be piped down the client response stream.
res.pipe(clientRes);
// Set up all things pushed to `res` on the server
// to be piped down the client response stream.
res.pipe(clientRes);

// Pass `req` and `res` to Sails
sails.router.route(req, res);

});
// To kick things off, pass `req` and `res` to the Sails router
sails.router.route(req, res);

// Write client request body to the simulated `req` (IncomingMessage)
// Req stream ends automatically if this is a GET or HEAD request
// - no need to do it again
if (opts.method !== 'GET' && opts.method !== 'HEAD' && opts.method !== 'DELETE') {
Expand All @@ -196,3 +171,4 @@ MockClientResponse.prototype._transform = function(chunk, encoding, next) {
this.push(chunk);
next();
};

228 changes: 61 additions & 167 deletions lib/router/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@
* Module dependencies.
*/

var _ = require('lodash');
var Writable = require('stream').Writable;
var QS = require('querystring');
var util = require('util');
var _ = require('lodash');
_.defaults = require('merge-defaults');
var express = require('express');
var Writable = require('stream').Writable;

var buildReq = require('./req');
var buildRes = require('./res');
var defaultHandlers = require('./bindDefaultHandlers');


Expand Down Expand Up @@ -133,45 +136,57 @@ Router.prototype.load = function(cb) {

Router.prototype.route = function(req, res, next) {
var sails = this.sails;
var _privateRouter = this._privateRouter;

// Use base req and res definitions to ensure the specified
// objects are at least ducktype-able as standard node HTTP
// req and req objects.
req = req || {};
_.defaults(req, { headers: {} });
res = res || {};
_.defaults(res, { locals: {} });
//
// Make sure request and response objects have reasonable defaults
// (will use the supplied definitions if possible)
req = buildReq(req, res);
res = buildRes(req, res);

// Bundle raw callback function
// Deprecation error:
res._cb = function noRouteCbSpecified(err) {
throw new Error('As of v0.10, `_cb()` shim is no longer supported in the Sails router.');
};

// Track request start time
req._startTime = new Date();

// Make sure request and response objects have reasonable defaults
// (will use the supplied definitions if possible)
req = reasonableDefaultRequest(req);
res = reasonableDefaultResponse(req, res);
// Run some basic middleware
qsParser(req,res, function (err) {
if (err) {
res.statusCode = 400;
res.body = err;
}

// Use our private router to route the request
this._privateRouter.router(req, res, function handleUnmatchedNext(err) {
bodyParser(req,res, function (err) {
if (err) {
res.statusCode = 400;
res.body = err;
}

//
// In the event of an unmatched `next()`, `next('foo')`,
// or `next('foo', errorCode)`...
//
// Use our private router to route the request
_privateRouter.router(req, res, function handleUnmatchedNext(err) {

// Use the default server error handler
if (err) {
sails.emit('router:request:500', err, req, res);
return;
}
//
// In the event of an unmatched `next()`, `next('foo')`,
// or `next('foo', errorCode)`...
//

// Use the default server error handler
if (err) {
sails.emit('router:request:500', err, req, res);
return;
}

// Or the default not found handler
sails.emit('router:request:404', req, res);
return;
// Or the default not found handler
sails.emit('router:request:404', req, res);
return;
});
});
});

};
Expand Down Expand Up @@ -289,149 +304,28 @@ Router.prototype.flush = function(routes) {





//
// TODO:
// replace w/ req.js and res.js:
//

/**
* Ensure that request object has a minimum set of reasonable defaults.
* Used primarily as a test fixture.
*
* @api private
*/

function FakeSession() {}

function reasonableDefaultRequest(req) {
// if (req.params && req.method) {
// return req;
// }
// else {
return _.defaults(req || {}, {
params: {},
session: new FakeSession(),
query: {},
body: {},
param: function(paramName) {

var key, params = {};
for (key in (req.params || {}) ) {
params[key] = params[key] || req.params[key];
}
for (key in (req.query || {}) ) {
params[key] = params[key] || req.query[key];
}
for (key in (req.body || {}) ) {
params[key] = params[key] || req.body[key];
}

// Grab the value of the parameter from the appropriate place
// and return it
return params[paramName];
},
wantsJSON: true,
method: 'GET'
});
// }
}


/**
* Ensure that response object has a minimum set of reasonable defaults
* Used primarily as a test fixture.
*
* @api private
*/

function reasonableDefaultResponse(req, res) {
if (typeof res !== 'object') {
res = new Writable();
// Extremely simple query string parser (`req.query`)
function qsParser(req,res,next) {
var queryStringPos = req.url.indexOf('?');
req.query = {};
if (queryStringPos !== -1) {
req.query = QS.parse(req.url.substr(queryStringPos + 1));
}

res.status = res.status || function status_shim (statusCode) {
res.statusCode = statusCode;
};

res.send = res.send || function send_shim () {
var args = normalizeResArgs(arguments);

if (!res.end || !res.write) {
return res._cb();
}
else {
res.statusCode = args.statusCode || res.statusCode || 200;

if (args.other) {
res.write(args.other);
}
res.end();
next();
}
// Extremely simple body parser (`req.body`)
function bodyParser (req, res, next) {
req.body = '';
req.on('readable', function() {
var chunk;
while (null !== (chunk = req.read())) {
req.body += chunk;
}
};

res.json = res.json || function json_shim () {
var args = normalizeResArgs(arguments);

});
req.on('end', function() {
try {
var json = JSON.stringify(args.other);
return res.send(json, args.statusCode || res.statusCode || 200);
}
catch(e) {
var failedStringify = new Error(
'Failed to stringify specified JSON response body :: ' + util.inspect(args.other) +
'\nError:\n' + util.inspect(e)
);
return res.send(failedStringify.stack, 500);
}
};

res.render = res.render || function render_shim (relativeViewPath, locals, cb) {
if (_.isFunction(arguments[1])) {
cb = arguments[1];
locals = {};
}
// console.log('\n\n\nRENDER\n\n', locals);//_.defaults(locals || {}, res.locals));
if (!req._sails.renderView) return res.send('Cannot call res.render() - `views` hook is not enabled', 500);
return res.send('Not implemented in core yet');
// return sails.renderView(relativeViewPath, _.defaults(locals || {}, res.locals), cb);
};


return res;


/**
* As long as one of them is a number (i.e. a status code),
* allows a 2-nary method to be called with flip-flopped arguments:
* method( [statusCode|other], [statusCode|other] )
*
* This avoids confusing errors & provides Express 2.x backwards compat.
*
* E.g. usage in res.send():
* var args = normalizeResArgs.apply(this, arguments),
* body = args.other,
* statusCode = args.statusCode;
*
* @api private
*/
function normalizeResArgs( args ) {

// Traditional usage:
// `method( other [,statusCode] )`
var isNumeric = function (x) {
return (+x === x);
};
if (isNumeric(args[0])) {
return {
statusCode: args[0],
other: args[1]
};
}
else return {
statusCode: args[1],
other: args[0]
};
}
req.body = JSON.parse(req.body);
} catch (e) { return next(e); }
next();
});
}
13 changes: 11 additions & 2 deletions lib/router/req.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ var MockReq = require('mock-req');
* tests-- both at the app-level and in Sails core.
*
* @return {Request} simulated HTTP request object
* @idempotent
*/

module.exports = function buildRequest (_req) {
Expand All @@ -30,9 +31,12 @@ module.exports = function buildRequest (_req) {
});
}

function FakeSession() {}
function FakeSession() {
// TODO: mimic the session store impl in sockets hook
// (all of this can drastically simplify the request interpreter)
}

return _.defaults(req || {}, {
req = _.defaults(req, {
params: [],
session: new FakeSession(),
query: {},
Expand All @@ -57,4 +61,9 @@ module.exports = function buildRequest (_req) {
wantsJSON: true,
method: 'GET'
});

return req;
};



1 change: 1 addition & 0 deletions lib/router/res.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ var MockRes = require('mock-res');
* Used primarily as a test fixture.
*
* @api private
* @idempotent
*/

module.exports = function buildResponse (req, _res) {
Expand Down

0 comments on commit 678e1c0

Please sign in to comment.