Skip to content

test: Prevent test timeout error by properly closing server #2490

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

Merged
merged 17 commits into from
Mar 12, 2025
Merged
9 changes: 3 additions & 6 deletions integration/test/ParseEventuallyQueueTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -193,8 +193,7 @@ describe('Parse EventuallyQueue', () => {
it('can saveEventually', async () => {
const parseServer = await reconfigureServer();
const object = new TestObject({ hash: 'saveSecret' });
await parseServer.handleShutdown();
await new Promise(resolve => parseServer.server.close(resolve));
await shutdownServer(parseServer);
await object.saveEventually();

const length = await Parse.EventuallyQueue.length();
Expand Down Expand Up @@ -225,8 +224,7 @@ describe('Parse EventuallyQueue', () => {
const acl = new Parse.ACL(user);
const object = new TestObject({ hash: 'saveSecret' });
object.setACL(acl);
await parseServer.handleShutdown();
await new Promise(resolve => parseServer.server.close(resolve));
await shutdownServer(parseServer);
await object.saveEventually();

const length = await Parse.EventuallyQueue.length();
Expand All @@ -251,8 +249,7 @@ describe('Parse EventuallyQueue', () => {
const parseServer = await reconfigureServer();
const object = new TestObject({ hash: 'deleteSecret' });
await object.save();
await parseServer.handleShutdown();
await new Promise(resolve => parseServer.server.close(resolve));
await shutdownServer(parseServer);
await object.destroyEventually();
const length = await Parse.EventuallyQueue.length();

Expand Down
31 changes: 21 additions & 10 deletions integration/test/ParseServerTest.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,37 @@
'use strict';

const assert = require('assert');

describe('ParseServer', () => {
it('can reconfigure server', async () => {
const parseServer = await reconfigureServer({ serverURL: 'www.google.com' });
assert.strictEqual(parseServer.config.serverURL, 'www.google.com');
await parseServer.handleShutdown();
await new Promise(resolve => parseServer.server.close(resolve));
await reconfigureServer();
let parseServer = await reconfigureServer({ serverURL: 'www.google.com' });
expect(parseServer.config.serverURL).toBe('www.google.com');

await shutdownServer(parseServer);

parseServer = await reconfigureServer();
expect(parseServer.config.serverURL).toBe('http://localhost:1337/parse');
});

it('can shutdown', async () => {
let close = 0;
const parseServer = await reconfigureServer();
parseServer.server.on('close', () => {
close += 1;
});
const object = new TestObject({ foo: 'bar' });
await parseServer.handleShutdown();
await new Promise(resolve => parseServer.server.close(resolve));
// Open a connection to the server
const query = new Parse.Query(TestObject);
await query.subscribe();
expect(openConnections.size > 0).toBeTruthy();

await shutdownServer(parseServer);
expect(close).toBe(1);
expect(openConnections.size).toBe(0);

await expectAsync(object.save()).toBeRejectedWithError(
'XMLHttpRequest failed: "Unable to connect to the Parse API"'
);
await reconfigureServer({});
await object.save();
assert(object.id);
expect(object.id).toBeDefined();
});
});
5 changes: 2 additions & 3 deletions integration/test/ParseUserTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ const provider = {
};
},
};
Parse.User._registerAuthenticationProvider(provider);

const authResponse = {
userID: 'test',
Expand All @@ -44,8 +43,8 @@ global.FB = {
};

describe('Parse User', () => {
afterAll(() => {
Parse.Object.unregisterSubclass('CustomUser');
beforeEach(() => {
Parse.User._registerAuthenticationProvider(provider);
});

it('can sign up users via static method', done => {
Expand Down
12 changes: 0 additions & 12 deletions integration/test/clear.js

This file was deleted.

68 changes: 44 additions & 24 deletions integration/test/helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ const ParseServer = require('parse-server').default;
const CustomAuth = require('./CustomAuth');
const { TestUtils } = require('parse-server');
const Parse = require('../../node');
const fs = require('fs');
const { resolvingPromise } = require('../../lib/node/promiseUtils');
const fs = require('fs').promises;
const path = require('path');
const dns = require('dns');
const MockEmailAdapterWithOptions = require('./support/MockEmailAdapterWithOptions');
Expand All @@ -21,6 +22,7 @@ const port = 1337;
const mountPath = '/parse';
const serverURL = 'http://localhost:1337/parse';
let didChangeConfiguration = false;
const distFiles = {};

/*
To generate the auth data below, the Twitter app "GitHub CI Test App" has
Expand Down Expand Up @@ -91,17 +93,41 @@ const defaultConfiguration = {
}),
};

const openConnections = {};
const openConnections = new Set();
let parseServer;

const destroyConnections = () => {
for (const socket of openConnections.values()) {
socket.destroy();
}
openConnections.clear();
};

const shutdownServer = async _parseServer => {
const closePromise = resolvingPromise();
_parseServer.server.on('close', () => {
closePromise.resolve();
});
await Promise.all([
_parseServer.config.databaseController.adapter.handleShutdown(),
_parseServer.liveQueryServer.shutdown(),
]);
_parseServer.server.close(error => {
if (error) {
console.error('Failed to close Parse Server', error);
}
});
destroyConnections();
await closePromise;
expect(openConnections.size).toBe(0);
parseServer = undefined;
};

const reconfigureServer = async (changedConfiguration = {}) => {
if (parseServer) {
await parseServer.handleShutdown();
await new Promise(resolve => parseServer.server.close(resolve));
parseServer = undefined;
await shutdownServer(parseServer);
return reconfigureServer(changedConfiguration);
}

didChangeConfiguration = Object.keys(changedConfiguration).length !== 0;
const newConfiguration = Object.assign({}, defaultConfiguration, changedConfiguration || {}, {
mountPath,
Expand All @@ -113,8 +139,7 @@ const reconfigureServer = async (changedConfiguration = {}) => {
return reconfigureServer(newConfiguration);
}
const app = parseServer.expressApp;
for (const fileName of ['parse.js', 'parse.min.js']) {
const file = fs.readFileSync(path.resolve(__dirname, `./../../dist/${fileName}`)).toString();
for (const [fileName, file] of Object.entries(distFiles)) {
app.get(`/${fileName}`, (_req, res) => {
res.send(`<html><head>
<meta charset="utf-8">
Expand All @@ -132,17 +157,10 @@ const reconfigureServer = async (changedConfiguration = {}) => {
</body></html>`);
});
}
app.get('/clear/:fast', (req, res) => {
const { fast } = req.params;
TestUtils.destroyAllDataPermanently(fast).then(() => {
res.send('{}');
});
});
parseServer.server.on('connection', connection => {
const key = `${connection.remoteAddress}:${connection.remotePort}`;
openConnections[key] = connection;
openConnections.add(connection);
connection.on('close', () => {
delete openConnections[key];
openConnections.delete(connection);
});
});
return parseServer;
Expand All @@ -155,12 +173,21 @@ global.Container = Parse.Object.extend('Container');
global.TestPoint = Parse.Object.extend('TestPoint');
global.TestObject = Parse.Object.extend('TestObject');
global.reconfigureServer = reconfigureServer;
global.shutdownServer = shutdownServer;
global.openConnections = openConnections;

beforeAll(async () => {
const promise = ['parse.js', 'parse.min.js'].map(fileName => {
return fs.readFile(path.resolve(__dirname, `./../../dist/${fileName}`), 'utf8').then(file => {
distFiles[fileName] = file;
});
});
await Promise.all(promise);
await reconfigureServer();
Parse.initialize('integration');
Parse.CoreManager.set('SERVER_URL', serverURL);
Parse.CoreManager.set('MASTER_KEY', 'notsosecret');
Parse.CoreManager.set('REQUEST_ATTEMPT_LIMIT', 1);
});

afterEach(async () => {
Expand All @@ -172,11 +199,4 @@ afterEach(async () => {
}
});

afterAll(() => {
// Jasmine process counts as one open connection
if (Object.keys(openConnections).length > 1) {
console.warn('There were open connections to the server left after the test finished');
}
});

module.exports = { twitterAuthData };
3 changes: 1 addition & 2 deletions jasmine.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,5 @@
"spec_files": [
"*Test.js"
],
"random": true,
"timeout": 20000
"random": true
}