[api test] Finished API and tests for hierarchical configuration storage.
This commit is contained in:
parent
7ef9b11d33
commit
1ef5797e83
13 changed files with 206 additions and 70 deletions
|
@ -70,8 +70,8 @@ Provider.prototype.use = function (name, options) {
|
|||
throw new Error('Cannot use reserved name: ' + name);
|
||||
}
|
||||
|
||||
options = options || {};
|
||||
options.type = options.type || name;
|
||||
options = options || {};
|
||||
var type = options.type || name;
|
||||
|
||||
function sameOptions (store) {
|
||||
return Object.keys(options).every(function (key) {
|
||||
|
@ -108,15 +108,19 @@ Provider.prototype.add = function (name, options) {
|
|||
throw new Error('Cannot use reserved name: ' + name);
|
||||
}
|
||||
|
||||
options = options || {};
|
||||
options.type = options.type || name;
|
||||
options = options || {};
|
||||
var type = options.type || name;
|
||||
|
||||
if (Object.keys(stores).indexOf(common.capitalize(options.type)) === -1) {
|
||||
throw new Error('Cannot add store with unknown type: ' + options.type);
|
||||
if (Object.keys(stores).indexOf(common.capitalize(type)) === -1) {
|
||||
throw new Error('Cannot add store with unknown type: ' + type);
|
||||
}
|
||||
|
||||
this[name] = this.create(options.type, options);
|
||||
this[name] = this.create(type, options);
|
||||
this._stores.push(name);
|
||||
|
||||
if (this[name].loadSync) {
|
||||
this[name].loadSync();
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
|
@ -156,7 +160,45 @@ Provider.prototype.create = function (type, options) {
|
|||
// Retrieves the value for the specified key (if any).
|
||||
//
|
||||
Provider.prototype.get = function (key, callback) {
|
||||
return this._execute('get', 1, key, callback);
|
||||
//
|
||||
// If there is no callback we can short-circuit into the default
|
||||
// logic for traversing stores.
|
||||
//
|
||||
if (!callback) {
|
||||
return this._execute('get', 1, key, callback);
|
||||
}
|
||||
|
||||
//
|
||||
// Otherwise the asynchronous, hierarchical `get` is
|
||||
// slightly more complicated because we do not need to traverse
|
||||
// the entire set of stores, but up until there is a defined value.
|
||||
//
|
||||
var current = 0,
|
||||
self = this,
|
||||
response;
|
||||
|
||||
async.whilst(function () {
|
||||
return typeof response === 'undefined' && current < self._stores.length;
|
||||
}, function (next) {
|
||||
var store = self[self._stores[current]];
|
||||
current++;
|
||||
|
||||
if (store.get.length >= 2) {
|
||||
return store.get(key, function (err, value) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
response = value;
|
||||
next();
|
||||
});
|
||||
}
|
||||
|
||||
response = store.get(key);
|
||||
next();
|
||||
}, function (err) {
|
||||
return err ? callback(err) : callback(null, response);
|
||||
});
|
||||
};
|
||||
|
||||
//
|
||||
|
@ -293,7 +335,7 @@ Provider.prototype.save = function (value, callback) {
|
|||
function saveStore(name, next) {
|
||||
var store = self[name];
|
||||
|
||||
if (!store.load && !store.saveSync) {
|
||||
if (!store.save && !store.saveSync) {
|
||||
return next(new Error('nconf store ' + store.type + ' has no save() method'));
|
||||
}
|
||||
|
||||
|
@ -357,10 +399,8 @@ Provider.prototype._execute = function (action, syncLength /* [arguments] */) {
|
|||
// Gets or sets a property representing overrides which supercede all
|
||||
// other values for this instance.
|
||||
//
|
||||
Provider.prototype.__defineSetter__('overrides', updateSystem.bind(this, 'overrides'));
|
||||
Provider.prototype.__defineGetter__('overrides', function () {
|
||||
return this._argv;
|
||||
});
|
||||
Provider.prototype.__defineSetter__('overrides', function (val) { updateSystem.call(this, 'overrides', val) });
|
||||
Provider.prototype.__defineGetter__('overrides', function () { return this._argv });
|
||||
|
||||
//
|
||||
// ### @argv {boolean}
|
||||
|
@ -368,10 +408,8 @@ Provider.prototype.__defineGetter__('overrides', function () {
|
|||
// by checking `optimist.argv`. Can be a boolean or the pass-thru
|
||||
// options for `optimist`.
|
||||
//
|
||||
Provider.prototype.__defineSetter__('argv', updateSystem.bind(this, 'argv'));
|
||||
Provider.prototype.__defineGetter__('argv', function () {
|
||||
return this._argv;
|
||||
});
|
||||
Provider.prototype.__defineSetter__('argv', function (val) { updateSystem.call(this, 'argv', val) });
|
||||
Provider.prototype.__defineGetter__('argv', function () { return this._argv });
|
||||
|
||||
//
|
||||
// ### @env {boolean}
|
||||
|
@ -379,10 +417,8 @@ Provider.prototype.__defineGetter__('argv', function () {
|
|||
// by checking `process.env`. Can be a boolean or an Array of
|
||||
// environment variables to extract.
|
||||
//
|
||||
Provider.prototype.__defineSetter__('env', updateSystem.bind(this, 'env'));
|
||||
Provider.prototype.__defineGetter__('env', function () {
|
||||
return this._env;
|
||||
});
|
||||
Provider.prototype.__defineSetter__('env', function (val) { updateSystem.call(this, 'env', val) });
|
||||
Provider.prototype.__defineGetter__('env', function () { return this._env });
|
||||
|
||||
//
|
||||
// Throw the `err` if a callback is not supplied
|
||||
|
|
|
@ -18,7 +18,7 @@ var fs = require('fs'),
|
|||
// around the Memory store that can persist configuration to disk.
|
||||
//
|
||||
var File = exports.File = function (options) {
|
||||
if (!options.file) {
|
||||
if (!options || !options.file) {
|
||||
throw new Error ('Missing required option `files`');
|
||||
}
|
||||
|
||||
|
@ -128,8 +128,8 @@ File.prototype.loadSync = function () {
|
|||
// Else, the path exists, read it from disk
|
||||
//
|
||||
try {
|
||||
data = fs.readFileSync(this.file, 'utf8');
|
||||
this.store = this.format.parse(data);
|
||||
data = this.format.parse(fs.readFileSync(this.file, 'utf8'));
|
||||
this.store = data;
|
||||
}
|
||||
catch (ex) {
|
||||
throw new Error("Error parsing your JSON configuration file.")
|
||||
|
|
|
@ -17,10 +17,10 @@ var util = require('util'),
|
|||
// and command-line arguments.
|
||||
//
|
||||
var System = exports.System = function (options) {
|
||||
options = options || {};
|
||||
Memory.call(this, options);
|
||||
|
||||
this.type = 'system';
|
||||
this.readOnly = true;
|
||||
this.overrides = options.overrides || null;
|
||||
this.env = options.env || false;
|
||||
this.argv = options.argv || false;
|
||||
|
|
11
test/fixtures/scripts/nconf-argv.js
vendored
Normal file
11
test/fixtures/scripts/nconf-argv.js
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
/*
|
||||
* default-argv.js: Test fixture for using optimist defaults with nconf.
|
||||
*
|
||||
* (C) 2011, Charlie Robbins
|
||||
*
|
||||
*/
|
||||
|
||||
var nconf = require('../../../lib/nconf');
|
||||
|
||||
nconf.argv = true;
|
||||
process.stdout.write(nconf.get('something'));
|
11
test/fixtures/scripts/nconf-env.js
vendored
Normal file
11
test/fixtures/scripts/nconf-env.js
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
/*
|
||||
* nconf-env.js: Test fixture for using process.env defaults with nconf.
|
||||
*
|
||||
* (C) 2011, Charlie Robbins
|
||||
*
|
||||
*/
|
||||
|
||||
var nconf = require('../../../lib/nconf');
|
||||
|
||||
nconf.env = true;
|
||||
process.stdout.write(nconf.get('SOMETHING'));
|
12
test/fixtures/scripts/provider-argv.js
vendored
Normal file
12
test/fixtures/scripts/provider-argv.js
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* provider-argv.js: Test fixture for using optimist defaults with nconf.
|
||||
*
|
||||
* (C) 2011, Charlie Robbins
|
||||
*
|
||||
*/
|
||||
|
||||
var nconf = require('../../../lib/nconf');
|
||||
|
||||
var provider = new (nconf.Provider)({ argv: true });
|
||||
|
||||
process.stdout.write(provider.get('something'));
|
12
test/fixtures/scripts/provider-env.js
vendored
Normal file
12
test/fixtures/scripts/provider-env.js
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* provider-argv.js: Test fixture for using process.env defaults with nconf.
|
||||
*
|
||||
* (C) 2011, Charlie Robbins
|
||||
*
|
||||
*/
|
||||
|
||||
var nconf = require('../../../lib/nconf');
|
||||
|
||||
var provider = new (nconf.Provider)({ env: true });
|
||||
|
||||
process.stdout.write(provider.get('SOMETHING'));
|
|
@ -27,10 +27,23 @@ exports.assertMerged = function (err, merged) {
|
|||
assert.isTrue(merged.elderberries);
|
||||
};
|
||||
|
||||
exports.assertDefaults = function (script) {
|
||||
exports.assertSystemConf = function (options) {
|
||||
return {
|
||||
topic: function () {
|
||||
spawn('node', [script, '--something', 'foobar'])
|
||||
var env = null;
|
||||
|
||||
if (options.env) {
|
||||
env = {}
|
||||
Object.keys(process.env).forEach(function (key) {
|
||||
env[key] = process.env[key];
|
||||
});
|
||||
|
||||
Object.keys(options.env).forEach(function (key) {
|
||||
env[key] = options.env[key];
|
||||
});
|
||||
}
|
||||
|
||||
spawn('node', [options.script].concat(options.argv), { env: env })
|
||||
.stdout.once('data', this.callback.bind(this, null));
|
||||
},
|
||||
"should respond with the value passed into the script": function (_, data) {
|
||||
|
|
|
@ -51,10 +51,25 @@ vows.describe('nconf').addBatch({
|
|||
}
|
||||
},
|
||||
"the get() method": {
|
||||
"should respond with the correct value": function () {
|
||||
assert.equal(nconf.get('foo:bar:bazz'), 'buzz');
|
||||
"without a callback": {
|
||||
"should respond with the correct value": function () {
|
||||
assert.equal(nconf.get('foo:bar:bazz'), 'buzz');
|
||||
}
|
||||
},
|
||||
"with a callback": {
|
||||
topic: function () {
|
||||
nconf.get('foo:bar:bazz', this.callback);
|
||||
},
|
||||
"should respond with the correct value": function (err, value) {
|
||||
assert.equal(value, 'buzz');
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}).addBatch({
|
||||
"When using nconf": {
|
||||
"with the memory store": {
|
||||
"the clear() method": {
|
||||
"should respond with the true": function () {
|
||||
assert.equal(nconf.get('foo:bar:bazz'), 'buzz');
|
||||
|
@ -65,9 +80,7 @@ vows.describe('nconf').addBatch({
|
|||
"the load() method": {
|
||||
"without a callback": {
|
||||
"should throw an exception": function () {
|
||||
assert.throws(function () {
|
||||
nconf.load();
|
||||
})
|
||||
assert.throws(function () { nconf.load() });
|
||||
}
|
||||
},
|
||||
"with a callback": {
|
||||
|
@ -82,9 +95,7 @@ vows.describe('nconf').addBatch({
|
|||
"the save() method": {
|
||||
"without a callback": {
|
||||
"should throw an exception": function () {
|
||||
assert.throws(function () {
|
||||
nconf.save();
|
||||
})
|
||||
assert.throws(function () { nconf.save() });
|
||||
}
|
||||
},
|
||||
"with a callback": {
|
||||
|
|
|
@ -16,32 +16,45 @@ var assert = require('assert'),
|
|||
var fixturesDir = path.join(__dirname, 'fixtures'),
|
||||
mergeFixtures = path.join(fixturesDir, 'merge'),
|
||||
files = [path.join(mergeFixtures, 'file1.json'), path.join(mergeFixtures, 'file2.json')],
|
||||
override = JSON.parse(fs.readFileSync(files[0]), 'utf8'),
|
||||
first = '/path/to/file1',
|
||||
second = '/path/to/file2';
|
||||
override = JSON.parse(fs.readFileSync(files[0]), 'utf8');
|
||||
|
||||
vows.describe('nconf/provider').addBatch({
|
||||
"When using nconf": {
|
||||
"an instance of 'nconf.Provider'": {
|
||||
"calling the use() method with the same store type and different options": {
|
||||
topic: new nconf.Provider().use('file', { file: first }),
|
||||
topic: new nconf.Provider().use('file', { file: files[0] }),
|
||||
"should use a new instance of the store type": function (provider) {
|
||||
var old = provider.file;
|
||||
|
||||
assert.equal(provider.file.file, first);
|
||||
provider.use('file', { file: second });
|
||||
assert.equal(provider.file.file, files[0]);
|
||||
provider.use('file', { file: files[1] });
|
||||
|
||||
assert.notStrictEqual(old, provider.file);
|
||||
assert.equal(provider.file.file, second);
|
||||
assert.equal(provider.file.file, files[1]);
|
||||
}
|
||||
},
|
||||
//"when 'useArgv' is true": helpers.assertDefaults(path.join(fixturesDir, 'scripts', 'nconf-override.js'))
|
||||
"when 'argv' is true": helpers.assertSystemConf({
|
||||
script: path.join(fixturesDir, 'scripts', 'provider-argv.js'),
|
||||
argv: ['--something', 'foobar']
|
||||
}),
|
||||
"when 'env' is true": helpers.assertSystemConf({
|
||||
script: path.join(fixturesDir, 'scripts', 'provider-env.js'),
|
||||
env: { SOMETHING: 'foobar' }
|
||||
}),
|
||||
},
|
||||
/*"the default nconf provider": {
|
||||
"when 'useArgv' is true": helpers.assertDefaults(path.join(fixturesDir, 'scripts', 'default-override.js'))
|
||||
}*/
|
||||
"the default nconf provider": {
|
||||
"when 'argv' is set to true": helpers.assertSystemConf({
|
||||
script: path.join(fixturesDir, 'scripts', 'nconf-argv.js'),
|
||||
argv: ['--something', 'foobar'],
|
||||
env: { SOMETHING: true }
|
||||
}),
|
||||
"when 'env' is set to true": helpers.assertSystemConf({
|
||||
script: path.join(fixturesDir, 'scripts', 'nconf-env.js'),
|
||||
env: { SOMETHING: 'foobar' }
|
||||
})
|
||||
}
|
||||
}
|
||||
})/*.addBatch({
|
||||
}).addBatch({
|
||||
"When using nconf": {
|
||||
"an instance of 'nconf.Provider'": {
|
||||
"the merge() method": {
|
||||
|
@ -49,18 +62,9 @@ vows.describe('nconf/provider').addBatch({
|
|||
"should have the result merged in": function (provider) {
|
||||
provider.load();
|
||||
provider.merge(override);
|
||||
helpers.assertMerged(null, provider);
|
||||
}
|
||||
},
|
||||
"the mergeFiles() method": {
|
||||
topic: function () {
|
||||
var provider = new nconf.Provider().use('memory');
|
||||
provider.mergeFiles(files, this.callback.bind(this, null, provider))
|
||||
},
|
||||
"should have the result merged in": function (_, provider) {
|
||||
helpers.assertMerged(null, provider);
|
||||
helpers.assertMerged(null, provider.file.store);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})*/.export(module);
|
||||
}).export(module);
|
|
@ -9,14 +9,14 @@ var fs = require('fs'),
|
|||
path = require('path'),
|
||||
vows = require('vows'),
|
||||
assert = require('assert'),
|
||||
nconf = require('../lib/nconf'),
|
||||
data = require('./fixtures/data').data,
|
||||
nconf = require('../../lib/nconf'),
|
||||
data = require('../fixtures/data').data,
|
||||
store;
|
||||
|
||||
vows.describe('nconf/stores/file').addBatch({
|
||||
"When using the nconf file store": {
|
||||
topic: function () {
|
||||
var filePath = path.join(__dirname, 'fixtures', 'store.json');
|
||||
var filePath = path.join(__dirname, '..', 'fixtures', 'store.json');
|
||||
fs.writeFileSync(filePath, JSON.stringify(data, null, 2));
|
||||
store = new nconf.stores.File({ file: filePath });
|
||||
return null;
|
||||
|
@ -33,7 +33,7 @@ vows.describe('nconf/stores/file').addBatch({
|
|||
},
|
||||
"When using the nconf file store": {
|
||||
topic: function () {
|
||||
var filePath = path.join(__dirname, 'fixtures', 'malformed.json');
|
||||
var filePath = path.join(__dirname, '..', 'fixtures', 'malformed.json');
|
||||
store = new nconf.stores.File({ file: filePath });
|
||||
return null;
|
||||
},
|
||||
|
@ -49,7 +49,7 @@ vows.describe('nconf/stores/file').addBatch({
|
|||
}).addBatch({
|
||||
"When using the nconf file store": {
|
||||
topic: function () {
|
||||
var tmpPath = path.join(__dirname, 'fixtures', 'tmp.json'),
|
||||
var tmpPath = path.join(__dirname, '..', 'fixtures', 'tmp.json'),
|
||||
tmpStore = new nconf.stores.File({ file: tmpPath });
|
||||
return tmpStore;
|
||||
},
|
||||
|
@ -120,7 +120,7 @@ vows.describe('nconf/stores/file').addBatch({
|
|||
},
|
||||
"when the target file doesn't exist higher in the directory tree": {
|
||||
topic: function () {
|
||||
var filePath = this.filePath = path.join(__dirname, 'fixtures', 'search-store.json');
|
||||
var filePath = this.filePath = path.join(__dirname, '..', 'fixtures', 'search-store.json');
|
||||
return new (nconf.stores.File)({
|
||||
dir: path.dirname(filePath),
|
||||
file: 'search-store.json'
|
||||
|
|
|
@ -7,8 +7,8 @@
|
|||
|
||||
var vows = require('vows'),
|
||||
assert = require('assert'),
|
||||
nconf = require('../lib/nconf'),
|
||||
merge = require('./fixtures/data').merge;
|
||||
nconf = require('../../lib/nconf'),
|
||||
merge = require('../fixtures/data').merge;
|
||||
|
||||
vows.describe('nconf/stores/memory').addBatch({
|
||||
"When using the nconf memory store": {
|
||||
|
|
26
test/stores/system-test.js
Normal file
26
test/stores/system-test.js
Normal file
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* system-test.js: Tests for the nconf system store.
|
||||
*
|
||||
* (C) 2011, Charlie Robbins
|
||||
*
|
||||
*/
|
||||
|
||||
var vows = require('vows'),
|
||||
assert = require('assert'),
|
||||
helpers = require('../helpers'),
|
||||
nconf = require('../../lib/nconf');
|
||||
|
||||
vows.describe('nconf/stores/system').addBatch({
|
||||
"An instance of nconf.stores.System": {
|
||||
topic: new nconf.stores.System(),
|
||||
"should have the correct methods defined": function (system) {
|
||||
assert.isFunction(system.loadSync);
|
||||
assert.isFunction(system.loadOverrides);
|
||||
assert.isFunction(system.loadArgv);
|
||||
assert.isFunction(system.loadEnv);
|
||||
assert.isFalse(system.argv);
|
||||
assert.isFalse(system.env);
|
||||
assert.isNull(system.overrides);
|
||||
}
|
||||
}
|
||||
}).export(module);
|
Loading…
Reference in a new issue