Created
March 18, 2011 01:05
-
-
Save stellaraccident/875455 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Implements the http protocol bindings for the session client api. | |
* All of the guts are actually passed down to a handler object that | |
* is passed in at construction time. | |
*/ | |
var connect=require('connect'); | |
function ClientApiHttp(handler) { | |
var pageRouter=connect.router(function(app) { | |
app.post('/registerUserAgent', handleRegisterUserAgent); | |
app.post('/userData', handleUserData); | |
}); | |
// -- handler functions | |
function handleRegisterUserAgent(req, res, next) { | |
res.writeHead(501); | |
res.end(); | |
} | |
function handleUserData(req, res, next) { | |
res.writeHead(501); | |
res.end(); | |
} | |
return pageRouter; | |
} | |
module.exports=ClientApiHttp; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var inherits=require('sys').inherits; | |
var Stream=require('stream').Stream; | |
/** | |
* Add a property descriptor to an object for the property that | |
* will complain if it is accessed. | |
*/ | |
function mineProperty(obj, prop) { | |
Object.defineProperty(obj, prop, { | |
get: function() { | |
throw new Error('Attempt to access unsupported property "' + prop + '" on mock object.'); | |
}, | |
set: function() { | |
throw new Error('Attempt to set unsupported property "' + prop + '" on mock object.'); | |
} | |
}); | |
} | |
function MockServerRequest(options) { | |
Stream.call(this); | |
this.url=options.url; | |
// Get the request data array in one of a few ways | |
var body; | |
if (options.bodyJson) { | |
if (typeof options.bodyJson === 'string') body=options.bodyJson; | |
else body=JSON.stringify(options.bodyJson); | |
} else { | |
body=options.body; | |
} | |
// Data can either be a single anything or an array | |
// of anything. Any individual data item will either | |
// be a buffer or coerced to a buffer via an intermediate | |
// cast to string | |
// If an array, then this will result in multiple chunks | |
// being written (ie multiple data events) | |
if (body===null || body===undefined) this._data=[]; | |
else { | |
this._data=body; | |
if (!Array.isArray(this._data)) { | |
this._data=[this._data]; | |
} | |
} | |
this._dataIndex=0; | |
this._paused=true; | |
this._dataPending=false; | |
this._encoding=options.encoding||'utf8'; | |
this.httpVersionMajor=options.httpVersionMajor||1; | |
this.httpVersionMinor=options.httpVersionMinor||1; | |
this.httpVersion=this.httpVersionMajor + '.' + this.httpVersionMinor; | |
this.method=options.method||'GET'; | |
this.headers={}; | |
this.trailers={}; | |
this.readable=true; | |
// Normalize headers | |
var headers={}; | |
this.headers=headers; | |
if (options.headers) { | |
Object.keys(options.headers).forEach(function(k) { | |
var v=options.headers[k]; | |
headers[k.toLowerCase()]=v; | |
}); | |
} | |
mineProperty(this, 'socket'); | |
mineProperty(this, 'connection'); | |
mineProperty(this, 'client'); | |
mineProperty(this, 'destroy'); | |
} | |
inherits(MockServerRequest, Stream); | |
MockServerRequest.prototype.setEncoding=function(encoding) { | |
this._encoding=encoding; | |
}; | |
MockServerRequest.prototype.pause=function() { | |
if (this._paused) throw new Error('Unbalanced call to MockServerRequest.pause()'); | |
this._paused=true; | |
}; | |
MockServerRequest.prototype.resume=function() { | |
if (!this._paused) throw new Error('Unbalanced call to MockServerRequest.resume()'); | |
this._paused=false; | |
if (!this._dataPending) { | |
this._dataPending=true; | |
process.nextTick(this._pumpData.bind(this)); | |
} | |
}; | |
MockServerRequest.prototype._pumpData=function() { | |
this._dataPending=false; | |
if (this._paused) return; | |
if (this._dataIndex>=this._data.length) { | |
// Already done | |
return; | |
} | |
var chunk=this._data[this._dataIndex++]; | |
if (!Buffer.isBuffer(chunk)) { | |
chunk=new Buffer(String(chunk), this._encoding); | |
} | |
this.emit('data', chunk); | |
if (this._dataIndex>=this._data.length) { | |
this.emit('end'); | |
} else if (!this._paused) { | |
process.nextTick(this._pumpData.bind(this)); | |
} | |
}; | |
function MockServerResponse(responseState, callback) { | |
Stream.call(this); | |
this._callback=callback; | |
this._responseState=responseState||{}; | |
this._responseState.chunks=[]; | |
this._responseState.trailers={}; | |
this.writable=true; | |
this.finished=false; | |
mineProperty(this, 'writeContinue'); | |
mineProperty(this, 'useChunkedEncodingByDefault'); | |
mineProperty(this, 'shouldKeepAlive'); | |
mineProperty(this, 'assignSocket'); | |
mineProperty(this, 'detachSocket'); | |
mineProperty(this, 'destroy'); | |
} | |
inherits(MockServerResponse, Stream); | |
MockServerResponse.prototype.writeHead=function(statusCode) { | |
var responseState=this._responseState; | |
if (responseState.didWriteHead) { | |
throw new Error('Duplicate call to MockServerResponse.writeHead'); | |
} | |
responseState.didWriteHead=true; | |
var reasonPhrase, headers, headerIndex, normHeaders={}; | |
if (typeof arguments[1] === 'string') { | |
reasonPhrase=arguments[1]; | |
headerIndex=2; | |
} else { | |
reasonPhrase=null; | |
headerIndex=1; | |
} | |
if (typeof arguments[headerIndex] === 'object') { | |
headers=arguments[headerIndex]; | |
} else { | |
headers={}; | |
} | |
responseState.statusCode=statusCode; | |
responseState.reasonPhrase=reasonPhrase; | |
responseState.headers=normHeaders; | |
// Normalize headers and dynamic headers | |
Object.keys(headers).forEach(function(k) { | |
var lk=k.toLowerCase(); | |
if (lk in normHeaders) { | |
throw new Error('Duplicate mixed case headers in call to writeHead: ' + k); | |
} | |
normHeaders[lk]=headers[k]; | |
}); | |
if (responseState.dynamicHeaders) { | |
Object.keys(responseState.dynamicHeaders).forEach(function(lk) { | |
if (! (lk in normHeaders) ) { | |
normHeaders[lk]=responseState.dynamicHeaders[lk]; | |
} | |
}); | |
} | |
}; | |
MockServerResponse.prototype.writeHeader=function() { | |
this.writeHead.apply(this, arguments); | |
}; | |
MockServerResponse.prototype.setHeader=function(name, value) { | |
var responseState=this._responseState; | |
if (arguments.length<2) { | |
throw new Error('name and value are required for setHeader'); | |
} | |
if (responseState.didWriteHead) { | |
throw new Error('Cannot manipulate headers after call to writeHead'); | |
} | |
if (!responseState.dynamicHeaders) responseState.dynamicHeaders={}; | |
var key=name.toLowerCase(); | |
responseState.dynamicHeaders[key]=value; | |
}; | |
MockServerResponse.prototype.getHeader=function(name) { | |
var responseState=this._responseState; | |
if (arguments.length<1) { | |
throw new Error('name is required for getHeader'); | |
} | |
if (responseState.didWriteHead) { | |
throw new Error('Cannot manipulate headers after call to writeHead'); | |
} | |
var key=name.toLowerCase(); | |
if (responseState.dynamicHeaders) return responseState.dynamicHeaders[key]; | |
else return undefined; | |
}; | |
MockServerResponse.prototype.removeHeader=function(name) { | |
var responseState=this._responseState; | |
if (arguments.length<1) { | |
throw new Error('name is required for removeHeader'); | |
} | |
if (responseState.didWriteHead) { | |
throw new Error('Cannot manipulate headers after call to writeHead'); | |
} | |
var key=name.toLowerCase(); | |
if (responseState.dynamicHeaders) delete responseState.dynamicHeaders[key]; | |
}; | |
MockServerResponse.prototype.write=function(chunk, encoding) { | |
var responseState=this._responseState; | |
if (!responseState.didWriteHead) { | |
throw new Error('Cannot write until call to writeHead'); | |
} | |
responseState.chunks.push([chunk, encoding]); | |
}; | |
MockServerResponse.prototype.addTrailers=function(headers) { | |
throw new Error('Not implemented'); | |
}; | |
MockServerResponse.prototype.end=function(data, encoding) { | |
var responseState=this._responseState; | |
if (responseState.finished) { | |
throw new Error('Duplicate call to end()'); | |
} | |
if (!responseState.didWriteHead) { | |
this.writeHead(200); | |
} | |
if (data) { | |
responseState.chunks.push([data, encoding]); | |
} | |
this.finished=true; | |
responseState.finished=true; | |
if (this._callback) this._callback(); | |
}; | |
function MockResults(assert) { | |
this.assert=assert; | |
} | |
MockResults.prototype.nextErr=null; | |
MockResults.prototype.nextInvoked=false; | |
MockResults.prototype.assertStatusCode=function(expected) { | |
this.assert.equal(this.statusCode, expected, 'Expected http status code of ' + expected); | |
}; | |
MockResults.prototype.decodeChunks=function() { | |
var text=[]; | |
this.chunks.forEach(function(chunk) { | |
var data=chunk[0], encoding=chunk[1]; | |
if (Buffer.isBuffer(data)) { | |
text.push(new Buffer(data, encoding)); | |
} else { | |
text.push(String(data)); | |
} | |
}); | |
return text.join(''); | |
}; | |
Object.defineProperty(MockResults.prototype, 'bodyText', { | |
get: function() { | |
if ('_bodyText' in this) return this._bodyText; | |
this._bodyText=this.decodeChunks(); | |
return this._bodyText; | |
} | |
}); | |
Object.defineProperty(MockResults.prototype, 'bodyJson', { | |
get: function() { | |
if ('_bodyJson' in this) return this._bodyJson; | |
try { | |
this._bodyJson=JSON.parse(this.bodyText); | |
} catch (e) { | |
throw new Error('Mock result expected to be json but was not: ' + this.bodyText); | |
} | |
return this._bodyJson; | |
} | |
}); | |
function execute(handler, options, callback) { | |
var results, req, res, next; | |
var assert=options.assert || require('assert'); | |
function assertFail(msg) { | |
assert.ok(false, msg); | |
} | |
function done() { | |
assert.ok(results.finished, 'Request did not finish'); | |
callback(results); | |
} | |
results=new MockResults(assert); | |
req=new MockServerRequest(options); | |
res=new MockServerResponse(results, done); | |
next=function(err) { | |
results.nextErr=err; | |
results.nextInvoked=true; | |
if (err && !options.expectNextErr) { | |
// 99% of the time this is a failure - treat it as such | |
// unless if told not to | |
assertFail('Mock http execute invoked next with an unexpected error: ' + err); | |
} else if (!options.expectNext) { | |
// Most things under test shouldn't invoke next so error by default. | |
// unless if expectNext is given | |
assertFail('Mock http execute invoked next unexpectedly (this usually means the middleware opted to not respond to the request)'); | |
} else if (results.didWriteHead) { | |
// Illegal to call next after having written headers | |
assertFail('Illegal call to next callback after writing http headers'); | |
} | |
results.didWriteHead=true; | |
results.finished=true; | |
// Otherwise, we're done here | |
done(); | |
}; | |
process.nextTick(function() { | |
req.resume(); | |
handler(req, res, next); | |
}); | |
} | |
// -- exports | |
exports.MockServerRequest=MockServerRequest; | |
exports.MockServerResponse=MockServerResponse; | |
exports.execute=execute; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require('./setup'); | |
var nodeunit=require('nodeunit'); | |
var ClientApiHttp=require('../lib/clientApiHttp'); | |
var ClientApiHandler=require('../lib/clientApiHandler'); | |
var mockhttp=require('mockhttp'); | |
module.exports=nodeunit.testCase({ | |
setUp: function(callback) { | |
this.handler=new ClientApiHandler(); | |
this.middleware=new ClientApiHttp(this.handler); | |
callback(); | |
}, | |
'test for smoke': function(test) { | |
test.done(); | |
}, | |
'test /registerUserAgent': function(test) { | |
mockhttp.execute(this.middleware, { | |
assert: test, | |
method: 'POST', | |
url: '/registerUserAgent' | |
}, function(results) { | |
results.assertStatusCode(200); | |
test.done(); | |
}); | |
} | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This is a snapshot of some code I was writing to do mock testing of http based handlers and a very trivial example showing usage.