Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,27 @@ git remote add upstream [email protected]:bitfinexcom/PARENT.git
git remote -v

git push origin master

## Config validation

If config file has corresponding `.example` file, by default `loadConf` will run a check for missing keys.

If config is missing some keys that are present in `.example` file, program will exit and report all missing keys.

`loadConf` accepts 3rd, optional validation object parameter:

```js
this.loadConf("cosmos.coin", "coin", {
symbol: {
required: true,
sameAsExample: true,
},
"some.nested.value": {
required: true,
},
});
```

Keys of this validation object are used to access config values using lodash `_.get` so you can
chain access pattern as in example above `some.nested.value`. You can specify here if the value is
`required: true` and if value should be equal to corresponding value from `.example` file `sameAsExample: true`.
70 changes: 67 additions & 3 deletions base.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,28 @@ const _ = require('lodash')
const async = require('async')
const EventEmitter = require('events')

const extractObjectKeys = (obj, keys = []) => {
for (const key in obj) {
keys.push(key)
if (_.isObject(obj[key]) && !_.isArray(obj[key])) {
extractObjectKeys(obj[key], keys)
}
}
return keys
}

const printOutput = (title, content) => {
console.log(`
###############################################
# ${title}
###############################################
#
# ${content}
#
###############################################
`)
}

class Base extends EventEmitter {
constructor (conf, ctx) {
super()
Expand All @@ -19,7 +41,7 @@ class Base extends EventEmitter {
init () {
if (this.conf) {
if (this.conf.skipCertCheck) {
process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = 0
process.env.NODE_TLS_REJECT_UNAUTHORIZED = 0
}
}

Expand Down Expand Up @@ -55,7 +77,7 @@ class Base extends EventEmitter {
return res
}

loadConf (c, group = null) {
loadConf (c, group = null, validation = null) {
const fprefix = this.ctx.env
const dirname = join(this.ctx.root, 'config')

Expand All @@ -65,7 +87,49 @@ class Base extends EventEmitter {
confPath = envConfPath
}

_.merge(this.conf, this.getConf(this.ctx.env, group, confPath))
const exampleConfigPath = `${confPath}.example`
const config = this.getConf(this.ctx.env, group, confPath)

if (fs.existsSync(exampleConfigPath)) {
const exampleConfig = this.getConf(this.ctx.env, group, exampleConfigPath)
const groupedExampleCfg = _.get(exampleConfig, group, exampleConfig)
const srcCfgKeys = extractObjectKeys(groupedExampleCfg)

const groupedCfg = _.get(config, group, config)
const destCfgKeys = extractObjectKeys(groupedCfg)

const missingKeys = _.difference(srcCfgKeys, destCfgKeys)

if (missingKeys.length) {
printOutput('CONFIG MISSING KEY/VALUE FROM CONFIG.EXAMPLE', `[${missingKeys}] missing in ${confPath}`)
process.exit(1)
}

if (validation) {
for (const key in validation) {
const check = validation[key]
if (check.required) {
const val = _.get(groupedCfg, key)
if (_.isNil(val) || (_.isString(val) && _.isEmpty(val))) {
printOutput('CONFIG MISSING MANDATORY VALUE', `['${key}'] missing value`)
process.exit(1)
}
}

if (check.sameAsExample) {
const cfgValue = _.get(groupedCfg, key)
const exampleCfgValue = _.get(groupedExampleCfg, key)

if (!_.isEqual(cfgValue, exampleCfgValue)) {
printOutput('CONFIG VALUE MISMATCH', `config['${key}']:[${cfgValue}] !== exampleConfig['${key}']:[${exampleCfgValue}]`)
process.exit(1)
}
}
}
}
}

_.merge(this.conf, config)

// e.g. util, coin or ext (i.e. derived from bfx-util-js)
this.group = group
Expand Down
16 changes: 15 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,19 @@
"name": "prdn",
"email": "[email protected]"
}
]
],
"devDependencies": {
"mocha": "^10.1.0",
"standard": "16.0.0",
"sinon": "^14.0.1"
},
"scripts": {
"test": "mocha ./test --exit",
"lint": "standard"
},
"standard": {
"env": [
"mocha"
]
}
}
60 changes: 60 additions & 0 deletions test/base.unit.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
'use strict'

/* eslint-env mocha */

const assert = require('assert')
const sinon = require('sinon')
const WrkBase = require('../base')
const validConf = require('./config/valid.coin')

describe('loadConf', () => {
beforeEach(() => {
sinon.stub(process, 'exit')
})

afterEach(() => {
process.exit.restore()
})

it('should exit when config keys do not match example config keys', () => {
const workerBase = new WrkBase({}, { root: __dirname })
workerBase.loadConf('missing-keys.coin', 'coin')
assert.ok(process.exit.calledOnceWithExactly(1))
})

it('should exit when config keys do not match example nested config keys', () => {
const workerBase = new WrkBase({}, { root: __dirname })
workerBase.loadConf('missing-nested-keys.coin', 'coin')
assert.ok(process.exit.calledOnceWithExactly(1))
})

it('should exit if required value is missing', () => {
const workerBase = new WrkBase({}, { root: __dirname })
workerBase.loadConf('missing-values.coin', 'coin', { symbol: { required: true } })
assert.ok(process.exit.calledOnceWithExactly(1))
})

it('should exit if cfg value not equal example value', () => {
const workerBase = new WrkBase({}, { root: __dirname })
workerBase.loadConf('value-mismatch.coin', 'coin', { sweepKeep: { sameAsExample: true } })
assert.ok(process.exit.calledOnceWithExactly(1))
})

it('should exit if nested cfg value not equal nested example value', () => {
const workerBase = new WrkBase({}, { root: __dirname })
workerBase.loadConf('nested-value-mismatch.coin', 'coin', { 'root.lvl2.lvl3.value': { sameAsExample: true } })
assert.ok(process.exit.calledOnceWithExactly(1))
})

it('should not exit when config keys match example config keys', () => {
const workerBase = new WrkBase({}, { root: __dirname })
workerBase.loadConf('valid.coin', 'coin', {
'root.lvl2.lvl3.value': { sameAsExample: true, required: true },
fee: { required: true }
})

assert.equal(process.exit.called, false)
assert.equal(process.exit.calledWith(1), false)
assert.deepStrictEqual(workerBase.conf.coin, validConf)
})
})
4 changes: 4 additions & 0 deletions test/config/missing-keys.coin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"provider": "http://127.0.0.1:8080",
"sweepKeep": 0.01
}
7 changes: 7 additions & 0 deletions test/config/missing-keys.coin.json.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"provider": "http://127.0.0.1:8080",
"currency": "TEST",
"symbol": "TEST",
"decimals": 8,
"sweepKeep": 0.01
}
12 changes: 12 additions & 0 deletions test/config/missing-nested-keys.coin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"provider": "http://127.0.0.1:8080",
"currency": "TEST",
"symbol": "TEST",
"decimals": 8,
"sweepKeep": 0.01,
"root": {
"lvl2": {
"lvl3": {}
}
}
}
14 changes: 14 additions & 0 deletions test/config/missing-nested-keys.coin.json.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"provider": "http://127.0.0.1:8080",
"currency": "TEST",
"symbol": "TEST",
"decimals": 8,
"sweepKeep": 0.01,
"root": {
"lvl2": {
"lvl3": {
"value": 1
}
}
}
}
7 changes: 7 additions & 0 deletions test/config/missing-values.coin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"provider": "http://127.0.0.1:8080",
"currency": "TEST",
"symbol": "",
"decimals": 8,
"sweepKeep": 0.01
}
7 changes: 7 additions & 0 deletions test/config/missing-values.coin.json.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"provider": "http://127.0.0.1:8080",
"currency": "TEST",
"symbol": "TEST",
"decimals": 8,
"sweepKeep": 0.01
}
18 changes: 18 additions & 0 deletions test/config/nested-value-mismatch.coin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"provider": "http://127.0.0.1:8080",
"currency": "TEST",
"symbol": "TEST",
"decimals": 8,
"sweepKeep": 0.001,
"root": {
"lvl2": {
"lvl3": {
"value": [
4,
5,
6
]
}
}
}
}
14 changes: 14 additions & 0 deletions test/config/nested-value-mismatch.coin.json.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"provider": "http://127.0.0.1:8080",
"currency": "TEST",
"symbol": "TEST",
"decimals": 8,
"sweepKeep": 0.001,
"root": {
"lvl2": {
"lvl3": {
"value": [1, 2, 3]
}
}
}
}
19 changes: 19 additions & 0 deletions test/config/valid.coin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"provider": "http://127.0.0.1:8080",
"currency": "TEST",
"symbol": "TEST",
"decimals": 8,
"sweepKeep": 0.01,
"fee": 0,
"root": {
"lvl2": {
"lvl3": {
"value": [
1,
2,
3
]
}
}
}
}
15 changes: 15 additions & 0 deletions test/config/valid.coin.json.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"provider": "http://127.0.0.1:8080",
"currency": "TEST",
"symbol": "TEST",
"decimals": 8,
"sweepKeep": 0.01,
"fee": 0,
"root": {
"lvl2": {
"lvl3": {
"value": [1, 2, 3]
}
}
}
}
7 changes: 7 additions & 0 deletions test/config/value-mismatch.coin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"provider": "http://127.0.0.1:8080",
"currency": "TEST",
"symbol": "TEST",
"decimals": 8,
"sweepKeep": 1000
}
7 changes: 7 additions & 0 deletions test/config/value-mismatch.coin.json.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"provider": "http://127.0.0.1:8080",
"currency": "TEST",
"symbol": "TEST",
"decimals": 8,
"sweepKeep": 0.001
}