Skip to content

Commit 51f360c

Browse files
committed
Merge branch 'alpha' into sanitize-errors
2 parents 79bf5f5 + 50650a3 commit 51f360c

File tree

9 files changed

+150
-17
lines changed

9 files changed

+150
-17
lines changed

changelogs/CHANGELOG_alpha.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,17 @@
1+
# [8.5.0-alpha.11](https://github.com/parse-community/parse-server/compare/8.5.0-alpha.10...8.5.0-alpha.11) (2025-11-17)
2+
3+
4+
### Bug Fixes
5+
6+
* Deprecation warning logged at server launch for nested Parse Server option even if option is explicitly set ([#9934](https://github.com/parse-community/parse-server/issues/9934)) ([c22cb0a](https://github.com/parse-community/parse-server/commit/c22cb0ae58e64cd0e4597ab9610d57a1155c44a2))
7+
8+
# [8.5.0-alpha.10](https://github.com/parse-community/parse-server/compare/8.5.0-alpha.9...8.5.0-alpha.10) (2025-11-17)
9+
10+
11+
### Bug Fixes
12+
13+
* Queries with object field `authData.provider.id` are incorrectly transformed to `_auth_data_provider.id` for custom classes ([#9932](https://github.com/parse-community/parse-server/issues/9932)) ([7b9fa18](https://github.com/parse-community/parse-server/commit/7b9fa18f968ec084ea0b35dad2b5ba0451d59787))
14+
115
# [8.5.0-alpha.9](https://github.com/parse-community/parse-server/compare/8.5.0-alpha.8...8.5.0-alpha.9) (2025-11-17)
216

317

package-lock.json

Lines changed: 14 additions & 14 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "parse-server",
3-
"version": "8.5.0-alpha.9",
3+
"version": "8.5.0-alpha.11",
44
"description": "An express module providing a Parse-compatible API server",
55
"main": "lib/index.js",
66
"repository": {

spec/Deprecator.spec.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,29 @@ describe('Deprecator', () => {
4545
`DeprecationWarning: ${options.usage} is deprecated and will be removed in a future version. ${options.solution}`
4646
);
4747
});
48+
49+
it('logs deprecation for nested option key with dot notation', async () => {
50+
deprecations = [{ optionKey: 'databaseOptions.allowPublicExplain', changeNewDefault: 'false' }];
51+
52+
spyOn(Deprecator, '_getDeprecations').and.callFake(() => deprecations);
53+
const logger = require('../lib/logger').logger;
54+
const logSpy = spyOn(logger, 'warn').and.callFake(() => {});
55+
56+
await reconfigureServer();
57+
expect(logSpy.calls.all()[0].args[0]).toEqual(
58+
`DeprecationWarning: The Parse Server option '${deprecations[0].optionKey}' default will change to '${deprecations[0].changeNewDefault}' in a future version.`
59+
);
60+
});
61+
62+
it('does not log deprecation for nested option key if option is set manually', async () => {
63+
deprecations = [{ optionKey: 'databaseOptions.allowPublicExplain', changeNewDefault: 'false' }];
64+
65+
spyOn(Deprecator, '_getDeprecations').and.callFake(() => deprecations);
66+
const logSpy = spyOn(Deprecator, '_logOption').and.callFake(() => {});
67+
const Config = require('../lib/Config');
68+
const config = Config.get('test');
69+
// Directly test scanParseServerOptions with nested option set
70+
Deprecator.scanParseServerOptions({ databaseOptions: { allowPublicExplain: true } });
71+
expect(logSpy).not.toHaveBeenCalled();
72+
});
4873
});

spec/MongoTransform.spec.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,23 @@ describe('parseObjectToMongoObjectForCreate', () => {
521521
expect(output.authData).toBe('random');
522522
done();
523523
});
524+
525+
it('should only transform authData.provider.id for _User class', () => {
526+
// Test that for _User class, authData.facebook.id is transformed
527+
const userInput = {
528+
'authData.facebook.id': '10000000000000001',
529+
};
530+
const userOutput = transform.transformWhere('_User', userInput, { fields: {} });
531+
expect(userOutput['_auth_data_facebook.id']).toBe('10000000000000001');
532+
533+
// Test that for non-User classes, authData.facebook.id is NOT transformed
534+
const customInput = {
535+
'authData.facebook.id': '10000000000000001',
536+
};
537+
const customOutput = transform.transformWhere('SpamAlerts', customInput, { fields: {} });
538+
expect(customOutput['authData.facebook.id']).toBe('10000000000000001');
539+
expect(customOutput['_auth_data_facebook.id']).toBeUndefined();
540+
});
524541
});
525542

526543
it('cannot have a custom field name beginning with underscore', done => {

spec/Utils.spec.js

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,4 +122,55 @@ describe('Utils', () => {
122122
expect(result).toBe('{"name":"test","number":42,"nested":{"key":"value"}}');
123123
});
124124
});
125+
126+
describe('getNestedProperty', () => {
127+
it('should get top-level property', () => {
128+
const obj = { foo: 'bar' };
129+
expect(Utils.getNestedProperty(obj, 'foo')).toBe('bar');
130+
});
131+
132+
it('should get nested property with dot notation', () => {
133+
const obj = { database: { options: { enabled: true } } };
134+
expect(Utils.getNestedProperty(obj, 'database.options.enabled')).toBe(true);
135+
});
136+
137+
it('should return undefined for non-existent property', () => {
138+
const obj = { foo: 'bar' };
139+
expect(Utils.getNestedProperty(obj, 'baz')).toBeUndefined();
140+
});
141+
142+
it('should return undefined for non-existent nested property', () => {
143+
const obj = { database: { options: {} } };
144+
expect(Utils.getNestedProperty(obj, 'database.options.enabled')).toBeUndefined();
145+
});
146+
147+
it('should return undefined when path traverses non-object', () => {
148+
const obj = { database: 'string' };
149+
expect(Utils.getNestedProperty(obj, 'database.options.enabled')).toBeUndefined();
150+
});
151+
152+
it('should return undefined for null object', () => {
153+
expect(Utils.getNestedProperty(null, 'foo')).toBeUndefined();
154+
});
155+
156+
it('should return undefined for empty path', () => {
157+
const obj = { foo: 'bar' };
158+
expect(Utils.getNestedProperty(obj, '')).toBeUndefined();
159+
});
160+
161+
it('should handle value of 0', () => {
162+
const obj = { database: { timeout: 0 } };
163+
expect(Utils.getNestedProperty(obj, 'database.timeout')).toBe(0);
164+
});
165+
166+
it('should handle value of false', () => {
167+
const obj = { database: { enabled: false } };
168+
expect(Utils.getNestedProperty(obj, 'database.enabled')).toBe(false);
169+
});
170+
171+
it('should handle value of empty string', () => {
172+
const obj = { database: { name: '' } };
173+
expect(Utils.getNestedProperty(obj, 'database.name')).toBe('');
174+
});
175+
});
125176
});

src/Adapters/Storage/Mongo/MongoTransform.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,7 @@ function transformQueryKeyValue(className, key, value, schema, count = false) {
305305
default: {
306306
// Other auth data
307307
const authDataMatch = key.match(/^authData\.([a-zA-Z0-9_]+)\.id$/);
308-
if (authDataMatch) {
308+
if (authDataMatch && className === '_User') {
309309
const provider = authDataMatch[1];
310310
// Special-case auth data.
311311
return { key: `_auth_data_${provider}.id`, value };

src/Deprecator/Deprecator.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import logger from '../logger';
22
import Deprecations from './Deprecations';
3+
import Utils from '../Utils';
34

45
/**
56
* The deprecator class.
@@ -21,7 +22,7 @@ class Deprecator {
2122
const changeNewDefault = deprecation.changeNewDefault;
2223

2324
// If default will change, only throw a warning if option is not set
24-
if (changeNewDefault != null && options[optionKey] == null) {
25+
if (changeNewDefault != null && Utils.getNestedProperty(options, optionKey) == null) {
2526
Deprecator._logOption({ optionKey, changeNewDefault, solution });
2627
}
2728
}

src/Utils.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,31 @@ class Utils {
444444
return value;
445445
};
446446
}
447+
448+
/**
449+
* Gets a nested property value from an object using dot notation.
450+
* @param {Object} obj The object to get the property from.
451+
* @param {String} path The property path in dot notation, e.g. 'databaseOptions.allowPublicExplain'.
452+
* @returns {any} The property value or undefined if not found.
453+
* @example
454+
* const obj = { database: { options: { enabled: true } } };
455+
* Utils.getNestedProperty(obj, 'database.options.enabled');
456+
* // Output: true
457+
*/
458+
static getNestedProperty(obj, path) {
459+
if (!obj || !path) {
460+
return undefined;
461+
}
462+
const keys = path.split('.');
463+
let current = obj;
464+
for (const key of keys) {
465+
if (current == null || typeof current !== 'object') {
466+
return undefined;
467+
}
468+
current = current[key];
469+
}
470+
return current;
471+
}
447472
}
448473

449474
module.exports = Utils;

0 commit comments

Comments
 (0)