From 5a20cd2292c694f1c83c009a9638574751afcd1a Mon Sep 17 00:00:00 2001 From: Aori Nevo Date: Sun, 7 Jul 2019 19:50:19 -0400 Subject: [PATCH 1/4] chore: add support for Hapi 18. --- lib/instrumentation/@hapi/hapi.js | 3 + lib/instrumentations.js | 1 + .../capture-params.tap.js | 0 .../errors.tap.js | 0 .../ext.tap.js | 0 .../hapi-17-utils.js | 0 .../hapi.tap.js | 0 .../ignoring.tap.js | 0 .../newrelic.js | 0 .../package.json | 0 .../plugins.tap.js | 0 .../render.tap.js | 0 .../router.tap.js | 0 .../segments.tap.js | 0 .../vhost.tap.js | 0 .../hapi/hapi-post-18/capture-params.tap.js | 150 +++++++++ .../versioned/hapi/hapi-post-18/errors.tap.js | 223 +++++++++++++ test/versioned/hapi/hapi-post-18/ext.tap.js | 140 ++++++++ .../hapi/hapi-post-18/hapi-17-utils.js | 19 ++ test/versioned/hapi/hapi-post-18/hapi.tap.js | 28 ++ .../hapi/hapi-post-18/ignoring.tap.js | 55 +++ test/versioned/hapi/hapi-post-18/newrelic.js | 19 ++ test/versioned/hapi/hapi-post-18/package.json | 34 ++ .../hapi/hapi-post-18/plugins.tap.js | 111 +++++++ .../versioned/hapi/hapi-post-18/render.tap.js | 221 ++++++++++++ .../versioned/hapi/hapi-post-18/router.tap.js | 314 ++++++++++++++++++ .../hapi/hapi-post-18/segments.tap.js | 170 ++++++++++ test/versioned/hapi/hapi-post-18/vhost.tap.js | 79 +++++ 28 files changed, 1567 insertions(+) create mode 100644 lib/instrumentation/@hapi/hapi.js rename test/versioned/hapi/{hapi-post-17 => hapi-post-17-pre-18}/capture-params.tap.js (100%) rename test/versioned/hapi/{hapi-post-17 => hapi-post-17-pre-18}/errors.tap.js (100%) rename test/versioned/hapi/{hapi-post-17 => hapi-post-17-pre-18}/ext.tap.js (100%) rename test/versioned/hapi/{hapi-post-17 => hapi-post-17-pre-18}/hapi-17-utils.js (100%) rename test/versioned/hapi/{hapi-post-17 => hapi-post-17-pre-18}/hapi.tap.js (100%) rename test/versioned/hapi/{hapi-post-17 => hapi-post-17-pre-18}/ignoring.tap.js (100%) rename test/versioned/hapi/{hapi-post-17 => hapi-post-17-pre-18}/newrelic.js (100%) rename test/versioned/hapi/{hapi-post-17 => hapi-post-17-pre-18}/package.json (100%) rename test/versioned/hapi/{hapi-post-17 => hapi-post-17-pre-18}/plugins.tap.js (100%) rename test/versioned/hapi/{hapi-post-17 => hapi-post-17-pre-18}/render.tap.js (100%) rename test/versioned/hapi/{hapi-post-17 => hapi-post-17-pre-18}/router.tap.js (100%) rename test/versioned/hapi/{hapi-post-17 => hapi-post-17-pre-18}/segments.tap.js (100%) rename test/versioned/hapi/{hapi-post-17 => hapi-post-17-pre-18}/vhost.tap.js (100%) create mode 100644 test/versioned/hapi/hapi-post-18/capture-params.tap.js create mode 100644 test/versioned/hapi/hapi-post-18/errors.tap.js create mode 100644 test/versioned/hapi/hapi-post-18/ext.tap.js create mode 100644 test/versioned/hapi/hapi-post-18/hapi-17-utils.js create mode 100644 test/versioned/hapi/hapi-post-18/hapi.tap.js create mode 100644 test/versioned/hapi/hapi-post-18/ignoring.tap.js create mode 100644 test/versioned/hapi/hapi-post-18/newrelic.js create mode 100644 test/versioned/hapi/hapi-post-18/package.json create mode 100644 test/versioned/hapi/hapi-post-18/plugins.tap.js create mode 100644 test/versioned/hapi/hapi-post-18/render.tap.js create mode 100644 test/versioned/hapi/hapi-post-18/router.tap.js create mode 100644 test/versioned/hapi/hapi-post-18/segments.tap.js create mode 100644 test/versioned/hapi/hapi-post-18/vhost.tap.js diff --git a/lib/instrumentation/@hapi/hapi.js b/lib/instrumentation/@hapi/hapi.js new file mode 100644 index 0000000000..f461a343e6 --- /dev/null +++ b/lib/instrumentation/@hapi/hapi.js @@ -0,0 +1,3 @@ +'use strict' + +module.exports = require('../hapi'); diff --git a/lib/instrumentations.js b/lib/instrumentations.js index 8e01d23064..9b4655a41b 100644 --- a/lib/instrumentations.js +++ b/lib/instrumentations.js @@ -13,6 +13,7 @@ module.exports = function instrumentations() { 'director': {type: MODULE_TYPE.WEB_FRAMEWORK}, 'express': {type: MODULE_TYPE.WEB_FRAMEWORK}, 'generic-pool': {type: MODULE_TYPE.GENERIC}, + '@hapi/hapi': {type: MODULE_TYPE.WEB_FRAMEWORK}, 'hapi': {type: MODULE_TYPE.WEB_FRAMEWORK}, 'ioredis': {type: MODULE_TYPE.DATASTORE}, 'koa': {module: '@newrelic/koa'}, diff --git a/test/versioned/hapi/hapi-post-17/capture-params.tap.js b/test/versioned/hapi/hapi-post-17-pre-18/capture-params.tap.js similarity index 100% rename from test/versioned/hapi/hapi-post-17/capture-params.tap.js rename to test/versioned/hapi/hapi-post-17-pre-18/capture-params.tap.js diff --git a/test/versioned/hapi/hapi-post-17/errors.tap.js b/test/versioned/hapi/hapi-post-17-pre-18/errors.tap.js similarity index 100% rename from test/versioned/hapi/hapi-post-17/errors.tap.js rename to test/versioned/hapi/hapi-post-17-pre-18/errors.tap.js diff --git a/test/versioned/hapi/hapi-post-17/ext.tap.js b/test/versioned/hapi/hapi-post-17-pre-18/ext.tap.js similarity index 100% rename from test/versioned/hapi/hapi-post-17/ext.tap.js rename to test/versioned/hapi/hapi-post-17-pre-18/ext.tap.js diff --git a/test/versioned/hapi/hapi-post-17/hapi-17-utils.js b/test/versioned/hapi/hapi-post-17-pre-18/hapi-17-utils.js similarity index 100% rename from test/versioned/hapi/hapi-post-17/hapi-17-utils.js rename to test/versioned/hapi/hapi-post-17-pre-18/hapi-17-utils.js diff --git a/test/versioned/hapi/hapi-post-17/hapi.tap.js b/test/versioned/hapi/hapi-post-17-pre-18/hapi.tap.js similarity index 100% rename from test/versioned/hapi/hapi-post-17/hapi.tap.js rename to test/versioned/hapi/hapi-post-17-pre-18/hapi.tap.js diff --git a/test/versioned/hapi/hapi-post-17/ignoring.tap.js b/test/versioned/hapi/hapi-post-17-pre-18/ignoring.tap.js similarity index 100% rename from test/versioned/hapi/hapi-post-17/ignoring.tap.js rename to test/versioned/hapi/hapi-post-17-pre-18/ignoring.tap.js diff --git a/test/versioned/hapi/hapi-post-17/newrelic.js b/test/versioned/hapi/hapi-post-17-pre-18/newrelic.js similarity index 100% rename from test/versioned/hapi/hapi-post-17/newrelic.js rename to test/versioned/hapi/hapi-post-17-pre-18/newrelic.js diff --git a/test/versioned/hapi/hapi-post-17/package.json b/test/versioned/hapi/hapi-post-17-pre-18/package.json similarity index 100% rename from test/versioned/hapi/hapi-post-17/package.json rename to test/versioned/hapi/hapi-post-17-pre-18/package.json diff --git a/test/versioned/hapi/hapi-post-17/plugins.tap.js b/test/versioned/hapi/hapi-post-17-pre-18/plugins.tap.js similarity index 100% rename from test/versioned/hapi/hapi-post-17/plugins.tap.js rename to test/versioned/hapi/hapi-post-17-pre-18/plugins.tap.js diff --git a/test/versioned/hapi/hapi-post-17/render.tap.js b/test/versioned/hapi/hapi-post-17-pre-18/render.tap.js similarity index 100% rename from test/versioned/hapi/hapi-post-17/render.tap.js rename to test/versioned/hapi/hapi-post-17-pre-18/render.tap.js diff --git a/test/versioned/hapi/hapi-post-17/router.tap.js b/test/versioned/hapi/hapi-post-17-pre-18/router.tap.js similarity index 100% rename from test/versioned/hapi/hapi-post-17/router.tap.js rename to test/versioned/hapi/hapi-post-17-pre-18/router.tap.js diff --git a/test/versioned/hapi/hapi-post-17/segments.tap.js b/test/versioned/hapi/hapi-post-17-pre-18/segments.tap.js similarity index 100% rename from test/versioned/hapi/hapi-post-17/segments.tap.js rename to test/versioned/hapi/hapi-post-17-pre-18/segments.tap.js diff --git a/test/versioned/hapi/hapi-post-17/vhost.tap.js b/test/versioned/hapi/hapi-post-17-pre-18/vhost.tap.js similarity index 100% rename from test/versioned/hapi/hapi-post-17/vhost.tap.js rename to test/versioned/hapi/hapi-post-17-pre-18/vhost.tap.js diff --git a/test/versioned/hapi/hapi-post-18/capture-params.tap.js b/test/versioned/hapi/hapi-post-18/capture-params.tap.js new file mode 100644 index 0000000000..e466c3376e --- /dev/null +++ b/test/versioned/hapi/hapi-post-18/capture-params.tap.js @@ -0,0 +1,150 @@ +'use strict' + +var DESTINATIONS = require('../../../../lib/config/attribute-filter').DESTINATIONS +var tap = require('tap') +var request = require('request') +var helper = require('../../../lib/agent_helper') +var utils = require('./hapi-17-utils') +var HTTP_ATTS = require('../../../lib/fixtures').httpAttributes + +tap.test('Hapi capture params support', function(t) { + t.autoend() + + var agent = null + var server = null + var port = null + + t.beforeEach(function(done) { + agent = helper.instrumentMockedAgent({ + attributes: { + enabled: true, + include: ['request.parameters.*'] + } + }) + + server = utils.getServer() + + agent.config.attributes.enabled = true + done() + }) + + t.afterEach(function() { + helper.unloadAgent(agent) + return server.stop() + }) + + t.test('simple case with no params', function(t) { + agent.on('transactionFinished', function(transaction) { + t.ok(transaction.trace, 'transaction has a trace.') + var attributes = transaction.trace.attributes.get(DESTINATIONS.TRANS_TRACE) + HTTP_ATTS.forEach(function(key) { + t.ok(attributes[key], 'Trace contains expected HTTP attribute: ' + key) + }) + }) + + server.route({ + method: 'GET', + path: '/test/', + handler: function() { + t.ok(agent.getTransaction(), 'transaction is available inside route handler') + return { status: 'ok' } + } + }) + + server.start().then(function() { + port = server.info.port + makeRequest(t, 'http://localhost:' + port + '/test/') + }) + }) + + t.test('case with route params', function(t) { + agent.on('transactionFinished', function(tx) { + t.ok(tx.trace, 'transaction has a trace.') + var attributes = tx.trace.attributes.get(DESTINATIONS.TRANS_TRACE) + t.equal( + attributes['request.parameters.id'], '1337', + 'Trace attributes include `id` route param' + ) + }) + + server.route({ + method: 'GET', + path: '/test/{id}/', + handler: function() { + t.ok(agent.getTransaction(), 'transaction is available') + return { status: 'ok' } + } + }) + + server.start().then(function() { + port = server.info.port + makeRequest(t, 'http://localhost:' + port + '/test/1337/') + }) + }) + + t.test('case with query params', function(t) { + agent.on('transactionFinished', function(tx) { + t.ok(tx.trace, 'transaction has a trace.') + var attributes = tx.trace.attributes.get(DESTINATIONS.TRANS_TRACE) + t.equal( + attributes['request.parameters.name'], 'hapi', + 'Trace attributes include `name` query param' + ) + }) + + server.route({ + method: 'GET', + path: '/test/', + handler: function() { + t.ok(agent.getTransaction(), 'transaction is available') + return { status: 'ok' } + } + }) + + server.start().then(function() { + port = server.info.port + makeRequest(t, 'http://localhost:' + port + '/test/?name=hapi') + }) + }) + + t.test('case with both route and query params', function(t) { + agent.on('transactionFinished', function(tx) { + t.ok(tx.trace, 'transaction has a trace.') + var attributes = tx.trace.attributes.get(DESTINATIONS.TRANS_TRACE) + t.equal( + attributes['request.parameters.id'], '1337', + 'Trace attributes include `id` route param' + ) + t.equal( + attributes['request.parameters.name'], 'hapi', + 'Trace attributes include `name` query param' + ) + }) + + server.route({ + method: 'GET', + path: '/test/{id}/', + handler: function() { + t.ok(agent.getTransaction(), 'transaction is available') + return { status: 'ok' } + } + }) + + server.start().then(function() { + port = server.info.port + makeRequest(t, 'http://localhost:' + port + '/test/1337/?name=hapi') + }) + }) +}) + +function makeRequest(t, uri) { + var params = { + uri: uri, + json: true + } + request.get(params, function(err, res, body) { + t.equal(res.statusCode, 200, "nothing exploded") + t.deepEqual(body, {status: 'ok'}, "got expected response") + t.end() + }) +} diff --git a/test/versioned/hapi/hapi-post-18/errors.tap.js b/test/versioned/hapi/hapi-post-18/errors.tap.js new file mode 100644 index 0000000000..cb8ab4fefe --- /dev/null +++ b/test/versioned/hapi/hapi-post-18/errors.tap.js @@ -0,0 +1,223 @@ +'use strict' + +var helper = require('../../../lib/agent_helper') +var http = require('http') +var tap = require('tap') +var utils = require('./hapi-17-utils') + +var agent +var server +var port + +tap.test('Hapi v17 error handling', function(t) { + t.autoend() + + t.beforeEach(function(done) { + agent = helper.instrumentMockedAgent() + + server = utils.getServer() + done() + }) + + t.afterEach(function() { + helper.unloadAgent(agent) + return server.stop() + }) + + t.test('does not report error when handler returns a string', function(t) { + server.route({ + method: 'GET', + path: '/test', + handler: function() { + return 'ok' + } + }) + + runTest(t, function(errors, statusCode) { + t.equals(errors.length, 0, 'should have no errors') + t.equals(statusCode, 200, 'should have a 200 status code') + t.end() + }) + }) + + t.test('reports error when an instance of Error is returned', function(t) { + server.route({ + method: 'GET', + path: '/test', + handler: function() { + return Promise.reject(new Error('rejected promise error')) + } + }) + + runTest(t, function(errors, statusCode) { + t.equals(errors.length, 1, 'should have one error') + + t.equals( + errors[0][2], + 'rejected promise error', + 'should have expected error message' + ) + + t.equals(statusCode, 500, 'should have expected error code') + t.end() + }) + }) + + t.test('reports error when thrown from a route', function(t) { + server.route({ + method: 'GET', + path: '/test', + handler: function() { + throw new Error('thrown error') + } + }) + + runTest(t, function(errors, statusCode) { + t.equals(errors.length, 1, 'should have one error') + t.equals(errors[0][2], 'thrown error', 'should have expected error message') + t.equals(statusCode, 500, 'should have expected error code') + t.end() + }) + }) + + t.test('reports error when thrown from a middleware', function(t) { + server.ext('onRequest', function() { + throw new Error('middleware error') + }) + + server.route({ + method: 'GET', + path: '/test', + handler: function() { + return 'ok' + } + }) + + runTest(t, function(errors, statusCode) { + t.equals(errors.length, 1, 'should have one error') + t.equals(errors[0][2], 'middleware error', 'should have expected error message') + t.equals(statusCode, 500, 'should have expected error code') + t.end() + }) + }) + + t.test('reports error when error handler replies with transformed error', (t) => { + server.ext('onPreResponse', (req) => { + t.ok(req.response instanceof Error, 'preResponse has error') + req.response.output.statusCode = 400 + return req.response + }) + + server.route({ + method: 'GET', + path: '/test', + handler: () => { + throw new Error('route handler error') + } + }) + + runTest(t, (errors, statusCode) => { + t.equals(errors.length, 1, 'has 1 reported error') + t.equals(errors[0][2], 'route handler error', 'has correct error message') + t.equals(statusCode, 400, 'has expected 400 status code') + t.end() + }) + }) + + t.test('reports error when error handler continues with transformed response', (t) => { + server.ext('onPreResponse', (req, h) => { + t.ok(req.response instanceof Error, 'preResponse has error') + req.response.output.statusCode = 400 + return h.continue + }) + + server.route({ + method: 'GET', + path: '/test', + handler: () => { + throw new Error('route handler error') + } + }) + + runTest(t, (errors, statusCode) => { + t.equals(errors.length, 1, 'has 1 reported error') + t.equals(errors[0][2], 'route handler error', 'has correct error message') + t.equals(statusCode, 400, 'has expected 400 status code') + t.end() + }) + }) + + t.test('reports error when error handler continues with original response', (t) => { + server.ext('onPreResponse', (req, h) => { + t.ok(req.response instanceof Error, 'preResponse has error') + return h.continue + }) + + server.route({ + method: 'GET', + path: '/test', + handler: () => { + throw new Error('route handler error') + } + }) + + runTest(t, (errors, statusCode) => { + t.equals(errors.length, 1, 'has 1 reported error') + t.equals(errors[0][2], 'route handler error', 'has correct error message') + t.equals(statusCode, 500, 'has expected 500 status code') + t.end() + }) + }) + + t.test('should not report error when error handler responds', (t) => { + server.ext('onPreResponse', (req) => { + t.ok(req.response.isBoom, 'preResponse has error') + return null + }) + + server.route({ + method: 'GET', + path: '/test', + handler: () => { + throw new Error('route handler error') + } + }) + + runTest(t, (errors, statusCode) => { + t.equals(errors.length, 0, 'has no reported errors') + t.equals(statusCode, 200, 'has expected 200 status') + t.end() + }) + }) +}) + +function runTest(t, callback) { + var statusCode + var errors + + agent.on('transactionFinished', function() { + errors = agent.errors.errors + if (statusCode) { + callback(errors, statusCode) + } + }) + + var endpoint = '/test' + server.start().then(function() { + port = server.info.port + makeRequest(endpoint, function(response) { + statusCode = response.statusCode + if (errors) { + callback(errors, statusCode) + } + response.resume() + }) + }) + t.tearDown(function() { + server.stop() + }) +} + +function makeRequest(path, callback) { + http.request({port: port, path: path}, callback).end() +} diff --git a/test/versioned/hapi/hapi-post-18/ext.tap.js b/test/versioned/hapi/hapi-post-18/ext.tap.js new file mode 100644 index 0000000000..a8e0e1cf49 --- /dev/null +++ b/test/versioned/hapi/hapi-post-18/ext.tap.js @@ -0,0 +1,140 @@ +'use strict' + +var request = require('request') +var tap = require('tap') +var helper = require('../../../lib/agent_helper') +var utils = require('./hapi-17-utils') + +tap.test('Hapi v17 ext', function(t) { + t.autoend() + + var agent = null + var server = null + var port = null + + // Queue that executes outside of a transaction context + var tasks = [] + var intervalId = setInterval(function() { + while (tasks.length) { + var task = tasks.pop() + task() + } + }, 10) + function resolveOutOfScope(val) { + return new Promise(function(resolve) { + tasks.push(function() { + resolve(val) + }) + }) + } + + t.tearDown(function() { + clearInterval(intervalId) + }) + + t.beforeEach(function(done) { + agent = helper.instrumentMockedAgent() + + server = utils.getServer() + done() + }) + + t.afterEach(function() { + helper.unloadAgent(agent) + return server.stop() + }) + + t.test('keeps context with a single handler', function(t) { + server.ext('onRequest', function(req, h) { + t.ok(agent.getTransaction(), 'transaction is available in onRequest handler') + return resolveOutOfScope(h.continue) + }) + + addRouteAndGet(t) + }) + + t.test('keeps context with a handler object with a single method', function(t) { + server.ext({ + type: 'onRequest', + method: function(req, h) { + t.ok(agent.getTransaction(), 'transaction is available in onRequest handler') + return resolveOutOfScope(h.continue) + } + }) + + addRouteAndGet(t) + }) + + t.test('keeps context with a handler object with an array of methods', function(t) { + server.ext({ + type: 'onRequest', + method: [ + function(req, h) { + t.ok(agent.getTransaction(), 'transaction is available in first handler') + return resolveOutOfScope(h.continue) + }, + function(req, h) { + t.ok(agent.getTransaction(), 'transaction is available in second handler') + return Promise.resolve(h.continue) + } + ] + }) + + addRouteAndGet(t) + }) + + t.test('keeps context with an array of handlers and an array of methods', function(t) { + server.ext([{ + type: 'onRequest', + method: [ + function(req, h) { + t.ok(agent.getTransaction(), 'transaction is available in first handler') + return resolveOutOfScope(h.continue) + }, + function(req, h) { + t.ok(agent.getTransaction(), 'transaction is available in second handler') + return Promise.resolve(h.continue) + } + ] + }, { + type: 'onPreHandler', + method: function(req, h) { + t.ok(agent.getTransaction(), 'transaction is available in third handler') + return resolveOutOfScope(h.continue) + } + }]) + + addRouteAndGet(t) + }) + + t.test('does not crash on non-request events', function(t) { + server.ext('onPreStart', function(s) { + t.notOk(agent.getTransaction(), 'should not have transaction in server events') + t.equal(s, server, 'should pass through arguments without change') + return Promise.resolve() + }) + + addRouteAndGet(t) + }) + + function addRouteAndGet(t) { + server.route({ + method: 'GET', + path: '/test', + handler: function myHandler() { + t.ok(agent.getTransaction(), 'transaction is available in route handler') + return 'ok' + } + }) + + server.start().then(function() { + port = server.info.port + request.get('http://localhost:' + port + '/test', function() { + t.end() + }) + }).catch(function(err) { + t.error(err, 'should not fail to start server and request') + t.end() + }) + } +}) diff --git a/test/versioned/hapi/hapi-post-18/hapi-17-utils.js b/test/versioned/hapi/hapi-post-18/hapi-17-utils.js new file mode 100644 index 0000000000..59af1b3c7b --- /dev/null +++ b/test/versioned/hapi/hapi-post-18/hapi-17-utils.js @@ -0,0 +1,19 @@ +'use strict' + +const tap = require('tap') + +exports.getServer = function getServer(cfg) { + cfg = cfg || {} + var host = cfg.host || 'localhost' + var port = cfg.port || 0 + var opts = cfg.options || {} + var hapi = cfg.hapi || require('@hapi/hapi') + + // v17 exports two references to the server object, + // so we'll let fate decide which to use for a given test + const servers = ['Server', 'server'] + const server = servers[Math.round(Math.random())] + + tap.comment(`Randomly testing with hapi.${server}`) + return hapi[server](Object.assign({}, opts, {host, port})) +} diff --git a/test/versioned/hapi/hapi-post-18/hapi.tap.js b/test/versioned/hapi/hapi-post-18/hapi.tap.js new file mode 100644 index 0000000000..65df0b795a --- /dev/null +++ b/test/versioned/hapi/hapi-post-18/hapi.tap.js @@ -0,0 +1,28 @@ +'use strict' + +var tap = require('tap') +var shims = require('../../../../lib/shim') +var helper = require('../../../lib/agent_helper') +var instrument = require('../../../../lib/instrumentation/hapi') +var utils = require('./hapi-17-utils') + +tap.test('instrumentation of Hapi', function(t) { + t.autoend() + + t.test('preserves server creation return', function(t) { + var agent = helper.loadMockedAgent() + var hapi = require('@hapi/hapi') + var returned = utils.getServer({ hapi: hapi }) + + t.ok(returned != null, 'Hapi returns from server creation') + + var shim = new shims.WebFrameworkShim(agent, 'hapi') + instrument(agent, hapi, 'hapi', shim) + + var returned2 = utils.getServer({ hapi: hapi }) + + t.ok(returned2 != null, 'Server creation returns when instrumented') + + t.end() + }) +}) diff --git a/test/versioned/hapi/hapi-post-18/ignoring.tap.js b/test/versioned/hapi/hapi-post-18/ignoring.tap.js new file mode 100644 index 0000000000..66624778c1 --- /dev/null +++ b/test/versioned/hapi/hapi-post-18/ignoring.tap.js @@ -0,0 +1,55 @@ +'use strict' + +var test = require('tap').test +var request = require('request') +var helper = require('../../../lib/agent_helper') +var API = require('../../../../api') +var utils = require('./hapi-17-utils') + +test('ignoring a Hapi route', function(t) { + t.plan(6) + + const agent = helper.instrumentMockedAgent() + const api = new API(agent) + const server = utils.getServer() + + t.tearDown(function() { + helper.unloadAgent(agent) + return server.stop() + }) + + agent.on('transactionFinished', function(transaction) { + t.ok(transaction.ignore, 'transaction is ignored') + + t.notOk(agent.traces.trace, 'should have no transaction trace') + + var metrics = agent.metrics.unscoped + t.equal(Object.keys(metrics).length, 1, + 'only supportability metrics added to agent collection' + ) + + var errors = agent.errors.errors + t.equal(errors.length, 0, 'no errors noticed') + }) + + server.route({ + method: 'GET', + path: '/order/{id}', + handler: function(req, h) { + api.setIgnoreTransaction(true) + return h.response({ status: 'cartcartcart' }).code(400) + } + }) + + server.start().then(function() { + var port = server.info.port + var params = { + uri: 'http://localhost:' + port + '/order/31337', + json: true + } + request.get(params, function(error, res, body) { + t.equal(res.statusCode, 400, 'got expected error') + t.deepEqual(body, {status: 'cartcartcart'}, 'got expected response') + }) + }) +}) diff --git a/test/versioned/hapi/hapi-post-18/newrelic.js b/test/versioned/hapi/hapi-post-18/newrelic.js new file mode 100644 index 0000000000..f0b4584b43 --- /dev/null +++ b/test/versioned/hapi/hapi-post-18/newrelic.js @@ -0,0 +1,19 @@ +'use strict' + +exports.config = { + app_name: ['My Application'], + license_key: 'license key here', + logging: { + level: 'trace' + }, + utilization: { + detect_aws: false, + detect_pcf: false, + detect_azure: false, + detect_gcp: false, + detect_docker: false + }, + transaction_tracer: { + enabled: true + } +} diff --git a/test/versioned/hapi/hapi-post-18/package.json b/test/versioned/hapi/hapi-post-18/package.json new file mode 100644 index 0000000000..c0b0e1a480 --- /dev/null +++ b/test/versioned/hapi/hapi-post-18/package.json @@ -0,0 +1,34 @@ +{ + "name": "hapi-v17-tests", + "version": "0.0.0", + "private": true, + "tests": [ + { + "engines": { + "node": ">=8.9" + }, + "dependencies": { + "ejs": "2.5.5", + "@hapi/hapi": ">=18.3.1", + "vision": "^5.0.0" + }, + "files": [ + "capture-params.tap.js", + "errors.tap.js", + "ext.tap.js", + "hapi.tap.js", + "ignoring.tap.js", + "plugins.tap.js", + "render.tap.js", + "router.tap.js", + "segments.tap.js", + "vhost.tap.js" + ] + } + ], + "dependencies": { + "ejs": "^2.5.5", + "@hapi/hapi": "^18.3.1", + "vision": "^5.0.0" + } +} diff --git a/test/versioned/hapi/hapi-post-18/plugins.tap.js b/test/versioned/hapi/hapi-post-18/plugins.tap.js new file mode 100644 index 0000000000..c687f17682 --- /dev/null +++ b/test/versioned/hapi/hapi-post-18/plugins.tap.js @@ -0,0 +1,111 @@ +'use strict' + +var helper = require('../../../lib/agent_helper') +var request = require('request') +var tap = require('tap') +var utils = require('./hapi-17-utils') + +tap.test('Hapi Plugins', function(t) { + t.autoend() + + var agent = null + var server = null + var port = null + + // queue that executes outside of a transaction context + var tasks = [] + var intervalId = setInterval(function() { + while (tasks.length) { + var task = tasks.pop() + task() + } + }, 10) + + t.tearDown(function() { + clearInterval(intervalId) + }) + + t.beforeEach(function(done) { + agent = helper.instrumentMockedAgent() + + server = utils.getServer() + done() + }) + + t.afterEach(function() { + helper.unloadAgent(agent) + return server.stop() + }) + + t.test('maintains transaction state', function(t) { + t.plan(3) + + var plugin = { + register: function(srvr) { + srvr.route({ + method: 'GET', + path: '/test', + handler: function myHandler() { + t.ok(agent.getTransaction(), 'transaction is available') + return Promise.resolve('hello') + } + }) + }, + name: 'foobar' + } + + agent.on('transactionFinished', function(tx) { + t.equal( + tx.getFullName(), 'WebTransaction/Hapi/GET//test', + 'should name transaction correctly' + ) + }) + + server.register(plugin) + .then(function() { + return server.start() + }) + .then(function() { + port = server.info.port + request.get('http://localhost:' + port + '/test', function(error, res, body) { + t.equal(body, 'hello', 'should not interfere with response') + }) + }) + }) + + t.test('includes route prefix in transaction name', function(t) { + t.plan(3) + + var plugin = { + register: function(srvr) { + srvr.route({ + method: 'GET', + path: '/test', + handler: function myHandler() { + t.ok(agent.getTransaction(), 'transaction is available') + return Promise.resolve('hello') + } + }) + }, + name: 'foobar' + } + + agent.on('transactionFinished', function(tx) { + t.equal( + tx.getFullName(), 'WebTransaction/Hapi/GET//prefix/test', + 'should name transaction correctly' + ) + }) + + server.register(plugin, {routes: { prefix: '/prefix' }}) + .then(function() { + return server.start() + }) + .then(function() { + port = server.info.port + request.get('http://localhost:' + port + '/prefix/test', function(error, res, body) { + t.equal(body, 'hello', 'should not interfere with response') + }) + }) + }) +}) diff --git a/test/versioned/hapi/hapi-post-18/render.tap.js b/test/versioned/hapi/hapi-post-18/render.tap.js new file mode 100644 index 0000000000..76275a0f3b --- /dev/null +++ b/test/versioned/hapi/hapi-post-18/render.tap.js @@ -0,0 +1,221 @@ +'use strict' + +var util = require('util') +var path = require('path') +var tap = require('tap') +var request = require('request') +var helper = require('../../../lib/agent_helper') +var API = require('../../../../api') +var utils = require('./hapi-17-utils') +var fixtures = require('../fixtures') + +tap.test('agent instrumentation of Hapi', function(t) { + t.autoend() + + var agent = null + var server = null + var port = null + + t.beforeEach(function(done) { + agent = helper.instrumentMockedAgent() + + server = utils.getServer() + done() + }) + + t.afterEach(function() { + helper.unloadAgent(agent) + return server.stop() + }) + + t.test('for a normal request', {timeout: 5000}, function(t) { + // set apdexT so apdex stats will be recorded + agent.config.apdex_t = 1 + + server.route({ + method: 'GET', + path: '/test', + handler: function() { + return { yep: true } + } + }) + + server.start().then(function() { + port = server.info.port + request.get('http://localhost:' + port + '/test', function(error, response, body) { + t.error(error, 'should not fail to make request') + + t.ok( + /application\/json/.test(response.headers['content-type']), + 'got correct content type' + ) + t.deepEqual(JSON.parse(body), { yep: true }, 'response survived') + + var stats + + stats = agent.metrics.getMetric('WebTransaction/Hapi/GET//test') + t.ok(stats, 'found unscoped stats for request path') + t.equal(stats.callCount, 1, '/test was only requested once') + + stats = agent.metrics.getOrCreateApdexMetric('Apdex/Hapi/GET//test') + t.ok(stats, 'found apdex stats for request path') + t.equal(stats.satisfying, 1, 'got satisfactory response time') + t.equal(stats.tolerating, 0, 'got no tolerable requests') + t.equal(stats.frustrating, 0, 'got no frustrating requests') + + stats = agent.metrics.getMetric('WebTransaction') + t.ok(stats, 'found roll-up statistics for web requests') + t.equal(stats.callCount, 1, 'only one web request was made') + + stats = agent.metrics.getMetric('HttpDispatcher') + t.ok(stats, 'found HTTP dispatcher statistics') + t.equal(stats.callCount, 1, 'only one HTTP-dispatched request was made') + + var serialized = JSON.stringify(agent.metrics) + t.ok( + serialized.match(/WebTransaction\/Hapi\/GET\/\/test/), + 'serialized metrics as expected' + ) + + t.end() + }) + }) + }) + + t.test('using EJS templates', {timeout: 2000}, function(t) { + server.route({ + method: 'GET', + path: '/test', + handler: function(req, h) { + return h.view('index', {title: 'yo dawg'}) + } + }) + + agent.once('transactionFinished', function(tx) { + console.log('---- metrics: ', agent.metrics); + var stats = agent.metrics.getMetric('View/index/Rendering') + t.ok(stats, 'View metric should exist') + t.equal(stats.callCount, 1, 'should note the view rendering') + verifyEnded(tx.trace.root, tx) + }) + + function verifyEnded(root, tx) { + for (var i = 0, len = root.children.length; i < len; i++) { + var segment = root.children[i] + t.ok( + segment.timer.hasEnd(), + util.format('verify %s (%s) has ended', segment.name, tx.id) + ) + if (segment.children) verifyEnded(segment, tx) + } + } + + server.register(require('vision')) + .then(function() { + server.views({ + path: path.join(__dirname, '../views'), + engines: { + ejs: require('ejs') + } + }) + return server.start() + }) + .then(function() { + port = server.info.port + request('http://localhost:' + port + '/test', function(error, response, body) { + if (error) t.fail(error) + + t.equal(response.statusCode, 200, 'response code should be 200') + t.equal(body, fixtures.htmlBody, 'template should still render fine') + + t.end() + }) + }) + }) + + t.test('should generate rum headers', { timeout: 1000 }, function(t) { + var api = new API(agent) + + agent.config.application_id = '12345' + agent.config.browser_monitoring.browser_key = '12345' + agent.config.browser_monitoring.js_agent_loader = 'function(){}' + + server.route({ + method: 'GET', + path: '/test', + handler: function(req, h) { + var rum = api.getBrowserTimingHeader() + t.equal(rum.substr(0,7), '//onRequest', + NAMES.HAPI.MIDDLEWARE + 'myHandler//test' + ]) + checkSegments(t, transaction.trace.root.children[0], [ + NAMES.HAPI.MIDDLEWARE + '//onRequest', + NAMES.HAPI.MIDDLEWARE + 'myHandler//test' + ]) + t.end() + }) + }) + + t.test('custom route handler and extension recorded as middleware', function(t) { + server.ext('onRequest', function(req, h) { + return h.continue + }) + + server.decorate('handler', 'customHandler', function(route, options) { + return function customHandler() { + return options.key1 + } + }) + + server.route({ + method: 'GET', + path: '/test', + handler: { customHandler: { key1: 'val1'} } + }) + + runTest(t, function(segments, transaction) { + checkMetrics(t, transaction.metrics, [ + NAMES.HAPI.MIDDLEWARE + '//onRequest', + NAMES.HAPI.MIDDLEWARE + 'customHandler//test' + ]) + checkSegments(t, transaction.trace.root.children[0], [ + NAMES.HAPI.MIDDLEWARE + '//onRequest', + NAMES.HAPI.MIDDLEWARE + 'customHandler//test' + ]) + t.end() + }) + }) +}) + +function runTest(t, callback) { + agent.on('transactionFinished', function(tx) { + var baseSegment = tx.trace.root.children[0] + callback(baseSegment.children, tx) + }) + + server.start().then(function() { + port = server.info.port + http.request({ port: port, path: '/test' }, function(response) { + response.resume() + }).end() + }) +} + +function checkMetrics(t, metrics, expected, path) { + path = path || '/test' + var expectedAll = [ + [{name: 'WebTransaction'}], + [{name: 'WebTransactionTotalTime'}], + [{name: 'HttpDispatcher'}], + [{name: 'WebTransaction/Hapi/GET/' + path}], + [{name: 'WebTransactionTotalTime/Hapi/GET/' + path}], + [{name: 'Apdex/Hapi/GET/' + path}], + [{name: 'Apdex'}] + ] + + for (var i = 0; i < expected.length; i++) { + var metric = expected[i] + expectedAll.push([{name: metric}]) + expectedAll.push([{name: metric, scope: 'WebTransaction/Hapi/GET/' + path}]) + } + + assertMetrics(metrics, expectedAll, true, false) +} + +function checkSegments(t, segments, expected, opts) { + t.doesNotThrow(function() { + assertSegments(segments, expected, opts) + }, 'should have expected segments') +} diff --git a/test/versioned/hapi/hapi-post-18/vhost.tap.js b/test/versioned/hapi/hapi-post-18/vhost.tap.js new file mode 100644 index 0000000000..0fa99d738c --- /dev/null +++ b/test/versioned/hapi/hapi-post-18/vhost.tap.js @@ -0,0 +1,79 @@ +'use strict' + +var DESTINATIONS = require('../../../../lib/config/attribute-filter').DESTINATIONS +var tap = require('tap') +var request = require('request') +var helper = require('../../../lib/agent_helper') +var utils = require('./hapi-17-utils') +var HTTP_ATTS = require('../../../lib/fixtures').httpAttributes + +tap.test('Hapi vhost support', function(t) { + t.autoend() + + t.test('should not explode when using vhosts', function(t) { + var agent = helper.instrumentMockedAgent({ + attributes: { + enabled: true, + include: ['request.parameters.*'] + } + }) + + var server = utils.getServer() + var port + + t.tearDown(function() { + return server.stop() + }) + + agent.on('transactionFinished', function(tx) { + t.ok(tx.trace, 'transaction has a trace.') + var attributes = tx.trace.attributes.get(DESTINATIONS.TRANS_TRACE) + HTTP_ATTS.forEach(function(key) { + t.ok(attributes[key], 'Trace contains expected HTTP attribute: ' + key) + }) + t.equal( + attributes['request.parameters.id'], '1337', + 'Trace attributes include `id` route param' + ) + t.equal( + attributes['request.parameters.name'], 'hapi', + 'Trace attributes include `name` query param' + ) + + helper.unloadAgent(agent) + }) + + server.route({ + method: 'GET', + path: '/test/{id}/', + vhost: 'localhost', + handler: function() { + t.ok(agent.getTransaction(), 'transaction is available') + return { status : 'ok' } + } + }) + + server.route({ + method: 'GET', + path: '/test/{id}/2', + vhost: 'localhost', + handler: function() { + t.ok(agent.getTransaction(), 'transaction is available') + return { status: 'ok' } + } + }) + + server.start().then(function() { + port = server.info.port + var params = { + uri: 'http://localhost:' + port + '/test/1337/2?name=hapi', + json: true + } + request.get(params, function(error, res, body) { + t.equal(res.statusCode, 200, 'nothing exploded') + t.deepEqual(body, {status: 'ok'}, 'got expected response') + t.end() + }) + }) + }) +}) From 83039000d764a4e0309b2c087697abf0928efba4 Mon Sep 17 00:00:00 2001 From: Aori Nevo Date: Mon, 8 Jul 2019 10:19:19 -0400 Subject: [PATCH 2/4] fix: linting errors. --- lib/instrumentation/@hapi/hapi.js | 2 +- test/versioned/hapi/hapi-post-18/render.tap.js | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/instrumentation/@hapi/hapi.js b/lib/instrumentation/@hapi/hapi.js index f461a343e6..70108f9a02 100644 --- a/lib/instrumentation/@hapi/hapi.js +++ b/lib/instrumentation/@hapi/hapi.js @@ -1,3 +1,3 @@ 'use strict' -module.exports = require('../hapi'); +module.exports = require('../hapi') diff --git a/test/versioned/hapi/hapi-post-18/render.tap.js b/test/versioned/hapi/hapi-post-18/render.tap.js index 76275a0f3b..135a359586 100644 --- a/test/versioned/hapi/hapi-post-18/render.tap.js +++ b/test/versioned/hapi/hapi-post-18/render.tap.js @@ -92,7 +92,6 @@ tap.test('agent instrumentation of Hapi', function(t) { }) agent.once('transactionFinished', function(tx) { - console.log('---- metrics: ', agent.metrics); var stats = agent.metrics.getMetric('View/index/Rendering') t.ok(stats, 'View metric should exist') t.equal(stats.callCount, 1, 'should note the view rendering') From 597cfb6700973c3ab04b8295462c8f01d6270d27 Mon Sep 17 00:00:00 2001 From: Aori Nevo Date: Tue, 9 Jul 2019 18:56:46 -0400 Subject: [PATCH 3/4] chore: resolve comments --- .../{hapi-post-17-pre-18 => hapi-17}/capture-params.tap.js | 0 .../hapi/{hapi-post-17-pre-18 => hapi-17}/errors.tap.js | 0 .../hapi/{hapi-post-17-pre-18 => hapi-17}/ext.tap.js | 0 .../hapi/{hapi-post-17-pre-18 => hapi-17}/hapi-17-utils.js | 0 .../hapi/{hapi-post-17-pre-18 => hapi-17}/hapi.tap.js | 0 .../hapi/{hapi-post-17-pre-18 => hapi-17}/ignoring.tap.js | 0 .../hapi/{hapi-post-17-pre-18 => hapi-17}/newrelic.js | 0 .../hapi/{hapi-post-17-pre-18 => hapi-17}/package.json | 0 .../hapi/{hapi-post-17-pre-18 => hapi-17}/plugins.tap.js | 0 .../hapi/{hapi-post-17-pre-18 => hapi-17}/render.tap.js | 0 .../hapi/{hapi-post-17-pre-18 => hapi-17}/router.tap.js | 0 .../hapi/{hapi-post-17-pre-18 => hapi-17}/segments.tap.js | 0 .../hapi/{hapi-post-17-pre-18 => hapi-17}/vhost.tap.js | 0 test/versioned/hapi/hapi-post-18/package.json | 4 ++-- 14 files changed, 2 insertions(+), 2 deletions(-) rename test/versioned/hapi/{hapi-post-17-pre-18 => hapi-17}/capture-params.tap.js (100%) rename test/versioned/hapi/{hapi-post-17-pre-18 => hapi-17}/errors.tap.js (100%) rename test/versioned/hapi/{hapi-post-17-pre-18 => hapi-17}/ext.tap.js (100%) rename test/versioned/hapi/{hapi-post-17-pre-18 => hapi-17}/hapi-17-utils.js (100%) rename test/versioned/hapi/{hapi-post-17-pre-18 => hapi-17}/hapi.tap.js (100%) rename test/versioned/hapi/{hapi-post-17-pre-18 => hapi-17}/ignoring.tap.js (100%) rename test/versioned/hapi/{hapi-post-17-pre-18 => hapi-17}/newrelic.js (100%) rename test/versioned/hapi/{hapi-post-17-pre-18 => hapi-17}/package.json (100%) rename test/versioned/hapi/{hapi-post-17-pre-18 => hapi-17}/plugins.tap.js (100%) rename test/versioned/hapi/{hapi-post-17-pre-18 => hapi-17}/render.tap.js (100%) rename test/versioned/hapi/{hapi-post-17-pre-18 => hapi-17}/router.tap.js (100%) rename test/versioned/hapi/{hapi-post-17-pre-18 => hapi-17}/segments.tap.js (100%) rename test/versioned/hapi/{hapi-post-17-pre-18 => hapi-17}/vhost.tap.js (100%) diff --git a/test/versioned/hapi/hapi-post-17-pre-18/capture-params.tap.js b/test/versioned/hapi/hapi-17/capture-params.tap.js similarity index 100% rename from test/versioned/hapi/hapi-post-17-pre-18/capture-params.tap.js rename to test/versioned/hapi/hapi-17/capture-params.tap.js diff --git a/test/versioned/hapi/hapi-post-17-pre-18/errors.tap.js b/test/versioned/hapi/hapi-17/errors.tap.js similarity index 100% rename from test/versioned/hapi/hapi-post-17-pre-18/errors.tap.js rename to test/versioned/hapi/hapi-17/errors.tap.js diff --git a/test/versioned/hapi/hapi-post-17-pre-18/ext.tap.js b/test/versioned/hapi/hapi-17/ext.tap.js similarity index 100% rename from test/versioned/hapi/hapi-post-17-pre-18/ext.tap.js rename to test/versioned/hapi/hapi-17/ext.tap.js diff --git a/test/versioned/hapi/hapi-post-17-pre-18/hapi-17-utils.js b/test/versioned/hapi/hapi-17/hapi-17-utils.js similarity index 100% rename from test/versioned/hapi/hapi-post-17-pre-18/hapi-17-utils.js rename to test/versioned/hapi/hapi-17/hapi-17-utils.js diff --git a/test/versioned/hapi/hapi-post-17-pre-18/hapi.tap.js b/test/versioned/hapi/hapi-17/hapi.tap.js similarity index 100% rename from test/versioned/hapi/hapi-post-17-pre-18/hapi.tap.js rename to test/versioned/hapi/hapi-17/hapi.tap.js diff --git a/test/versioned/hapi/hapi-post-17-pre-18/ignoring.tap.js b/test/versioned/hapi/hapi-17/ignoring.tap.js similarity index 100% rename from test/versioned/hapi/hapi-post-17-pre-18/ignoring.tap.js rename to test/versioned/hapi/hapi-17/ignoring.tap.js diff --git a/test/versioned/hapi/hapi-post-17-pre-18/newrelic.js b/test/versioned/hapi/hapi-17/newrelic.js similarity index 100% rename from test/versioned/hapi/hapi-post-17-pre-18/newrelic.js rename to test/versioned/hapi/hapi-17/newrelic.js diff --git a/test/versioned/hapi/hapi-post-17-pre-18/package.json b/test/versioned/hapi/hapi-17/package.json similarity index 100% rename from test/versioned/hapi/hapi-post-17-pre-18/package.json rename to test/versioned/hapi/hapi-17/package.json diff --git a/test/versioned/hapi/hapi-post-17-pre-18/plugins.tap.js b/test/versioned/hapi/hapi-17/plugins.tap.js similarity index 100% rename from test/versioned/hapi/hapi-post-17-pre-18/plugins.tap.js rename to test/versioned/hapi/hapi-17/plugins.tap.js diff --git a/test/versioned/hapi/hapi-post-17-pre-18/render.tap.js b/test/versioned/hapi/hapi-17/render.tap.js similarity index 100% rename from test/versioned/hapi/hapi-post-17-pre-18/render.tap.js rename to test/versioned/hapi/hapi-17/render.tap.js diff --git a/test/versioned/hapi/hapi-post-17-pre-18/router.tap.js b/test/versioned/hapi/hapi-17/router.tap.js similarity index 100% rename from test/versioned/hapi/hapi-post-17-pre-18/router.tap.js rename to test/versioned/hapi/hapi-17/router.tap.js diff --git a/test/versioned/hapi/hapi-post-17-pre-18/segments.tap.js b/test/versioned/hapi/hapi-17/segments.tap.js similarity index 100% rename from test/versioned/hapi/hapi-post-17-pre-18/segments.tap.js rename to test/versioned/hapi/hapi-17/segments.tap.js diff --git a/test/versioned/hapi/hapi-post-17-pre-18/vhost.tap.js b/test/versioned/hapi/hapi-17/vhost.tap.js similarity index 100% rename from test/versioned/hapi/hapi-post-17-pre-18/vhost.tap.js rename to test/versioned/hapi/hapi-17/vhost.tap.js diff --git a/test/versioned/hapi/hapi-post-18/package.json b/test/versioned/hapi/hapi-post-18/package.json index c0b0e1a480..1d7d6449dc 100644 --- a/test/versioned/hapi/hapi-post-18/package.json +++ b/test/versioned/hapi/hapi-post-18/package.json @@ -1,5 +1,5 @@ { - "name": "hapi-v17-tests", + "name": "hapi-v18-tests", "version": "0.0.0", "private": true, "tests": [ @@ -9,7 +9,7 @@ }, "dependencies": { "ejs": "2.5.5", - "@hapi/hapi": ">=18.3.1", + "@hapi/hapi": "^18.0.0", "vision": "^5.0.0" }, "files": [ From 5ff6069f35ed1ba30e95064b1f585c07aa6552db Mon Sep 17 00:00:00 2001 From: Aori Nevo Date: Tue, 9 Jul 2019 19:22:06 -0400 Subject: [PATCH 4/4] chore: favor ^18.3.1 over ^18.0.0 --- test/versioned/hapi/hapi-post-18/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/versioned/hapi/hapi-post-18/package.json b/test/versioned/hapi/hapi-post-18/package.json index 1d7d6449dc..6912aa3bb3 100644 --- a/test/versioned/hapi/hapi-post-18/package.json +++ b/test/versioned/hapi/hapi-post-18/package.json @@ -9,7 +9,7 @@ }, "dependencies": { "ejs": "2.5.5", - "@hapi/hapi": "^18.0.0", + "@hapi/hapi": "^18.3.1", "vision": "^5.0.0" }, "files": [