diff --git a/spec/Deprecator.spec.js b/spec/Deprecator.spec.js index 3af5d10c31..80e8632014 100644 --- a/spec/Deprecator.spec.js +++ b/spec/Deprecator.spec.js @@ -45,4 +45,29 @@ describe('Deprecator', () => { `DeprecationWarning: ${options.usage} is deprecated and will be removed in a future version. ${options.solution}` ); }); + + it('logs deprecation for nested option key with dot notation', async () => { + deprecations = [{ optionKey: 'databaseOptions.allowPublicExplain', changeNewDefault: 'false' }]; + + spyOn(Deprecator, '_getDeprecations').and.callFake(() => deprecations); + const logger = require('../lib/logger').logger; + const logSpy = spyOn(logger, 'warn').and.callFake(() => {}); + + await reconfigureServer(); + expect(logSpy.calls.all()[0].args[0]).toEqual( + `DeprecationWarning: The Parse Server option '${deprecations[0].optionKey}' default will change to '${deprecations[0].changeNewDefault}' in a future version.` + ); + }); + + it('does not log deprecation for nested option key if option is set manually', async () => { + deprecations = [{ optionKey: 'databaseOptions.allowPublicExplain', changeNewDefault: 'false' }]; + + spyOn(Deprecator, '_getDeprecations').and.callFake(() => deprecations); + const logSpy = spyOn(Deprecator, '_logOption').and.callFake(() => {}); + const Config = require('../lib/Config'); + const config = Config.get('test'); + // Directly test scanParseServerOptions with nested option set + Deprecator.scanParseServerOptions({ databaseOptions: { allowPublicExplain: true } }); + expect(logSpy).not.toHaveBeenCalled(); + }); }); diff --git a/spec/Utils.spec.js b/spec/Utils.spec.js index b1277c6bfe..2bbc5656a2 100644 --- a/spec/Utils.spec.js +++ b/spec/Utils.spec.js @@ -122,4 +122,55 @@ describe('Utils', () => { expect(result).toBe('{"name":"test","number":42,"nested":{"key":"value"}}'); }); }); + + describe('getNestedProperty', () => { + it('should get top-level property', () => { + const obj = { foo: 'bar' }; + expect(Utils.getNestedProperty(obj, 'foo')).toBe('bar'); + }); + + it('should get nested property with dot notation', () => { + const obj = { database: { options: { enabled: true } } }; + expect(Utils.getNestedProperty(obj, 'database.options.enabled')).toBe(true); + }); + + it('should return undefined for non-existent property', () => { + const obj = { foo: 'bar' }; + expect(Utils.getNestedProperty(obj, 'baz')).toBeUndefined(); + }); + + it('should return undefined for non-existent nested property', () => { + const obj = { database: { options: {} } }; + expect(Utils.getNestedProperty(obj, 'database.options.enabled')).toBeUndefined(); + }); + + it('should return undefined when path traverses non-object', () => { + const obj = { database: 'string' }; + expect(Utils.getNestedProperty(obj, 'database.options.enabled')).toBeUndefined(); + }); + + it('should return undefined for null object', () => { + expect(Utils.getNestedProperty(null, 'foo')).toBeUndefined(); + }); + + it('should return undefined for empty path', () => { + const obj = { foo: 'bar' }; + expect(Utils.getNestedProperty(obj, '')).toBeUndefined(); + }); + + it('should handle value of 0', () => { + const obj = { database: { timeout: 0 } }; + expect(Utils.getNestedProperty(obj, 'database.timeout')).toBe(0); + }); + + it('should handle value of false', () => { + const obj = { database: { enabled: false } }; + expect(Utils.getNestedProperty(obj, 'database.enabled')).toBe(false); + }); + + it('should handle value of empty string', () => { + const obj = { database: { name: '' } }; + expect(Utils.getNestedProperty(obj, 'database.name')).toBe(''); + }); + }); }); diff --git a/src/Deprecator/Deprecator.js b/src/Deprecator/Deprecator.js index 27033c946d..4744efbdd8 100644 --- a/src/Deprecator/Deprecator.js +++ b/src/Deprecator/Deprecator.js @@ -1,5 +1,6 @@ import logger from '../logger'; import Deprecations from './Deprecations'; +import Utils from '../Utils'; /** * The deprecator class. @@ -21,7 +22,7 @@ class Deprecator { const changeNewDefault = deprecation.changeNewDefault; // If default will change, only throw a warning if option is not set - if (changeNewDefault != null && options[optionKey] == null) { + if (changeNewDefault != null && Utils.getNestedProperty(options, optionKey) == null) { Deprecator._logOption({ optionKey, changeNewDefault, solution }); } } diff --git a/src/Utils.js b/src/Utils.js index f46e09de1e..cc00ac0bb7 100644 --- a/src/Utils.js +++ b/src/Utils.js @@ -444,6 +444,31 @@ class Utils { return value; }; } + + /** + * Gets a nested property value from an object using dot notation. + * @param {Object} obj The object to get the property from. + * @param {String} path The property path in dot notation, e.g. 'databaseOptions.allowPublicExplain'. + * @returns {any} The property value or undefined if not found. + * @example + * const obj = { database: { options: { enabled: true } } }; + * Utils.getNestedProperty(obj, 'database.options.enabled'); + * // Output: true + */ + static getNestedProperty(obj, path) { + if (!obj || !path) { + return undefined; + } + const keys = path.split('.'); + let current = obj; + for (const key of keys) { + if (current == null || typeof current !== 'object') { + return undefined; + } + current = current[key]; + } + return current; + } } module.exports = Utils;