diff --git a/test/unit/transaction.test.js b/test/unit/transaction.test.js index 8741415ea5..fa74ac8342 100644 --- a/test/unit/transaction.test.js +++ b/test/unit/transaction.test.js @@ -6,13 +6,6 @@ 'use strict' const tap = require('tap') -// TODO: convert to normal tap style. -// Below allows use of mocha DSL with tap runner. -tap.mochaGlobals() - -const chai = require('chai') -const should = chai.should() -const expect = chai.expect const helper = require('../lib/agent_helper') const API = require('../../api') const AttributeFilter = require('../../lib/config/attribute-filter') @@ -23,623 +16,630 @@ const Segment = require('../../lib/transaction/trace/segment') const hashes = require('../../lib/util/hashes') const sinon = require('sinon') -describe('Transaction', function () { +tap.test('Transaction unit tests', (t) => { + t.autoend() + let agent = null - let contextManager = null - let trans = null + let txn = null - beforeEach(function () { - agent = helper.loadMockedAgent({ - attributes: { enabled: true } - }) - contextManager = helper.getContextManager() - trans = new Transaction(agent) + t.beforeEach(function () { + agent = helper.loadMockedAgent() + txn = new Transaction(agent) }) - afterEach(function () { + t.afterEach(() => { helper.unloadAgent(agent) }) - it('should require an agent to create new transactions', function () { - expect(function () { - return new Transaction() - }).throws(/must be bound to the agent/) - }) - - it('should create a trace on demand', function () { - const trace = trans.trace - expect(trace).instanceOf(Trace) - expect(trans.trace).equal(trace) - }) - - it('should have at most one associated trace', function () { - const trace = trans.trace - expect(trace).not.instanceof(Array) - }) + t.test('basic transaction tests', (t) => { + t.throws( + () => { + return new Transaction() + }, + /must be bound to the agent/, + 'should require an agent to create new transactions' + ) - it('should hand its metrics off to the agent upon finalization', function (done) { - agent.on('transactionFinished', function (inner) { - expect(inner.metrics).equal(trans.metrics) + const trace = txn.trace + t.ok(trace instanceof Trace, 'should create a trace on demand') + t.notOk(trace instanceof Array, 'should have at most one associated trace') - return done() + agent.on('transactionFinished', (inner) => { + t.equal( + inner.metrics, + txn.metrics, + 'should hand its metrics off to the agent upon finalization' + ) + t.end() }) - trans.end() + txn.end() }) - describe('when distributed tracing is enabled', function () { - beforeEach(function () { - agent.config.distributed_tracing.enabled = true - }) + t.test('with DT enabled, should produce span events when finalizing', (t) => { + agent.config.distributed_tracing.enabled = true - afterEach(function () { - agent.config.distributed_tracing.enabled = false + agent.once('transactionFinished', () => { + t.equal(agent.spanEventAggregator.length, 1, 'should have a span event') }) - - it('should produce span events when finalizing', function (done) { - agent.once('transactionFinished', function () { - expect(agent.spanEventAggregator.length).to.equal(1) - - return done() - }) - helper.runInTransaction(agent, function (txn) { - const childSegment = txn.trace.add('child') - childSegment.start() - - txn.end() - }) + helper.runInTransaction(agent, function (inner) { + const childSegment = inner.trace.add('child') + childSegment.start() + inner.end() }) - it('should not produce span events when ignored', function (done) { - agent.once('transactionFinished', function () { - expect(agent.spanEventAggregator.length).to.equal(0) - - return done() - }) - - helper.runInTransaction(agent, function (txn) { - const childSegment = txn.trace.add('child') - childSegment.start() - - txn.ignore = true - txn.end() - }) - }) + t.end() }) - it('should hand itself off to the agent upon finalization', function (done) { - agent.on('transactionFinished', function (inner) { - expect(inner).equal(trans) + t.test('with DT enabled, should not produce span events when ignored', (t) => { + agent.config.distributed_tracing.enabled = true - return done() + agent.once('transactionFinished', () => { + t.equal(agent.spanEventAggregator.length, 0, 'should have no span events') + }) + helper.runInTransaction(agent, function (inner) { + const childSegment = inner.trace.add('child') + childSegment.start() + inner.ignore = true + inner.end() }) - trans.end() + t.end() }) - it('should flush logs on end', function (done) { - sinon.spy(trans.logs, 'flush') + t.test('handing itself off to the agent upon finalization', (t) => { agent.on('transactionFinished', (inner) => { - expect(inner.logs.flush.callCount).to.equal(1) - done() + t.same(inner, txn, 'should have the same transaction') + t.end() }) - trans.logs.add('log-line1') - trans.logs.add('log-line2') - trans.end() + txn.end() }) - it('should not flush logs when transaction is ignored', function (done) { - sinon.spy(trans.logs, 'flush') + t.test('should flush logs on end', (t) => { + sinon.spy(txn.logs, 'flush') agent.on('transactionFinished', (inner) => { - expect(inner.logs.flush.callCount).to.equal(0) - done() + t.equal(inner.logs.flush.callCount, 1, 'should call `flush` once') + t.end() }) - trans.logs.add('log-line1') - trans.logs.add('log-line2') - trans.ignore = true - trans.end() + txn.logs.add('log-line1') + txn.logs.add('log-line2') + txn.end() }) - describe('upon creation', function () { - it('should have an ID', function () { - should.exist(trans.id) - }) - - it('should have associated metrics', function () { - should.exist(trans.metrics) + t.test('should not flush logs when transaction is ignored', (t) => { + sinon.spy(txn.logs, 'flush') + agent.on('transactionFinished', (inner) => { + t.equal(inner.logs.flush.callCount, 0, 'should not call `flush`') + t.end() }) - it('should be timing its duration', function () { - return expect(trans.timer.isActive()).true - }) + txn.logs.add('log-line1') + txn.logs.add('log-line2') + txn.ignore = true + txn.end() + }) - it('should have no associated URL (for hidden class)', function () { - expect(trans.url).equal(null) - }) + t.test('initial transaction attributes', (t) => { + t.ok(txn.id, 'should have an ID') + t.ok(txn.metrics, 'should have associated metrics') + t.ok(txn.timer.isActive(), 'should be timing its duration') + t.equal(txn.url, null, 'should have no associated URL (for hidden class)') + t.equal(txn.name, null, 'should have no name set (for hidden class)') + t.equal(txn.nameState.getName(), null, 'should have no PARTIAL name set (for hidden class)') + t.equal(txn.statusCode, null, 'should have no HTTP status code set (for hidden class)') + t.equal(txn.error, null, 'should have no error attached (for hidden class)') + t.equal(txn.verb, null, 'should have no HTTP method / verb set (for hidden class)') + t.notOk(txn.ignore, 'should not be ignored by default (for hidden class)') + t.equal(txn.sampled, null, 'should not have a sampled state set') + t.end() + }) - it('should have no name set (for hidden class)', function () { - expect(trans.name).equal(null) - }) + t.test('with associated metrics', (t) => { + t.ok(txn.metrics instanceof Metrics, 'should have metrics') + t.not(txn.metrics, getMetrics(agent), 'should manage its own independent of the agent') + t.equal( + getMetrics(agent).apdexT, + txn.metrics.apdexT, + 'should have the same apdex threshold as the agent' + ) + t.equal(agent.mapper, txn.metrics.mapper, 'should have the same metrics mapper as the agent') + t.end() + }) - it('should have no PARTIAL name set (for hidden class)', function () { - expect(trans.nameState.getName()).equal(null) - }) + t.test('web transactions', (t) => { + txn.type = Transaction.TYPES.BG + t.notOk(txn.isWeb(), 'should know when it is not a web transaction') + txn.type = Transaction.TYPES.WEB + t.ok(txn.isWeb(), 'should know when it is a web transaction') + t.end() + }) - it('should have no HTTP status code set (for hidden class)', function () { - expect(trans.statusCode).equal(null) - }) + t.test('when dealing with individual metrics', (t) => { + let tt = new Transaction(agent) + tt.measure('Custom/Test01') + t.ok(tt.metrics.getMetric('Custom/Test01'), 'should add metrics by name') - it('should have no error attached (for hidden class)', function () { - expect(trans.error).equal(null) - }) + tt.end() - it('should have no HTTP method / verb set (for hidden class)', function () { - expect(trans.verb).equal(null) - }) + const TRACE_NAME = 'Custom/Test06' + const SLEEP_DURATION = 43 + tt = new Transaction(agent) - it('should not be ignored by default (for hidden class)', function () { - return expect(trans.ignore).false - }) + tt.measure(TRACE_NAME, null, SLEEP_DURATION) + tt.measure(TRACE_NAME, null, SLEEP_DURATION - 5) - it('should not have a sampled state set', function () { - expect(trans.sampled).to.equal(null) - }) - }) + const statistics = tt.metrics.getMetric(TRACE_NAME) + t.equal( + statistics.callCount, + 2, + 'should allow multiple overlapping metric measurements for same name' + ) + t.ok(statistics.max > (SLEEP_DURATION - 1) / 1000, 'should measure at least 42 milliseconds') - describe('with associated metrics', function () { - it('should manage its own independent of the agent', function () { - expect(trans.metrics).instanceOf(Metrics) - expect(trans.metrics).not.equal(getMetrics(agent)) - }) + tt.end() - it('should have the same apdex threshold as the agent', function () { - expect(getMetrics(agent).apdexT).equal(trans.metrics.apdexT) - }) + tt = new Transaction(agent) + tt.measure('Custom/Test16', null, 65) + tt.end() - it('should have the same metrics mapper as the agent', function () { - expect(agent.mapper).equal(trans.metrics.mapper) - }) - }) + const metrics = tt.metrics.getMetric('Custom/Test16') + t.equal(metrics.total, 0.065, 'should allow manual setting of metric durations') - it('should know when it is not a web transaction', function () { - const tx = new Transaction(agent) - tx.type = Transaction.TYPES.BG - expect(tx.isWeb()).to.be.false + t.end() }) - it('should know when it is a web transaction', function () { - const tx = new Transaction(agent) - tx.type = Transaction.TYPES.WEB - expect(tx.isWeb()).to.be.true - }) + t.test('when setting apdex for key transactions', (t) => { + txn._setApdex('Apdex/TestController/key', 1200, 667) + const metric = txn.metrics.getMetric('Apdex/TestController/key') - describe('when dealing with individual metrics', function () { - it('should add metrics by name', function () { - const tt = new Transaction(agent) + t.equal(metric.apdexT, 0.667, 'should set apdexT to the key transaction apdexT') + t.equal(metric.satisfying, 0, 'should not have satisfied') + t.equal(metric.tolerating, 1, 'should have been tolerated') + t.equal(metric.frustrating, 0, 'should not have frustrated') - tt.measure('Custom/Test01') - should.exist(tt.metrics.getMetric('Custom/Test01')) + txn._setApdex('Apdex/TestController/another', 1200) + const another = txn.metrics.getMetric('Apdex/TestController/another') + t.equal(another.apdexT, 0.1, 'should not require a key transaction apdexT') + t.end() + }) +}) - tt.end() +tap.test('Transaction naming tests', (t) => { + t.autoend() + let agent = null + let txn = null + function beforeEach() { + agent = helper.loadMockedAgent({ + attributes: { + enabled: true, + include: ['request.parameters.*'] + } }) + agent.config.emit('attributes.include') + txn = new Transaction(agent) + } - it('should allow multiple overlapping metric measurements for same name', function () { - const TRACE_NAME = 'Custom/Test06' - const SLEEP_DURATION = 43 - const tt = new Transaction(agent) + t.afterEach(() => { + helper.unloadAgent(agent) + }) - tt.measure(TRACE_NAME, null, SLEEP_DURATION) - tt.measure(TRACE_NAME, null, SLEEP_DURATION - 5) + t.test('getName', (t) => { + t.autoend() + t.beforeEach(beforeEach) - const statistics = tt.metrics.getMetric(TRACE_NAME) - expect(statistics.callCount).to.equal(2) - expect(statistics.max).above((SLEEP_DURATION - 1) / 1000) + t.test('base test', (t) => { + t.equal(txn.getName(), null, 'should return `null` if there is no name, partialName, or url') + t.end() }) - it('should allow manual setting of metric durations', function () { - const tt = new Transaction(agent) + t.test('partial name should remain unset if it was not set before', (t) => { + txn.url = '/some/pathname' + t.equal(txn.nameState.getName(), null, 'should have no namestate') + t.equal(txn.getName(), 'NormalizedUri/*', 'should have a default partial name') + t.equal(txn.nameState.getName(), null, 'should still have no namestate') + t.end() + }) - tt.measure('Custom/Test16', null, 65) - tt.end() + t.test('should return the right name if partialName and url are set', (t) => { + txn.nameState.setPrefix('Framework') + txn.nameState.setVerb('verb') + txn.nameState.appendPath('route') + txn.url = '/route' + t.equal(txn.getName(), 'WebFrameworkUri/Framework/VERB/route', 'should have full name') + t.equal(txn.nameState.getName(), 'Framework/VERB/route', 'should have the partial name') + t.end() + }) - const metrics = tt.metrics.getMetric('Custom/Test16') - expect(metrics.total).equal(0.065) + t.test('should return the name if it has already been set', (t) => { + txn.setPartialName('foo/bar') + t.equal(txn.getName(), 'foo/bar', 'name should be as set') + t.end() }) }) - describe('when being named', function () { - beforeEach(function () { - agent.config.attributes.enabled = true - agent.config.attributes.include = ['request.parameters.*'] - agent.config.emit('attributes.include') + t.test('isIgnored', (t) => { + t.autoend() + t.beforeEach(beforeEach) - trans = new Transaction(agent) + t.test('should return true if a transaction is ignored by a rule', (t) => { + const api = new API(agent) + api.addIgnoringRule('^/test/') + txn.finalizeNameFromUri('/test/string?do=thing&another=thing', 200) + t.ok(txn.isIgnored(), 'should ignore the transaction') + t.end() }) + }) - describe('getName', function () { - it('should return `null` if there is no name, partialName, or url', function () { - expect(trans.getName()).to.be.null - }) - - it('partial name should remain unset if it was not set before', function () { - trans.url = '/some/pathname' - expect(trans.nameState.getName()).to.be.null - expect(trans.getName()).to.equal('NormalizedUri/*') - expect(trans.nameState.getName()).to.be.null - }) - - it('should return the right name if partialName and url are set', function () { - trans.nameState.setPrefix('Framework') - trans.nameState.setVerb('verb') - trans.nameState.appendPath('route') - trans.url = '/route' - expect(trans.getName()).to.equal('WebFrameworkUri/Framework/VERB/route') - expect(trans.nameState.getName()).to.equal('Framework/VERB/route') - }) + t.test('getFullName', (t) => { + t.autoend() + t.beforeEach(beforeEach) - it('should return the name if it has already been set', function () { - trans.setPartialName('foo/bar') - expect(trans.getName()).equal('foo/bar') - }) + t.test('should return null if it does not have name, partialName, or url', (t) => { + t.equal(txn.getFullName(), null, 'should not have a full name') + t.end() }) - describe('isIgnored', function () { - it('should return true if a transaction is ignored by a rule', function () { - const api = new API(agent) - api.addIgnoringRule('^/test/') - trans.finalizeNameFromUri('/test/string?do=thing&another=thing', 200) - expect(trans.isIgnored()).true - }) + t.test('partial name should remain unset if it was not set before', (t) => { + txn.url = '/some/pathname' + t.equal(txn.nameState.getName(), null, 'should have no namestate') + t.equal( + txn.getFullName(), + 'WebTransaction/NormalizedUri/*', + 'should have a default full name' + ) + t.equal(txn.nameState.getName(), null, 'should still have no namestate') + t.end() }) - describe('getFullName', function () { - it('should return null if it does not have name, partialName, or url', function () { - expect(trans.getFullName()).equal(null) - }) - - it('partial name should remain unset if it was not set before', function () { - trans.url = '/some/pathname' - expect(trans.nameState.getName()).to.equal(null) - expect(trans.getFullName()).to.equal('WebTransaction/NormalizedUri/*') - expect(trans.nameState.getName()).to.equal(null) - }) - - it('should return the right name if partialName and url are set', function () { - trans.nameState.setPrefix('Framework') - trans.nameState.setVerb('verb') - trans.nameState.appendPath('route') - trans.url = '/route' - expect(trans.getFullName()).to.equal('WebTransaction/WebFrameworkUri/Framework/VERB/route') - expect(trans.nameState.getName()).to.equal('Framework/VERB/route') - }) - - it('should return the name if it has already been set', function () { - trans.name = 'OtherTransaction/foo/bar' - expect(trans.getFullName()).to.equal('OtherTransaction/foo/bar') - }) - - it('should return the forced name if set', function () { - trans.name = 'FullName' - trans._partialName = 'PartialName' - trans.forceName = 'ForcedName' - expect(trans.getFullName()).to.equal('WebTransaction/ForcedName') - }) + t.test('should return the right name if partialName and url are set', (t) => { + txn.nameState.setPrefix('Framework') + txn.nameState.setVerb('verb') + txn.nameState.appendPath('route') + txn.url = '/route' + t.equal( + txn.getFullName(), + 'WebTransaction/WebFrameworkUri/Framework/VERB/route', + 'should have full name' + ) + t.equal(txn.nameState.getName(), 'Framework/VERB/route', 'should have full name') + t.end() }) - describe('with no partial name set', function () { - it('produces a normalized (backstopped) name when status is 200', function () { - trans.finalizeNameFromUri('/test/string?do=thing&another=thing', 200) - expect(trans.name).equal('WebTransaction/NormalizedUri/*') - }) - - it('produces a normalized partial name when status is 200', function () { - trans.finalizeNameFromUri('/test/string?do=thing&another=thing', 200) - expect(trans._partialName).equal('NormalizedUri/*') - }) - - it('passes through status code when status is 200', function () { - trans.finalizeNameFromUri('/test/string?do=thing&another=thing', 200) - expect(trans.statusCode).equal(200) - }) - - it('produces a non-error name when status code is ignored', function () { - agent.config.error_collector.ignore_status_codes = [404, 500] - trans.finalizeNameFromUri('/test/string?do=thing&another=thing', 500) - expect(trans.name).equal('WebTransaction/NormalizedUri/*') - }) - - it('produces a non-error partial name when status code is ignored', function () { - agent.config.error_collector.ignore_status_codes = [404, 500] - trans.finalizeNameFromUri('/test/string?do=thing&another=thing', 500) - expect(trans._partialName).equal('NormalizedUri/*') - }) - - it('passes through status code when status is 404', function () { - trans.finalizeNameFromUri('/test/string?do=thing&another=thing', 404) - expect(trans.statusCode).equal(404) - }) + t.test('should return the name if it has already been set', (t) => { + txn.name = 'OtherTransaction/foo/bar' + t.equal(txn.getFullName(), 'OtherTransaction/foo/bar') + t.end() + }) - it('produces a `not found` partial name when status is 404', function () { - trans.nameState.setName('Expressjs', 'GET', '/') - trans.finalizeNameFromUri('/test/string?do=thing&another=thing', 404) - expect(trans._partialName).equal('Expressjs/GET/(not found)') - }) + t.test('should return the forced name if set', (t) => { + txn.name = 'FullName' + txn._partialName = 'PartialName' + txn.forceName = 'ForcedName' + t.equal(txn.getFullName(), 'WebTransaction/ForcedName') + t.end() + }) + }) - it('produces a `not found` name when status is 404', function () { - trans.nameState.setName('Expressjs', 'GET', '/') - trans.finalizeNameFromUri('/test/string?do=thing&another=thing', 404) - expect(trans.name).equal('WebTransaction/Expressjs/GET/(not found)') - }) + t.test('with no partial name set', (t) => { + t.autoend() + t.beforeEach(beforeEach) - it('passes through status code when status is 405', function () { - trans.finalizeNameFromUri('/test/string?do=thing&another=thing', 405) - expect(trans.statusCode).equal(405) - }) + t.test('produces a normalized (backstopped) name when status is 200', (t) => { + txn.finalizeNameFromUri('/test/string?do=thing&another=thing', 200) + t.equal(txn.name, 'WebTransaction/NormalizedUri/*') + t.end() + }) - it('produces a `method not allowed` partial name when status is 405', function () { - trans.nameState.setName('Expressjs', 'GET', '/') - trans.finalizeNameFromUri('/test/string?do=thing&another=thing', 405) - expect(trans._partialName).equal('Expressjs/GET/(method not allowed)') - }) + t.test('produces a normalized partial name when status is 200', (t) => { + txn.finalizeNameFromUri('/test/string?do=thing&another=thing', 200) + t.equal(txn._partialName, 'NormalizedUri/*') + t.end() + }) - it('produces a `method not allowed` name when status is 405', function () { - trans.nameState.setName('Expressjs', 'GET', '/') - trans.finalizeNameFromUri('/test/string?do=thing&another=thing', 405) - expect(trans.name).equal('WebTransaction/Expressjs/GET/(method not allowed)') - }) + t.test('passes through status code when status is 200', (t) => { + txn.finalizeNameFromUri('/test/string?do=thing&another=thing', 200) + t.equal(txn.statusCode, 200) + t.end() + }) - it('produces a name based on 501 status code message', function () { - trans.finalizeNameFromUri('/test/string?do=thing&another=thing', 501) - expect(trans.name).equal('WebTransaction/WebFrameworkUri/(not implemented)') - }) + t.test('produces a non-error name when status code is ignored', (t) => { + agent.config.error_collector.ignore_status_codes = [404, 500] + txn.finalizeNameFromUri('/test/string?do=thing&another=thing', 500) + t.equal(txn.name, 'WebTransaction/NormalizedUri/*') + t.end() + }) - it('produces a regular partial name based on 501 status code message', function () { - trans.finalizeNameFromUri('/test/string?do=thing&another=thing', 501) - expect(trans._partialName).equal('WebFrameworkUri/(not implemented)') - }) + t.test('produces a non-error partial name when status code is ignored', (t) => { + agent.config.error_collector.ignore_status_codes = [404, 500] + txn.finalizeNameFromUri('/test/string?do=thing&another=thing', 500) + t.equal(txn._partialName, 'NormalizedUri/*') + t.end() + }) - it('passes through status code when status is 501', function () { - trans.finalizeNameFromUri('/test/string?do=thing&another=thing', 501) - expect(trans.statusCode).equal(501) - }) + t.test('passes through status code when status is 404', (t) => { + txn.finalizeNameFromUri('/test/string?do=thing&another=thing', 404) + t.equal(txn.statusCode, 404) + t.end() + }) - it('should update value from segment normalizer rules', function () { - const url = 'NormalizedUri/test/explicit/string/lyrics' - trans.forceName = url - trans.url = url - agent.txSegmentNormalizer.load([ - { prefix: 'WebTransaction/NormalizedUri', terms: ['test', 'string'] } - ]) - trans.finalizeNameFromUri(url, 200) - expect(trans.name).to.equal('WebTransaction/NormalizedUri/test/*/string/*') - }) + t.test('produces a `not found` partial name when status is 404', (t) => { + txn.nameState.setName('Expressjs', 'GET', '/') + txn.finalizeNameFromUri('/test/string?do=thing&another=thing', 404) + t.equal(txn._partialName, 'Expressjs/GET/(not found)') + t.end() }) - describe('with a custom partial name set', function () { - beforeEach(function () { - trans.nameState.setPrefix('Custom') - trans.nameState.appendPath('test') - }) + t.test('produces a `not found` name when status is 404', (t) => { + txn.nameState.setName('Expressjs', 'GET', '/') + txn.finalizeNameFromUri('/test/string?do=thing&another=thing', 404) + t.equal(txn.name, 'WebTransaction/Expressjs/GET/(not found)') + t.end() + }) - it('produces a custom name when status is 200', function () { - trans.finalizeNameFromUri('/test/string?do=thing&another=thing', 200) - expect(trans.name).equal('WebTransaction/Custom/test') - }) + t.test('passes through status code when status is 405', (t) => { + txn.finalizeNameFromUri('/test/string?do=thing&another=thing', 405) + t.equal(txn.statusCode, 405) + t.end() + }) - it('produces a partial name when status is 200', function () { - trans.finalizeNameFromUri('/test/string?do=thing&another=thing', 200) - expect(trans.nameState.getName()).equal('Custom/test') - }) + t.test('produces a `method not allowed` partial name when status is 405', (t) => { + txn.nameState.setName('Expressjs', 'GET', '/') + txn.finalizeNameFromUri('/test/string?do=thing&another=thing', 405) + t.equal(txn._partialName, 'Expressjs/GET/(method not allowed)') + t.end() + }) - it('should rename a transaction when told to by a rule', function () { - agent.transactionNameNormalizer.addSimple('^(WebTransaction/Custom)/test$', '$1/*') - trans.finalizeNameFromUri('/test/string?do=thing&another=thing', 200) - expect(trans.name).equal('WebTransaction/Custom/*') - }) + t.test('produces a `method not allowed` name when status is 405', (t) => { + txn.nameState.setName('Expressjs', 'GET', '/') + txn.finalizeNameFromUri('/test/string?do=thing&another=thing', 405) + t.equal(txn.name, 'WebTransaction/Expressjs/GET/(method not allowed)') + t.end() + }) - it('passes through status code when status is 200', function () { - trans.finalizeNameFromUri('/test/string?do=thing&another=thing', 200) - expect(trans.statusCode).equal(200) - }) + t.test('produces a name based on 501 status code message', (t) => { + txn.finalizeNameFromUri('/test/string?do=thing&another=thing', 501) + t.equal(txn.name, 'WebTransaction/WebFrameworkUri/(not implemented)') + t.end() + }) - it('keeps the custom name when error status is ignored', function () { - agent.config.error_collector.ignore_status_codes = [404, 500] - trans.finalizeNameFromUri('/test/string?do=thing&another=thing', 500) - expect(trans.name).equal('WebTransaction/Custom/test') - }) + t.test('produces a regular partial name based on 501 status code message', (t) => { + txn.finalizeNameFromUri('/test/string?do=thing&another=thing', 501) + t.equal(txn._partialName, 'WebFrameworkUri/(not implemented)') + t.end() + }) - it('keeps the custom partial name when error status is ignored', function () { - trans.finalizeNameFromUri('/test/string?do=thing&another=thing', 404) - expect(trans.nameState.getName()).equal('Custom/test') - }) + t.test('passes through status code when status is 501', (t) => { + txn.finalizeNameFromUri('/test/string?do=thing&another=thing', 501) + t.equal(txn.statusCode, 501) + t.end() + }) - it('passes through status code when status is 404', function () { - trans.finalizeNameFromUri('/test/string?do=thing&another=thing', 404) - expect(trans.statusCode).equal(404) - }) + t.test('should update value from segment normalizer rules', (t) => { + const url = 'NormalizedUri/test/explicit/string/lyrics' + txn.forceName = url + txn.url = url + agent.txSegmentNormalizer.load([ + { prefix: 'WebTransaction/NormalizedUri', terms: ['test', 'string'] } + ]) + txn.finalizeNameFromUri(url, 200) + t.equal(txn.name, 'WebTransaction/NormalizedUri/test/*/string/*') + t.end() + }) - it('produces the custom name even when status is 501', function () { - trans.finalizeNameFromUri('/test/string?do=thing&another=thing', 501) - expect(trans.name).equal('WebTransaction/Custom/test') - }) + t.test('should not scope web transactions to their URL', (t) => { + txn.finalizeNameFromUri('/test/1337?action=edit', 200) + t.not(txn.name, '/test/1337?action=edit') + t.not(txn.name, 'WebTransaction/Uri/test/1337') + t.end() + }) + }) - it('produces the custome partial name even when status is 501', function () { - trans.finalizeNameFromUri('/test/string?do=thing&another=thing', 501) - expect(trans.nameState.getName()).equal('Custom/test') - }) + t.test('with a custom partial name set', (t) => { + t.autoend() - it('passes through status code when status is 501', function () { - trans.finalizeNameFromUri('/test/string?do=thing&another=thing', 501) - expect(trans.statusCode).equal(501) - }) + t.beforeEach(() => { + beforeEach() + txn.nameState.setPrefix('Custom') + txn.nameState.appendPath('test') + agent.transactionNameNormalizer.rules = [] + }) - it('should ignore a transaction when told to by a rule', function () { - agent.transactionNameNormalizer.addSimple('^WebTransaction/Custom/test$') - trans.finalizeNameFromUri('/test/string?do=thing&another=thing', 200) - return expect(trans.isIgnored()).true - }) + t.test('produces a custom name when status is 200', (t) => { + txn.finalizeNameFromUri('/test/string?do=thing&another=thing', 200) + t.equal(txn.name, 'WebTransaction/Custom/test') + t.end() }) - }) - describe('when setting apdex for key transactions', function () { - let tx = null - let metric = null + t.test('produces a partial name when status is 200', (t) => { + txn.finalizeNameFromUri('/test/string?do=thing&another=thing', 200) + t.equal(txn.nameState.getName(), 'Custom/test') + t.end() + }) - before(function () { - tx = new Transaction(agent) - tx._setApdex('Apdex/TestController/key', 1200, 667) + t.test('should rename a transaction when told to by a rule', (t) => { + agent.transactionNameNormalizer.addSimple('^(WebTransaction/Custom)/test$', '$1/*') + txn.finalizeNameFromUri('/test/string?do=thing&another=thing', 200) + t.equal(txn.name, 'WebTransaction/Custom/*') + t.end() + }) - metric = tx.metrics.getMetric('Apdex/TestController/key') + t.test('passes through status code when status is 200', (t) => { + txn.finalizeNameFromUri('/test/string?do=thing&another=thing', 200) + t.equal(txn.statusCode, 200) + t.end() }) - it('should set apdexT to the key transaction apdexT', function () { - expect(metric.apdexT).equal(0.667) + t.test('keeps the custom name when error status is ignored', (t) => { + agent.config.error_collector.ignore_status_codes = [404, 500] + txn.finalizeNameFromUri('/test/string?do=thing&another=thing', 500) + t.equal(txn.name, 'WebTransaction/Custom/test') + t.end() }) - it('should not have satisfied', function () { - expect(metric.satisfying).equal(0) + t.test('keeps the custom partial name when error status is ignored', (t) => { + txn.finalizeNameFromUri('/test/string?do=thing&another=thing', 404) + t.equal(txn.nameState.getName(), 'Custom/test') + t.end() }) - it('should have been tolerated', function () { - expect(metric.tolerating).equal(1) + t.test('passes through status code when status is 404', (t) => { + txn.finalizeNameFromUri('/test/string?do=thing&another=thing', 404) + t.equal(txn.statusCode, 404) + t.end() }) - it('should not have frustrated', function () { - expect(metric.frustrating).equal(0) + t.test('produces the custom name even when status is 501', (t) => { + txn.finalizeNameFromUri('/test/string?do=thing&another=thing', 501) + t.equal(txn.name, 'WebTransaction/Custom/test') + t.end() }) - it('should not require a key transaction apdexT', function () { - tx._setApdex('Apdex/TestController/another', 1200) - const another = tx.metrics.getMetric('Apdex/TestController/another') - expect(another.apdexT).equal(0.1) + t.test('produces the custom partial name even when status is 501', (t) => { + txn.finalizeNameFromUri('/test/string?do=thing&another=thing', 501) + t.equal(txn.nameState.getName(), 'Custom/test') + t.end() }) - }) - describe('when producing a summary of the whole transaction', function () { - it('should produce a human-readable summary') - it('should produce a metrics summary suitable for the collector') - }) + t.test('passes through status code when status is 501', (t) => { + txn.finalizeNameFromUri('/test/string?do=thing&another=thing', 501) + t.equal(txn.statusCode, 501) + t.end() + }) - it('should not scope web transactions to their URL', function () { - const tx = new Transaction(agent) - tx.finalizeNameFromUri('/test/1337?action=edit', 200) - expect(tx.name).not.equal('/test/1337?action=edit') - expect(tx.name).not.equal('WebTransaction/Uri/test/1337') + t.test('should ignore a transaction when told to by a rule', (t) => { + agent.transactionNameNormalizer.addSimple('^WebTransaction/Custom/test$') + txn.finalizeNameFromUri('/test/string?do=thing&another=thing', 200) + t.ok(txn.isIgnored()) + t.end() + }) }) - describe('pathHashes', function () { - let transaction + t.test('pathHashes', (t) => { + t.autoend() + t.beforeEach(beforeEach) - beforeEach(function () { - transaction = new Transaction(agent) - }) - - it('should add up to 10 items to to pathHashes', function () { + t.test('should add up to 10 items to to pathHashes', (t) => { const toAdd = ['1', '2', '3', '4', '4', '5', '6', '7', '8', '9', '10', '11'] const expected = ['10', '9', '8', '7', '6', '5', '4', '3', '2', '1'] - toAdd.forEach(transaction.pushPathHash.bind(transaction)) - expect(transaction.pathHashes).deep.equal(expected) + toAdd.forEach(txn.pushPathHash.bind(txn)) + t.same(txn.pathHashes, expected) + t.end() }) - it('should not include current pathHash in alternatePathHashes', function () { - transaction.name = '/a/b/c' - transaction.referringPathHash = '/d/e/f' + t.test('should not include current pathHash in alternatePathHashes', (t) => { + txn.name = '/a/b/c' + txn.referringPathHash = '/d/e/f' const curHash = hashes.calculatePathHash( agent.config.applications()[0], - transaction.name, - transaction.referringPathHash + txn.name, + txn.referringPathHash ) - transaction.pathHashes = ['/a', curHash, '/a/b'] - expect(transaction.alternatePathHashes()).equal('/a,/a/b') - transaction.nameState.setPrefix(transaction.name) - transaction.name = null - transaction.pathHashes = ['/a', '/a/b'] - expect(transaction.alternatePathHashes()).equal('/a,/a/b') + txn.pathHashes = ['/a', curHash, '/a/b'] + t.equal(txn.alternatePathHashes(), '/a,/a/b') + txn.nameState.setPrefix(txn.name) + txn.name = null + txn.pathHashes = ['/a', '/a/b'] + t.equal(txn.alternatePathHashes(), '/a,/a/b') + t.end() }) - it('should return null when no alternate pathHashes exist', function () { - transaction.nameState.setPrefix('/a/b/c') - transaction.referringPathHash = '/d/e/f' + t.test('should return null when no alternate pathHashes exist', (t) => { + txn.nameState.setPrefix('/a/b/c') + txn.referringPathHash = '/d/e/f' const curHash = hashes.calculatePathHash( agent.config.applications()[0], - transaction.nameState.getName(), - transaction.referringPathHash + txn.nameState.getName(), + txn.referringPathHash ) - transaction.pathHashes = [curHash] - expect(transaction.alternatePathHashes()).equal(null) - transaction.pathHashes = [] - expect(transaction.alternatePathHashes()).equal(null) + txn.pathHashes = [curHash] + t.equal(txn.alternatePathHashes(), null) + txn.pathHashes = [] + t.equal(txn.alternatePathHashes(), null) + t.end() }) }) +}) - describe('hasErrors', function () { - let transaction - - beforeEach(function () { - transaction = new Transaction(agent) - }) +tap.test('Transaction methods', (t) => { + t.autoend() + let txn = null + let agent = null - it('should return true if exceptions property is not empty', function () { - expect(transaction.hasErrors()).to.be.false - transaction.exceptions.push(new Error()) - expect(transaction.hasErrors()).to.be.true + function bookends(t) { + t.beforeEach(() => { + agent = helper.loadMockedAgent() + txn = new Transaction(agent) }) - it('should return true if statusCode is an error', function () { - transaction.statusCode = 500 - expect(transaction.hasErrors()).to.be.true + t.afterEach(() => { + helper.unloadAgent(agent) }) - }) - - describe('isSampled', function () { - let transaction + } - beforeEach(function () { - transaction = new Transaction(agent) - }) + t.test('hasErrors', (t) => { + t.autoend() + bookends(t) - it('should be true when the transaction is sampled', function () { - // the first 10 transactions are sampled so this should be true - expect(transaction.isSampled()).to.be.true + t.test('should return true if exceptions property is not empty', (t) => { + t.notOk(txn.hasErrors()) + txn.exceptions.push(new Error()) + t.ok(txn.hasErrors()) + t.end() }) - it('should be false when the transaction is not sampled', function () { - transaction.priority = Infinity - transaction.sampled = false - expect(transaction.isSampled()).to.be.false + t.test('should return true if statusCode is an error', (t) => { + txn.statusCode = 500 + t.ok(txn.hasErrors()) + t.end() }) }) - describe('getIntrinsicAttributes', function () { - let transaction + t.test('isSampled', (t) => { + t.autoend() + bookends(t) - beforeEach(function () { - transaction = new Transaction(agent) + t.test('should be true when the transaction is sampled', (t) => { + // the first 10 transactions are sampled so this should be true + t.ok(txn.isSampled()) + t.end() }) - it('includes CAT attributes when enabled', function () { - transaction.agent.config.cross_application_tracer.enabled = true - transaction.agent.config.distributed_tracing.enabled = false - transaction.tripId = '3456' - transaction.referringTransactionGuid = '1234' - transaction.incomingCatId = '2345' + t.test('should be false when the transaction is not sampled', (t) => { + txn.priority = Infinity + txn.sampled = false + t.notOk(txn.isSampled()) + t.end() + }) + }) - const attributes = transaction.getIntrinsicAttributes() - expect(attributes.referring_transaction_guid).equal('1234') - expect(attributes.client_cross_process_id).equal('2345') - expect(attributes.path_hash).to.be.a('string') - expect(attributes.trip_id).equal('3456') + t.test('getIntrinsicAttributes', (t) => { + t.autoend() + bookends(t) + + t.test('includes CAT attributes when enabled', (t) => { + txn.agent.config.cross_application_tracer.enabled = true + txn.agent.config.distributed_tracing.enabled = false + txn.tripId = '3456' + txn.referringTransactionGuid = '1234' + txn.incomingCatId = '2345' + + const attributes = txn.getIntrinsicAttributes() + t.equal(attributes.referring_transaction_guid, '1234') + t.equal(attributes.client_cross_process_id, '2345') + t.type(attributes.path_hash, 'string') + t.equal(attributes.trip_id, '3456') + t.end() }) - it('includes Synthetics attributes', function () { - transaction.syntheticsData = { + t.test('includes Synthetics attributes', (t) => { + txn.syntheticsData = { version: 1, accountId: 123, resourceId: 'resId', @@ -647,627 +647,695 @@ describe('Transaction', function () { monitorId: 'monId' } - const attributes = transaction.getIntrinsicAttributes() - expect(attributes.synthetics_resource_id).equal('resId') - expect(attributes.synthetics_job_id).equal('jobId') - expect(attributes.synthetics_monitor_id).equal('monId') + const attributes = txn.getIntrinsicAttributes() + t.equal(attributes.synthetics_resource_id, 'resId') + t.equal(attributes.synthetics_job_id, 'jobId') + t.equal(attributes.synthetics_monitor_id, 'monId') + t.end() }) - it('returns different object every time', function () { - expect(transaction.getIntrinsicAttributes()).to.not.equal( - transaction.getIntrinsicAttributes() - ) + t.test('returns different object every time', (t) => { + t.not(txn.getIntrinsicAttributes(), txn.getIntrinsicAttributes()) + t.end() }) - it('includes distributed trace attributes', function () { - const attributes = transaction.getIntrinsicAttributes() - expect(transaction.priority.toString().length).to.be.at.most(8) + t.test('includes distributed trace attributes', (t) => { + const attributes = txn.getIntrinsicAttributes() + t.ok(txn.priority.toString().length <= 8) - expect(attributes).to.have.property('guid', transaction.id) - expect(attributes).to.have.property('traceId', transaction.traceId) - expect(attributes).to.have.property('priority', transaction.priority) - expect(attributes).to.have.property('sampled', true) + t.has(attributes, { + guid: txn.id, + traceId: txn.traceId, + priority: txn.priority, + sampled: true + }) + t.end() }) }) - describe('getResponseDurationInMillis', function () { - let transaction - - beforeEach(function () { - transaction = new Transaction(agent) - }) + t.test('getResponseDurationInMillis', (t) => { + t.autoend() + bookends(t) - describe('for web transactions', function () { - it('should use the time until transaction.end() is called', function () { - transaction.url = 'someUrl' + t.test('for web transactions', (t) => { + txn.url = 'someUrl' - // add a segment that will end after the transaction ends - const childSegment = transaction.trace.add('child') - childSegment.start() + // add a segment that will end after the txn ends + const childSegment = txn.trace.add('child') + childSegment.start() - transaction.end() - childSegment.end() + txn.end() + childSegment.end() - // response time should equal the transaction timer duration - expect(transaction.getResponseTimeInMillis()).to.equal( - transaction.timer.getDurationInMillis() - ) - }) + // response time should equal the transaction timer duration + t.equal( + txn.getResponseTimeInMillis(), + txn.timer.getDurationInMillis(), + 'should use the time until transaction.end() is called' + ) + t.end() }) - describe('for background transactions', function () { - it('should report response time equal to trace duration', function () { - // add a segment that will end after the transaction ends - transaction.type = Transaction.TYPES.BG - const bgTransactionSegment = transaction.trace.add('backgroundWork') - bgTransactionSegment.start() + t.test('for background transactions', (t) => { + // add a segment that will end after the transaction ends + txn.type = Transaction.TYPES.BG + const bgTransactionSegment = txn.trace.add('backgroundWork') + bgTransactionSegment.start() - transaction.end() - bgTransactionSegment.end() + txn.end() + bgTransactionSegment.end() - // response time should equal the full duration of the trace - expect(transaction.getResponseTimeInMillis()).to.equal( - transaction.trace.getDurationInMillis() - ) - }) + // response time should equal the full duration of the trace + t.equal( + txn.getResponseTimeInMillis(), + txn.trace.getDurationInMillis(), + 'should report response time equal to trace duration' + ) + t.end() }) }) +}) - describe('_acceptDistributedTracePayload', function () { - let tx = null +tap.test('_acceptDistributedTracePayload', (t) => { + t.autoend() + let txn = null + let agent = null - beforeEach(function () { - agent.recordSupportability = sinon.spy() - agent.config.distributed_tracing.enabled = true - agent.config.trusted_account_key = '1' + t.beforeEach(function () { + agent = helper.loadMockedAgent({ + distributed_tracing: { enabled: true } + }) + agent.config.trusted_account_key = '1' + // Clear deprecated values just to be extra sure. + agent.config._process_id = null + agent.config.account_ids = null - // Clear deprecated values just to be extra sure. - agent.config.cross_process_id = null - agent.config.trusted_account_ids = null + agent.recordSupportability = sinon.spy() - tx = new Transaction(agent) - }) + txn = new Transaction(agent) + }) - afterEach(function () { - agent.recordSupportability.restore && agent.recordSupportability.restore() - }) + t.afterEach(function () { + helper.unloadAgent(agent) + agent = null + }) - it('records supportability metric if no payload was passed', function () { - tx._acceptDistributedTracePayload(null) - expect(tx.agent.recordSupportability.args[0][0]).to.equal( - 'DistributedTrace/AcceptPayload/Ignored/Null' - ) - }) + t.test('records supportability metric if no payload was passed', (t) => { + txn._acceptDistributedTracePayload(null) + t.equal( + txn.agent.recordSupportability.args[0][0], + 'DistributedTrace/AcceptPayload/Ignored/Null' + ) + t.end() + }) - describe('when already marked as distributed trace', function () { - it('records `Multiple` supportability metric if parentId exists', function () { - tx.isDistributedTrace = true - tx.parentId = 'exists' + t.test( + 'when already marked as distributed trace, records `Multiple` supportability metric if parentId exists', + (t) => { + txn.isDistributedTrace = true + txn.parentId = 'exists' - tx._acceptDistributedTracePayload({}) - expect(tx.agent.recordSupportability.args[0][0]).to.equal( - 'DistributedTrace/AcceptPayload/Ignored/Multiple' - ) - }) + txn._acceptDistributedTracePayload({}) + t.equal( + txn.agent.recordSupportability.args[0][0], + 'DistributedTrace/AcceptPayload/Ignored/Multiple' + ) + t.end() + } + ) - it('records `CreateBeforeAccept` metric if parentId does not exist', function () { - tx.isDistributedTrace = true + t.test( + 'when already marked as distributed trace, records `CreateBeforeAccept` metric if parentId does not exist', + (t) => { + txn.isDistributedTrace = true - tx._acceptDistributedTracePayload({}) - expect(tx.agent.recordSupportability.args[0][0]).to.equal( - 'DistributedTrace/AcceptPayload/Ignored/CreateBeforeAccept' - ) - }) - }) + txn._acceptDistributedTracePayload({}) + t.equal( + txn.agent.recordSupportability.args[0][0], + 'DistributedTrace/AcceptPayload/Ignored/CreateBeforeAccept' + ) + t.end() + } + ) - it('should not accept payload if no configured trusted key', function () { - tx.agent.config.trusted_account_key = null - tx.agent.config.account_id = null + t.test('should not accept payload if no configured trusted key', (t) => { + txn.agent.config.trusted_account_key = null + txn.agent.config.account_id = null + + const data = { + ac: '1', + ty: 'App', + tx: txn.id, + tr: txn.id, + ap: 'test', + ti: Date.now() - 1 + } - const data = { - ac: '1', - ty: 'App', - tx: tx.id, - tr: tx.id, - ap: 'test', - ti: Date.now() - 1 - } + txn._acceptDistributedTracePayload({ v: [0, 1], d: data }) - tx._acceptDistributedTracePayload({ v: [0, 1], d: data }) + t.equal(txn.agent.recordSupportability.args[0][0], 'DistributedTrace/AcceptPayload/Exception') + t.notOk(txn.isDistributedTrace) + t.end() + }) - expect(tx.agent.recordSupportability.args[0][0]).to.equal( - 'DistributedTrace/AcceptPayload/Exception' - ) - expect(tx.isDistributedTrace).to.not.be.true - }) + t.test('should not accept payload if DT disabled', (t) => { + txn.agent.config.distributed_tracing.enabled = false - it('should not accept payload if DT disabled', function () { - tx.agent.config.distributed_tracing.enabled = false + const data = { + ac: '1', + ty: 'App', + tx: txn.id, + tr: txn.id, + ap: 'test', + ti: Date.now() - 1 + } - const data = { - ac: '1', - ty: 'App', - tx: tx.id, - tr: tx.id, - ap: 'test', - ti: Date.now() - 1 - } + txn._acceptDistributedTracePayload({ v: [0, 1], d: data }) - tx._acceptDistributedTracePayload({ v: [0, 1], d: data }) + t.equal(txn.agent.recordSupportability.args[0][0], 'DistributedTrace/AcceptPayload/Exception') + t.notOk(txn.isDistributedTrace) + t.end() + }) - expect(tx.agent.recordSupportability.args[0][0]).to.equal( - 'DistributedTrace/AcceptPayload/Exception' - ) - expect(tx.isDistributedTrace).to.not.be.true - }) + t.test('should accept payload if config valid and CAT disabled', (t) => { + txn.agent.config.cross_application_tracer.enabled = false - it('should accept payload if config valid and CAT disabled', function () { - tx.agent.config.cross_application_tracer.enabled = false + const data = { + ac: '1', + ty: 'App', + tx: txn.id, + tr: txn.id, + ap: 'test', + ti: Date.now() - 1 + } - const data = { - ac: '1', - ty: 'App', - tx: tx.id, - tr: tx.id, - ap: 'test', - ti: Date.now() - 1 - } + txn._acceptDistributedTracePayload({ v: [0, 1], d: data }) - tx._acceptDistributedTracePayload({ v: [0, 1], d: data }) + t.ok(txn.isDistributedTrace) + t.end() + }) - expect(tx.isDistributedTrace).to.be.true - }) + t.test('fails if payload version is above agent-supported version', (t) => { + txn._acceptDistributedTracePayload({ v: [1, 0] }) + t.equal( + txn.agent.recordSupportability.args[0][0], + 'DistributedTrace/AcceptPayload/ParseException' + ) + t.notOk(txn.isDistributedTrace) + t.end() + }) - it('fails if payload version is above agent-supported version', function () { - tx._acceptDistributedTracePayload({ v: [1, 0] }) - expect(tx.agent.recordSupportability.args[0][0]).to.equal( - 'DistributedTrace/AcceptPayload/ParseException' - ) - expect(tx.isDistributedTrace).to.not.be.true + t.test('fails if payload account id is not in trusted ids', (t) => { + const data = { + ac: 2, + ty: 'App', + id: txn.id, + tr: txn.id, + ap: 'test', + ti: Date.now() + } + + txn._acceptDistributedTracePayload({ + v: [0, 1], + d: data }) + t.equal( + txn.agent.recordSupportability.args[0][0], + 'DistributedTrace/AcceptPayload/Ignored/UntrustedAccount' + ) + t.notOk(txn.isDistributedTrace) + t.end() + }) - it('fails if payload account id is not in trusted ids', function () { - const data = { - ac: 2, - ty: 'App', - id: tx.id, - tr: tx.id, - ap: 'test', - ti: Date.now() + t.test('fails if payload data is missing required keys', (t) => { + txn._acceptDistributedTracePayload({ + v: [0, 1], + d: { + ac: 1 } - - tx._acceptDistributedTracePayload({ - v: [0, 1], - d: data - }) - expect(tx.agent.recordSupportability.args[0][0]).to.equal( - 'DistributedTrace/AcceptPayload/Ignored/UntrustedAccount' - ) - expect(tx.isDistributedTrace).to.not.be.true }) + t.equal( + txn.agent.recordSupportability.args[0][0], + 'DistributedTrace/AcceptPayload/ParseException' + ) + t.notOk(txn.isDistributedTrace) + t.end() + }) - it('fails if payload data is missing required keys', function () { - tx._acceptDistributedTracePayload({ - v: [0, 1], - d: { - ac: 1 - } - }) - expect(tx.agent.recordSupportability.args[0][0]).to.equal( - 'DistributedTrace/AcceptPayload/ParseException' - ) - expect(tx.isDistributedTrace).to.not.be.true - }) + t.test('takes the priority and sampled state from the incoming payload', (t) => { + const data = { + ac: '1', + ty: 'App', + id: txn.id, + tr: txn.id, + ap: 'test', + pr: 1.9999999, + sa: true, + ti: Date.now() + } - it('takes the priority and sampled state from the incoming payload', function () { - const data = { - ac: '1', - ty: 'App', - id: tx.id, - tr: tx.id, - ap: 'test', - pr: 1.9999999, - sa: true, - ti: Date.now() - } + txn._acceptDistributedTracePayload({ v: [0, 1], d: data }) + t.ok(txn.sampled) + t.equal(txn.priority, data.pr) + // Should not truncate accepted priority + t.equal(txn.priority.toString().length, 9) + t.end() + }) - tx._acceptDistributedTracePayload({ v: [0, 1], d: data }) - expect(tx.sampled).to.be.true - expect(tx.priority).to.equal(data.pr) - // Should not truncate accepted priority - expect(tx.priority.toString().length).to.equal(9) - }) + t.test('does not take the distributed tracing data if priority is missing', (t) => { + const data = { + ac: 1, + ty: 'App', + id: txn.id, + tr: txn.id, + ap: 'test', + sa: true, + ti: Date.now() + } - it('does not take the distributed tracing data if priority is missing', function () { - const data = { - ac: 1, - ty: 'App', - id: tx.id, - tr: tx.id, - ap: 'test', - sa: true, - ti: Date.now() - } + txn._acceptDistributedTracePayload({ v: [0, 1], d: data }) + t.equal(txn.priority, null) + t.equal(txn.sampled, null) + t.end() + }) - tx._acceptDistributedTracePayload({ v: [0, 1], d: data }) - expect(tx.priority).to.equal(null) - expect(tx.sampled).to.equal(null) - }) + t.test('stores payload props on transaction', (t) => { + const data = { + ac: '1', + ty: 'App', + tx: txn.id, + tr: txn.id, + ap: 'test', + ti: Date.now() - 1 + } - it('stores payload props on transaction', function () { - const data = { - ac: '1', - ty: 'App', - tx: tx.id, - tr: tx.id, - ap: 'test', - ti: Date.now() - 1 - } + txn._acceptDistributedTracePayload({ v: [0, 1], d: data }) + t.equal(txn.agent.recordSupportability.args[0][0], 'DistributedTrace/AcceptPayload/Success') + t.equal(txn.parentId, data.tx) + t.equal(txn.parentType, data.ty) + t.equal(txn.traceId, data.tr) + t.ok(txn.isDistributedTrace) + t.ok(txn.parentTransportDuration > 0) + t.end() + }) - tx._acceptDistributedTracePayload({ v: [0, 1], d: data }) - expect(tx.agent.recordSupportability.args[0][0]).to.equal( - 'DistributedTrace/AcceptPayload/Success' - ) - expect(tx.parentId).to.equal(data.tx) - expect(tx.parentType).to.equal(data.ty) - expect(tx.traceId).to.equal(data.tr) - expect(tx.isDistributedTrace).to.be.true - expect(tx.parentTransportDuration).to.be.greaterThan(0) - }) - - it('should 0 transport duration when receiving payloads from the future', function () { - const data = { - ac: '1', - ty: 'App', - tx: tx.id, - id: tx.trace.root.id, - tr: tx.id, - ap: 'test', - ti: Date.now() + 1000 - } + t.test('should 0 transport duration when receiving payloads from the future', (t) => { + const data = { + ac: '1', + ty: 'App', + tx: txn.id, + id: txn.trace.root.id, + tr: txn.id, + ap: 'test', + ti: Date.now() + 1000 + } - tx._acceptDistributedTracePayload({ v: [0, 1], d: data }) - expect(tx.agent.recordSupportability.args[0][0]).to.equal( - 'DistributedTrace/AcceptPayload/Success' - ) - expect(tx.parentId).to.equal(data.tx) - expect(tx.parentSpanId).to.equal(tx.trace.root.id) - expect(tx.parentType).to.equal(data.ty) - expect(tx.traceId).to.equal(data.tr) - expect(tx.isDistributedTrace).to.be.true - expect(tx.parentTransportDuration).to.equal(0) - }) + txn._acceptDistributedTracePayload({ v: [0, 1], d: data }) + t.equal(txn.agent.recordSupportability.args[0][0], 'DistributedTrace/AcceptPayload/Success') + t.equal(txn.parentId, data.tx) + t.equal(txn.parentSpanId, txn.trace.root.id) + t.equal(txn.parentType, data.ty) + t.equal(txn.traceId, data.tr) + t.ok(txn.isDistributedTrace) + t.equal(txn.parentTransportDuration, 0) + t.end() }) + t.end() +}) - describe('_getParsedPayload', function () { - let tx = null - let payload = null +tap.test('_getParsedPayload', (t) => { + t.autoend() - beforeEach(function () { - agent.recordSupportability = sinon.spy() - tx = new Transaction(agent) - payload = JSON.stringify({ - test: 'payload' - }) - }) + let txn = null + let agent = null + let payload = null - afterEach(function () { - agent.recordSupportability.restore && agent.recordSupportability.restore() + t.beforeEach(() => { + agent = helper.loadMockedAgent({ + distributed_tracing: { enabled: true } }) - it('returns parsed JSON object', function () { - const res = tx._getParsedPayload(payload) - expect(res).to.deep.equal({ test: 'payload' }) + agent.recordSupportability = sinon.spy() + txn = new Transaction(agent) + payload = JSON.stringify({ + test: 'payload' }) + }) + + t.afterEach(() => { + helper.unloadAgent(agent) + agent = null + }) + + t.test('returns parsed JSON object', (t) => { + const res = txn._getParsedPayload(payload) + t.same(res, { test: 'payload' }) + t.end() + }) + + t.test('returns parsed object from base64 string', (t) => { + txn.agent.config.encoding_key = 'test' + + const res = txn._getParsedPayload(payload.toString('base64')) + t.same(res, { test: 'payload' }) + t.end() + }) + + t.test('returns null if string is invalid JSON', (t) => { + const res = txn._getParsedPayload('{invalid JSON string}') + t.equal(res, null) + t.equal( + txn.agent.recordSupportability.args[0][0], + 'DistributedTrace/AcceptPayload/ParseException' + ) + t.end() + }) + + t.test('returns null if decoding fails', (t) => { + txn.agent.config.encoding_key = 'test' + payload = hashes.obfuscateNameUsingKey(payload, 'some other key') - it('returns parsed object from base64 string', function () { - tx.agent.config.encoding_key = 'test' + const res = txn._getParsedPayload(payload) + t.equal(res, null) + t.end() + }) +}) + +tap.test('_createDistributedTracePayload', (t) => { + t.autoend() + + let txn = null + let agent = null + let contextManager = null - const res = tx._getParsedPayload(payload.toString('base64')) - expect(res).to.deep.equal({ test: 'payload' }) + t.beforeEach(() => { + agent = helper.loadMockedAgent({ + distributed_tracing: { enabled: true } }) - it('returns null if string is invalid JSON', function () { - const res = tx._getParsedPayload('{invalid JSON string}') - expect(res).to.be.null - expect(tx.agent.recordSupportability.args[0][0]).to.equal( - 'DistributedTrace/AcceptPayload/ParseException' - ) - }) + agent.recordSupportability = sinon.spy() + agent.config.account_id = '5678' + agent.config.primary_application_id = '1234' + agent.config.trusted_account_key = '5678' - it('returns null if decoding fails', function () { - tx.agent.config.encoding_key = 'test' - payload = hashes.obfuscateNameUsingKey(payload, 'some other key') + // Clear deprecated values just to be extra sure. + agent.config.cross_process_id = null + agent.config.trusted_account_ids = null - const res = tx._getParsedPayload(payload) - expect(res).to.be.null - }) + contextManager = helper.getContextManager() + txn = new Transaction(agent) }) - describe('_createDistributedTracePayload', function () { - let tx = null + t.afterEach(() => { + helper.unloadAgent(agent) + agent = null + }) - beforeEach(function () { - agent.recordSupportability = sinon.spy() - agent.config.distributed_tracing.enabled = true - agent.config.account_id = '5678' - agent.config.primary_application_id = '1234' - agent.config.trusted_account_key = '5678' + t.test('should not create payload when DT disabled', (t) => { + txn.agent.config.distributed_tracing.enabled = false - // Clear deprecated values just to be extra sure. - agent.config.cross_process_id = null - agent.config.trusted_account_ids = null + const payload = txn._createDistributedTracePayload().text() + t.equal(payload, '') + t.equal(txn.agent.recordSupportability.callCount, 0) + t.notOk(txn.isDistributedTrace) + t.end() + }) - tx = new Transaction(agent) - }) + t.test('should create payload when DT enabled and CAT disabled', (t) => { + txn.agent.config.cross_application_tracer.enabled = false - afterEach(function () { - agent.recordSupportability.restore && agent.recordSupportability.restore() - }) + const payload = txn._createDistributedTracePayload().text() - it('should not create payload when DT disabled', function () { - tx.agent.config.distributed_tracing.enabled = false + t.not(payload, null) + t.not(payload, '') + t.end() + }) - const payload = tx._createDistributedTracePayload().text() - expect(payload).to.equal('') - expect(tx.agent.recordSupportability.callCount).to.equal(0) - expect(tx.isDistributedTrace).to.not.be.true - }) + t.test('does not change existing priority', (t) => { + txn.priority = 999 + txn.sampled = false - it('should create payload when DT enabled and CAT disabled', function () { - tx.agent.config.cross_application_tracer.enabled = false + txn._createDistributedTracePayload() - const payload = tx._createDistributedTracePayload().text() + t.equal(txn.priority, 999) + t.notOk(txn.sampled) + t.end() + }) - expect(payload).to.not.be.null - expect(payload).to.not.equal('') - }) + t.test('sets the transaction as sampled if the trace is chosen', (t) => { + const payload = JSON.parse(txn._createDistributedTracePayload().text()) + t.equal(payload.d.sa, txn.sampled) + t.equal(payload.d.pr, txn.priority) + t.end() + }) - it('does not change existing priority', () => { - tx.priority = 999 - tx.sampled = false + t.test('adds the current span id as the parent span id', (t) => { + agent.config.span_events.enabled = true + contextManager.setContext(txn.trace.root) + txn.sampled = true + const payload = JSON.parse(txn._createDistributedTracePayload().text()) + t.equal(payload.d.id, txn.trace.root.id) + contextManager.setContext(null) + agent.config.span_events.enabled = false + t.end() + }) - tx._createDistributedTracePayload() + t.test('does not add the span id if the transaction is not sampled', (t) => { + agent.config.span_events.enabled = true + txn._calculatePriority() + txn.sampled = false + contextManager.setContext(txn.trace.root) + const payload = JSON.parse(txn._createDistributedTracePayload().text()) + t.equal(payload.d.id, undefined) + contextManager.setContext(null) + agent.config.span_events.enabled = false + t.end() + }) - expect(tx.priority).to.equal(999) - expect(tx.sampled).to.be.false - }) + t.test('returns stringified payload object', (t) => { + const payload = txn._createDistributedTracePayload().text() + t.type(payload, 'string') + t.equal(txn.agent.recordSupportability.args[0][0], 'DistributedTrace/CreatePayload/Success') + t.ok(txn.isDistributedTrace) + t.end() + }) +}) - it('sets the transaction as sampled if the trace is chosen', function () { - const payload = JSON.parse(tx._createDistributedTracePayload().text()) - expect(payload.d.sa).to.equal(tx.sampled) - expect(payload.d.pr).to.equal(tx.priority) - }) +tap.test('acceptDistributedTraceHeaders', (t) => { + t.autoend() - it('adds the current span id as the parent span id', function () { - agent.config.span_events.enabled = true - contextManager.setContext(tx.trace.root) - tx.sampled = true - const payload = JSON.parse(tx._createDistributedTracePayload().text()) - expect(payload.d.id).to.equal(tx.trace.root.id) - contextManager.setContext(null) - agent.config.span_events.enabled = false - }) + let agent = null - it('does not add the span id if the transaction is not sampled', function () { - agent.config.span_events.enabled = true - tx._calculatePriority() - tx.sampled = false - contextManager.setContext(tx.trace.root) - const payload = JSON.parse(tx._createDistributedTracePayload().text()) - expect(payload.d.id).to.be.undefined - contextManager.setContext(null) - agent.config.span_events.enabled = false - }) - - it('returns stringified payload object', function () { - const payload = tx._createDistributedTracePayload().text() - expect(typeof payload).to.equal('string') - expect(tx.agent.recordSupportability.args[0][0]).to.equal( - 'DistributedTrace/CreatePayload/Success' - ) - expect(tx.isDistributedTrace).to.be.true + t.beforeEach(() => { + agent = helper.loadMockedAgent({ + distributed_tracing: { enabled: true }, + span_events: { enabled: true } }) + agent.config.trusted_account_key = '1' }) - describe('acceptDistributedTraceHeaders', () => { - it('should accept a valid trace context traceparent header', () => { - agent.config.distributed_tracing.enabled = true - agent.config.trusted_account_key = '1' - agent.config.span_events.enabled = true + t.afterEach(() => { + helper.unloadAgent(agent) + agent = null + }) - const goodParent = '00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-00' + t.test('should accept a valid trace context traceparent header', (t) => { + const goodParent = '00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-00' - const headers = { - traceparent: goodParent - } + const headers = { + traceparent: goodParent + } - helper.runInTransaction(agent, function (txn) { - const childSegment = txn.trace.add('child') - childSegment.start() + helper.runInTransaction(agent, function (txn) { + const childSegment = txn.trace.add('child') + childSegment.start() - txn.acceptDistributedTraceHeaders('HTTP', headers) + txn.acceptDistributedTraceHeaders('HTTP', headers) - expect(txn.traceId).to.equal('4bf92f3577b34da6a3ce929d0e0e4736') - expect(txn.parentSpanId).to.equal('00f067aa0ba902b7') + t.equal(txn.traceId, '4bf92f3577b34da6a3ce929d0e0e4736') + t.equal(txn.parentSpanId, '00f067aa0ba902b7') - txn.end() - }) + txn.end() + t.end() }) + }) - it('should not accept invalid trace context traceparent header', () => { - agent.config.distributed_tracing.enabled = true - agent.config.trusted_account_key = '1' - agent.config.span_events.enabled = true - - helper.runInTransaction(agent, function (txn) { - const childSegment = txn.trace.add('child') - childSegment.start() + t.test('should not accept invalid trace context traceparent header', (t) => { + helper.runInTransaction(agent, function (txn) { + const childSegment = txn.trace.add('child') + childSegment.start() - const originalHeaders = createHeadersAndInsertTrace(txn) + const originalHeaders = createHeadersAndInsertTrace(txn) - const origTraceparent = originalHeaders.traceparent - const traceparent = 'asdlkfjasdl;fkja' - const tracestate = 'stuff' + const origTraceparent = originalHeaders.traceparent + const traceparent = 'asdlkfjasdl;fkja' + const tracestate = 'stuff' - const headers = { - traceparent, - tracestate - } + const headers = { + traceparent, + tracestate + } - txn.acceptDistributedTraceHeaders('HTTP', headers) + txn.acceptDistributedTraceHeaders('HTTP', headers) - const secondHeaders = createHeadersAndInsertTrace(txn) + const secondHeaders = createHeadersAndInsertTrace(txn) - expect(secondHeaders.traceparent).to.equal(origTraceparent) - txn.end() - }) + t.equal(secondHeaders.traceparent, origTraceparent) + txn.end() + t.end() }) + }) - it('should use newrelic format when no traceparent', () => { - const trustedAccountKey = '123' - - agent.config.distributed_tracing.enabled = true - agent.config.trusted_account_key = trustedAccountKey - agent.config.span_events.enabled = true - - const incomingTraceId = '6e2fea0b173fdad0' - const expectedTraceId = '0000000000000000' + incomingTraceId - - const newrelicDtData = { - v: [0, 1], - d: { - ty: 'Mobile', - ac: trustedAccountKey, - ap: '51424', - id: '5f474d64b9cc9b2a', - tr: incomingTraceId, - pr: 0.1234, - sa: true, - ti: '1482959525577', - tx: '27856f70d3d314b7' - } + t.test('should use newrelic format when no traceparent', (t) => { + const trustedAccountKey = '123' + agent.config.trusted_account_key = trustedAccountKey + + const incomingTraceId = '6e2fea0b173fdad0' + const expectedTraceId = '0000000000000000' + incomingTraceId + + const newrelicDtData = { + v: [0, 1], + d: { + ty: 'Mobile', + ac: trustedAccountKey, + ap: '51424', + id: '5f474d64b9cc9b2a', + tr: incomingTraceId, + pr: 0.1234, + sa: true, + ti: '1482959525577', + tx: '27856f70d3d314b7' } + } - helper.runInTransaction(agent, function (txn) { - const childSegment = txn.trace.add('child') - childSegment.start() + helper.runInTransaction(agent, function (txn) { + const childSegment = txn.trace.add('child') + childSegment.start() - const headers = { - newrelic: JSON.stringify(newrelicDtData) - } + const headers = { + newrelic: JSON.stringify(newrelicDtData) + } - txn.acceptDistributedTraceHeaders('HTTP', headers) + txn.acceptDistributedTraceHeaders('HTTP', headers) - expect(txn.isDistributedTrace).to.be.true - expect(txn.acceptedDistributedTrace).to.be.true + t.ok(txn.isDistributedTrace) + t.ok(txn.acceptedDistributedTrace) - const outboundHeaders = createHeadersAndInsertTrace(txn) - const splitData = outboundHeaders.traceparent.split('-') - const [, traceId] = splitData + const outboundHeaders = createHeadersAndInsertTrace(txn) + const splitData = outboundHeaders.traceparent.split('-') + const [, traceId] = splitData - expect(traceId).to.equal(expectedTraceId) - txn.end() - }) + t.equal(traceId, expectedTraceId) + txn.end() + t.end() }) + }) - it('should not throw error when headers is a string', () => { - const trustedAccountKey = '123' - - agent.config.distributed_tracing.enabled = true - agent.config.trusted_account_key = trustedAccountKey - agent.config.span_events.enabled = true + t.test('should not throw error when headers is a string', (t) => { + const trustedAccountKey = '123' + agent.config.trusted_account_key = trustedAccountKey - helper.runInTransaction(agent, function (txn) { - const childSegment = txn.trace.add('child') - childSegment.start() + helper.runInTransaction(agent, function (txn) { + const childSegment = txn.trace.add('child') + childSegment.start() - const headers = 'JUST A STRING' + const headers = 'JUST A STRING' - expect(function () { - txn.acceptDistributedTraceHeaders('HTTP', headers) - }).not.throws() + t.doesNotThrow(function () { + txn.acceptDistributedTraceHeaders('HTTP', headers) + }) - expect(txn.isDistributedTrace).to.be.null - expect(txn.acceptedDistributedTrace).to.be.null + t.equal(txn.isDistributedTrace, null) + t.equal(txn.acceptedDistributedTrace, null) - txn.end() - }) + txn.end() + t.end() }) + }) - it('should only accept the first tracecontext', () => { - agent.config.distributed_tracing.enabled = true - agent.config.trusted_account_key = '1' - agent.config.span_events.enabled = true - - const expectedTraceId = 'da8bc8cc6d062849b0efcf3c169afb5a' - const expectedParentSpanId = '7d3efb1b173fecfa' - const expectedAppId = '2827902' + t.test('should only accept the first tracecontext', (t) => { + const expectedTraceId = 'da8bc8cc6d062849b0efcf3c169afb5a' + const expectedParentSpanId = '7d3efb1b173fecfa' + const expectedAppId = '2827902' - const firstTraceContext = { - traceparent: `00-${expectedTraceId}-${expectedParentSpanId}-01`, - tracestate: `1@nr=0-0-1-${expectedAppId}-7d3efb1b173fecfa-e8b91a159289ff74-1-1.23456-1518469636035` - } + const firstTraceContext = { + traceparent: `00-${expectedTraceId}-${expectedParentSpanId}-01`, + tracestate: `1@nr=0-0-1-${expectedAppId}-7d3efb1b173fecfa-e8b91a159289ff74-1-1.23456-1518469636035` + } - const secondTraceContext = { - traceparent: '00-37375fc353f345b5801b166e31b76136-b4a07f08064ee8f9-00', - tracestate: '1@nr=0-0-1-3837903-b4a07f08064ee8f9-e8b91a159289ff74-0-0.123456-1518469636035' - } + const secondTraceContext = { + traceparent: '00-37375fc353f345b5801b166e31b76136-b4a07f08064ee8f9-00', + tracestate: '1@nr=0-0-1-3837903-b4a07f08064ee8f9-e8b91a159289ff74-0-0.123456-1518469636035' + } - helper.runInTransaction(agent, function (txn) { - const childSegment = txn.trace.add('child') - childSegment.start() + helper.runInTransaction(agent, function (txn) { + const childSegment = txn.trace.add('child') + childSegment.start() - txn.acceptDistributedTraceHeaders('HTTP', firstTraceContext) - txn.acceptDistributedTraceHeaders('HTTP', secondTraceContext) + txn.acceptDistributedTraceHeaders('HTTP', firstTraceContext) + txn.acceptDistributedTraceHeaders('HTTP', secondTraceContext) - expect(txn.traceId).to.equal(expectedTraceId) - expect(txn.parentSpanId).to.equal(expectedParentSpanId) - expect(txn.parentApp).to.equal('2827902') + t.equal(txn.traceId, expectedTraceId) + t.equal(txn.parentSpanId, expectedParentSpanId) + t.equal(txn.parentApp, '2827902') - txn.end() - }) + txn.end() + t.end() }) + }) - it('should not accept tracecontext after sending a trace', () => { - agent.config.distributed_tracing.enabled = true - agent.config.trusted_account_key = '1' - agent.config.span_events.enabled = true - - const unexpectedTraceId = 'da8bc8cc6d062849b0efcf3c169afb5a' - const unexpectedParentSpanId = '7d3efb1b173fecfa' - const unexpectedAppId = '2827902' + t.test('should not accept tracecontext after sending a trace', (t) => { + const unexpectedTraceId = 'da8bc8cc6d062849b0efcf3c169afb5a' + const unexpectedParentSpanId = '7d3efb1b173fecfa' + const unexpectedAppId = '2827902' - const firstTraceContext = { - traceparent: `00-${unexpectedTraceId}-${unexpectedParentSpanId}-01`, - tracestate: `1@nr=0-0-1-${unexpectedAppId}-7d3efb1b173fecfa-e8b91a159289ff74-1-1.23456-1518469636035` - } + const firstTraceContext = { + traceparent: `00-${unexpectedTraceId}-${unexpectedParentSpanId}-01`, + tracestate: `1@nr=0-0-1-${unexpectedAppId}-7d3efb1b173fecfa-e8b91a159289ff74-1-1.23456-1518469636035` + } - helper.runInTransaction(agent, function (txn) { - const childSegment = txn.trace.add('child') - childSegment.start() + helper.runInTransaction(agent, function (txn) { + const childSegment = txn.trace.add('child') + childSegment.start() - const outboundHeaders = {} - txn.insertDistributedTraceHeaders(outboundHeaders) + const outboundHeaders = {} + txn.insertDistributedTraceHeaders(outboundHeaders) - txn.acceptDistributedTraceHeaders('HTTP', firstTraceContext) + txn.acceptDistributedTraceHeaders('HTTP', firstTraceContext) - expect(txn.traceId).to.not.equal(unexpectedTraceId) - expect(txn.parentSpanId).to.not.equal(unexpectedParentSpanId) - expect(txn.parentApp).to.not.equal('2827902') + t.not(txn.traceId, unexpectedTraceId) + t.not(txn.parentSpanId, unexpectedParentSpanId) + t.not(txn.parentApp, '2827902') - const traceparentParts = outboundHeaders.traceparent.split('-') - const [, expectedTraceId] = traceparentParts + const traceparentParts = outboundHeaders.traceparent.split('-') + const [, expectedTraceId] = traceparentParts - expect(txn.traceId).to.equal(expectedTraceId) + t.equal(txn.traceId, expectedTraceId) - txn.end() - }) + txn.end() + t.end() }) }) +}) + +tap.test('insertDistributedTraceHeaders', (t) => { + t.autoend() + + let agent = null + let contextManager = null + + t.beforeEach(function () { + agent = helper.loadMockedAgent() + contextManager = helper.getContextManager() + }) + + t.afterEach(() => { + helper.unloadAgent(agent) + }) - describe('insertDistributedTraceHeaders', () => { - it('should lowercase traceId for tracecontext when recieved upper from newrelic format', () => { + t.test( + 'should lowercase traceId for tracecontext when recieved upper from newrelic format', + (t) => { const trustedAccountKey = '123' agent.config.account_id = 'AccountId1' @@ -1304,8 +1372,8 @@ describe('Transaction', function () { txn.acceptDistributedTraceHeaders('HTTP', headers) - expect(txn.isDistributedTrace).to.be.true - expect(txn.acceptedDistributedTrace).to.be.true + t.ok(txn.isDistributedTrace) + t.ok(txn.acceptedDistributedTrace) const insertedHeaders = {} txn.insertDistributedTraceHeaders(insertedHeaders) @@ -1313,273 +1381,311 @@ describe('Transaction', function () { const splitData = insertedHeaders.traceparent.split('-') const [, traceId] = splitData - expect(traceId).to.equal(expectedTraceContextTraceId) + t.equal(traceId, expectedTraceContextTraceId) const rawPayload = Buffer.from(insertedHeaders.newrelic, 'base64').toString('utf-8') const payload = JSON.parse(rawPayload) // newrelic header should have traceId untouched - expect(payload.d.tr).to.equal(incomingTraceId) + t.equal(payload.d.tr, incomingTraceId) // traceId used for metrics shoudl go untouched - expect(txn.traceId).to.equal(incomingTraceId) + t.equal(txn.traceId, incomingTraceId) txn.end() + t.end() }) - }) + } + ) - it('should generate a valid new trace context traceparent header', () => { - agent.config.distributed_tracing.enabled = true - agent.config.trusted_account_key = '1' - agent.config.span_events.enabled = true + t.test('should generate a valid new trace context traceparent header', (t) => { + agent.config.distributed_tracing.enabled = true + agent.config.trusted_account_key = '1' + agent.config.span_events.enabled = true - const tx = new Transaction(agent) + const txn = new Transaction(agent) - contextManager.setContext(tx.trace.root) + contextManager.setContext(txn.trace.root) - const outboundHeaders = createHeadersAndInsertTrace(tx) - const traceparent = outboundHeaders.traceparent - const traceparentParts = traceparent.split('-') + const outboundHeaders = createHeadersAndInsertTrace(txn) + const traceparent = outboundHeaders.traceparent + const traceparentParts = traceparent.split('-') - const lowercaseHexRegex = /^[a-f0-9]+/ + const lowercaseHexRegex = /^[a-f0-9]+/ - expect(traceparentParts.length).to.equal(4) - expect(traceparentParts[0], 'version').to.equal('00') - expect(traceparentParts[1].length, 'traceId').to.equal(32) - expect(traceparentParts[2].length, 'parentId').to.equal(16) - expect(traceparentParts[3], 'flags').to.equal('01') + t.equal(traceparentParts.length, 4) + t.equal(traceparentParts[0], '00', 'version matches') + t.equal(traceparentParts[1].length, 32, 'traceId of length 32') + t.equal(traceparentParts[2].length, 16, 'parentId of length 16') + t.equal(traceparentParts[3], '01', 'flags match') - expect(traceparentParts[1], 'traceId is lowercase hex').to.match(lowercaseHexRegex) - expect(traceparentParts[2], 'parentId is lowercase hex').to.match(lowercaseHexRegex) + t.match(traceparentParts[1], lowercaseHexRegex, 'traceId is lowercase hex') + t.match(traceparentParts[2], lowercaseHexRegex, 'parentId is lowercase hex') - contextManager.setContext(null) - }) + t.end() + }) - it('should generate new parentId when spans_events disabled', () => { - agent.config.distributed_tracing.enabled = true - agent.config.trusted_account_key = '1' - agent.config.span_events.enabled = false + t.test('should generate new parentId when spans_events disabled', (t) => { + agent.config.distributed_tracing.enabled = true + agent.config.trusted_account_key = '1' + agent.config.span_events.enabled = false - const tx = new Transaction(agent) - const lowercaseHexRegex = /^[a-f0-9]+/ + const txn = new Transaction(agent) + const lowercaseHexRegex = /^[a-f0-9]+/ - contextManager.setContext(tx.trace.root) + contextManager.setContext(txn.trace.root) - const outboundHeaders = createHeadersAndInsertTrace(tx) - const traceparent = outboundHeaders.traceparent - const traceparentParts = traceparent.split('-') + const outboundHeaders = createHeadersAndInsertTrace(txn) + const traceparent = outboundHeaders.traceparent + const traceparentParts = traceparent.split('-') - expect(traceparentParts[2].length, 'parentId').to.equal(16) + t.equal(traceparentParts[2].length, 16, 'parentId has length 16') - expect(traceparentParts[2], 'parentId is lowercase hex').to.match(lowercaseHexRegex) - }) + t.match(traceparentParts[2], lowercaseHexRegex, 'parentId is lowercase hex') + t.end() + }) - it('should set traceparent sample part to 01 for sampled transaction', () => { - agent.config.distributed_tracing.enabled = true - agent.config.trusted_account_key = '1' - agent.config.span_events.enabled = true + t.test('should set traceparent sample part to 01 for sampled transaction', (t) => { + agent.config.distributed_tracing.enabled = true + agent.config.trusted_account_key = '1' + agent.config.span_events.enabled = true - const tx = new Transaction(agent) + const txn = new Transaction(agent) - contextManager.setContext(tx.trace.root) - tx.sampled = true + contextManager.setContext(txn.trace.root) + txn.sampled = true - const outboundHeaders = createHeadersAndInsertTrace(tx) - const traceparent = outboundHeaders.traceparent - const traceparentParts = traceparent.split('-') + const outboundHeaders = createHeadersAndInsertTrace(txn) + const traceparent = outboundHeaders.traceparent + const traceparentParts = traceparent.split('-') - expect(traceparentParts[3], 'flags').to.equal('01') + t.equal(traceparentParts[3], '01', 'flags match') - contextManager.setContext(null) - }) + t.end() + }) - it('should set traceparent traceid if traceparent exists on transaction', () => { - agent.config.distributed_tracing.enabled = true - agent.config.trusted_account_key = '1' - agent.config.span_events.enabled = true + t.test('should set traceparent traceid if traceparent exists on transaction', (t) => { + agent.config.distributed_tracing.enabled = true + agent.config.trusted_account_key = '1' + agent.config.span_events.enabled = true - const tx = new Transaction(agent) - const traceparent = '00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-00' - const tracestate = '323322332234234234423' + const txn = new Transaction(agent) + const traceparent = '00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-00' + const tracestate = '323322332234234234423' - tx.acceptTraceContextPayload(traceparent, tracestate) + txn.acceptTraceContextPayload(traceparent, tracestate) - contextManager.setContext(tx.trace.root) + contextManager.setContext(txn.trace.root) - const outboundHeaders = createHeadersAndInsertTrace(tx) - const traceparentParts = outboundHeaders.traceparent.split('-') + const outboundHeaders = createHeadersAndInsertTrace(txn) + const traceparentParts = outboundHeaders.traceparent.split('-') - expect(traceparentParts[1], 'traceId').to.equal('4bf92f3577b34da6a3ce929d0e0e4736') + t.equal(traceparentParts[1], '4bf92f3577b34da6a3ce929d0e0e4736', 'traceId matches') - contextManager.setContext(null) - }) + t.end() + }) - it('generates a priority for entry-point transactions', () => { - const tx = new Transaction(agent) + t.test('generates a priority for entry-point transactions', (t) => { + const txn = new Transaction(agent) - expect(tx.priority).to.equal(null) - expect(tx.sampled).to.equal(null) + t.equal(txn.priority, null) + t.equal(txn.sampled, null) - tx.insertDistributedTraceHeaders({}) + txn.insertDistributedTraceHeaders({}) - expect(tx.priority).to.be.a('number') - expect(tx.sampled).to.be.a('boolean') - }) + t.type(txn.priority, 'number') + t.type(txn.sampled, 'boolean') + t.end() }) +}) - describe('acceptTraceContextPayload', () => { - it('should accept a valid trace context traceparent header', () => { - agent.config.distributed_tracing.enabled = true - agent.config.trusted_account_key = '1' - agent.config.span_events.enabled = true +tap.test('acceptTraceContextPayload', (t) => { + t.autoend() - const goodParent = '00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-00' + let agent = null - helper.runInTransaction(agent, function (txn) { - const childSegment = txn.trace.add('child') - childSegment.start() + t.beforeEach(function () { + agent = helper.loadMockedAgent() + }) + + t.afterEach(() => { + helper.unloadAgent(agent) + }) - txn.acceptTraceContextPayload(goodParent, 'stuff') + t.test('should accept a valid trace context traceparent header', (t) => { + agent.config.distributed_tracing.enabled = true + agent.config.trusted_account_key = '1' + agent.config.span_events.enabled = true - expect(txn.traceId).to.equal('4bf92f3577b34da6a3ce929d0e0e4736') - expect(txn.parentSpanId).to.equal('00f067aa0ba902b7') + const goodParent = '00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-00' - txn.end() - }) + helper.runInTransaction(agent, function (txn) { + const childSegment = txn.trace.add('child') + childSegment.start() + + txn.acceptTraceContextPayload(goodParent, 'stuff') + + t.equal(txn.traceId, '4bf92f3577b34da6a3ce929d0e0e4736') + t.equal(txn.parentSpanId, '00f067aa0ba902b7') + + txn.end() + t.end() }) + }) - it('should not accept invalid trace context traceparent header', () => { - agent.config.distributed_tracing.enabled = true - agent.config.trusted_account_key = '1' - agent.config.span_events.enabled = true + t.test('should not accept invalid trace context traceparent header', (t) => { + agent.config.distributed_tracing.enabled = true + agent.config.trusted_account_key = '1' + agent.config.span_events.enabled = true - helper.runInTransaction(agent, function (txn) { - const childSegment = txn.trace.add('child') - childSegment.start() + helper.runInTransaction(agent, function (txn) { + const childSegment = txn.trace.add('child') + childSegment.start() - const originalHeaders = createHeadersAndInsertTrace(txn) - const origTraceparent = originalHeaders.traceparent - const traceparent = 'asdlkfjasdl;fkja' - const tracestate = 'stuff' + const originalHeaders = createHeadersAndInsertTrace(txn) + const origTraceparent = originalHeaders.traceparent + const traceparent = 'asdlkfjasdl;fkja' + const tracestate = 'stuff' - txn.acceptTraceContextPayload(traceparent, tracestate) + txn.acceptTraceContextPayload(traceparent, tracestate) - const secondHeaders = createHeadersAndInsertTrace(txn) + const secondHeaders = createHeadersAndInsertTrace(txn) - expect(secondHeaders.traceparent).to.equal(origTraceparent) - txn.end() - }) + t.equal(secondHeaders.traceparent, origTraceparent) + txn.end() + t.end() }) + }) - it('should not accept tracestate when trusted_account_key missing', () => { - agent.config.trusted_account_key = null - agent.config.distributed_tracing.enabled = true - agent.config.span_events.enabled = true + t.test('should not accept tracestate when trusted_account_key missing', (t) => { + agent.config.trusted_account_key = null + agent.config.distributed_tracing.enabled = true + agent.config.span_events.enabled = true - const incomingTraceparent = '00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-00' - // When two bugs combine, we might accept a tracestate we shouldn't - const incomingNullKeyedTracestate = - 'null@nr=0-0-33-2827902-7d3efb1b173fecfa-e8b91a159289ff74-1-1.23456-1518469636035' + const incomingTraceparent = '00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-00' + // When two bugs combine, we might accept a tracestate we shouldn't + const incomingNullKeyedTracestate = + 'null@nr=0-0-33-2827902-7d3efb1b173fecfa-e8b91a159289ff74-1-1.23456-1518469636035' - helper.runInTransaction(agent, function (txn) { - const childSegment = txn.trace.add('child') - childSegment.start() + helper.runInTransaction(agent, function (txn) { + const childSegment = txn.trace.add('child') + childSegment.start() - txn.acceptTraceContextPayload(incomingTraceparent, incomingNullKeyedTracestate) + txn.acceptTraceContextPayload(incomingTraceparent, incomingNullKeyedTracestate) - // traceparent - expect(txn.traceId).to.equal('4bf92f3577b34da6a3ce929d0e0e4736') - expect(txn.parentSpanId).to.equal('00f067aa0ba902b7') + // traceparent + t.equal(txn.traceId, '4bf92f3577b34da6a3ce929d0e0e4736') + t.equal(txn.parentSpanId, '00f067aa0ba902b7') - // tracestate - expect(txn.parentType).to.not.exist - expect(txn.accountId).to.not.exist - expect(txn.parentApp).to.not.exist - expect(txn.parentId).to.not.exist + // tracestate + t.equal(txn.parentType, null) + t.equal(txn.accountId, undefined) + t.equal(txn.parentApp, null) + t.equal(txn.parentId, null) - txn.end() - }) + txn.end() + t.end() }) + }) - it('should accept tracestate when trusted_account_key matches', () => { - agent.config.trusted_account_key = '33' - agent.config.distributed_tracing.enabled = true - agent.config.span_events.enabled = true + t.test('should accept tracestate when trusted_account_key matches', (t) => { + agent.config.trusted_account_key = '33' + agent.config.distributed_tracing.enabled = true + agent.config.span_events.enabled = true - const incomingTraceparent = '00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-00' - // When two bugs combine, we might accept a tracestate we shouldn't - const incomingNullKeyedTracestate = - '33@nr=0-0-33-2827902-7d3efb1b173fecfa-e8b91a159289ff74-1-1.23456-1518469636035' + const incomingTraceparent = '00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-00' + // When two bugs combine, we might accept a tracestate we shouldn't + const incomingNullKeyedTracestate = + '33@nr=0-0-33-2827902-7d3efb1b173fecfa-e8b91a159289ff74-1-1.23456-1518469636035' - helper.runInTransaction(agent, function (txn) { - const childSegment = txn.trace.add('child') - childSegment.start() + helper.runInTransaction(agent, function (txn) { + const childSegment = txn.trace.add('child') + childSegment.start() - txn.acceptTraceContextPayload(incomingTraceparent, incomingNullKeyedTracestate) + txn.acceptTraceContextPayload(incomingTraceparent, incomingNullKeyedTracestate) - // traceparent - expect(txn.traceId).to.equal('4bf92f3577b34da6a3ce929d0e0e4736') - expect(txn.parentSpanId).to.equal('00f067aa0ba902b7') + // traceparent + t.equal(txn.traceId, '4bf92f3577b34da6a3ce929d0e0e4736') + t.equal(txn.parentSpanId, '00f067aa0ba902b7') - // tracestate - expect(txn.parentType).to.equal('App') - expect(txn.parentAcct).to.equal('33') - expect(txn.parentApp).to.equal('2827902') - expect(txn.parentId).to.equal('e8b91a159289ff74') + // tracestate + t.equal(txn.parentType, 'App') + t.equal(txn.parentAcct, '33') + t.equal(txn.parentApp, '2827902') + t.equal(txn.parentId, 'e8b91a159289ff74') - txn.end() - }) + txn.end() + t.end() }) }) +}) + +tap.test('addDistributedTraceIntrinsics', (t) => { + t.autoend() - describe('addDistributedTraceIntrinsics', function () { - let tx = null - let attributes = null + let txn = null + let attributes = null + let agent = null - beforeEach(function () { - attributes = {} - tx = new Transaction(agent) + t.beforeEach(() => { + agent = helper.loadMockedAgent({ + attributes: { enabled: true } }) + attributes = {} + txn = new Transaction(agent) + }) + + t.afterEach(() => { + helper.unloadAgent(agent) + }) - it('does not change existing priority', () => { - tx.priority = 999 - tx.sampled = false + t.test('does not change existing priority', (t) => { + txn.priority = 999 + txn.sampled = false - tx.addDistributedTraceIntrinsics(attributes) + txn.addDistributedTraceIntrinsics(attributes) - expect(tx.priority).to.equal(999) - expect(tx.sampled).to.be.false - }) + t.equal(txn.priority, 999) + t.notOk(txn.sampled) + t.end() + }) - it('adds expected attributes if no payload was received', function () { - tx.isDistributedTrace = false + t.test('adds expected attributes if no payload was received', (t) => { + txn.isDistributedTrace = false - tx.addDistributedTraceIntrinsics(attributes) + txn.addDistributedTraceIntrinsics(attributes) - expect(attributes).to.have.property('guid', tx.id) - expect(attributes).to.have.property('traceId', tx.traceId) - expect(attributes).to.have.property('priority', tx.priority) - expect(attributes).to.have.property('sampled', true) - }) + const expected = { + guid: txn.id, + traceId: txn.traceId, + priority: txn.priority, + sampled: true + } + t.has(attributes, expected) + t.end() + }) - it('adds DT attributes if payload was accepted', function () { - tx.agent.config.account_id = '5678' - tx.agent.config.primary_application_id = '1234' - tx.agent.config.trusted_account_key = '5678' - tx.agent.config.distributed_tracing.enabled = true + t.test('adds DT attributes if payload was accepted', (t) => { + txn.agent.config.account_id = '5678' + txn.agent.config.primary_application_id = '1234' + txn.agent.config.trusted_account_key = '5678' + txn.agent.config.distributed_tracing.enabled = true - const payload = tx._createDistributedTracePayload().text() - tx.isDistributedTrace = false - tx._acceptDistributedTracePayload(payload, 'AMQP') + const payload = txn._createDistributedTracePayload().text() + txn.isDistributedTrace = false + txn._acceptDistributedTracePayload(payload, 'AMQP') - tx.addDistributedTraceIntrinsics(attributes) + txn.addDistributedTraceIntrinsics(attributes) - expect(attributes).to.have.property('parent.type', 'App') - expect(attributes).to.have.property('parent.app', '1234') - expect(attributes).to.have.property('parent.account', '5678') - expect(attributes).to.have.property('parent.transportType', 'AMQP') - expect(attributes).to.have.property('parent.transportDuration') - }) + const expected = { + 'parent.type': 'App', + 'parent.app': '1234', + 'parent.account': '5678', + 'parent.transportType': 'AMQP' + } + t.has(attributes, expected) + t.hasProp(attributes, 'parent.transportDuration') + t.end() }) }) @@ -1784,7 +1890,7 @@ tap.test('when being named with finalizeNameFromUri', (t) => { transaction.finalizeNameFromUri('/some/random/path', 200) const attrs = transaction.trace.attributes.get(AttributeFilter.DESTINATIONS.TRANS_TRACE) - expect(attrs).to.deep.equal({}) + t.same(attrs, {}) t.end() } @@ -1870,10 +1976,10 @@ tap.test('when being named with finalizeName', (t) => { transaction = null }) - t.test('should call finalizeNameFromUri if no name is given for a web tx', (t) => { + t.test('should call finalizeNameFromUri if no name is given for a web txn', (t) => { let called = false - transaction.finalizeNameFromUri = function () { + transaction.finalizeNameFromUri = () => { called = true } transaction.type = 'web'