Skip to content

Commit

Permalink
Merge pull request #5 from Renaud8469/hapi-plugin
Browse files Browse the repository at this point in the history
Hapi plugin
  • Loading branch information
Renaud Dahl committed Sep 1, 2017
2 parents 88c4d48 + dbd9ed6 commit e2d5f30
Show file tree
Hide file tree
Showing 8 changed files with 231 additions and 12 deletions.
4 changes: 3 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
const transitions = require('./state_transitions')
const expressInterceptors = require('./interceptors/express')
const hapiInterceptors = require('./interceptors/hapi')

module.exports = {
addTransition: transitions.addTransition,
getTransitionList: transitions.getTransitionList,
halInterceptor: expressInterceptors.halInterceptor,
halDefaultInterceptor: expressInterceptors.halDefaultInterceptor,
sirenInterceptor: expressInterceptors.sirenInterceptor,
sirenDefaultInterceptor: expressInterceptors.sirenDefaultInterceptor
sirenDefaultInterceptor: expressInterceptors.sirenDefaultInterceptor,
hapiRegister: hapiInterceptors.register
}
12 changes: 6 additions & 6 deletions interceptors/express/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const interceptor = require('express-interceptor')
const translators = require('../../translators')
const translators = require('../../translators')
const transitions = require('../../state_transitions')

function isHalInterceptable (req) {
Expand All @@ -17,11 +17,11 @@ function isSirenInterceptable (req) {
/*
* General interception function
*/
function generalInterception(mediaType, translateFunc) {
function generalInterception (mediaType, translateFunc) {
return function (req, res) {
return function (body, send) {
res.set('Content-type', mediaType)

let data
try {
data = JSON.parse(body)
Expand All @@ -43,14 +43,14 @@ const sirenIntercept = generalInterception('application/vnd.siren+json', transla

function setupHalInterceptor (req, res) {
return {
isInterceptable : isHalInterceptable(req),
isInterceptable: isHalInterceptable(req),
intercept: halIntercept(req, res)
}
}

function setupHalDefaultInterceptor (req, res) {
return {
isInterceptable : () => true,
isInterceptable: () => true,
intercept: halIntercept(req, res)
}
}
Expand All @@ -64,7 +64,7 @@ function setupSirenInterceptor (req, res) {

function setupSirenDefaultInterceptor (req, res) {
return {
isInterceptable : () => true,
isInterceptable: () => true,
intercept: sirenIntercept(req, res)
}
}
Expand Down
63 changes: 63 additions & 0 deletions interceptors/hapi/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
const translators = require('../../translators')
const transitions = require('../../state_transitions')

const formatFunctions = {
hal: {
header: /application\/vnd\.hal\+json/,
translate: translators.translateToHal
},
siren: {
header: /application\/vnd\.siren\+json/,
translate: translators.translateToSiren
}
}

function modify (format) {
return function (request, reply) {
if (formatFunctions[format].header.test(request.headers['accept'])) {
try {
let body = request.response.source
let state = transitions.getState(request.url.path, request.method)
let host = request.connection.info.protocol + '://' + request.info.host
let isAuth = request.isAuthenticated
let newResponse = formatFunctions[format].translate(body, state, host, isAuth)
reply(newResponse)
} catch (e) {
console.log(e)
reply.continue()
}
} else {
reply.continue()
}
}
}

/*
* Register this as Hapi plugin.
* Options should be :
* { mediaTypes: ['hal', 'siren'], transitions: [...] }
*/
function register (server, options, next) {
if (!options || !options.mediaTypes) {
next()
} else {
if (options.transitions) {
for (let tr of options.transitions) {
transitions.addTransition(tr)
}
}
for (let format of options.mediaTypes) {
server.ext('onPreResponse', modify(format))
}
next()
}
}

register.attributes = {
pkg: require('../../package.json')
}

module.exports = {
modify,
register
}
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "hypermedia-transitions",
"version": "1.1.1",
"version": "1.2.0",
"description": "Module to specify state transitions and transform them into hypermedia format",
"main": "index.js",
"directories": {
Expand All @@ -9,7 +9,7 @@
"scripts": {
"test": "npm run lint && npm run test_coverage",
"test_mocha": "_mocha test --recursive",
"lint": "eslint index.js state_transitions translators",
"lint": "eslint index.js state_transitions translators interceptors",
"test_coverage": "istanbul cover --include-all-sources -x 'test/**' --report lcov _mocha -- --recursive test && NODE_ENV=test istanbul check-coverage --statement 90 --functions 90 --lines 90 --branches 90",
"cover": "istanbul cover --include-all-sources -x 'test/**' ./node_modules/mocha/bin/_mocha --report lcovonly -- --recursive test -R spec && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage"
},
Expand Down
2 changes: 1 addition & 1 deletion state_transitions/state.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ function urlMatch (reqUrl, transitionUrl) {
// The transitionUrl might be a template
// The reqUrl might have additionnal parameters
let path = url.parse(reqUrl).pathname
let convertedTransitionUrl = transitionUrl.replace(/\{\w+\}/g, '(\\w+)')
let convertedTransitionUrl = transitionUrl.replace(/\{\w+\}/g, '((\\w|-)+)')
path = removeSlashEnd(path)
convertedTransitionUrl = removeSlashEnd(convertedTransitionUrl)
let regex = new RegExp('^' + convertedTransitionUrl + '$')
Expand Down
3 changes: 3 additions & 0 deletions test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,7 @@ describe('Check if all functions are here', () => {
it('Should contain sirenInterceptor', () => {
expect(transition_module.sirenInterceptor).to.exist
})
it('Should contain hapiRegister', () => {
expect(transition_module.sirenInterceptor).to.exist
})
})
151 changes: 151 additions & 0 deletions test/interceptors/hapi/index.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
const hapi_plugin = require('../../../interceptors/hapi')
const chai = require('chai')
const expect = chai.expect
const transitions = require('../../../state_transitions')

const pkg = require('../../../package.json')

describe('Testing Hapi plugin properties', () => {
it('Should have a register function', () => {
expect(hapi_plugin.register).to.exist
})

it('Should have attributes property', () => {
expect(hapi_plugin.register.attributes).to.exist
})

it('Should be set to package.json', () => {
expect(hapi_plugin.register.attributes.pkg).to.equals(pkg)
})
})

describe('Register function', () => {
let results = {
startingEvent: '',
registeredFormats: 0,
nextCalled: false
}
let serverTest = {
ext: function (specialEvent, someFunc) {
results.startingEvent = specialEvent
results.registeredFormats++
}
}
let nextTest = function () { results.nextCalled = true }
let options1 = {
mediaTypes: ['hal', 'siren']
}
let transition_test = {
rel: "Relation",
target: "Resource",
accessibleFrom: [{ state: "somewhere" }],
href: "/resources",
method: "get"
}

let options2 = {
mediaTypes: ['hal'],
transitions: [transition_test]
}

beforeEach(() => {
results.startingEvent = ''
results.registeredFormats = 0
nextCalled = false
})

describe('No options specified', () => {
it('Should call next and nothing more', () => {
hapi_plugin.register(serverTest, {}, nextTest)
expect(results.registeredFormats).to.equal(0)
expect(results.nextCalled).to.be.true
})
})

describe('No transitions specified', () => {
it('Should call server ext', () => {
hapi_plugin.register(serverTest, options1, nextTest)
expect(results.registeredFormats).to.equal(2)
expect(results.nextCalled).to.be.true
expect(results.startingEvent).to.equal('onPreResponse')
})
})

describe('With transitions', () => {
it('Should add transitions', () => {
hapi_plugin.register(serverTest, options2, nextTest)
expect(results.registeredFormats).to.equal(1)
expect(results.nextCalled).to.be.true
expect(results.startingEvent).to.equal('onPreResponse')
expect(transitions.getTransitionList()).to.include(transition_test)
})
})
})

describe('Modify function', () => {
let results = {
continueCalled: false,
newResponse: {}
}
let headers1 = {
'accept': 'application/json'
}
let headers2 = {
'accept': 'application/vnd.hal+json'
}
let headers3 = {}
let transition_test = {
rel: "relation",
target: "resource",
accessibleFrom: [{ state: "somewhere" }],
href: "/resources",
method: "get"
}
let requestTest = {
response: {
source: {}
},
url: {
path: '/resources'
},
method: 'get',
connection: { info: { protocol: 'http' } },
info: { host: 'example.org' }
}
let replyTest = function (resp) {
results.newResponse = resp
}
replyTest.continue = function () { results.continueCalled = true }

before(() => { transitions.addTransition(transition_test) })
beforeEach(() => {
results.continueCalled = false,
results.newResponse = {}
})

describe('When called with wrong header', () => {
it('Should reply.continue with no header', () => {
requestTest.headers = headers3
hapi_plugin.modify('hal')(requestTest, replyTest)
expect(results.continueCalled).to.be.true
})

it('Should reply.continue with wrong header', () => {
requestTest.headers = headers1
hapi_plugin.modify('hal')(requestTest, replyTest)
expect(results.continueCalled).to.be.true
})
})

describe('when called with right header', () => {
it('Should return a minimal Hal response', () => {
requestTest.headers = headers2
hapi_plugin.modify('hal')(requestTest, replyTest)
expect(results.continueCalled).to.be.false
expect(results.newResponse).to.have.property('_links')
})
})
})



0 comments on commit e2d5f30

Please sign in to comment.