UNPKG

halacious

Version:

A better HAL processor for Hapi

1,403 lines (1,286 loc) 62.3 kB
'use strict'; var chai = require('chai'); var should = chai.should(); var plugin = require('../lib/plugin'); var hapi = require('hapi'); var sinonChai = require('sinon-chai'); var chaiString = require('chai-string'); var halacious = require('../'); var vision = require('vision'); var _ = require('lodash'); var url = require('url'); chai.use(sinonChai); chai.use(chaiString); describe('Halacious Plugin', function () { it('should have a registration function', function () { plugin.should.have.property('register'); plugin.register.should.be.a('Function'); }); it('should expose a namespace function', function (done) { var server = new hapi.Server(); server.connection({ port: 9090 }); server.register(halacious, function () { server.plugins.halacious.should.have.property('namespaces'); server.plugins.halacious.namespace.should.be.a('Function'); done(); }); }); it('should create a namespace', function (done) { var server = new hapi.Server(); server.connection({ port: 9090 }); server.register(halacious, function () { var ns = server.plugins.halacious.namespaces.add({ name: 'mycompany', prefix: 'mco' }); should.exist(ns); ns.should.have.property('name', 'mycompany'); ns.should.have.property('prefix', 'mco'); ns.should.have.property('rel'); ns.rel.should.be.a('Function'); done(); }); }); it('should look up a namespace', function (done) { var server = new hapi.Server(); server.connection({ port: 9090 }); server.register(halacious, function () { server.plugins.halacious.namespaces.add({ name: 'mycompany', prefix: 'mco' }); var ns = server.plugins.halacious.namespace('mycompany'); ns.rel({ name: 'boss', description: 'An employees boss' }); ns.rels.should.have.property('boss'); ns.rels.boss.should.have.property('name', 'boss'); ns.rels.boss.should.have.property('description', 'An employees boss'); done(); }); }); it('should return a sorted array of namespaces', function () { var server = new hapi.Server(); server.connection({ port: 9090 }); server.register(halacious, function () { var namespaces; server.plugins.halacious.namespaces.add({ name: 'yourcompany', prefix: 'yco' }); server.plugins.halacious.namespaces.add({ name: 'mycompany', prefix: 'mco' }); server.plugins.halacious.namespaces.add({ name: 'ourcompany', prefix: 'oco' }); namespaces = server.plugins.halacious.namespaces(); namespaces.should.have.length(3); namespaces[0].should.have.property('name', 'mycompany'); namespaces[1].should.have.property('name', 'ourcompany'); namespaces[2].should.have.property('name', 'yourcompany'); }); }); it('should fail when registering an invalid namespace', function () { var server = new hapi.Server(); server.connection({ port: 9090 }); server.register(halacious, function () { var plugin = server.plugins.halacious; plugin.namespaces.add.bind(plugin.namespaces, { name: 'mycompany', prefirx: 'mco'}).should.throw('prefirx is not allowed'); }); }); it('should add a rel to a namespace', function (done) { var server = new hapi.Server(); server.connection({ port: 9090 }); server.register(halacious, function () { var ns = server.plugins.halacious.namespaces.add({ name: 'mycompany', prefix: 'mco' }); ns.rel({ name: 'boss', description: 'An employees boss' }); ns.rels.should.have.property('boss'); ns.rels.boss.should.have.property('name', 'boss'); ns.rels.boss.should.have.property('description', 'An employees boss'); done(); }); }); it('should look up a rel by prefix:name', function (done) { var server = new hapi.Server(); server.connection({ port: 9090 }); server.register(halacious, function () { var ns = server.plugins.halacious.namespaces.add({ name: 'mycompany', prefix: 'mco' }); ns.rel({ name: 'datasources', description: 'A list of datasources' }); var rel = server.plugins.halacious.rel('mco:datasources'); should.exist(rel); rel.should.have.property('name', 'datasources'); rel.should.have.property('description', 'A list of datasources'); done(); }); }); it('should remove a namespace', function() { var server = new hapi.Server(); server.connection({ port: 9090 }); server.register(halacious, function () { server.plugins.halacious.namespaces.add({ name: 'mycompany', prefix: 'mco' }); server.plugins.halacious.namespaces.add({ name: 'yourcompany', prefix: 'yco' }); server.plugins.halacious.namespaces().should.have.length(2); server.plugins.halacious.namespaces.remove('yourcompany'); server.plugins.halacious.namespaces().should.have.length(1); server.plugins.halacious.namespaces()[0].should.have.property('name', 'mycompany'); }); }); it('should look up a rel by ns / name', function (done) { var server = new hapi.Server(); server.connection({ port: 9090 }); server.register(halacious, function () { var ns = server.plugins.halacious.namespaces.add({ name: 'mycompany', prefix: 'mco' }); ns.rel({ name: 'datasources', description: 'A list of datasources' }); var rel = server.plugins.halacious.rel('mycompany', 'datasources'); should.exist(rel); rel.should.have.property('name', 'datasources'); rel.should.have.property('description', 'A list of datasources'); done(); }); }); it('should add a rel to a specified namespace', function (done) { var server = new hapi.Server(); server.connection({ port: 9090 }); server.register(halacious, function () { var rels, plugin = server.plugins.halacious; plugin.namespaces.add({ name: 'thiscompany', prefix: 'tco' }); plugin.rels.add('thiscompany', 'a_rel'); plugin.rels.add('thiscompany', { name: 'b_rel' }); rels = _.values(plugin.namespace('thiscompany').rels); rels.should.have.length(2); _.pluck(rels, 'name').should.deep.equal(['a_rel', 'b_rel']); done(); }); }); it('should return a sorted list of rels', function (done) { var server = new hapi.Server(); server.connection({ port: 9090 }); server.register(halacious, function () { var rels, plugin = server.plugins.halacious; plugin.namespaces.add({ name: 'mycompany', prefix: 'mco' }).rel('a_rel').rel('c_rel'); plugin.namespaces.add({ name: 'yourcompany', prefix: 'yco'}).rel('b_rel').rel('d_rel'); rels = plugin.rels(); rels.should.have.length(4); _.pluck(rels, 'name').should.deep.equal(['a_rel', 'b_rel', 'c_rel', 'd_rel']); done(); }); }); it('should bomb on a bad rel in strict mode', function (done) { var server = new hapi.Server(); server.connection({ port: 9090 }); server.route({ method: 'get', path: '/foo', config: { handler: function (req, reply) { reply({ name: 'Billy Bob' }); }, plugins: { hal: { links: { 'mco:badRel': './badRel' } } } } }); server.register({ register: halacious, options: { strict: true }}, function (err) { if (err) return done(err); server.plugins.halacious.namespaces.add({ dir: __dirname + '/rels/mycompany', prefix: 'mco' }); server.inject({ method: 'get', url: '/foo', headers: { Accept: 'application/hal+json' } }, function (res) { res.statusCode.should.equal(500); done(); }); }); }); it('should install a directory-style namespace', function (done) { var server = new hapi.Server(); server.connection({ port: 9090 }); server.register(halacious, function () { var ns = server.plugins.halacious.namespaces.add({ dir: __dirname + '/rels/mycompany', prefix: 'mco' }); var rel1 = server.plugins.halacious.rel('mco:datasources'); var rel2 = server.plugins.halacious.rel('mco:datasource'); should.exist(ns); should.exist(rel1); should.exist(rel2); rel1.should.have.property('name', 'datasources'); rel2.should.have.property('name', 'datasource'); done(); }); }); it('should route rel documentation', function (done) { var server = new hapi.Server({ debug: { log: ['error']}}); server.connection(); server.register(vision, function (err) { if (err) done(err); }); server.register(halacious, function (err) { if (err) return done(err); server.plugins.halacious.namespaces.add({dir: __dirname + '/rels/mycompany', prefix: 'mco'}); }); server.start(function(err) { if (err) return done(err); server.inject({ method: 'get', url: '/rels/mycompany/boss' }, function (res) { res.statusCode.should.equal(200); res.payload.should.not.be.empty; done(); }); }) }); it('should resolve a named route path', function (done) { var server = new hapi.Server(); server.connection({ port: 9090 }); server.route({ method: 'get', path: '/{a}/{b}/{c}', config: { handler: function (req, reply) { reply({ a: req.params.a, b: req.params.b, c: req.params.c }); }, plugins: { hal: { name: 'test-route' } } } }); server.register(halacious, function (err) { if (err) return done(err); var path = server.plugins.halacious.route('test-route', {a: 'i', b: 'aint', c: 'fack'}); path.should.equal('/i/aint/fack'); done(); }); }); it('should convert a json entity into a HAL representation with self and a simple link', function (done) { var server = new hapi.Server(); server.connection({ port: 9090 }); var result; server.route({ method: 'get', path: '/people/{id}', config: { handler: function (req, reply) { reply({ firstName: 'Bob', lastName: 'Smith' }); }, plugins: { hal: { links: { 'mco:boss': './boss' } } } } }); server.register(halacious, function (err) { if (err) return done(err); server.plugins.halacious.namespaces .add({ name: 'mycompany', prefix: 'mco' }).rel({ name: 'boss' }); }); server.inject({ method: 'get', url: '/people/100', headers: { Accept: 'application/hal+json' } }, function (res) { res.statusCode.should.equal(200); result = JSON.parse(res.payload); result.should.deep.equal({ _links: { self: { href: '/people/100' }, curies: [{ name: 'mco', href: '/rels/mycompany/{rel}', templated: true }], 'mco:boss': { href: '/people/100/boss' } }, firstName: 'Bob', lastName: 'Smith' }); done(); }); }); it('should convert a json entity into a HAL representation with self and a templated link', function (done) { var server = new hapi.Server(); server.connection({ port: 9090 }); var result; server.route({ method: 'get', path: '/people/{id}', config: { handler: function (req, reply) { reply({ firstName: 'Bob', lastName: 'Smith', bossId: '1234' }); }, plugins: { hal: { links: { 'mco:boss': { href: '../{bossId}', title: 'Boss' } } } } } }); server.register(halacious, function (err) { if (err) return done(err); server.plugins.halacious.namespaces .add({ name: 'mycompany', prefix: 'mco' }).rel({ name: 'boss' }); }); server.inject({ method: 'get', url: '/people/100', headers: { Accept: 'application/hal+json' } }, function (res) { res.statusCode.should.equal(200); result = JSON.parse(res.payload); result.should.deep.equal({ _links: { self: { href: '/people/100' }, curies: [{ name: 'mco', href: '/rels/mycompany/{rel}', templated: true }], 'mco:boss': { href: '/people/1234', title: 'Boss' } }, firstName: 'Bob', lastName: 'Smith', bossId: '1234' }); done(); }); }); it('should allow for programmatic population of a hal entity', function (done) { var server = new hapi.Server(); server.connection({ port: 9090 }); var result; server.route({ method: 'get', path: '/people/{id}', config: { handler: function (req, reply) { reply({ firstName: 'Bob', lastName: 'Smith', bossId: '1234' }); }, plugins: { hal: { prepare: function(rep, done) { rep.link('mco:boss', 'http://www.whitehouse.gov'); done(); } } } } }); server.register(halacious, function (err) { if (err) return done(err); server.plugins.halacious.namespaces .add({ name: 'mycompany', prefix: 'mco' }).rel({ name: 'boss' }); }); server.inject({ method: 'get', url: '/people/100', headers: { Accept: 'application/hal+json' } }, function (res) { res.statusCode.should.equal(200); result = JSON.parse(res.payload); result.should.deep.equal({ _links: { self: { href: '/people/100' }, curies: [{ name: 'mco', href: '/rels/mycompany/{rel}', templated: true }], 'mco:boss': { href: 'http://www.whitehouse.gov' } }, firstName: 'Bob', lastName: 'Smith', bossId: '1234' }); done(); }); }); it('should support a hal configuration function', function (done) { var server = new hapi.Server(); server.connection({ port: 9090 }); var result; server.route({ method: 'get', path: '/people/{id}', config: { handler: function (req, reply) { reply({ firstName: 'Bob', lastName: 'Smith', bossId: '1234' }); }, plugins: { hal: function(rep, done) { rep.link('mco:boss', 'http://www.whitehouse.gov'); done(); } } } }); server.register(halacious, function (err) { if (err) return done(err); server.plugins.halacious.namespaces .add({ name: 'mycompany', prefix: 'mco' }).rel({ name: 'boss' }); }); server.inject({ method: 'get', url: '/people/100', headers: { Accept: 'application/hal+json' } }, function (res) { res.statusCode.should.equal(200); result = JSON.parse(res.payload); result.should.deep.equal({ _links: { self: { href: '/people/100' }, curies: [{ name: 'mco', href: '/rels/mycompany/{rel}', templated: true }], 'mco:boss': { href: 'http://www.whitehouse.gov' } }, firstName: 'Bob', lastName: 'Smith', bossId: '1234' }); done(); }); }); it('should embed an object property', function (done) { var server = new hapi.Server(); server.connection({ port: 9090 }); var result; server.route({ method: 'get', path: '/people/{id}', config: { handler: function (req, reply) { reply({ firstName: 'Bob', lastName: 'Smith', boss: { firstName: 'Boss', lastName: 'Man'} }); }, plugins: { hal: { embedded: { 'mco:boss': { path: 'boss', href: './boss' } } } } } }); server.register(halacious, function (err) { if (err) return done(err); server.plugins.halacious.namespaces .add({ name: 'mycompany', prefix: 'mco' }).rel({ name: 'boss' }); }); server.inject({ method: 'get', url: '/people/100', headers: { Accept: 'application/hal+json' } }, function (res) { res.statusCode.should.equal(200); result = JSON.parse(res.payload); result.should.deep.equal({ _links: { self: { href: '/people/100' }, curies: [{ name: 'mco', href: '/rels/mycompany/{rel}', templated: true }] }, firstName: 'Bob', lastName: 'Smith', _embedded: { 'mco:boss': { _links: { self: { href: '/people/100/boss'} }, firstName: 'Boss', lastName: 'Man' } } }); done(); }); }); it('should support embedded url templates', function (done) { var server = new hapi.Server(); server.connection({ port: 9090 }); var result; server.route({ method: 'get', path: '/people/{id}', config: { handler: function (req, reply) { reply({ id: 100, firstName: 'Bob', lastName: 'Smith', boss: { id: 200, firstName: 'Boss', lastName: 'Man'} }); }, plugins: { hal: { embedded: { 'mco:boss': { path: 'boss', href: '/people/{self.id}/{item.id}' } } } } } }); server.register(halacious, function (err) { if (err) return done(err); server.plugins.halacious.namespaces .add({ name: 'mycompany', prefix: 'mco' }).rel({ name: 'boss' }); }); server.inject({ method: 'get', url: '/people/100', headers: { Accept: 'application/hal+json' } }, function (res) { res.statusCode.should.equal(200); result = JSON.parse(res.payload); result.should.deep.equal({ _links: { self: { href: '/people/100' }, curies: [{ name: 'mco', href: '/rels/mycompany/{rel}', templated: true }] }, id: 100, firstName: 'Bob', lastName: 'Smith', _embedded: { 'mco:boss': { _links: { self: { href: '/people/100/200'} }, id: 200, firstName: 'Boss', lastName: 'Man' } } }); done(); }); }); it('should provide embedded collection support', function (done) { var server = new hapi.Server(); server.connection({ port: 9090 }); var result; server.route({ method: 'get', path: '/people', config: { handler: function (req, reply) { reply({ start: 0, count: 2, total: 2, items: [ { id: 100, firstName: 'Bob', lastName: 'Smith' }, { id: 200, firstName: 'Boss', lastName: 'Man'} ] }); }, plugins: { hal: { embedded: { 'mco:person': { path: 'items', href: './{item.id}', links: { 'mco:boss': './boss' } } } } } } }); server.register(halacious, function (err) { if (err) return done(err); server.plugins.halacious.namespaces .add({ name: 'mycompany', prefix: 'mco' }).rel({ name: 'boss' }); }); server.inject({ method: 'get', url: '/people', headers: { Accept: 'application/hal+json' } }, function (res) { res.statusCode.should.equal(200); result = JSON.parse(res.payload); result.should.deep.equal({ _links: { self: { href: '/people' }, curies: [{ name: 'mco', href: '/rels/mycompany/{rel}', templated: true }] }, start: 0, count: 2, total: 2, _embedded: { 'mco:person': [ { _links: { self: { href: '/people/100' }, 'mco:boss': { href: '/people/100/boss'}}, id: 100, firstName: 'Bob', lastName: 'Smith' }, { _links: { self: { href: '/people/200' }, 'mco:boss': { href: '/people/200/boss'}}, id: 200, firstName: 'Boss', lastName: 'Man' } ] } }); done(); }); }); it('should invoke an optional toHal() method on the source entity', function (done) { var server = new hapi.Server(); server.connection({ port: 9090 }); var result; server.route({ method: 'get', path: '/people/{id}', config: { handler: function (req, reply) { reply({ firstName: 'Bob', lastName: 'Smith', bossId: '1234', toHal: function(rep, done) { rep.link('mco:boss', './boss'); done(); } }); } } }); server.register(halacious, function (err) { if (err) return done(err); server.plugins.halacious.namespaces .add({ name: 'mycompany', prefix: 'mco' }).rel({ name: 'boss' }); }); server.inject({ method: 'get', url: '/people/100', headers: { Accept: 'application/hal+json' } }, function (res) { res.statusCode.should.equal(200); result = JSON.parse(res.payload); result.should.deep.equal({ _links: { self: { href: '/people/100' }, curies: [{ name: 'mco', href: '/rels/mycompany/{rel}', templated: true }], 'mco:boss': { href: '/people/100/boss' } }, firstName: 'Bob', lastName: 'Smith', bossId: '1234' }); done(); }); }); it('should allow for programmatic population of a hal entity and it\'s configured embedded entities', function (done) { var server = new hapi.Server(); server.connection({ port: 9090 }); var result; server.route({ method: 'get', path: '/people/{id}', config: { handler: function (req, reply) { reply({ firstName: 'Bob', lastName: 'Smith', bossId: '1234', foo: {id: '5678'}}); }, plugins: { hal: { prepare: function(rep, done) { rep.link('mco:boss', 'http://www.whitehouse.gov'); done(); }, embedded: { 'foo' : { path: 'foo', href: '/foo/{item.id}', prepare: function(rep, next) { setTimeout(function() { rep.link('foo:bar', 'http://www.foo.com'); next(); }, 500); } } } } } } }); server.register(halacious, function (err) { if (err) return done(err); server.plugins.halacious.namespaces .add({ name: 'mycompany', prefix: 'mco' }).rel({ name: 'boss' }); }); server.inject({ method: 'get', url: '/people/100', headers: { Accept: 'application/hal+json' } }, function (res) { res.statusCode.should.equal(200); result = JSON.parse(res.payload); result.should.deep.equal({ _links: { self: { href: '/people/100' }, curies: [{ name: 'mco', href: '/rels/mycompany/{rel}', templated: true }], 'mco:boss': { href: 'http://www.whitehouse.gov' } }, firstName: 'Bob', lastName: 'Smith', bossId: '1234', _embedded: { foo: { _links: { self: { href: '/foo/5678' }, 'foo:bar': { href: 'http://www.foo.com' } }, id: '5678' } } }); done(); }); }); it('should omit missing configured embedded entities', function (done) { var server = new hapi.Server(); server.connection({ port: 9090 }); var result; server.route({ method: 'get', path: '/people/{id}', config: { handler: function (req, reply) { reply({ firstName: 'Bob', lastName: 'Smith', bossId: '1234', foo: {id: '5678'}}); }, plugins: { hal: { prepare: function(rep, done) { rep.link('mco:boss', 'http://www.whitehouse.gov'); done(); }, embedded: { 'foo' : { path: 'foo', href: '/foo/{item.id}', prepare: function(rep, next) { rep.link('foo:bar', 'http://www.foo.com'); next(); } }, 'bar' : { path: 'notthere', href: '/bar/{item.id}' } } } } } }); server.register(halacious, function (err) { if (err) return done(err); server.plugins.halacious.namespaces .add({ name: 'mycompany', prefix: 'mco' }).rel({ name: 'boss' }); }); server.inject({ method: 'get', url: '/people/100', headers: { Accept: 'application/hal+json' } }, function (res) { res.statusCode.should.equal(200); result = JSON.parse(res.payload); result.should.deep.equal({ _links: { self: { href: '/people/100' }, curies: [{ name: 'mco', href: '/rels/mycompany/{rel}', templated: true }], 'mco:boss': { href: 'http://www.whitehouse.gov' } }, firstName: 'Bob', lastName: 'Smith', bossId: '1234', _embedded: { foo: { _links: { self: { href: '/foo/5678' }, 'foo:bar': { href: 'http://www.foo.com' } }, id: '5678' } } }); done(); }); }); it('should allow an embedded entity to be forced to be a single element array', function (done) { var server = new hapi.Server(); server.connection({ port: 9090 }); var result; server.route({ method: 'get', path: '/people/{id}', config: { handler: function (req, reply) { reply({ firstName: 'Bob', lastName: 'Smith', bossId: '1234', foo: [{id: '5678'}]}); }, plugins: { hal: { prepare: function(rep, done) { rep.link('mco:boss', 'http://www.whitehouse.gov'); done(); }, embedded: { 'foo' : { path: 'foo', href: '/foo/{item.id}', prepare: function(rep, next) { rep.link('foo:bar', 'http://www.foo.com'); next(); } } } } } } }); server.register(halacious, function (err) { if (err) return done(err); server.plugins.halacious.namespaces .add({ name: 'mycompany', prefix: 'mco' }).rel({ name: 'boss' }); }); server.inject({ method: 'get', url: '/people/100', headers: { Accept: 'application/hal+json' } }, function (res) { res.statusCode.should.equal(200); result = JSON.parse(res.payload); result.should.deep.equal({ _links: { self: { href: '/people/100' }, curies: [{ name: 'mco', href: '/rels/mycompany/{rel}', templated: true }], 'mco:boss': { href: 'http://www.whitehouse.gov' } }, firstName: 'Bob', lastName: 'Smith', bossId: '1234', _embedded: { foo: [{ _links: { self: { href: '/foo/5678' }, 'foo:bar': { href: 'http://www.foo.com' } }, id: '5678' }] } }); done(); }); }); it('should preserve 201 status code and use the location header when an entity has been POSTed', function (done) { var server = new hapi.Server(); server.connection({ port: 9090 }); var result; server.route({ method: 'post', path: '/people', config: { handler: function (req, reply) { reply({ id: 100, firstName: 'Bob', lastName: 'Smith' }).created('/people/100'); } } }); server.register(halacious, function (err) { if (err) return done(err); }); server.inject({ method: 'post', url: '/people', headers: { Accept: 'application/hal+json' } }, function (res) { try { res.statusCode.should.equal(201); result = JSON.parse(res.payload); result.should.deep.equal({ _links: { self: { href: '/people/100' } }, id: 100, firstName: 'Bob', lastName: 'Smith' }); done(); } catch (err) { done(err); } }); }); it('use of location header for absolute link generation should not break url search', function (done) { var server = new hapi.Server(); server.connection({ port: 9090 }); var result; server.route({ method: 'post', path: '/people', config: { handler: function (req, reply) { reply({ id: 100, firstName: 'Bob', lastName: 'Smith' }).created('/people/100?donotbreakthis=true'); } } }); server.register({ register:halacious, options: { absolute: true } }, function (err) { if (err) return done(err); }); server.inject({ method: 'post', url: '/people', headers: { Accept: 'application/hal+json' } }, function (res) { try { res.statusCode.should.equal(201); result = JSON.parse(res.payload); result.should.have.a.property('_links').that.has.a.property('self').that.has.a.property('href').that.endsWith('/people/100?donotbreakthis=true'); done(); } catch (err) { done(err); } }); }); it('should support an array of acceptable media types', function (done) { var server = new hapi.Server(); server.connection({ port: 9090 }); var result; server.route({ method: 'get', path: '/people/{id}', config: { handler: function (req, reply) { reply({ firstName: 'Bob', lastName: 'Smith' }); } } }); server.register({ register: halacious, options: { mediaTypes: ['application/json', 'application/hal+json']}}, function (err) { if (err) return done(err); }); // test application/json server.inject({ method: 'get', url: '/people/100' }, function (res) { res.statusCode.should.equal(200); res.headers['content-type'].should.contain('application/json'); result = JSON.parse(res.payload); result.should.deep.equal({ _links: { self: { href: '/people/100' } }, firstName: 'Bob', lastName: 'Smith' }); // test application/hal+json server.inject({ method: 'get', url: '/people/100', headers: { 'Accept': 'application/hal+json' } }, function (res) { res.statusCode.should.equal(200); res.headers['content-type'].should.contain('application/hal+json'); result = JSON.parse(res.payload); result.should.deep.equal({ _links: { self: { href: '/people/100' } }, firstName: 'Bob', lastName: 'Smith' }); done(); }); }); }); it('should regurgitate known query parameters in the self link', function (done) { var server = new hapi.Server(); server.connection({ port: 9090 }); var result; server.route({ method: 'get', path: '/people', config: { handler: function (req, reply) { reply({ items: [{ id: 100, firstName: 'Louis', lastName: 'CK' }]}); }, plugins: { hal: { embedded: { items: { path: 'items', href: './{item.id}' } }, query: '{?q*,start,limit}' } } } }); server.register({ register: halacious, options: { mediaTypes: ['application/json', 'application/hal+json']}}, function (err) { if (err) return done(err); }); // test application/json server.inject({ method: 'get', url: '/people?q=funny&start=1&token=12345', headers: { Accept: 'application/hal+json'} }, function (res) { res.statusCode.should.equal(200); result = JSON.parse(res.payload); result.should.deep.equal({ _links: { self: { href: '/people?q=funny&start=1' } }, _embedded: { items: [ { _links: { self: { href: '/people/100' }}, id: 100, firstName: 'Louis', lastName: 'CK' } ] } }); done(); }); }); it('should resolve relative locations', function (done) { var server = new hapi.Server(); server.connection({ port: 9090 }); var result; server.route({ method: 'post', path: '/api/people', config: { handler: function (req, reply) { reply({ id: 100, firstName: 'Louis', lastName: 'CK' }).created('api/people/100'); } } }); server.register({ register: halacious, options: { mediaTypes: ['application/json', 'application/hal+json']}}, function (err) { if (err) return done(err); }); // test application/json server.inject({ method: 'post', url: '/api/people', headers: { Accept: 'application/hal+json'} }, function (res) { try { res.statusCode.should.equal(201); result = JSON.parse(res.payload); result.should.deep.equal({ _links: { self: { href: '/api/people/100' } }, id: 100, firstName: 'Louis', lastName: 'CK' }); done(); } catch (err) { done(err); } }); }); it('should preserve response headers', function (done) { var server = new hapi.Server(); server.connection({ port: 9090 }); server.route({ method: 'get', path: '/api/people/100', config: { handler: function (req, reply) { reply({ id: 100, firstName: 'Louis', lastName: 'CK' }).header('Last-Modified', new Date()); } } }); server.register({ register: halacious, options: { mediaTypes: ['application/json', 'application/hal+json']}}, function (err) { if (err) return done(err); }); // test application/json server.inject({ method: 'get', url: '/api/people/100', headers: { Accept: 'application/hal+json'} }, function (res) { try { res.statusCode.should.equal(200); res.headers['content-type'].should.equal('application/hal+json'); should.exist(res.headers['last-modified']); done(); } catch (err) { done(err); } }); }); describe('when the absolute flag is turned on', function () { it('should create an absolute self link', function (done) { var server = new hapi.Server(); server.connection({ port: 9090 }); server.route({ method: 'get', path: '/api/people/100', config: { handler: function (req, reply) { reply({ id: 100, firstName: 'Louis', lastName: 'CK' }); }, plugins: { hal: { absolute: true } } } }); server.register({ register: halacious, options: { mediaTypes: ['application/json', 'application/hal+json']}}, function (err) { if (err) return done(err); }); server.inject({ method: 'get', url: 'http://localhost:9090/api/people/100', headers: { Accept: 'application/hal+json'} }, function (res) { var result = JSON.parse(res.payload); result._links.self.should.have.property('href', 'http://localhost:9090/api/people/100'); done(); }); }); it('should create an absolute non-self link', function (done) { var server = new hapi.Server(); server.connection({ port: 9090 }); server.route({ method: 'get', path: '/api/people/100', config: { handler: function (req, reply) { reply({ id: 100, firstName: 'Louis', lastName: 'CK' }); }, plugins: { hal: { absolute: true, links: { schedule: './schedule' } } } } }); server.register({ register: halacious, options: { mediaTypes: ['application/json', 'application/hal+json']}}, function (err) { if (err) return done(err); }); server.inject({ method: 'get', url: 'http://localhost:9090/api/people/100', headers: { Accept: 'application/hal+json'} }, function (res) { var result = JSON.parse(res.payload); result._links.schedule.should.have.property('href', 'http://localhost:9090/api/people/100/schedule'); done(); }); }); it('should embed an object with an absolute link', function (done) { var server = new hapi.Server(); server.connection({ port: 9090 }); server.route({ method: 'get', path: '/api/people/100', config: { handler: function (req, reply) { reply({ firstName: 'Bob', lastName: 'Smith', boss: { firstName: 'Boss', lastName: 'Man'} }); }, plugins: { hal: { absolute: true, embedded: { 'mco:boss': { path: 'boss', href: './boss' } } } } } }); server.register({ register: halacious, options: { mediaTypes: ['application/json', 'application/hal+json']}}, function (err) { if (err) return done(err); }); server.inject({ method: 'get', url: 'http://localhost:9090/api/people/100', headers: { Accept: 'application/hal+json'} }, function (res) { var result = JSON.parse(res.payload); result._embedded['mco:boss']._links.self.should.have.property('href', 'http://localhost:9090/api/people/100/boss'); done(); }); }); it('should handle created entities', function (done) { var server = new hapi.Server(); server.connection({ port: 9090 }); server.route({ method: 'post', path: '/api/people', config: { handler: function (req, reply) { reply({ firstName: 'Bob', lastName: 'Smith' }).created('/api/people/100'); }, plugins: { hal: { absolute: true } } } }); server.register({ register: halacious, options: { mediaTypes: ['application/json', 'application/hal+json']}}, function (err) { if (err) return done(err); }); server.inject({ method: 'post', url: 'http://localhost:9090/api/people', headers: { Accept: 'application/hal+json'} }, function (res) { var result = JSON.parse(res.payload); result._links.self.should.have.property('href', 'http://localhost:9090/api/people/100'); done(); }); }); }); it('should support resolving embedded hrefs by ids', function (done) { var server = new hapi.Server(); server.connection({ port: 9090 }); var result; server.route({ method: 'get', path: '/people/{id}', config: { id: 'person', handler: function (req, reply) { reply({ id: req.params.id, firstName: 'Bob', lastName: 'Smith', bossId: '1234' }); }, plugins: { hal: { query: '{?full}' } } } }); server.route({ method: 'get', path: '/people', handler: function(req, reply) { reply({ items: [{ id: 100 }, { id: 200 }] }); }, config: { plugins: { hal: { embedded: { 'mco:person': { path: 'items', href: function(rep, ctx) { return rep.route('person', { id: ctx.item.id }); } } } } } } }); server.register(halacious, function (err) { if (err) return done(err); server.plugins.halacious.namespaces .add({ name: 'mycompany', prefix: 'mco' }).rel({ name: 'person' }); }); server.inject({ method: 'get', url: '/people', headers: { Accept: 'application/hal