restify-new-nodejs-compatible
Version:
REST framework
763 lines (664 loc) • 20.5 kB
JavaScript
;
/* eslint-disable func-names */
var url = require('url');
var restifyClients = require('restify-clients');
var errs = require('restify-errors');
var restify = require('../lib');
if (require.cache[__dirname + '/lib/helper.js']) {
delete require.cache[__dirname + '/lib/helper.js'];
}
var helper = require('./lib/helper.js');
///--- Globals
var after = helper.after;
var before = helper.before;
var test = helper.test;
var PORT = process.env.UNIT_TEST_PORT || 0;
var CLIENT;
var STRING_CLIENT;
var SERVER;
var LOCALHOST;
var SLOCALHOST;
before(function(cb) {
try {
SERVER = restify.createServer({
dtrace: helper.dtrace,
handleUncaughtExceptions: true,
log: helper.getLog('server'),
version: ['2.0.0', '0.5.4', '1.4.3']
});
SERVER.use(restify.plugins.queryParser());
SERVER.listen(PORT, '127.0.0.1', function() {
PORT = SERVER.address().port;
CLIENT = restifyClients.createJsonClient({
url: 'http://127.0.0.1:' + PORT,
dtrace: helper.dtrace,
retry: false
});
STRING_CLIENT = restifyClients.createStringClient({
url: 'http://127.0.0.1:' + PORT,
dtrace: helper.dtrace,
retry: false
});
LOCALHOST = 'http://' + '127.0.0.1:' + PORT;
SLOCALHOST = 'https://' + '127.0.0.1:' + PORT;
cb();
});
} catch (e) {
console.error(e.stack);
process.exit(1);
}
});
after(function(cb) {
try {
CLIENT.close();
STRING_CLIENT.close();
SERVER.close(function() {
CLIENT = null;
SERVER = null;
cb();
});
} catch (e) {
console.error(e.stack);
process.exit(1);
}
});
// helper for joining array into strings
function join() {
var args = [].slice.call(arguments, 0);
return args.join('');
}
test('redirect to new string url as-is', function(t) {
SERVER.get('/1', function(req, res, next) {
res.redirect('www.foo.com', next);
});
CLIENT.get(join(LOCALHOST, '/1'), function(err, _, res) {
t.ifError(err);
t.equal(res.statusCode, 302);
t.equal(res.headers.location, 'www.foo.com');
t.end();
});
});
test('redirect to new relative string url as-is', function(t) {
SERVER.get('/20', function(req, res, next) {
res.redirect('/1', next);
});
CLIENT.get(join(LOCALHOST, '/20?a=1'), function(err, _, res) {
t.ifError(err);
t.equal(res.statusCode, 302);
t.equal(res.headers.location, '/1');
t.end();
});
});
test('redirect to current url (reload)', function(t) {
SERVER.get('/2', function(req, res, next) {
res.redirect(
{
reload: true
},
next
);
});
CLIENT.get(join(LOCALHOST, '/2'), function(err, _, res) {
t.ifError(err);
t.equal(res.statusCode, 302);
t.equal(res.headers.location, join(LOCALHOST, '/2'));
t.end();
});
});
test('redirect to current url from http -> https', function(t) {
SERVER.get('/3', function(req, res, next) {
res.redirect(
{
secure: true
},
next
);
});
CLIENT.get(join(LOCALHOST, '/3'), function(err, _, res) {
t.ifError(err);
t.equal(res.statusCode, 302);
t.equal(res.headers.location, join(SLOCALHOST, '/3'));
t.end();
});
});
test('redirect to current url from https -> http', function(t) {
SERVER.get('/3', function(req, res, next) {
res.redirect(
{
reload: true,
secure: false
},
next
);
});
CLIENT.get(join(LOCALHOST, '/3'), function(err, _, res) {
t.ifError(err);
t.equal(res.statusCode, 302);
t.equal(res.headers.location, join(LOCALHOST, '/3'));
t.end();
});
});
test('redirect by changing path', function(t) {
SERVER.get('/4', function(req, res, next) {
res.redirect(
{
pathname: '1'
},
next
);
});
CLIENT.get(join(LOCALHOST, '/4'), function(err, _, res) {
t.ifError(err);
t.equal(res.statusCode, 302);
t.equal(res.headers.location, join(LOCALHOST, '/1'));
t.end();
});
});
test(
'GH-1494: redirect should succeed even if req.url does not specify host' +
' or protocol',
function(t) {
SERVER.get('/5', function(req, res, next) {
res.redirect(
{
pathname: '/'
},
next
);
});
// use a relative URL here instead of request with full
// protocol and host.
// this causes node to receive different values for req.url,
// which affects
// how reconstruction of the redirect URL is done. for example including
// full host will result in a req.url value of:
// http://127.0.0.1:57824/5
// using relative URL results in a req.url value of:
// /5
// this causes a bug as documented in GH-1494
CLIENT.get('/5', function(err, _, res) {
t.ifError(err);
t.equal(res.statusCode, 302);
t.equal(res.headers.location, '/');
t.end();
});
}
);
test('redirect should add query params', function(t) {
SERVER.get('/5', function(req, res, next) {
res.redirect(
{
query: {
a: 1
}
},
next
);
});
CLIENT.get(join(LOCALHOST, '/5'), function(err, _, res) {
t.ifError(err);
t.equal(res.statusCode, 302);
t.equal(res.headers.location, join(LOCALHOST, '/5?a=1'));
t.end();
});
});
test('redirect should extend existing query params', function(t) {
SERVER.get('/6', function(req, res, next) {
res.redirect(
{
query: {
b: 2
}
},
next
);
});
CLIENT.get(join(LOCALHOST, '/6?a=1'), function(err, _, res) {
t.ifError(err);
t.equal(res.statusCode, 302);
var parsedUrl = url.parse(res.headers.location, true);
t.deepEqual(parsedUrl.query, {
a: 1,
b: 2
});
t.equal(parsedUrl.query.b, 2);
t.equal(parsedUrl.pathname, '/6');
// t.equal(res.headers.location, join(LOCALHOST, '/6?a=1&b=2'));
t.end();
});
});
test('redirect should stomp over existing query params', function(t) {
SERVER.get('/7', function(req, res, next) {
res.redirect(
{
overrideQuery: true,
query: {
b: 2
}
},
next
);
});
CLIENT.get(join(LOCALHOST, '/7?a=1'), function(err, _, res) {
t.ifError(err);
t.equal(res.statusCode, 302);
t.equal(res.headers.location, join(LOCALHOST, '/7?b=2'));
t.end();
});
});
test('redirect with 301 status code', function(t) {
SERVER.get('/8', function(req, res, next) {
res.redirect(
{
permanent: true
},
next
);
});
CLIENT.get(join(LOCALHOST, '/8'), function(err, _, res) {
t.ifError(err);
t.equal(res.statusCode, 301);
t.equal(res.headers.location, join(LOCALHOST, '/8'));
t.end();
});
});
test('redirect with 301 status code ising string url', function(t) {
SERVER.get('/30', function(req, res, next) {
res.redirect(301, '/foo', next);
});
CLIENT.get(join(LOCALHOST, '/30'), function(err, _, res) {
t.ifError(err);
t.equal(res.statusCode, 301);
t.equal(res.headers.location, '/foo');
t.end();
});
});
test('redirect using options.url', function(t) {
SERVER.get('/8', function(req, res, next) {
res.redirect(
{
hostname: 'www.foo.com',
pathname: '/8',
query: {
a: 1
}
},
next
);
});
CLIENT.get(join(LOCALHOST, '/8'), function(err, _, res) {
t.ifError(err);
t.equal(res.statusCode, 302);
t.equal(res.headers.location, 'http://www.foo.com/8?a=1');
t.end();
});
});
test('redirect using opts.port', function(t) {
SERVER.get('/9', function(req, res, next) {
res.redirect(
{
port: 3000
},
next
);
});
CLIENT.get(join(LOCALHOST, '/9'), function(err, _, res) {
t.ifError(err);
t.equal(res.statusCode, 302);
var parsedUrl = url.parse(res.headers.location, true);
t.equal(parsedUrl.port, 3000);
t.end();
});
});
test('redirect using external url and custom port', function(t) {
SERVER.get('/9', function(req, res, next) {
res.redirect(
{
hostname: 'www.foo.com',
pathname: '/99',
port: 3000
},
next
);
});
CLIENT.get(join(LOCALHOST, '/9'), function(err, _, res) {
t.ifError(err);
t.equal(res.statusCode, 302);
var parsedUrl = url.parse(res.headers.location, true);
t.equal(parsedUrl.port, 3000);
t.equal(parsedUrl.hostname, 'www.foo.com');
t.equal(parsedUrl.pathname, '/99');
t.end();
});
});
test('redirect using default hostname with custom port', function(t) {
SERVER.get('/9', function(req, res, next) {
res.redirect(
{
pathname: '/99',
port: 3000
},
next
);
});
CLIENT.get(join(LOCALHOST, '/9'), function(err, _, res) {
t.ifError(err);
t.equal(res.statusCode, 302);
var parsedUrl = url.parse(res.headers.location, true);
t.equal(parsedUrl.port, 3000);
t.equal(parsedUrl.pathname, '/99');
t.equal(res.headers.location, 'http://127.0.0.1:3000/99');
t.end();
});
});
// eslint-disable-next-line
test('redirect should cause InternalError when invoked without next', function(t) {
SERVER.get('/9', function(req, res, next) {
res.redirect();
});
CLIENT.get(join(LOCALHOST, '/9'), function(err, _, res, body) {
t.equal(res.statusCode, 500);
// json parse the response
t.equal(body.code, 'Internal');
t.end();
});
});
// eslint-disable-next-line
test('redirect should call next with false to stop handler stack execution', function(t) {
var wasRun = false;
function A(req, res, next) {
req.a = 1;
next();
}
function B(req, res, next) {
req.b = 2;
wasRun = true;
next();
}
function redirect(req, res, next) {
res.redirect('/10', next);
}
SERVER.get('/10', [A, redirect, B]);
CLIENT.get(join(LOCALHOST, '/10'), function(err, _, res) {
t.ifError(err);
t.equal(res.statusCode, 302);
t.equal(res.headers.location, '/10');
// handler B should not be executed
t.equal(wasRun, false);
t.end();
});
});
test('redirect should emit a redirect event', function(t) {
var wasEmitted = false;
var redirectLocation;
function preRedirectHandler(req, res, next) {
res.on('redirect', function(payload) {
wasEmitted = true;
redirectLocation = payload;
});
next();
}
function redirect(req, res, next) {
res.redirect('/10', next);
}
SERVER.get('/10', [preRedirectHandler, redirect]);
CLIENT.get(join(LOCALHOST, '/10'), function(err, _, res) {
t.ifError(err);
t.equal(res.statusCode, 302);
t.equal(res.headers.location, '/10');
// event 'redirect' should have been emitted
t.equal(wasEmitted, true);
t.equal(redirectLocation, '/10');
t.end();
});
});
test('writeHead should emit a header event', function(t) {
var wasEmitted = false;
var payloadPlaceholder;
// writeHead is called on each request
function handler(req, res, next) {
res.on('header', function(payload) {
wasEmitted = true;
payloadPlaceholder = payload;
});
res.send(302);
next();
}
SERVER.get('/10', [handler]);
CLIENT.get(join(LOCALHOST, '/10'), function(err, _, res) {
t.ifError(err);
t.equal(res.statusCode, 302);
// event 'header' should have been emitted
t.equal(wasEmitted, true);
t.equal(payloadPlaceholder, undefined);
t.end();
});
});
test('should fail to set header due to missing formatter', function(t) {
// when a formatter is not set up for a specific content-type, restify will
// default to octet-stream.
SERVER.get('/11', function handle(req, res, next) {
res.header('content-type', 'application/hal+json');
res.send(200, JSON.stringify({ hello: 'world' }));
return next();
});
CLIENT.get(join(LOCALHOST, '/11'), function(err, _, res) {
t.ifError(err);
t.equal(res.statusCode, 200);
t.equal(res.headers['content-type'], 'application/octet-stream');
t.end();
});
});
test('should not fail to send null as body', function(t) {
SERVER.get('/12', function handle(req, res, next) {
res.send(200, null);
return next();
});
CLIENT.get(join(LOCALHOST, '/12'), function(err, _, res) {
t.ifError(err);
t.equal(res.statusCode, 200);
t.end();
});
});
test('should not fail to send null as body without status code', function(t) {
SERVER.get('/13', function handle(req, res, next) {
res.send(null);
return next();
});
CLIENT.get(join(LOCALHOST, '/13'), function(err, _, res) {
t.ifError(err);
t.equal(res.statusCode, 200);
t.end();
});
});
test('should prefer explicit status code over error status code', function(t) {
SERVER.get('/14', function handle(req, res, next) {
res.send(200, new errs.InternalServerError('boom'));
return next();
});
CLIENT.get(join(LOCALHOST, '/14'), function(err, _, res, body) {
t.ifError(err);
t.equal(res.statusCode, 200);
// ensure error body was still sent
t.equal(body.code, 'InternalServer');
t.equal(body.message, 'boom');
t.end();
});
});
test('GH-951: should send without formatting', function(t) {
SERVER.get('/15', function handle(req, res, next) {
res.header('content-type', 'application/json');
res.sendRaw(
200,
JSON.stringify({
hello: 'world'
})
);
return next();
});
STRING_CLIENT.get(join(LOCALHOST, '/15'), function(err, _, res, body) {
t.ifError(err);
t.equal(
body,
JSON.stringify({
hello: 'world'
})
);
t.end();
});
});
test('GH-951: sendRaw accepts only strings or buffers', function(t) {
SERVER.on('uncaughtException', function(req, res, route, err) {
t.ok(err);
// Node v8 uses static error codes
// and `name` includes the error name and error code as well which
// caused this test to break. Just changing the logic to check for
// string instead
t.equal(err.name.indexOf('AssertionError') >= 0, true);
t.equal(err.message, 'res.sendRaw() accepts only strings or buffers');
t.end();
});
SERVER.get('/16', function handle(req, res, next) {
res.header('content-type', 'application/json');
res.sendRaw(200, {
hello: 'world'
});
return next();
});
// throw away response, we don't need it.
STRING_CLIENT.get(join(LOCALHOST, '/16'));
});
test('GH-1429: setting code with res.status not respected', function(t) {
SERVER.get('/404', function(req, res, next) {
res.status(404);
res.send(null);
});
CLIENT.get(join(LOCALHOST, '/404'), function(err, _, res) {
t.equal(res.statusCode, 404);
t.end();
});
});
test('should support multiple set-cookie headers', function(t) {
SERVER.get('/set-cookie', function(req, res, next) {
res.header('Set-Cookie', 'a=1');
res.header('Set-Cookie', 'b=2');
res.send(null);
});
CLIENT.get(join(LOCALHOST, '/set-cookie'), function(err, _, res) {
t.equal(res.headers['set-cookie'].length, 2);
t.end();
});
});
test('GH-1607: should send bools with explicit status code', function(t) {
SERVER.get('/bool/:value', function(req, res, next) {
res.send(200, req.params.value === 'true' ? true : false);
return next();
});
STRING_CLIENT.get(join(LOCALHOST, '/bool/false'), function(
err,
req,
res,
data
) {
t.equal(data, 'false');
STRING_CLIENT.get(join(LOCALHOST, '/bool/true'), function(
err2,
req2,
res2,
data2
) {
t.equal(data2, 'true');
t.end();
});
});
});
test('GH-1607: should send numbers with explicit status code', function(t) {
SERVER.get('/zero', function(req, res, next) {
res.send(200, 0);
return next();
});
SERVER.get('/one', function(req, res, next) {
res.send(200, 1);
return next();
});
STRING_CLIENT.get(join(LOCALHOST, '/zero'), function(err, req, res, data) {
t.equal(data, '0');
STRING_CLIENT.get(join(LOCALHOST, '/one'), function(
err2,
req2,
res2,
data2
) {
t.equal(data2, '1');
t.end();
});
});
});
test('GH-1791: should send 0 as 0 with application/json', function(t) {
SERVER.get('/zero', function(req, res, next) {
res.contentType = 'application/json';
res.send(200, 0);
return next();
});
STRING_CLIENT.get(join(LOCALHOST, '/zero'), function(err, req, res, data) {
t.equal(data, '0');
t.end();
});
});
test('GH-1791: should send false as false with application/json', function(t) {
SERVER.get('/false', function(req, res, next) {
res.contentType = 'application/json';
res.send(200, false);
return next();
});
STRING_CLIENT.get(join(LOCALHOST, '/false'), function(err, req, res, data) {
t.equal(data, 'false');
t.end();
});
});
// eslint-disable-next-line
test('GH-1791: should send empty string as "" with application/json', function(t) {
SERVER.get('/empty', function(req, res, next) {
res.contentType = 'application/json';
res.send(200, '');
return next();
});
STRING_CLIENT.get(join(LOCALHOST, '/empty'), function(err, req, res, data) {
t.equal(data, '""');
t.end();
});
});
test('GH-1791: should send null as null with application/json', function(t) {
SERVER.get('/null', function(req, res, next) {
res.contentType = 'application/json';
res.send(200, null);
return next();
});
STRING_CLIENT.get(join(LOCALHOST, '/null'), function(err, req, res, data) {
t.equal(data, 'null');
t.end();
});
});
// eslint-disable-next-line
test('GH-1791: should send undefined as empty with application/json', function(t) {
SERVER.get('/undef', function(req, res, next) {
res.contentType = 'application/json';
res.send(200, undefined);
return next();
});
STRING_CLIENT.get(join(LOCALHOST, '/undef'), function(err, req, res, data) {
t.equal(data, '');
t.end();
});
});
test('GH-1791: should send NaN as null with application/json', function(t) {
SERVER.get('/nan', function(req, res, next) {
res.contentType = 'application/json';
res.send(200, NaN);
return next();
});
STRING_CLIENT.get(join(LOCALHOST, '/nan'), function(err, req, res, data) {
t.equal(data, 'null');
t.end();
});
});