@nearform/trail-hapi-plugin
Version:
Hapi plugin for the audit trail logging service
508 lines (421 loc) • 14.4 kB
JavaScript
'use strict'
const { expect } = require('@hapi/code')
const Lab = require('@hapi/lab')
const sinon = require('sinon')
module.exports.lab = Lab.script()
const { describe, it: test, before, after } = module.exports.lab
const { DateTime } = require('luxon')
const { errorsMessages } = require('../../lib/schemas/errors')
const testServer = require('../test-server')
describe('Trails REST operations', () => {
let server = null
before(async () => {
server = await testServer.buildDefault()
})
after(async () => {
return testServer.stopAll()
})
describe('JSON spec', () => {
for (const url of ['/trails/openapi.json', '/trails/swagger.json']) {
test(`GET ${url} it should server the API spec file`, async () => {
const response = await server.inject({
method: 'GET',
url
})
expect(response.statusCode).to.equal(200)
const payload = JSON.parse(response.payload)
expect(payload).include({
openapi: '3.0.1'
})
})
}
})
describe('GET /trails', async () => {
test('it should search trails and return it with 200', async () => {
await server.trailCore.performDatabaseOperations(client => client.query('TRUNCATE trails'))
const id = await server.trailCore.insert({
when: '2016-01-02T18:04:05.123+03:00',
who: '1',
what: '2',
subject: '3'
})
const response = await server.inject({
method: 'GET',
url: `/trails?from=${encodeURIComponent('2014-01-02T18:04:05.123+03:00')}&to=${encodeURIComponent('2018-01-02T18:04:05.123+03:00')}`
})
expect(response.statusCode).to.equal(200)
const trails = JSON.parse(response.payload)
expect(trails.count).to.equals(1)
expect(trails.data[0]).to.include({
id: id,
when: DateTime.fromISO('2016-01-02T15:04:05.123', { zone: 'utc' }).toISO(),
who: {
id: '1',
attributes: {}
},
what: {
id: '2',
attributes: {}
},
subject: {
id: '3',
attributes: {}
},
where: {},
why: {},
meta: {}
})
await server.trailCore.delete(id)
})
test('it should search trails and return a empty array when no records are found', async () => {
await server.trailCore.performDatabaseOperations(client => client.query('TRUNCATE trails'))
const id = await server.trailCore.insert({
when: '2016-01-02T18:04:05.123+03:00',
who: '1',
what: '2',
subject: '3'
})
const response = await server.inject({
method: 'GET',
url: `/trails?from=${encodeURIComponent('2014-01-02T18:04:05.123+03:00')}&to=${encodeURIComponent('2018-01-02T18:04:05.123+03:00')}&who=foo`
})
expect(response.statusCode).to.equal(200)
const trails = JSON.parse(response.payload)
expect(trails.count).to.equals(0)
expect(trails.data).to.equal([])
await server.trailCore.delete(id)
})
test('it should return 422 in case of validation errors', async () => {
const response = await server.inject({
method: 'GET',
url: '/trails?from=bar&where=foo'
})
expect(response.statusCode).to.equal(422)
expect(JSON.parse(response.payload)).to.include({
statusCode: 422,
error: 'Unprocessable Entity',
message: 'Invalid input data.',
reasons: {
from: 'must be a valid UTC timestamp in the format YYYY-MM-DDTHH:MM:SS.sss (example: 2018-07-06T12:34:56.123)',
to: 'must be present and non empty',
where: 'is not a valid attribute'
}
})
})
})
describe('GET /trails/enumerate', async () => {
test('it should enumerate trails and return it with 200', async () => {
await server.trailCore.performDatabaseOperations(client => client.query('TRUNCATE trails'))
const id = await server.trailCore.insert({
when: '2016-01-02T18:04:05.123+03:00',
who: '1',
what: '2',
subject: '3'
})
const response = await server.inject({
method: 'GET',
url: `/trails/enumerate?from=${encodeURIComponent('2014-01-02T18:04:05.123+03:00')}&to=${encodeURIComponent('2018-01-02T18:04:05.123+03:00')}&type=who`
})
expect(response.statusCode).to.equal(200)
const enumeration = JSON.parse(response.payload)
expect(enumeration).to.equal(['1'])
await server.trailCore.delete(id)
})
test('it should enumerate trails and return a empty array when no records are found', async () => {
await server.trailCore.performDatabaseOperations(client => client.query('TRUNCATE trails'))
const id = await server.trailCore.insert({
when: '2016-01-02T18:04:05.123+03:00',
who: '1',
what: '2',
subject: '3'
})
const response = await server.inject({
method: 'GET',
url: `/trails/enumerate?from=${encodeURIComponent('2014-01-02T18:04:05.123+03:00')}&to=${encodeURIComponent('2015-01-02T18:04:05.123+03:00')}&type=who`
})
expect(response.statusCode).to.equal(200)
const enumeration = JSON.parse(response.payload)
expect(enumeration).to.equal([])
await server.trailCore.delete(id)
})
test('it should return 422 in case of validation errors', async () => {
const response = await server.inject({
method: 'GET',
url: '/trails/enumerate?from=bar'
})
expect(response.statusCode).to.equal(422)
expect(JSON.parse(response.payload)).to.equal({
statusCode: 422,
error: 'Unprocessable Entity',
message: 'Invalid input data.',
reasons: {
from: 'must be a valid UTC timestamp in the format YYYY-MM-DDTHH:MM:SS.sss (example: 2018-07-06T12:34:56.123)',
to: 'must be present and non empty',
type: 'must be present and non empty'
}
})
})
})
describe('POST /trails', async () => {
test('it should create a new trail and return it with 201', async () => {
const response = await server.inject({
method: 'POST',
url: '/trails',
payload: {
when: '2016-01-02T18:04:05.123+03:00',
who: 'me',
what: { id: 'FOO', abc: 'cde' },
subject: 'FOO'
}
})
expect(response.statusCode).to.equal(201)
const trail = JSON.parse(response.payload)
expect(trail).to.include({
when: DateTime.fromISO('2016-01-02T15:04:05.123', { zone: 'utc' }).toISO(),
who: {
id: 'me',
attributes: {}
},
what: {
id: 'FOO',
attributes: {
abc: 'cde'
}
},
subject: {
id: 'FOO',
attributes: {}
},
where: {},
why: {},
meta: {}
})
await server.trailCore.delete(trail.id)
})
test('it should return 400 in case of invalid Content-Type header', async () => {
const response = await server.inject({
method: 'POST',
url: '/trails',
headers: {
'Content-Type': 'text/plain'
},
payload: 'abc'
})
expect(response.statusCode).to.equal(400)
expect(JSON.parse(response.payload)).to.include({ statusCode: 400, error: 'Bad Request', message: errorsMessages['json.contentType'] })
})
test('it should return 400 in case of invalid JSON payload', async () => {
const response = await server.inject({
method: 'POST',
url: '/trails',
payload: '{"a":1'
})
expect(response.statusCode).to.equal(400)
expect(JSON.parse(response.payload)).to.include({ statusCode: 400, error: 'Bad Request', message: errorsMessages['json.format'] })
})
test('it should return 422 in case of validation errors', async () => {
const response = await server.inject({
method: 'POST',
url: '/trails',
payload: {
when: 'invalid',
who: 123,
what: {},
meta: 'FOO'
}
})
expect(response.statusCode).to.equal(422)
expect(JSON.parse(response.payload)).to.include({
statusCode: 422,
error: 'Unprocessable Entity',
message: 'Invalid input data.',
reasons: {
meta: errorsMessages['object.base'],
subject: errorsMessages['any.required'],
what: errorsMessages['custom.stringOrObject'],
when: errorsMessages['string.isoDate'],
who: errorsMessages['custom.stringOrObject']
}
})
})
})
describe('GET /trails/{id}', async () => {
test('it should retrieve a existing trail and return it with 200', async () => {
const id = await server.trailCore.insert({
when: '2016-01-02T18:04:05.123+03:00',
who: 'me',
what: { id: 'FOO', abc: 'cde' },
subject: 'FOO'
})
const response = await server.inject({
method: 'GET',
url: `/trails/${id}`
})
expect(response.statusCode).to.equal(200)
const trail = JSON.parse(response.payload)
expect(trail).to.include({
when: DateTime.fromISO('2016-01-02T15:04:05.123', { zone: 'utc' }).toISO(),
who: {
id: 'me',
attributes: {}
},
what: {
id: 'FOO',
attributes: {
abc: 'cde'
}
},
subject: {
id: 'FOO',
attributes: {}
},
where: {},
why: {},
meta: {}
})
await server.trailCore.delete(id)
})
test('it should return 404 in case of a invalid trail', async () => {
const response = await server.inject({
method: 'GET',
url: '/trails/0'
})
expect(response.statusCode).to.equal(404)
expect(JSON.parse(response.payload)).to.include({
statusCode: 404,
error: 'Not Found',
message: 'Trail with id 0 not found.'
})
})
test('it should return 500 in case of internal errors', async () => {
const anotherServer = await testServer.build()
const spy = sinon.stub(anotherServer.trailCore, 'get').rejects(new Error('FOO'))
const response = await anotherServer.inject({
method: 'GET',
url: '/trails/123'
})
expect(response.statusCode).to.equal(500)
const parsed = JSON.parse(response.payload)
expect(parsed).to.include({
statusCode: 500,
error: 'Internal Server Error',
message: '[Error] FOO'
})
expect(parsed.stack).to.be.array()
spy.restore()
})
test('it should return 500 with error code in case of internal errors', async () => {
const anotherServer = await testServer.build()
const error = new Error('FOO')
error.code = 'CODE'
const spy = sinon.stub(anotherServer.trailCore, 'get').rejects(error)
const response = await anotherServer.inject({
method: 'GET',
url: '/trails/123'
})
expect(response.statusCode).to.equal(500)
const parsed = JSON.parse(response.payload)
expect(parsed).to.include({
statusCode: 500,
error: 'Internal Server Error',
message: '[CODE] FOO'
})
expect(parsed.stack).to.be.array()
spy.restore()
})
})
describe('PUT /trails/{id}', async () => {
test('it should update a existing trail and return it with 202', async () => {
const id = await server.trailCore.insert({
when: '2016-01-02T18:04:05.123+03:00',
who: '1',
what: '2',
subject: '3'
})
expect((await server.trailCore.get(id)).who).to.include({ id: '1' })
const response = await server.inject({
method: 'PUT',
url: `/trails/${id}`,
payload: {
when: '2016-01-02T18:04:05.123+03:00',
who: 'me',
what: { id: 'FOO', abc: 'cde' },
subject: 'FOO'
}
})
expect(response.statusCode).to.equal(202)
const trail = JSON.parse(response.payload)
expect(trail).to.include({
when: DateTime.fromISO('2016-01-02T15:04:05.123', { zone: 'utc' }).toISO(),
who: {
id: 'me',
attributes: {}
},
what: {
id: 'FOO',
attributes: {
abc: 'cde'
}
},
subject: {
id: 'FOO',
attributes: {}
},
where: {},
why: {},
meta: {}
})
expect((await server.trailCore.get(id)).who).to.include({ id: 'me' })
await server.trailCore.delete(id)
})
test('it should return 404 in case of a invalid trail', async () => {
const response = await server.inject({
method: 'PUT',
url: '/trails/0',
payload: {
when: '2016-01-02T18:04:05.123+03:00',
who: 'me',
what: { id: 'FOO', abc: 'cde' },
subject: 'FOO'
}
})
expect(response.statusCode).to.equal(404)
expect(JSON.parse(response.payload)).to.include({
statusCode: 404,
error: 'Not Found',
message: 'Trail with id 0 not found.'
})
})
})
describe('DELETE /trails/{id}', async () => {
test('it should delete a existing trail and acknowledge with 204', async () => {
const id = await server.trailCore.insert({
when: '2016-01-02T18:04:05.123+03:00',
who: '1',
what: '2',
subject: '3'
})
expect(await server.trailCore.get(id)).to.be.object()
const response = await server.inject({
method: 'DELETE',
url: `/trails/${id}`
})
expect(response.statusCode).to.equal(204)
expect(response.payload).to.equal('')
expect(await server.trailCore.get(id)).to.be.null()
await server.trailCore.delete(id)
})
test('it should return 404 in case of a invalid trail', async () => {
const response = await server.inject({
method: 'DELETE',
url: '/trails/0'
})
expect(response.statusCode).to.equal(404)
expect(JSON.parse(response.payload)).to.include({
statusCode: 404,
error: 'Not Found',
message: 'Trail with id 0 not found.'
})
})
})
})