Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Parse Server doesn't shutdown gracefully #9634

Merged
merged 11 commits into from
Mar 27, 2025
8 changes: 3 additions & 5 deletions spec/EnableExpressErrorHandler.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,9 @@ const request = require('../lib/request');
describe('Enable express error handler', () => {
it('should call the default handler in case of error, like updating a non existing object', async done => {
spyOn(console, 'error');
const parseServer = await reconfigureServer(
Object.assign({}, defaultConfiguration, {
enableExpressErrorHandler: true,
})
);
const parseServer = await reconfigureServer({
enableExpressErrorHandler: true,
});
parseServer.app.use(function (err, req, res, next) {
expect(err.message).toBe('Object not found.');
next(err);
Expand Down
2 changes: 0 additions & 2 deletions spec/ParseAPI.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,7 @@ describe('miscellaneous', () => {
expect(results.length).toEqual(1);
expect(results[0]['foo']).toEqual('bar');
});
});

describe('miscellaneous', function () {
it('create a GameScore object', function (done) {
const obj = new Parse.Object('GameScore');
obj.set('score', 1337);
Expand Down
6 changes: 1 addition & 5 deletions spec/ParseConfigKey.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ describe('Config Keys', () => {

it('recognizes invalid keys in root', async () => {
await expectAsync(reconfigureServer({
...defaultConfiguration,
invalidKey: 1,
})).toBeResolved();
const error = loggerErrorSpy.calls.all().reduce((s, call) => s += call.args[0], '');
Expand All @@ -21,7 +20,6 @@ describe('Config Keys', () => {

it('recognizes invalid keys in pages.customUrls', async () => {
await expectAsync(reconfigureServer({
...defaultConfiguration,
pages: {
customUrls: {
invalidKey: 1,
Expand All @@ -37,7 +35,6 @@ describe('Config Keys', () => {

it('recognizes invalid keys in liveQueryServerOptions', async () => {
await expectAsync(reconfigureServer({
...defaultConfiguration,
liveQueryServerOptions: {
invalidKey: 1,
MasterKey: 1,
Expand All @@ -50,7 +47,6 @@ describe('Config Keys', () => {

it('recognizes invalid keys in rateLimit', async () => {
await expectAsync(reconfigureServer({
...defaultConfiguration,
rateLimit: [
{ invalidKey: 1 },
{ RequestPath: 1 },
Expand All @@ -64,7 +60,7 @@ describe('Config Keys', () => {
expect(error).toMatch('rateLimit\\[2\\]\\.RequestTimeWindow');
});

it('recognizes valid keys in default configuration', async () => {
it_only_db('mongo')('recognizes valid keys in default configuration', async () => {
await expectAsync(reconfigureServer({
...defaultConfiguration,
})).toBeResolved();
Expand Down
47 changes: 33 additions & 14 deletions spec/ParseGraphQLServer.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -431,17 +431,32 @@ describe('ParseGraphQLServer', () => {
objects.push(object1, object2, object3, object4);
}

beforeEach(async () => {
async function createGQLFromParseServer(_parseServer) {
if (parseLiveQueryServer) {
await parseLiveQueryServer.server.close();
}
if (httpServer) {
await httpServer.close();
}
const expressApp = express();
httpServer = http.createServer(expressApp);
expressApp.use('/parse', parseServer.app);
expressApp.use('/parse', _parseServer.app);
parseLiveQueryServer = await ParseServer.createLiveQueryServer(httpServer, {
port: 1338,
});
parseGraphQLServer = new ParseGraphQLServer(_parseServer, {
graphQLPath: '/graphql',
playgroundPath: '/playground',
subscriptionsPath: '/subscriptions',
});
parseGraphQLServer.applyGraphQL(expressApp);
parseGraphQLServer.applyPlayground(expressApp);
parseGraphQLServer.createSubscriptions(httpServer);
await new Promise(resolve => httpServer.listen({ port: 13377 }, resolve));
}

beforeEach(async () => {
await createGQLFromParseServer(parseServer);

const subscriptionClient = new SubscriptionClient(
'ws://localhost:13377/subscriptions',
Expand Down Expand Up @@ -753,10 +768,6 @@ describe('ParseGraphQLServer', () => {
}
});

afterAll(async () => {
await resetGraphQLCache();
});

it('should have Node interface', async () => {
const schemaTypes = (
await apolloClient.query({
Expand Down Expand Up @@ -2821,7 +2832,8 @@ describe('ParseGraphQLServer', () => {
}
});
it('Id inputs should work either with global id or object id with objectId higher than 19', async () => {
await reconfigureServer({ objectIdSize: 20 });
const parseServer = await reconfigureServer({ objectIdSize: 20 });
await createGQLFromParseServer(parseServer);
const obj = new Parse.Object('SomeClass');
await obj.save({ name: 'aname', type: 'robot' });
const result = await apolloClient.query({
Expand Down Expand Up @@ -5328,7 +5340,7 @@ describe('ParseGraphQLServer', () => {
parseServer = await global.reconfigureServer({
maxLimit: 10,
});

await createGQLFromParseServer(parseServer);
const promises = [];
for (let i = 0; i < 100; i++) {
const obj = new Parse.Object('SomeClass');
Expand Down Expand Up @@ -6841,7 +6853,7 @@ describe('ParseGraphQLServer', () => {
parseServer = await global.reconfigureServer({
publicServerURL: 'http://localhost:13377/parse',
});

await createGQLFromParseServer(parseServer);
const body = new FormData();
body.append(
'operations',
Expand Down Expand Up @@ -7049,6 +7061,7 @@ describe('ParseGraphQLServer', () => {
challengeAdapter,
},
});
await createGQLFromParseServer(parseServer);
const clientMutationId = uuidv4();

const result = await apolloClient.mutate({
Expand Down Expand Up @@ -7095,6 +7108,7 @@ describe('ParseGraphQLServer', () => {
challengeAdapter,
},
});
await createGQLFromParseServer(parseServer);
const clientMutationId = uuidv4();
const userSchema = new Parse.Schema('_User');
userSchema.addString('someField');
Expand Down Expand Up @@ -7169,7 +7183,7 @@ describe('ParseGraphQLServer', () => {
},
},
});

await createGQLFromParseServer(parseServer);
userSchema.addString('someField');
userSchema.addPointer('aPointer', '_User');
await userSchema.update();
Expand Down Expand Up @@ -7239,7 +7253,7 @@ describe('ParseGraphQLServer', () => {
challengeAdapter,
},
});

await createGQLFromParseServer(parseServer);
const user = new Parse.User();
await user.save({ username: 'username', password: 'password' });

Expand Down Expand Up @@ -7310,6 +7324,7 @@ describe('ParseGraphQLServer', () => {
challengeAdapter,
},
});
await createGQLFromParseServer(parseServer);
const clientMutationId = uuidv4();
const user = new Parse.User();
user.setUsername('user1');
Expand Down Expand Up @@ -7441,6 +7456,7 @@ describe('ParseGraphQLServer', () => {
emailAdapter: emailAdapter,
publicServerURL: 'http://test.test',
});
await createGQLFromParseServer(parseServer);
const user = new Parse.User();
user.setUsername('user1');
user.setPassword('user1');
Expand Down Expand Up @@ -7488,6 +7504,7 @@ describe('ParseGraphQLServer', () => {
},
},
});
await createGQLFromParseServer(parseServer);
const user = new Parse.User();
user.setUsername('user1');
user.setPassword('user1');
Expand Down Expand Up @@ -7550,6 +7567,7 @@ describe('ParseGraphQLServer', () => {
emailAdapter: emailAdapter,
publicServerURL: 'http://test.test',
});
await createGQLFromParseServer(parseServer);
const user = new Parse.User();
user.setUsername('user1');
user.setPassword('user1');
Expand Down Expand Up @@ -9306,7 +9324,7 @@ describe('ParseGraphQLServer', () => {
parseServer = await global.reconfigureServer({
publicServerURL: 'http://localhost:13377/parse',
});

await createGQLFromParseServer(parseServer);
const body = new FormData();
body.append(
'operations',
Expand Down Expand Up @@ -9339,7 +9357,6 @@ describe('ParseGraphQLServer', () => {
headers,
body,
});

expect(res.status).toEqual(200);

const result = JSON.parse(await res.text());
Expand Down Expand Up @@ -9553,6 +9570,7 @@ describe('ParseGraphQLServer', () => {
parseServer = await global.reconfigureServer({
publicServerURL: 'http://localhost:13377/parse',
});
await createGQLFromParseServer(parseServer);
const schemaController = await parseServer.config.databaseController.loadSchema();
await schemaController.addClassIfNotExists('SomeClassWithRequiredFile', {
someField: { type: 'File', required: true },
Expand Down Expand Up @@ -9617,6 +9635,7 @@ describe('ParseGraphQLServer', () => {
parseServer = await global.reconfigureServer({
publicServerURL: 'http://localhost:13377/parse',
});
await createGQLFromParseServer(parseServer);
const schema = new Parse.Schema('SomeClass');
schema.addFile('someFileField');
schema.addPointer('somePointerField', 'SomeClass');
Expand Down Expand Up @@ -9725,7 +9744,7 @@ describe('ParseGraphQLServer', () => {
parseServer = await global.reconfigureServer({
publicServerURL: 'http://localhost:13377/parse',
});

await createGQLFromParseServer(parseServer);
const body = new FormData();
body.append(
'operations',
Expand Down
76 changes: 75 additions & 1 deletion spec/ParseLiveQuery.spec.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
'use strict';
const http = require('http');
const Auth = require('../lib/Auth');
const UserController = require('../lib/Controllers/UserController').UserController;
const Config = require('../lib/Config');
const ParseServer = require('../lib/index').ParseServer;
const triggers = require('../lib/triggers');
const { resolvingPromise, sleep } = require('../lib/TestUtils');
const { resolvingPromise, sleep, getConnectionsCount } = require('../lib/TestUtils');
const request = require('../lib/request');
const validatorFail = () => {
throw 'you are not authorized';
};
Expand Down Expand Up @@ -1181,6 +1183,78 @@ describe('ParseLiveQuery', function () {
await new Promise(resolve => server.server.close(resolve));
});

it_id('45655b74-716f-4fa1-a058-67eb21f3c3db')(it)('does shutdown separate liveQuery server', async () => {
await reconfigureServer({ appId: 'test_app_id' });
let close = false;
const config = {
appId: 'hello_test',
masterKey: 'world',
port: 1345,
mountPath: '/1',
serverURL: 'http://localhost:1345/1',
liveQuery: {
classNames: ['Yolo'],
},
startLiveQueryServer: true,
verbose: false,
silent: true,
liveQueryServerOptions: {
port: 1346,
},
serverCloseComplete: () => {
close = true;
},
};
if (process.env.PARSE_SERVER_TEST_DB === 'postgres') {
config.databaseAdapter = new databaseAdapter.constructor({
uri: databaseURI,
collectionPrefix: 'test_',
});
config.filesAdapter = defaultConfiguration.filesAdapter;
}
const parseServer = await ParseServer.startApp(config);
expect(parseServer.liveQueryServer).toBeDefined();
expect(parseServer.liveQueryServer.server).not.toBe(parseServer.server);

// Open a connection to the liveQuery server
const client = await Parse.CoreManager.getLiveQueryController().getDefaultLiveQueryClient();
client.serverURL = 'ws://localhost:1346/1';
const query = await new Parse.Query('Yolo').subscribe();

// Open a connection to the parse server
const health = await request({
method: 'GET',
url: `http://localhost:1345/1/health`,
json: true,
headers: {
'X-Parse-Application-Id': 'hello_test',
'X-Parse-Master-Key': 'world',
'Content-Type': 'application/json',
},
agent: new http.Agent({ keepAlive: true }),
}).then(res => res.data);
expect(health.status).toBe('ok');

let parseConnectionCount = await getConnectionsCount(parseServer.server);
let liveQueryConnectionCount = await getConnectionsCount(parseServer.liveQueryServer.server);

expect(parseConnectionCount > 0).toBe(true);
expect(liveQueryConnectionCount > 0).toBe(true);
await Promise.all([
parseServer.handleShutdown(),
new Promise(resolve => query.on('close', resolve)),
]);
expect(close).toBe(true);
await new Promise(resolve => setTimeout(resolve, 100));
expect(parseServer.liveQueryServer.server.address()).toBeNull();
expect(parseServer.liveQueryServer.subscriber.isOpen).toBeFalse();

parseConnectionCount = await getConnectionsCount(parseServer.server);
liveQueryConnectionCount = await getConnectionsCount(parseServer.liveQueryServer.server);
expect(parseConnectionCount).toBe(0);
expect(liveQueryConnectionCount).toBe(0);
});

it('prevent afterSave trigger if not exists', async () => {
await reconfigureServer({
liveQuery: {
Expand Down
Loading
Loading