Skip to content

Commit

Permalink
Feature/make config backwards compatible (#71)
Browse files Browse the repository at this point in the history
* Add back utils file and work on parsing default config

* Tidy up the connect function

* Add URI backwards compatability test

* Add a test for invalid config object

* Update package to 8.2.1, improve default handling
  • Loading branch information
lewisdaly authored Oct 9, 2019
1 parent dce9036 commit 30f00e5
Show file tree
Hide file tree
Showing 6 changed files with 238 additions and 16 deletions.
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@mojaloop/central-services-database",
"version": "8.2.0",
"version": "8.2.1",
"description": "Shared database code for central services",
"main": "src/index.js",
"license": "Apache-2.0",
Expand Down
62 changes: 48 additions & 14 deletions src/database.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,26 @@

const Knex = require('knex')
const Table = require('./table')
const Utils = require('./utils.js')

/* Default config to fall back to when using deprecated URI connection string */
const defaultConfig = {
connection: {
host: 'localhost',
port: 3306
},
pool: {
min: 2,
max: 10,
acquireTimeoutMillis: 30000,
createTimeoutMillis: 3000,
destroyTimeoutMillis: 5000,
idleTimeoutMillis: 30000,
reapIntervalMillis: 1000,
createRetryIntervalMillis: 200
},
debug: false
}

class Database {
constructor () {
Expand All @@ -26,22 +46,36 @@ class Database {
return this._knex
}

/**
* @function connect
*
* @description Connect to the database given the config object. Returns null if database is already connected.
*
* @params {Object} config - The knex connection object. For more information see: http://knexjs.org/#Installation-client
*
* @returns null - if database is already connected.
* @returns void
*
* @throws {Error} - if Database scheme is invalid
*/
async connect (config) {
if (!this._knex) {
if (config.connection.database) {
this._schema = config.connection.database
return configureKnex(config).then(knex => {
this._knex = knex
return this._listTables().then(tables => {
this._tables = tables
this._setTableProperties()
})
})
} else {
throw new Error('Invalid database schema in database config')
}
if (this._knex) {
return null
}
return null

if (typeof config === 'string') {
console.warn('`Database.connect()` called using deprecated string config. Please ugrade this to use the knex config object.')
config = Utils.buildDefaultConfig(defaultConfig, config)
}

if (!config || !config.connection || !config.connection.database) {
throw new Error('Invalid database schema in database config')
}

this._schema = config.connection.database
this._knex = await configureKnex(config)
this._tables = await this._listTables()
await this._setTableProperties()
}

async disconnect () {
Expand Down
39 changes: 39 additions & 0 deletions src/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
'use strict'

/**
* @function buildDefaultConfig
*
* @desciption Converts a deprecated Database Connection URI based config to the newer, Knex compatible version
*
* @returns {Object} - Knex compatible config
*/
const buildDefaultConfig = (defaultConfig, configStr) => {
const connectionRegex = /^(mysql|psql)(?::\/\/)(.*)(?::)(.*)@(.*)(?::)(\d*)(?:\/)(.*)$/
const matches = configStr.match(connectionRegex)

// Check that we got the expected amount: 6 capture groups + 1 js defaults = 7
if (!matches || matches.length !== 7) {
throw new Error(`Invalid database config string: ${configStr}`)
}

// skip the first, .match() puts the original string at index 0
matches.shift()
const [clientStr, user, password, host, port, database] = matches

return {
...defaultConfig,
client: clientStr,
connection: {
...defaultConfig.connection,
user,
password,
host,
port,
database
}
}
}

module.exports = {
buildDefaultConfig
}
35 changes: 35 additions & 0 deletions test/unit/database.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,41 @@ Test('database', databaseTest => {
}
})

getKnexTest.test('handle connection with a deprecated URI', async test => {
// Arrange
const URI = 'mysql://central_ledger:password@mysql-cl:3307/central_ledger_db'

// Act
try {
await dbInstance.connect(URI)
const knex = await dbInstance.getKnex()

// Assert
test.ok(knex)
} catch (e) {
test.fail('Error thrown')
}

test.end()
})

getKnexTest.test('should fail if the config.connection object is undefined', async test => {
// Arrange
const config = {}

// Act
try {
await dbInstance.connect(config)

test.fail('Should have thrown error')
} catch (e) {
// Assert
test.equal(e.message, 'Invalid database schema in database config')
}

test.end()
})

getKnexTest.test('throw error when database is not connected', test => {
try {
dbInstance.getKnex()
Expand Down
114 changes: 114 additions & 0 deletions test/unit/utils.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
'use strict'

const src = '../../src'
const Test = require('tapes')(require('tape'))
const Utils = require(`${src}/utils`)

Test('utils', utilsTest => {
utilsTest.test('buildDefaultConfig should', defaultTest => {
defaultTest.test('handle a mysql connection string', test => {
// Arrange
const defaultConfig = {
connection: {
host: 'localhost',
port: 3306
},
pool: {
field1: true
}
}
const URI = 'mysql://central_ledger:password@mysql-cl:3307/central_ledger_db'
const expected = {
client: 'mysql',
connection: {
host: 'mysql-cl',
port: '3307',
user: 'central_ledger',
password: 'password',
database: 'central_ledger_db'
},
pool: {
field1: true
}
}
// Act
const config = Utils.buildDefaultConfig(defaultConfig, URI)

// Assert
test.deepEqual(config, expected)
test.end()
})

defaultTest.test('not override all values in defaultConfig.connection', test => {
// Arrange
const defaultConfig = {
connection: {
newFieldNotOverriden: true
},
pool: {
field1: true
}
}
const URI = 'mysql://central_ledger:password@mysql-cl:3307/central_ledger_db'
const expected = {
client: 'mysql',
connection: {
newFieldNotOverriden: true,
host: 'mysql-cl',
port: '3307',
user: 'central_ledger',
password: 'password',
database: 'central_ledger_db'
},
pool: {
field1: true
}
}
// Act
const config = Utils.buildDefaultConfig(defaultConfig, URI)

// Assert
test.deepEqual(config, expected)
test.end()
})

defaultTest.test('fails when connections string does not use mysql or psql', test => {
// Arrange
const defaultConfig = {}
const URI = 'mssql://central_ledger:password@mysql-cl:3307/central_ledger_db'

// Act
try {
Utils.buildDefaultConfig(defaultConfig, URI)
test.fail('Should have thrown error')
} catch (err) {
// Assert
test.equal(err.message, 'Invalid database config string: mssql://central_ledger:password@mysql-cl:3307/central_ledger_db')
}

test.end()
})

defaultTest.test('fail when the connection string is invalid', test => {
// Arrange
const defaultConfig = {}
// Missing a `:`
const URI = 'mssql://central_ledgerpassword@mysql-cl:3307/central_ledger_db'

// Act
try {
Utils.buildDefaultConfig(defaultConfig, URI)
test.fail('Should have thrown error')
} catch (err) {
// Assert
test.equal(err.message, 'Invalid database config string: mssql://central_ledgerpassword@mysql-cl:3307/central_ledger_db')
}

test.end()
})

defaultTest.end()
})

utilsTest.end()
})

0 comments on commit 30f00e5

Please sign in to comment.