* Add a failing test for issue #7340 If any delay occurs after "message.event" assignation in LiveQueryServer._onAfterSave, the next subscription or request with a different event might overwrite it, and by that using the wrong "push" function name. * Remove updade of message and use res.event instead This prevent computing function name from a incorrect event if multiple subscriptions override one by one the message.event. * Update CHANGELOG.md * Replace setTimeout by async/await expressions
This commit is contained in:
committed by
GitHub
parent
45d00cee60
commit
87dcd23b6a
@@ -130,6 +130,7 @@ ___
|
||||
- Fix file upload issue for S3 compatible storage (Linode, DigitalOcean) by avoiding empty tags property when creating a file (Ali Oguzhan Yildiz) [#7300](https://github.com/parse-community/parse-server/pull/7300)
|
||||
- Add building Docker image as CI check (Manuel Trezza) [#7332](https://github.com/parse-community/parse-server/pull/7332)
|
||||
- Add NPM package-lock version check to CI (Manuel Trezza) [#7333](https://github.com/parse-community/parse-server/pull/7333)
|
||||
- Fix incorrect LiveQuery events triggered for multiple subscriptions on the same class with different events [#7341](https://github.com/parse-community/parse-server/pull/7341)
|
||||
___
|
||||
## 4.5.0
|
||||
[Full Changelog](https://github.com/parse-community/parse-server/compare/4.4.0...4.5.0)
|
||||
|
||||
@@ -9,6 +9,8 @@ const queryHashValue = 'hash';
|
||||
const testUserId = 'userId';
|
||||
const testClassName = 'TestObject';
|
||||
|
||||
const timeout = () => jasmine.timeout(100);
|
||||
|
||||
describe('ParseLiveQueryServer', function () {
|
||||
beforeEach(function (done) {
|
||||
// Mock ParseWebSocketServer
|
||||
@@ -750,10 +752,10 @@ describe('ParseLiveQueryServer', function () {
|
||||
|
||||
// Make sure we send command to client, since _matchesACL is async, we have to
|
||||
// wait and check
|
||||
setTimeout(function () {
|
||||
expect(client.pushDelete).toHaveBeenCalled();
|
||||
done();
|
||||
}, jasmine.ASYNC_TEST_WAIT_TIME);
|
||||
await timeout();
|
||||
|
||||
expect(client.pushDelete).toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
|
||||
it('has no subscription and can handle object save command', async () => {
|
||||
@@ -785,14 +787,14 @@ describe('ParseLiveQueryServer', function () {
|
||||
parseLiveQueryServer._onAfterSave(message);
|
||||
|
||||
// Make sure we do not send command to client
|
||||
setTimeout(function () {
|
||||
expect(client.pushCreate).not.toHaveBeenCalled();
|
||||
expect(client.pushEnter).not.toHaveBeenCalled();
|
||||
expect(client.pushUpdate).not.toHaveBeenCalled();
|
||||
expect(client.pushDelete).not.toHaveBeenCalled();
|
||||
expect(client.pushLeave).not.toHaveBeenCalled();
|
||||
done();
|
||||
}, jasmine.ASYNC_TEST_WAIT_TIME);
|
||||
await timeout();
|
||||
|
||||
expect(client.pushCreate).not.toHaveBeenCalled();
|
||||
expect(client.pushEnter).not.toHaveBeenCalled();
|
||||
expect(client.pushUpdate).not.toHaveBeenCalled();
|
||||
expect(client.pushDelete).not.toHaveBeenCalled();
|
||||
expect(client.pushLeave).not.toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
|
||||
it('can handle object enter command which matches some subscriptions', async done => {
|
||||
@@ -822,14 +824,14 @@ describe('ParseLiveQueryServer', function () {
|
||||
parseLiveQueryServer._onAfterSave(message);
|
||||
|
||||
// Make sure we send enter command to client
|
||||
setTimeout(function () {
|
||||
expect(client.pushCreate).not.toHaveBeenCalled();
|
||||
expect(client.pushEnter).toHaveBeenCalled();
|
||||
expect(client.pushUpdate).not.toHaveBeenCalled();
|
||||
expect(client.pushDelete).not.toHaveBeenCalled();
|
||||
expect(client.pushLeave).not.toHaveBeenCalled();
|
||||
done();
|
||||
}, jasmine.ASYNC_TEST_WAIT_TIME);
|
||||
await timeout();
|
||||
|
||||
expect(client.pushCreate).not.toHaveBeenCalled();
|
||||
expect(client.pushEnter).toHaveBeenCalled();
|
||||
expect(client.pushUpdate).not.toHaveBeenCalled();
|
||||
expect(client.pushDelete).not.toHaveBeenCalled();
|
||||
expect(client.pushLeave).not.toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
|
||||
it('can handle object update command which matches some subscriptions', async done => {
|
||||
@@ -855,14 +857,14 @@ describe('ParseLiveQueryServer', function () {
|
||||
parseLiveQueryServer._onAfterSave(message);
|
||||
|
||||
// Make sure we send update command to client
|
||||
setTimeout(function () {
|
||||
expect(client.pushCreate).not.toHaveBeenCalled();
|
||||
expect(client.pushEnter).not.toHaveBeenCalled();
|
||||
expect(client.pushUpdate).toHaveBeenCalled();
|
||||
expect(client.pushDelete).not.toHaveBeenCalled();
|
||||
expect(client.pushLeave).not.toHaveBeenCalled();
|
||||
done();
|
||||
}, jasmine.ASYNC_TEST_WAIT_TIME);
|
||||
await timeout();
|
||||
|
||||
expect(client.pushCreate).not.toHaveBeenCalled();
|
||||
expect(client.pushEnter).not.toHaveBeenCalled();
|
||||
expect(client.pushUpdate).toHaveBeenCalled();
|
||||
expect(client.pushDelete).not.toHaveBeenCalled();
|
||||
expect(client.pushLeave).not.toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
|
||||
it('can handle object leave command which matches some subscriptions', async done => {
|
||||
@@ -892,14 +894,81 @@ describe('ParseLiveQueryServer', function () {
|
||||
parseLiveQueryServer._onAfterSave(message);
|
||||
|
||||
// Make sure we send leave command to client
|
||||
setTimeout(function () {
|
||||
expect(client.pushCreate).not.toHaveBeenCalled();
|
||||
expect(client.pushEnter).not.toHaveBeenCalled();
|
||||
expect(client.pushUpdate).not.toHaveBeenCalled();
|
||||
expect(client.pushDelete).not.toHaveBeenCalled();
|
||||
expect(client.pushLeave).toHaveBeenCalled();
|
||||
done();
|
||||
}, jasmine.ASYNC_TEST_WAIT_TIME);
|
||||
await timeout();
|
||||
|
||||
expect(client.pushCreate).not.toHaveBeenCalled();
|
||||
expect(client.pushEnter).not.toHaveBeenCalled();
|
||||
expect(client.pushUpdate).not.toHaveBeenCalled();
|
||||
expect(client.pushDelete).not.toHaveBeenCalled();
|
||||
expect(client.pushLeave).toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
|
||||
it('sends correct events for object with multiple subscriptions', async done => {
|
||||
const parseLiveQueryServer = new ParseLiveQueryServer({});
|
||||
|
||||
Parse.Cloud.afterLiveQueryEvent('TestObject', () => {
|
||||
// Simulate delay due to trigger, auth, etc.
|
||||
return jasmine.timeout(10);
|
||||
});
|
||||
|
||||
// Make mock request message
|
||||
const message = generateMockMessage(true);
|
||||
// Add mock client
|
||||
const clientId = 1;
|
||||
const client = addMockClient(parseLiveQueryServer, clientId);
|
||||
client.sessionToken = 'sessionToken';
|
||||
|
||||
// Mock queryHash for this special test
|
||||
const mockQueryHash = jasmine.createSpy('matchesQuery').and.returnValue('hash1');
|
||||
jasmine.mockLibrary('../lib/LiveQuery/QueryTools', 'queryHash', mockQueryHash);
|
||||
// Add mock subscription 1
|
||||
const requestId2 = 2;
|
||||
await addMockSubscription(parseLiveQueryServer, clientId, requestId2, null, null, 'hash1');
|
||||
|
||||
// Mock queryHash for this special test
|
||||
const mockQueryHash2 = jasmine.createSpy('matchesQuery').and.returnValue('hash2');
|
||||
jasmine.mockLibrary('../lib/LiveQuery/QueryTools', 'queryHash', mockQueryHash2);
|
||||
// Add mock subscription 2
|
||||
const requestId3 = 3;
|
||||
await addMockSubscription(parseLiveQueryServer, clientId, requestId3, null, null, 'hash2');
|
||||
// Mock _matchesSubscription to return matching
|
||||
// In order to mimic a leave, then enter, we need original match return true
|
||||
// and the current match return false, then the other way around
|
||||
let counter = 0;
|
||||
parseLiveQueryServer._matchesSubscription = function (parseObject) {
|
||||
if (!parseObject) {
|
||||
return false;
|
||||
}
|
||||
counter += 1;
|
||||
// true, false, false, true
|
||||
return counter < 2 || counter > 3;
|
||||
};
|
||||
parseLiveQueryServer._matchesACL = function () {
|
||||
// Simulate call
|
||||
return jasmine.timeout(10).then(() => true);
|
||||
};
|
||||
parseLiveQueryServer._onAfterSave(message);
|
||||
|
||||
// Make sure we send leave and enter command to client
|
||||
await timeout();
|
||||
|
||||
expect(client.pushCreate).not.toHaveBeenCalled();
|
||||
expect(client.pushEnter).toHaveBeenCalledTimes(1);
|
||||
expect(client.pushEnter).toHaveBeenCalledWith(
|
||||
requestId3,
|
||||
{ key: 'value', className: 'TestObject' },
|
||||
{ key: 'originalValue', className: 'TestObject' }
|
||||
);
|
||||
expect(client.pushUpdate).not.toHaveBeenCalled();
|
||||
expect(client.pushDelete).not.toHaveBeenCalled();
|
||||
expect(client.pushLeave).toHaveBeenCalledTimes(1);
|
||||
expect(client.pushLeave).toHaveBeenCalledWith(
|
||||
requestId2,
|
||||
{ key: 'value', className: 'TestObject' },
|
||||
{ key: 'originalValue', className: 'TestObject' }
|
||||
);
|
||||
done();
|
||||
});
|
||||
|
||||
it('can handle update command with original object', async done => {
|
||||
@@ -936,15 +1005,15 @@ describe('ParseLiveQueryServer', function () {
|
||||
parseLiveQueryServer._onAfterSave(message);
|
||||
|
||||
// Make sure we send update command to client
|
||||
setTimeout(function () {
|
||||
expect(client.pushUpdate).toHaveBeenCalled();
|
||||
const args = parseWebSocket.send.calls.mostRecent().args;
|
||||
const toSend = JSON.parse(args[0]);
|
||||
await timeout();
|
||||
|
||||
expect(toSend.object).toBeDefined();
|
||||
expect(toSend.original).toBeDefined();
|
||||
done();
|
||||
}, jasmine.ASYNC_TEST_WAIT_TIME);
|
||||
expect(client.pushUpdate).toHaveBeenCalled();
|
||||
const args = parseWebSocket.send.calls.mostRecent().args;
|
||||
const toSend = JSON.parse(args[0]);
|
||||
|
||||
expect(toSend.object).toBeDefined();
|
||||
expect(toSend.original).toBeDefined();
|
||||
done();
|
||||
});
|
||||
|
||||
it('can handle object create command which matches some subscriptions', async done => {
|
||||
@@ -970,14 +1039,14 @@ describe('ParseLiveQueryServer', function () {
|
||||
parseLiveQueryServer._onAfterSave(message);
|
||||
|
||||
// Make sure we send create command to client
|
||||
setTimeout(function () {
|
||||
expect(client.pushCreate).toHaveBeenCalled();
|
||||
expect(client.pushEnter).not.toHaveBeenCalled();
|
||||
expect(client.pushUpdate).not.toHaveBeenCalled();
|
||||
expect(client.pushDelete).not.toHaveBeenCalled();
|
||||
expect(client.pushLeave).not.toHaveBeenCalled();
|
||||
done();
|
||||
}, jasmine.ASYNC_TEST_WAIT_TIME);
|
||||
await timeout();
|
||||
|
||||
expect(client.pushCreate).toHaveBeenCalled();
|
||||
expect(client.pushEnter).not.toHaveBeenCalled();
|
||||
expect(client.pushUpdate).not.toHaveBeenCalled();
|
||||
expect(client.pushDelete).not.toHaveBeenCalled();
|
||||
expect(client.pushLeave).not.toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
|
||||
it('can handle create command with fields', async done => {
|
||||
@@ -1020,14 +1089,14 @@ describe('ParseLiveQueryServer', function () {
|
||||
parseLiveQueryServer._onAfterSave(message);
|
||||
|
||||
// Make sure we send create command to client
|
||||
setTimeout(function () {
|
||||
expect(client.pushCreate).toHaveBeenCalled();
|
||||
const args = parseWebSocket.send.calls.mostRecent().args;
|
||||
const toSend = JSON.parse(args[0]);
|
||||
expect(toSend.object).toBeDefined();
|
||||
expect(toSend.original).toBeUndefined();
|
||||
done();
|
||||
}, jasmine.ASYNC_TEST_WAIT_TIME);
|
||||
await timeout();
|
||||
|
||||
expect(client.pushCreate).toHaveBeenCalled();
|
||||
const args = parseWebSocket.send.calls.mostRecent().args;
|
||||
const toSend = JSON.parse(args[0]);
|
||||
expect(toSend.object).toBeDefined();
|
||||
expect(toSend.original).toBeUndefined();
|
||||
done();
|
||||
});
|
||||
|
||||
it('can match subscription for null or undefined parse object', function () {
|
||||
@@ -1737,7 +1806,8 @@ describe('ParseLiveQueryServer', function () {
|
||||
clientId,
|
||||
requestId,
|
||||
parseWebSocket,
|
||||
query
|
||||
query,
|
||||
customQueryHashValue
|
||||
) {
|
||||
// If parseWebSocket is null, we use the default one
|
||||
if (!parseWebSocket) {
|
||||
@@ -1765,12 +1835,12 @@ describe('ParseLiveQueryServer', function () {
|
||||
// Make mock subscription
|
||||
const subscription = parseLiveQueryServer.subscriptions
|
||||
.get(query.className)
|
||||
.get(queryHashValue);
|
||||
.get(customQueryHashValue || queryHashValue);
|
||||
subscription.hasSubscribingClient = function () {
|
||||
return false;
|
||||
};
|
||||
subscription.className = query.className;
|
||||
subscription.hash = queryHashValue;
|
||||
subscription.hash = customQueryHashValue || queryHashValue;
|
||||
if (subscription.clientRequestIds && subscription.clientRequestIds.has(clientId)) {
|
||||
subscription.clientRequestIds.get(clientId).push(requestId);
|
||||
} else {
|
||||
|
||||
@@ -515,3 +515,5 @@ jasmine.restoreLibrary = function (library, name) {
|
||||
}
|
||||
require(library)[name] = libraryCache[library][name];
|
||||
};
|
||||
|
||||
jasmine.timeout = t => new Promise(resolve => setTimeout(resolve, t));
|
||||
|
||||
@@ -298,7 +298,6 @@ class ParseLiveQueryServer {
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
message.event = type;
|
||||
res = {
|
||||
event: type,
|
||||
sessionToken: client.sessionToken,
|
||||
@@ -334,8 +333,7 @@ class ParseLiveQueryServer {
|
||||
originalParseObject = res.original.toJSON();
|
||||
originalParseObject.className = res.original.className || className;
|
||||
}
|
||||
const functionName =
|
||||
'push' + message.event.charAt(0).toUpperCase() + message.event.slice(1);
|
||||
const functionName = 'push' + res.event.charAt(0).toUpperCase() + res.event.slice(1);
|
||||
if (client[functionName]) {
|
||||
client[functionName](requestId, currentParseObject, originalParseObject);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user