Skip to content

Commit

Permalink
test: added unit tests for MySQL instrumentation
Browse files Browse the repository at this point in the history
  • Loading branch information
jmartin4563 authored May 25, 2023
1 parent d1781b1 commit b693ba0
Show file tree
Hide file tree
Showing 13 changed files with 1,142 additions and 5 deletions.
23 changes: 18 additions & 5 deletions lib/instrumentation/mysql/mysql.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const dbutils = require('../../db/utils')
const properties = require('../../util/properties')
const symbols = require('../../symbols')

exports.callbackInitialize = function callbackInitialize(shim, mysql) {
function callbackInitialize(shim, mysql) {
shim.setDatastore(shim.MYSQL)
shim[symbols.wrappedPoolConnection] = false

Expand Down Expand Up @@ -62,10 +62,10 @@ exports.callbackInitialize = function callbackInitialize(shim, mysql) {
}
}

exports.promiseInitialize = function promiseInitialize(shim) {
function promiseInitialize(shim) {
const callbackAPI = shim.require('./index')
if (callbackAPI && !shim.isWrapped(callbackAPI.createConnection)) {
exports.callbackInitialize(shim, callbackAPI)
callbackInitialize(shim, callbackAPI)
}
}

Expand Down Expand Up @@ -244,15 +244,28 @@ function getInstanceParameters(shim, queryable, query) {
shim.logger.trace('No query config detected, not collecting db instance data')
}

storeDatabaseName(shim, queryable, query)
storeDatabaseName(queryable, query)
return parameters
}

function storeDatabaseName(shim, queryable, query) {
function storeDatabaseName(queryable, query) {
if (queryable[symbols.storeDatabase]) {
const databaseName = dbutils.extractDatabaseChangeFromUse(query)
if (databaseName) {
queryable[symbols.databaseName] = databaseName
}
}
}

module.exports = {
callbackInitialize,
promiseInitialize,
wrapGetConnection,
wrapGetConnectionCallback,
wrapQueryable,
extractQueryArgs,
describeQuery,
describePoolQuery,
getInstanceParameters,
storeDatabaseName
}
39 changes: 39 additions & 0 deletions test/unit/instrumentation/mysql/describePoolQuery.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright 2023 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

'use strict'

const tap = require('tap')
const sinon = require('sinon')
const instrumentation = require('../../../../lib/instrumentation/mysql/mysql')

tap.test('describeQuery', (t) => {
t.autoend()

t.test('should pull the configuration for the query segment', (t) => {
const mockShim = {
logger: {
trace: sinon.stub().returns()
},
isString: sinon.stub().returns(true),
isArray: sinon.stub().returns(false)
}

const mockArgs = ['SELECT * FROM foo', sinon.stub()]

const result = instrumentation.describePoolQuery(mockShim, null, null, mockArgs)
t.same(result, {
stream: true,
query: null,
callback: 1,
name: 'MySQL Pool#query',
record: false
})

t.ok(mockShim.logger.trace.calledWith('Recording pool query'))

t.end()
})
})
51 changes: 51 additions & 0 deletions test/unit/instrumentation/mysql/describeQuery.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright 2023 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

'use strict'

const tap = require('tap')
const sinon = require('sinon')
const instrumentation = require('../../../../lib/instrumentation/mysql/mysql')
const symbols = require('../../../../lib/symbols')

tap.test('describeQuery', (t) => {
t.autoend()

t.test('should pull the configuration for the query segment', (t) => {
const mockShim = {
logger: {
trace: sinon.stub().returns()
},
isString: sinon.stub().returns(true),
isArray: sinon.stub().returns(false)
}

const mockArgs = ['SELECT * FROM foo', sinon.stub()]

instrumentation[symbols.databaseName] = 'my-db-name'
instrumentation.config = {
host: 'example.com',
port: '1234'
}
const result = instrumentation.describeQuery(mockShim, null, null, mockArgs)
t.same(result, {
stream: true,
query: 'SELECT * FROM foo',
callback: 1,
parameters: { host: 'example.com', port_path_or_id: '1234', database_name: 'my-db-name' },
record: true
})

t.ok(mockShim.logger.trace.calledWith('Recording query'))
t.ok(
mockShim.logger.trace.calledWith(
{ query: true, callback: true, parameters: true },
'Query segment descriptor'
)
)

t.end()
})
})
51 changes: 51 additions & 0 deletions test/unit/instrumentation/mysql/extractQueryArgs.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright 2023 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

'use strict'

const tap = require('tap')
const sinon = require('sinon')
const instrumentation = require('../../../../lib/instrumentation/mysql/mysql')

tap.test('extractQueryArgs', (t) => {
t.autoend()

let mockShim
let mockArgs
let mockCallback

t.beforeEach(() => {
mockShim = {
isString: sinon.stub().returns(),
isArray: sinon.stub().returns()
}

mockArgs = []

mockCallback = sinon.stub()
})

t.test('should extract the query and callback when the first arg is a string', (t) => {
mockShim.isString.returns(true)
mockArgs.push('SELECT * FROM foo', mockCallback)

const results = instrumentation.extractQueryArgs(mockShim, mockArgs)
t.same(results, { query: 'SELECT * FROM foo', callback: 1 })

t.end()
})

t.test('should extract the query and callback when the first arg is an object property', (t) => {
mockShim.isString.returns(false)
mockShim.isArray.returns(true)

mockArgs.push({ sql: 'SELECT * FROM foo' }, [], mockCallback)

const results = instrumentation.extractQueryArgs(mockShim, mockArgs)
t.same(results, { query: 'SELECT * FROM foo', callback: 2 })

t.end()
})
})
107 changes: 107 additions & 0 deletions test/unit/instrumentation/mysql/getInstanceParameters.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/*
* Copyright 2023 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

'use strict'

const tap = require('tap')
const sinon = require('sinon')
const instrumentation = require('../../../../lib/instrumentation/mysql/mysql')
const symbols = require('../../../../lib/symbols')

tap.test('getInstanceParameters', (t) => {
t.autoend()

let mockShim
let mockQueryable
let mockQuery

t.beforeEach(() => {
mockShim = {
logger: {
trace: sinon.stub().returns()
}
}

mockQueryable = {}

mockQuery = 'SELECT * FROM foo'
})

t.test('should log if unable to find configuration to pull info', (t) => {
const result = instrumentation.getInstanceParameters(mockShim, mockQueryable, mockQuery)

t.same(
result,
{ host: null, port_path_or_id: null, database_name: null },
'should return the default parameters'
)
t.ok(
mockShim.logger.trace.calledWith('No query config detected, not collecting db instance data'),
'should log'
)

t.end()
})

t.test('should favor connectionConfig over config', (t) => {
mockQueryable = {
config: {
port: '1234',
connectionConfig: {
port: '5678'
}
}
}

const result = instrumentation.getInstanceParameters(mockShim, mockQueryable, mockQuery)
t.equal(result.port_path_or_id, '5678')
t.end()
})

t.test('should favor the symbol DB name over config', (t) => {
mockQueryable = {
config: {
database: 'database-a'
}
}

mockQueryable[symbols.databaseName] = 'database-b'

const result = instrumentation.getInstanceParameters(mockShim, mockQueryable, mockQuery)
t.equal(result.database_name, 'database-b')
t.end()
})

t.test('should set the appropriate parameters for "normal" connections', (t) => {
mockQueryable = {
config: {
database: 'test-database',
host: 'example.com',
port: '1234'
}
}

const result = instrumentation.getInstanceParameters(mockShim, mockQueryable, mockQuery)
t.same(result, { host: 'example.com', port_path_or_id: '1234', database_name: 'test-database' })
t.end()
})

t.test('should set the appropriate parameters for unix socket connections', (t) => {
mockQueryable = {
config: {
database: 'test-database',
socketPath: '/var/run/mysqld/mysqld.sock'
}
}

const result = instrumentation.getInstanceParameters(mockShim, mockQueryable, mockQuery)
t.same(result, {
host: 'localhost',
port_path_or_id: '/var/run/mysqld/mysqld.sock',
database_name: 'test-database'
})
t.end()
})
})
80 changes: 80 additions & 0 deletions test/unit/instrumentation/mysql/index.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* Copyright 2023 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

'use strict'

const tap = require('tap')
const sinon = require('sinon')
const proxyquire = require('proxyquire')

const symbols = require('../../../../lib/symbols')

tap.test('mysql instrumentation', (t) => {
t.autoend()

let mockShim
let mockMysql
let instrumentation

t.beforeEach(() => {
mockShim = {
MYSQL: 'test-mysql',
setDatastore: sinon.stub().returns(),
wrapReturn: sinon.stub().returns(),
isWrapped: sinon.stub().returns(),
require: sinon.stub().returns(mockMysql)
}

mockMysql = {
createConnection: sinon.stub().returns(),
createPool: sinon.stub().returns(),
createPoolCluster: sinon.stub().returns()
}

instrumentation = proxyquire('../../../../lib/instrumentation/mysql/mysql', {})
})

t.test('callbackInitialize should set the datastore and symbols', (t) => {
instrumentation.callbackInitialize(mockShim, mockMysql)

t.ok(mockShim.setDatastore.calledWith('test-mysql'), 'should set the datastore to mysql')
t.equal(
mockShim[symbols.wrappedPoolConnection],
false,
'should default the wrappedPoolConnection symbol to false'
)
t.end()
})

t.test(
'promiseInitialize not should call callbackInitialized if createConnection is already wrapped',
(t) => {
instrumentation.callbackInitialize = sinon.stub().returns()
mockShim.isWrapped.returns(true)
instrumentation.promiseInitialize(mockShim, mockMysql)

t.notOk(
mockShim[symbols.wrappedPoolConnection],

'should not have applied the symbol'
)
t.end()
}
)

t.test('promiseInitialize should call callbackInitialized', (t) => {
instrumentation.callbackInitialize = sinon.stub().returns()
mockShim.isWrapped.returns(false)
instrumentation.promiseInitialize(mockShim, mockMysql)

t.ok(mockShim.setDatastore.calledWith('test-mysql'), 'should set the datastore to mysql')
t.equal(
mockShim[symbols.wrappedPoolConnection],
false,
'should default the wrappedPoolConnection symbol to false'
)
t.end()
})
})
Loading

0 comments on commit b693ba0

Please sign in to comment.