Fix #7340 by correclty computing function name for push event (#7341)

* 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:
Perceval Archimbaud
2021-04-13 23:47:14 +02:00
committed by GitHub
parent 45d00cee60
commit 87dcd23b6a
4 changed files with 137 additions and 66 deletions

View File

@@ -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 {