From 4e54199972f882165395eaf2b08c050421e25815 Mon Sep 17 00:00:00 2001 From: James Sumners Date: Wed, 31 Jul 2024 09:30:39 -0400 Subject: [PATCH 1/2] chore: Converted agent unit tests to node:test --- THIRD_PARTY_NOTICES.md | 19 +- package.json | 3 +- test/lib/fake-cert.js | 17 + test/lib/promise-resolvers.js | 28 + test/lib/test-collector.js | 194 ++++ test/unit/agent/agent.test.js | 1532 +++++++++++++--------------- test/unit/agent/intrinsics.test.js | 266 +++-- test/unit/agent/synthetics.test.js | 42 +- third_party_manifest.json | 41 +- 9 files changed, 1129 insertions(+), 1013 deletions(-) create mode 100644 test/lib/fake-cert.js create mode 100644 test/lib/promise-resolvers.js create mode 100644 test/lib/test-collector.js diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md index f345a1748a..0800ef20f8 100644 --- a/THIRD_PARTY_NOTICES.md +++ b/THIRD_PARTY_NOTICES.md @@ -71,6 +71,7 @@ code, the source code can be found at [https://github.com/newrelic/node-newrelic * [proxyquire](#proxyquire) * [rfdc](#rfdc) * [rimraf](#rimraf) +* [self-cert](#self-cert) * [should](#should) * [sinon](#sinon) * [superagent](#superagent) @@ -4023,6 +4024,22 @@ IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ``` +### self-cert + +This product includes source derived from [self-cert](https://github.com/jsumners/self-cert) ([v2.0.0](https://github.com/jsumners/self-cert/tree/v2.0.0)), distributed under the [MIT License](https://github.com/jsumners/self-cert/blob/v2.0.0/Readme.md): + +``` +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +``` + ### should This product includes source derived from [should](https://github.com/shouldjs/should.js) ([v13.2.3](https://github.com/shouldjs/should.js/tree/v13.2.3)), distributed under the [MIT License](https://github.com/shouldjs/should.js/blob/v13.2.3/LICENSE): @@ -4053,7 +4070,7 @@ THE SOFTWARE. ### sinon -This product includes source derived from [sinon](https://github.com/sinonjs/sinon) ([v4.5.0](https://github.com/sinonjs/sinon/tree/v4.5.0)), distributed under the [BSD-3-Clause License](https://github.com/sinonjs/sinon/blob/v4.5.0/LICENSE): +This product includes source derived from [sinon](https://github.com/sinonjs/sinon) ([v5.1.1](https://github.com/sinonjs/sinon/tree/v5.1.1)), distributed under the [BSD-3-Clause License](https://github.com/sinonjs/sinon/blob/v5.1.1/LICENSE): ``` (The BSD License) diff --git a/package.json b/package.json index c9940980d1..92686b3af7 100644 --- a/package.json +++ b/package.json @@ -256,8 +256,9 @@ "proxyquire": "^1.8.0", "rfdc": "^1.3.1", "rimraf": "^2.6.3", + "self-cert": "^2.0.0", "should": "*", - "sinon": "^4.5.0", + "sinon": "^5.1.1", "superagent": "^9.0.1", "tap": "^16.3.4", "temp": "^0.8.1" diff --git a/test/lib/fake-cert.js b/test/lib/fake-cert.js new file mode 100644 index 0000000000..fc9fd8a48c --- /dev/null +++ b/test/lib/fake-cert.js @@ -0,0 +1,17 @@ +/* + * Copyright 2024 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +'use strict' + +const selfCert = require('self-cert') +module.exports = selfCert({ + attrs: { + stateName: 'Georgia', + locality: 'Atlanta', + orgName: 'New Relic', + shortName: 'new_relic' + }, + expires: new Date('2099-12-31') +}) diff --git a/test/lib/promise-resolvers.js b/test/lib/promise-resolvers.js new file mode 100644 index 0000000000..01da435da7 --- /dev/null +++ b/test/lib/promise-resolvers.js @@ -0,0 +1,28 @@ +/* + * Copyright 2024 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +'use strict' + +/** + * Implements https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/withResolvers + * + * This can be removed once Node.js v22 is the minimum. + * + * @returns {{resolve, reject, promise: Promise}} + */ +module.exports = function promiseResolvers() { + if (typeof Promise.withResolvers === 'function') { + // Node.js >=22 natively supports this. + return Promise.withResolvers() + } + + let resolve + let reject + const promise = new Promise((a, b) => { + resolve = a + reject = b + }) + return { promise, resolve, reject } +} diff --git a/test/lib/test-collector.js b/test/lib/test-collector.js new file mode 100644 index 0000000000..797fa5bf66 --- /dev/null +++ b/test/lib/test-collector.js @@ -0,0 +1,194 @@ +/* + * Copyright 2024 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +'use strict' + +// This provides an in-process http server to use in place of +// collector.newrelic.com. It allows for custom handlers so that test specific +// assertions can be made. + +const https = require('node:https') +const querystring = require('node:querystring') +const helper = require('./agent_helper') +const fakeCert = require('./fake-cert') + +class Collector { + #handlers = new Map() + #server + #address + + constructor() { + this.#server = https.createServer({ + key: fakeCert.privateKey, + cert: fakeCert.certificate + }) + this.#server.on('request', (req, res) => { + const qs = querystring.decode(req.url.slice(req.url.indexOf('?') + 1)) + const handler = this.#handlers.get(qs.method) + if (typeof handler !== 'function') { + res.writeHead(500) + return res.end('handler not found: ' + req.url) + } + + res.json = function ({ payload, code = 200 }) { + this.writeHead(code, { 'content-type': 'application/json' }) + this.end(JSON.stringify(payload)) + } + + handler.isDone = true + handler(req, res) + }) + + // We don't need this server keeping the process alive. + this.#server.unref() + } + + /** + * A configuration object that can be passed to an "agent" instance so that + * the agent will communicate with this test server instead of the real + * server. + * + * Important: the `.listen` method must be invoked first in order to have + * the `host` and `port` defined. + * + * @returns {object} + */ + get agentConfig() { + return { + host: this.host, + port: this.port, + license_key: 'testing', + certificates: [this.cert] + } + } + + /** + * The host the server is listening on. + * + * @returns {string} + */ + get host() { + return this.#address?.address + } + + /** + * The port number the server is listening on. + * + * @returns {number} + */ + get port() { + return this.#address?.port + } + + /** + * A copy of the public certificate used to secure the server. Use this + * like `new Agent({ certificates: [collector.cert] })`. + * + * @returns {string} + */ + get cert() { + return fakeCert.certificate + } + + /** + * The most basic `agent_settings` handler. Useful when you do not need to + * customize the handler. + * + * @returns {function} + */ + get agentSettingsHandler() { + return function (req, res) { + res.json({ payload: { return_value: [] } }) + } + } + + /** + * The most basic `preconnect` handler. Useful when you do not need to + * customize the handler. + * + * @returns {function} + */ + get preconnectHandler() { + const host = this.host + const port = this.port + return function (req, res) { + res.json({ + payload: { + return_value: { + redirect_host: `${host}:${port}`, + security_policies: {} + } + } + }) + } + } + + /** + * Adds a new handler for the provided endpoint. + * + * @param {string} endpoint A string like + * `/agent_listener/invoke_raw_method?method=preconnect`. Notice that a query + * string with the `method` parameter is present. This is required, as the + * value of `method` will be used to look up the handler when receiving + * requests. + * @param {function} handler A typical `(req, res) => {}` handler. For + * convenience, `res` is extended with a `json({ payload, code = 200 })` + * method for easily sending JSON responses. + */ + addHandler(endpoint, handler) { + const qs = querystring.decode(endpoint.slice(endpoint.indexOf('?') + 1)) + this.#handlers.set(qs.method, handler) + } + + /** + * Shutdown the server and forcefully close all current connections. + */ + close() { + this.#server.closeAllConnections() + } + + /** + * Determine if a handler has been invoked. + * + * @param {string} method Name of the method to check, e.g. "preconnect". + * @returns {boolean} + */ + isDone(method) { + return this.#handlers.get(method)?.isDone === true + } + + /** + * Start the server listening for requests. + * + * @returns {Promise} Returns a standard server address object. + */ + async listen() { + let address + await new Promise((resolve, reject) => { + this.#server.listen(0, '127.0.0.1', (err) => { + if (err) { + return reject(err) + } + address = this.#server.address() + resolve() + }) + }) + + this.#address = address + + // Add handlers for the required agent startup connections. These should + // be overwritten by tests that exercise the startup phase, but adding these + // stubs makes it easier to test other connection events. + this.addHandler(helper.generateCollectorPath('preconnect', 42), this.preconnectHandler) + this.addHandler(helper.generateCollectorPath('connect', 42), (req, res) => { + res.json({ payload: { return_value: { agent_run_id: 42 } } }) + }) + this.addHandler(helper.generateCollectorPath('agent_settings', 42), this.agentSettingsHandler) + + return address + } +} + +module.exports = Collector diff --git a/test/unit/agent/agent.test.js b/test/unit/agent/agent.test.js index c2d1c40aa0..62106fe92a 100644 --- a/test/unit/agent/agent.test.js +++ b/test/unit/agent/agent.test.js @@ -5,9 +5,12 @@ 'use strict' -const tap = require('tap') +const test = require('node:test') +const assert = require('node:assert') +const promiseResolvers = require('../../lib/promise-resolvers') +const Collector = require('../../lib/test-collector') + const sinon = require('sinon') -const nock = require('nock') const helper = require('../../lib/agent_helper') const sampler = require('../../../lib/sampler') const configurator = require('../../../lib/config') @@ -16,643 +19,562 @@ const Transaction = require('../../../lib/transaction') const CollectorResponse = require('../../../lib/collector/response') const RUN_ID = 1337 -const URL = 'https://collector.newrelic.com' -tap.test('should require configuration passed to constructor', (t) => { - t.throws(() => new Agent()) - t.end() +test('should require configuration passed to constructor', () => { + assert.throws(() => new Agent()) }) -tap.test('should not throw with valid config', (t) => { +test('should not throw with valid config', () => { const config = configurator.initialize({ agent_enabled: false }) const agent = new Agent(config) - - t.notOk(agent.config.agent_enabled) - t.end() + assert.equal(agent.config.agent_enabled, false) }) -tap.test('when loaded with defaults', (t) => { - t.autoend() - - let agent = null - - t.beforeEach(() => { - // Load agent with default 'stopped' state - agent = helper.loadMockedAgent(null, false) +test('when loaded with defaults', async (t) => { + t.beforeEach((ctx) => { + ctx.nr = {} + // Load agent with default 'stopped' state. + ctx.nr.agent = helper.loadMockedAgent(null, false) }) - t.afterEach(() => { - helper.unloadAgent(agent) - agent = null + t.afterEach((ctx) => { + helper.unloadAgent(ctx.nr.agent) }) - t.test('bootstraps its configuration', (t) => { - t.ok(agent.config) - t.end() + await t.test('bootstraps its configuration', (t) => { + const { agent } = t.nr + assert.equal(Object.hasOwn(agent, 'config'), true) }) - t.test('has an error tracer', (t) => { - t.ok(agent.errors) - t.end() + await t.test('has error tracer', (t) => { + const { agent } = t.nr + assert.equal(Object.hasOwn(agent, 'errors'), true) }) - t.test('has query tracer', (t) => { - t.ok(agent.queries) - t.end() + await t.test('has query tracer', (t) => { + const { agent } = t.nr + assert.equal(Object.hasOwn(agent, 'queries'), true) }) - t.test('uses an aggregator to apply top N slow trace logic', (t) => { - t.ok(agent.traces) - t.end() + await t.test('uses an aggregator to apply top N slow trace logic', (t) => { + const { agent } = t.nr + assert.equal(Object.hasOwn(agent, 'traces'), true) }) - t.test('has a URL normalizer', (t) => { - t.ok(agent.urlNormalizer) - t.end() + await t.test('has URL normalizer', (t) => { + const { agent } = t.nr + assert.equal(Object.hasOwn(agent, 'urlNormalizer'), true) }) - t.test('has a metric name normalizer', (t) => { - t.ok(agent.metricNameNormalizer) - t.end() + await t.test('has a metric name normalizer', (t) => { + const { agent } = t.nr + assert.equal(Object.hasOwn(agent, 'metricNameNormalizer'), true) }) - t.test('has a transaction name normalizer', (t) => { - t.ok(agent.transactionNameNormalizer) - t.end() + await t.test('has a transaction name normalizer', (t) => { + const { agent } = t.nr + assert.equal(Object.hasOwn(agent, 'transactionNameNormalizer'), true) }) - t.test('has a consolidated metrics collection that transactions feed into', (t) => { - t.ok(agent.metrics) - t.end() + await t.test('has a consolidated metrics collection that transactions feed into', (t) => { + const { agent } = t.nr + assert.equal(Object.hasOwn(agent, 'metrics'), true) }) - t.test('has a function to look up the active transaction', (t) => { - t.ok(agent.getTransaction) - // should not throw + await t.test('has a function to look up the active transaction', (t) => { + const { agent } = t.nr + assert.equal(typeof agent.getTransaction === 'function', true) + // Should not throw: agent.getTransaction() - - t.end() }) - t.test('requires new configuration to reconfigure the agent', (t) => { - t.throws(() => agent.reconfigure()) - t.end() + await t.test('requires new configuration to reconfigure the agent', (t) => { + const { agent } = t.nr + assert.throws(() => agent.reconfigure()) }) - t.test('defaults to a state of `stopped`', (t) => { - t.equal(agent._state, 'stopped') - t.end() + await t.test('defaults to a state of "stopped"', (t) => { + const { agent } = t.nr + assert.equal(agent._state, 'stopped') }) - t.test('requires a valid value when changing state', (t) => { - t.throws(() => agent.setState('bogus'), new Error('Invalid state bogus')) - t.end() + await t.test('requires a valid value when changing state', (t) => { + const { agent } = t.nr + assert.throws(() => agent.setState('bogus'), /Invalid state bogus/) }) - t.test('has some debugging configuration by default', (t) => { - t.ok(agent.config.debug) - t.end() + await t.test('has some debugging configuration by default', (t) => { + const { agent } = t.nr + assert.equal(Object.hasOwn(agent.config, 'debug'), true) }) }) -tap.test('should load naming rules when configured', (t) => { +test('should load naming rules when configured', () => { const config = configurator.initialize({ rules: { name: [ { pattern: '^/t', name: 'u' }, - { pattern: /^\/u/, name: 't' } + { pattern: '/^/u/', name: 't' } ] } }) - const configured = new Agent(config) - const rules = configured.userNormalizer.rules - tap.equal(rules.length, 2 + 1) // +1 default ignore rule - // Rules are reversed by default - t.equal(rules[2].pattern.source, '^\\/u') - - t.equal(rules[1].pattern.source, '^\\/t') - - t.end() + assert.equal(rules.length, 2 + 1) // +1 default ignore rule + // Rules are reversed by default: + assert.equal(rules[2].pattern.source, '\\/^\\/u\\/') + assert.equal(rules[1].pattern.source, '^\\/t') }) -tap.test('should load ignoring rules when configured', (t) => { +test('should load ignoring rules when configured', () => { const config = configurator.initialize({ rules: { ignore: [/^\/ham_snadwich\/ignore/] } }) - const configured = new Agent(config) - const rules = configured.userNormalizer.rules - t.equal(rules.length, 1) - t.equal(rules[0].pattern.source, '^\\/ham_snadwich\\/ignore') - t.equal(rules[0].ignore, true) - t.end() + assert.equal(rules.length, 1) + assert.equal(rules[0].pattern.source, '^\\/ham_snadwich\\/ignore') + assert.equal(rules[0].ignore, true) }) -tap.test('when forcing transaction ignore status', (t) => { - t.autoend() - - let agentInstance = null - - t.beforeEach(() => { +test('when forcing transaction ignore status', async (t) => { + t.beforeEach((ctx) => { + ctx.nr = {} const config = configurator.initialize({ rules: { ignore: [/^\/ham_snadwich\/ignore/] } }) - agentInstance = new Agent(config) + ctx.nr.agent = new Agent(config) }) - t.afterEach(() => { - agentInstance = null - }) + await t.test('should not error when forcing an ignore', (t) => { + const { agent } = t.nr + const tx = new Transaction(agent) + tx.forceIgnore = true + tx.finalizeNameFromUri('/ham_snadwich/attend', 200) - t.test('should not error when forcing an ignore', (t) => { - const transaction = new Transaction(agentInstance) - transaction.forceIgnore = true - transaction.finalizeNameFromUri('/ham_snadwich/attend', 200) - t.equal(transaction.ignore, true) - - // should not throw - transaction.end() - - t.end() + assert.equal(tx.ignore, true) + // Should not throw: + tx.end() }) - t.test('should not error when forcing a non-ignore', (t) => { - const transaction = new Transaction(agentInstance) - transaction.forceIgnore = false - transaction.finalizeNameFromUri('/ham_snadwich/ignore', 200) - t.equal(transaction.ignore, false) + await t.test('should not error when forcing a non-ignore', (t) => { + const { agent } = t.nr + const tx = new Transaction(agent) + tx.forceIgnore = false + tx.finalizeNameFromUri('/ham_snadwich/ignore', 200) - // should not throw - transaction.end() - - t.end() + assert.equal(tx.ignore, false) + // Should not throw: + tx.end() }) - t.test('should ignore when finalizeNameFromUri is not called', (t) => { - const transaction = new Transaction(agentInstance) - transaction.forceIgnore = true - agentInstance._transactionFinished(transaction) - t.equal(transaction.ignore, true) - - t.end() + await t.test('should ignore when finalizeNameFromUri is not called', (t) => { + const { agent } = t.nr + const tx = new Transaction(agent) + tx.forceIgnore = true + agent._transactionFinished(tx) + assert.equal(tx.ignore, true) }) }) -tap.test('#harvest.start should start all aggregators', (t) => { - // Load agent with default 'stopped' state +test('#harvesters.start should start all aggregators', (t) => { const agent = helper.loadMockedAgent(null, false) - agent.config.application_logging.forwarding.enabled = true - - t.teardown(() => { + t.after(() => { helper.unloadAgent(agent) }) agent.harvester.start() - - t.ok(agent.traces.sendTimer) - t.ok(agent.errors.traceAggregator.sendTimer) - t.ok(agent.errors.eventAggregator.sendTimer) - t.ok(agent.spanEventAggregator.sendTimer) - t.ok(agent.transactionEventAggregator.sendTimer) - t.ok(agent.customEventAggregator.sendTimer) - t.ok(agent.logs.sendTimer) - - t.end() + const aggregators = [ + agent.traces, + agent.errors.traceAggregator, + agent.errors.eventAggregator, + agent.spanEventAggregator, + agent.transactionEventAggregator, + agent.customEventAggregator, + agent.logs + ] + for (const agg of aggregators) { + assert.equal(Object.prototype.toString.call(agg.sendTimer), '[object Object]') + } }) -tap.test('#harvesters.stop should stop all aggregators', (t) => { - // Load agent with default 'stopped' state +test('#harvesters.stop should stop all aggregators', (t) => { + // Load agent with default 'stopped' state: const agent = helper.loadMockedAgent(null, false) - agent.config.application_logging.forwarding.enabled = true - - t.teardown(() => { + t.after(() => { helper.unloadAgent(agent) }) agent.harvester.start() agent.harvester.stop() - t.notOk(agent.traces.sendTimer) - t.notOk(agent.errors.traceAggregator.sendTimer) - t.notOk(agent.errors.eventAggregator.sendTimer) - t.notOk(agent.spanEventAggregator.sendTimer) - t.notOk(agent.transactionEventAggregator.sendTimer) - t.notOk(agent.customEventAggregator.sendTimer) - t.notOk(agent.logs.sendTimer) - - t.end() + const aggregators = [ + agent.traces, + agent.errors.traceAggregator, + agent.errors.eventAggregator, + agent.spanEventAggregator, + agent.transactionEventAggregator, + agent.customEventAggregator, + agent.logs + ] + for (const agg of aggregators) { + assert.equal(agg.sendTimer, null) + } }) -tap.test('#onConnect should reconfigure all the aggregators', (t) => { +test('#onConnect should reconfigure all the aggregators', async (t) => { + const { promise, resolve } = promiseResolvers() const EXPECTED_AGG_COUNT = 9 - - // Load agent with default 'stopped' state const agent = helper.loadMockedAgent(null, false) agent.config.application_logging.forwarding.enabled = true - // mock out the base reconfigure method - const proto = agent.traces.__proto__.__proto__.__proto__ + // Mock out the base reconfigure method: + const proto = Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(agent.traces))) sinon.stub(proto, 'reconfigure') - t.teardown(() => { + t.after(() => { helper.unloadAgent(agent) proto.reconfigure.restore() }) agent.config.event_harvest_config = { - report_period_ms: 5000, + report_period_ms: 5_000, harvest_limits: { span_event_data: 1 } } agent.onConnect(false, () => { - t.equal(proto.reconfigure.callCount, EXPECTED_AGG_COUNT) - - t.end() + assert.equal(proto.reconfigure.callCount, EXPECTED_AGG_COUNT) + resolve() }) -}) -tap.test('when starting', (t) => { - t.autoend() + await promise +}) - let agent = null +test('when starting', async (t) => { + t.beforeEach(async (ctx) => { + ctx.nr = {} - t.beforeEach(() => { - nock.disableNetConnect() + const collector = new Collector() + ctx.nr.collector = collector + await collector.listen() - // Load agent with default 'stopped' state - agent = helper.loadMockedAgent(null, false) + ctx.nr.agent = helper.loadMockedAgent(collector.agentConfig, false) }) - t.afterEach(() => { - helper.unloadAgent(agent) - agent = null - - if (!nock.isDone()) { - /* eslint-disable-next-line no-console */ - console.error('Cleaning pending mocks: %j', nock.pendingMocks()) - nock.cleanAll() - } - - nock.enableNetConnect() + t.afterEach((ctx) => { + helper.unloadAgent(ctx.nr.agent) }) - t.test('should require a callback', (t) => { - t.throws(() => agent.start(), new Error('callback required!')) - - t.end() + await t.test('should require a callback', (t) => { + const { agent } = t.nr + assert.throws(() => agent.start(), /callback required/) }) - t.test('should change state to `starting`', (t) => { + await t.test('should change to "starting"', async (t) => { + const { promise, resolve } = promiseResolvers() + const { agent } = t.nr agent.collector.connect = function () { - t.equal(agent._state, 'starting') - t.end() + assert.equal(agent._state, 'starting') + resolve() } - - agent.start(function cbStart() {}) + agent.start(() => {}) + await promise }) - t.test('should not error when disabled via configuration', (t) => { + await t.test('should not error when disabled via configuration', async (t) => { + const { promise, resolve, reject } = promiseResolvers() + const { agent } = t.nr agent.config.agent_enabled = false agent.collector.connect = function () { - t.error(new Error('should not be called')) - t.end() + reject(Error('should not be called')) } - agent.start(() => { - t.end() - }) + agent.start(() => resolve()) + await promise }) - t.test('should emit `stopped` when disabled via configuration', (t) => { + await t.test('should emit "stopped" when disabled via configuration', async (t) => { + const { promise, resolve, reject } = promiseResolvers() + const { agent } = t.nr agent.config.agent_enabled = false agent.collector.connect = function () { - t.error(new Error('should not be called')) - t.end() - } - - agent.start(function cbStart() { - t.equal(agent._state, 'stopped') - t.end() - }) - }) - - t.test('should error when no license key is included', (t) => { - agent.config.license_key = undefined - agent.collector.connect = function () { - t.error(new Error('should not be called')) - t.end() + reject(Error('should not be called')) } - - agent.start(function cbStart(error) { - t.ok(error) - - t.end() + agent.start(() => { + assert.equal(agent._state, 'stopped') + resolve() }) + await promise }) - t.test('should say why startup failed without license key', (t) => { + await t.test('should error when no license key is included', async (t) => { + const { promise, resolve, reject } = promiseResolvers() + const { agent } = t.nr agent.config.license_key = undefined - agent.collector.connect = function () { - t.error(new Error('should not be called')) - t.end() + reject(Error('should not be called')) } - - agent.start(function cbStart(error) { - t.equal(error.message, 'Not starting without license key!') - - t.end() + agent.start((error) => { + assert.equal(error.message, 'Not starting without license key!') + resolve() }) + await promise }) - t.test('should call connect when using proxy', (t) => { + await t.test('should call connect when using proxy', async (t) => { + const { promise, resolve } = promiseResolvers() + const { agent } = t.nr agent.config.proxy = 'fake://url' - agent.collector.connect = function (callback) { - t.ok(callback) - - t.end() + assert.equal(typeof callback, 'function') + resolve() } - agent.start(() => {}) + await promise }) - t.test('should call connect when config is correct', (t) => { + await t.test('should call connect when config is correct', async (t) => { + const { promise, resolve } = promiseResolvers() + const { agent } = t.nr agent.collector.connect = function (callback) { - t.ok(callback) - t.end() + assert.equal(typeof callback, 'function') + resolve() } - agent.start(() => {}) + await promise }) - t.test('should error when connection fails', (t) => { - const passed = new Error('passin on through') - + await t.test('should error when connection fails', async (t) => { + const { promise, resolve } = promiseResolvers() + const { agent } = t.nr + const expected = Error('boom') agent.collector.connect = function (callback) { - callback(passed) + callback(expected) } - - agent.start(function cbStart(error) { - t.equal(error, passed) - - t.end() + agent.start((error) => { + assert.equal(error, expected) + resolve() }) + await promise }) - t.test('should harvest at connect when metrics are already there', (t) => { - const metrics = nock(URL) - .post(helper.generateCollectorPath('metric_data', RUN_ID)) - .reply(200, { return_value: [] }) + await t.test('should harvest at connect when metrics are already there', async (t) => { + const { promise, resolve } = promiseResolvers() + const { agent, collector } = t.nr + + collector.addHandler(helper.generateCollectorPath('metric_data', RUN_ID), (req, res) => { + res.json({ payload: { return_value: [] } }) + }) agent.collector.connect = function (callback) { agent.collector.isConnected = () => true callback(null, CollectorResponse.success(null, { agent_run_id: RUN_ID })) } - agent.config.run_id = RUN_ID - agent.metrics.measureMilliseconds('Test/Bogus', null, 1) - agent.start(function cbStart(error) { - t.error(error) - t.ok(metrics.isDone()) - - t.end() + agent.start((error) => { + agent.forceHarvestAll(() => { + assert.equal(error, undefined) + // assert.equal(metrics.isDone(), true) + assert.equal(collector.isDone('metric_data'), true) + resolve() + }) }) + + await promise }) }) -tap.test('initial harvest', (t) => { - t.autoend() - - const origInterval = global.setInterval +test('initial harvest', async (t) => { + t.beforeEach(async (ctx) => { + ctx.nr = {} - let agent = null - let redirect = null - let connect = null - let settings = null - - t.beforeEach(() => { - nock.disableNetConnect() - - global.setInterval = (callback) => { - return Object.assign({ unref: () => {} }, setImmediate(callback)) - } + const collector = new Collector() + ctx.nr.collector = collector + await collector.listen() - // Load agent with default 'stopped' state - agent = helper.loadMockedAgent(null, false) - - // Avoid detection work / network call attempts - agent.config.utilization = { + ctx.nr.agent = helper.loadMockedAgent(collector.agentConfig, false) + ctx.nr.agent.config.utilization = { detect_aws: false, detect_pcf: false, detect_azure: false, detect_gcp: false, detect_docker: false } - - agent.config.no_immediate_harvest = true - - redirect = nock(URL) - .post(helper.generateCollectorPath('preconnect')) - .reply(200, { - return_value: { - redirect_host: 'collector.newrelic.com', - security_policies: {} - } - }) - - connect = nock(URL) - .post(helper.generateCollectorPath('connect')) - .reply(200, { return_value: { agent_run_id: RUN_ID } }) - - settings = nock(URL) - .post(helper.generateCollectorPath('agent_settings', RUN_ID)) - .reply(200, { return_value: [] }) + ctx.nr.agent.config.no_immediate_harvest = true }) - t.afterEach(() => { - global.setInterval = origInterval - - helper.unloadAgent(agent) - agent = null - - if (!nock.isDone()) { - /* eslint-disable-next-line no-console */ - console.error('Cleaning pending mocks: %j', nock.pendingMocks()) - nock.cleanAll() - } - - nock.enableNetConnect() + t.afterEach((ctx) => { + helper.unloadAgent(ctx.nr.agent) + ctx.nr.collector.close() }) - t.test('should not blow up when harvest cycle runs', (t) => { + await t.test('should not blow up when harvest cycle runs', async (t) => { + const { promise, resolve } = promiseResolvers() + const { agent, collector } = t.nr agent.start(() => { setTimeout(() => { - t.ok(redirect.isDone()) - t.ok(connect.isDone()) - t.ok(settings.isDone()) - - t.end() + assert.equal(collector.isDone('preconnect'), true) + assert.equal(collector.isDone('connect'), true) + assert.equal(collector.isDone('agent_settings'), true) + resolve() }, 15) }) + await promise }) - t.test('should start aggregators after initial harvest', (t) => { + await t.test('should start aggregators after initial harvest', async (t) => { + const { promise, resolve } = promiseResolvers() + const { agent, collector } = t.nr + sinon.stub(agent.harvester, 'start') + t.after(() => sinon.restore()) agent.start(() => { setTimeout(() => { - t.equal(agent.harvester.start.callCount, 1) - t.ok(redirect.isDone()) - t.ok(connect.isDone()) - t.ok(settings.isDone()) - - t.end() + assert.equal(agent.harvester.start.callCount, 1) + assert.equal(collector.isDone('preconnect'), true) + assert.equal(collector.isDone('connect'), true) + assert.equal(collector.isDone('agent_settings'), true) + resolve() }, 15) }) - }) - t.test('should not blow up when harvest cycle errors', (t) => { - const metrics = nock(URL).post(helper.generateCollectorPath('metric_data', RUN_ID)).reply(503) + await promise + }) - agent.start(function cbStart() { - setTimeout(function () { - global.setInterval = origInterval + await t.test('should not blow up when harvest cycle errors', async (t) => { + const { promise, resolve } = promiseResolvers() + const { agent, collector } = t.nr - redirect.done() - connect.done() - settings.done() - metrics.done() + collector.addHandler(helper.generateCollectorPath('metric_data', RUN_ID), (req, res) => { + res.writeHead(503) + res.end() + }) - t.end() - }, 15) + agent.start(() => { + agent.forceHarvestAll(() => { + assert.equal(collector.isDone('preconnect'), true) + assert.equal(collector.isDone('connect'), true) + assert.equal(collector.isDone('agent_settings'), true) + assert.equal(collector.isDone('metric_data'), true) + resolve() + }) }) + await promise }) }) -tap.test('when stopping', (t) => { - t.autoend() - - function nop() {} - - let agent = null - - t.beforeEach(() => { - // Load agent with default 'stopped' state - agent = helper.loadMockedAgent(null, false) +test('when stopping', async (t) => { + t.beforeEach((ctx) => { + ctx.nr = {} + ctx.nr.agent = helper.loadMockedAgent(null, false) }) - t.afterEach(() => { - helper.unloadAgent(agent) - agent = null + t.afterEach((ctx) => { + helper.unloadAgent(ctx.nr.agent) }) - t.test('should require a callback', (t) => { - t.throws(() => agent.stop(), new Error('callback required!')) - t.end() + await t.test('should require a callback', (t) => { + const { agent } = t.nr + assert.throws(() => agent.stop(), /callback required!/) }) - t.test('should stop sampler', (t) => { + await t.test('should stop sampler', (t) => { + const { agent } = t.nr sampler.start(agent) - agent.collector.shutdown = nop - agent.stop(nop) - - t.equal(sampler.state, 'stopped') - t.end() + agent.collector.shutdown = () => {} + agent.stop(() => {}) + assert.equal(sampler.state, 'stopped') }) - t.test('should change state to `stopping`', (t) => { + await t.test('should change state to "stopping"', (t) => { + const { agent } = t.nr sampler.start(agent) - agent.collector.shutdown = nop - agent.stop(nop) - - t.equal(agent._state, 'stopping') - t.end() + agent.collector.shutdown = () => {} + agent.stop(() => {}) + assert.equal(agent._state, 'stopping') }) - t.test('should not shut down connection if not connected', (t) => { - agent.stop(function cbStop(error) { - t.error(error) - t.end() + await t.test('should not shut down connection if not connected', async (t) => { + const { promise, resolve } = promiseResolvers() + const { agent } = t.nr + agent.stop((error) => { + assert.equal(error, undefined) + resolve() }) + await promise }) }) -tap.test('when stopping after connected', (t) => { - t.autoend() +test('when stopping after connected', async (t) => { + t.beforeEach(async (ctx) => { + ctx.nr = {} - let agent = null + const collector = new Collector() + ctx.nr.collector = collector + await collector.listen() - t.beforeEach(() => { - nock.disableNetConnect() - - // Load agent with default 'stopped' state - agent = helper.loadMockedAgent(null, false) + ctx.nr.agent = helper.loadMockedAgent(collector.agentConfig, false) }) - t.afterEach(() => { - helper.unloadAgent(agent) - agent = null - - if (!nock.isDone()) { - /* eslint-disable-next-line no-console */ - console.error('Cleaning pending mocks: %j', nock.pendingMocks()) - nock.cleanAll() - } - - nock.enableNetConnect() + t.afterEach((ctx) => { + helper.unloadAgent(ctx.nr.agent) + ctx.nr.collector.close() }) - t.test('should call shutdown', (t) => { + await t.test('should call shutdown', async (t) => { + const { promise, resolve } = promiseResolvers() + const { agent, collector } = t.nr + agent.config.run_id = RUN_ID - const shutdown = nock(URL) - .post(helper.generateCollectorPath('shutdown', RUN_ID)) - .reply(200, { return_value: null }) - agent.stop(function cbStop(error) { - t.error(error) - t.notOk(agent.config.run_id) + collector.addHandler(helper.generateCollectorPath('shutdown', RUN_ID), (req, res) => { + res.json({ payload: { return_value: null } }) + }) - t.ok(shutdown.isDone()) - t.end() + agent.stop((error) => { + assert.equal(error, undefined) + assert.equal(agent.config.run_id, null) + assert.equal(collector.isDone('shutdown'), true) + resolve() }) + await promise }) - t.test('should pass through error if shutdown fails', (t) => { + await t.test('should pass through error if shutdown fails', async (t) => { + const { promise, resolve } = promiseResolvers() + const { agent, collector } = t.nr + agent.config.run_id = RUN_ID - const shutdown = nock(URL) - .post(helper.generateCollectorPath('shutdown', RUN_ID)) - .replyWithError('whoops!') - agent.stop((error) => { - t.ok(error) - t.equal(error.message, 'whoops!') + let shutdownIsDone = false + collector.addHandler(helper.generateCollectorPath('shutdown', RUN_ID), (req) => { + shutdownIsDone = true + req.destroy() + }) - t.ok(shutdown.isDone()) - t.end() + agent.stop((error) => { + assert.equal(error.message, 'socket hang up') + assert.equal(shutdownIsDone, true) + resolve() }) + await promise }) }) -tap.test('when connected', (t) => { - t.autoend() - - let agent = null - - t.beforeEach(() => { - nock.disableNetConnect() +test('when connected', async (t) => { + t.beforeEach(async (ctx) => { + ctx.nr = {} - // Load agent with default 'stopped' state - agent = helper.loadMockedAgent(null, false) + const collector = new Collector() + ctx.nr.collector = collector + await collector.listen() - // Avoid detection work / network call attempts - agent.config.utilization = { + ctx.nr.agent = helper.loadMockedAgent(collector.agentConfig, false) + ctx.nr.agent.config.utilization = { detect_aws: false, detect_pcf: false, detect_azure: false, @@ -661,36 +583,53 @@ tap.test('when connected', (t) => { } }) - t.afterEach(() => { - helper.unloadAgent(agent) - agent = null + t.afterEach((ctx) => { + helper.unloadAgent(ctx.nr.agent) + ctx.nr.collector.close() + }) - if (!nock.isDone()) { - /* eslint-disable-next-line no-console */ - console.error('Cleaning pending mocks: %j', nock.pendingMocks()) - nock.cleanAll() - } + await t.test('should update the metric apdexT value after connect', async (t) => { + const { promise, resolve } = promiseResolvers() + const { agent } = t.nr - nock.enableNetConnect() + assert.equal(agent.metrics._apdexT, 0.1) + agent.config.apdex_t = 0.666 + agent.onConnect(false, () => { + assert.equal(agent.metrics._apdexT, 0.666) + assert.equal(agent.metrics._metrics.apdexT, 0.666) + resolve() + }) + await promise }) - function mockHandShake(config = {}) { - const redirect = nock(URL) - .post(helper.generateCollectorPath('preconnect')) - .reply(200, { - return_value: { - redirect_host: 'collector.newrelic.com', - security_policies: {} - } - }) + await t.test('should reset the config and metrics normalizer on connection', async (t) => { + const { promise, resolve } = promiseResolvers() + const { agent, collector } = t.nr + const config = { + agent_run_id: 1122, + apdex_t: 0.742, + url_rules: [] + } - const handshake = nock(URL) - .post(helper.generateCollectorPath('connect')) - .reply(200, { return_value: config }) - return { redirect, handshake } - } + collector.addHandler(helper.generateCollectorPath('connect'), (req, res) => { + res.json({ payload: { return_value: config } }) + }) - function setupAggregators(enableAggregator) { + assert.equal(agent.metrics._apdexT, 0.1) + agent.start((error) => { + assert.equal(error, undefined) + assert.equal(collector.isDone('preconnect'), true) + assert.equal(collector.isDone('connect'), true) + assert.equal(agent._state, 'started') + assert.equal(agent.config.run_id, 1122) + assert.equal(agent.metrics._apdexT, 0.742) + assert.deepStrictEqual(agent.urlNormalizer.rules, []) + resolve() + }) + await promise + }) + + function setupAggregators({ enableAggregator: enableAggregator = true, agent, collector }) { agent.config.application_logging.enabled = enableAggregator agent.config.application_logging.forwarding.enabled = enableAggregator agent.config.slow_sql.enabled = enableAggregator @@ -701,276 +640,195 @@ tap.test('when connected', (t) => { agent.config.transaction_tracer.enabled = enableAggregator agent.config.collect_errors = enableAggregator agent.config.error_collector.capture_events = enableAggregator - const runId = 1122 - const config = { - agent_run_id: runId - } - const { redirect, handshake } = mockHandShake(config) - const metrics = nock(URL) - .post(helper.generateCollectorPath('metric_data', runId)) - .reply(200, { return_value: [] }) - const logs = nock(URL) - .post(helper.generateCollectorPath('log_event_data', runId)) - .reply(200, { return_value: [] }) - const sql = nock(URL) - .post(helper.generateCollectorPath('sql_trace_data', runId)) - .reply(200, { return_value: [] }) - const spanEventAggregator = nock(URL) - .post(helper.generateCollectorPath('span_event_data', runId)) - .reply(200, { return_value: [] }) - const transactionEvents = nock(URL) - .post(helper.generateCollectorPath('analytic_event_data', runId)) - .reply(200, { return_value: [] }) - const transactionSamples = nock(URL) - .post(helper.generateCollectorPath('transaction_sample_data', runId)) - .reply(200, { return_value: [] }) - const customEvents = nock(URL) - .post(helper.generateCollectorPath('custom_event_data', runId)) - .reply(200, { return_value: [] }) - const errorTransactionEvents = nock(URL) - .post(helper.generateCollectorPath('error_data', runId)) - .reply(200, { return_value: [] }) - const errorEvents = nock(URL) - .post(helper.generateCollectorPath('error_event_data', runId)) - .reply(200, { return_value: [] }) - - return { - redirect, - handshake, - metrics, - logs, - sql, - spanEventAggregator, - transactionSamples, - transactionEvents, - customEvents, - errorTransactionEvents, - errorEvents - } - } - - t.test('should update the metric apdexT value after connect', (t) => { - t.equal(agent.metrics._apdexT, 0.1) - agent.config.apdex_t = 0.666 - agent.onConnect(false, () => { - t.ok(agent.metrics._apdexT) - - t.equal(agent.metrics._apdexT, 0.666) - t.equal(agent.metrics._metrics.apdexT, 0.666) + const runId = 1122 + const config = { agent_run_id: runId } + const payload = { return_value: [] } - t.end() + collector.addHandler(helper.generateCollectorPath('connect'), (req, res) => { + res.json({ payload: { return_value: config } }) }) - }) - - t.test('should reset the config and metrics normalizer on connection', (t) => { - const config = { - agent_run_id: 1122, - apdex_t: 0.742, - url_rules: [] - } - - const { redirect, handshake } = mockHandShake(config) - const shutdown = nock(URL) - .post(helper.generateCollectorPath('shutdown', 1122)) - .reply(200, { return_value: null }) - - t.equal(agent.metrics._apdexT, 0.1) - agent.start(function cbStart(error) { - t.error(error) - t.ok(redirect.isDone()) - t.ok(handshake.isDone()) - - t.equal(agent._state, 'started') - t.equal(agent.config.run_id, 1122) - t.equal(agent.metrics._apdexT, 0.742) - t.same(agent.urlNormalizer.rules, []) - - agent.stop(function cbStop() { - t.ok(shutdown.isDone()) - - t.end() - }) + // Note: we cannot re-use a single handler function because the `isDone` + // indicator is attached to the handler. Therefore, if we reused the same + // function for all events, all events would look like they are "done" if + // any one of them gets invoked. + collector.addHandler(helper.generateCollectorPath('metric_data', runId), (req, res) => { + res.json({ payload }) }) - }) + collector.addHandler(helper.generateCollectorPath('log_event_data', runId), (req, res) => { + res.json({ payload }) + }) + collector.addHandler(helper.generateCollectorPath('sql_trace_data', runId), (req, res) => { + res.json({ payload }) + }) + collector.addHandler(helper.generateCollectorPath('span_event_data', runId), (req, res) => { + res.json({ payload }) + }) + collector.addHandler(helper.generateCollectorPath('analytic_event_data', runId), (req, res) => { + res.json({ payload }) + }) + collector.addHandler( + helper.generateCollectorPath('transaction_sample_data', runId), + (req, res) => { + res.json({ payload }) + } + ) + collector.addHandler(helper.generateCollectorPath('custom_event_data', runId), (req, res) => { + res.json({ payload }) + }) + collector.addHandler(helper.generateCollectorPath('error_data', runId), (req, res) => { + res.json({ payload }) + }) + collector.addHandler(helper.generateCollectorPath('error_event_data', runId), (req, res) => { + res.json({ payload }) + }) + } - t.test('should force harvest of all aggregators 1 second after connect', (t) => { - const { - redirect, - handshake, - metrics, - logs, - sql, - spanEventAggregator, - transactionEvents, - transactionSamples, - customEvents, - errorTransactionEvents, - errorEvents - } = setupAggregators(true) + await t.test('should force harvest of all aggregators 1 second after connect', async (t) => { + const { promise, resolve } = promiseResolvers() + const { agent, collector } = t.nr + + setupAggregators({ agent, collector }) agent.logs.add([{ key: 'bar' }]) const tx = new helper.FakeTransaction(agent, '/path/to/fake') tx.metrics = { apdexT: 0 } - const segment = new helper.FakeSegment(tx, 2000) + const segment = new helper.FakeSegment(tx, 2_000) agent.queries.add(segment, 'mysql', 'select * from foo', 'Stack\nFrames') agent.spanEventAggregator.add(segment) agent.transactionEventAggregator.add(tx) agent.customEventAggregator.add({ key: 'value' }) agent.traces.add(tx) - const err = new Error('test error') + const err = Error('test error') agent.errors.traceAggregator.add(err) agent.errors.eventAggregator.add(err) - agent.start((err) => { - t.error(err) - t.ok(redirect.isDone()) - t.ok(handshake.isDone()) - t.ok(metrics.isDone()) - t.ok(logs.isDone()) - t.ok(sql.isDone()) - t.ok(spanEventAggregator.isDone()) - t.ok(transactionEvents.isDone()) - t.ok(transactionSamples.isDone()) - t.ok(customEvents.isDone()) - t.ok(errorTransactionEvents.isDone()) - t.ok(errorEvents.isDone()) - t.end() + agent.start((error) => { + agent.forceHarvestAll(() => { + assert.equal(error, undefined) + assert.equal(collector.isDone('preconnect'), true) + assert.equal(collector.isDone('connect'), true) + assert.equal(collector.isDone('metric_data'), true) + assert.equal(collector.isDone('log_event_data'), true) + assert.equal(collector.isDone('sql_trace_data'), true) + assert.equal(collector.isDone('span_event_data'), true) + assert.equal(collector.isDone('analytic_event_data'), true) + assert.equal(collector.isDone('transaction_sample_data'), true) + assert.equal(collector.isDone('custom_event_data'), true) + assert.equal(collector.isDone('error_data'), true) + assert.equal(collector.isDone('error_event_data'), true) + resolve() + }) }) + await promise }) - t.test( + await t.test( 'should force harvest of only metric data 1 second after connect when all other aggregators are disabled', - (t) => { - const { - redirect, - handshake, - metrics, - logs, - sql, - spanEventAggregator, - transactionEvents, - transactionSamples, - customEvents, - errorTransactionEvents, - errorEvents - } = setupAggregators(false) + async (t) => { + const { promise, resolve } = promiseResolvers() + const { agent, collector } = t.nr + + setupAggregators({ enableAggregator: false, agent, collector }) agent.logs.add([{ key: 'bar' }]) const tx = new helper.FakeTransaction(agent, '/path/to/fake') tx.metrics = { apdexT: 0 } - const segment = new helper.FakeSegment(tx, 2000) + const segment = new helper.FakeSegment(tx, 2_000) agent.queries.add(segment, 'mysql', 'select * from foo', 'Stack\nFrames') agent.spanEventAggregator.add(segment) agent.transactionEventAggregator.add(tx) agent.customEventAggregator.add({ key: 'value' }) agent.traces.add(tx) - const err = new Error('test error') + const err = Error('test error') agent.errors.traceAggregator.add(err) agent.errors.eventAggregator.add(err) - agent.start((err) => { - t.error(err) - t.ok(redirect.isDone()) - t.ok(handshake.isDone()) - t.ok(metrics.isDone()) - t.notOk(logs.isDone()) - t.notOk(sql.isDone()) - t.notOk(spanEventAggregator.isDone()) - t.notOk(transactionEvents.isDone()) - t.notOk(transactionSamples.isDone()) - t.notOk(customEvents.isDone()) - t.notOk(errorTransactionEvents.isDone()) - t.notOk(errorEvents.isDone()) - /** - * cleaning pending calls to avoid the afterEach - * saying it is clearing pending calls - * we know these are pending so let's be explicit - * vs the afterEach which helps us understanding things - * that need cleaned up - */ - nock.cleanAll() - t.end() + agent.start((error) => { + agent.forceHarvestAll(() => { + assert.equal(error, undefined) + assert.equal(collector.isDone('preconnect'), true) + assert.equal(collector.isDone('connect'), true) + assert.equal(collector.isDone('metric_data'), true) + assert.equal(collector.isDone('log_event_data'), false) + assert.equal(collector.isDone('sql_trace_data'), false) + assert.equal(collector.isDone('span_event_data'), false) + assert.equal(collector.isDone('analytic_event_data'), false) + assert.equal(collector.isDone('transaction_sample_data'), false) + assert.equal(collector.isDone('custom_event_data'), false) + assert.equal(collector.isDone('error_data'), false) + assert.equal(collector.isDone('error_event_data'), false) + resolve() + }) }) + await promise } ) - t.test('should not post data when there is none in aggregators during a force harvest', (t) => { - const { - redirect, - handshake, - metrics, - logs, - sql, - spanEventAggregator, - transactionEvents, - transactionSamples, - customEvents, - errorTransactionEvents, - errorEvents - } = setupAggregators(true) - agent.start((err) => { - t.error(err) - t.ok(redirect.isDone()) - t.ok(handshake.isDone()) - t.ok(metrics.isDone()) - t.notOk(logs.isDone()) - t.notOk(sql.isDone()) - t.notOk(spanEventAggregator.isDone()) - t.notOk(transactionEvents.isDone()) - t.notOk(transactionSamples.isDone()) - t.notOk(customEvents.isDone()) - t.notOk(errorTransactionEvents.isDone()) - t.notOk(errorEvents.isDone()) - /** - * cleaning pending calls to avoid the afterEach - * saying it is clearing pending calls - * we know these are pending so let's be explicit - * vs the afterEach which helps us understanding things - * that need cleaned up - */ - nock.cleanAll() - t.end() - }) - }) + await t.test( + 'should not post data when there is none in aggregators during a force harvest', + async (t) => { + const { promise, resolve } = promiseResolvers() + const { agent, collector } = t.nr + + setupAggregators({ agent, collector }) + + agent.start((error) => { + assert.equal(error, undefined) + assert.equal(collector.isDone('preconnect'), true) + assert.equal(collector.isDone('connect'), true) + assert.equal(collector.isDone('metric_data'), true) + assert.equal(collector.isDone('log_event_data'), false) + assert.equal(collector.isDone('sql_trace_data'), false) + assert.equal(collector.isDone('span_event_data'), false) + assert.equal(collector.isDone('analytic_event_data'), false) + assert.equal(collector.isDone('transaction_sample_data'), false) + assert.equal(collector.isDone('custom_event_data'), false) + assert.equal(collector.isDone('error_data'), false) + assert.equal(collector.isDone('error_event_data'), false) + resolve() + }) + await promise + } + ) }) -tap.test('when handling finished transactions', (t) => { - t.autoend() - - let agent = null - - t.beforeEach(() => { - // Load agent with default 'stopped' state - agent = helper.loadMockedAgent(null, false) +test('when handling finished transactions', async (t) => { + t.beforeEach((ctx) => { + ctx.nr = {} + ctx.nr.agent = helper.loadMockedAgent(null, false) }) - t.afterEach(() => { - helper.unloadAgent(agent) - agent = null + t.afterEach((ctx) => { + helper.unloadAgent(ctx.nr.agent) }) - t.test('should capture the trace off a finished transaction', (t) => { - const transaction = new Transaction(agent) - // need to initialize the trace - transaction.trace.setDurationInMillis(2100) + await t.test('should capture the trace off a finished transaction', async (t) => { + const { agent } = t.nr + const { promise, resolve } = promiseResolvers() + const tx = new Transaction(agent) - agent.once('transactionFinished', function () { - const trace = agent.traces.trace - t.ok(trace) - t.equal(trace.getDurationInMillis(), 2100) + // Initialize the trace: + tx.trace.setDurationInMillis(2_100) - t.end() + agent.once('transactionFinished', () => { + const trace = agent.traces.trace + assert.equal(Object.prototype.toString.call(trace), '[object Object]') + assert.equal(trace.getDurationInMillis(), 2_100) + resolve() }) - - transaction.end() + tx.end() + await promise }) - t.test('should capture the synthetic trace off a finished transaction', (t) => { - const transaction = new Transaction(agent) - // need to initialize the trace - transaction.trace.setDurationInMillis(2100) - transaction.syntheticsData = { + await t.test('should capture the synthetic trace off a finished transaction', async (t) => { + const { agent } = t.nr + const { promise, resolve } = promiseResolvers() + const tx = new Transaction(agent) + + // Initialize trace: + tx.trace.setDurationInMillis(2_100) + tx.syntheticsData = { version: 1, accountId: 357, resourceId: 'resId', @@ -978,309 +836,306 @@ tap.test('when handling finished transactions', (t) => { monitorId: 'monId' } - agent.once('transactionFinished', function () { - t.notOk(agent.traces.trace) - t.equal(agent.traces.syntheticsTraces.length, 1) + agent.once('transactionFinished', () => { + assert.equal(agent.traces.trace, null) + assert.equal(agent.traces.syntheticsTraces.length, 1) const trace = agent.traces.syntheticsTraces[0] - t.equal(trace.getDurationInMillis(), 2100) + assert.equal(trace.getDurationInMillis(), 2_100) - t.end() + resolve() }) - - transaction.end() + tx.end() + await promise }) - t.test('should not merge metrics when transaction is ignored', (t) => { - const transaction = new Transaction(agent) - transaction.ignore = true + await t.test('should not merge metrics when transaction is ignored', (t) => { + const { agent } = t.nr + const tx = new Transaction(agent) + tx.ignore = true - /* Top-level method is bound into EE, so mock the metrics collection - * instead. - */ + // Top-level method is bound into EE, so mock the metrics collection instead. const mock = sinon.mock(agent.metrics) mock.expects('merge').never() - transaction.end() - - t.end() + tx.end() }) - t.test('should not merge errors when transaction is ignored', (t) => { - const transaction = new Transaction(agent) - transaction.ignore = true + await t.test('should not merge errors when transaction is ignored', (t) => { + const { agent } = t.nr + const tx = new Transaction(agent) + tx.ignore = true - /* Top-level method is bound into EE, so mock the error tracer instead. - */ + // Top-level method is bound into EE, so mock the metrics collection instead. const mock = sinon.mock(agent.errors) mock.expects('onTransactionFinished').never() - transaction.end() - t.end() + tx.end() }) - t.test('should not aggregate trace when transaction is ignored', (t) => { - const transaction = new Transaction(agent) - transaction.ignore = true + await t.test('should not aggregate trace when transaction is ignored', (t) => { + const { agent } = t.nr + const tx = new Transaction(agent) + tx.ignore = true - /* Top-level *and* second-level methods are bound into EEs, so mock the - * transaction trace record method instead. - */ - const mock = sinon.mock(transaction) + // Top-level method is bound into EE, so mock the metrics collection instead. + const mock = sinon.mock(tx) mock.expects('record').never() - transaction.end() - t.end() + tx.end() }) }) -tap.test('when sampling_target changes', (t) => { - t.autoend() - - let agent = null - - t.beforeEach(() => { - // Load agent with default 'stopped' state - agent = helper.loadMockedAgent(null, false) +test('when sampling_target changes', async (t) => { + t.beforeEach((ctx) => { + ctx.nr = {} + ctx.nr.agent = helper.loadMockedAgent(null, false) }) - t.afterEach(() => { - helper.unloadAgent(agent) - agent = null + t.afterEach((ctx) => { + helper.unloadAgent(ctx.nr.agent) }) - t.test('should adjust the current sampling target', (t) => { - t.not(agent.transactionSampler.samplingTarget, 5) + await t.test('should adjust the current sampling target', (t) => { + const { agent } = t.nr + assert.notEqual(agent.transactionSampler.samplingTarget, 5) agent.config.onConnect({ sampling_target: 5 }) - t.equal(agent.transactionSampler.samplingTarget, 5) - - t.end() + assert.equal(agent.transactionSampler.samplingTarget, 5) }) - t.test('should adjust the sampling period', (t) => { - t.not(agent.transactionSampler.samplingPeriod, 100) + await t.test('should adjust the sampling period', (t) => { + const { agent } = t.nr + assert.notEqual(agent.transactionSampler.samplingPeriod, 100) agent.config.onConnect({ sampling_target_period_in_seconds: 0.1 }) - t.equal(agent.transactionSampler.samplingPeriod, 100) - - t.end() + assert.equal(agent.transactionSampler.samplingPeriod, 100) }) }) -tap.test('when event_harvest_config updated on connect with a valid config', (t) => { - t.autoend() - - const validHarvestConfig = { - report_period_ms: 5000, - harvest_limits: { - analytic_event_data: 833, - custom_event_data: 833, - error_event_data: 8, - span_event_data: 200, - log_event_data: 833 +test('when event_harvest_config update on connect with a valid config', async (t) => { + t.beforeEach((ctx) => { + const validHarvestConfig = { + report_period_ms: 5_000, + harvest_limits: { + analytic_event_data: 833, + custom_event_data: 833, + error_event_data: 8, + span_event_data: 200, + log_event_data: 833 + } } - } - - let agent = null - t.beforeEach(() => { - // Load agent with default 'stopped' state - agent = helper.loadMockedAgent(null, false) + ctx.nr = {} + ctx.nr.agent = helper.loadMockedAgent(null, false) + ctx.nr.agent.config.onConnect({ event_harvest_config: validHarvestConfig }) - agent.config.onConnect({ event_harvest_config: validHarvestConfig }) + ctx.nr.validHarvestConfig = validHarvestConfig }) - t.afterEach(() => { - helper.unloadAgent(agent) - agent = null + t.afterEach((ctx) => { + helper.unloadAgent(ctx.nr.agent) }) - t.test('should generate ReportPeriod supportability', (t) => { + await t.test('should generate ReportPeriod supportability', async (t) => { + const { agent, validHarvestConfig } = t.nr + const { promise, resolve } = promiseResolvers() agent.onConnect(false, () => { const expectedMetricName = 'Supportability/EventHarvest/ReportPeriod' - const metric = agent.metrics.getMetric(expectedMetricName) - - t.ok(metric) - t.equal(metric.total, validHarvestConfig.report_period_ms) - - t.end() + assert.equal(metric.total, validHarvestConfig.report_period_ms) + resolve() }) + await promise }) - t.test('should generate AnalyticEventData/HarvestLimit supportability', (t) => { + await t.test('should generate AnalyticEventData/HarvestLimit supportability', async (t) => { + const { agent, validHarvestConfig } = t.nr + const { promise, resolve } = promiseResolvers() agent.onConnect(false, () => { const expectedMetricName = 'Supportability/EventHarvest/AnalyticEventData/HarvestLimit' - const metric = agent.metrics.getMetric(expectedMetricName) - - t.ok(metric) - t.equal(metric.total, validHarvestConfig.harvest_limits.analytic_event_data) - - t.end() + assert.equal(metric.total, validHarvestConfig.harvest_limits.analytic_event_data) + resolve() }) + await promise }) - t.test('should generate CustomEventData/HarvestLimit supportability', (t) => { + await t.test('should generate CustomEventData/HarvestLimit supportability', async (t) => { + const { agent, validHarvestConfig } = t.nr + const { promise, resolve } = promiseResolvers() agent.onConnect(false, () => { const expectedMetricName = 'Supportability/EventHarvest/CustomEventData/HarvestLimit' - const metric = agent.metrics.getMetric(expectedMetricName) - - t.ok(metric) - t.equal(metric.total, validHarvestConfig.harvest_limits.custom_event_data) - - t.end() + assert.equal(metric.total, validHarvestConfig.harvest_limits.custom_event_data) + resolve() }) + await promise }) - t.test('should generate ErrorEventData/HarvestLimit supportability', (t) => { + await t.test('should generate ErrorEventData/HarvestLimit supportability', async (t) => { + const { agent, validHarvestConfig } = t.nr + const { promise, resolve } = promiseResolvers() agent.onConnect(false, () => { const expectedMetricName = 'Supportability/EventHarvest/ErrorEventData/HarvestLimit' - const metric = agent.metrics.getMetric(expectedMetricName) - - t.ok(metric) - t.equal(metric.total, validHarvestConfig.harvest_limits.error_event_data) - t.end() + assert.equal(metric.total, validHarvestConfig.harvest_limits.error_event_data) + resolve() }) + await promise }) - t.test('should generate SpanEventData/HarvestLimit supportability', (t) => { + await t.test('should generate SpanEventData/HarvestLimit supportability', async (t) => { + const { agent, validHarvestConfig } = t.nr + const { promise, resolve } = promiseResolvers() agent.onConnect(false, () => { const expectedMetricName = 'Supportability/EventHarvest/SpanEventData/HarvestLimit' - const metric = agent.metrics.getMetric(expectedMetricName) - - t.ok(metric) - t.equal(metric.total, validHarvestConfig.harvest_limits.span_event_data) - t.end() + assert.equal(metric.total, validHarvestConfig.harvest_limits.span_event_data) + resolve() }) + await promise }) - t.test('should generate LogEventData/HarvestLimit supportability', (t) => { + + await t.test('should generate LogEventData/HarvestLimit supportability', async (t) => { + const { agent, validHarvestConfig } = t.nr + const { promise, resolve } = promiseResolvers() agent.onConnect(false, () => { const expectedMetricName = 'Supportability/EventHarvest/LogEventData/HarvestLimit' - const metric = agent.metrics.getMetric(expectedMetricName) - - t.ok(metric) - t.equal(metric.total, validHarvestConfig.harvest_limits.log_event_data) - t.end() + assert.equal(metric.total, validHarvestConfig.harvest_limits.log_event_data) + resolve() }) + await promise }) }) -tap.test('logging supportability on connect', (t) => { - t.autoend() - let agent +test('logging supportability on connect', async (t) => { const keys = ['Forwarding', 'Metrics', 'LocalDecorating'] - t.beforeEach(() => { - nock.disableNetConnect() - agent = helper.loadMockedAgent(null, false) + t.beforeEach((ctx) => { + ctx.nr = {} + ctx.nr.agent = helper.loadMockedAgent(null, false) }) - t.afterEach(() => { - helper.unloadAgent(agent) - agent = null + + t.afterEach((ctx) => { + helper.unloadAgent(ctx.nr.agent) }) - t.test('should increment disabled metrics when logging features are off', (t) => { + await t.test('should increment disabled metrics when logging features are off', async (t) => { + const { agent } = t.nr + const { promise, resolve } = promiseResolvers() + agent.config.application_logging.enabled = true agent.config.application_logging.metrics.enabled = false agent.config.application_logging.forwarding.enabled = false agent.config.application_logging.local_decorating.enabled = false agent.onConnect(false, () => { - keys.forEach((key) => { + for (const key of keys) { const disabled = agent.metrics.getMetric(`Supportability/Logging/${key}/Nodejs/disabled`) const enabled = agent.metrics.getMetric(`Supportability/Logging/${key}/Nodejs/enabled`) - t.equal(disabled.callCount, 1) - t.notOk(enabled) - }) - t.end() + assert.equal(disabled.callCount, 1) + assert.equal(enabled, undefined) + } + resolve() }) + await promise }) - t.test( + await t.test( 'should increment disabled metrics when logging features are on but application_logging.enabled is false', - (t) => { + async (t) => { + const { agent } = t.nr + const { promise, resolve } = promiseResolvers() + agent.config.application_logging.enabled = false agent.config.application_logging.metrics.enabled = true agent.config.application_logging.forwarding.enabled = true agent.config.application_logging.local_decorating.enabled = true agent.onConnect(false, () => { - keys.forEach((key) => { + for (const key of keys) { const disabled = agent.metrics.getMetric(`Supportability/Logging/${key}/Nodejs/disabled`) const enabled = agent.metrics.getMetric(`Supportability/Logging/${key}/Nodejs/enabled`) - t.equal(disabled.callCount, 1) - t.notOk(enabled) - }) - t.end() + assert.equal(disabled.callCount, 1) + assert.equal(enabled, undefined) + } + resolve() }) + await promise } ) - t.test('should increment enabled metrics when logging features are on', (t) => { + await t.test('should increment disabled metrics when logging features are on', async (t) => { + const { agent } = t.nr + const { promise, resolve } = promiseResolvers() + agent.config.application_logging.enabled = true agent.config.application_logging.metrics.enabled = true agent.config.application_logging.forwarding.enabled = true agent.config.application_logging.local_decorating.enabled = true agent.onConnect(false, () => { - keys.forEach((key) => { + for (const key of keys) { const disabled = agent.metrics.getMetric(`Supportability/Logging/${key}/Nodejs/disabled`) const enabled = agent.metrics.getMetric(`Supportability/Logging/${key}/Nodejs/enabled`) - t.equal(enabled.callCount, 1) - t.notOk(disabled) - }) - t.end() + assert.equal(enabled.callCount, 1) + assert.equal(disabled, undefined) + } + resolve() }) + await promise }) - t.test('should default llm to an object', (t) => { - t.same(agent.llm, {}) - t.end() + await t.test('should default llm to an object', (t) => { + const { agent } = t.nr + assert.deepStrictEqual(agent.llm, {}) }) }) -tap.test('getNRLinkingMetadata', (t) => { - t.autoend() - let agent - - t.beforeEach(() => { - agent = helper.loadMockedAgent() +test('getNRLinkingMetadata', async (t) => { + t.beforeEach((ctx) => { + ctx.nr = {} + ctx.nr.agent = helper.loadMockedAgent() }) - t.afterEach(() => { - helper.unloadAgent(agent) + t.afterEach((ctx) => { + helper.unloadAgent(ctx.nr.agent) }) - t.test('should properly format the NR-LINKING pipe string', (t) => { + await t.test('should properly format the NR-LINKING pipe string', async (t) => { + const { agent } = t.nr + const { promise, resolve } = promiseResolvers() agent.config.entity_guid = 'unit-test' helper.runInTransaction(agent, 'nr-linking-test', (tx) => { const nrLinkingMeta = agent.getNRLinkingMetadata() const expectedLinkingMeta = ` NR-LINKING|unit-test|${agent.config.getHostnameSafe()}|${ tx.traceId }|${tx.trace.root.id}|New%20Relic%20for%20Node.js%20tests|` - t.equal( + assert.equal( nrLinkingMeta, expectedLinkingMeta, 'NR-LINKING metadata should be properly formatted' ) - t.end() + resolve() }) + await promise }) - t.test('should properly handle if parts of NR-LINKING are undefined', (t) => { + await t.test('should properly handle if parts of NR-LINKING are undefined', (t) => { + const { agent } = t.nr const nrLinkingMeta = agent.getNRLinkingMetadata() const expectedLinkingMeta = ` NR-LINKING||${agent.config.getHostnameSafe()}|||New%20Relic%20for%20Node.js%20tests|` - t.equal(nrLinkingMeta, expectedLinkingMeta, 'NR-LINKING metadata should be properly formatted') - t.end() + assert.equal( + nrLinkingMeta, + expectedLinkingMeta, + 'NR-LINKING metadata should be properly formatted' + ) }) }) -tap.test('_reset*', (t) => { - t.autoend() - - t.beforeEach(() => { +test('_reset*', async (t) => { + t.beforeEach((ctx) => { + ctx.nr = {} const agent = helper.loadMockedAgent() + ctx.nr.agent = agent + const sandbox = sinon.createSandbox() sandbox.stub(agent.queries, 'clear') sandbox.stub(agent.errors, 'clearAll') @@ -1288,43 +1143,40 @@ tap.test('_reset*', (t) => { sandbox.stub(agent.errors.eventAggregator, 'reconfigure') sandbox.stub(agent.transactionEventAggregator, 'clear') sandbox.stub(agent.customEventAggregator, 'clear') - - t.context.agent = agent - t.context.sandbox = sandbox + ctx.nr.sandbox = sandbox }) - t.afterEach(() => { - helper.unloadAgent(t.context.agent) - t.context.sandbox.restore() + t.afterEach((ctx) => { + helper.unloadAgent(ctx.nr.agent) + ctx.nr.sandbox.restore() }) - t.test('should clear queries on _resetQueries', (t) => { - const { agent } = t.context + await t.test('should clear queries on _resetQueries', (t) => { + const { agent } = t.nr agent._resetQueries() - t.equal(agent.queries.clear.callCount, 1) - t.end() + assert.equal(agent.queries.clear.callCount, 1) }) - t.test('should clear all errors and reconfigure error traces and events on _resetErrors', (t) => { - const { agent } = t.context - agent._resetErrors() - t.equal(agent.errors.clearAll.callCount, 1) - t.equal(agent.errors.traceAggregator.reconfigure.callCount, 1) - t.equal(agent.errors.eventAggregator.reconfigure.callCount, 1) - t.end() - }) + await t.test( + 'should clear errors and reconfigure error traces and events on _resetErrors', + (t) => { + const { agent } = t.nr + agent._resetErrors() + assert.equal(agent.errors.clearAll.callCount, 1) + assert.equal(agent.errors.traceAggregator.reconfigure.callCount, 1) + assert.equal(agent.errors.eventAggregator.reconfigure.callCount, 1) + } + ) - t.test('should clear transaction events on _resetEvents', (t) => { - const { agent } = t.context + await t.test('should clear transaction events on _resetEvents', (t) => { + const { agent } = t.nr agent._resetEvents() - t.equal(agent.transactionEventAggregator.clear.callCount, 1) - t.end() + assert.equal(agent.transactionEventAggregator.clear.callCount, 1) }) - t.test('should clear custom events on _resetCustomEvents', (t) => { - const { agent } = t.context + await t.test('should clear custom events on _resetCustomEvents', (t) => { + const { agent } = t.nr agent._resetCustomEvents() - t.equal(agent.customEventAggregator.clear.callCount, 1) - t.end() + assert.equal(agent.customEventAggregator.clear.callCount, 1) }) }) diff --git a/test/unit/agent/intrinsics.test.js b/test/unit/agent/intrinsics.test.js index ba2289566a..264d206e15 100644 --- a/test/unit/agent/intrinsics.test.js +++ b/test/unit/agent/intrinsics.test.js @@ -5,172 +5,157 @@ 'use strict' -const tap = require('tap') -const helper = require('../../lib/agent_helper.js') +const test = require('node:test') +const assert = require('node:assert') const sinon = require('sinon') +const helper = require('../../lib/agent_helper.js') const Transaction = require('../../../lib/transaction') const crossAgentTests = require('../../lib/cross_agent_tests/cat/cat_map.json') -const cat = require('../../../lib/util/cat.js') +const CAT = require('../../../lib/util/cat.js') const NAMES = require('../../../lib/metrics/names.js') -tap.test('when CAT is disabled (default agent settings)', (t) => { - t.autoend() - - let agent = null - - t.beforeEach(() => { - agent = helper.loadMockedAgent() +test('when CAT is disabled (default agent settings)', async (t) => { + t.beforeEach((ctx) => { + ctx.nr = {} + ctx.nr.agent = helper.loadMockedAgent() }) - t.afterEach(() => { - helper.unloadAgent(agent) - agent = null + t.afterEach((ctx) => { + helper.unloadAgent(ctx.nr.agent) }) - crossAgentTests.forEach(function (test) { - t.test(test.name + ' tx event should only contain non-CAT intrinsic attrs', (t) => { + for await (const cat of crossAgentTests) { + await t.test(cat.name + ' tx event should only contain non-CAT intrinsic attrs', (t) => { + const { agent } = t.nr const expectedDuration = 0.02 const expectedTotalTime = 0.03 - const start = Date.now() + const tx = getMockTransaction(agent, cat, start, expectedDuration, expectedTotalTime) + const attrs = agent._addIntrinsicAttrsFromTransaction(tx) - const trans = getMockTransaction(agent, test, start, expectedDuration, expectedTotalTime) - - const attrs = agent._addIntrinsicAttrsFromTransaction(trans) - - t.same(Object.keys(attrs), [ - 'webDuration', - 'timestamp', - 'name', + assert.deepStrictEqual(Object.keys(attrs).sort(), [ 'duration', - 'totalTime', - 'type', 'error', - 'traceId', 'guid', + 'name', 'priority', - 'sampled' + 'sampled', + 'timestamp', + 'totalTime', + 'traceId', + 'type', + 'webDuration' ]) - t.equal(attrs.duration, expectedDuration) - t.equal(attrs.webDuration, expectedDuration) - t.equal(attrs.totalTime, expectedTotalTime) + assert.equal(attrs.duration, expectedDuration) + assert.equal(attrs.webDuration, expectedDuration) + assert.equal(attrs.totalTime, expectedTotalTime) - t.equal(attrs.timestamp, start) - t.equal(attrs.name, test.transactionName) - t.equal(attrs.type, 'Transaction') - t.equal(attrs.error, false) - - t.end() + assert.equal(attrs.timestamp, start) + assert.equal(attrs.name, cat.transactionName) + assert.equal(attrs.type, 'Transaction') + assert.equal(attrs.error, false) }) - }) + } - t.test('includes queueDuration', (t) => { - const transaction = new Transaction(agent) - transaction.measure(NAMES.QUEUETIME, null, 100) - const attrs = agent._addIntrinsicAttrsFromTransaction(transaction) - t.equal(attrs.queueDuration, 0.1) + await t.test('includes queueDuration', (t) => { + const { agent } = t.nr + const tx = new Transaction(agent) + tx.measure(NAMES.QUEUETIME, null, 100) - t.end() + const attrs = agent._addIntrinsicAttrsFromTransaction(tx) + assert.equal(attrs.queueDuration, 0.1) }) - t.test('includes externalDuration', (t) => { - const transaction = new Transaction(agent) - transaction.measure(NAMES.EXTERNAL.ALL, null, 100) - const attrs = agent._addIntrinsicAttrsFromTransaction(transaction) - t.equal(attrs.externalDuration, 0.1) + await t.test('includes externalDuration', (t) => { + const { agent } = t.nr + const tx = new Transaction(agent) + tx.measure(NAMES.EXTERNAL.ALL, null, 100) - t.end() + const attrs = agent._addIntrinsicAttrsFromTransaction(tx) + assert.equal(attrs.externalDuration, 0.1) }) - t.test('includes databaseDuration', (t) => { - const transaction = new Transaction(agent) - transaction.measure(NAMES.DB.ALL, null, 100) - const attrs = agent._addIntrinsicAttrsFromTransaction(transaction) - t.equal(attrs.databaseDuration, 0.1) + await t.test('includes databaseDuration', (t) => { + const { agent } = t.nr + const tx = new Transaction(agent) + tx.measure(NAMES.DB.ALL, null, 100) - t.end() + const attrs = agent._addIntrinsicAttrsFromTransaction(tx) + assert.equal(attrs.databaseDuration, 0.1) }) - t.test('includes externalCallCount', (t) => { - const transaction = new Transaction(agent) - transaction.measure(NAMES.EXTERNAL.ALL, null, 100) - transaction.measure(NAMES.EXTERNAL.ALL, null, 100) - const attrs = agent._addIntrinsicAttrsFromTransaction(transaction) - t.equal(attrs.externalCallCount, 2) + await t.test('includes externalCallCount', (t) => { + const { agent } = t.nr + const tx = new Transaction(agent) + tx.measure(NAMES.EXTERNAL.ALL, null, 100) + tx.measure(NAMES.EXTERNAL.ALL, null, 100) - t.end() + const attrs = agent._addIntrinsicAttrsFromTransaction(tx) + assert.equal(attrs.externalCallCount, 2) }) - t.test('includes databaseDuration', (t) => { - const transaction = new Transaction(agent) - transaction.measure(NAMES.DB.ALL, null, 100) - transaction.measure(NAMES.DB.ALL, null, 100) - const attrs = agent._addIntrinsicAttrsFromTransaction(transaction) - t.equal(attrs.databaseCallCount, 2) + await t.test('includes databaseCallCount', (t) => { + const { agent } = t.nr + const tx = new Transaction(agent) + tx.measure(NAMES.DB.ALL, null, 100) + tx.measure(NAMES.DB.ALL, null, 100) - t.end() + const attrs = agent._addIntrinsicAttrsFromTransaction(tx) + assert.equal(attrs.databaseCallCount, 2) }) - t.test('should call transaction.hasErrors() for error attribute', (t) => { - const transaction = new Transaction(agent) + await t.test('should call transaction.hasErrors() for error attribute', (t) => { + const { agent } = t.nr + const tx = new Transaction(agent) let mock = null let attrs = null - mock = sinon.mock(transaction) + mock = sinon.mock(tx) mock.expects('hasErrors').returns(true) - attrs = agent._addIntrinsicAttrsFromTransaction(transaction) + attrs = agent._addIntrinsicAttrsFromTransaction(tx) mock.verify() mock.restore() - t.equal(true, attrs.error) + assert.equal(attrs.error, true) - mock = sinon.mock(transaction) + mock = sinon.mock(tx) mock.expects('hasErrors').returns(false) - attrs = agent._addIntrinsicAttrsFromTransaction(transaction) + attrs = agent._addIntrinsicAttrsFromTransaction(tx) mock.verify() mock.restore() - t.equal(false, attrs.error) - - t.end() + assert.equal(attrs.error, false) }) }) -tap.test('when CAT is enabled', (t) => { - t.autoend() - - let agent = null +test('when CAT is enabled', async (t) => { + const expectedDurationsInSeconds = [0.03, 0.15, 0.5] - t.beforeEach(() => { - // App name from test data - agent = helper.loadMockedAgent({ + t.beforeEach((ctx) => { + ctx.nr = {} + ctx.nr.agent = helper.loadMockedAgent({ apdex_t: 0.05, cross_application_tracer: { enabled: true }, distributed_tracing: { enabled: false } }) - agent.config.applications = function newFake() { + ctx.nr.agent.config.applications = function newFake() { return ['testAppName'] } }) - t.afterEach(() => { - helper.unloadAgent(agent) - agent = null + t.afterEach((ctx) => { + helper.unloadAgent(ctx.nr.agent) }) - const expectedDurationsInSeconds = [0.03, 0.15, 0.5] - - crossAgentTests.forEach(function (test, index) { - t.test(test.name + ' tx event should contain all intrinsic attrs', (t) => { - const idx = index % expectedDurationsInSeconds.length + for (let i = 0; i < crossAgentTests.length; i += 1) { + const cat = crossAgentTests[i] + await t.test(cat.name + ' tx event should contain all intrinsic attrs', (t) => { + const { agent } = t.nr + const idx = i % expectedDurationsInSeconds.length const expectedDuration = expectedDurationsInSeconds[idx] - const expectedTotalTime = 0.03 - const start = Date.now() - const trans = getMockTransaction(agent, test, start, expectedDuration, expectedTotalTime) - - const attrs = agent._addIntrinsicAttrsFromTransaction(trans) - + const tx = getMockTransaction(agent, cat, start, expectedDuration, expectedTotalTime) + const attrs = agent._addIntrinsicAttrsFromTransaction(tx) const keys = [ 'webDuration', 'timestamp', @@ -188,57 +173,62 @@ tap.test('when CAT is enabled', (t) => { 'nr.apdexPerfZone' ] - for (let i = 0; i < test.nonExpectedIntrinsicFields.length; ++i) { - keys.splice(keys.indexOf(test.nonExpectedIntrinsicFields[i]), 1) + for (let j = 0; j < cat.nonExpectedIntrinsicFields.length; ++j) { + keys.splice(keys.indexOf(cat.nonExpectedIntrinsicFields[j]), 1) } - if (!test.expectedIntrinsicFields['nr.pathHash']) { + if (Object.hasOwn(cat.expectedIntrinsicFields, 'nr.pathHash') === false) { keys.splice(keys.indexOf('nr.apdexPerfZone'), 1) } - t.same(Object.keys(attrs), keys) - - t.equal(attrs.duration, expectedDuration) - t.equal(attrs.webDuration, expectedDuration) - t.equal(attrs.totalTime, expectedTotalTime) - t.equal(attrs.duration, expectedDuration) - t.equal(attrs.timestamp, start) - t.equal(attrs.name, test.transactionName) - t.equal(attrs.type, 'Transaction') - t.equal(attrs.error, false) - t.equal(attrs['nr.guid'], test.expectedIntrinsicFields['nr.guid']) - t.equal(attrs['nr.pathHash'], test.expectedIntrinsicFields['nr.pathHash']) - t.equal(attrs['nr.referringPathHash'], test.expectedIntrinsicFields['nr.referringPathHash']) - t.equal(attrs['nr.tripId'], test.expectedIntrinsicFields['nr.tripId']) - - t.equal( - attrs['nr.referringTransactionGuid'], - test.expectedIntrinsicFields['nr.referringTransactionGuid'] + assert.deepStrictEqual(Object.keys(attrs), keys) + + assert.equal(attrs.duration, expectedDuration) + assert.equal(attrs.webDuration, expectedDuration) + assert.equal(attrs.totalTime, expectedTotalTime) + assert.equal(attrs.duration, expectedDuration) + assert.equal(attrs.timestamp, start) + assert.equal(attrs.name, cat.transactionName) + assert.equal(attrs.type, 'Transaction') + assert.equal(attrs.error, false) + assert.equal(attrs['nr.guid'], cat.expectedIntrinsicFields['nr.guid']) + assert.equal(attrs['nr.pathHash'], cat.expectedIntrinsicFields['nr.pathHash']) + assert.equal( + attrs['nr.referringPathHash'], + cat.expectedIntrinsicFields['nr.referringPathHash'] + ) + assert.equal(attrs['nr.tripId'], cat.expectedIntrinsicFields['nr.tripId']) + + assert.equal( + cat.expectedIntrinsicFields['nr.referringTransactionGuid'], + attrs['nr.referringTransactionGuid'] ) - t.equal( - attrs['nr.alternatePathHashes'], - test.expectedIntrinsicFields['nr.alternatePathHashes'] + assert.equal( + cat.expectedIntrinsicFields['nr.alternatePathHashes'], + attrs['nr.alternatePathHashes'] ) - if (test.expectedIntrinsicFields['nr.pathHash']) { + if (Object.hasOwn(cat.expectedIntrinsicFields, 'nr.pathHash') === true) { // nr.apdexPerfZone not specified in the test, this is used to exercise it. + const attr = attrs['nr.apdexPerfZone'] switch (idx) { - case 0: - t.equal(attrs['nr.apdexPerfZone'], 'S') + case 0: { + assert.equal(attr, 'S') break - case 1: - t.equal(attrs['nr.apdexPerfZone'], 'T') + } + case 1: { + assert.equal(attr, 'T') break - case 2: - t.equal(attrs['nr.apdexPerfZone'], 'F') + } + case 2: { + assert.equal(attr, 'F') break + } } } - - t.end() }) - }) + } }) function getMockTransaction(agent, test, start, durationInSeconds, totalTimeInSeconds) { @@ -264,10 +254,10 @@ function getMockTransaction(agent, test, start, durationInSeconds, totalTimeInSe // CAT data if (test.inboundPayload) { - cat.assignCatToTransaction(test.inboundPayload[0], test.inboundPayload, transaction) + CAT.assignCatToTransaction(test.inboundPayload[0], test.inboundPayload, transaction) } else { // Simulate the headers being unparsable or not existing - cat.assignCatToTransaction(null, null, transaction) + CAT.assignCatToTransaction(null, null, transaction) } if (test.outboundRequests) { diff --git a/test/unit/agent/synthetics.test.js b/test/unit/agent/synthetics.test.js index 5b702767a5..004d53bc88 100644 --- a/test/unit/agent/synthetics.test.js +++ b/test/unit/agent/synthetics.test.js @@ -5,27 +5,29 @@ 'use strict' -const tap = require('tap') +const test = require('node:test') +const assert = require('node:assert') +const promiseResolvers = require('../../lib/promise-resolvers') const helper = require('../../lib/agent_helper') -tap.test('synthetics transaction traces', (t) => { - t.autoend() - - let agent - - t.beforeEach(() => { - agent = helper.loadMockedAgent({ +test('synthetics transaction traces', async (t) => { + t.beforeEach((ctx) => { + ctx.nr = {} + ctx.nr.agent = helper.loadMockedAgent({ trusted_account_ids: [357] }) }) - t.afterEach(() => { - helper.unloadAgent(agent) + t.afterEach((ctx) => { + helper.unloadAgent(ctx.nr.agent) }) - t.test('should include synthetic intrinsics if header is set', (t) => { - helper.runInTransaction(agent, function (txn) { - txn.syntheticsData = { + await t.test('should include synthetic intrinsics if header is set', async (t) => { + const { agent } = t.nr + const { promise, resolve } = promiseResolvers() + + helper.runInTransaction(agent, function (tx) { + tx.syntheticsData = { version: 1, accountId: 357, resourceId: 'resId', @@ -33,13 +35,15 @@ tap.test('synthetics transaction traces', (t) => { monitorId: 'monId' } - txn.end() - const trace = txn.trace - t.equal(trace.intrinsics.synthetics_resource_id, 'resId') - t.equal(trace.intrinsics.synthetics_job_id, 'jobId') - t.equal(trace.intrinsics.synthetics_monitor_id, 'monId') + tx.end() + const trace = tx.trace + assert.equal(trace.intrinsics.synthetics_resource_id, 'resId') + assert.equal(trace.intrinsics.synthetics_job_id, 'jobId') + assert.equal(trace.intrinsics.synthetics_monitor_id, 'monId') - t.end() + resolve() }) + + await promise }) }) diff --git a/third_party_manifest.json b/third_party_manifest.json index cae8878a3e..0d4b90f1a3 100644 --- a/third_party_manifest.json +++ b/third_party_manifest.json @@ -1,5 +1,5 @@ { - "lastUpdated": "Fri Jul 26 2024 17:29:52 GMT-0400 (Eastern Daylight Time)", + "lastUpdated": "Wed Jul 31 2024 09:30:40 GMT-0400 (Eastern Daylight Time)", "projectName": "New Relic Node Agent", "projectUrl": "https://github.com/newrelic/node-newrelic", "includeOptDeps": true, @@ -239,15 +239,15 @@ "publisher": "AWS SDK for JavaScript Team", "url": "https://aws.amazon.com/javascript/" }, - "@aws-sdk/s3-request-presigner@3.614.0": { + "@aws-sdk/s3-request-presigner@3.616.0": { "name": "@aws-sdk/s3-request-presigner", - "version": "3.614.0", + "version": "3.616.0", "range": "^3.556.0", "licenses": "Apache-2.0", "repoUrl": "https://github.com/aws/aws-sdk-js-v3", - "versionedRepoUrl": "https://github.com/aws/aws-sdk-js-v3/tree/v3.614.0", + "versionedRepoUrl": "https://github.com/aws/aws-sdk-js-v3/tree/v3.616.0", "licenseFile": "node_modules/@aws-sdk/s3-request-presigner/LICENSE", - "licenseUrl": "https://github.com/aws/aws-sdk-js-v3/blob/v3.614.0/LICENSE", + "licenseUrl": "https://github.com/aws/aws-sdk-js-v3/blob/v3.616.0/LICENSE", "licenseTextSource": "file", "publisher": "AWS SDK for JavaScript Team", "url": "https://aws.amazon.com/javascript/" @@ -493,15 +493,15 @@ "publisher": "Michael Radionov", "url": "https://github.com/mradionov" }, - "eslint-plugin-jsdoc@48.7.0": { + "eslint-plugin-jsdoc@48.8.3": { "name": "eslint-plugin-jsdoc", - "version": "48.7.0", + "version": "48.8.3", "range": "^48.0.5", "licenses": "BSD-3-Clause", "repoUrl": "https://github.com/gajus/eslint-plugin-jsdoc", - "versionedRepoUrl": "https://github.com/gajus/eslint-plugin-jsdoc/tree/v48.7.0", + "versionedRepoUrl": "https://github.com/gajus/eslint-plugin-jsdoc/tree/v48.8.3", "licenseFile": "node_modules/eslint-plugin-jsdoc/LICENSE", - "licenseUrl": "https://github.com/gajus/eslint-plugin-jsdoc/blob/v48.7.0/LICENSE", + "licenseUrl": "https://github.com/gajus/eslint-plugin-jsdoc/blob/v48.8.3/LICENSE", "licenseTextSource": "file", "publisher": "Gajus Kuizinas", "email": "gajus@gajus.com", @@ -737,6 +737,19 @@ "email": "i@izs.me", "url": "http://blog.izs.me/" }, + "self-cert@2.0.0": { + "name": "self-cert", + "version": "2.0.0", + "range": "^2.0.0", + "licenses": "MIT", + "repoUrl": "https://github.com/jsumners/self-cert", + "versionedRepoUrl": "https://github.com/jsumners/self-cert/tree/v2.0.0", + "licenseFile": "node_modules/self-cert/Readme.md", + "licenseUrl": "https://github.com/jsumners/self-cert/blob/v2.0.0/Readme.md", + "licenseTextSource": "spdx", + "publisher": "James Sumners", + "email": "james.sumners@gmail.com" + }, "should@13.2.3": { "name": "should", "version": "13.2.3", @@ -750,15 +763,15 @@ "publisher": "TJ Holowaychuk", "email": "tj@vision-media.ca" }, - "sinon@4.5.0": { + "sinon@5.1.1": { "name": "sinon", - "version": "4.5.0", - "range": "^4.5.0", + "version": "5.1.1", + "range": "^5.1.1", "licenses": "BSD-3-Clause", "repoUrl": "https://github.com/sinonjs/sinon", - "versionedRepoUrl": "https://github.com/sinonjs/sinon/tree/v4.5.0", + "versionedRepoUrl": "https://github.com/sinonjs/sinon/tree/v5.1.1", "licenseFile": "node_modules/sinon/LICENSE", - "licenseUrl": "https://github.com/sinonjs/sinon/blob/v4.5.0/LICENSE", + "licenseUrl": "https://github.com/sinonjs/sinon/blob/v5.1.1/LICENSE", "licenseTextSource": "file", "publisher": "Christian Johansen" }, From 0465628bdde3eb6b381a90f7b0a1362723f2e41c Mon Sep 17 00:00:00 2001 From: James Sumners Date: Wed, 31 Jul 2024 09:54:51 -0400 Subject: [PATCH 2/2] fix sinon mistake --- test/unit/instrumentation/mysql/wrapCreatePoolCluster.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/instrumentation/mysql/wrapCreatePoolCluster.test.js b/test/unit/instrumentation/mysql/wrapCreatePoolCluster.test.js index 8227aa98b2..bdc3a65a7c 100644 --- a/test/unit/instrumentation/mysql/wrapCreatePoolCluster.test.js +++ b/test/unit/instrumentation/mysql/wrapCreatePoolCluster.test.js @@ -38,7 +38,7 @@ tap.test('wrapCreatePoolCluster', (t) => { } mockPoolCluster = { - of: sinon.stub.returns(), + of: sinon.stub().returns(), getConnection: sinon.stub().returns() }