diff --git a/test/versioned/koa/code-level-metrics.tap.js b/test/versioned/koa/code-level-metrics.tap.js deleted file mode 100644 index 889c3fdbb4..0000000000 --- a/test/versioned/koa/code-level-metrics.tap.js +++ /dev/null @@ -1,262 +0,0 @@ -/* - * Copyright 2022 New Relic Corporation. All rights reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -'use strict' - -const tap = require('tap') -const helper = require('../../lib/agent_helper') -const http = require('http') -let koaRouterAvailable -let atKoaRouterAvailable - -try { - require('./node_modules/koa-router/package.json') - koaRouterAvailable = true -} catch (err) { - koaRouterAvailable = false -} - -try { - require('./node_modules/@koa/router/package.json') - atKoaRouterAvailable = true -} catch (err) { - atKoaRouterAvailable = false -} - -async function setupApp({ useKoaRouter, useAtKoaRouter, isCLMEnabled }) { - const agent = helper.instrumentMockedAgent({ code_level_metrics: { enabled: isCLMEnabled } }) - let router - - if (useKoaRouter) { - const Router = require('koa-router') - router = new Router() - } - - if (useAtKoaRouter) { - const Router = require('@koa/router') - router = new Router() - } - - const Koa = require('koa') - const app = new Koa() - const server = await startServer(app) - - return { agent, app, router, server } -} - -async function makeRequest(params) { - return new Promise((resolve, reject) => { - const req = http.request(params, (res) => { - if (res.statusCode < 200 || res.statusCode >= 300) { - reject(new Error(`Status Code: ${res.statusCode}`)) - return - } - - const data = [] - - res.on('data', (chunk) => { - data.push(chunk) - }) - - res.on('end', () => resolve(Buffer.concat(data).toString())) - }) - - req.on('error', (err) => { - reject(err) - }) - - req.end() - }) -} - -async function startServer(app) { - return new Promise((resolve) => { - const server = app.listen(0, () => resolve(server)) - }) -} - -async function teardownApp(server, agent) { - return new Promise((resolve) => { - if (agent) { - helper.unloadAgent(agent) - } - - if (server) { - server.close(resolve) - } else { - resolve() - } - }) -} - -tap.test('Vanilla koa, no router', (t) => { - t.autoend() - - let agent - let app - let server - ;[true, false].forEach((isCLMEnabled) => { - t.test(`should ${isCLMEnabled ? 'add' : 'not add'} CLM attributes`, async (t) => { - ;({ agent, app, server } = await setupApp({ isCLMEnabled })) - - app.use(function one(_, next) { - next() - }) - - app.use(function two(ctx) { - ctx.body = 'done' - }) - - agent.on('transactionFinished', (transaction) => { - const baseSegment = transaction.trace.root.children[0] - t.clmAttrs({ - segments: [ - { - segment: baseSegment.children[0], - name: 'one', - filepath: 'code-level-metrics.tap.js' - }, - { - segment: baseSegment.children[0].children[0], - name: 'two', - filepath: 'code-level-metrics.tap.js' - } - ], - enabled: isCLMEnabled, - test: t - }) - }) - - const response = await makeRequest({ port: server.address().port }) - - t.equal(response, 'done', 'should return the correct data') - - t.teardown(async () => { - await teardownApp(server, agent) - }) - }) - }) -}) - -tap.test('Using koa-router', { skip: !koaRouterAvailable }, (t) => { - t.autoend() - - let agent - let app - let server - let router - ;[true, false].forEach((isCLMEnabled) => { - t.test(`should ${isCLMEnabled ? 'add' : 'not add'} CLM attributes`, async (t) => { - ;({ agent, app, server, router } = await setupApp({ isCLMEnabled, useKoaRouter: true })) - - const Router = require('koa-router') - const nestedRouter = new Router() - - nestedRouter.get('/second', function secondMiddleware(ctx) { - ctx.body = 'winner winner, chicken dinner' - }) - - router.use(function appLevelMiddleware(ctx, next) { - ctx.body = 'nope, not here' - return next() - }) - router.use('/:first', nestedRouter.routes()) - app.use(router.routes()) - - agent.on('transactionFinished', (transaction) => { - const baseSegment = transaction.trace.root.children[0] - - t.clmAttrs({ - segments: [ - { - segment: baseSegment.children[0], - name: 'dispatch', - filepath: 'koa-router/lib/router.js' - }, - { - segment: baseSegment.children[0].children[0], - name: 'appLevelMiddleware', - filepath: 'code-level-metrics.tap.js' - }, - { - segment: baseSegment.children[0].children[0].children[0], - name: 'secondMiddleware', - filepath: 'code-level-metrics.tap.js' - } - ], - enabled: isCLMEnabled, - test: t - }) - }) - - const response = await makeRequest({ port: server.address().port, path: '/123/second' }) - t.equal(response, 'winner winner, chicken dinner', 'should return the correct data') - t.teardown(async () => { - await teardownApp(server, agent) - }) - }) - }) -}) - -tap.test('Using @koa/router', { skip: !atKoaRouterAvailable }, (t) => { - t.autoend() - - let agent - let app - let server - let router - ;[true, false].forEach((isCLMEnabled) => { - t.test(`should ${isCLMEnabled ? 'add' : 'not add'} CLM attributes`, async (t) => { - ;({ agent, app, server, router } = await setupApp({ isCLMEnabled, useAtKoaRouter: true })) - - const Router = require('@koa/router') - const nestedRouter = new Router() - - nestedRouter.get('/second', function secondMiddleware(ctx) { - ctx.body = 'winner winner, chicken dinner' - }) - - router.use(function appLevelMiddleware(ctx, next) { - ctx.body = 'nope, not here' - return next() - }) - router.use('/:first', nestedRouter.routes()) - app.use(router.routes()) - - agent.on('transactionFinished', (transaction) => { - const baseSegment = transaction.trace.root.children[0] - - t.clmAttrs({ - segments: [ - { - segment: baseSegment.children[0], - name: 'dispatch', - filepath: '@koa/router/lib/router.js' - }, - { - segment: baseSegment.children[0].children[0], - name: 'appLevelMiddleware', - filepath: 'code-level-metrics.tap.js' - }, - { - segment: baseSegment.children[0].children[0].children[0], - name: 'secondMiddleware', - filepath: 'code-level-metrics.tap.js' - } - ], - enabled: isCLMEnabled, - test: t - }) - }) - - const response = await makeRequest({ port: server.address().port, path: '/123/second' }) - t.equal(response, 'winner winner, chicken dinner', 'should return the correct data') - - t.teardown(async () => { - await teardownApp(server, agent) - }) - }) - }) -}) diff --git a/test/versioned/koa/code-level-metrics.test.js b/test/versioned/koa/code-level-metrics.test.js new file mode 100644 index 0000000000..5975dfbb32 --- /dev/null +++ b/test/versioned/koa/code-level-metrics.test.js @@ -0,0 +1,258 @@ +/* + * Copyright 2024 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +'use strict' + +const test = require('node:test') +const http = require('node:http') +const tspl = require('@matteo.collina/tspl') + +const { removeModules } = require('../../lib/cache-buster') +const helper = require('../../lib/agent_helper') +const assertClmAttrs = require('../../lib/custom-assertions/assert-clm-attrs') + +async function setupApp({ ctx, useKoaRouter, useAtKoaRouter, isCLMEnabled }) { + const agent = helper.instrumentMockedAgent({ code_level_metrics: { enabled: isCLMEnabled } }) + let router + + if (useKoaRouter === true) { + const Router = require('koa-router') + router = new Router() + } + + if (useAtKoaRouter === true) { + const Router = require('@koa/router') + router = new Router() + } + + const Koa = require('koa') + const app = new Koa() + const server = await startServer(app) + + ctx.agent = agent + ctx.app = app + ctx.router = router + ctx.server = server +} + +async function startServer(app) { + return new Promise((resolve) => { + const server = app.listen(0, () => resolve(server)) + }) +} + +async function makeRequest(params) { + return new Promise((resolve, reject) => { + const req = http.request(params, (res) => { + if (res.statusCode < 200 || res.statusCode >= 300) { + reject(new Error(`Status Code: ${res.statusCode}`)) + return + } + + const data = [] + + res.on('data', (chunk) => { + data.push(chunk) + }) + + res.on('end', () => resolve(Buffer.concat(data).toString())) + }) + + req.on('error', (err) => { + reject(err) + }) + + req.end() + }) +} + +test('vanilla koa, no router', async (t) => { + t.beforeEach((ctx) => { + ctx.nr = {} + }) + + t.afterEach((ctx) => { + ctx.nr.server.close() + removeModules(['koa', '@koa/router', 'koa-router']) + helper.unloadAgent(ctx.nr.agent) + }) + + for (const isCLMEnabled of [true, false]) { + await t.test(`should ${isCLMEnabled ? 'add' : 'not add'} CLM attributes`, async (t) => { + const plan = tspl(t, { plan: 9 }) + + await setupApp({ ctx: t.nr, isCLMEnabled }) + const { agent, app, server } = t.nr + + app.use(function one(_, next) { + next() + }) + app.use(function two(ctx) { + ctx.body = 'done' + }) + + agent.on('transactionFinished', (tx) => { + const baseSegment = tx.trace.root.children[0] + assertClmAttrs( + { + segments: [ + { + segment: baseSegment.children[0], + name: 'one', + filepath: 'code-level-metrics.test.js' + }, + { + segment: baseSegment.children[0].children[0], + name: 'two', + filepath: 'code-level-metrics.test.js' + } + ], + enabled: isCLMEnabled + }, + { assert: plan } + ) + }) + + const response = await makeRequest({ port: server.address().port }) + plan.equal(response, 'done', 'should return the correct data') + + await plan.completed + }) + } +}) + +test('using koa-router', async (t) => { + t.beforeEach((ctx) => { + ctx.nr = {} + }) + + t.afterEach((ctx) => { + ctx.nr.server.close() + removeModules(['koa', '@koa/router', 'koa-router']) + helper.unloadAgent(ctx.nr.agent) + }) + + for (const isCLMEnabled of [true, false]) { + await t.test(`should ${isCLMEnabled ? 'add' : 'not add'} CLM attributes`, async (t) => { + const plan = tspl(t, { plan: 13 }) + + await setupApp({ ctx: t.nr, isCLMEnabled, useKoaRouter: true }) + const { agent, app, router, server } = t.nr + + const Router = require('koa-router') + const nestedRouter = new Router() + + nestedRouter.get('/second', function secondMiddleware(ctx) { + ctx.body = 'winner winner, chicken dinner' + }) + + router.use(function appLevelMiddleware(ctx, next) { + ctx.body = 'nope, not here' + return next() + }) + router.use('/:first', nestedRouter.routes()) + app.use(router.routes()) + + agent.on('transactionFinished', (tx) => { + const baseSegment = tx.trace.root.children[0] + assertClmAttrs( + { + segments: [ + { + segment: baseSegment.children[0], + name: 'dispatch', + filepath: 'koa-router/lib/router.js' + }, + { + segment: baseSegment.children[0].children[0], + name: 'appLevelMiddleware', + filepath: 'code-level-metrics.test.js' + }, + { + segment: baseSegment.children[0].children[0].children[0], + name: 'secondMiddleware', + filepath: 'code-level-metrics.test.js' + } + ], + enabled: isCLMEnabled + }, + { assert: plan } + ) + }) + + const response = await makeRequest({ port: server.address().port, path: '/123/second' }) + plan.equal(response, 'winner winner, chicken dinner', 'should return the correct data') + + await plan.completed + }) + } +}) + +test('using @koa/router', async (t) => { + t.beforeEach((ctx) => { + ctx.nr = {} + }) + + t.afterEach((ctx) => { + ctx.nr.server.close() + removeModules(['koa', '@koa/router', 'koa-router']) + helper.unloadAgent(ctx.nr.agent) + }) + + for (const isCLMEnabled of [true, false]) { + await t.test(`should ${isCLMEnabled ? 'add' : 'not add'} CLM attributes`, async (t) => { + const plan = tspl(t, { plan: 13 }) + + await setupApp({ ctx: t.nr, isCLMEnabled, useAtKoaRouter: true }) + const { agent, app, router, server } = t.nr + + const Router = require('@koa/router') + const nestedRouter = new Router() + + nestedRouter.get('/second', function secondMiddleware(ctx) { + ctx.body = 'winner winner, chicken dinner' + }) + + router.use(function appLevelMiddleware(ctx, next) { + ctx.body = 'nope, not here' + return next() + }) + router.use('/:first', nestedRouter.routes()) + app.use(router.routes()) + + agent.on('transactionFinished', (tx) => { + const baseSegment = tx.trace.root.children[0] + assertClmAttrs( + { + segments: [ + { + segment: baseSegment.children[0], + name: 'dispatch', + filepath: '@koa/router/lib/router.js' + }, + { + segment: baseSegment.children[0].children[0], + name: 'appLevelMiddleware', + filepath: 'code-level-metrics.test.js' + }, + { + segment: baseSegment.children[0].children[0].children[0], + name: 'secondMiddleware', + filepath: 'code-level-metrics.test.js' + } + ], + enabled: isCLMEnabled + }, + { assert: plan } + ) + }) + + const response = await makeRequest({ port: server.address().port, path: '/123/second' }) + plan.equal(response, 'winner winner, chicken dinner', 'should return the correct data') + + await plan.completed + }) + } +}) diff --git a/test/versioned/koa/koa-route.tap.js b/test/versioned/koa/koa-route.tap.js deleted file mode 100644 index 9048776692..0000000000 --- a/test/versioned/koa/koa-route.tap.js +++ /dev/null @@ -1,197 +0,0 @@ -/* - * Copyright 2020 New Relic Corporation. All rights reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -'use strict' - -const tap = require('tap') -const helper = require('../../lib/agent_helper') -require('../../lib/metrics_helper') -const { run } = require('./utils') - -tap.test('koa-route instrumentation', function (t) { - t.beforeEach(function (t) { - t.context.agent = helper.instrumentMockedAgent() - const Koa = require('koa') - t.context.app = new Koa() - t.context.route = require('koa-route') - }) - - t.afterEach(function (t) { - t.context.server.close() - helper.unloadAgent(t.context.agent) - }) - - t.test('should name and produce segments for koa-route middleware', function (t) { - const { agent, app, route } = t.context - const first = route.get('/resource', function firstMiddleware(ctx) { - ctx.body = 'hello' - }) - app.use(first) - agent.on('transactionFinished', function (tx) { - t.assertSegments(tx.trace.root, [ - 'WebTransaction/WebFrameworkUri/Koa/GET//resource', - ['Nodejs/Middleware/Koa/firstMiddleware//resource'] - ]) - t.equal( - tx.name, - 'WebTransaction/WebFrameworkUri/Koa/GET//resource', - 'transaction should be named after the middleware responsible for responding' - ) - t.end() - }) - run({ path: '/resource', context: t.context }) - }) - - t.test('should name the transaction after the last responder', function (t) { - const { agent, app, route } = t.context - const first = route.get('/:first', function firstMiddleware(ctx, param, next) { - ctx.body = 'first' - return next() - }) - const second = route.get('/:second', function secondMiddleware(ctx) { - ctx.body = 'second' - }) - app.use(first) - app.use(second) - agent.on('transactionFinished', function (tx) { - t.assertSegments(tx.trace.root, [ - 'WebTransaction/WebFrameworkUri/Koa/GET//:second', - [ - 'Nodejs/Middleware/Koa/firstMiddleware//:first', - ['Nodejs/Middleware/Koa/secondMiddleware//:second'] - ] - ]) - t.equal( - tx.name, - 'WebTransaction/WebFrameworkUri/Koa/GET//:second', - 'transaction should be named after the middleware responsible for responding' - ) - t.end() - }) - run({ context: t.context }) - }) - - t.test('should name the transaction properly when responding after next', function (t) { - const { agent, app, route } = t.context - const first = route.get('/:first', function firstMiddleware(ctx, param, next) { - return next().then(function respond() { - ctx.body = 'first' - }) - }) - const second = route.get('/:second', function secondMiddleware(ctx) { - ctx.body = 'second' - }) - app.use(first) - app.use(second) - agent.on('transactionFinished', function (tx) { - t.assertSegments(tx.trace.root, [ - 'WebTransaction/WebFrameworkUri/Koa/GET//:first', - [ - 'Nodejs/Middleware/Koa/firstMiddleware//:first', - ['Nodejs/Middleware/Koa/secondMiddleware//:second'] - ] - ]) - t.equal( - tx.name, - 'WebTransaction/WebFrameworkUri/Koa/GET//:first', - 'transaction should be named after the middleware responsible for responding' - ) - t.end() - }) - run({ context: t.context }) - }) - - t.test('should work with early responding', function (t) { - const { agent, app, route } = t.context - const first = route.get('/:first', function firstMiddleware(ctx) { - ctx.body = 'first' - return Promise.resolve() - }) - const second = route.get('/:second', function secondMiddleware(ctx) { - ctx.body = 'second' - }) - app.use(first) - app.use(second) - agent.on('transactionFinished', function (tx) { - t.assertSegments(tx.trace.root, [ - 'WebTransaction/WebFrameworkUri/Koa/GET//:first', - ['Nodejs/Middleware/Koa/firstMiddleware//:first'] - ]) - t.equal( - tx.name, - 'WebTransaction/WebFrameworkUri/Koa/GET//:first', - 'transaction should be named after the middleware responsible for responding' - ) - t.end() - }) - run({ context: t.context }) - }) - - t.test('should name the transaction after the source of the error that occurred', function (t) { - const { agent, app, route } = t.context - const first = route.get('/:first', function firstMiddleware(ctx, param, next) { - return next() - }) - const second = route.get('/:second', function secondMiddleware() { - throw new Error('some error') - }) - app.use(first) - app.use(second) - agent.on('transactionFinished', function (tx) { - t.assertSegments(tx.trace.root, [ - 'WebTransaction/WebFrameworkUri/Koa/GET//:second', - [ - 'Nodejs/Middleware/Koa/firstMiddleware//:first', - ['Nodejs/Middleware/Koa/secondMiddleware//:second'] - ] - ]) - t.equal( - tx.name, - 'WebTransaction/WebFrameworkUri/Koa/GET//:second', - 'transaction should be named after the middleware responsible for responding' - ) - t.end() - }) - run({ context: t.context }) - }) - - t.test('should work properly when used along with non-route middleware', function (t) { - const { agent, app, route } = t.context - const first = function firstMiddleware(ctx, next) { - return next() - } - const second = route.get('/resource', function secondMiddleware(ctx, next) { - ctx.body = 'hello' - return next() - }) - const third = function thirdMiddleware(ctx, next) { - return next() - } - app.use(first) - app.use(second) - app.use(third) - agent.on('transactionFinished', function (tx) { - t.assertSegments(tx.trace.root, [ - 'WebTransaction/WebFrameworkUri/Koa/GET//resource', - [ - 'Nodejs/Middleware/Koa/firstMiddleware', - [ - 'Nodejs/Middleware/Koa/secondMiddleware//resource', - ['Nodejs/Middleware/Koa/thirdMiddleware'] - ] - ] - ]) - t.equal( - tx.name, - 'WebTransaction/WebFrameworkUri/Koa/GET//resource', - 'transaction should be named after the middleware responsible for responding' - ) - t.end() - }) - run({ path: '/resource', context: t.context }) - }) - - t.end() -}) diff --git a/test/versioned/koa/koa-route.test.js b/test/versioned/koa/koa-route.test.js new file mode 100644 index 0000000000..05f9fe5340 --- /dev/null +++ b/test/versioned/koa/koa-route.test.js @@ -0,0 +1,200 @@ +/* + * Copyright 2024 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +'use strict' + +const test = require('node:test') +const assert = require('node:assert') + +const { removeModules } = require('../../lib/cache-buster') +const { run } = require('./utils') +const assertSegments = require('../../lib/custom-assertions/assert-segments') +const helper = require('../../lib/agent_helper') + +test.beforeEach((ctx) => { + ctx.nr = {} + ctx.nr.agent = helper.instrumentMockedAgent() + + const Koa = require('koa') + ctx.nr.app = new Koa() + ctx.nr.route = require('koa-route') +}) + +test.afterEach((ctx) => { + removeModules(['koa', 'koa-router']) + helper.unloadAgent(ctx.nr.agent) + ctx.nr.server.close() +}) + +test('should name and produce segments for koa-route middleware', (t, end) => { + const { agent, app, route } = t.nr + const first = route.get('/resource', function firstMiddleware(ctx) { + ctx.body = 'hello' + }) + app.use(first) + agent.on('transactionFinished', function (tx) { + assertSegments(tx.trace.root, [ + 'WebTransaction/WebFrameworkUri/Koa/GET//resource', + ['Nodejs/Middleware/Koa/firstMiddleware//resource'] + ]) + assert.equal( + tx.name, + 'WebTransaction/WebFrameworkUri/Koa/GET//resource', + 'transaction should be named after the middleware responsible for responding' + ) + end() + }) + run({ path: '/resource', context: t.nr }) +}) + +test('should name the transaction after the last responder', (t, end) => { + const { agent, app, route } = t.nr + const first = route.get('/:first', function firstMiddleware(ctx, param, next) { + ctx.body = 'first' + return next() + }) + const second = route.get('/:second', function secondMiddleware(ctx) { + ctx.body = 'second' + }) + app.use(first) + app.use(second) + agent.on('transactionFinished', function (tx) { + assertSegments(tx.trace.root, [ + 'WebTransaction/WebFrameworkUri/Koa/GET//:second', + [ + 'Nodejs/Middleware/Koa/firstMiddleware//:first', + ['Nodejs/Middleware/Koa/secondMiddleware//:second'] + ] + ]) + assert.equal( + tx.name, + 'WebTransaction/WebFrameworkUri/Koa/GET//:second', + 'transaction should be named after the middleware responsible for responding' + ) + end() + }) + run({ context: t.nr }) +}) + +test('should name the transaction properly when responding after next', (t, end) => { + const { agent, app, route } = t.nr + const first = route.get('/:first', function firstMiddleware(ctx, param, next) { + return next().then(function respond() { + ctx.body = 'first' + }) + }) + const second = route.get('/:second', function secondMiddleware(ctx) { + ctx.body = 'second' + }) + app.use(first) + app.use(second) + agent.on('transactionFinished', function (tx) { + assertSegments(tx.trace.root, [ + 'WebTransaction/WebFrameworkUri/Koa/GET//:first', + [ + 'Nodejs/Middleware/Koa/firstMiddleware//:first', + ['Nodejs/Middleware/Koa/secondMiddleware//:second'] + ] + ]) + assert.equal( + tx.name, + 'WebTransaction/WebFrameworkUri/Koa/GET//:first', + 'transaction should be named after the middleware responsible for responding' + ) + end() + }) + run({ context: t.nr }) +}) + +test('should work with early responding', (t, end) => { + const { agent, app, route } = t.nr + const first = route.get('/:first', function firstMiddleware(ctx) { + ctx.body = 'first' + return Promise.resolve() + }) + const second = route.get('/:second', function secondMiddleware(ctx) { + ctx.body = 'second' + }) + app.use(first) + app.use(second) + agent.on('transactionFinished', function (tx) { + assertSegments(tx.trace.root, [ + 'WebTransaction/WebFrameworkUri/Koa/GET//:first', + ['Nodejs/Middleware/Koa/firstMiddleware//:first'] + ]) + assert.equal( + tx.name, + 'WebTransaction/WebFrameworkUri/Koa/GET//:first', + 'transaction should be named after the middleware responsible for responding' + ) + end() + }) + run({ context: t.nr }) +}) + +test('should name the transaction after the source of the error that occurred', (t, end) => { + const { agent, app, route } = t.nr + const first = route.get('/:first', function firstMiddleware(ctx, param, next) { + return next() + }) + const second = route.get('/:second', function secondMiddleware() { + throw Error('some error') + }) + app.silent = true + app.use(first) + app.use(second) + agent.on('transactionFinished', function (tx) { + assertSegments(tx.trace.root, [ + 'WebTransaction/WebFrameworkUri/Koa/GET//:second', + [ + 'Nodejs/Middleware/Koa/firstMiddleware//:first', + ['Nodejs/Middleware/Koa/secondMiddleware//:second'] + ] + ]) + assert.equal( + tx.name, + 'WebTransaction/WebFrameworkUri/Koa/GET//:second', + 'transaction should be named after the middleware responsible for responding' + ) + end() + }) + run({ context: t.nr }) +}) + +test('should work properly when used along with non-route middleware', (t, end) => { + const { agent, app, route } = t.nr + const first = function firstMiddleware(ctx, next) { + return next() + } + const second = route.get('/resource', function secondMiddleware(ctx, next) { + ctx.body = 'hello' + return next() + }) + const third = function thirdMiddleware(ctx, next) { + return next() + } + app.use(first) + app.use(second) + app.use(third) + agent.on('transactionFinished', function (tx) { + assertSegments(tx.trace.root, [ + 'WebTransaction/WebFrameworkUri/Koa/GET//resource', + [ + 'Nodejs/Middleware/Koa/firstMiddleware', + [ + 'Nodejs/Middleware/Koa/secondMiddleware//resource', + ['Nodejs/Middleware/Koa/thirdMiddleware'] + ] + ] + ]) + assert.equal( + tx.name, + 'WebTransaction/WebFrameworkUri/Koa/GET//resource', + 'transaction should be named after the middleware responsible for responding' + ) + end() + }) + run({ path: '/resource', context: t.nr }) +}) diff --git a/test/versioned/koa/koa-router.tap.js b/test/versioned/koa/koa-router.test.js similarity index 100% rename from test/versioned/koa/koa-router.tap.js rename to test/versioned/koa/koa-router.test.js diff --git a/test/versioned/koa/koa.tap.js b/test/versioned/koa/koa.tap.js deleted file mode 100644 index 9f2844fcce..0000000000 --- a/test/versioned/koa/koa.tap.js +++ /dev/null @@ -1,408 +0,0 @@ -/* - * Copyright 2020 New Relic Corporation. All rights reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -'use strict' - -const tap = require('tap') -const http = require('http') -const helper = require('../../lib/agent_helper') -require('../../lib/metrics_helper') - -tap.test('Koa instrumentation', (t) => { - t.autoend() - - t.beforeEach(() => { - t.context.agent = helper.instrumentMockedAgent() - const Koa = require('koa') - t.context.app = new Koa() - t.context.testShim = helper.getShim(Koa) - }) - - t.afterEach((t) => { - t.context.server.close() - helper.unloadAgent(t.context.agent) - }) - - t.test('Should name after koa framework and verb when body set', (t) => { - const { agent, app } = t.context - - app.use(function one(ctx, next) { - return next().then(() => { - // do nothing - }) - }) - - app.use(function two(ctx) { - ctx.body = 'done' - }) - - agent.on('transactionFinished', (tx) => { - t.equal( - tx.name, - 'WebTransaction/WebFrameworkUri/Koa/GET//', - 'should have name without post-response name info' - ) - }) - - run(t) - }) - - t.test('Should name (not found) when no work is performed', (t) => { - const { agent, app } = t.context - - app.use(function one(ctx, next) { - return next().then(() => { - // do nothing - }) - }) - - app.use(function two() { - // do nothing - }) - - agent.on('transactionFinished', (tx) => { - t.equal( - tx.name, - 'WebTransaction/WebFrameworkUri/Koa/GET/(not found)', - 'should name after status code message' - ) - }) - - run(t, 'Not Found') - }) - - t.test('names the transaction after the middleware that sets the body', (t) => { - const { agent, app } = t.context - - app.use(function one(ctx, next) { - const tx = agent.getTransaction() - return next().then(() => tx.nameState.appendPath('one-end')) - }) - - app.use(function two(ctx) { - const tx = agent.getTransaction() - tx.nameState.appendPath('two') - ctx.body = 'done' - }) - - agent.on('transactionFinished', (tx) => { - t.equal( - tx.name, - 'WebTransaction/WebFrameworkUri/Koa/GET//two', - 'should have name without post-response name info' - ) - }) - - run(t) - }) - - t.test('names the transaction after the last middleware that sets the body', (t) => { - const { agent, app } = t.context - - app.use(function one(ctx, next) { - const tx = agent.getTransaction() - return next().then(() => tx.nameState.appendPath('one-end')) - }) - - app.use(function two(ctx, next) { - const tx = agent.getTransaction() - tx.nameState.appendPath('two') - ctx.body = 'not actually done' - return next() - }) - - app.use(function three(ctx) { - const tx = agent.getTransaction() - tx.nameState.appendPath('three') - ctx.body = 'done' - }) - - agent.on('transactionFinished', (tx) => { - t.equal( - tx.name, - 'WebTransaction/WebFrameworkUri/Koa/GET//three', - 'should have name without post-response name info' - ) - }) - - run(t) - }) - - t.test('names the transaction off the status setting middleware', (t) => { - const { agent, app } = t.context - - app.use(function one(ctx, next) { - const tx = agent.getTransaction() - return next().then(() => tx.nameState.appendPath('one-end')) - }) - - app.use(function two(ctx) { - const tx = agent.getTransaction() - tx.nameState.appendPath('two') - ctx.status = 202 - }) - - agent.on('transactionFinished', (tx) => { - t.equal( - tx.name, - 'WebTransaction/WebFrameworkUri/Koa/GET//two', - 'should have name without post-response name info' - ) - }) - - run(t, 'Accepted', (err, res) => { - t.error(err) - t.equal(res.statusCode, 202, 'should not interfere with status code setting') - t.end() - }) - }) - - t.test('names the transaction when body set even if status set after', (t) => { - const { agent, app } = t.context - - app.use(function one(ctx, next) { - const tx = agent.getTransaction() - return next().then(() => tx.nameState.appendPath('one-end')) - }) - - app.use(function two(ctx) { - const tx = agent.getTransaction() - tx.nameState.appendPath('two') - ctx.body = 'done' - - tx.nameState.appendPath('setting-status') - ctx.status = 202 - }) - - agent.on('transactionFinished', (tx) => { - t.equal( - tx.name, - 'WebTransaction/WebFrameworkUri/Koa/GET//two', - 'should have name without post-response name info' - ) - }) - - run(t, (err, res) => { - t.error(err) - t.equal(res.statusCode, 202, 'should not interfere with status code setting') - t.end() - }) - }) - - t.test('produces transaction trace with multiple middleware', (t) => { - const { agent, app } = t.context - - app.use(function one(ctx, next) { - return next() - }) - app.use(function two(ctx) { - ctx.response.body = 'done' - }) - - agent.on('transactionFinished', (tx) => { - checkSegments(t, tx) - }) - - run(t) - }) - - t.test('correctly records actions interspersed among middleware', (t) => { - const { agent, app, testShim } = t.context - - app.use(function one(ctx, next) { - testShim.createSegment('testSegment') - return next().then(function () { - testShim.createSegment('nestedSegment') - }) - }) - app.use(function two(ctx, next) { - return new Promise(function (resolve) { - setTimeout(resolve, 10) - }).then(next) - }) - app.use(function three(ctx) { - ctx.body = 'done' - }) - - agent.on('transactionFinished', (tx) => { - t.assertSegments(tx.trace.root, [ - 'WebTransaction/WebFrameworkUri/Koa/GET//', - [ - 'Nodejs/Middleware/Koa/one', - [ - 'Truncated/testSegment', - 'Nodejs/Middleware/Koa/two', - ['timers.setTimeout', ['Callback: '], 'Nodejs/Middleware/Koa/three'], - 'Truncated/nestedSegment' - ] - ] - ]) - }) - - run(t) - }) - - t.test('maintains transaction state between middleware', (t) => { - const { agent, app } = t.context - let tx - - app.use(async function one(ctx, next) { - tx = agent.getTransaction() - - await next() - - t.ok(tx) - }) - - app.use(async function two(ctx, next) { - t.equal(tx.id, agent.getTransaction().id, 'two has transaction context') - await next() - }) - - app.use(function three(ctx, next) { - t.equal(tx.id, agent.getTransaction().id, 'three has transaction context') - return new Promise((resolve) => { - setImmediate(() => { - next().then(() => { - t.equal( - tx.id, - agent.getTransaction().id, - 'still have context after in-context timer hop' - ) - resolve() - }) - }) - }) - }) - - app.use(function four(ctx) { - t.equal(tx.id, agent.getTransaction().id, 'four has transaction context') - ctx.body = 'done' - }) - - agent.on('transactionFinished', function (txn) { - t.assertSegments(tx.trace.root, [ - txn.name, - [ - 'Nodejs/Middleware/Koa/one', - [ - 'Nodejs/Middleware/Koa/two', - ['Nodejs/Middleware/Koa/three', ['Nodejs/Middleware/Koa/four']] - ] - ] - ]) - }) - - run(t) - }) - - t.test('errors handled within middleware are not recorded', (t) => { - const { agent, app } = t.context - - app.use(function one(ctx, next) { - return next().catch(function (err) { - t.equal(err.message, 'middleware error', 'caught expected error') - ctx.status = 200 - ctx.body = 'handled error' - }) - }) - app.use(function two(ctx) { - throw new Error('middleware error') - ctx.body = 'done' - }) - - agent.on('transactionFinished', (tx) => { - const errors = agent.errors.traceAggregator.errors - t.equal(errors.length, 0, 'no errors are recorded') - checkSegments(t, tx) - }) - - run(t, 'handled error') - }) - - t.test('errors not handled by middleware are recorded', (t) => { - const { agent, app } = t.context - - app.use(function one(ctx, next) { - return next().catch(function (err) { - t.equal(err.message, 'middleware error', 'caught expected error') - ctx.status = 500 - ctx.body = 'error is not actually handled' - }) - }) - app.use(function two() { - throw new Error('middleware error') - }) - - agent.on('transactionFinished', (tx) => { - const errors = agent.errors.traceAggregator.errors - t.equal(errors.length, 1, 'recorded expected number of errors') - const error = errors[0][2] - t.equal(error, 'middleware error', 'recorded expected error') - checkSegments(t, tx) - }) - run(t, 'error is not actually handled') - }) - - t.test('errors caught by default error listener are recorded', (t) => { - const { agent, app } = t.context - - app.use(function one(ctx, next) { - return next() - }) - app.use(function two() { - throw new Error('middleware error') - }) - app.on('error', function (err) { - t.equal(err.message, 'middleware error', 'caught expected error') - }) - - agent.on('transactionFinished', (tx) => { - const errors = agent.errors.traceAggregator.errors - t.equal(errors.length, 1, 'recorded expected number of errors') - const error = errors[0][2] - t.equal(error, 'middleware error', 'recorded expected error') - checkSegments(t, tx) - }) - run(t, 'Internal Server Error') - }) - - function run(t, expected, cb) { - if (typeof expected !== 'string') { - // run(t [, cb]) - cb = expected - expected = 'done' - } - - t.context.server = t.context.app.listen(0, () => { - http.get({ port: t.context.server.address().port }, (res) => { - let body = '' - res.on('data', (data) => (body += data.toString('utf8'))) - res.on('error', (err) => cb && cb(err)) - res.on('end', () => { - if (expected) { - t.equal(body, expected, 'should send expected response') - } - - if (!cb) { - t.end() - return - } - - cb(null, res) - }) - }) - }) - } -}) - -function checkSegments(t, tx) { - t.assertSegments(tx.trace.root, [ - // Until koa-router is instrumented and transaction naming is addressed, - // names will be inconsistent depending on whether there is an error. - tx.name, - ['Nodejs/Middleware/Koa/one', ['Nodejs/Middleware/Koa/two']] - ]) -} diff --git a/test/versioned/koa/koa.test.js b/test/versioned/koa/koa.test.js new file mode 100644 index 0000000000..17928ce988 --- /dev/null +++ b/test/versioned/koa/koa.test.js @@ -0,0 +1,454 @@ +/* + * Copyright 2024 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +'use strict' + +const test = require('node:test') +const http = require('node:http') +const tspl = require('@matteo.collina/tspl') + +const { removeModules } = require('../../lib/cache-buster') +const assertSegments = require('../../lib/custom-assertions/assert-segments') +const helper = require('../../lib/agent_helper') + +test.beforeEach((ctx) => { + ctx.nr = {} + ctx.nr.agent = helper.instrumentMockedAgent() + + const Koa = require('koa') + ctx.nr.app = new Koa() + ctx.nr.testShim = helper.getShim(Koa) +}) + +test.afterEach((ctx) => { + ctx.nr.server.close() + helper.unloadAgent(ctx.nr.agent) + removeModules(['koa']) +}) + +function run({ t, expected = 'done', cb, end, plan }) { + t.nr.server = t.nr.app.listen(0, () => { + http.get({ port: t.nr.server.address().port }, (res) => { + let body = '' + res.on('data', (data) => { + body += data.toString('utf8') + }) + res.on('error', (err) => cb && cb(err)) + res.on('end', () => { + if (expected) { + plan.equal(body, expected, 'should send expected response') + } + + if (!cb) { + end && end() + return + } + + cb(null, res) + }) + }) + }) +} + +function checkSegments(plan, tx) { + assertSegments( + tx.trace.root, + [ + // Until koa-router is instrumented and transaction naming is addressed, + // names will be inconsistent depending on whether there is an error. + tx.name, + ['Nodejs/Middleware/Koa/one', ['Nodejs/Middleware/Koa/two']] + ], + {}, + { assert: plan } + ) +} + +test('Should name after koa framework and verb when body set', async (t) => { + const plan = tspl(t, { plan: 2 }) + const { agent, app } = t.nr + + app.use(function one(ctx, next) { + return next().then(() => { + // do nothing + }) + }) + + app.use(function two(ctx) { + ctx.body = 'done' + }) + + agent.on('transactionFinished', (tx) => { + plan.equal( + tx.name, + 'WebTransaction/WebFrameworkUri/Koa/GET//', + 'should have name without post-response name info' + ) + }) + + run({ t, plan }) + await plan.completed +}) + +test('Should name (not found) when no work is performed', async (t) => { + const plan = tspl(t, { plan: 2 }) + const { agent, app } = t.nr + + app.use(function one(ctx, next) { + return next().then(() => { + // do nothing + }) + }) + + app.use(function two() { + // do nothing + }) + + agent.on('transactionFinished', (tx) => { + plan.equal( + tx.name, + 'WebTransaction/WebFrameworkUri/Koa/GET/(not found)', + 'should name after status code message' + ) + }) + + run({ t, expected: 'Not Found', plan }) + await plan.completed +}) + +test('names the transaction after the middleware that sets the body', async (t) => { + const plan = tspl(t, { plan: 2 }) + const { agent, app } = t.nr + + app.use(function one(ctx, next) { + const tx = agent.getTransaction() + return next().then(() => tx.nameState.appendPath('one-end')) + }) + + app.use(function two(ctx) { + const tx = agent.getTransaction() + tx.nameState.appendPath('two') + ctx.body = 'done' + }) + + agent.on('transactionFinished', (tx) => { + plan.equal( + tx.name, + 'WebTransaction/WebFrameworkUri/Koa/GET//two', + 'should have name without post-response name info' + ) + }) + + run({ t, plan }) + await plan.completed +}) + +test('names the transaction after the last middleware that sets the body', async (t) => { + const plan = tspl(t, { plan: 2 }) + const { agent, app } = t.nr + + app.use(function one(ctx, next) { + const tx = agent.getTransaction() + return next().then(() => tx.nameState.appendPath('one-end')) + }) + + app.use(function two(ctx, next) { + const tx = agent.getTransaction() + tx.nameState.appendPath('two') + ctx.body = 'not actually done' + return next() + }) + + app.use(function three(ctx) { + const tx = agent.getTransaction() + tx.nameState.appendPath('three') + ctx.body = 'done' + }) + + agent.on('transactionFinished', (tx) => { + plan.equal( + tx.name, + 'WebTransaction/WebFrameworkUri/Koa/GET//three', + 'should have name without post-response name info' + ) + }) + + run({ t, plan }) + await plan.completed +}) + +test('names the transaction off the status setting middleware', async (t) => { + const plan = tspl(t, { plan: 4 }) + const { agent, app } = t.nr + + app.use(function one(ctx, next) { + const tx = agent.getTransaction() + return next().then(() => tx.nameState.appendPath('one-end')) + }) + + app.use(function two(ctx) { + const tx = agent.getTransaction() + tx.nameState.appendPath('two') + ctx.status = 202 + }) + + agent.on('transactionFinished', (tx) => { + plan.equal( + tx.name, + 'WebTransaction/WebFrameworkUri/Koa/GET//two', + 'should have name without post-response name info' + ) + }) + + run({ + t, + expected: 'Accepted', + cb: (err, res) => { + plan.ifError(err) + plan.equal(res.statusCode, 202, 'should not interfere with status code setting') + }, + plan + }) + await plan.completed +}) + +test('names the transaction when body set even if status set after', async (t) => { + const plan = tspl(t, { plan: 4 }) + const { agent, app } = t.nr + + app.use(function one(ctx, next) { + const tx = agent.getTransaction() + return next().then(() => tx.nameState.appendPath('one-end')) + }) + + app.use(function two(ctx) { + const tx = agent.getTransaction() + tx.nameState.appendPath('two') + ctx.body = 'done' + + tx.nameState.appendPath('setting-status') + ctx.status = 202 + }) + + agent.on('transactionFinished', (tx) => { + plan.equal( + tx.name, + 'WebTransaction/WebFrameworkUri/Koa/GET//two', + 'should have name without post-response name info' + ) + }) + + run({ + t, + cb: (err, res) => { + plan.ifError(err) + plan.equal(res.statusCode, 202, 'should not interfere with status code setting') + }, + plan + }) + await plan.completed +}) + +test('produces transaction trace with multiple middleware', async (t) => { + const plan = tspl(t, { plan: 7 }) + const { agent, app } = t.nr + + app.use(function one(ctx, next) { + return next() + }) + app.use(function two(ctx) { + ctx.response.body = 'done' + }) + + agent.on('transactionFinished', (tx) => { + checkSegments(plan, tx) + }) + + run({ t, plan }) + await plan.completed +}) + +test('correctly records actions interspersed among middleware', async (t) => { + const plan = tspl(t, { plan: 17 }) + const { agent, app, testShim } = t.nr + + app.use(function one(ctx, next) { + testShim.createSegment('testSegment') + return next().then(function () { + testShim.createSegment('nestedSegment') + }) + }) + app.use(function two(ctx, next) { + return new Promise(function (resolve) { + setTimeout(resolve, 10) + }).then(next) + }) + app.use(function three(ctx) { + ctx.body = 'done' + }) + + agent.on('transactionFinished', (tx) => { + assertSegments( + tx.trace.root, + [ + 'WebTransaction/WebFrameworkUri/Koa/GET//', + [ + 'Nodejs/Middleware/Koa/one', + [ + 'Truncated/testSegment', + 'Nodejs/Middleware/Koa/two', + ['timers.setTimeout', ['Callback: '], 'Nodejs/Middleware/Koa/three'], + 'Truncated/nestedSegment' + ] + ] + ], + {}, + { assert: plan } + ) + }) + + run({ t, plan }) + await plan.completed +}) + +test('maintains transaction state between middleware', async (t) => { + const plan = tspl(t, { plan: 16 }) + const { agent, app } = t.nr + let tx + + app.use(async function one(ctx, next) { + tx = agent.getTransaction() + + await next() + + plan.ok(tx) + }) + + app.use(async function two(ctx, next) { + plan.equal(tx.id, agent.getTransaction().id, 'two has transaction context') + await next() + }) + + app.use(function three(ctx, next) { + plan.equal(tx.id, agent.getTransaction().id, 'three has transaction context') + return new Promise((resolve) => { + setImmediate(() => { + next().then(() => { + plan.equal( + tx.id, + agent.getTransaction().id, + 'still have context after in-context timer hop' + ) + resolve() + }) + }) + }) + }) + + app.use(function four(ctx) { + plan.equal(tx.id, agent.getTransaction().id, 'four has transaction context') + ctx.body = 'done' + }) + + agent.on('transactionFinished', function (txn) { + assertSegments( + tx.trace.root, + [ + txn.name, + [ + 'Nodejs/Middleware/Koa/one', + [ + 'Nodejs/Middleware/Koa/two', + ['Nodejs/Middleware/Koa/three', ['Nodejs/Middleware/Koa/four']] + ] + ] + ], + {}, + { assert: plan } + ) + }) + + run({ t, plan }) + await plan.completed +}) + +test('errors handled within middleware are not recorded', async (t) => { + const plan = tspl(t, { plan: 9 }) + const { agent, app } = t.nr + + app.use(function one(ctx, next) { + return next().catch(function (err) { + plan.equal(err.message, 'middleware error', 'caught expected error') + ctx.status = 200 + ctx.body = 'handled error' + }) + }) + app.use(function two(ctx) { + throw new Error('middleware error') + ctx.body = 'done' + }) + + agent.on('transactionFinished', (tx) => { + const errors = agent.errors.traceAggregator.errors + plan.equal(errors.length, 0, 'no errors are recorded') + checkSegments(plan, tx) + }) + + run({ t, expected: 'handled error', plan }) + await plan.completed +}) + +test('errors not handled by middleware are recorded', async (t) => { + const plan = tspl(t, { plan: 10 }) + const { agent, app } = t.nr + + app.use(function one(ctx, next) { + return next().catch(function (err) { + plan.equal(err.message, 'middleware error', 'caught expected error') + ctx.status = 500 + ctx.body = 'error is not actually handled' + }) + }) + app.use(function two() { + throw new Error('middleware error') + }) + + agent.on('transactionFinished', (tx) => { + const errors = agent.errors.traceAggregator.errors + plan.equal(errors.length, 1, 'recorded expected number of errors') + const error = errors[0][2] + plan.equal(error, 'middleware error', 'recorded expected error') + checkSegments(plan, tx) + }) + + run({ t, expected: 'error is not actually handled', plan }) + await plan.completed +}) + +test('errors caught by default error listener are recorded', async (t) => { + const plan = tspl(t, { plan: 10 }) + const { agent, app } = t.nr + + app.use(function one(ctx, next) { + return next() + }) + app.use(function two() { + throw new Error('middleware error') + }) + app.on('error', function (err) { + plan.equal(err.message, 'middleware error', 'caught expected error') + }) + + agent.on('transactionFinished', (tx) => { + const errors = agent.errors.traceAggregator.errors + plan.equal(errors.length, 1, 'recorded expected number of errors') + const error = errors[0][2] + plan.equal(error, 'middleware error', 'recorded expected error') + checkSegments(plan, tx) + }) + + run({ t, expected: 'Internal Server Error', plan }) + await plan.completed +}) diff --git a/test/versioned/koa/package.json b/test/versioned/koa/package.json index 070e8e87aa..904dbf149d 100644 --- a/test/versioned/koa/package.json +++ b/test/versioned/koa/package.json @@ -20,8 +20,8 @@ } }, "files": [ - "koa.tap.js", - "code-level-metrics.tap.js" + "koa.test.js", + "code-level-metrics.test.js" ] }, { @@ -39,8 +39,8 @@ } }, "files": [ - "koa-router.tap.js", - "code-level-metrics.tap.js" + "koa-router.test.js", + "code-level-metrics.test.js" ] }, { @@ -58,8 +58,8 @@ } }, "files": [ - "scoped-koa-router.tap.js", - "code-level-metrics.tap.js" + "scoped-koa-router.test.js", + "code-level-metrics.test.js" ] }, { @@ -77,7 +77,7 @@ } }, "files": [ - "koa-route.tap.js" + "koa-route.test.js" ] } ], diff --git a/test/versioned/koa/router-common.js b/test/versioned/koa/router-common.js index 9c2ef19f98..7e84aff9d0 100644 --- a/test/versioned/koa/router-common.js +++ b/test/versioned/koa/router-common.js @@ -4,7 +4,12 @@ */ 'use strict' -const fs = require('fs') + +const test = require('node:test') +const assert = require('node:assert') +const fs = require('node:fs') + +const assertSegments = require('../../lib/custom-assertions/assert-segments') /** * koa-router and @koa/router updated how they defined wildcard routing @@ -27,13 +32,12 @@ function getPathToRegexpVersion() { } module.exports = (pkg) => { - const tap = require('tap') require('../../lib/metrics_helper') const helper = require('../../lib/agent_helper') const semver = require('semver') const { run } = require('./utils') - tap.test(`${pkg} instrumentation`, (t) => { + test(`${pkg} instrumentation`, async (t) => { const { version: pkgVersion } = require(`${pkg}/package.json`) const paramMiddlewareName = 'Nodejs/Middleware/Koa/middleware//:first' const pathToRegexVersion = getPathToRegexpVersion() @@ -56,28 +60,28 @@ module.exports = (pkg) => { return spanName } - function testSetup(t) { - t.context.agent = helper.instrumentMockedAgent() + function testSetup(ctx) { + ctx.nr = {} + ctx.nr.agent = helper.instrumentMockedAgent() const Koa = require('koa') - t.context.app = new Koa() + ctx.nr.app = new Koa() const Router = require(pkg) - t.context.router = new Router() - t.context.Router = Router + ctx.nr.router = new Router() + ctx.nr.Router = Router } - function tearDown(t) { - t.context?.server?.close() - helper.unloadAgent(t.context.agent) + function tearDown(ctx) { + ctx.nr?.server?.close() + helper.unloadAgent(ctx.nr.agent) } - t.test('with single router', (t) => { + await t.test('with single router', async (t) => { t.beforeEach(testSetup) t.afterEach(tearDown) - t.autoend() - t.test('should name and produce segments for matched path', (t) => { - const { agent, router, app } = t.context + await t.test('should name and produce segments for matched path', (t, end) => { + const { agent, router, app } = t.nr router.get( '/:first', function firstMiddleware(ctx, next) { @@ -92,7 +96,7 @@ module.exports = (pkg) => { app.use(router.routes()) agent.on('transactionFinished', (tx) => { - t.assertSegments(tx.trace.root, [ + assertSegments(tx.trace.root, [ 'WebTransaction/WebFrameworkUri/Koa/GET//:first', [ 'Koa/Router: /', @@ -102,74 +106,74 @@ module.exports = (pkg) => { ] ] ]) - t.equal( + assert.equal( tx.name, 'WebTransaction/WebFrameworkUri/Koa/GET//:first', 'transaction should be named after the matched path' ) - t.end() + end() }) - run({ context: t.context }) + run({ context: t.nr }) }) - t.test('should name after matched path using middleware() alias', (t) => { - const { agent, router, app } = t.context + await t.test('should name after matched path using middleware() alias', (t, end) => { + const { agent, router, app } = t.nr router.get('/:first', function firstMiddleware(ctx) { ctx.body = 'first' }) app.use(router.middleware()) agent.on('transactionFinished', (tx) => { - t.assertSegments(tx.trace.root, [ + assertSegments(tx.trace.root, [ 'WebTransaction/WebFrameworkUri/Koa/GET//:first', ['Koa/Router: /', ['Nodejs/Middleware/Koa/firstMiddleware//:first']] ]) - t.equal( + assert.equal( tx.name, 'WebTransaction/WebFrameworkUri/Koa/GET//:first', 'transaction should be named after the matched path' ) - t.end() + end() }) - run({ context: t.context }) + run({ context: t.nr }) }) - t.test('should handle transaction state loss', (t) => { - const { agent, router, app } = t.context + await t.test('should handle transaction state loss', (t, end) => { + const { agent, router, app } = t.nr let savedCtx = null router.get('/:any', (ctx) => { savedCtx = ctx }) app.use(router.middleware()) agent.on('transactionFinished', () => { - t.doesNotThrow(() => (savedCtx._matchedRoute = 'test')) - t.end() + assert.doesNotThrow(() => (savedCtx._matchedRoute = 'test')) + end() }) - run({ context: t.context }) + run({ context: t.nr }) }) - t.test('should name and produce segments for matched regex path', (t) => { - const { agent, router, app } = t.context + await t.test('should name and produce segments for matched regex path', (t, end) => { + const { agent, router, app } = t.nr router.get(/.*rst$/, function firstMiddleware(ctx) { ctx.body = 'first' }) app.use(router.routes()) agent.on('transactionFinished', (tx) => { - t.assertSegments(tx.trace.root, [ + assertSegments(tx.trace.root, [ 'WebTransaction/WebFrameworkUri/Koa/GET//.*rst$', ['Koa/Router: /', ['Nodejs/Middleware/Koa/firstMiddleware//.*rst$/']] ]) - t.equal( + assert.equal( tx.name, 'WebTransaction/WebFrameworkUri/Koa/GET//.*rst$', 'transaction should be named after the matched regex pattern' ) - t.end() + end() }) - run({ path: '/first', context: t.context }) + run({ path: '/first', context: t.nr }) }) - t.test('should name and produce segments for matched wildcard path', (t) => { - const { agent, router, app } = t.context + await t.test('should name and produce segments for matched wildcard path', (t, end) => { + const { agent, router, app } = t.nr let path = '(.*)' if (semver.gte(pathToRegexVersion, '8.0.0')) { path = '{*any}' @@ -179,22 +183,22 @@ module.exports = (pkg) => { }) app.use(router.routes()) agent.on('transactionFinished', (tx) => { - t.assertSegments(tx.trace.root, [ + assertSegments(tx.trace.root, [ `WebTransaction/WebFrameworkUri/Koa/GET//:first/${path}`, ['Koa/Router: /', [`Nodejs/Middleware/Koa/firstMiddleware//:first/${path}`]] ]) - t.equal( + assert.equal( tx.name, `WebTransaction/WebFrameworkUri/Koa/GET//:first/${path}`, 'transaction should be named after the matched regex path' ) - t.end() + end() }) - run({ path: '/123/456', context: t.context }) + run({ path: '/123/456', context: t.nr }) }) - t.test('should name and produce segments with router paramware', (t) => { - const { agent, router, app } = t.context + await t.test('should name and produce segments with router paramware', (t, end) => { + const { agent, router, app } = t.nr router.param('first', function firstParamware(id, ctx, next) { ctx.body = 'first' return next() @@ -204,7 +208,7 @@ module.exports = (pkg) => { }) app.use(router.routes()) agent.on('transactionFinished', (tx) => { - t.assertSegments(tx.trace.root, [ + assertSegments(tx.trace.root, [ 'WebTransaction/WebFrameworkUri/Koa/GET//:first', [ 'Koa/Router: /', @@ -217,49 +221,53 @@ module.exports = (pkg) => { ] ] ]) - t.equal( + assert.equal( tx.name, 'WebTransaction/WebFrameworkUri/Koa/GET//:first', 'transaction should be named after the matched path' ) - t.end() + end() }) - run({ context: t.context }) + run({ context: t.nr }) }) - t.test('should name transaction after matched path with erroring parameware', (t) => { - const { agent, router, app } = t.context - router.param('first', function firstParamware() { - throw new Error('wrong param') - }) - router.get('/:first', function firstMiddleware() {}) + await t.test( + 'should name transaction after matched path with erroring parameware', + (t, end) => { + const { agent, router, app } = t.nr + router.param('first', function firstParamware() { + throw new Error('wrong param') + }) + router.get('/:first', function firstMiddleware() {}) - app.use(router.routes()) - agent.on('transactionFinished', (tx) => { - t.assertSegments(tx.trace.root, [ - 'WebTransaction/WebFrameworkUri/Koa/GET//:first', - [ - 'Koa/Router: /', + app.silent = true + app.use(router.routes()) + agent.on('transactionFinished', (tx) => { + assertSegments(tx.trace.root, [ + 'WebTransaction/WebFrameworkUri/Koa/GET//:first', [ - paramMiddlewareName, - ['Nodejs/Middleware/Koa/firstParamware//[param handler :first]'] + 'Koa/Router: /', + [ + paramMiddlewareName, + ['Nodejs/Middleware/Koa/firstParamware//[param handler :first]'] + ] ] - ] - ]) - const errors = agent.errors.eventAggregator - t.equal(errors.length, 1, 'the error has been recorded') - t.equal( - tx.name, - 'WebTransaction/WebFrameworkUri/Koa/GET//:first', - 'transaction should be named after the matched path' - ) - t.end() - }) - run({ context: t.context }) - }) + ]) + const errors = agent.errors.eventAggregator + assert.equal(errors.length, 1, 'the error has been recorded') + assert.equal( + tx.name, + 'WebTransaction/WebFrameworkUri/Koa/GET//:first', + 'transaction should be named after the matched path' + ) + end() + }) + run({ context: t.nr }) + } + ) - t.test('should name the transaction after the last matched path (layer)', (t) => { - const { agent, router, app } = t.context + await t.test('should name the transaction after the last matched path (layer)', (t, end) => { + const { agent, router, app } = t.nr router.get('/:first', function firstMiddleware(ctx, next) { ctx.body = 'first' return next().then(function someMoreContent() { @@ -272,7 +280,7 @@ module.exports = (pkg) => { app.use(router.routes()) agent.on('transactionFinished', (tx) => { - t.assertSegments(tx.trace.root, [ + assertSegments(tx.trace.root, [ 'WebTransaction/WebFrameworkUri/Koa/GET//:second', [ 'Koa/Router: /', @@ -282,18 +290,18 @@ module.exports = (pkg) => { ] ] ]) - t.equal( + assert.equal( tx.name, 'WebTransaction/WebFrameworkUri/Koa/GET//:second', 'transaction should be named after the matched path' ) - t.end() + end() }) - run({ context: t.context }) + run({ context: t.nr }) }) - t.test('tx name should not be named after error handling middleware', (t) => { - const { agent, router, app } = t.context + await t.test('tx name should not be named after error handling middleware', (t, end) => { + const { agent, router, app } = t.nr app.use(function errorHandler(ctx, next) { return next().catch((err) => { ctx.body = { err: err.message } @@ -306,7 +314,7 @@ module.exports = (pkg) => { app.use(router.routes()) agent.on('transactionFinished', (tx) => { - t.assertSegments(tx.trace.root, [ + assertSegments(tx.trace.root, [ 'WebTransaction/WebFrameworkUri/Koa/GET//:first', [ 'Nodejs/Middleware/Koa/errorHandler', @@ -314,19 +322,19 @@ module.exports = (pkg) => { ] ]) const errors = agent.errors.eventAggregator - t.equal(errors.length, 0, 'should not record error') - t.equal( + assert.equal(errors.length, 0, 'should not record error') + assert.equal( tx.name, 'WebTransaction/WebFrameworkUri/Koa/GET//:first', 'transaction should be named after the matched layer path' ) - t.end() + end() }) - run({ context: t.context }) + run({ context: t.nr }) }) - t.test('transaction name should not be affected by unhandled error', (t) => { - const { agent, router, app } = t.context + await t.test('transaction name should not be affected by unhandled error', (t, end) => { + const { agent, router, app } = t.nr app.use(function errorHandler(ctx, next) { return next() }) @@ -337,7 +345,7 @@ module.exports = (pkg) => { app.use(router.routes()) agent.on('transactionFinished', (tx) => { - t.assertSegments(tx.trace.root, [ + assertSegments(tx.trace.root, [ 'WebTransaction/WebFrameworkUri/Koa/GET//:first', [ 'Nodejs/Middleware/Koa/errorHandler', @@ -345,57 +353,60 @@ module.exports = (pkg) => { ] ]) const errors = agent.errors.eventAggregator - t.equal(errors.length, 1, 'error should be recorded') - t.equal( + assert.equal(errors.length, 1, 'error should be recorded') + assert.equal( tx.name, 'WebTransaction/WebFrameworkUri/Koa/GET//:first', 'transaction should be named after the matched layer path' ) - t.end() + end() }) - run({ context: t.context }) + run({ context: t.nr }) }) - t.test('should name tx after route declarations with supported http methods', (t) => { - const { agent, router, app } = t.context - // This will register the same middleware (i.e. secondMiddleware) - // under both the /:first and /:second routes. Use does not register middleware - // w/ supported methods they cannot handle routes. - router.use(['/:first', '/:second'], function secondMiddleware(ctx, next) { - ctx.body += ' second' - return next() - }) - router.get('/:second', function terminalMiddleware(ctx) { - ctx.body = ' second' - }) + await t.test( + 'should name tx after route declarations with supported http methods', + (t, end) => { + const { agent, router, app } = t.nr + // This will register the same middleware (i.e. secondMiddleware) + // under both the /:first and /:second routes. Use does not register middleware + // w/ supported methods they cannot handle routes. + router.use(['/:first', '/:second'], function secondMiddleware(ctx, next) { + ctx.body += ' second' + return next() + }) + router.get('/:second', function terminalMiddleware(ctx) { + ctx.body = ' second' + }) - const segmentTree = semver.gte(pathToRegexVersion, '8.0.0') - ? ['Nodejs/Middleware/Koa/terminalMiddleware//:second'] - : [ - 'Nodejs/Middleware/Koa/secondMiddleware//:first', - [ - 'Nodejs/Middleware/Koa/secondMiddleware//:second', - ['Nodejs/Middleware/Koa/terminalMiddleware//:second'] + const segmentTree = semver.gte(pathToRegexVersion, '8.0.0') + ? ['Nodejs/Middleware/Koa/terminalMiddleware//:second'] + : [ + 'Nodejs/Middleware/Koa/secondMiddleware//:first', + [ + 'Nodejs/Middleware/Koa/secondMiddleware//:second', + ['Nodejs/Middleware/Koa/terminalMiddleware//:second'] + ] ] - ] - app.use(router.routes()) - agent.on('transactionFinished', (tx) => { - t.assertSegments(tx.trace.root, [ - 'WebTransaction/WebFrameworkUri/Koa/GET//:second', - ['Koa/Router: /', segmentTree] - ]) - t.equal( - tx.name, - 'WebTransaction/WebFrameworkUri/Koa/GET//:second', - 'transaction should be named after the last matched path' - ) - t.end() - }) - run({ context: t.context }) - }) + app.use(router.routes()) + agent.on('transactionFinished', (tx) => { + assertSegments(tx.trace.root, [ + 'WebTransaction/WebFrameworkUri/Koa/GET//:second', + ['Koa/Router: /', segmentTree] + ]) + assert.equal( + tx.name, + 'WebTransaction/WebFrameworkUri/Koa/GET//:second', + 'transaction should be named after the last matched path' + ) + end() + }) + run({ context: t.nr }) + } + ) - t.test('names transaction (not found) with array of paths and no handler', (t) => { - const { agent, router, app } = t.context + await t.test('names transaction (not found) with array of paths and no handler', (t, end) => { + const { agent, router, app } = t.nr // This will register the same middleware (i.e. secondMiddleware) // under both the /:first and /:second routes. router.use(['/:first', '/:second'], function secondMiddleware(ctx, next) { @@ -404,24 +415,24 @@ module.exports = (pkg) => { }) app.use(router.routes()) agent.on('transactionFinished', (tx) => { - t.assertSegments(tx.trace.root, [ + assertSegments(tx.trace.root, [ 'WebTransaction/WebFrameworkUri/Koa/GET/(not found)', ['Koa/Router: /'] ]) - t.equal( + assert.equal( tx.name, 'WebTransaction/WebFrameworkUri/Koa/GET/(not found)', 'transaction should be named (not found)' ) - t.end() + end() }) - run({ context: t.context }) + run({ context: t.nr }) }) - t.test( + await t.test( 'names tx (not found) when no matching route and base middleware does not set body', - (t) => { - const { agent, router, app } = t.context + (t, end) => { + const { agent, router, app } = t.nr app.use(function baseMiddleware(ctx, next) { next() }) @@ -433,29 +444,28 @@ module.exports = (pkg) => { }) app.use(router.routes()) agent.on('transactionFinished', (tx) => { - t.assertSegments(tx.trace.root, [ + assertSegments(tx.trace.root, [ 'WebTransaction/WebFrameworkUri/Koa/GET/(not found)', ['Nodejs/Middleware/Koa/baseMiddleware', ['Koa/Router: /']] ]) - t.equal( + assert.equal( tx.name, 'WebTransaction/WebFrameworkUri/Koa/GET/(not found)', 'transaction should be named (not found)' ) - t.end() + end() }) - run({ path: '/', context: t.context }) + run({ path: '/', context: t.nr }) } ) }) - t.test('using multiple routers', (t) => { + await t.test('using multiple routers', async (t) => { t.beforeEach(testSetup) t.afterEach(tearDown) - t.autoend() - t.test('should name transaction after last route for identical matches', (t) => { - const { agent, router, app } = t.context + await t.test('should name transaction after last route for identical matches', (t, end) => { + const { agent, router, app } = t.nr const Router = require(pkg) const router2 = new Router() router.get('/:first', function firstMiddleware(ctx, next) { @@ -474,7 +484,7 @@ module.exports = (pkg) => { // the dispatch function blocking its returned promise on the // resolution of a recursively returned promise. // https://github.com/koajs/compose/blob/e754ca3c13e9248b3f453d98ea0b618e09578e2d/index.js#L42-L44 - t.assertSegments(tx.trace.root, [ + assertSegments(tx.trace.root, [ 'WebTransaction/WebFrameworkUri/Koa/GET//:second', [ 'Koa/Router: /', @@ -484,18 +494,18 @@ module.exports = (pkg) => { ] ] ]) - t.equal( + assert.equal( tx.name, 'WebTransaction/WebFrameworkUri/Koa/GET//:second', 'transaction should be named after the most specific matched path' ) - t.end() + end() }) - run({ context: t.context }) + run({ context: t.nr }) }) - t.test('should name tx after last matched route even if body not set', (t) => { - const { agent, router, app } = t.context + await t.test('should name tx after last matched route even if body not set', (t, end) => { + const { agent, router, app } = t.nr const Router = require(pkg) const router2 = new Router() router.get('/first', function firstMiddleware(ctx, next) { @@ -512,7 +522,7 @@ module.exports = (pkg) => { // the dispatch function blocking its returned promise on the // resolution of a recursively returned promise. // https://github.com/koajs/compose/blob/e754ca3c13e9248b3f453d98ea0b618e09578e2d/index.js#L42-L44 - t.assertSegments(tx.trace.root, [ + assertSegments(tx.trace.root, [ 'WebTransaction/WebFrameworkUri/Koa/GET//:second', [ 'Koa/Router: /', @@ -522,24 +532,23 @@ module.exports = (pkg) => { ] ] ]) - t.equal( + assert.equal( tx.name, 'WebTransaction/WebFrameworkUri/Koa/GET//:second', 'transaction should be named after the last matched path' ) - t.end() + end() }) - run({ path: '/first', context: t.context }) + run({ path: '/first', context: t.nr }) }) }) - t.test('using nested or prefixed routers', (t) => { + await t.test('using nested or prefixed routers', async (t) => { t.beforeEach(testSetup) t.afterEach(tearDown) - t.autoend() - t.test('should name after most last matched path', (t) => { - const { agent, router, Router, app } = t.context + await t.test('should name after most last matched path', (t, end) => { + const { agent, router, Router, app } = t.nr const router2 = new Router() router2.get('/:second', function secondMiddleware(ctx) { ctx.body = ' second' @@ -547,22 +556,22 @@ module.exports = (pkg) => { router.use('/:first', router2.routes()) app.use(router.routes()) agent.on('transactionFinished', (tx) => { - t.assertSegments(tx.trace.root, [ + assertSegments(tx.trace.root, [ 'WebTransaction/WebFrameworkUri/Koa/GET//:first/:second', ['Koa/Router: /', [getNestedSpanName('secondMiddleware')]] ]) - t.equal( + assert.equal( tx.name, 'WebTransaction/WebFrameworkUri/Koa/GET//:first/:second', 'transaction should be named after the last matched path' ) - t.end() + end() }) - run({ path: '/123/456/', context: t.context }) + run({ path: '/123/456/', context: t.nr }) }) - t.test('app-level middleware should not rename tx from matched path', (t) => { - const { agent, router, Router, app } = t.context + await t.test('app-level middleware should not rename tx from matched path', (t, end) => { + const { agent, router, Router, app } = t.nr app.use(function appLevelMiddleware(ctx, next) { return next().then(() => { ctx.body = 'do not want this to set the name' @@ -577,157 +586,165 @@ module.exports = (pkg) => { app.use(router.routes()) agent.on('transactionFinished', (tx) => { - t.assertSegments(tx.trace.root, [ + assertSegments(tx.trace.root, [ 'WebTransaction/WebFrameworkUri/Koa/GET//:first/:second', [ 'Nodejs/Middleware/Koa/appLevelMiddleware', ['Koa/Router: /', [getNestedSpanName('terminalMiddleware')]] ] ]) - t.equal( + assert.equal( tx.name, 'WebTransaction/WebFrameworkUri/Koa/GET//:first/:second', 'should be named after last matched route' ) - t.end() + end() }) - run({ path: '/123/second', context: t.context }) + run({ path: '/123/second', context: t.nr }) }) - t.test('app-level middleware should not rename tx from matched prefix path', (t) => { - const { agent, router, app } = t.context - app.use(function appLevelMiddleware(ctx, next) { - return next().then(() => { - ctx.body = 'do not want this to set the name' + await t.test( + 'app-level middleware should not rename tx from matched prefix path', + (t, end) => { + const { agent, router, app } = t.nr + app.use(function appLevelMiddleware(ctx, next) { + return next().then(() => { + ctx.body = 'do not want this to set the name' + }) }) - }) - router.get('/:second', function terminalMiddleware(ctx) { - ctx.body = 'this is a test' - }) - router.prefix('/:first') - app.use(router.routes()) + router.get('/:second', function terminalMiddleware(ctx) { + ctx.body = 'this is a test' + }) + router.prefix('/:first') + app.use(router.routes()) - agent.on('transactionFinished', (tx) => { - t.assertSegments(tx.trace.root, [ - 'WebTransaction/WebFrameworkUri/Koa/GET//:first/:second', - [ - 'Nodejs/Middleware/Koa/appLevelMiddleware', - ['Koa/Router: /', ['Nodejs/Middleware/Koa/terminalMiddleware//:first/:second']] - ] - ]) - t.equal( - tx.name, - 'WebTransaction/WebFrameworkUri/Koa/GET//:first/:second', - 'should be named after the last matched path' - ) - t.end() - }) - run({ path: '/123/second', context: t.context }) - }) + agent.on('transactionFinished', (tx) => { + assertSegments(tx.trace.root, [ + 'WebTransaction/WebFrameworkUri/Koa/GET//:first/:second', + [ + 'Nodejs/Middleware/Koa/appLevelMiddleware', + ['Koa/Router: /', ['Nodejs/Middleware/Koa/terminalMiddleware//:first/:second']] + ] + ]) + assert.equal( + tx.name, + 'WebTransaction/WebFrameworkUri/Koa/GET//:first/:second', + 'should be named after the last matched path' + ) + end() + }) + run({ path: '/123/second', context: t.nr }) + } + ) }) - t.test('using allowedMethods', (t) => { + await t.test('using allowedMethods', async (t) => { // `@koa/router@13.0.0` changed the allowedMethods middleware function from named to arrow function // update span name for assertions const allowedMethodsFnName = semver.gte(pkgVersion, '13.0.0') ? '' : 'allowedMethods' - t.autoend() - t.test('with throw: true', (t) => { + await t.test('with throw: true', async (t) => { t.beforeEach(testSetup) t.afterEach(tearDown) - t.autoend() - t.test('should name transaction after status `method now allowed` message', (t) => { - const { agent, router, app } = t.context - router.post('/:first', function firstMiddleware() {}) - app.use(router.routes()) - app.use(router.allowedMethods({ throw: true })) - agent.on('transactionFinished', (tx) => { - t.assertSegments(tx.trace.root, [ - 'WebTransaction/WebFrameworkUri/Koa/GET/(method not allowed)', - ['Koa/Router: /', [`Nodejs/Middleware/Koa/${allowedMethodsFnName}`]] - ]) - t.equal( - tx.name, - 'WebTransaction/WebFrameworkUri/Koa/GET/(method not allowed)', - 'transaction should be named after corresponding status code message' - ) - const errors = agent.errors.eventAggregator - t.equal(errors.length, 1, 'the error has been recorded') - t.end() - }) - run({ context: t.context }) - }) + await t.test( + 'should name transaction after status `method now allowed` message', + (t, end) => { + const { agent, router, app } = t.nr + router.post('/:first', function firstMiddleware() {}) + app.use(router.routes()) + app.use(router.allowedMethods({ throw: true })) + agent.on('transactionFinished', (tx) => { + assertSegments(tx.trace.root, [ + 'WebTransaction/WebFrameworkUri/Koa/GET/(method not allowed)', + ['Koa/Router: /', [`Nodejs/Middleware/Koa/${allowedMethodsFnName}`]] + ]) + assert.equal( + tx.name, + 'WebTransaction/WebFrameworkUri/Koa/GET/(method not allowed)', + 'transaction should be named after corresponding status code message' + ) + const errors = agent.errors.eventAggregator + assert.equal(errors.length, 1, 'the error has been recorded') + end() + }) + run({ context: t.nr }) + } + ) - t.test('should name transaction after status `not implemented` message', (t) => { - const { agent, Router, app } = t.context + await t.test('should name transaction after status `not implemented` message', (t, end) => { + const { agent, Router, app } = t.nr const router = new Router({ methods: ['POST'] }) router.post('/:first', function firstMiddleware() {}) + app.silent = true app.use(router.routes()) app.use(router.allowedMethods({ throw: true })) agent.on('transactionFinished', (tx) => { - t.assertSegments(tx.trace.root, [ + assertSegments(tx.trace.root, [ 'WebTransaction/WebFrameworkUri/Koa/GET/(not implemented)', ['Koa/Router: /', [`Nodejs/Middleware/Koa/${allowedMethodsFnName}`]] ]) - t.equal( + assert.equal( tx.name, 'WebTransaction/WebFrameworkUri/Koa/GET/(not implemented)', 'transaction should be named after corresponding status code message' ) const errors = agent.errors.eventAggregator - t.equal(errors.length, 1, 'the error has been recorded') - t.end() + assert.equal(errors.length, 1, 'the error has been recorded') + end() }) - run({ context: t.context }) - }) - - t.test('error handler normalizes tx name if body is reset without status', (t) => { - const { agent, router, Router, app } = t.context - app.use(function errorHandler(ctx, next) { - return next().catch(() => { - // resetting the body without manually persisting ctx.status - // results in status 200 - ctx.body = { msg: 'error is handled' } + run({ context: t.nr }) + }) + + await t.test( + 'error handler normalizes tx name if body is reset without status', + (t, end) => { + const { agent, router, Router, app } = t.nr + app.use(function errorHandler(ctx, next) { + return next().catch(() => { + // resetting the body without manually persisting ctx.status + // results in status 200 + ctx.body = { msg: 'error is handled' } + }) }) - }) - const nestedRouter = new Router() - nestedRouter.post('/:second', function terminalMiddleware(ctx) { - ctx.body = 'would want this to set name if verb were correct' - }) - router.use('/:first', nestedRouter.routes(), nestedRouter.allowedMethods()) - app.use(router.routes()) - app.use(router.allowedMethods({ throw: true })) + const nestedRouter = new Router() + nestedRouter.post('/:second', function terminalMiddleware(ctx) { + ctx.body = 'would want this to set name if verb were correct' + }) + router.use('/:first', nestedRouter.routes(), nestedRouter.allowedMethods()) + app.use(router.routes()) + app.use(router.allowedMethods({ throw: true })) - agent.on('transactionFinished', (tx) => { - t.assertSegments(tx.trace.root, [ - 'WebTransaction/NormalizedUri/*', - [ - 'Nodejs/Middleware/Koa/errorHandler', - ['Koa/Router: /', [`Nodejs/Middleware/Koa/${allowedMethodsFnName}`]] - ] - ]) - t.equal( - tx.name, - 'WebTransaction/NormalizedUri/*', - 'should have normalized transaction name' - ) - const errors = agent.errors.eventAggregator - t.equal(errors.length, 0, 'error should not be recorded') - t.end() - }) - run({ path: '/123/456', context: t.context }) - }) + agent.on('transactionFinished', (tx) => { + assertSegments(tx.trace.root, [ + 'WebTransaction/NormalizedUri/*', + [ + 'Nodejs/Middleware/Koa/errorHandler', + ['Koa/Router: /', [`Nodejs/Middleware/Koa/${allowedMethodsFnName}`]] + ] + ]) + assert.equal( + tx.name, + 'WebTransaction/NormalizedUri/*', + 'should have normalized transaction name' + ) + const errors = agent.errors.eventAggregator + assert.equal(errors.length, 0, 'error should not be recorded') + end() + }) + run({ path: '/123/456', context: t.nr }) + } + ) - t.test( + await t.test( 'should name tx after status message when base middleware does not set body', - (t) => { - const { agent, router, Router, app } = t.context + (t, end) => { + const { agent, router, Router, app } = t.nr // Because allowedMethods throws & no user catching, it is considered // unhandled and will push the base route back on app.use(function baseMiddleware(ctx, next) { @@ -744,82 +761,84 @@ module.exports = (pkg) => { app.use(router.allowedMethods({ throw: true })) agent.on('transactionFinished', (tx) => { - t.assertSegments(tx.trace.root, [ + assertSegments(tx.trace.root, [ 'WebTransaction/WebFrameworkUri/Koa/GET/(method not allowed)', [ 'Nodejs/Middleware/Koa/baseMiddleware', ['Koa/Router: /', [`Nodejs/Middleware/Koa/${allowedMethodsFnName}`]] ] ]) - t.equal( + assert.equal( tx.name, 'WebTransaction/WebFrameworkUri/Koa/GET/(method not allowed)', 'should name after returned status code' ) const errors = agent.errors.eventAggregator - t.equal(errors.length, 1, 'should notice thrown error') + assert.equal(errors.length, 1, 'should notice thrown error') - t.end() + end() }) - run({ path: '/123/456', context: t.context }) + run({ path: '/123/456', context: t.nr }) } ) }) - t.test('with throw: false', (t) => { + await t.test('with throw: false', async (t) => { t.beforeEach(testSetup) t.afterEach(tearDown) - t.autoend() - t.test('should name transaction after status `method now allowed` message', (t) => { - const { agent, router, app } = t.context - router.post('/:first', function firstMiddleware() {}) - app.use(router.routes()) - app.use(router.allowedMethods()) - agent.on('transactionFinished', (tx) => { - t.assertSegments(tx.trace.root, [ - 'WebTransaction/WebFrameworkUri/Koa/GET/(method not allowed)', - ['Koa/Router: /', [`Nodejs/Middleware/Koa/${allowedMethodsFnName}`]] - ]) - t.equal( - tx.name, - 'WebTransaction/WebFrameworkUri/Koa/GET/(method not allowed)', - 'transaction should be named after corresponding status code message' - ) - // Agent will automatically create error for 405 status code. - const errors = agent.errors.eventAggregator - t.equal(errors.length, 1, 'the error has been recorded') - t.end() - }) - run({ context: t.context }) - }) + await t.test( + 'should name transaction after status `method now allowed` message', + (t, end) => { + const { agent, router, app } = t.nr + router.post('/:first', function firstMiddleware() {}) + app.use(router.routes()) + app.use(router.allowedMethods()) + agent.on('transactionFinished', (tx) => { + assertSegments(tx.trace.root, [ + 'WebTransaction/WebFrameworkUri/Koa/GET/(method not allowed)', + ['Koa/Router: /', [`Nodejs/Middleware/Koa/${allowedMethodsFnName}`]] + ]) + assert.equal( + tx.name, + 'WebTransaction/WebFrameworkUri/Koa/GET/(method not allowed)', + 'transaction should be named after corresponding status code message' + ) + // Agent will automatically create error for 405 status code. + const errors = agent.errors.eventAggregator + assert.equal(errors.length, 1, 'the error has been recorded') + end() + }) + run({ context: t.nr }) + } + ) - t.test('should name transaction after status `not implemented` message', (t) => { - const { agent, app, Router } = t.context + await t.test('should name transaction after status `not implemented` message', (t, end) => { + const { agent, app, Router } = t.nr const router = new Router({ methods: ['POST'] }) router.post('/:first', function firstMiddleware() {}) app.use(router.routes()) app.use(router.allowedMethods()) agent.on('transactionFinished', (tx) => { - t.assertSegments(tx.trace.root, [ + assertSegments(tx.trace.root, [ 'WebTransaction/WebFrameworkUri/Koa/GET/(not implemented)', ['Koa/Router: /', [`Nodejs/Middleware/Koa/${allowedMethodsFnName}`]] ]) - t.equal( + assert.equal( tx.name, 'WebTransaction/WebFrameworkUri/Koa/GET/(not implemented)', 'transaction should be named after corresponding status code message' ) // Agent will automatically create error for 501 status code. const errors = agent.errors.eventAggregator - t.equal(errors.length, 1, 'the error has been recorded') - t.end() + assert.equal(errors.length, 1, 'the error has been recorded') + end() }) - run({ context: t.context }) + run({ context: t.nr }) }) - t.test('should name tx after `method not allowed` with prefixed router', (t) => { - const { agent, router, app } = t.context + await t.test('should name tx after `method not allowed` with prefixed router', (t, end) => { + const { agent, router, app } = t.nr app.use(function appLevelMiddleware(ctx, next) { return next().then(() => { ctx.body = 'should not set the name' @@ -833,25 +852,25 @@ module.exports = (pkg) => { app.use(router.allowedMethods()) agent.on('transactionFinished', (tx) => { - t.assertSegments(tx.trace.root, [ + assertSegments(tx.trace.root, [ 'WebTransaction/WebFrameworkUri/Koa/GET/(method not allowed)', [ 'Nodejs/Middleware/Koa/appLevelMiddleware', ['Koa/Router: /', [`Nodejs/Middleware/Koa/${allowedMethodsFnName}`]] ] ]) - t.equal( + assert.equal( tx.name, 'WebTransaction/WebFrameworkUri/Koa/GET/(method not allowed)', 'transaction should be named after corresponding status code message' ) - t.end() + end() }) - run({ path: '/123/second', context: t.context }) + run({ path: '/123/second', context: t.nr }) }) - t.test('should name tx after `not implemented` with prefixed router', (t) => { - const { agent, app, Router } = t.context + await t.test('should name tx after `not implemented` with prefixed router', (t, end) => { + const { agent, app, Router } = t.nr const router = new Router({ methods: ['POST'] }) app.use(function appLevelMiddleware(ctx, next) { @@ -867,25 +886,25 @@ module.exports = (pkg) => { app.use(router.allowedMethods()) agent.on('transactionFinished', (tx) => { - t.assertSegments(tx.trace.root, [ + assertSegments(tx.trace.root, [ 'WebTransaction/WebFrameworkUri/Koa/GET/(not implemented)', [ 'Nodejs/Middleware/Koa/appLevelMiddleware', ['Koa/Router: /', [`Nodejs/Middleware/Koa/${allowedMethodsFnName}`]] ] ]) - t.equal( + assert.equal( tx.name, 'WebTransaction/WebFrameworkUri/Koa/GET/(not implemented)', 'transaction should be named after corresponding status code message' ) - t.end() + end() }) - run({ path: '/123/first', context: t.context }) + run({ path: '/123/first', context: t.nr }) }) - t.test('should name and produce segments for existing matched path', (t) => { - const { agent, app, Router } = t.context + await t.test('should name and produce segments for existing matched path', (t, end) => { + const { agent, app, Router } = t.nr const router = new Router({ methods: ['GET'] }) router.get('/:first', function firstMiddleware(ctx) { ctx.body = 'first' @@ -893,21 +912,20 @@ module.exports = (pkg) => { app.use(router.routes()) app.use(router.allowedMethods()) agent.on('transactionFinished', (tx) => { - t.assertSegments(tx.trace.root, [ + assertSegments(tx.trace.root, [ 'WebTransaction/WebFrameworkUri/Koa/GET//:first', ['Koa/Router: /', ['Nodejs/Middleware/Koa/firstMiddleware//:first']] ]) - t.equal( + assert.equal( tx.name, 'WebTransaction/WebFrameworkUri/Koa/GET//:first', 'transaction should be named after the matched path' ) - t.end() + end() }) - run({ context: t.context }) + run({ context: t.nr }) }) }) }) - t.end() }) } diff --git a/test/versioned/koa/scoped-koa-router.tap.js b/test/versioned/koa/scoped-koa-router.test.js similarity index 100% rename from test/versioned/koa/scoped-koa-router.tap.js rename to test/versioned/koa/scoped-koa-router.test.js