diff --git a/.gitignore b/.gitignore index 3c3629e..0778e65 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ -node_modules +/node_modules/ +/coverage/ +/npm-debug.log diff --git a/index.js b/index.js index c3535b1..7cdd2c7 100644 --- a/index.js +++ b/index.js @@ -1,243 +1,3 @@ 'use strict'; -const - fs = require('fs') -, path = require('path') -; - -let autoInstance = null; -let _ = { - 'ensureAutoInstance': function (instance) { - if (autoInstance) { return; } - autoInstance = instance || publicFuncs.load(); - }, - - 'configPath': path.resolve(path.dirname(require.main.filename), 'envs'), - 'getConfigFile': function (id) { - return path.resolve(_.configPath, id); - }, - - 'getSchema': function (name) { - if (!name) { name = 'config'; } - let schemaPath = _.getConfigFile(name +'.schema'); - let schema; - try { - schema = JSON.parse(fs.readFileSync(schemaPath, "utf8")); - } catch (err) { - if (~err.message.toLowerCase().indexOf('cannot find')) { - throw new Error( - 'There is no "'+ name +'.schema" file in your envs folder!' - ); - } else if (SyntaxError && err instanceof SyntaxError) { - throw new Error( - 'There is a syntax error in your schema file "'+ schemaPath +'": '+ - '"'+ err.message +'". Please fix it and try again.' - ); - } - throw err; - } - if (!schema) { throw new Error('No "'+ name +'.schema" found!'); } - return schema; - }, - 'hasKeyInSchema': function (key, schema) { - if (typeof(schema['# '+ key]) != 'undefined') { return '# '+ key; } - if (typeof(schema['? '+ key]) != 'undefined') { return '? '+ key; } - return false; - }, - - 'getCleanSchema': function (schema, prefix) { - let key, val, prefixedEnvKey, clean = {}; - for (key in schema) { val = schema[key]; - prefixedEnvKey = prefix + key; - if (key.substring(0, 2) != '# ' && key.substring(0, 2) != '? ') { - throw new Error( - 'Schema key "'+ prefixedEnvKey +'" doesn\'t have a required prefix!' - ); - } - - let isArr = (val instanceof Array); - let isObj = (val instanceof Object); - if (isObj && !isArr) { - val = _.getCleanSchema(val, key + '.'); - } - clean[key.substring(2)] = val; - } - return clean; - }, - - 'objType': function (obj) { - let t = typeof(obj); - if (t != 'object') { return t; } - let isArr = (obj instanceof Array); - return (isArr) ? 'array' : 'object'; - }, - 'validateConfig': function (schema, envConfig) { - _.checkUnexpectedKeys(envConfig, schema); - _.checkMissingOrWrongKeys(envConfig, schema); - return true; - }, - 'checkUnexpectedKeys': function (envConfig, schema, prefix) { - //For friendly output - if (!prefix) { prefix = ''; } - - let envKey, schemaKey, prefixedEnvKey, val; - for (envKey in envConfig) { //check each config key - schemaKey = _.hasKeyInSchema(envKey, schema); - prefixedEnvKey = prefix + envKey; - //Checking unexpected keys - if (!schemaKey) { - throw new Error( - 'Invalid key "'+ prefixedEnvKey +'" in current env config.' - ); - } - //Going deeper in recursion - val = envConfig[envKey]; - let isArr = (val instanceof Array); - let isObj = (val instanceof Object); - if (isObj && !isArr) { - _.checkUnexpectedKeys(val, schema[schemaKey], prefixedEnvKey +'.'); - } - } - return true; - }, - 'checkMissingOrWrongKeys': function (envConfig, schema, prefix) { - let schemaKey, envKey, schemaVal, envVal, isOptional, isPresent; - let prefixedEnvKey, schemaType, envType, isArr, isObj; - - for (schemaKey in schema) { //check each schema config key - envKey = schemaKey.substr(2); - schemaVal = schema[schemaKey]; - envVal = envConfig[envKey]; - isOptional = schemaKey.substr(0, 1) == '?'; - isPresent = (typeof(envVal) != 'undefined'); - //For friendly output - if (!prefix) { prefix = ''; } - prefixedEnvKey = prefix + envKey; - //Checking required keys - if (!isPresent) { - if (isOptional) { continue; } - throw new Error( - 'Required key "'+ prefixedEnvKey +'" missing '+ - 'from your current env config!' - ); - } - //Checking types - schemaType = _.objType(schemaVal); - envType = _.objType(envVal); - if (schemaType != envType) { - throw new Error( - 'Env config key "'+ prefixedEnvKey +'" must be of '+ - 'type "'+ schemaType +'" ("'+ envType +'" found)' - ); - } - //Going deeper in recursion - isArr = (schemaVal instanceof Array); - isObj = (schemaVal instanceof Object); - if (isObj && !isArr) { - _.checkMissingOrWrongKeys(envVal, schemaVal, prefixedEnvKey +'.'); - } - } - return true; - }, - - 'supermerge': function (o1, o2) { let r = {}, k, v; - for (k in o1) { r[k] = o1[k]; } - for (k in o2) { v = o2[k]; - if (o1[k] && o1[k].constructor === Object && v.constructor === Object) { - r[k] = _.supermerge(o1[k], v); //array + array = merge - } else { r[k] = v; } //other combination = override - } return r; - } -}; - -let cache = {}; -let publicFuncs = { - 'load': function (envID, forceNew) { - if (!envID) { envID = ''; } - if (!forceNew && cache[envID]) { return cache[envID]; } - - let envConfig = false; - let saveAsDefault = false; - - if (!envID) { - let rootPath = path.dirname(require.main.filename); - let fileList = fs.readdirSync(_.configPath); - - let match = fileList.some((currentFile) => { - if (path.extname(currentFile) != '.json') { return false; } - - envConfig = require(_.getConfigFile(currentFile)); - if (envConfig.path == rootPath) { - envID = currentFile; - return true; - } - }); - if (!match) { return false; } - saveAsDefault = true; - } - let envConfigObj = new EnvConfig(envID); - if (!forceNew) { - cache[envID] = envConfigObj; - } - if (saveAsDefault) { - cache[''] = envConfigObj; - } - _.ensureAutoInstance(envConfigObj); - if (!autoInstance) { autoInstance = envConfigObj; } - return envConfigObj; - }, - - 'has': function (key) { _.ensureAutoInstance(); - return autoInstance.has(key); - }, - 'get': function (key, def) { _.ensureAutoInstance(); - return autoInstance.get(key, def); - } -}; - - -class EnvConfig { - load(file) { return publicFuncs.load(file); } - - constructor(id) { - this.id = id; - let currentEnvFile = require(_.getConfigFile(id)); - - let schema = _.getSchema(); - _.validateConfig(schema, currentEnvFile); - let defaultData = _.getCleanSchema(schema); - this.currentConfig = _.supermerge(defaultData, currentEnvFile); - } - - has(key) { - let toRet = this.currentConfig; - let parts = key.split('.'); - let i, cur; - for (i in parts) { - cur = parts[i]; - toRet = toRet[cur]; - if (typeof(toRet) == 'undefined') { - return false; - } - } - return true; - } - get(key, def) { - let toRet = this.currentConfig; - let parts = key.split('.'); - let i, cur; - for (i in parts) { - cur = parts[i]; - toRet = toRet[cur]; - if (typeof(toRet) == 'undefined') { - if (typeof(def) != 'undefined') { return def; } - throw new Error( - 'Can\'t find key "'+ key +'" on current env config ("'+ this.id +'")!' - ); - } - } - return toRet; - } -} - -module.exports = publicFuncs; +module.exports = require('./lib/AutoEnvConfig'); diff --git a/lib/AutoEnvConfig.js b/lib/AutoEnvConfig.js new file mode 100644 index 0000000..d953e69 --- /dev/null +++ b/lib/AutoEnvConfig.js @@ -0,0 +1,287 @@ +'use strict'; + +const + fs = require('fs') +, path = require('path') +; + +let magicInstance = null; +let defaultEnvID = null; +let instancesCache = {}; + +let _ = { + 'ensureMagicInstance': function (instance) { + if (magicInstance) { return magicInstance; } + if (!instance) { return publicFuncs.load(); } + magicInstance = instance; + return magicInstance; + }, + + 'rootPath': path.resolve(path.dirname(require.main.filename)), + 'envsPath': path.resolve(path.dirname(require.main.filename), 'envs'), + 'getConfigFilePath': function (id) { + return path.resolve(_.envsPath, id); + }, + + 'getSchema': function (name) { + if (!name) { name = 'config'; } + let schemaPath = _.getConfigFilePath(name +'.schema'); + let schema; + try { + schema = _.loadReference(schemaPath); + } catch (err) { + if (~err.message.toLowerCase().indexOf('no such file or directory')) { + throw new Error( + 'There is no "'+ name +'.schema" file in your envs folder!' + ); + } else if (SyntaxError && err instanceof SyntaxError) { + throw new Error( + 'There is a syntax error in your schema file "'+ schemaPath +'": '+ + '"'+ err.message +'". Please fix it and try again.' + ); + } + throw err; + } + return schema; + }, + 'hasKeyInSchema': function (key, schema) { + if (typeof(schema['# '+ key]) != 'undefined') { return '# '+ key; } + if (typeof(schema['? '+ key]) != 'undefined') { return '? '+ key; } + return false; + }, + + 'getCleanSchema': function (schema, prefix) { + //For friendly output + if (!prefix) { prefix = ''; } + + let key, val, prefixedEnvKey, clean = {}; + for (key in schema) { val = schema[key]; + prefixedEnvKey = prefix + key; + if (key.substring(0, 2) != '# ' && key.substring(0, 2) != '? ') { + throw new Error( + 'Schema key "'+ prefixedEnvKey +'" doesn\'t have a required prefix!' + ); + } + prefixedEnvKey = prefix + key.substring(2); + + let isArr = (val instanceof Array); + let isObj = (val instanceof Object); + if (isObj && !isArr) { + val = _.getCleanSchema(val, prefixedEnvKey + '.'); + } + clean[key.substring(2)] = val; + } + return clean; + }, + + 'objType': function (obj) { + let t = typeof(obj); + if (t != 'object') { return t; } + let isArr = (obj instanceof Array); + return (isArr) ? 'array' : 'object'; + }, + 'validateConfig': function (schema, envConfig) { + _.checkUnexpectedKeys(envConfig, schema); + _.checkMissingOrWrongKeys(envConfig, schema); + return true; + }, + 'checkUnexpectedKeys': function (envConfig, schema, prefix) { + //For friendly output + if (!prefix) { prefix = ''; } + + let envKey, schemaKey, prefixedEnvKey, val; + for (envKey in envConfig) { //check each config key + schemaKey = _.hasKeyInSchema(envKey, schema); + prefixedEnvKey = prefix + envKey; + //Checking unexpected keys + if (!schemaKey) { + throw new Error( + 'Unexpected key "'+ prefixedEnvKey +'" in current env config.' + ); + } + //Going deeper in recursion + val = envConfig[envKey]; + let isArr = (val instanceof Array); + let isObj = (val instanceof Object); + if (isObj && !isArr) { + _.checkUnexpectedKeys(val, schema[schemaKey], prefixedEnvKey +'.'); + } + } + return true; + }, + 'checkMissingOrWrongKeys': function (envConfig, schema, prefix) { + //For friendly output + if (!prefix) { prefix = ''; } + let schemaKey, envKey, schemaVal, envVal, isOptional, isPresent; + let prefixedEnvKey, schemaType, envType, isArr, isObj; + + for (schemaKey in schema) { //check each schema config key + envKey = schemaKey.substr(2); + schemaVal = schema[schemaKey]; + envVal = envConfig[envKey]; + + isOptional = schemaKey.substr(0, 1) == '?'; + isPresent = (typeof(envVal) != 'undefined'); + prefixedEnvKey = prefix + envKey; + //Checking required keys + if (!isPresent) { + if (isOptional) { continue; } + throw new Error( + 'Required key "'+ prefixedEnvKey +'" missing '+ + 'from your current env config!' + ); + } + //Checking types + schemaType = _.objType(schemaVal); + envType = _.objType(envVal); + if (schemaType != envType) { + throw new Error( + 'Env config key "'+ prefixedEnvKey +'" must be of '+ + 'type "'+ schemaType +'" ("'+ envType +'" found)' + ); + } + //Going deeper in recursion + isArr = (schemaVal instanceof Array); + isObj = (schemaVal instanceof Object); + if (isObj && !isArr) { + _.checkMissingOrWrongKeys(envVal, schemaVal, prefixedEnvKey +'.'); + } + } + return true; + }, + + 'supermerge': function (o1, o2) { let r = {}, k, v; + for (k in o1) { r[k] = o1[k]; } + for (k in o2) { v = o2[k]; + if (o1[k] && o1[k].constructor === Object && v.constructor === Object) { + r[k] = _.supermerge(o1[k], v); //array + array = merge + } else { r[k] = v; } //other combination = override + } return r; + }, + + 'loadReference': function (filename) { + if (path.extname(filename) == '') { filename += '.json'; } + return JSON.parse(fs.readFileSync(filename, "utf8")); + } +}; + +let publicFuncs = { + '_reset': function () { //reset autoInstance and cache. mostly for unit tests + defaultEnvID = null; + magicInstance = null; + instancesCache = {}; + }, + + 'getDefaultEnvID': function () { + if (defaultEnvID) { return defaultEnvID; } + + let fileList = fs.readdirSync(_.envsPath); + + let envConfig; + let envID = false; + fileList.some((currentFile) => { + if (path.extname(currentFile) != '.json') { return false; } + + try { + envConfig = _.loadReference(_.getConfigFilePath(currentFile)); + } catch (err) { + if (SyntaxError && err instanceof SyntaxError) { + //ignore syntax errors when searching for default file + return false; + } + throw err; + } + if (envConfig.path == _.rootPath) { + envID = currentFile; + return true; + } + }); + return (defaultEnvID = envID); + }, + 'load': function (envID, forceNew) { + if (!envID) { envID = ''; } + if (!forceNew && instancesCache[envID]) { return instancesCache[envID]; } + + let saveAsDefault = false; + if (envID == '') { + saveAsDefault = true; + envID = publicFuncs.getDefaultEnvID(); + if (!envID) { return false; } + } + let autoEnvConfigInstance = new AutoEnvConfig(envID); + if (saveAsDefault) { + instancesCache[''] = autoEnvConfigInstance; + } + if (!forceNew) { + instancesCache[envID] = autoEnvConfigInstance; + } + _.ensureMagicInstance(autoEnvConfigInstance); + return autoEnvConfigInstance; + }, + + 'has': function (key) { _.ensureMagicInstance(); + return magicInstance.has(key); + }, + 'get': function (key, def) { + _.ensureMagicInstance(); + return magicInstance.get(key, def); + } +}; + + +class AutoEnvConfig { + load(file) { return publicFuncs.load(file); } + + constructor(id) { + this.id = id; + let currentEnvFile; + try { + currentEnvFile = _.loadReference(_.getConfigFilePath(id)); + } catch (err) { + if (SyntaxError && err instanceof SyntaxError) { + throw new Error( + 'There is a syntax error in your config file "'+ id +'": '+ + '"'+ err.message +'". Please fix it and try again.' + ); + } + throw err; + } + + let schema = _.getSchema(); + let defaultData = _.getCleanSchema(schema); + _.validateConfig(schema, currentEnvFile); + this.currentConfig = _.supermerge(defaultData, currentEnvFile); + } + + has(key) { + let toRet = this.currentConfig; + let parts = key.split('.'); + let i, cur; + for (i in parts) { + cur = parts[i]; + toRet = toRet[cur]; + if (typeof(toRet) == 'undefined') { + return false; + } + } + return true; + } + get(key, def) { + let toRet = this.currentConfig; + let parts = key.split('.'); + let i, cur; + for (i in parts) { + cur = parts[i]; + toRet = toRet[cur]; + if (typeof(toRet) == 'undefined') { + if (typeof(def) != 'undefined') { return def; } + throw new Error( + 'Can\'t find key "'+ key +'" on current env config ("'+ this.id +'") and there was no default on function call!' + ); + } + } + return toRet; + } +} + +module.exports = publicFuncs; diff --git a/package.json b/package.json index 00e0098..bec41ee 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,8 @@ "homepage": "https://github.com/dnunes/autoenvconfig/", "main": "index.js", "scripts": { - "test": "./node_modules/.bin/mocha --reporter spec" + "cover": "./node_modules/.bin/istanbul cover ./node_modules/mocha/bin/_mocha -- -c", + "test": "./node_modules/.bin/mocha -c" }, "keywords": [ "multiple", @@ -30,5 +31,12 @@ "type": "MIT", "url": "https://github.com/dnunes/autoenvconfig/blob/master/LICENSE" } - ] + ], + "devDependencies": { + "chai": "^3.5.0", + "istanbul": "^0.4.5", + "mocha": "^3.0.2", + "rewire": "^2.5.2", + "sinon": "^1.17.5" + } } diff --git a/test/envs/config.schema b/test/envs/config.schema new file mode 100644 index 0000000..bf3caaa --- /dev/null +++ b/test/envs/config.schema @@ -0,0 +1,14 @@ +{ + "# id": "", + "# envtype": "", + "# path": "", + + "# requiredKey": "", + "? deep": { + "? key": { + "# supported": "myValue", + "? asWell": "otherValue" + } + }, + "# arr": [] +} diff --git a/test/envs/config_noprefix.schema b/test/envs/config_noprefix.schema new file mode 100644 index 0000000..f89ee64 --- /dev/null +++ b/test/envs/config_noprefix.schema @@ -0,0 +1,14 @@ +{ + "# id": "", + "# envtype": "", + "# path": "", + + "# requiredKey": "", + "# deep": { + "? key": { + "supported": "myValue", + "? asWell": "otherValue" + } + }, + "# arr": [] +} diff --git a/test/envs/config_parseError.schema b/test/envs/config_parseError.schema new file mode 100644 index 0000000..c645f92 --- /dev/null +++ b/test/envs/config_parseError.schema @@ -0,0 +1,14 @@ +{ + "# id": INVALID :) + "# envtype": "INVALID", + "# path": "INVALID", + + "# requiredKey": "" //there should be a comma here + "# deep": { //also, no comments are allowed in json files + "? key": { + "# supported": "myValue", + "? asWell": "otherValue" + } + }, + "# arr": [] +} diff --git a/test/envs/env1.json b/test/envs/env1.json new file mode 100644 index 0000000..6771d17 --- /dev/null +++ b/test/envs/env1.json @@ -0,0 +1,13 @@ +{ + "id": "env1", + "envtype": "staging", + "path": "/some/random/path", + + "requiredKey": "value1", + "deep": { + "key": { + "supported": ":)" + } + }, + "arr": [] +} diff --git a/test/envs/env2.json b/test/envs/env2.json new file mode 100644 index 0000000..48149cb --- /dev/null +++ b/test/envs/env2.json @@ -0,0 +1,13 @@ +{ + "id": "env2", + "envtype": "production", + "path": "/some/random/path", + + "requiredKey": "value2", + "deep": { + "key": { + "supported": ":)" + } + }, + "arr": [] +} diff --git a/test/envs/env_missingProperty.json b/test/envs/env_missingProperty.json new file mode 100644 index 0000000..2048483 --- /dev/null +++ b/test/envs/env_missingProperty.json @@ -0,0 +1,13 @@ +{ + "id": "envMissing", + "envtype": "production", + "path": "/some/random/path", + + "requiredKey": "missing", + "deep": { + "key": { + + } + }, + "arr": [] +} diff --git a/test/envs/env_parseError.json b/test/envs/env_parseError.json new file mode 100644 index 0000000..c9cf9eb --- /dev/null +++ b/test/envs/env_parseError.json @@ -0,0 +1,13 @@ +{ + "id": "envParseError", + "envtype": "production", + "path": "/some/random/path", + + "requiredKey": "valueParseError" //there should be a comma here + "deep": { //also, no comments are allowed in json files + "key": { + "supported": ":)" + } + }, + "arr": [] +} diff --git a/test/envs/env_typeMismatch.json b/test/envs/env_typeMismatch.json new file mode 100644 index 0000000..ac74e9a --- /dev/null +++ b/test/envs/env_typeMismatch.json @@ -0,0 +1,13 @@ +{ + "id": "envMismatch", + "envtype": "staging", + "path": "/some/random/path", + + "requiredKey": "value1", + "deep": { + "key": "this should be an object" + + + }, + "arr": [] +} diff --git a/test/envs/env_unexpectedProperty.json b/test/envs/env_unexpectedProperty.json new file mode 100644 index 0000000..df584b1 --- /dev/null +++ b/test/envs/env_unexpectedProperty.json @@ -0,0 +1,14 @@ +{ + "id": "envUnexpected", + "envtype": "production", + "path": "/some/random/path", + + "requiredKey": "valueUnexpected", + "deep": { + "key": { + "supported": ":)", + "unexpected": "this is not on schema!" + } + }, + "arr": [] +} diff --git a/test/envs/magic.json b/test/envs/magic.json new file mode 100644 index 0000000..9ba99bd --- /dev/null +++ b/test/envs/magic.json @@ -0,0 +1,13 @@ +{ + "id": "magic", + "envtype": "local", + "path": %curpath%, + + "requiredKey": "magic", + "deep": { + "key": { + "supported": ":)" + } + }, + "arr": [] +} diff --git a/test/full.test.js b/test/full.test.js new file mode 100644 index 0000000..ac2911d --- /dev/null +++ b/test/full.test.js @@ -0,0 +1,262 @@ +/* global before, afterEach, after, describe, it */ +'use strict'; + +const + rewire = require('rewire') +, expect = require('chai').expect +, sinon = require('sinon') +; + + + +//### Module to be tested +let AutoEnvConfig = rewire('../lib/AutoEnvConfig.js'); + + + +//### Setup, mocks and cleanup +const + fs = require('fs') +, path = require('path'); + +let + sandbox, + AutoEnvConfigClass +; + +let replaceFiles = {}; + +//# Setup +before(() => { + let mockRootPath = __dirname; + let mockSearchPath = path.resolve(mockRootPath, 'envs'); + + let _ = AutoEnvConfig.__get__('_'); + AutoEnvConfigClass = AutoEnvConfig.__get__('AutoEnvConfig'); + _.rootPath = mockRootPath; //override search path + _.envsPath = mockSearchPath; //override search path + + sandbox = sinon.sandbox.create(); + + //This stub allows us to replace the path in the "magic.conf" file + let origReadFileSync = fs.readFileSync; + sandbox.stub(fs, 'readFileSync', function (filepath, encoding) { + let pathparts = filepath.split(path.sep); + let filename = pathparts.pop(); + if (replaceFiles[filename]) { //use invalid or different files for tests + filename = replaceFiles[filename]; + } + pathparts.push(filename); + filepath = pathparts.join(path.sep); + + let content = origReadFileSync(filepath, encoding); + //if it's the right file, let's replace the path to the current one + if (filename == 'magic.json') { + content = content.replace('%curpath%', JSON.stringify(mockRootPath)); + } + return content; + }); +}); + +//# Reset on each test +afterEach(() => { + AutoEnvConfig._reset(); + replaceFiles = {}; +}); //reset autoInstance and cache + +//# Cleanup +after(() => { + sandbox.restore(); +}); + + + +//### Tests :) +describe('Load Error Handling', function() { + it('throws exception when no ".schema" is present', function () { + replaceFiles = {'config.schema': 'dontexist.schema'}; + let expectedErrMessage = 'There is no "config.schema" file in your envs folder!'; + expect(AutoEnvConfig.load).to.throw(expectedErrMessage); + }); + + it('throws exception when ".schema" is invalid (parse error)', function () { + replaceFiles = {'config.schema': 'config_parseError.schema'}; + let expectedErrMessage = 'There is a syntax error in your schema file '; + expect(AutoEnvConfig.load).to.throw(expectedErrMessage); + }); + + it('throw exception when ".schema" have keys without required prefix', function () { + replaceFiles = {'config.schema': 'config_noprefix.schema'}; + let expectedErrMessage = 'Schema key "deep.key.supported" doesn\'t have a required prefix!'; + expect(AutoEnvConfig.load).to.throw(expectedErrMessage); + }); + + it('throws exception when ".conf" is invalid (parse error)', function () { + let fn = function () { AutoEnvConfig.load('env_parseError.json'); }; + let expectedErrMessage = 'There is a syntax error in your config file'; + expect(fn).to.throw(expectedErrMessage); + }); + + it('throws exception when ".conf" have properties not present in ".schema"', function () { + let fn = function () { AutoEnvConfig.load('env_unexpectedProperty.json'); }; + let expectedErrMessage = 'Unexpected key "deep.key.unexpected" in current env config.'; + expect(fn).to.throw(expectedErrMessage); + }); + + it('throws exception when ".conf" does not have some required property', function () { + let fn = function () { AutoEnvConfig.load('env_missingProperty.json'); }; + let expectedErrMessage = 'Required key "deep.key.supported" missing from your current env config!'; + expect(fn).to.throw(expectedErrMessage); + }); + + it('throws exception when ".conf" have a property with a type that does not match schema', function () { + let fn = function () { AutoEnvConfig.load('env_typeMismatch.json'); }; + let expectedErrMessage = 'Env config key "deep.key" must be of type "object" ("string" found)'; + expect(fn).to.throw(expectedErrMessage); + }); + + it('magic "module.load()" returns false when there is no matching env path', function () { + replaceFiles = {'magic.json': 'env1.json'}; + let magicInstance = AutoEnvConfig.load(); + expect(magicInstance).to.be.false; + }); + + it('".load" should use caches when called more than once even with "forceNew" flag', function () { + AutoEnvConfig.load('', 'forceNew'); + AutoEnvConfig.load('', 'forceNew'); + AutoEnvConfig.load(); + expect(true).to.be.false; + }); +}); + + +describe('Specific Loading', function() { + it('should load when using "module.load(name)"', function () { + let specificInstance = AutoEnvConfig.load('env1'); + let specificKey = specificInstance.get('requiredKey'); + + expect(specificInstance).to.be.instanceof(AutoEnvConfigClass); + expect(specificKey).to.be.equal('value1'); + }); + + it('should load when using "module.load(name.json)"', function () { + let specificInstance = AutoEnvConfig.load('env1.json'); + let specificKey = specificInstance.get('requiredKey'); + + expect(specificInstance).to.be.instanceof(AutoEnvConfigClass); + expect(specificKey).to.be.equal('value1'); + }); + + it('should load when using "instance.load(name)"', function () { + let specificInstance1 = AutoEnvConfig.load('env1'); + let specificKey1 = specificInstance1.get('requiredKey'); + let specificInstance2 = specificInstance1.load('env2'); + let specificKey2 = specificInstance2.get('requiredKey'); + + expect(specificInstance1).to.be.instanceof(AutoEnvConfigClass); + expect(specificInstance2).to.be.instanceof(AutoEnvConfigClass); + expect(specificInstance1).to.not.be.equal(specificInstance2); + expect(specificKey1).to.be.equal('value1'); + expect(specificKey2).to.be.equal('value2'); + }); +}); + + +describe('Magic Loading', function() { + it('should use on magic "module.load()"', function () { + let magicInstance = AutoEnvConfig.load(); + let magicKey = magicInstance.get('requiredKey'); + + expect(magicInstance).to.be.instanceof(AutoEnvConfigClass); + expect(magicKey).to.be.equal('magic'); + }); + + it('should use on magic "module.get"', function () { + let magicKey = AutoEnvConfig.get('requiredKey'); + + expect(magicKey).to.be.equal('magic'); + }); + + it('should use on magic "module.load()" after "module.load(name)"', function () { + let specificInstance = AutoEnvConfig.load('env1'); + let specificKey = specificInstance.get('requiredKey'); + let magicInstance = AutoEnvConfig.load(); + let magicKey = magicInstance.get('requiredKey'); + + expect(magicInstance).to.be.instanceof(AutoEnvConfigClass); + expect(specificInstance).to.be.instanceof(AutoEnvConfigClass); + expect(magicInstance).to.not.be.equal(specificInstance); + expect(specificKey).to.be.equal('value1'); + expect(magicKey).to.be.equal('magic'); + }); + + it('should use on magic "module.get" after "module.load(name)"', function () { + let specificInstance = AutoEnvConfig.load('env1'); + let magicKey = AutoEnvConfig.get("requiredKey"); + + expect(specificInstance).to.be.instanceof(AutoEnvConfigClass); + expect(magicKey).to.be.equal('value1'); + }); +}); + +describe('Magic Methods', function() { + it('magic "module.has(key)" should return true when key is present', function () { + let hasMagicKey = AutoEnvConfig.has('requiredKey'); + expect(hasMagicKey).to.be.true; + }); + it('magic "module.has(key)" should return false when key is not present', function () { + let hasMagicKey = AutoEnvConfig.has('nonexistentKey'); + expect(hasMagicKey).to.be.false; + }); + it('magic "module.get(key)" should return value when key is present', function () { + let magicKey = AutoEnvConfig.get('requiredKey'); + expect(magicKey).to.be.equal('magic'); + }); + it('magic "module.get(key)" should throw when key is not present', function () { + let fn = function () { AutoEnvConfig.get('nonexistentKey'); }; + expect(fn).to.throw('Can\'t find key "nonexistentKey" on current env config ("magic.json") and there was no default on function call!'); + }); + it('magic "module.get(key, default)" should return value when default is supplied and key is present', function () { + let magicKey = AutoEnvConfig.get('requiredKey', 'defaultValue'); + expect(magicKey).to.be.equal('magic'); + }); + it('magic "module.get(key, default)" should return default it is supplied and key is not present', function () { + let magicKey = AutoEnvConfig.get('nonexistentKey', 'defaultValue'); + expect(magicKey).to.be.equal('defaultValue'); + }); +}); + +describe('Instance Methods', function() { + it('"instance.has(key)" should return true when key is present', function () { + let magicInstance = AutoEnvConfig.load(); + let hasInstanceKey = magicInstance.has('requiredKey'); + expect(magicInstance).to.be.instanceof(AutoEnvConfigClass); + expect(hasInstanceKey).to.be.true; + }); + it('"instance.has(key)" should return false when key is not present', function () { + let magicInstance = AutoEnvConfig.load(); + let hasInstanceKey = magicInstance.has('nonexistentKey'); + expect(magicInstance).to.be.instanceof(AutoEnvConfigClass); + expect(hasInstanceKey).to.be.false; + }); + it('"instance.get(key)" should return value when key is present', function () { + let magicInstance = AutoEnvConfig.load(); + let instanceKey = magicInstance.get('requiredKey'); + expect(instanceKey).to.be.equal('magic'); + }); + it('"instance.get(key)" should throw when key is not present', function () { + let magicInstance = AutoEnvConfig.load(); + let fn = function () { magicInstance.get('nonexistentKey'); }; + expect(fn).to.throw('Can\'t find key "nonexistentKey" on current env config ("magic.json") and there was no default on function call!'); + }); + it('"instance.get(key, default)" should return value when default is supplied and key is present', function () { + let magicInstance = AutoEnvConfig.load(); + let instanceKey = magicInstance.get('requiredKey', 'defaultValue'); + expect(instanceKey).to.be.equal('magic'); + }); + it('"instance.get(key, default)" should return default it is supplied and key is not present', function () { + let magicInstance = AutoEnvConfig.load(); + let instanceKey = magicInstance.get('nonexistentKey', 'defaultValue'); + expect(instanceKey).to.be.equal('defaultValue'); + }); +});