echoecho
Version:
Simple testing echo responses to HTTP requests
937 lines (882 loc) • 33.9 kB
JavaScript
var vows = require('vows'),
assert = require('assert'),
http = require('http'),
parse = require('url').parse,
qs = require('querystring'),
fs = require('fs'),
echoecho = require('../lib/echo'),
ip = process.env.IP || '127.0.0.1',
port = process.env.PORT || 8181;
//Server to test against.
var server = http.createServer(function(req, res) {
var conf = {
dirroot: __dirname
};
if (req.url.indexOf('-express') > -1) {
conf = null;
var p = parse(req.url);
req.url = req.url.replace('-express', '');
req.body = qs.parse(p.query);
echoecho.serve(req, res, conf);
} else if (echoecho.handle(req)) {
echoecho.serve(req, res, conf);
} else {
if (req.url.indexOf('skipthisrequest') > -1) {
echoecho.serve(req, res, conf);
} else {
res.writeHead(200);
res.end('DEFAULT');
}
}
});
server.listen(port);
var fetch = function(o, callback) {
var err = null,
method = o.method || 'get',
fn = method.toUpperCase();
var headers = { };
if (o.body) {
headers['Content-Type'] = 'application/x-www-form-urlencoded';
headers['Content-Length'] = o.body.length;
}
var request = http.request({
url: ip,
port: port,
path: o.path,
method: fn,
headers: headers
}, function(res) {
var data = '';
res.on('data', function(c) {
data += c;
});
res.on('end', function() {
callback(err, {
body: data,
headers: res.headers,
code: res.statusCode
});
});
}).on('error', function(e) {
err = e;
});
if (o.body) {
request.write(o.body);
}
request.end();
};
var tests = {
'should be loaded': {
topic: function () {
return echoecho
},
'and have load method': function (topic) {
assert.isFunction(topic.load);
},
'and have handle method': function (topic) {
assert.isFunction(topic.handle);
},
'and have serve method': function (topic) {
assert.isFunction(topic.serve);
},
'and have paths method': function (topic) {
assert.isFunction(topic.paths);
},
'and have scheme object': function (topic) {
assert.isObject(topic.scheme);
},
'and have BASE object': function (topic) {
assert.isObject(topic.BASE);
},
'and have EchoEcho Function': function (topic) {
assert.isFunction(topic.EchoEcho);
},
'and should load paths': {
topic: function() {
echoecho.paths([
'/foo/bar/baz/index.html',
'/foo/bar/bat/',
'/foo/bar/not'
]);
return echoecho.BASE;
},
"and has 3 paths": function(topic) {
assert.equal(Object.keys(topic).length, 3);
},
"and removes .html": function(topic) {
assert.isTrue(topic['/foo/bar/baz']);
},
"and removes trailing slash": function(topic) {
assert.isTrue(topic['/foo/bar/bat']);
},
"and handles no trailing slash": function(topic) {
assert.isTrue(topic['/foo/bar/not']);
},
'and use handler with object': {
topic: function() {
return echoecho.handle({
url: '/foo/bar/baz/echo/status/500'
});
},
'should return true': function(topic) {
assert.isTrue(topic);
}
},
'and use handler with string': {
topic: function() {
return echoecho.handle('/foo/bar/baz/echo/jsonp');
},
'should return true': function(topic) {
assert.isTrue(topic);
}
},
'and use validator with object': {
topic: function() {
return echoecho.validate({
url: '/foo/bar/baz/echo/status/500'
});
},
'should return true': function(topic) {
assert.equal(topic, '500');
}
},
'and use validator with string': {
topic: function() {
return echoecho.validate('/foo/bar/baz/echo/jsonp');
},
'should return true': function(topic) {
assert.equal(topic, 'jsonp');
}
},
"and default response": {
topic: function() {
fetch({
method: 'GET',
path: '/'
}, this.callback);
},
"should be returned": function(topic) {
assert.equal(topic.code, 200);
assert.equal(topic.body, 'DEFAULT');
}
},
"and get status 200": {
topic: function() {
fetch({
method: 'GET',
path: '/foo/bar/baz/echo/status/200'
}, this.callback);
},
"with OK body": function(topic) {
assert.equal(topic.headers['access-control-allow-origin'], '*');
assert.equal(topic.code, 200);
assert.equal(topic.body, 'OK');
}
},
"and get status 404": {
topic: function() {
fetch({
method: 'GET',
path: '/foo/bar/baz/echo/status/404'
}, this.callback);
},
"with Not Found body": function(topic) {
assert.equal(topic.headers['access-control-allow-origin'], '*');
assert.equal(topic.code, 404);
assert.equal(topic.body, 'Not Found');
}
},
"and get status asdf": {
topic: function() {
fetch({
method: 'GET',
path: '/foo/bar/baz/echo/status/asdf'
}, this.callback);
},
"with Not Found body": function(topic) {
assert.equal(topic.code, 500);
assert.equal(topic.body, 'Unknown Echo Status');
}
},
"and get unknown scheme route": {
topic: function() {
fetch({
method: 'GET',
path: '/foo/bar/baz/echo/asdfasdf'
}, this.callback);
},
"with Not Found body": function(topic) {
assert.equal(topic.code, 404);
assert.equal(topic.body, 'Not Found');
}
},
"and get no scheme route": {
topic: function() {
fetch({
method: 'GET',
path: '/foo/bar/baz/echo/'
}, this.callback);
},
"with Not Found body": function(topic) {
assert.equal(topic.code, 404);
assert.equal(topic.body, 'Not Found');
}
},
"and get unknown scheme route that is unhandled": {
topic: function() {
fetch({
method: 'GET',
path: '/foo/bar/baz/echo/skipthisrequest'
}, this.callback);
},
"with Not Found body": function(topic) {
assert.equal(topic.code, 404);
assert.equal(topic.body, 'Not Found');
}
},
"and get query": {
topic: function() {
fetch({
method: 'GET',
path: '/foo/bar/baz/echo/get?foo=bar'
}, this.callback);
},
"with query body": function(topic) {
assert.equal(topic.headers['access-control-allow-origin'], '*');
assert.equal(topic.code, 200);
assert.equal(topic.body, 'foo=bar');
}
},
"and get no query": {
topic: function() {
fetch({
method: 'GET',
path: '/foo/bar/baz/echo/get'
}, this.callback);
},
"with query body": function(topic) {
assert.equal(topic.headers['access-control-allow-origin'], '*');
assert.equal(topic.code, 200);
assert.equal(topic.body, '');
}
},
"and get mismatch": {
topic: function() {
fetch({
method: 'POST',
path: '/foo/bar/baz/echo/get?foo=bar'
}, this.callback);
},
"with query body": function(topic) {
assert.equal(topic.code, 403);
assert.equal(topic.body, 'foo=bar');
}
},
"and post": {
topic: function() {
fetch({
method: 'POST',
path: '/foo/bar/baz/echo/post',
body: 'foo=bar'
}, this.callback);
},
"with query body": function(topic) {
assert.equal(topic.code, 200);
assert.equal(topic.body, 'foo=bar');
}
},
"and post with no body": {
topic: function() {
fetch({
method: 'POST',
path: '/foo/bar/baz/echo/post',
body: ''
}, this.callback);
},
"with query body": function(topic) {
assert.equal(topic.code, 204);
assert.equal(topic.body, '');
}
},
"and post mismatch": {
topic: function() {
fetch({
method: 'PUT',
path: '/foo/bar/baz/echo/post',
body: 'foo=bar'
}, this.callback);
},
"with query body": function(topic) {
assert.equal(topic.code, 403);
assert.equal(topic.body, 'foo=bar');
}
},
"and delete": {
topic: function() {
fetch({
method: 'DELETE',
path: '/foo/bar/baz/echo/delete?foo=bar'
}, this.callback);
},
"with query body": function(topic) {
assert.equal(topic.code, 200);
assert.equal(topic.body, 'foo=bar');
}
},
"and delete with no query": {
topic: function() {
fetch({
method: 'DELETE',
path: '/foo/bar/baz/echo/delete'
}, this.callback);
},
"with query body": function(topic) {
assert.equal(topic.code, 200);
assert.equal(topic.body, '');
}
},
"and delete mismatch": {
topic: function() {
fetch({
method: 'GET',
path: '/foo/bar/baz/echo/delete?foo=bar'
}, this.callback);
},
"with query body": function(topic) {
assert.equal(topic.code, 403);
assert.equal(topic.body, 'foo=bar');
}
},
"and put": {
topic: function() {
fetch({
method: 'PUT',
path: '/foo/bar/baz/echo/put',
body: 'foo=bar'
}, this.callback);
},
"with query body": function(topic) {
assert.equal(topic.code, 200);
assert.equal(topic.body, 'foo=bar');
}
},
"and put mismatch": {
topic: function() {
fetch({
method: 'PUT',
path: '/foo/bar/baz/echo/post',
body: 'foo=bar'
}, this.callback);
},
"with query body": function(topic) {
assert.equal(topic.code, 403);
assert.equal(topic.body, 'foo=bar');
}
},
"and get default json": {
topic: function() {
fetch({
method: 'GET',
path: '/foo/bar/baz/echo/json'
}, this.callback);
},
"with query body": function(topic) {
assert.equal(topic.code, 200);
assert.equal(topic.body, '{ "echo": true }');
}
},
"and post default json": {
topic: function() {
fetch({
method: 'POST',
path: '/foo/bar/baz/echo/json'
}, this.callback);
},
"with query body": function(topic) {
assert.equal(topic.code, 200);
assert.equal(topic.body, '{ "echo": true }');
}
},
"and get custom json": {
topic: function() {
var self = this,
url = '/foo/bar/baz/echo/json?foo=bar&baz=world';
fetch({
method: 'GET',
path: url
}, function(err, data) {
var q = parse(url);
data.expected = JSON.stringify(qs.parse(q.query));
self.callback(err, data);
});
},
"with query body": function(topic) {
assert.equal(topic.code, 200);
assert.equal(topic.headers['content-type'], 'application/json');
assert.equal(topic.body, topic.expected);
}
},
"and post custom json": {
topic: function() {
var self = this,
body = 'foo=bar&baz=world&do=not',
url = '/foo/bar/baz/echo/json?' + body;
fetch({
method: 'POST',
path: url,
body: body
}, function(err, data) {
data.expected = JSON.stringify(qs.parse(body));
self.callback(err, data);
});
},
"with query body": function(topic) {
assert.equal(topic.code, 200);
assert.equal(topic.headers['content-type'], 'application/json');
assert.equal(topic.body, topic.expected);
}
},
'and handle invalid custom json': {
topic: function() {
fetch({
path: '/foo/bar/baz/echo/json?response=invalidJSON'
}, this.callback);
},
'with query body': function(res) {
assert.equal(res.body, 'Bad Request');
}
},
"and post custom json, express style": {
topic: function() {
var self = this,
body = 'foo=bar&baz=world&do=not',
url = '/foo/bar/baz/echo/json-express?' + body;
fetch({
method: 'POST',
path: url,
body: body
}, function(err, data) {
data.expected = JSON.stringify(qs.parse(body));
self.callback(err, data);
});
},
"with query body": function(topic) {
assert.equal(topic.code, 200);
assert.equal(topic.headers['content-type'], 'application/json');
assert.equal(topic.body, topic.expected);
}
},
"and get default jsonp": {
topic: function() {
fetch({
method: 'GET',
path: '/foo/bar/baz/echo/jsonp?callback=baz'
}, this.callback);
},
"with query body": function(topic) {
assert.equal(topic.code, 200);
assert.equal(topic.body, 'baz({"echo":true,"callback":"baz"});');
}
},
"and get default jsonp with empty querystring (?&callback=baz)": {
topic: function() {
fetch({
method: 'GET',
path: '/foo/bar/baz/echo/jsonp?&callback=baz'
}, this.callback);
},
"with query body": function(topic) {
assert.equal(topic.code, 200);
assert.equal('baz({"echo":true,"callback":"baz"});', topic.body);
}
},
"and get default jsonp without callback": {
topic: function() {
fetch({
method: 'GET',
path: '/foo/bar/baz/echo/jsonp?baz'
}, this.callback);
},
"with query body": function(topic) {
assert.equal(topic.code, 403);
var b = JSON.parse(topic.body);
assert.equal(b.echo, 'error, no callback');
}
},
"and post default jsonp": {
topic: function() {
fetch({
method: 'POST',
path: '/foo/bar/baz/echo/jsonp?callback=foo'
}, this.callback);
},
"with query body": function(topic) {
assert.equal(topic.code, 200);
assert.equal(topic.body, 'foo({"echo":true,"callback":"foo"});');
}
},
"and get custom jsonp": {
topic: function() {
var self = this,
url = '/foo/bar/baz/echo/jsonp?callback=yo&foo=bar&baz=world';
fetch({
method: 'GET',
path: url
}, function(err, data) {
var q = parse(url);
var payload = qs.parse(q.query);
var callback = payload.callback;
delete payload.callback;
//payload.callback = callback;
data.expected = callback + '(' + JSON.stringify(payload) + ');';
self.callback(err, data);
});
},
"with query body": function(topic) {
assert.equal(topic.code, 200);
assert.equal(topic.headers['content-type'], 'application/json');
assert.equal(topic.body, topic.expected);
}
},
"and post custom jsonp": {
topic: function() {
var self = this,
url = '/foo/bar/baz/echo/jsonp?callback=yoyo&foo=bar&baz=world&do=not';
fetch({
method: 'POST',
path: url
}, function(err, data) {
var q = parse(url);
var payload = qs.parse(q.query);
var callback = payload.callback;
delete payload.callback;
data.expected = callback + '(' + JSON.stringify(payload) + ');';
self.callback(err, data);
});
},
"with query body": function(topic) {
assert.equal(topic.code, 200);
assert.equal(topic.headers['content-type'], 'application/json');
assert.equal(topic.body, topic.expected);
}
},
"and post custom jsonp, express style": {
topic: function() {
var self = this,
body = 'callback=yoyo&foo=bar&baz=world&do=not',
url = '/foo/bar/baz/echo/jsonp-express?' + body;
fetch({
method: 'POST',
path: url
}, function(err, data) {
var payload = qs.parse(body);
var callback = payload.callback;
delete payload.callback;
data.expected = callback + '(' + JSON.stringify(payload) + ');';
self.callback(err, data);
});
},
"with query body": function(topic) {
assert.equal(topic.code, 200);
assert.equal(topic.headers['content-type'], 'application/json');
assert.equal(topic.body, topic.expected);
}
},
"and post custom jsonp, express style without callback": {
topic: function() {
var body = 'foo=bar&baz=world&do=not',
url = '/foo/bar/baz/echo/jsonp-express?' + body;
fetch({
method: 'POST',
path: url
}, this.callback);
},
"with query body": function(topic) {
assert.equal(topic.code, 403);
assert.equal(topic.code, 403);
var b = JSON.parse(topic.body);
assert.equal(b.echo, 'error, no callback');
}
},
"and delay 3 seconds": {
topic: function() {
fetch({
method: 'GET',
path: '/foo/bar/baz/echo/delay/3'
}, this.callback);
},
"with query body": function(topic) {
assert.equal(topic.code, 200);
assert.equal(topic.body, 'waited for 3 seconds');
}
},
'and delay by a range of seconds': {
topic: function() {
fetch({
method: 'GET',
path: '/foo/bar/baz/echo/delay/1-3'
}, this.callback);
},
'with query body': function(res) {
var delay = res.body.match(/[\d.]+/);
delay = delay && parseFloat(delay);
assert(delay <= 3, 'delay should be less than 3 seconds');
assert(delay >= 1, 'delay should be more than 1 second');
assert.equal(res.code, 200);
}
},
'and default to 0 for bad delay values': {
topic: function() {
fetch({
method: 'GET',
path: '/foo/bar/baz/echo/delay/delay'
}, this.callback);
},
'with query body': function(res) {
assert.equal(res.code, 200);
assert.equal(res.body, 'waited for 0 seconds');
}
},
'should be instantitable': {
'should be different objects': {
topic: function() {
var one = new echoecho.EchoEcho({
paths: ['/one/one.html']
});
var two = new echoecho.EchoEcho({
paths: ['/two/one/', '/two/two/']
});
return [one, two]
},
'different paths': function(topic) {
assert.notDeepEqual(topic[0].BASE, topic[1].BASE);
}
},
'unknown queries with all option': {
'and use validator with object': {
topic: function() {
return new echoecho.EchoEcho({ all: true }).validate({
url: '/have/not/used/echo/status/500'
});
},
'should return "500"': function(topic) {
assert.equal(topic, '500');
}
},
'and use validator with string': {
topic: function() {
return new echoecho.EchoEcho({ all: true }).validate('/have/not/used/this/yet/echo/jsonp');
},
'should return "jsonp"': function(topic) {
assert.equal(topic, 'jsonp');
}
},
'and use validator with no echo': {
topic: function() {
return new echoecho.EchoEcho({ all: true }).validate('/have/not/used/this/yet/ech/jsonp');
},
'should return false': function(topic) {
assert.isFalse(topic);
}
}
},
'unknown queries without all option': {
'and use validator with object': {
topic: function() {
return new echoecho.EchoEcho().validate({
url: '/have/not/used/echo/status/500'
});
},
'should return false': function(topic) {
assert.isFalse(topic);
}
},
'and use validator with string': {
topic: function() {
return new echoecho.EchoEcho().validate('/have/not/used/this/yet/echo/jsonp');
},
'should return false': function(topic) {
assert.isFalse(topic);
}
}
}
}
}
}
};
Object.keys(http.STATUS_CODES).forEach(function(code) {
var s = http.STATUS_CODES[code],
c = Number(code, 10);
if (c < 200) {
return;
}
var test = {
topic: function() {
fetch({
method: 'GET',
path: '/foo/bar/baz/echo/status/' + code
}, this.callback);
}
};
test["with " + s + " body"] = function(topic) {
assert.equal(topic.code, code);
assert.equal(topic.body, s);
};
tests["should be loaded"]['and should load paths']["get status " + code] = test;
});
tests["should be loaded"]['and should load paths']["should load custom scheme"] = {
topic: function() {
this.count = Object.keys(echoecho.scheme);
echoecho.load({
'davglass': function(req, res) {
res.writeHead(200, echoecho.scheme.headers);
res.end('DAVGLASS WAS HERE');
}
});
return echoecho;
},
"should have a new scheme": function() {
assert.notEqual(this.count, Object.keys(echoecho.scheme));
assert.equal(this.count.length + 1, Object.keys(echoecho.scheme).length);
},
"and should use new scheme": {
topic: function() {
fetch({
method: 'GET',
path: '/foo/bar/baz/echo/davglass'
}, this.callback);
},
"custom scheme should respond": function(topic) {
assert.equal(topic.body, 'DAVGLASS WAS HERE');
}
}
};
// -- Setup for general testing of routes -------------------------------------
var testRoutes = [{
name: 'get',
method: 'GET'
}, {
name: 'post',
method: 'POST'
}, {
name: 'json',
method: 'GET'
}, {
name: 'json',
method: 'POST'
}, {
name: 'put',
method: 'PUT'
}, {
name: 'delete',
method: 'DELETE'
}
];
// -- `response` query parameter tests ----------------------------------------
function assertResponseParam(route, method) {
var json = '{"diaper":"huggies"}';
path = '/foo/bar/baz/echo/' + route + '?response=' + json
context = {
topic: function() {
fetch({
method: method,
path: path
}, this.callback);
}
};
context[route + ' route should use custom response'] = function (res) {
assert.equal(res.body, json);
}
return context;
}
tests['should be loaded']
['and should load paths']
['and should use custom response (query)'] = {};
testRoutes.forEach(function (route) {
tests['should be loaded']
['and should load paths']
['and should use custom response (query)']
['for route ' + route.name + ' with method ' + route.method]
= assertResponseParam(route.name, route.method);
});
// -- `file` query parameter tests --------------------------------------------
var file = fs.readFileSync(__dirname + '/fixtures/file.json', 'utf8');
function assertFileParam(route, method) {
var path = '/foo/bar/baz/echo/' + route + '?file=fixtures/file.json',
context = {
topic: function() {
fetch({
method: method,
path: path
}, this.callback);
}
};
context[route + ' route should use custom response'] = function (res) {
assert.equal(res.body, file);
};
return context;
}
tests['should be loaded']
['and should load paths']
['and should use custom response (file)'] = {};
testRoutes.forEach(function (route) {
tests['should be loaded']
['and should load paths']
['and should use custom response (file)']
['for route ' + route.name + ' with method ' + route.method]
= assertFileParam(route.name, route.method);
});
// -- delayable routes --------------------------------------------------------
function assertDelayedRoute(route, method) {
var json = '{"megatron":"decepticon","optimus":"autobot"}',
path = '/foo/bar/baz/echo/delay/1/' + route + '?response=' + json,
context = {
topic: function() {
fetch({
method: method,
path: path
}, this.callback);
}
};
context[route + ' route should be delayed'] = function (res) {
assert.equal(res.body, json);
}
return context;
}
tests['should be loaded']
['and should load paths']
['and routes should be delayable'] = {};
testRoutes.forEach(function (route) {
tests['should be loaded']
['and should load paths']
['and routes should be delayable']
['for route ' + route.name + ' with method ' + route.method]
= assertDelayedRoute(route.name, route.method);
});
// -- `type` query parameter tests --------------------------------------------
function assertContentType(route, method) {
var path = '/foo/bar/baz/echo/' + route + '?type=css';
context = {
topic: function() {
fetch({
method: method,
path: path
}, this.callback);
}
};
context[route + ' route should respond with specified content type'] = function (res) {
assert.equal(res.headers['content-type'], 'text/css');
}
return context;
}
tests['should be loaded']
['and should load paths']
['and should respond with specified content type'] = {};
testRoutes.forEach(function (route) {
tests['should be loaded']
['and should load paths']
['and should respond with specified content type']
['for route ' + route.name]
= assertContentType(route.name, route.method);
});
vows.describe('echoecho').addBatch(tests).export(module);