Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
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
25 changes: 24 additions & 1 deletion lib/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -410,10 +410,33 @@ util.makeImmutable = function(object, property, value) {
});
} else {
// Call recursively if an object.
if (util.isObject(value) && !(value instanceof RegExp) && !(value instanceof Date)) {
if (util.isObject(value)) {
// Create a proxy, to capture user updates of configuration options, and throw an exception for awareness, as per:
// https://github.com/lorenwest/node-config/issues/514
value = new Proxy(util.makeImmutable(value), {
get(target, property, receiver) {
// Config's own defined prototype properties and methods (e.g., `get`, `has`, etc.)
const ownProps = [
...Object.getOwnPropertyNames(Config.prototype),
...Object.getOwnPropertyNames(target),
]

// Bypass proxy receiver for properties directly on the target (e.g., RegExp.prototype.source)
// or properties that are not functions to prevent errors related to internal object methods.
if (ownProps.includes(property) || (property in target && typeof target[property] !== 'function')) {
return Reflect.get(target, property);
}

// Otherwise, use the proxy receiver to handle the property access
const ref = Reflect.get(target, property, receiver);

// Binds the method's `this` context to the target object (e.g., Date.prototype.toISOString)
// to ensure it behaves correctly when called on the proxy.
if (typeof ref === 'function') {
return ref.bind(target);
}
return ref;
},
set (target, name) {
const message = (Reflect.has(target, name) ? 'update' : 'add');
// Notify the user.
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"node": ">= 10.0.0"
},
"scripts": {
"test": "./node_modules/vows/bin/vows test/*.js --spec"
"test": "./node_modules/vows/bin/vows test/*.js --spec",
"vows": "./node_modules/vows/bin/vows"
}
}
32 changes: 25 additions & 7 deletions test/11-regexp.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,35 @@ vows.describe('Tests for regexp').addBatch({
return CONFIG;
},

'A regexp should not be proxified': function() {
const regExp = CONFIG.get('SomeMore.regexp1')
assert(regExp instanceof RegExp && !require('util').types.isProxy(regExp))
'A regexp should be immutable': function(config) {
assert.throws(() => {
const SomeMore = config.get('SomeMore');
SomeMore.regexp1 = /new value/
}, /Can not update runtime configuration/)
},

'A regexp should not be replaced': function() {
assert.deepEqual(CONFIG.SomeMore.regexp1, /This is a Regexp/g);
'A regexp should be able to call RegExp methods': function(config) {
assert.doesNotThrow(() => {
/** @type {RegExp} */
const regExp = config.get('SomeMore.regexp1');
regExp.exec();
})
},

'A regexp should be replaced': function() {
assert.deepEqual(CONFIG.SomeMore.regexp2, /This is the replaced/g);
'A regexp should be able to access own props': function(config) {
assert.doesNotThrow(() => {
/** @type {RegExp} */
const regExp = config.get('SomeMore.regexp1')
assert.ok(regExp.source)
})
},

'A regexp should be the correct source': function(config) {
assert.equal(config.SomeMore.regexp1.source, /This is a Regexp/g.source);
},

'A regexp should be the replaced app instance value': function(config) {
assert.equal(config.SomeMore.regexp2.source, /This is the replaced/g.source);
}
}
})
Expand Down
23 changes: 19 additions & 4 deletions test/21-date.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

process.env.NODE_CONFIG_DIR = __dirname + '/config';
process.env.NODE_ENV = 'test';
process.env.NODE_APP_INSTANCE = 'date';

var requireUncached = require('./_utils/requireUncached');

Expand All @@ -17,9 +16,25 @@ vows.describe('Tests for date').addBatch({
return CONFIG;
},

'A date should not be proxified': function() {
const date1 = CONFIG.get('SomeMore.date1')
assert(date1 instanceof Date && !require('util').types.isProxy(date1))
'A date should be immutable': function(config) {
assert.throws(() => {
const SomeMore = config.get('SomeMore');
SomeMore.date1 = new Date()
}, /Can not update runtime configuration/)
},

'A date should be able to call Date methods': function(config) {
assert.doesNotThrow(() => {
/** @type {Date} */
const date1 = config.get('SomeMore.date1');
date1.toISOString();
})
},

'A date should be the correct value': function(config) {
/** @type {Date} */
const date1 = config.get('SomeMore.date1');
assert.equal(date1.toISOString(), '2024-12-18T04:54:56.118Z');
},
}
})
Expand Down
38 changes: 38 additions & 0 deletions test/22-binary.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
'use strict';

process.env.NODE_CONFIG_DIR = __dirname + '/config';
process.env.NODE_ENV = 'test';
process.env.NODE_APP_INSTANCE = '3';

var requireUncached = require('./_utils/requireUncached');

var CONFIG = requireUncached(__dirname + '/../lib/config');
var { Buffer } = require('buffer')
var assert = require('assert');
var vows = require('vows');

vows.describe('Tests for binary').addBatch({
'Binary tests': {
topic: function() {
return CONFIG;
},

'A binary value should be immutable': function(config) {
assert.throws(() => {
const auth = config.get('auth');
auth.secret = new Uint8Array([ 1, 2, 3 ]);
}, /Can not update runtime configuration/)
},

'A binary value should remain unmangled': (config) => {
const expectedSecret = new Uint8Array([ 0, 1, 2, 3, 4, 5 ]);
const actualSecret = config.get('auth.secret');

const expectedBuffer = Buffer.from(expectedSecret);
const actualBuffer = Buffer.from(actualSecret);

assert.deepEqual(expectedBuffer, actualBuffer);
}
}
})
.export(module);
25 changes: 25 additions & 0 deletions test/23-serialize.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
'use strict';

process.env.NODE_CONFIG_DIR = __dirname + '/23-serialize';

var requireUncached = require('./_utils/requireUncached');

var CONFIG = requireUncached(__dirname + '/../lib/config');
var assert = require('assert');
var vows = require('vows');

vows.describe('Tests for serialization').addBatch({
'Binary tests': {
topic: function() {
return CONFIG;
},

'should be serializable with complex values': (config) => {
assert.doesNotThrow(() => {
const val = config.get('level1')
JSON.stringify(val)
})
}
}
})
.export(module);
5 changes: 5 additions & 0 deletions test/23-serialize/default.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports = {
level1: {
func1: () => `doesn't matter`
},
};
2 changes: 1 addition & 1 deletion test/config/default.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ module.exports = {
SomeMore: {
regexp1: /This is a Regexp/g,
regexp2: /This is another/g,
date1: new Date()
date1: new Date('2024-12-18T04:54:56.118Z'),
},

};
3 changes: 3 additions & 0 deletions test/config/local-3.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@

Customers:
altDbPort1: 2209

auth:
secret: !!binary 'AAECAwQF'