Skip to content

Commit 636a307

Browse files
committed
feat: merge
2 parents e36a4ba + 2424054 commit 636a307

File tree

13 files changed

+667
-36
lines changed

13 files changed

+667
-36
lines changed

changelogs/CHANGELOG_alpha.md

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,23 @@
1-
# [8.4.0-alpha.3](https://github.com/parse-community/parse-server/compare/8.4.0-alpha.2...8.4.0-alpha.3) (2025-11-06)
1+
# [8.5.0-alpha.3](https://github.com/parse-community/parse-server/compare/8.5.0-alpha.2...8.5.0-alpha.3) (2025-11-07)
22

3-
- Deprecation DEPPS12: database option `allowPublicExplain` defaults to `true` ([#7519](https://github.com/parse-community/parse-server/issues/7519)) ([DEPPS12](https://github.com/parse-community/parse-server/blob/alpha/DEPRECATIONS.md#depps12))
3+
4+
### Features
5+
6+
* Add support for more MongoDB driver options ([#9911](https://github.com/parse-community/parse-server/issues/9911)) ([cff451e](https://github.com/parse-community/parse-server/commit/cff451eabdc380affa600ed711de66f7bd1d00aa))
7+
8+
# [8.5.0-alpha.2](https://github.com/parse-community/parse-server/compare/8.5.0-alpha.1...8.5.0-alpha.2) (2025-11-07)
9+
10+
11+
### Features
12+
13+
* Add support for MongoDB driver options `serverSelectionTimeoutMS`, `maxIdleTimeMS`, `heartbeatFrequencyMS` ([#9910](https://github.com/parse-community/parse-server/issues/9910)) ([1b661e9](https://github.com/parse-community/parse-server/commit/1b661e98c86a1db79e076a7297cd9199a72ae1ac))
14+
15+
# [8.5.0-alpha.1](https://github.com/parse-community/parse-server/compare/8.4.0...8.5.0-alpha.1) (2025-11-07)
16+
17+
18+
### Features
19+
20+
* Allow option `publicServerURL` to be set dynamically as asynchronous function ([#9803](https://github.com/parse-community/parse-server/issues/9803)) ([460a65c](https://github.com/parse-community/parse-server/commit/460a65cf612f4c86af8038cafcc7e7ffe9eb8440))
421

522
# [8.4.0-alpha.2](https://github.com/parse-community/parse-server/compare/8.4.0-alpha.1...8.4.0-alpha.2) (2025-11-05)
623

package-lock.json

Lines changed: 2 additions & 2 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.4.0",
3+
"version": "8.5.0-alpha.3",
44
"description": "An express module providing a Parse-compatible API server",
55
"main": "lib/index.js",
66
"repository": {

spec/ParseConfigKey.spec.js

Lines changed: 61 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -73,16 +73,70 @@ describe('Config Keys', () => {
7373
filesAdapter: null,
7474
databaseAdapter: null,
7575
databaseOptions: {
76-
retryWrites: true,
77-
maxTimeMS: 1000,
78-
maxStalenessSeconds: 10,
76+
appName: 'MyParseApp',
77+
78+
// Cannot be tested as it requires authentication setup
79+
// authMechanism: 'SCRAM-SHA-256',
80+
// authMechanismProperties: { SERVICE_NAME: 'mongodb' },
81+
82+
authSource: 'admin',
83+
autoSelectFamily: true,
84+
autoSelectFamilyAttemptTimeout: 3000,
85+
compressors: ['zlib'],
86+
connectTimeoutMS: 5000,
87+
directConnection: false,
88+
disableIndexFieldValidation: true,
89+
forceServerObjectId: false,
90+
heartbeatFrequencyMS: 10000,
91+
localThresholdMS: 15,
92+
maxConnecting: 2,
93+
maxIdleTimeMS: 60000,
7994
maxPoolSize: 10,
95+
maxStalenessSeconds: 90,
96+
maxTimeMS: 1000,
8097
minPoolSize: 5,
81-
connectTimeoutMS: 5000,
98+
99+
// Cannot be tested as it requires a proxy setup
100+
// proxyHost: 'proxy.example.com',
101+
// proxyPassword: 'proxypass',
102+
// proxyPort: 1080,
103+
// proxyUsername: 'proxyuser',
104+
105+
readConcernLevel: 'majority',
106+
readPreference: 'secondaryPreferred',
107+
readPreferenceTags: [{ dc: 'east' }],
108+
109+
// Cannot be tested as it requires a replica set setup
110+
// replicaSet: 'myReplicaSet',
111+
112+
retryReads: true,
113+
retryWrites: true,
114+
serverMonitoringMode: 'auto',
115+
serverSelectionTimeoutMS: 5000,
82116
socketTimeoutMS: 5000,
83-
autoSelectFamily: true,
84-
autoSelectFamilyAttemptTimeout: 3000,
85-
disableIndexFieldValidation: true
117+
118+
// Cannot be tested as it requires a replica cluster setup
119+
// srvMaxHosts: 0,
120+
// srvServiceName: 'mongodb',
121+
122+
ssl: false,
123+
tls: false,
124+
tlsAllowInvalidCertificates: false,
125+
tlsAllowInvalidHostnames: false,
126+
tlsCAFile: __dirname + '/support/cert/cert.pem',
127+
tlsCertificateKeyFile: __dirname + '/support/cert/cert.pem',
128+
tlsCertificateKeyFilePassword: 'password',
129+
waitQueueTimeoutMS: 5000,
130+
zlibCompressionLevel: 6,
131+
},
132+
})).toBeResolved();
133+
await expectAsync(reconfigureServer({
134+
databaseURI: 'mongodb://localhost:27017/parse',
135+
filesAdapter: null,
136+
databaseAdapter: null,
137+
databaseOptions: {
138+
// The following option needs to be tested separately due to driver config rules
139+
tlsInsecure: false,
86140
},
87141
})).toBeResolved();
88142
expect(loggerErrorSpy.calls.all().reduce((s, call) => s += call.args[0], '')).not.toMatch(invalidKeyErrorMessage);

spec/index.spec.js

Lines changed: 168 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -363,7 +363,7 @@ describe('server', () => {
363363

364364
it('should throw when getting invalid mount', done => {
365365
reconfigureServer({ publicServerURL: 'blabla:/some' }).catch(error => {
366-
expect(error).toEqual('publicServerURL should be a valid HTTPS URL starting with https://');
366+
expect(error).toEqual('The option publicServerURL must be a valid URL starting with http:// or https://.');
367367
done();
368368
});
369369
});
@@ -685,4 +685,171 @@ describe('server', () => {
685685
})
686686
.catch(done.fail);
687687
});
688+
689+
describe('publicServerURL', () => {
690+
it('should load publicServerURL', async () => {
691+
await reconfigureServer({
692+
publicServerURL: () => 'https://example.com/1',
693+
});
694+
695+
await new Parse.Object('TestObject').save();
696+
697+
const config = Config.get(Parse.applicationId);
698+
expect(config.publicServerURL).toEqual('https://example.com/1');
699+
});
700+
701+
it('should load publicServerURL from Promise', async () => {
702+
await reconfigureServer({
703+
publicServerURL: () => Promise.resolve('https://example.com/1'),
704+
});
705+
706+
await new Parse.Object('TestObject').save();
707+
708+
const config = Config.get(Parse.applicationId);
709+
expect(config.publicServerURL).toEqual('https://example.com/1');
710+
});
711+
712+
it('should handle publicServerURL function throwing error', async () => {
713+
const errorMessage = 'Failed to get public server URL';
714+
await reconfigureServer({
715+
publicServerURL: () => {
716+
throw new Error(errorMessage);
717+
},
718+
});
719+
720+
// The error should occur when trying to save an object (which triggers loadKeys in middleware)
721+
await expectAsync(
722+
new Parse.Object('TestObject').save()
723+
).toBeRejected();
724+
});
725+
726+
it('should handle publicServerURL Promise rejection', async () => {
727+
const errorMessage = 'Async fetch of public server URL failed';
728+
await reconfigureServer({
729+
publicServerURL: () => Promise.reject(new Error(errorMessage)),
730+
});
731+
732+
// The error should occur when trying to save an object (which triggers loadKeys in middleware)
733+
await expectAsync(
734+
new Parse.Object('TestObject').save()
735+
).toBeRejected();
736+
});
737+
738+
it('executes publicServerURL function on every config access', async () => {
739+
let counter = 0;
740+
await reconfigureServer({
741+
publicServerURL: () => {
742+
counter++;
743+
return `https://example.com/${counter}`;
744+
},
745+
});
746+
747+
// First request - should call the function
748+
await new Parse.Object('TestObject').save();
749+
const config1 = Config.get(Parse.applicationId);
750+
expect(config1.publicServerURL).toEqual('https://example.com/1');
751+
expect(counter).toEqual(1);
752+
753+
// Second request - should call the function again
754+
await new Parse.Object('TestObject').save();
755+
const config2 = Config.get(Parse.applicationId);
756+
expect(config2.publicServerURL).toEqual('https://example.com/2');
757+
expect(counter).toEqual(2);
758+
759+
// Third request - should call the function again
760+
await new Parse.Object('TestObject').save();
761+
const config3 = Config.get(Parse.applicationId);
762+
expect(config3.publicServerURL).toEqual('https://example.com/3');
763+
expect(counter).toEqual(3);
764+
});
765+
766+
it('executes publicServerURL function on every password reset email', async () => {
767+
let counter = 0;
768+
const emailCalls = [];
769+
770+
const emailAdapter = MockEmailAdapterWithOptions({
771+
sendPasswordResetEmail: ({ link }) => {
772+
emailCalls.push(link);
773+
return Promise.resolve();
774+
},
775+
});
776+
777+
await reconfigureServer({
778+
appName: 'test-app',
779+
publicServerURL: () => {
780+
counter++;
781+
return `https://example.com/${counter}`;
782+
},
783+
emailAdapter,
784+
});
785+
786+
// Create a user
787+
const user = new Parse.User();
788+
user.setUsername('user');
789+
user.setPassword('pass');
790+
user.setEmail('user@example.com');
791+
await user.signUp();
792+
793+
// Should use first publicServerURL
794+
const counterBefore1 = counter;
795+
await Parse.User.requestPasswordReset('user@example.com');
796+
await jasmine.timeout();
797+
expect(emailCalls.length).toEqual(1);
798+
expect(emailCalls[0]).toContain(`https://example.com/${counterBefore1 + 1}`);
799+
expect(counter).toBeGreaterThanOrEqual(2);
800+
801+
// Should use updated publicServerURL
802+
const counterBefore2 = counter;
803+
await Parse.User.requestPasswordReset('user@example.com');
804+
await jasmine.timeout();
805+
expect(emailCalls.length).toEqual(2);
806+
expect(emailCalls[1]).toContain(`https://example.com/${counterBefore2 + 1}`);
807+
expect(counterBefore2).toBeGreaterThan(counterBefore1);
808+
});
809+
810+
it('executes publicServerURL function on every verification email', async () => {
811+
let counter = 0;
812+
const emailCalls = [];
813+
814+
const emailAdapter = MockEmailAdapterWithOptions({
815+
sendVerificationEmail: ({ link }) => {
816+
emailCalls.push(link);
817+
return Promise.resolve();
818+
},
819+
});
820+
821+
await reconfigureServer({
822+
appName: 'test-app',
823+
verifyUserEmails: true,
824+
publicServerURL: () => {
825+
counter++;
826+
return `https://example.com/${counter}`;
827+
},
828+
emailAdapter,
829+
});
830+
831+
// Should trigger verification email with first publicServerURL
832+
const counterBefore1 = counter;
833+
const user1 = new Parse.User();
834+
user1.setUsername('user1');
835+
user1.setPassword('pass1');
836+
user1.setEmail('user1@example.com');
837+
await user1.signUp();
838+
await jasmine.timeout();
839+
expect(emailCalls.length).toEqual(1);
840+
expect(emailCalls[0]).toContain(`https://example.com/${counterBefore1 + 1}`);
841+
842+
// Should trigger verification email with updated publicServerURL
843+
const counterBefore2 = counter;
844+
const user2 = new Parse.User();
845+
user2.setUsername('user2');
846+
user2.setPassword('pass2');
847+
user2.setEmail('user2@example.com');
848+
await user2.signUp();
849+
await jasmine.timeout();
850+
expect(emailCalls.length).toEqual(2);
851+
expect(emailCalls[1]).toContain(`https://example.com/${counterBefore2 + 1}`);
852+
expect(counterBefore2).toBeGreaterThan(counterBefore1);
853+
});
854+
});
688855
});

0 commit comments

Comments
 (0)