UNPKG

hapi

Version:

HTTP Server framework

1,380 lines (968 loc) 70.1 kB
// Load modules var Lab = require('lab'); var Fs = require('fs'); var Stream = require('stream'); var Request = require('request'); var Hapi = require('../..'); var ResponseError = require('../../lib/response/error'); // Declare internals var internals = {}; // Test shortcuts var expect = Lab.expect; var before = Lab.before; var after = Lab.after; var describe = Lab.experiment; var it = Lab.test; describe('Response', function () { describe('Text', function () { it('returns a text reply', function (done) { var handler = function (request) { request.reply('text\0!') .type('text/plain') .encoding('ascii') .ttl(1000) .state('sid', 'abcdefg123456') .state('other', 'something', { isSecure: true }) .unstate('x') .header('Content-Type', 'text/plain; something=something') .code(200); }; var handlerBound = function () { this.reply('Tada'); }; var server = new Hapi.Server({ cors: { origin: ['test.example.com', 'www.example.com'] } }); server.route({ method: 'GET', path: '/', config: { handler: handler, cache: { expiresIn: 9999 } } }); server.route({ method: 'GET', path: '/bound', config: { handler: handlerBound } }); server.state('sid', { encoding: 'base64' }); server.state('always', { autoValue: 'present' }); server.ext('onPostHandler', function (request, next) { request.setState('test', '123'); request.clearState('empty'); next(); }); server.inject('/', function (res) { expect(res.statusCode).to.equal(200); expect(res.payload).to.exist; expect(res.payload).to.equal('text !'); expect(res.headers['cache-control']).to.equal('max-age=1, must-revalidate'); expect(res.headers['access-control-allow-origin']).to.equal('test.example.com www.example.com'); expect(res.headers['access-control-allow-credentials']).to.not.exist; expect(res.headers['set-cookie']).to.deep.equal(['sid=YWJjZGVmZzEyMzQ1Ng==', 'other=something; Secure', 'x=; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:00 GMT', "test=123", "empty=; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:00 GMT", "always=present"]); server.inject({ url: '/bound', headers: { origin: 'www.example.com' } }, function (res) { expect(res.result).to.exist; expect(res.result).to.equal('Tada'); expect(res.headers['access-control-allow-origin']).to.equal('www.example.com'); done(); }); }); }); it('returns an error on bad cookie', function (done) { var handler = function (request) { request.reply('text').state(';sid', 'abcdefg123456'); }; var server = new Hapi.Server(); server.route({ method: 'GET', path: '/', config: { handler: handler } }); server.inject('/', function (res) { expect(res.result).to.exist; expect(res.result.message).to.equal('An internal server error occurred'); expect(res.headers['set-cookie']).to.not.exist; done(); }); }); }); describe('Buffer', function () { it('returns a reply', function (done) { var handler = function () { this.reply(new Buffer('Tada1')).code(299); }; var server = new Hapi.Server(); server.route({ method: 'GET', path: '/', config: { handler: handler } }); server.inject('/', function (res) { expect(res.statusCode).to.equal(299); expect(res.result).to.equal('Tada1'); done(); }); }); }); describe('Obj', function () { it('returns an JSONP response', function (done) { var handler = function (request) { request.reply({ some: 'value' }); }; var server = new Hapi.Server(); server.route({ method: 'GET', path: '/', config: { jsonp: 'callback', handler: handler } }); server.inject('/?callback=me', function (res) { expect(res.payload).to.equal('me({"some":"value"});'); expect(res.headers['content-length']).to.equal(21); done(); }); }); it('returns an JSONP response when response is a buffer', function (done) { var handler = function (request) { request.reply(new Buffer('value')); }; var server = new Hapi.Server(); server.route({ method: 'GET', path: '/', config: { jsonp: 'callback', handler: handler } }); server.inject('/?callback=me', function (res) { expect(res.payload).to.equal('me(value);'); expect(res.headers['content-length']).to.equal(10); done(); }); }); it('returns response on bad JSONP parameter', function (done) { var handler = function (request) { request.reply({ some: 'value' }); }; var server = new Hapi.Server(); server.route({ method: 'GET', path: '/', config: { jsonp: 'callback', handler: handler } }); server.inject('/?callback=me*', function (res) { expect(res.result).to.exist; expect(res.result.message).to.equal('Invalid JSONP parameter value'); done(); }); }); }); describe('Error', function () { it('returns an error reply', function (done) { var handler = function (request) { request.reply(new Error('boom')); }; var server = new Hapi.Server(); server.route({ method: 'GET', path: '/', handler: handler }); server.inject('/', function (res) { expect(res.statusCode).to.equal(500); expect(res.result).to.exist; expect(res.result.message).to.equal('boom'); done(); }); }); it('returns an error response reply', function (done) { var handler = function (request) { var error = new ResponseError(Hapi.error.internal('kaboom')); request.reply(error); }; var server = new Hapi.Server(); server.once('internalError', function (request, err) { expect(err).to.exist; expect(err.trace[0]).to.contain('test/integration/response'); done(); }); server.route({ method: 'GET', path: '/', handler: handler }); server.inject('/', function (res) { expect(res.statusCode).to.equal(500); expect(res.result).to.exist; expect(res.result.message).to.equal('An internal server error occurred'); }); }); it('emits internalError when view file for handler not found', function (done) { var options = { views: { engines: { 'html': 'handlebars' }, path: __dirname } }; var server = new Hapi.Server(options); server.once('internalError', function (request, err) { expect(err).to.exist; expect(err.message).to.contain('View file not found'); done(); }); server.route({ method: 'GET', path: '/{param}', handler: { view: 'noview' } }); server.inject('/hello', function (res) { expect(res.statusCode).to.equal(500); expect(res.result).to.exist; expect(res.result.message).to.equal('An internal server error occurred'); }); }); }); describe('Empty', function () { var handler = function (request) { return request.reply().code(299); }; var server = new Hapi.Server({ cors: { credentials: true } }); server.route([ { method: 'GET', path: '/', handler: handler }, ]); it('returns an empty reply', function (done) { server.inject('/', function (res) { expect(res.statusCode).to.equal(299); expect(res.result).to.equal(''); expect(res.headers['access-control-allow-credentials']).to.equal('true'); done(); }); }); }); describe('File', function () { it('returns a file in the response with the correct headers', function (done) { var server = new Hapi.Server(0, { files: { relativeTo: 'routes' } }); var handler = function (request) { request.reply(new Hapi.response.File(__dirname + '/../../package.json')); }; server.route({ method: 'GET', path: '/file', handler: handler }); server.start(function () { Request.get(server.info.uri + '/file', function (err, res, body) { expect(err).to.not.exist; expect(body).to.contain('hapi'); expect(res.headers['content-type']).to.equal('application/json'); expect(res.headers['content-length']).to.exist; expect(res.headers['content-disposition']).to.not.exist; done(); }); }); }); it('returns a file in the response with the correct headers using cwd relative paths without content-disposition header', function (done) { var server = new Hapi.Server(0, { files: { relativeTo: 'cwd' } }); server.route({ method: 'GET', path: '/', handler: { file: './package.json' } }); server.start(function () { Request.get(server.info.uri, function (err, res, body) { expect(err).to.not.exist; expect(body).to.contain('hapi'); expect(res.headers['content-type']).to.equal('application/json'); expect(res.headers['content-length']).to.exist; expect(res.headers['content-disposition']).to.not.exist; done(); }); }); }); it('returns a file in the response with the inline content-disposition header when using route config', function (done) { var server = new Hapi.Server(0, { files: { relativeTo: 'cwd' } }); server.route({ method: 'GET', path: '/', handler: { file: { path: './package.json', mode: 'inline' } } }); server.start(function () { Request.get(server.info.uri, function (err, res, body) { expect(err).to.not.exist; expect(body).to.contain('hapi'); expect(res.headers['content-type']).to.equal('application/json'); expect(res.headers['content-length']).to.exist; expect(res.headers['content-disposition']).to.equal('inline; filename=package.json'); done(); }); }); }); it('returns a file in the response with the attachment content-disposition header when using route config', function (done) { var server = new Hapi.Server(0, { files: { relativeTo: 'cwd' } }); server.route({ method: 'GET', path: '/', handler: { file: { path: './package.json', mode: 'attachment' } } }); server.start(function () { Request.get(server.info.uri, function (err, res, body) { expect(err).to.not.exist; expect(body).to.contain('hapi'); expect(res.headers['content-type']).to.equal('application/json'); expect(res.headers['content-length']).to.exist; expect(res.headers['content-disposition']).to.equal('attachment; filename=package.json'); done(); }); }); }); it('returns a file in the response without the content-disposition header when using route config mode false', function (done) { var server = new Hapi.Server(0, { files: { relativeTo: 'cwd' } }); server.route({ method: 'GET', path: '/', handler: { file: { path: './package.json', mode: false } } }); server.start(function () { Request.get(server.info.uri, function (err, res, body) { expect(err).to.not.exist; expect(body).to.contain('hapi'); expect(res.headers['content-type']).to.equal('application/json'); expect(res.headers['content-length']).to.exist; expect(res.headers['content-disposition']).to.not.exist; done(); }); }); }); it('returns a file with correct headers when using attachment mode', function (done) { var server = new Hapi.Server(0, { files: { relativeTo: 'routes' } }); var handler = function (request) { request.reply(new Hapi.response.File(__dirname + '/../../package.json', { mode: 'attachment' })); }; server.route({ method: 'GET', path: '/file', handler: handler }); server.start(function () { Request.get(server.info.uri + '/file', function (err, res, body) { expect(err).to.not.exist; expect(body).to.contain('hapi'); expect(res.headers['content-type']).to.equal('application/json'); expect(res.headers['content-length']).to.exist; expect(res.headers['content-disposition']).to.equal('attachment; filename=package.json'); done(); }); }); }); it('returns a file with correct headers when using inline mode', function (done) { var server = new Hapi.Server(0, { files: { relativeTo: 'routes' } }); var handler = function (request) { request.reply(new Hapi.response.File(__dirname + '/../../package.json', { mode: 'inline' })); }; server.route({ method: 'GET', path: '/file', handler: handler }); server.start(function () { Request.get(server.info.uri + '/file', function (err, res, body) { expect(err).to.not.exist; expect(body).to.contain('hapi'); expect(res.headers['content-type']).to.equal('application/json'); expect(res.headers['content-length']).to.exist; expect(res.headers['content-disposition']).to.equal('inline; filename=package.json'); done(); }); }); }); it('returns a 404 when the file is not found', function (done) { var server = new Hapi.Server(0, { files: { relativeTo: '/no/such/path/x1' } }); server.route({ method: 'GET', path: '/filenotfound', handler: { file: 'nopes' } }); server.start(function () { Request.get(server.info.uri + '/filenotfound', function (err, res) { expect(err).to.not.exist; expect(res.statusCode).to.equal(404); done(); }); }); }); it('returns a 403 when the file is a directory', function (done) { var server = new Hapi.Server(0); server.route({ method: 'GET', path: '/filefolder', handler: { file: 'examples' } }); server.start(function () { Request.get(server.info.uri + '/filefolder', function (err, res) { expect(err).to.not.exist; expect(res.statusCode).to.equal(403); done(); }); }); }); var filenameFn = function (request) { return '../../' + request.params.file; }; it('returns a file using the build-in handler config', function (done) { var server = new Hapi.Server(0, { files: { relativeTo: 'routes' } }); server.route({ method: 'GET', path: '/staticfile', handler: { file: __dirname + '/../../package.json' } }); server.start(function () { Request.get(server.info.uri + '/staticfile', function (err, res, body) { expect(err).to.not.exist; expect(body).to.contain('hapi'); expect(res.headers['content-type']).to.equal('application/json'); expect(res.headers['content-length']).to.exist; done(); }); }); }); it('returns a file using the file function with the build-in handler config', function (done) { var server = new Hapi.Server(0, { files: { relativeTo: 'routes' } }); server.route({ method: 'GET', path: '/filefn/{file}', handler: { file: filenameFn } }); server.start(function () { Request.get(server.info.uri + '/filefn/index.js', function (err, res, body) { expect(err).to.not.exist; expect(body).to.contain('./lib'); expect(res.headers['content-type']).to.equal('application/javascript'); expect(res.headers['content-length']).to.exist; done(); }); }); }); it('returns a file in the response with the correct headers (relative path)', function (done) { var server = new Hapi.Server(0, { files: { relativeTo: 'routes' } }); var relativeHandler = function (request) { request.reply(new Hapi.response.File('./package.json')); }; server.route({ method: 'GET', path: '/relativefile', handler: relativeHandler }); server.start(function () { Request.get(server.info.uri + '/relativefile', function (err, res, body) { expect(err).to.not.exist; expect(body).to.contain('hapi'); expect(res.headers['content-type']).to.equal('application/json'); expect(res.headers['content-length']).to.exist; done(); }); }); }); it('returns a file using the built-in handler config (relative path)', function (done) { var server = new Hapi.Server(0, { files: { relativeTo: 'routes' } }); server.route({ method: 'GET', path: '/relativestaticfile', handler: { file: '../../package.json' } }); server.start(function () { Request.get(server.info.uri + '/relativestaticfile', function (err, res, body) { expect(err).to.not.exist; expect(body).to.contain('hapi'); expect(res.headers['content-type']).to.equal('application/json'); expect(res.headers['content-length']).to.exist; done(); }); }); }); it('invalidates etags when file changes', function (done) { var server = new Hapi.Server({ files: { relativeTo: 'routes' } }); server.route({ method: 'GET', path: '/note', handler: { file: './file/note.txt' } }); // No etag, never requested server.inject('/note', function (res1) { expect(res1.statusCode).to.equal(200); expect(res1.result).to.equal('Test'); expect(res1.headers.etag).to.not.exist; // No etag, previously requested server.inject('/note', function (res2) { expect(res2.statusCode).to.equal(200); expect(res2.result).to.equal('Test'); expect(res2.headers.etag).to.exist; var etag1 = res2.headers.etag; // etag server.inject({ url: '/note', headers: { 'if-none-match': etag1 } }, function (res3) { expect(res3.statusCode).to.equal(304); var fd = Fs.openSync(__dirname + '/file/note.txt', 'w'); Fs.writeSync(fd, new Buffer('Test'), 0, 4); Fs.closeSync(fd); // etag after file modified, content unchanged server.inject({ url: '/note', headers: { 'if-none-match': etag1 } }, function (res4) { expect(res4.statusCode).to.equal(200); expect(res4.result).to.equal('Test'); expect(res4.headers.etag).to.not.exist; // No etag, previously requested server.inject({ url: '/note' }, function (res5) { expect(res5.statusCode).to.equal(200); expect(res5.result).to.equal('Test'); expect(res5.headers.etag).to.exist; var etag2 = res5.headers.etag; expect(etag1).to.equal(etag2); var fd = Fs.openSync(__dirname + '/file/note.txt', 'w'); Fs.writeSync(fd, new Buffer('Test1'), 0, 5); Fs.closeSync(fd); // etag after file modified, content changed server.inject({ url: '/note', headers: { 'if-none-match': etag2 } }, function (res6) { expect(res6.statusCode).to.equal(200); expect(res6.result).to.equal('Test1'); expect(res6.headers.etag).to.not.exist; // No etag, previously requested server.inject('/note', function (res7) { expect(res7.statusCode).to.equal(200); expect(res7.result).to.equal('Test1'); expect(res7.headers.etag).to.exist; var etag3 = res7.headers.etag; expect(etag1).to.not.equal(etag3); var fd = Fs.openSync(__dirname + '/file/note.txt', 'w'); Fs.writeSync(fd, new Buffer('Test'), 0, 4); Fs.closeSync(fd); // No etag, content restored server.inject('/note', function (res8) { expect(res8.statusCode).to.equal(200); expect(res8.result).to.equal('Test'); // No etag, previously requested server.inject('/note', function (res9) { expect(res9.statusCode).to.equal(200); expect(res9.result).to.equal('Test'); expect(res9.headers.etag).to.exist; var etag4 = res9.headers.etag; expect(etag1).to.equal(etag4); done(); }); }); }); }); }); }); }); }); }); }); it('returns a 304 when the request has if-modified-since and the response hasn\'t been modified since', function (done) { var server = new Hapi.Server(0); var handler = function (request) { request.reply(new Hapi.response.File(__dirname + '/../../package.json')); }; server.route({ method: 'GET', path: '/file', handler: handler }); server.start(function () { Request.get({ url: server.info.uri + '/file' }, function (err, res1) { var headers = { 'if-modified-since': res1.headers.date }; Request.get({ url: server.info.uri + '/file', headers: headers }, function (err, res2) { expect(res2.statusCode).to.equal(304); done(); }); }); }); }); it('returns a gzipped file in the response when the request accepts gzip', function (done) { var server = new Hapi.Server(0, { files: { relativeTo: 'routes' } }); var handler = function (request) { request.reply(new Hapi.response.File(__dirname + '/../../package.json')); }; server.route({ method: 'GET', path: '/file', handler: handler }); server.start(function () { Request.get({ url: server.info.uri + '/file', headers: { 'accept-encoding': 'gzip' } }, function (err, res, body) { expect(err).to.not.exist; expect(res.headers['content-type']).to.equal('application/json'); expect(res.headers['content-encoding']).to.equal('gzip'); expect(res.headers['content-length']).to.not.exist; expect(body).to.exist; done(); }); }); }); it('returns a deflated file in the response when the request accepts deflate', function (done) { var server = new Hapi.Server(0, { files: { relativeTo: 'routes' } }); var handler = function (request) { request.reply(new Hapi.response.File(__dirname + '/../../package.json')); }; server.route({ method: 'GET', path: '/file', handler: handler }); server.start(function () { Request.get({ url: server.info.uri + '/file', headers: { 'accept-encoding': 'deflate' } }, function (err, res, body) { expect(err).to.not.exist; expect(res.headers['content-type']).to.equal('application/json'); expect(res.headers['content-encoding']).to.equal('deflate'); expect(res.headers['content-length']).to.not.exist; expect(body).to.exist; done(); }); }); }); it('doesn\'t throw an error when adding a route with a parameter and function path', function (done) { var fn = function () { var server = new Hapi.Server(0, { files: { relativeTo: 'routes' } }); server.route({ method: 'GET', path: '/fileparam/{path}', handler: { file: function () { } } }); }; expect(fn).to.not.throw(Error); done(); }); }); describe('Directory', function () { var server = new Hapi.Server(0, { files: { relativeTo: 'routes' } }); server.route({ method: 'GET', path: '/directory/{path*}', handler: { directory: { path: '.' } } }); // Use '.' to test path normalization server.route({ method: 'GET', path: '/showhidden/{path*}', handler: { directory: { path: './', showHidden: true, listing: true } } }); server.route({ method: 'GET', path: '/noshowhidden/{path*}', handler: { directory: { path: './', listing: true } } }); server.route({ method: 'GET', path: '/{path*}', handler: { directory: { path: './', index: true, listing: true } } }); server.route({ method: 'GET', path: '/showindex/{path*}', handler: { directory: { path: './', index: true, listing: true } } }); server.route({ method: 'GET', path: '/multiple/{path*}', handler: { directory: { path: ['./', '../'], listing: true } } }); before(function (done) { server.start(function () { done(); }); }); it('returns a 403 when no index exists and listing is disabled', function (done) { Request.get(server.info.uri + '/directory/', function (err, res, body) { expect(err).to.not.exist; expect(res.statusCode).to.equal(403); done(); }); }); it('returns a 403 when requesting a path containing \'..\'', function (done) { Request.get(server.info.uri + '/directory/..', function (err, res, body) { expect(err).to.not.exist; expect(res.statusCode).to.equal(403); done(); }); }); it('returns a 404 when requesting an unknown file within a directory', function (done) { Request.get(server.info.uri + '/directory/xyz', function (err, res, body) { expect(err).to.not.exist; expect(res.statusCode).to.equal(404); done(); }); }); it('returns a file when requesting a file from the directory', function (done) { Request.get(server.info.uri + '/directory/response.js', function (err, res, body) { expect(err).to.not.exist; expect(res.statusCode).to.equal(200); expect(body).to.contain('hapi'); done(); }); }); it('returns a file when requesting a file from multi directory setup', function (done) { Request.get(server.info.uri + '/multiple/unit/response/directory.js', function (err, res, body) { expect(err).to.not.exist; expect(res.statusCode).to.equal(200); expect(body).to.contain('no_such_path'); done(); }); }); it('returns the correct file when requesting a file from a child directory', function (done) { Request.get(server.info.uri + '/directory/directory/index.html', function (err, res, body) { expect(err).to.not.exist; expect(res.statusCode).to.equal(200); expect(body).to.contain('test'); done(); }); }); it('returns the correct listing links when viewing top level path', function (done) { Request.get(server.info.uri + '/', function (err, res, body) { expect(err).to.not.exist; expect(res.statusCode).to.equal(200); expect(body).to.contain('href="/response.js"'); done(); }); }); it('doesn\'t contain any double / when viewing sub path listing', function (done) { Request.get(server.info.uri + '/showindex/', function (err, res, body) { expect(err).to.not.exist; expect(res.statusCode).to.equal(200); expect(body).to.not.contain('//'); done(); }); }); it('has the correct link to sub folders when inside of a sub folder listing', function (done) { Request.get(server.info.uri + '/showindex/directory/subdir', function (err, res, body) { expect(err).to.not.exist; expect(res.statusCode).to.equal(200); expect(body).to.contain('href="/showindex/directory/subdir/subsubdir"'); done(); }); }); it('returns a 403 when index and listing are disabled', function (done) { server.route({ method: 'GET', path: '/directoryx/{path*}', handler: { directory: { path: '../../', index: false } } }); Request.get(server.info.uri + '/directoryx/', function (err, res, body) { expect(err).to.not.exist; expect(res.statusCode).to.equal(403); done(); }); }); server.route({ method: 'GET', path: '/directorylist/{path*}', handler: { directory: { path: '../../', listing: true } } }); it('returns a list of files when listing is enabled', function (done) { Request.get(server.info.uri + '/directorylist/', function (err, res, body) { expect(err).to.not.exist; expect(res.statusCode).to.equal(200); expect(body).to.contain('package.json'); done(); }); }); it('returns a list of files for subdirectory', function (done) { Request.get(server.info.uri + '/directorylist/test', function (err, res, body) { expect(err).to.not.exist; expect(res.statusCode).to.equal(200); expect(body).to.contain('integration'); done(); }); }); it('returns a list of files when listing is enabled and index disabled', function (done) { server.route({ method: 'GET', path: '/directorylistx/{path*}', handler: { directory: { path: '../../', listing: true, index: false } } }); Request.get(server.info.uri + '/directorylistx/', function (err, res, body) { expect(err).to.not.exist; expect(res.statusCode).to.equal(200); expect(body).to.contain('package.json'); done(); }); }); server.route({ method: 'GET', path: '/directoryIndex/{path*}', handler: { directory: { path: './directory/' } } }); it('returns the index when found', function (done) { Request.get(server.info.uri + '/directoryIndex/', function (err, res, body) { expect(err).to.not.exist; expect(res.statusCode).to.equal(200); expect(body).to.contain('<p>test</p>'); done(); }); }); it('returns a 500 when index.html is a directory', function (done) { Request.get(server.info.uri + '/directoryIndex/invalid', function (err, res, body) { expect(err).to.not.exist; expect(res.statusCode).to.equal(500); done(); }); }); it('returns the correct file when using a fn directory handler', function (done) { var directoryFn = function (request) { return '../../lib'; }; server.route({ method: 'GET', path: '/directoryfn/{path?}', handler: { directory: { path: directoryFn } } }); Request.get(server.info.uri + '/directoryfn/defaults.js', function (err, res, body) { expect(err).to.not.exist; expect(res.statusCode).to.equal(200); expect(body).to.contain('export'); done(); }); }); it('returns listing with hidden files when hidden files should be shown', function (done) { Request.get(server.info.uri + '/showhidden/', function (err, res, body) { expect(err).to.not.exist; expect(body).to.contain('.hidden'); done(); }); }); it('returns listing without hidden files when hidden files should not be shown', function (done) { Request.get(server.info.uri + '/noshowhidden/', function (err, res, body) { expect(err).to.not.exist; expect(body).to.not.contain('.hidden'); expect(body).to.contain('response.js'); done(); }); }); it('returns a 404 response when requesting a hidden file when showHidden is disabled', function (done) { Request.get(server.info.uri + '/noshowhidden/.hidden', function (err, res, body) { expect(err).to.not.exist; expect(res.statusCode).to.equal(404); done(); }); }); it('returns a file when requesting a hidden file when showHidden is enabled', function (done) { Request.get(server.info.uri + '/showhidden/.hidden', function (err, res, body) { expect(err).to.not.exist; expect(body).to.contain('test'); done(); }); }); }); describe('Stream', function () { var TestStream = function (request, issue) { Stream.Readable.call(this); this.issue = issue; this.request = request; return this; }; Hapi.utils.inherits(TestStream, Stream.Readable); TestStream.prototype._read = function (size) { var self = this; if (this.isDone) { return; } this.isDone = true; switch (this.issue) { case 'error': if (!this.x) { this.emit('error', new Error()); this.x = true; } break; case 'closes': this.push('here is the response'); process.nextTick(function () { self.request.raw.req.emit('close'); process.nextTick(function () { self.push(null); }); }); break; default: this.push('x'); this.push(null); break; } }; var handler = function (request) { request.reply(new TestStream(request, request.params.issue)) .bytes(request.params.issue ? 0 : 1) .ttl(2000); }; var handler2 = function (request) { var HeadersStream = function () { Stream.Readable.call(this); this.response = { headers: { custom: 'header' } }; return this; }; Hapi.utils.inherits(HeadersStream, Stream.Readable); HeadersStream.prototype._read = function (size) { if (this.isDone) { return; } this.isDone = true; this.push('hello'); this.push(null); }; request.reply(new Hapi.response.Stream(new HeadersStream())); }; var handler3 = function (request) { request.reply(new TestStream(request, request.params.issue)) .created('/special') .bytes(request.params.issue ? 0 : 1) .ttl(3000); }; var handler4 = function (request) { request.reply(new TestStream(request)) .state(';sid', 'abcdefg123456'); }; var handler5 = function (request) { var HeadersStream = function () { Stream.Readable.call(this); this.response = { statusCode: 201 }; return this; }; Hapi.utils.inherits(HeadersStream, Stream.Readable); HeadersStream.prototype._read = function (size) { if (this.isDone) { return; } this.isDone = true; this.push('hello'); this.push(null); }; request.reply(new Hapi.response.Stream(new HeadersStream())); }; var handler6 = function (request) { var oldMode = new TestStream(request); oldMode.pause(); request.reply(oldMode); }; var server = new Hapi.Server('0.0.0.0', 19798, { cors: { origin: ['test.example.com'] }, location: 'http://example.com:8080' }); server.route({ method: 'GET', path: '/stream/{issue?}', config: { handler: handler, cache: { expiresIn: 9999 } } }); server.route({ method: 'POST', path: '/stream/{issue?}', config: { handler: handler } }); server.route({ method: 'GET', path: '/stream2', config: { handler: handler2 } }); server.route({ method: 'GET', path: '/stream3', config: { handler: handler3, cache: { expiresIn: 9999 } } }); server.route({ method: 'GET', path: '/stream4', config: { handler: handler4 } }); server.route({ method: 'GET', path: '/stream5', config: { handler: handler5 } }); server.route({ method: 'GET', path: '/stream6', config: { handler: handler6 } }); it('returns a stream reply', function (done) { server.inject('/stream/', function (res) { expect(res.result).to.equal('x'); expect(res.statusCode).to.equal(200); expect(res.headers['cache-control']).to.equal('max-age=2, must-revalidate'); expect(res.headers['access-control-allow-origin']).to.equal('test.example.com'); done(); }); }); it('returns a stream reply with custom response headers', function (done) { server.inject('/stream2', function (res) { expect(res.statusCode).to.equal(200); expect(res.headers.custom).to.equal('header'); done(); }); }); it('returns a stream reply with custom response status code', function (done) { server.inject('/stream5', function (res) { expect(res.statusCode).to.equal(201); done(); }); }); it('returns a stream reply using old style stream interface', function (done) { server.inject('/stream6', function (res) { expect(res.result).to.equal('x'); expect(res.statusCode).to.equal(200); done(); }); }); var TimerStream = function () { Stream.Readable.call(this); return this; }; Hapi.utils.inherits(TimerStream, Stream.Readable); TimerStream.prototype._read = function (size) { var self = this; if (this.isDone) { return; } this.isDone = true; setTimeout(function () { self.push('hi'); self.push(null); }, 5); }; it('returns a gzipped stream reply without a content-length header when accept-encoding is gzip', function (done) { var streamHandler = function (request) { request.reply(new TimerStream()); }; var server1 = new Hapi.Server(0); server1.route({ method: 'GET', path: '/stream', handler: streamHandler }); server1.start(function () { Request.get({ uri: server1.info.uri + '/stream', headers: { 'Content-Type': 'application/json', 'accept-encoding': 'gzip' } }, function (err, res) { expect(res.statusCode).to.equal(200); expect(res.headers['content-length']).to.not.exist; done(); }); }); }); it('returns a deflated stream reply without a content-length header when accept-encoding is deflate', function (done) { var streamHandler = function (request) { request.reply(new TimerStream()); }; var server1 = new Hapi.Server(0); server1.route({ method: 'GET', path: '/stream', handler: streamHandler }); server1.start(function () { Request.get({ uri: server1.info.uri + '/stream', headers: { 'Content-Type': 'application/json', 'accept-encoding': 'deflate' } }, function (err, res) { expect(res.statusCode).to.equal(200); expect(res.headers['content-length']).to.not.exist; done(); }); }); }); it('returns a stream reply (created)', function (done) { server.inject('/stream3', function (res) { expect(res.result).to.equal('x'); expect(res.statusCode).to.equal(201); expect(res.headers.location).to.equal(server.settings.location + '/special'); expect(res.headers['cache-control']).to.equal('no-cache'); expect(res.headers['access-control-allow-origin']).to.equal('test.example.com'); done(); }); }); it('returns an error on bad state', function (done) { server.inject('/stream4', function (res) { expect(res.statusCode).to.equal(500); done(); }); }); it('returns a broken stream reply on error issue', function (done) { server.inject('/stream/error', function (res) { expect(res.result).to.equal(''); done(); }); }); it('stops processing the stream when the request closes', function (done) { server.start(function () { Request.get({ uri: 'http://127.0.0.1:19798/stream/closes', headers: { 'Accept-Encoding': 'gzip' } }, function (err, res) { expect(res.statusCode).to.equal(200); done(); }); }); }); }); describe('Cached', function () { it('returns a cached reply', function (done) { var cacheHandler = function (request) { request.reply({ status: 'cached' }); }; var server = new Hapi.Server(0); server.route({ method: 'GET', path: '/cache', config: { handler: cacheHandler, cache: { mode: 'server+client', expiresIn: 5000 } } }); server.start(function () { server.inject('/cache', function (res1) { expect(res1.result).to.exist; expect(res1.result.status).to.equal('cached'); server.inject('/cache', function (res2) { expect(res2.result).to.deep.equal('{"status":"cached"}'); done(); }); }); }); }); }); describe('View', function () { var viewPath = __dirname + '/../unit/templates/valid'; var msg = "Hello, World!"; var handler = function (request) { return request.reply.view('test', { message: msg }); }; var handlerDirect = function (request) { return request.reply(request.generateView('test', { message: msg })); }; var absoluteHandler = function (request) { return request.reply.view(viewPath + '/test', { message: msg }); }; var insecureHandler = function (request) { return request.reply.view('../test', { message: msg }); }; var nonexistentHandler = function (request) { return request.reply.view('testNope', { message: msg }); }; var invalidHandler = function (request) { return request.reply.view('badmustache', { message: msg }, { path: viewPath + '/../invalid' }); }; var layoutConflictHandler = function (request) { return request.reply.view('test', { message: msg, content: 'fail' }); }; var layoutErrHandler = function (request) { return request.reply.view('test', { message: msg }, { path: viewPath + '/../invalid' }); }; var testMultiHandlerJade = function (request) { return request.reply.view('testMulti.jade', { message: "Hello World!" }); }; var testMultiHandlerHB = function (request) { return request.reply.view('test.html', { message: "Hello World!" }); }; var testMultiHandlerUnknown = function (request) { return request.reply.view('test', { message: "Hello World!" }); }; var testMultiHandlerMissing = function (request) { return request.reply.view('test.xyz', { message: "Hello World!" }); }; describe('Default', function (done) { var server = new Hapi.Server({ views: { engines: { 'html': 'handlebars' }, path: viewPath } }); server.route({ method: 'GET', path: '/views', config: { handler: handler } }); server.route({ method: 'GET', path: '/views/direct', config: { handler: handlerDirect } }); server.route({ method: 'GET', path: '/views/abspath', config: { handler: absoluteHandler } }); server.route({ method: 'GET', path: '/views/insecure', config: { handler: insecureHandler } }); server.route({ method: 'GET', path: '/views/nonexistent', config: { handler: nonexistentHandler } }); server.route({ method: 'GET', path: '/views/invalid', config: { handler: invalidHandler