* 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)
|
- 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 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)
|
- 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
|
## 4.5.0
|
||||||
[Full Changelog](https://github.com/parse-community/parse-server/compare/4.4.0...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 testUserId = 'userId';
|
||||||
const testClassName = 'TestObject';
|
const testClassName = 'TestObject';
|
||||||
|
|
||||||
|
const timeout = () => jasmine.timeout(100);
|
||||||
|
|
||||||
describe('ParseLiveQueryServer', function () {
|
describe('ParseLiveQueryServer', function () {
|
||||||
beforeEach(function (done) {
|
beforeEach(function (done) {
|
||||||
// Mock ParseWebSocketServer
|
// Mock ParseWebSocketServer
|
||||||
@@ -750,10 +752,10 @@ describe('ParseLiveQueryServer', function () {
|
|||||||
|
|
||||||
// Make sure we send command to client, since _matchesACL is async, we have to
|
// Make sure we send command to client, since _matchesACL is async, we have to
|
||||||
// wait and check
|
// wait and check
|
||||||
setTimeout(function () {
|
await timeout();
|
||||||
expect(client.pushDelete).toHaveBeenCalled();
|
|
||||||
done();
|
expect(client.pushDelete).toHaveBeenCalled();
|
||||||
}, jasmine.ASYNC_TEST_WAIT_TIME);
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('has no subscription and can handle object save command', async () => {
|
it('has no subscription and can handle object save command', async () => {
|
||||||
@@ -785,14 +787,14 @@ describe('ParseLiveQueryServer', function () {
|
|||||||
parseLiveQueryServer._onAfterSave(message);
|
parseLiveQueryServer._onAfterSave(message);
|
||||||
|
|
||||||
// Make sure we do not send command to client
|
// Make sure we do not send command to client
|
||||||
setTimeout(function () {
|
await timeout();
|
||||||
expect(client.pushCreate).not.toHaveBeenCalled();
|
|
||||||
expect(client.pushEnter).not.toHaveBeenCalled();
|
expect(client.pushCreate).not.toHaveBeenCalled();
|
||||||
expect(client.pushUpdate).not.toHaveBeenCalled();
|
expect(client.pushEnter).not.toHaveBeenCalled();
|
||||||
expect(client.pushDelete).not.toHaveBeenCalled();
|
expect(client.pushUpdate).not.toHaveBeenCalled();
|
||||||
expect(client.pushLeave).not.toHaveBeenCalled();
|
expect(client.pushDelete).not.toHaveBeenCalled();
|
||||||
done();
|
expect(client.pushLeave).not.toHaveBeenCalled();
|
||||||
}, jasmine.ASYNC_TEST_WAIT_TIME);
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can handle object enter command which matches some subscriptions', async done => {
|
it('can handle object enter command which matches some subscriptions', async done => {
|
||||||
@@ -822,14 +824,14 @@ describe('ParseLiveQueryServer', function () {
|
|||||||
parseLiveQueryServer._onAfterSave(message);
|
parseLiveQueryServer._onAfterSave(message);
|
||||||
|
|
||||||
// Make sure we send enter command to client
|
// Make sure we send enter command to client
|
||||||
setTimeout(function () {
|
await timeout();
|
||||||
expect(client.pushCreate).not.toHaveBeenCalled();
|
|
||||||
expect(client.pushEnter).toHaveBeenCalled();
|
expect(client.pushCreate).not.toHaveBeenCalled();
|
||||||
expect(client.pushUpdate).not.toHaveBeenCalled();
|
expect(client.pushEnter).toHaveBeenCalled();
|
||||||
expect(client.pushDelete).not.toHaveBeenCalled();
|
expect(client.pushUpdate).not.toHaveBeenCalled();
|
||||||
expect(client.pushLeave).not.toHaveBeenCalled();
|
expect(client.pushDelete).not.toHaveBeenCalled();
|
||||||
done();
|
expect(client.pushLeave).not.toHaveBeenCalled();
|
||||||
}, jasmine.ASYNC_TEST_WAIT_TIME);
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can handle object update command which matches some subscriptions', async done => {
|
it('can handle object update command which matches some subscriptions', async done => {
|
||||||
@@ -855,14 +857,14 @@ describe('ParseLiveQueryServer', function () {
|
|||||||
parseLiveQueryServer._onAfterSave(message);
|
parseLiveQueryServer._onAfterSave(message);
|
||||||
|
|
||||||
// Make sure we send update command to client
|
// Make sure we send update command to client
|
||||||
setTimeout(function () {
|
await timeout();
|
||||||
expect(client.pushCreate).not.toHaveBeenCalled();
|
|
||||||
expect(client.pushEnter).not.toHaveBeenCalled();
|
expect(client.pushCreate).not.toHaveBeenCalled();
|
||||||
expect(client.pushUpdate).toHaveBeenCalled();
|
expect(client.pushEnter).not.toHaveBeenCalled();
|
||||||
expect(client.pushDelete).not.toHaveBeenCalled();
|
expect(client.pushUpdate).toHaveBeenCalled();
|
||||||
expect(client.pushLeave).not.toHaveBeenCalled();
|
expect(client.pushDelete).not.toHaveBeenCalled();
|
||||||
done();
|
expect(client.pushLeave).not.toHaveBeenCalled();
|
||||||
}, jasmine.ASYNC_TEST_WAIT_TIME);
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can handle object leave command which matches some subscriptions', async done => {
|
it('can handle object leave command which matches some subscriptions', async done => {
|
||||||
@@ -892,14 +894,81 @@ describe('ParseLiveQueryServer', function () {
|
|||||||
parseLiveQueryServer._onAfterSave(message);
|
parseLiveQueryServer._onAfterSave(message);
|
||||||
|
|
||||||
// Make sure we send leave command to client
|
// Make sure we send leave command to client
|
||||||
setTimeout(function () {
|
await timeout();
|
||||||
expect(client.pushCreate).not.toHaveBeenCalled();
|
|
||||||
expect(client.pushEnter).not.toHaveBeenCalled();
|
expect(client.pushCreate).not.toHaveBeenCalled();
|
||||||
expect(client.pushUpdate).not.toHaveBeenCalled();
|
expect(client.pushEnter).not.toHaveBeenCalled();
|
||||||
expect(client.pushDelete).not.toHaveBeenCalled();
|
expect(client.pushUpdate).not.toHaveBeenCalled();
|
||||||
expect(client.pushLeave).toHaveBeenCalled();
|
expect(client.pushDelete).not.toHaveBeenCalled();
|
||||||
done();
|
expect(client.pushLeave).toHaveBeenCalled();
|
||||||
}, jasmine.ASYNC_TEST_WAIT_TIME);
|
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 => {
|
it('can handle update command with original object', async done => {
|
||||||
@@ -936,15 +1005,15 @@ describe('ParseLiveQueryServer', function () {
|
|||||||
parseLiveQueryServer._onAfterSave(message);
|
parseLiveQueryServer._onAfterSave(message);
|
||||||
|
|
||||||
// Make sure we send update command to client
|
// Make sure we send update command to client
|
||||||
setTimeout(function () {
|
await timeout();
|
||||||
expect(client.pushUpdate).toHaveBeenCalled();
|
|
||||||
const args = parseWebSocket.send.calls.mostRecent().args;
|
|
||||||
const toSend = JSON.parse(args[0]);
|
|
||||||
|
|
||||||
expect(toSend.object).toBeDefined();
|
expect(client.pushUpdate).toHaveBeenCalled();
|
||||||
expect(toSend.original).toBeDefined();
|
const args = parseWebSocket.send.calls.mostRecent().args;
|
||||||
done();
|
const toSend = JSON.parse(args[0]);
|
||||||
}, jasmine.ASYNC_TEST_WAIT_TIME);
|
|
||||||
|
expect(toSend.object).toBeDefined();
|
||||||
|
expect(toSend.original).toBeDefined();
|
||||||
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can handle object create command which matches some subscriptions', async done => {
|
it('can handle object create command which matches some subscriptions', async done => {
|
||||||
@@ -970,14 +1039,14 @@ describe('ParseLiveQueryServer', function () {
|
|||||||
parseLiveQueryServer._onAfterSave(message);
|
parseLiveQueryServer._onAfterSave(message);
|
||||||
|
|
||||||
// Make sure we send create command to client
|
// Make sure we send create command to client
|
||||||
setTimeout(function () {
|
await timeout();
|
||||||
expect(client.pushCreate).toHaveBeenCalled();
|
|
||||||
expect(client.pushEnter).not.toHaveBeenCalled();
|
expect(client.pushCreate).toHaveBeenCalled();
|
||||||
expect(client.pushUpdate).not.toHaveBeenCalled();
|
expect(client.pushEnter).not.toHaveBeenCalled();
|
||||||
expect(client.pushDelete).not.toHaveBeenCalled();
|
expect(client.pushUpdate).not.toHaveBeenCalled();
|
||||||
expect(client.pushLeave).not.toHaveBeenCalled();
|
expect(client.pushDelete).not.toHaveBeenCalled();
|
||||||
done();
|
expect(client.pushLeave).not.toHaveBeenCalled();
|
||||||
}, jasmine.ASYNC_TEST_WAIT_TIME);
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can handle create command with fields', async done => {
|
it('can handle create command with fields', async done => {
|
||||||
@@ -1020,14 +1089,14 @@ describe('ParseLiveQueryServer', function () {
|
|||||||
parseLiveQueryServer._onAfterSave(message);
|
parseLiveQueryServer._onAfterSave(message);
|
||||||
|
|
||||||
// Make sure we send create command to client
|
// Make sure we send create command to client
|
||||||
setTimeout(function () {
|
await timeout();
|
||||||
expect(client.pushCreate).toHaveBeenCalled();
|
|
||||||
const args = parseWebSocket.send.calls.mostRecent().args;
|
expect(client.pushCreate).toHaveBeenCalled();
|
||||||
const toSend = JSON.parse(args[0]);
|
const args = parseWebSocket.send.calls.mostRecent().args;
|
||||||
expect(toSend.object).toBeDefined();
|
const toSend = JSON.parse(args[0]);
|
||||||
expect(toSend.original).toBeUndefined();
|
expect(toSend.object).toBeDefined();
|
||||||
done();
|
expect(toSend.original).toBeUndefined();
|
||||||
}, jasmine.ASYNC_TEST_WAIT_TIME);
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can match subscription for null or undefined parse object', function () {
|
it('can match subscription for null or undefined parse object', function () {
|
||||||
@@ -1737,7 +1806,8 @@ describe('ParseLiveQueryServer', function () {
|
|||||||
clientId,
|
clientId,
|
||||||
requestId,
|
requestId,
|
||||||
parseWebSocket,
|
parseWebSocket,
|
||||||
query
|
query,
|
||||||
|
customQueryHashValue
|
||||||
) {
|
) {
|
||||||
// If parseWebSocket is null, we use the default one
|
// If parseWebSocket is null, we use the default one
|
||||||
if (!parseWebSocket) {
|
if (!parseWebSocket) {
|
||||||
@@ -1765,12 +1835,12 @@ describe('ParseLiveQueryServer', function () {
|
|||||||
// Make mock subscription
|
// Make mock subscription
|
||||||
const subscription = parseLiveQueryServer.subscriptions
|
const subscription = parseLiveQueryServer.subscriptions
|
||||||
.get(query.className)
|
.get(query.className)
|
||||||
.get(queryHashValue);
|
.get(customQueryHashValue || queryHashValue);
|
||||||
subscription.hasSubscribingClient = function () {
|
subscription.hasSubscribingClient = function () {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
subscription.className = query.className;
|
subscription.className = query.className;
|
||||||
subscription.hash = queryHashValue;
|
subscription.hash = customQueryHashValue || queryHashValue;
|
||||||
if (subscription.clientRequestIds && subscription.clientRequestIds.has(clientId)) {
|
if (subscription.clientRequestIds && subscription.clientRequestIds.has(clientId)) {
|
||||||
subscription.clientRequestIds.get(clientId).push(requestId);
|
subscription.clientRequestIds.get(clientId).push(requestId);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -515,3 +515,5 @@ jasmine.restoreLibrary = function (library, name) {
|
|||||||
}
|
}
|
||||||
require(library)[name] = libraryCache[library][name];
|
require(library)[name] = libraryCache[library][name];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
jasmine.timeout = t => new Promise(resolve => setTimeout(resolve, t));
|
||||||
|
|||||||
@@ -298,7 +298,6 @@ class ParseLiveQueryServer {
|
|||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
message.event = type;
|
|
||||||
res = {
|
res = {
|
||||||
event: type,
|
event: type,
|
||||||
sessionToken: client.sessionToken,
|
sessionToken: client.sessionToken,
|
||||||
@@ -334,8 +333,7 @@ class ParseLiveQueryServer {
|
|||||||
originalParseObject = res.original.toJSON();
|
originalParseObject = res.original.toJSON();
|
||||||
originalParseObject.className = res.original.className || className;
|
originalParseObject.className = res.original.className || className;
|
||||||
}
|
}
|
||||||
const functionName =
|
const functionName = 'push' + res.event.charAt(0).toUpperCase() + res.event.slice(1);
|
||||||
'push' + message.event.charAt(0).toUpperCase() + message.event.slice(1);
|
|
||||||
if (client[functionName]) {
|
if (client[functionName]) {
|
||||||
client[functionName](requestId, currentParseObject, originalParseObject);
|
client[functionName](requestId, currentParseObject, originalParseObject);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user