diff --git a/test/versioned/express/app-use.tap.js b/test/versioned/express/app-use.test.js similarity index 74% rename from test/versioned/express/app-use.tap.js rename to test/versioned/express/app-use.test.js index 5a6e597691..1c3d127167 100644 --- a/test/versioned/express/app-use.tap.js +++ b/test/versioned/express/app-use.test.js @@ -4,35 +4,38 @@ */ 'use strict' - -const test = require('tap').test +const test = require('node:test') const helper = require('../../lib/agent_helper') const http = require('http') const { isExpress5 } = require('./utils') +const tsplan = require('@matteo.collina/tspl') +const promiseResolvers = require('../../lib/promise-resolvers') // This test is no longer applicable in express 5 as mounting a child router does not emit the same // mount event -test('app should be at top of stack when mounted', { skip: isExpress5 }, function (t) { +test('app should be at top of stack when mounted', { skip: isExpress5() }, function (t, end) { const agent = helper.instrumentMockedAgent() const express = require('express') - t.teardown(() => { + t.after(() => { helper.unloadAgent(agent) }) - t.plan(1) + const plan = tsplan(t, { plan: 1 }) const main = express() const child = express() child.on('mount', function () { - t.equal(main._router.stack.length, 3, '3 middleware functions: query parser, Express, child') + plan.equal(main._router.stack.length, 3, '3 middleware functions: query parser, Express, child') + end() }) main.use(child) }) -test('app should be at top of stack when mounted', function (t) { +test('app should be at top of stack when mounted', async function (t) { + const { promise, resolve } = promiseResolvers() const agent = helper.instrumentMockedAgent() const express = require('express') @@ -42,10 +45,19 @@ test('app should be at top of stack when mounted', function (t) { const router = new express.Router() const router2 = new express.Router() const server = http.createServer(main) + // track how many requests and wait for all to be done + let tests = 0 + + const int = setInterval(() => { + if (tests === 5) { + resolve() + } + }, 10) - t.teardown(function () { + t.after(function () { helper.unloadAgent(agent) server.close() + clearInterval(int) }) main.use('/:app', app) @@ -58,7 +70,7 @@ test('app should be at top of stack when mounted', function (t) { router2.get('/', respond) main.get('/:foo/:bar', respond) - t.plan(10) + const plan = tsplan(t, { plan: 10 }) // store finished transactions const finishedTransactions = {} @@ -70,59 +82,67 @@ test('app should be at top of stack when mounted', function (t) { server.listen(port, function () { const host = 'http://localhost:' + port helper.makeGetRequest(host + '/myApp/myChild/app', function (err, res, body) { - t.notOk(err) - t.equal( + plan.ok(!err) + plan.equal( finishedTransactions[body].nameState.getName(), 'Expressjs/GET//:app/:child/app', 'should set partialName correctly for nested apps' ) + ++tests }) helper.makeGetRequest(host + '/myApp/nestedApp ', function (err, res, body) { - t.notOk(err) - t.equal( + plan.ok(!err) + plan.equal( finishedTransactions[body].nameState.getName(), 'Expressjs/GET//:app/nestedApp', 'should set partialName correctly for deeply nested apps' ) + ++tests }) helper.makeGetRequest(host + '/myApp/myChild/router', function (err, res, body) { - t.notOk(err) - t.equal( + plan.ok(!err) + plan.equal( finishedTransactions[body].nameState.getName(), 'Expressjs/GET//:router/:child/router', 'should set partialName correctly for nested routers' ) + ++tests }) helper.makeGetRequest(host + '/myApp/nestedRouter', function (err, res, body) { - t.notOk(err) - t.equal( + plan.ok(!err) + plan.equal( finishedTransactions[body].nameState.getName(), 'Expressjs/GET//:router/nestedRouter', 'should set partialName correctly for deeply nested routers' ) + ++tests }) helper.makeGetRequest(host + '/foo/bar', function (err, res, body) { - t.notOk(err) - t.equal( + plan.ok(!err) + plan.equal( finishedTransactions[body].nameState.getName(), 'Expressjs/GET//:foo/:bar', 'should reset partialName after a router without a matching route' ) + ++tests }) }) }) function respond(req, res) { - res.send(agent.getTransaction().id) + const tx = agent.getTransaction() + res.send(tx.id) } + + await promise }) -test('should not pass wrong args when transaction is not present', function (t) { - t.plan(5) +test('should not pass wrong args when transaction is not present', function (t, end) { + const plan = tsplan(t, { plan: 5 }) const agent = helper.instrumentMockedAgent() @@ -136,7 +156,7 @@ test('should not pass wrong args when transaction is not present', function (t) main.use('/', router) main.use('/', router2) - t.teardown(function () { + t.after(function () { helper.unloadAgent(agent) server.close() }) @@ -148,18 +168,18 @@ test('should not pass wrong args when transaction is not present', function (t) }) router2.get('/', function (req, res, next) { - t.equal(req, args[0]) - t.equal(res, args[1]) - t.equal(typeof next, 'function') + plan.equal(req, args[0]) + plan.equal(res, args[1]) + plan.equal(typeof next, 'function') res.send('ok') }) helper.randomPort(function (port) { server.listen(port, function () { helper.makeGetRequest('http://localhost:' + port + '/', function (err, res, body) { - t.notOk(err) - t.equal(body, 'ok') - t.end() + plan.ok(!err) + plan.equal(body, 'ok') + end() }) }) }) diff --git a/test/versioned/express/async-error.tap.js b/test/versioned/express/async-error.test.js similarity index 70% rename from test/versioned/express/async-error.tap.js rename to test/versioned/express/async-error.test.js index 56014e6bc5..6b48af509c 100644 --- a/test/versioned/express/async-error.tap.js +++ b/test/versioned/express/async-error.test.js @@ -4,9 +4,9 @@ */ 'use strict' - +const assert = require('node:assert') const path = require('path') -const test = require('tap').test +const test = require('node:test') const fork = require('child_process').fork const { isExpress5 } = require('./utils') @@ -17,26 +17,26 @@ const { isExpress5 } = require('./utils') */ const COMPLETION = 27 -test('Express async throw', { skip: isExpress5() }, function (t) { +test('Express async throw', { skip: isExpress5() }, function (t, end) { const erk = fork(path.join(__dirname, 'erk.js')) let timer erk.on('error', function (error) { - t.fail(error) - t.end() + assert.ok(!error) + end() }) erk.on('exit', function (code) { clearTimeout(timer) - t.notEqual(code, COMPLETION, "request didn't complete") - t.end() + assert.notEqual(code, COMPLETION, "request didn't complete") + end() }) // wait for the child vm to boot erk.on('message', function (message) { if (message === 'ready') { timer = setTimeout(function () { - t.fail('hung waiting for exit') + end(new Error('hung waiting for exit')) erk.kill() }, 1000) timer.unref() diff --git a/test/versioned/express/async-handlers.tap.js b/test/versioned/express/async-handlers.tap.js deleted file mode 100644 index b90df9dc55..0000000000 --- a/test/versioned/express/async-handlers.tap.js +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2024 New Relic Corporation. All rights reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -'use strict' - -const { makeRequest, setup, isExpress5 } = require('./utils') -const { test } = require('tap') - -test('should properly track async handlers', { skip: !isExpress5() }, (t) => { - setup(t) - const { app } = t.context - const mwTimeout = 20 - const handlerTimeout = 25 - - app.use(async function (req, res, next) { - await new Promise((resolve) => { - setTimeout(() => { - resolve() - }, mwTimeout) - }) - next() - }) - app.use('/test', async function handler(req, res) { - await new Promise((resolve) => { - setTimeout(resolve, handlerTimeout) - }) - res.send('ok') - }) - - runTest(t, '/test', (tx) => { - const [children] = tx.trace.root.children - const [mw, handler] = children.children - t.ok( - Math.ceil(mw.getDurationInMillis()) >= mwTimeout, - `should be at least ${mwTimeout} for middleware segment` - ) - t.ok( - Math.ceil(handler.getDurationInMillis()) >= handlerTimeout, - `should be at least ${handlerTimeout} for handler segment` - ) - t.end() - }) -}) - -test('should properly handle errors in async handlers', { skip: !isExpress5() }, (t) => { - setup(t) - const { app } = t.context - - app.use(() => { - return Promise.reject(new Error('whoops i failed')) - }) - app.use('/test', function handler(req, res) { - t.fail('should not call handler on error') - res.send('ok') - }) - // eslint-disable-next-line no-unused-vars - app.use(function (error, req, res, next) { - res.status(400).end() - }) - - runTest(t, '/test', (tx) => { - const errors = tx.agent.errors.traceAggregator.errors - t.equal(errors.length, 1) - const [error] = errors - t.equal(error[2], 'HttpError 400', 'should return 400 from custom error handler') - t.end() - }) -}) - -function runTest(t, endpoint, callback) { - const { agent, app } = t.context - - agent.on('transactionFinished', callback) - - const server = app.listen(function () { - makeRequest(this, endpoint, function (response) { - response.resume() - }) - }) - - t.teardown(() => { - server.close() - }) -} diff --git a/test/versioned/express/async-handlers.test.js b/test/versioned/express/async-handlers.test.js new file mode 100644 index 0000000000..449f2ed261 --- /dev/null +++ b/test/versioned/express/async-handlers.test.js @@ -0,0 +1,86 @@ +/* + * Copyright 2024 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +'use strict' +const assert = require('node:assert') +const test = require('node:test') +const helper = require('../../lib/agent_helper') +const { makeRequest, setup, isExpress5 } = require('./utils') + +test('async handlers', { skip: !isExpress5() }, async (t) => { + t.beforeEach(async (ctx) => { + await setup(ctx) + }) + + t.afterEach((ctx) => { + helper.unloadAgent(ctx.nr.agent) + ctx.nr.server.close() + }) + + await t.test('should properly track async handlers', async (t) => { + const { app } = t.nr + const mwTimeout = 20 + const handlerTimeout = 25 + + app.use(async function (req, res, next) { + await new Promise((resolve) => { + setTimeout(() => { + resolve() + }, mwTimeout) + }) + next() + }) + app.use('/test', async function handler(req, res) { + await new Promise((resolve) => { + setTimeout(resolve, handlerTimeout) + }) + res.send('ok') + }) + + const tx = await runTest(t, '/test') + const [children] = tx.trace.root.children + const [mw, handler] = children.children + assert.ok( + Math.ceil(mw.getDurationInMillis()) >= mwTimeout, + `should be at least ${mwTimeout} for middleware segment` + ) + assert.ok( + Math.ceil(handler.getDurationInMillis()) >= handlerTimeout, + `should be at least ${handlerTimeout} for handler segment` + ) + }) + + await test('should properly handle errors in async handlers', async (t) => { + const { app } = t.nr + + app.use(() => { + return Promise.reject(new Error('whoops i failed')) + }) + app.use('/test', function handler() { + throw new Error('should not call handler on error') + }) + // eslint-disable-next-line no-unused-vars + app.use(function (error, req, res, next) { + res.status(400).end() + }) + + const tx = await runTest(t, '/test') + const errors = tx.agent.errors.traceAggregator.errors + assert.equal(errors.length, 1) + const [error] = errors + assert.equal(error[2], 'HttpError 400', 'should return 400 from custom error handler') + }) +}) + +async function runTest(t, endpoint) { + const { agent, server } = t.nr + return new Promise((resolve) => { + agent.on('transactionFinished', resolve) + + makeRequest(server, endpoint, function (response) { + response.resume() + }) + }) +} diff --git a/test/versioned/express/bare-router.tap.js b/test/versioned/express/bare-router.tap.js deleted file mode 100644 index ed244e0ebf..0000000000 --- a/test/versioned/express/bare-router.tap.js +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2020 New Relic Corporation. All rights reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -'use strict' - -const test = require('tap').test -const helper = require('../../lib/agent_helper') - -test('Express router introspection', function (t) { - t.plan(11) - - const agent = helper.instrumentMockedAgent() - - const express = require('express') - const app = express() - const server = require('http').createServer(app) - - t.teardown(() => { - server.close(() => { - helper.unloadAgent(agent) - }) - }) - - // need to capture parameters - agent.config.attributes.enabled = true - - agent.on('transactionFinished', function (transaction) { - t.equal(transaction.name, 'WebTransaction/Expressjs/GET//test', 'transaction has expected name') - - t.equal(transaction.url, '/test', 'URL is left alone') - t.equal(transaction.statusCode, 200, 'status code is OK') - t.equal(transaction.verb, 'GET', 'HTTP method is GET') - t.ok(transaction.trace, 'transaction has trace') - - const web = transaction.trace.root.children[0] - t.ok(web, 'trace has web segment') - t.equal(web.name, transaction.name, 'segment name and transaction name match') - - t.equal(web.partialName, 'Expressjs/GET//test', 'should have partial name for apdex') - }) - - app.get('/test', function (req, res) { - t.ok(agent.getTransaction(), 'transaction is available') - - res.send({ status: 'ok' }) - res.end() - }) - - helper.randomPort(function (port) { - server.listen(port, function () { - const url = 'http://localhost:' + port + '/test' - helper.makeGetRequest(url, { json: true }, function (error, res, body) { - t.equal(res.statusCode, 200, 'nothing exploded') - t.same(body, { status: 'ok' }, 'got expected response') - }) - }) - }) -}) diff --git a/test/versioned/express/bare-router.test.js b/test/versioned/express/bare-router.test.js new file mode 100644 index 0000000000..616834a37b --- /dev/null +++ b/test/versioned/express/bare-router.test.js @@ -0,0 +1,61 @@ +/* + * Copyright 2020 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +'use strict' + +const test = require('node:test') +const helper = require('../../lib/agent_helper') +const tsplan = require('@matteo.collina/tspl') +const { setup } = require('./utils') + +test.beforeEach(async (ctx) => { + await setup(ctx) +}) + +test.afterEach((ctx) => { + helper.unloadAgent(ctx.nr.agent) + ctx.nr.server.close() +}) + +test('Express router introspection', function (t, end) { + const { agent, app, port } = t.nr + const plan = tsplan(t, { plan: 11 }) + + // need to capture parameters + agent.config.attributes.enabled = true + + agent.on('transactionFinished', function (transaction) { + plan.equal( + transaction.name, + 'WebTransaction/Expressjs/GET//test', + 'transaction has expected name' + ) + + plan.equal(transaction.url, '/test', 'URL is left alone') + plan.equal(transaction.statusCode, 200, 'status code is OK') + plan.equal(transaction.verb, 'GET', 'HTTP method is GET') + plan.ok(transaction.trace, 'transaction has trace') + + const web = transaction.trace.root.children[0] + plan.ok(web, 'trace has web segment') + plan.equal(web.name, transaction.name, 'segment name and transaction name match') + + plan.equal(web.partialName, 'Expressjs/GET//test', 'should have partial name for apdex') + }) + + app.get('/test', function (req, res) { + plan.ok(agent.getTransaction(), 'transaction is available') + + res.send({ status: 'ok' }) + res.end() + }) + + const url = 'http://localhost:' + port + '/test' + helper.makeGetRequest(url, { json: true }, function (error, res, body) { + plan.equal(res.statusCode, 200, 'nothing exploded') + plan.deepEqual(body, { status: 'ok' }, 'got expected response') + end() + }) +}) diff --git a/test/versioned/express/captures-params.tap.js b/test/versioned/express/captures-params.tap.js deleted file mode 100644 index aaa2aa5cd7..0000000000 --- a/test/versioned/express/captures-params.tap.js +++ /dev/null @@ -1,264 +0,0 @@ -/* - * Copyright 2020 New Relic Corporation. All rights reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -'use strict' - -// shut up, Express -process.env.NODE_ENV = 'test' - -const DESTINATIONS = require('../../../lib/config/attribute-filter').DESTINATIONS -const tap = require('tap') -const helper = require('../../lib/agent_helper') -const HTTP_ATTS = require('../../lib/fixtures').httpAttributes - -// CONSTANTS -const TEST_HOST = 'localhost' -const TEST_URL = 'http://' + TEST_HOST + ':' - -tap.test('test attributes.enabled for express', function (t) { - t.autoend() - - let agent = null - t.beforeEach(function () { - agent = helper.instrumentMockedAgent({ - apdex_t: 1, - allow_all_headers: false, - attributes: { - enabled: true, - include: ['request.parameters.*'] - } - }) - }) - - t.afterEach(function () { - helper.unloadAgent(agent) - }) - - t.test('no variables', function (t) { - const app = require('express')() - const server = require('http').createServer(app) - let port = null - - t.teardown(function () { - server.close() - }) - - app.get('/user/', function (req, res) { - t.ok(agent.getTransaction(), 'transaction is available') - - res.send({ yep: true }) - res.end() - }) - - agent.on('transactionFinished', function (transaction) { - t.ok(transaction.trace, 'transaction has a trace.') - const attributes = transaction.trace.attributes.get(DESTINATIONS.TRANS_TRACE) - HTTP_ATTS.forEach(function (key) { - t.ok(attributes[key], 'Trace contains expected HTTP attribute: ' + key) - }) - if (attributes.httpResponseMessage) { - t.equal(attributes.httpResponseMessage, 'OK', 'Trace contains httpResponseMessage') - } - }) - - helper.randomPort(function (_port) { - port = _port - server.listen(port, TEST_HOST, function () { - const url = TEST_URL + port + '/user/' - helper.makeGetRequest(url, function (error, response, body) { - if (error) { - t.fail(error) - } - - t.ok( - /application\/json/.test(response.headers['content-type']), - 'got correct content type' - ) - - t.same(body, { yep: true }, 'Express correctly serves.') - t.end() - }) - }) - }) - }) - - t.test('route variables', function (t) { - const app = require('express')() - const server = require('http').createServer(app) - let port = null - - t.teardown(function () { - server.close() - }) - - app.get('/user/:id', function (req, res) { - t.ok(agent.getTransaction(), 'transaction is available') - - res.send({ yep: true }) - res.end() - }) - - agent.on('transactionFinished', function (transaction) { - t.ok(transaction.trace, 'transaction has a trace.') - const attributes = transaction.trace.attributes.get(DESTINATIONS.TRANS_TRACE) - t.equal( - attributes['request.parameters.route.id'], - '5', - 'Trace attributes include `id` route param' - ) - }) - - helper.randomPort(function (_port) { - port = _port - server.listen(port, TEST_HOST, function () { - const url = TEST_URL + port + '/user/5' - helper.makeGetRequest(url, function (error, response, body) { - if (error) { - t.fail(error) - } - - t.ok( - /application\/json/.test(response.headers['content-type']), - 'got correct content type' - ) - - t.same(body, { yep: true }, 'Express correctly serves.') - t.end() - }) - }) - }) - }) - - t.test('query variables', { timeout: 1000 }, function (t) { - const app = require('express')() - const server = require('http').createServer(app) - let port = null - - t.teardown(function () { - server.close() - }) - - app.get('/user/', function (req, res) { - t.ok(agent.getTransaction(), 'transaction is available') - - res.send({ yep: true }) - res.end() - }) - - agent.on('transactionFinished', function (transaction) { - t.ok(transaction.trace, 'transaction has a trace.') - const attributes = transaction.trace.attributes.get(DESTINATIONS.TRANS_TRACE) - t.equal( - attributes['request.parameters.name'], - 'bob', - 'Trace attributes include `name` query param' - ) - }) - - helper.randomPort(function (_port) { - port = _port - server.listen(port, TEST_HOST, function () { - const url = TEST_URL + port + '/user/?name=bob' - helper.makeGetRequest(url, function (error, response, body) { - if (error) { - t.fail(error) - } - - t.ok( - /application\/json/.test(response.headers['content-type']), - 'got correct content type' - ) - - t.same(body, { yep: true }, 'Express correctly serves.') - t.end() - }) - }) - }) - }) - - t.test('route and query variables', function (t) { - const app = require('express')() - const server = require('http').createServer(app) - let port = null - - t.teardown(function () { - server.close() - }) - - app.get('/user/:id', function (req, res) { - t.ok(agent.getTransaction(), 'transaction is available') - - res.send({ yep: true }) - res.end() - }) - - agent.on('transactionFinished', function (transaction) { - t.ok(transaction.trace, 'transaction has a trace.') - const attributes = transaction.trace.attributes.get(DESTINATIONS.TRANS_TRACE) - t.equal( - attributes['request.parameters.route.id'], - '5', - 'Trace attributes include `id` route param' - ) - t.equal( - attributes['request.parameters.name'], - 'bob', - 'Trace attributes include `name` query param' - ) - }) - - helper.randomPort(function (_port) { - port = _port - server.listen(port, TEST_HOST, function () { - const url = TEST_URL + port + '/user/5?name=bob' - helper.makeGetRequest(url, function (error, response, body) { - if (error) { - t.fail(error) - } - - t.ok( - /application\/json/.test(response.headers['content-type']), - 'got correct content type' - ) - - t.same(body, { yep: true }, 'Express correctly serves.') - t.end() - }) - }) - }) - }) - - t.test('query params should not mask route attributes', function (t) { - const app = require('express')() - const server = require('http').createServer(app) - let port = null - - t.teardown(function () { - server.close() - }) - - app.get('/user/:id', function (req, res) { - res.end() - }) - - agent.on('transactionFinished', function (transaction) { - const attributes = transaction.trace.attributes.get(DESTINATIONS.TRANS_TRACE) - t.equal( - attributes['request.parameters.route.id'], - '5', - 'attributes should include route params' - ) - t.equal(attributes['request.parameters.id'], '6', 'attributes should include query params') - t.end() - }) - - helper.randomPort(function (_port) { - port = _port - server.listen(port, TEST_HOST, function () { - helper.makeGetRequest(TEST_URL + port + '/user/5?id=6') - }) - }) - }) -}) diff --git a/test/versioned/express/captures-params.test.js b/test/versioned/express/captures-params.test.js new file mode 100644 index 0000000000..73db905cc9 --- /dev/null +++ b/test/versioned/express/captures-params.test.js @@ -0,0 +1,199 @@ +/* + * Copyright 2020 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +'use strict' + +// shut up, Express +process.env.NODE_ENV = 'test' +const DESTINATIONS = require('../../../lib/config/attribute-filter').DESTINATIONS +const assert = require('node:assert') +const test = require('node:test') +const helper = require('../../lib/agent_helper') +const HTTP_ATTS = require('../../lib/fixtures').httpAttributes +const { setup } = require('./utils') + +// CONSTANTS +const TEST_HOST = 'localhost' +const TEST_URL = 'http://' + TEST_HOST + ':' + +test('test attributes.enabled for express', async function (t) { + t.beforeEach(async function (ctx) { + await setup(ctx, { + apdex_t: 1, + allow_all_headers: false, + attributes: { + enabled: true, + include: ['request.parameters.*'] + } + }) + }) + + t.afterEach(function (ctx) { + helper.unloadAgent(ctx.nr.agent) + ctx.nr.server.close() + }) + + await t.test('no variables', function (t, end) { + const { agent, app, port } = t.nr + app.get('/user/', function (req, res) { + assert.ok(agent.getTransaction(), 'transaction is available') + + res.send({ yep: true }) + res.end() + }) + + agent.on('transactionFinished', function (transaction) { + assert.ok(transaction.trace, 'transaction has a trace.') + const attributes = transaction.trace.attributes.get(DESTINATIONS.TRANS_TRACE) + HTTP_ATTS.forEach(function (key) { + assert.ok(attributes[key], 'Trace contains expected HTTP attribute: ' + key) + }) + if (attributes.httpResponseMessage) { + assert.equal(attributes.httpResponseMessage, 'OK', 'Trace contains httpResponseMessage') + } + }) + + const url = TEST_URL + port + '/user/' + helper.makeGetRequest(url, function (error, response, body) { + assert.ok(!error) + assert.ok( + /application\/json/.test(response.headers['content-type']), + 'got correct content type' + ) + + assert.deepEqual(body, { yep: true }, 'Express correctly serves.') + end() + }) + }) + + await t.test('route variables', function (t, end) { + const { agent, app, port } = t.nr + + app.get('/user/:id', function (req, res) { + assert.ok(agent.getTransaction(), 'transaction is available') + + res.send({ yep: true }) + res.end() + }) + + agent.on('transactionFinished', function (transaction) { + assert.ok(transaction.trace, 'transaction has a trace.') + const attributes = transaction.trace.attributes.get(DESTINATIONS.TRANS_TRACE) + assert.equal( + attributes['request.parameters.route.id'], + '5', + 'Trace attributes include `id` route param' + ) + }) + + const url = TEST_URL + port + '/user/5' + helper.makeGetRequest(url, function (error, response, body) { + assert.ok(!error) + assert.ok( + /application\/json/.test(response.headers['content-type']), + 'got correct content type' + ) + + assert.deepEqual(body, { yep: true }, 'Express correctly serves.') + end() + }) + }) + + await t.test('query variables', { timeout: 1000 }, function (t, end) { + const { agent, app, port } = t.nr + + app.get('/user/', function (req, res) { + assert.ok(agent.getTransaction(), 'transaction is available') + + res.send({ yep: true }) + res.end() + }) + + agent.on('transactionFinished', function (transaction) { + assert.ok(transaction.trace, 'transaction has a trace.') + const attributes = transaction.trace.attributes.get(DESTINATIONS.TRANS_TRACE) + assert.equal( + attributes['request.parameters.name'], + 'bob', + 'Trace attributes include `name` query param' + ) + }) + + const url = TEST_URL + port + '/user/?name=bob' + helper.makeGetRequest(url, function (error, response, body) { + assert.ok(!error) + assert.ok( + /application\/json/.test(response.headers['content-type']), + 'got correct content type' + ) + + assert.deepEqual(body, { yep: true }, 'Express correctly serves.') + end() + }) + }) + + await t.test('route and query variables', function (t, end) { + const { agent, app, port } = t.nr + + app.get('/user/:id', function (req, res) { + assert.ok(agent.getTransaction(), 'transaction is available') + + res.send({ yep: true }) + res.end() + }) + + agent.on('transactionFinished', function (transaction) { + assert.ok(transaction.trace, 'transaction has a trace.') + const attributes = transaction.trace.attributes.get(DESTINATIONS.TRANS_TRACE) + assert.equal( + attributes['request.parameters.route.id'], + '5', + 'Trace attributes include `id` route param' + ) + assert.equal( + attributes['request.parameters.name'], + 'bob', + 'Trace attributes include `name` query param' + ) + }) + + const url = TEST_URL + port + '/user/5?name=bob' + helper.makeGetRequest(url, function (error, response, body) { + assert.ok(!error) + assert.ok( + /application\/json/.test(response.headers['content-type']), + 'got correct content type' + ) + + assert.deepEqual(body, { yep: true }, 'Express correctly serves.') + end() + }) + }) + + await t.test('query params should not mask route attributes', function (t, end) { + const { agent, app, port } = t.nr + + app.get('/user/:id', function (req, res) { + res.end() + }) + + agent.on('transactionFinished', function (transaction) { + const attributes = transaction.trace.attributes.get(DESTINATIONS.TRANS_TRACE) + assert.equal( + attributes['request.parameters.route.id'], + '5', + 'attributes should include route params' + ) + assert.equal( + attributes['request.parameters.id'], + '6', + 'attributes should include query params' + ) + end() + }) + + helper.makeGetRequest(TEST_URL + port + '/user/5?id=6') + }) +}) diff --git a/test/versioned/express/client-disconnect.tap.js b/test/versioned/express/client-disconnect.test.js similarity index 75% rename from test/versioned/express/client-disconnect.tap.js rename to test/versioned/express/client-disconnect.test.js index 28ec65c153..8505abe891 100644 --- a/test/versioned/express/client-disconnect.tap.js +++ b/test/versioned/express/client-disconnect.test.js @@ -4,13 +4,13 @@ */ 'use strict' - -const tap = require('tap') +const assert = require('node:assert') +const test = require('node:test') const helper = require('../../lib/agent_helper') -require('../../lib/metrics_helper') const http = require('http') +const { assertSegments } = require('../../lib/custom-assertions') -function generateApp(t) { +function generateApp() { const express = require('express') const bodyParser = require('body-parser') @@ -20,33 +20,30 @@ function generateApp(t) { app.post('/test', function controller(req, res) { const timeout = setTimeout(() => { const err = new Error('should not hit this as request was aborted') - t.error(err) - + assert.ok(!err) res.status(200).send('OK') }, req.body.timeout) res.on('close', () => { - t.comment('cancelling setTimeout') clearTimeout(timeout) }) }) - return app + return app.listen(0) } -tap.test('Client Premature Disconnection', (t) => { - t.setTimeout(3000) +test('Client Premature Disconnection', { timeout: 3000 }, (t, end) => { const agent = helper.instrumentMockedAgent() - const server = generateApp(t).listen(0) + const server = generateApp() const { port } = server.address() - t.teardown(() => { + t.after(() => { server.close() helper.unloadAgent(agent) }) agent.on('transactionFinished', (transaction) => { - t.assertSegments( + assertSegments( transaction.trace.root, [ 'WebTransaction/Expressjs/POST//test', @@ -59,8 +56,8 @@ tap.test('Client Premature Disconnection', (t) => { { exact: false } ) - t.equal(agent.getTransaction(), null, 'should have ended the transaction') - t.end() + assert.equal(agent.getTransaction(), null, 'should have ended the transaction') + end() }) const postData = JSON.stringify({ timeout: 1500 }) @@ -77,12 +74,13 @@ tap.test('Client Premature Disconnection', (t) => { }, function () {} ) - request.on('error', () => t.comment('swallowing request error')) + request.on('error', (err) => { + assert.equal(err.code, 'ECONNRESET') + }) request.write(postData) request.end() setTimeout(() => { - t.comment('aborting request') request.destroy() }, 100) }) diff --git a/test/versioned/express/errors.tap.js b/test/versioned/express/errors.tap.js deleted file mode 100644 index 4d771473b0..0000000000 --- a/test/versioned/express/errors.tap.js +++ /dev/null @@ -1,268 +0,0 @@ -/* - * Copyright 2020 New Relic Corporation. All rights reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -'use strict' - -const http = require('http') -const tap = require('tap') -const { setup, makeRequest } = require('./utils') - -tap.test('reports error when thrown from a route', function (t) { - setup(t) - const { app } = t.context - - app.get('/test', function () { - throw new Error('some error') - }) - - runTest(t, function (errors, statusCode) { - t.equal(errors.length, 1) - t.equal(statusCode, 500) - t.end() - }) -}) - -tap.test('reports error when thrown from a middleware', function (t) { - setup(t) - const { app } = t.context - - app.use(function () { - throw new Error('some error') - }) - - runTest(t, function (errors, statusCode) { - t.equal(errors.length, 1) - t.equal(statusCode, 500) - t.end() - }) -}) - -tap.test('reports error when called in next from a middleware', function (t) { - setup(t) - const { app } = t.context - - app.use(function (req, res, next) { - next(new Error('some error')) - }) - - runTest(t, function (errors, statusCode) { - t.equal(errors.length, 1) - t.equal(statusCode, 500) - t.end() - }) -}) - -tap.test('should not report error when error handler responds', function (t) { - setup(t) - const { app } = t.context - - app.get('/test', function () { - throw new Error('some error') - }) - - // eslint-disable-next-line no-unused-vars - app.use(function (error, req, res, next) { - res.end() - }) - - runTest(t, function (errors, statusCode) { - t.equal(errors.length, 0) - t.equal(statusCode, 200) - t.end() - }) -}) - -tap.test( - 'should report error when error handler responds, but sets error status code', - function (t) { - setup(t) - const { app } = t.context - - app.get('/test', function () { - throw new Error('some error') - }) - - // eslint-disable-next-line no-unused-vars - app.use(function (error, req, res, next) { - res.status(400).end() - }) - - runTest(t, function (errors, statusCode) { - t.equal(errors.length, 1) - t.equal(errors[0][2], 'some error') - t.equal(statusCode, 400) - t.end() - }) - } -) - -tap.test('should report errors passed out of errorware', function (t) { - setup(t) - const { app } = t.context - - app.get('/test', function () { - throw new Error('some error') - }) - - app.use(function (error, req, res, next) { - next(error) - }) - - runTest(t, function (errors, statuscode) { - t.equal(errors.length, 1) - t.equal(statuscode, 500) - t.end() - }) -}) - -tap.test('should report errors from errorware followed by routes', function (t) { - setup(t) - const { app } = t.context - - app.use(function () { - throw new Error('some error') - }) - - app.use(function (error, req, res, next) { - next(error) - }) - - app.get('/test', function (req, res) { - res.end() - }) - - runTest(t, function (errors, statuscode) { - t.equal(errors.length, 1) - t.equal(statuscode, 500) - t.end() - }) -}) - -tap.test('should not report errors swallowed by errorware', function (t) { - setup(t) - const { app } = t.context - - app.get('/test', function () { - throw new Error('some error') - }) - - app.use(function (err, req, res, next) { - next() - }) - - app.get('/test', function (req, res) { - res.end() - }) - - runTest(t, function (errors, statuscode) { - t.equal(errors.length, 0) - t.equal(statuscode, 200) - t.end() - }) -}) - -tap.test('should not report errors handled by errorware outside router', function (t) { - setup(t) - const { app, express } = t.context - - const router1 = express.Router() // eslint-disable-line new-cap - router1.get('/test', function () { - throw new Error('some error') - }) - - app.use(router1) - - // eslint-disable-next-line no-unused-vars - app.use(function (error, req, res, next) { - res.end() - }) - - runTest(t, function (errors, statuscode) { - t.equal(errors.length, 0) - t.equal(statuscode, 200) - t.end() - }) -}) - -tap.test('does not error when request is aborted', function (t) { - t.plan(3) - setup(t) - const { app, agent } = t.context - - let request = null - - app.get('/test', function (req, res, next) { - t.comment('middleware') - t.ok(agent.getTransaction(), 'transaction exists') - - // generate error after client has aborted - request.abort() - setTimeout(function () { - t.comment('timed out') - t.ok(agent.getTransaction() == null, 'transaction has already ended') - next(new Error('some error')) - }, 100) - }) - - // eslint-disable-next-line no-unused-vars - app.use(function (error, req, res, next) { - t.comment('errorware') - t.ok(agent.getTransaction() == null, 'no active transaction when responding') - res.end() - }) - - const server = app.listen(function () { - t.comment('making request') - const port = this.address().port - request = http.request( - { - hostname: 'localhost', - port: port, - path: '/test' - }, - function () {} - ) - request.end() - - // add error handler, otherwise aborting will cause an exception - request.on('error', function (err) { - t.comment('request errored: ' + err) - }) - request.on('abort', function () { - t.comment('request aborted') - }) - }) - - t.teardown(function () { - server.close() - }) -}) - -function runTest(t, callback) { - let statusCode - let errors - const { agent, app } = t.context - - agent.on('transactionFinished', function () { - errors = agent.errors.traceAggregator.errors - if (statusCode) { - callback(errors, statusCode) - } - }) - - const endpoint = '/test' - const server = app.listen(function () { - makeRequest(this, endpoint, function (response) { - statusCode = response.statusCode - if (errors) { - callback(errors, statusCode) - } - response.resume() - }) - }) - t.teardown(function () { - server.close() - }) -} diff --git a/test/versioned/express/errors.test.js b/test/versioned/express/errors.test.js new file mode 100644 index 0000000000..86e9b73f0c --- /dev/null +++ b/test/versioned/express/errors.test.js @@ -0,0 +1,252 @@ +/* + * Copyright 2020 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +'use strict' +const assert = require('node:assert') +const http = require('http') +const test = require('node:test') +const helper = require('../../lib/agent_helper') +const { setup, makeRequest } = require('./utils') +const tsplan = require('@matteo.collina/tspl') + +test('Error handling tests', async (t) => { + t.beforeEach(async (ctx) => { + await setup(ctx) + }) + + t.afterEach((ctx) => { + helper.unloadAgent(ctx.nr.agent) + ctx.nr.server.close() + }) + + await t.test('reports error when thrown from a route', function (t, end) { + const { app } = t.nr + + app.get('/test', function () { + throw new Error('some error') + }) + + runTest(t, function (errors, statusCode) { + assert.equal(errors.length, 1) + assert.equal(statusCode, 500) + end() + }) + }) + + await t.test('reports error when thrown from a middleware', function (t, end) { + const { app } = t.nr + + app.use(function () { + throw new Error('some error') + }) + + runTest(t, function (errors, statusCode) { + assert.equal(errors.length, 1) + assert.equal(statusCode, 500) + end() + }) + }) + + await t.test('reports error when called in next from a middleware', function (t, end) { + const { app } = t.nr + + app.use(function (req, res, next) { + next(new Error('some error')) + }) + + runTest(t, function (errors, statusCode) { + assert.equal(errors.length, 1) + assert.equal(statusCode, 500) + end() + }) + }) + + await t.test('should not report error when error handler responds', function (t, end) { + const { app } = t.nr + + app.get('/test', function () { + throw new Error('some error') + }) + + // eslint-disable-next-line no-unused-vars + app.use(function (error, req, res, next) { + res.end() + }) + + runTest(t, function (errors, statusCode) { + assert.equal(errors.length, 0) + assert.equal(statusCode, 200) + end() + }) + }) + + await t.test( + 'should report error when error handler responds, but sets error status code', + function (t, end) { + const { app } = t.nr + + app.get('/test', function () { + throw new Error('some error') + }) + + // eslint-disable-next-line no-unused-vars + app.use(function (error, req, res, next) { + res.status(400).end() + }) + + runTest(t, function (errors, statusCode) { + assert.equal(errors.length, 1) + assert.equal(errors[0][2], 'some error') + assert.equal(statusCode, 400) + end() + }) + } + ) + + await t.test('should report errors passed out of errorware', function (t, end) { + const { app } = t.nr + + app.get('/test', function () { + throw new Error('some error') + }) + + app.use(function (error, req, res, next) { + next(error) + }) + + runTest(t, function (errors, statuscode) { + assert.equal(errors.length, 1) + assert.equal(statuscode, 500) + end() + }) + }) + + await t.test('should report errors from errorware followed by routes', function (t, end) { + const { app } = t.nr + + app.use(function () { + throw new Error('some error') + }) + + app.use(function (error, req, res, next) { + next(error) + }) + + app.get('/test', function (req, res) { + res.end() + }) + + runTest(t, function (errors, statuscode) { + assert.equal(errors.length, 1) + assert.equal(statuscode, 500) + end() + }) + }) + + await t.test('should not report errors swallowed by errorware', function (t, end) { + const { app } = t.nr + + app.get('/test', function () { + throw new Error('some error') + }) + + app.use(function (err, req, res, next) { + next() + }) + + app.get('/test', function (req, res) { + res.end() + }) + + runTest(t, function (errors, statuscode) { + assert.equal(errors.length, 0) + assert.equal(statuscode, 200) + end() + }) + }) + + await t.test('should not report errors handled by errorware outside router', function (t, end) { + const { app, express } = t.nr + + const router1 = express.Router() // eslint-disable-line new-cap + router1.get('/test', function () { + throw new Error('some error') + }) + + app.use(router1) + + // eslint-disable-next-line no-unused-vars + app.use(function (error, req, res, next) { + res.end() + }) + + runTest(t, function (errors, statuscode) { + assert.equal(errors.length, 0) + assert.equal(statuscode, 200) + end() + }) + }) + + await t.test('does not error when request is aborted', function (t, end) { + const plan = tsplan(t, { plan: 4 }) + const { app, agent, port } = t.nr + let request = null + + app.get('/test', function (req, res, next) { + plan.ok(agent.getTransaction(), 'transaction exists') + + // generate error after client has aborted + request.abort() + setTimeout(function () { + plan.equal(agent.getTransaction(), null, 'transaction has already ended') + next(new Error('some error')) + }, 100) + }) + + // eslint-disable-next-line no-unused-vars + app.use(function (error, req, res, next) { + plan.equal(agent.getTransaction(), null, 'no active transaction when responding') + res.end() + end() + }) + + request = http.request( + { + hostname: 'localhost', + port, + path: '/test' + }, + function () {} + ) + request.end() + + // add error handler, otherwise aborting will cause an exception + request.on('error', function (err) { + plan.equal(err.code, 'ECONNRESET') + }) + }) +}) + +function runTest(t, callback) { + let statusCode + let errors + const { agent, server } = t.nr + + agent.on('transactionFinished', function () { + errors = agent.errors.traceAggregator.errors + if (statusCode) { + callback(errors, statusCode) + } + }) + + const endpoint = '/test' + makeRequest(server, endpoint, function (response) { + statusCode = response.statusCode + if (errors) { + callback(errors, statusCode) + } + response.resume() + }) +} diff --git a/test/versioned/express/express-enrouten.tap.js b/test/versioned/express/express-enrouten.tap.js deleted file mode 100644 index 7b2643263d..0000000000 --- a/test/versioned/express/express-enrouten.tap.js +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2020 New Relic Corporation. All rights reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -/** - * This test checks for regressions on the route stack manipulation for Express apps. - */ -'use strict' - -const test = require('tap').test -const helper = require('../../lib/agent_helper') -const { isExpress5 } = require('./utils') - -test('Express + express-enrouten compatibility test', { skip: isExpress5() }, function (t) { - t.plan(2) - - const agent = helper.instrumentMockedAgent() - const express = require('express') - const enrouten = require('express-enrouten') - const app = express() - const server = require('http').createServer(app) - - app.use(enrouten({ directory: './fixtures' })) - - t.teardown(() => { - server.close(() => { - helper.unloadAgent(agent) - }) - }) - - // New Relic + express-enrouten used to have a bug, where any routes after the - // first one would be lost. - server.listen(0, function () { - const port = server.address().port - helper.makeGetRequest('http://localhost:' + port + '/', function (error, res) { - t.equal(res.statusCode, 200, 'First Route loaded') - }) - - helper.makeGetRequest('http://localhost:' + port + '/foo', function (error, res) { - t.equal(res.statusCode, 200, 'Second Route loaded') - }) - }) -}) diff --git a/test/versioned/express/express-enrouten.test.js b/test/versioned/express/express-enrouten.test.js new file mode 100644 index 0000000000..ee0f52f124 --- /dev/null +++ b/test/versioned/express/express-enrouten.test.js @@ -0,0 +1,43 @@ +/* + * Copyright 2020 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * This test checks for regressions on the route stack manipulation for Express apps. + */ +'use strict' + +const test = require('node:test') +const helper = require('../../lib/agent_helper') +const { isExpress5 } = require('./utils') +const tsplan = require('@matteo.collina/tspl') +const { setup } = require('./utils') + +test.beforeEach(async (ctx) => { + await setup(ctx) +}) + +test.afterEach((ctx) => { + helper.unloadAgent(ctx.nr.agent) + ctx.nr.server.close() +}) + +test('Express + express-enrouten compatibility test', { skip: isExpress5() }, function (t, end) { + const { app, port } = t.nr + const plan = tsplan(t, { plan: 2 }) + + const enrouten = require('express-enrouten') + app.use(enrouten({ directory: './fixtures' })) + + // New Relic + express-enrouten used to have a bug, where any routes after the + // first one would be lost. + helper.makeGetRequest('http://localhost:' + port + '/', function (error, res) { + plan.equal(res.statusCode, 200, 'First Route loaded') + }) + + helper.makeGetRequest('http://localhost:' + port + '/foo', function (error, res) { + plan.equal(res.statusCode, 200, 'Second Route loaded') + end() + }) +}) diff --git a/test/versioned/express/ignoring.tap.js b/test/versioned/express/ignoring.test.js similarity index 52% rename from test/versioned/express/ignoring.tap.js rename to test/versioned/express/ignoring.test.js index bb7fdc620e..64b77c044e 100644 --- a/test/versioned/express/ignoring.tap.js +++ b/test/versioned/express/ignoring.test.js @@ -5,48 +5,49 @@ 'use strict' -const test = require('tap').test +const test = require('node:test') const helper = require('../../lib/agent_helper') const API = require('../../../api') +const tsplan = require('@matteo.collina/tspl') +const { setup } = require('./utils') -test('ignoring an Express route', function (t) { - t.plan(7) +test.beforeEach(async (ctx) => { + await setup(ctx) +}) - const agent = helper.instrumentMockedAgent() +test.afterEach((ctx) => { + helper.unloadAgent(ctx.nr.agent) + ctx.nr.server.close() +}) - const api = new API(agent) - const express = require('express') - const app = express() - const server = require('http').createServer(app) +test('ignoring an Express route', function (t, end) { + const { agent, app, port } = t.nr + const plan = tsplan(t, { plan: 7 }) - t.teardown(() => { - server.close(() => { - helper.unloadAgent(agent) - }) - }) + const api = new API(agent) agent.on('transactionFinished', function (transaction) { - t.equal( + plan.equal( transaction.name, 'WebTransaction/Expressjs/GET//polling/:id', 'transaction has expected name even on error' ) - t.ok(transaction.ignore, 'transaction is ignored') + plan.ok(transaction.ignore, 'transaction is ignored') - t.notOk(agent.traces.trace, 'should have no transaction trace') + plan.ok(!agent.traces.trace, 'should have no transaction trace') const metrics = agent.metrics._metrics.unscoped // loading k2 adds instrumentation metrics for things it loads const expectedMetrics = helper.isSecurityAgentEnabled(agent) ? 11 : 3 - t.equal( + plan.equal( Object.keys(metrics).length, expectedMetrics, 'only supportability metrics added to agent collection' ) const errors = agent.errors.traceAggregator.errors - t.equal(errors.length, 0, 'no errors noticed') + plan.equal(errors.length, 0, 'no errors noticed') }) app.get('/polling/:id', function (req, res) { @@ -55,12 +56,10 @@ test('ignoring an Express route', function (t) { res.end() }) - server.listen(0, function () { - const port = server.address().port - const url = 'http://localhost:' + port + '/polling/31337' - helper.makeGetRequest(url, function (error, res, body) { - t.equal(res.statusCode, 400, 'got expected error') - t.same(body, { status: 'pollpollpoll' }, 'got expected response') - }) + const url = 'http://localhost:' + port + '/polling/31337' + helper.makeGetRequest(url, function (error, res, body) { + plan.equal(res.statusCode, 400, 'got expected error') + plan.deepEqual(body, { status: 'pollpollpoll' }, 'got expected response') + end() }) }) diff --git a/test/versioned/express/issue171.tap.js b/test/versioned/express/issue171.tap.js deleted file mode 100644 index b4683fc818..0000000000 --- a/test/versioned/express/issue171.tap.js +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2020 New Relic Corporation. All rights reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -'use strict' - -const helper = require('../../lib/agent_helper') - -helper.instrumentMockedAgent() - -const test = require('tap').test -const http = require('http') -const app = require('express')() - -test("adding 'handle' middleware", function (t) { - t.plan(2) - - // eslint-disable-next-line no-unused-vars - function handle(err, req, res, next) { - t.ok(err, 'error should exist') - - res.statusCode = 500 - res.end() - } - - app.use('/', function () { - throw new Error() - }) - - app.use(handle) - - app.listen(function () { - const server = this - const port = server.address().port - - http - .request({ port: port }, function (res) { - // drain response to let process exit - res.pipe(process.stderr) - - t.equal(res.statusCode, 500) - server.close() - }) - .end() - }) -}) diff --git a/test/versioned/express/issue171.test.js b/test/versioned/express/issue171.test.js new file mode 100644 index 0000000000..ce09fcebcf --- /dev/null +++ b/test/versioned/express/issue171.test.js @@ -0,0 +1,50 @@ +/* + * Copyright 2020 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +'use strict' + +const helper = require('../../lib/agent_helper') +const test = require('node:test') +const tsplan = require('@matteo.collina/tspl') +const { setup } = require('./utils') +const http = require('http') + +test.beforeEach(async (ctx) => { + await setup(ctx) +}) + +test.afterEach((ctx) => { + helper.unloadAgent(ctx.nr.agent) + ctx.nr.server.close() +}) + +test("adding 'handle' middleware", function (t, end) { + const { app, port } = t.nr + const plan = tsplan(t, { plan: 2 }) + + // eslint-disable-next-line no-unused-vars + function handle(err, req, res, next) { + plan.ok(err, 'error should exist') + + res.statusCode = 500 + res.end() + } + + app.use('/', function () { + throw new Error() + }) + + app.use(handle) + + http + .request({ port: port }, function (res) { + // drain response to let process exit + res.pipe(process.stderr) + + plan.equal(res.statusCode, 500) + end() + }) + .end() +}) diff --git a/test/versioned/express/middleware-name.tap.js b/test/versioned/express/middleware-name.tap.js deleted file mode 100644 index f53adf5973..0000000000 --- a/test/versioned/express/middleware-name.tap.js +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2020 New Relic Corporation. All rights reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -'use strict' - -const test = require('tap').test -const helper = require('../../lib/agent_helper') - -test('should name middleware correctly', function (t) { - const agent = helper.instrumentMockedAgent() - - const app = require('express')() - - app.use('/', testMiddleware) - - const server = app.listen(0, function () { - const router = app._router || app.router - const mwLayer = router.stack.filter((layer) => layer.name === 'testMiddleware') - t.equal(mwLayer.length, 1, 'should only find one testMiddleware function') - t.end() - }) - - t.teardown(function () { - server.close() - helper.unloadAgent(agent) - }) - - function testMiddleware(req, res, next) { - next() - } -}) diff --git a/test/versioned/express/middleware-name.test.js b/test/versioned/express/middleware-name.test.js new file mode 100644 index 0000000000..a0d848a311 --- /dev/null +++ b/test/versioned/express/middleware-name.test.js @@ -0,0 +1,31 @@ +/* + * Copyright 2020 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +'use strict' +const assert = require('node:assert') +const test = require('node:test') +const helper = require('../../lib/agent_helper') +const { setup } = require('./utils') + +test.beforeEach(async (ctx) => { + await setup(ctx) +}) + +test.afterEach((ctx) => { + helper.unloadAgent(ctx.nr.agent) + ctx.nr.server.close() +}) + +test('should name middleware correctly', function (t) { + const { app } = t.nr + app.use('/', testMiddleware) + + const router = app._router || app.router + const mwLayer = router.stack.filter((layer) => layer.name === 'testMiddleware') + assert.equal(mwLayer.length, 1, 'should only find one testMiddleware function') + function testMiddleware(req, res, next) { + next() + } +}) diff --git a/test/versioned/express/package.json b/test/versioned/express/package.json index 694ed62abe..82d63966fc 100644 --- a/test/versioned/express/package.json +++ b/test/versioned/express/package.json @@ -22,24 +22,24 @@ "ejs": "2.5.9" }, "files": [ - "app-use.tap.js", - "async-error.tap.js", - "async-handlers.tap.js", - "bare-router.tap.js", - "captures-params.tap.js", - "client-disconnect.tap.js", - "errors.tap.js", - "express-enrouten.tap.js", - "ignoring.tap.js", - "issue171.tap.js", - "middleware-name.tap.js", - "render.tap.js", - "require.tap.js", - "route-iteration.tap.js", - "route-param.tap.js", - "router-params.tap.js", - "segments.tap.js", - "transaction-naming.tap.js" + "app-use.test.js", + "async-error.test.js", + "async-handlers.test.js", + "bare-router.test.js", + "captures-params.test.js", + "client-disconnect.test.js", + "errors.test.js", + "express-enrouten.test.js", + "ignoring.test.js", + "issue171.test.js", + "middleware-name.test.js", + "render.test.js", + "require.test.js", + "route-iteration.test.js", + "route-param.test.js", + "router-params.test.js", + "segments.test.js", + "transaction-naming.test.js" ] } ] diff --git a/test/versioned/express/render.tap.js b/test/versioned/express/render.tap.js deleted file mode 100644 index 610804c785..0000000000 --- a/test/versioned/express/render.tap.js +++ /dev/null @@ -1,661 +0,0 @@ -/* - * Copyright 2020 New Relic Corporation. All rights reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -'use strict' - -// shut up, Express -process.env.NODE_ENV = 'test' - -const test = require('tap').test -const helper = require('../../lib/agent_helper') -const API = require('../../../api') -const symbols = require('../../../lib/symbols') - -const TEST_PATH = '/test' -const TEST_HOST = 'localhost' -const TEST_URL = 'http://' + TEST_HOST + ':' -const DELAY = 600 -const BODY = - '\n' + - '\n' + - '
\n' + - 'I heard u like HTML.
\n' + - '\n' + - '\n' - -// Regression test for issue 154 -// https://github.com/newrelic/node-newrelic/pull/154 -test('using only the express router', function (t) { - const agent = helper.instrumentMockedAgent() - const router = require('express').Router() // eslint-disable-line new-cap - t.teardown(() => { - helper.unloadAgent(agent) - }) - - router.get('/test', function () { - // - }) - - router.get('/test2', function () { - // - }) - - // just try not to blow up - t.end() -}) - -test('the express router should go through a whole request lifecycle', function (t) { - const agent = helper.instrumentMockedAgent() - const router = require('express').Router() // eslint-disable-line new-cap - const finalhandler = require('finalhandler') - - t.plan(2) - - t.teardown(() => { - helper.unloadAgent(agent) - }) - - router.get('/test', function (_, res) { - t.ok(true) - res.end() - }) - - const server = require('http').createServer(function onRequest(req, res) { - router(req, res, finalhandler(req, res)) - }) - server.listen(0, function () { - const port = server.address().port - helper.makeRequest('http://localhost:' + port + '/test', function (error) { - server.close() - - t.error(error) - t.end() - }) - }) -}) - -test('agent instrumentation of Express', function (t) { - t.plan(7) - - let agent = null - let app = null - let server = null - - t.beforeEach(function () { - agent = helper.instrumentMockedAgent() - - app = require('express')() - server = require('http').createServer(app) - }) - - t.afterEach(function () { - server.close() - helper.unloadAgent(agent) - - agent = null - app = null - server = null - }) - - t.test('for a normal request', { timeout: 1000 }, function (t) { - // set apdexT so apdex stats will be recorded - agent.config.apdex_t = 1 - - app.get(TEST_PATH, function (req, res) { - res.send({ yep: true }) - }) - - server.listen(0, TEST_HOST, function () { - const port = server.address().port - helper.makeGetRequest(TEST_URL + port + TEST_PATH, function (error, response, body) { - t.error(error, 'should not fail making request') - - t.ok(/application\/json/.test(response.headers['content-type']), 'got correct content type') - - t.same(body, { yep: true }, 'Express correctly serves.') - - let stats - - stats = agent.metrics.getMetric('WebTransaction/Expressjs/GET//test') - t.ok(stats, 'found unscoped stats for request path') - t.equal(stats.callCount, 1, '/test was only requested once') - - stats = agent.metrics.getMetric('Apdex/Expressjs/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') - - const serialized = JSON.stringify(agent.metrics._toPayloadSync()) - t.ok( - serialized.match(/WebTransaction\/Expressjs\/GET\/\/test/), - 'serialized metrics as expected' - ) - - t.end() - }) - }) - }) - - t.test('ignore apdex when ignoreApdex is true on transaction', { timeout: 1000 }, function (t) { - // set apdexT so apdex stats will be recorded - agent.config.apdex_t = 1 - - app.get(TEST_PATH, function (req, res) { - const tx = agent.getTransaction() - tx.ignoreApdex = true - res.send({ yep: true }) - }) - - server.listen(0, TEST_HOST, function () { - const port = server.address().port - helper.makeGetRequest(TEST_URL + port + TEST_PATH, function () { - let stats - - stats = agent.metrics.getMetric('WebTransaction/Expressjs/GET//test') - t.ok(stats, 'found unscoped stats for request path') - t.equal(stats.callCount, 1, '/test was only requested once') - - stats = agent.metrics.getMetric('Apdex/Expressjs/GET//test') - t.notOk(stats, 'should not have apdex metrics') - - 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') - t.end() - }) - }) - }) - - t.test('using EJS templates', { timeout: 1000 }, function (t) { - app.set('views', __dirname + '/views') - app.set('view engine', 'ejs') - - app.get(TEST_PATH, function (req, res) { - res.render('index', { title: 'yo dawg' }) - }) - - agent.once('transactionFinished', function () { - const stats = agent.metrics.getMetric('View/index/Rendering') - t.equal(stats.callCount, 1, 'should note the view rendering') - }) - - server.listen(0, TEST_HOST, function () { - const port = server.address().port - helper.makeGetRequest(TEST_URL + port + TEST_PATH, function (error, response, body) { - t.error(error, 'should not error making request') - - t.equal(response.statusCode, 200, 'response code should be 200') - t.equal(body, BODY, 'template should still render fine') - - t.end() - }) - }) - }) - - t.test('should generate rum headers', { timeout: 1000 }, function (t) { - const api = new API(agent) - - agent.config.application_id = '12345' - agent.config.browser_monitoring.browser_key = '12345' - agent.config.browser_monitoring.js_agent_loader = 'function() {}' - - app.set('views', __dirname + '/views') - app.set('view engine', 'ejs') - - app.get(TEST_PATH, function (req, res) { - const rum = api.getBrowserTimingHeader() - t.equal(rum.substring(0, 7), '