Skip to content

Commit 5518b92

Browse files
author
percypyan
committed
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.
1 parent 45d00ce commit 5518b92

File tree

1 file changed

+89
-11
lines changed

1 file changed

+89
-11
lines changed

spec/ParseLiveQueryServer.spec.js

+89-11
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,16 @@ const queryHashValue = 'hash';
99
const testUserId = 'userId';
1010
const testClassName = 'TestObject';
1111

12+
const ASYNC_TEST_WAIT_TIME = 100;
13+
14+
function resolveAfter(result, msTimeout) {
15+
return new Promise(res => {
16+
setTimeout(() => {
17+
res(result);
18+
}, msTimeout);
19+
});
20+
}
21+
1222
describe('ParseLiveQueryServer', function () {
1323
beforeEach(function (done) {
1424
// Mock ParseWebSocketServer
@@ -753,7 +763,7 @@ describe('ParseLiveQueryServer', function () {
753763
setTimeout(function () {
754764
expect(client.pushDelete).toHaveBeenCalled();
755765
done();
756-
}, jasmine.ASYNC_TEST_WAIT_TIME);
766+
}, ASYNC_TEST_WAIT_TIME);
757767
});
758768

759769
it('has no subscription and can handle object save command', async () => {
@@ -792,7 +802,7 @@ describe('ParseLiveQueryServer', function () {
792802
expect(client.pushDelete).not.toHaveBeenCalled();
793803
expect(client.pushLeave).not.toHaveBeenCalled();
794804
done();
795-
}, jasmine.ASYNC_TEST_WAIT_TIME);
805+
}, ASYNC_TEST_WAIT_TIME);
796806
});
797807

798808
it('can handle object enter command which matches some subscriptions', async done => {
@@ -829,7 +839,7 @@ describe('ParseLiveQueryServer', function () {
829839
expect(client.pushDelete).not.toHaveBeenCalled();
830840
expect(client.pushLeave).not.toHaveBeenCalled();
831841
done();
832-
}, jasmine.ASYNC_TEST_WAIT_TIME);
842+
}, ASYNC_TEST_WAIT_TIME);
833843
});
834844

835845
it('can handle object update command which matches some subscriptions', async done => {
@@ -862,7 +872,7 @@ describe('ParseLiveQueryServer', function () {
862872
expect(client.pushDelete).not.toHaveBeenCalled();
863873
expect(client.pushLeave).not.toHaveBeenCalled();
864874
done();
865-
}, jasmine.ASYNC_TEST_WAIT_TIME);
875+
}, ASYNC_TEST_WAIT_TIME);
866876
});
867877

868878
it('can handle object leave command which matches some subscriptions', async done => {
@@ -899,7 +909,74 @@ describe('ParseLiveQueryServer', function () {
899909
expect(client.pushDelete).not.toHaveBeenCalled();
900910
expect(client.pushLeave).toHaveBeenCalled();
901911
done();
902-
}, jasmine.ASYNC_TEST_WAIT_TIME);
912+
}, ASYNC_TEST_WAIT_TIME);
913+
});
914+
915+
it('can handle object multiple commands which matches some subscriptions', async done => {
916+
const parseLiveQueryServer = new ParseLiveQueryServer({});
917+
918+
Parse.Cloud.afterLiveQueryEvent('TestObject', () => {
919+
// Simulate delay due to trigger, auth, etc.
920+
return resolveAfter(null, 10);
921+
});
922+
923+
// Make mock request message
924+
const message = generateMockMessage(true);
925+
// Add mock client
926+
const clientId = 1;
927+
const client = addMockClient(parseLiveQueryServer, clientId);
928+
client.sessionToken = 'sessionToken';
929+
930+
// Mock queryHash for this special test
931+
const mockQueryHash = jasmine.createSpy('matchesQuery').and.returnValue('hash1');
932+
jasmine.mockLibrary('../lib/LiveQuery/QueryTools', 'queryHash', mockQueryHash);
933+
// Add mock subscription 1
934+
const requestId2 = 2;
935+
await addMockSubscription(parseLiveQueryServer, clientId, requestId2, null, null, 'hash1');
936+
937+
// Mock queryHash for this special test
938+
const mockQueryHash2 = jasmine.createSpy('matchesQuery').and.returnValue('hash2');
939+
jasmine.mockLibrary('../lib/LiveQuery/QueryTools', 'queryHash', mockQueryHash2);
940+
// Add mock subscription 2
941+
const requestId3 = 3;
942+
await addMockSubscription(parseLiveQueryServer, clientId, requestId3, null, null, 'hash2');
943+
// Mock _matchesSubscription to return matching
944+
// In order to mimic a leave, then enter, we need original match return true
945+
// and the current match return false, then the other way around
946+
let counter = 0;
947+
parseLiveQueryServer._matchesSubscription = function (parseObject) {
948+
if (!parseObject) {
949+
return false;
950+
}
951+
counter += 1;
952+
// true, false, false, true
953+
return counter < 2 || counter > 3;
954+
};
955+
parseLiveQueryServer._matchesACL = function () {
956+
// Simulate call
957+
return resolveAfter(true, 10);
958+
};
959+
parseLiveQueryServer._onAfterSave(message);
960+
961+
// Make sure we send leave and enter command to client
962+
setTimeout(function () {
963+
expect(client.pushCreate).not.toHaveBeenCalled();
964+
expect(client.pushEnter).toHaveBeenCalledTimes(1);
965+
expect(client.pushEnter).toHaveBeenCalledWith(
966+
requestId3,
967+
{ key: 'value', className: 'TestObject' },
968+
{ key: 'originalValue', className: 'TestObject' }
969+
);
970+
expect(client.pushUpdate).not.toHaveBeenCalled();
971+
expect(client.pushDelete).not.toHaveBeenCalled();
972+
expect(client.pushLeave).toHaveBeenCalledTimes(1);
973+
expect(client.pushLeave).toHaveBeenCalledWith(
974+
requestId2,
975+
{ key: 'value', className: 'TestObject' },
976+
{ key: 'originalValue', className: 'TestObject' }
977+
);
978+
done();
979+
}, ASYNC_TEST_WAIT_TIME);
903980
});
904981

905982
it('can handle update command with original object', async done => {
@@ -944,7 +1021,7 @@ describe('ParseLiveQueryServer', function () {
9441021
expect(toSend.object).toBeDefined();
9451022
expect(toSend.original).toBeDefined();
9461023
done();
947-
}, jasmine.ASYNC_TEST_WAIT_TIME);
1024+
}, ASYNC_TEST_WAIT_TIME);
9481025
});
9491026

9501027
it('can handle object create command which matches some subscriptions', async done => {
@@ -977,7 +1054,7 @@ describe('ParseLiveQueryServer', function () {
9771054
expect(client.pushDelete).not.toHaveBeenCalled();
9781055
expect(client.pushLeave).not.toHaveBeenCalled();
9791056
done();
980-
}, jasmine.ASYNC_TEST_WAIT_TIME);
1057+
}, ASYNC_TEST_WAIT_TIME);
9811058
});
9821059

9831060
it('can handle create command with fields', async done => {
@@ -1027,7 +1104,7 @@ describe('ParseLiveQueryServer', function () {
10271104
expect(toSend.object).toBeDefined();
10281105
expect(toSend.original).toBeUndefined();
10291106
done();
1030-
}, jasmine.ASYNC_TEST_WAIT_TIME);
1107+
}, ASYNC_TEST_WAIT_TIME);
10311108
});
10321109

10331110
it('can match subscription for null or undefined parse object', function () {
@@ -1737,7 +1814,8 @@ describe('ParseLiveQueryServer', function () {
17371814
clientId,
17381815
requestId,
17391816
parseWebSocket,
1740-
query
1817+
query,
1818+
customQueryHashValue
17411819
) {
17421820
// If parseWebSocket is null, we use the default one
17431821
if (!parseWebSocket) {
@@ -1765,12 +1843,12 @@ describe('ParseLiveQueryServer', function () {
17651843
// Make mock subscription
17661844
const subscription = parseLiveQueryServer.subscriptions
17671845
.get(query.className)
1768-
.get(queryHashValue);
1846+
.get(customQueryHashValue || queryHashValue);
17691847
subscription.hasSubscribingClient = function () {
17701848
return false;
17711849
};
17721850
subscription.className = query.className;
1773-
subscription.hash = queryHashValue;
1851+
subscription.hash = customQueryHashValue || queryHashValue;
17741852
if (subscription.clientRequestIds && subscription.clientRequestIds.has(clientId)) {
17751853
subscription.clientRequestIds.get(clientId).push(requestId);
17761854
} else {

0 commit comments

Comments
 (0)