Push scalability (#3080)
* Update status through increment * adds support for incrementing nested keys * fix issue when having spaces in keys for ordering * Refactors PushController to use worker * Adds tests for custom push queue config * Makes PushController adapter independant * Better logging of _PushStatus in VERBOSE
This commit is contained in:
@@ -2,6 +2,12 @@
|
||||
|
||||
const request = require('request');
|
||||
|
||||
const delayPromise = (delay) => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, delay);
|
||||
});
|
||||
}
|
||||
|
||||
describe('Parse.Push', () => {
|
||||
var setup = function() {
|
||||
var pushAdapter = {
|
||||
@@ -16,8 +22,8 @@ describe('Parse.Push', () => {
|
||||
}
|
||||
return Promise.resolve({
|
||||
err: null,
|
||||
deviceType: installation.deviceType,
|
||||
result: true
|
||||
device: installation,
|
||||
transmitted: true
|
||||
})
|
||||
});
|
||||
return Promise.all(promises);
|
||||
@@ -63,6 +69,8 @@ describe('Parse.Push', () => {
|
||||
alert: 'Hello world!'
|
||||
}
|
||||
}, {useMasterKey: true})
|
||||
}).then(() => {
|
||||
return delayPromise(500);
|
||||
})
|
||||
.then(() => {
|
||||
done();
|
||||
@@ -83,6 +91,8 @@ describe('Parse.Push', () => {
|
||||
alert: 'Hello world!'
|
||||
}
|
||||
}, {useMasterKey: true})
|
||||
}).then(() => {
|
||||
return delayPromise(500);
|
||||
}).then(() => {
|
||||
done();
|
||||
}).catch((err) => {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
var PushController = require('../src/Controllers/PushController').PushController;
|
||||
var StatusHandler = require('../src/StatusHandler');
|
||||
var Config = require('../src/Config');
|
||||
var validatePushType = require('../src/Push/utils').validatePushType;
|
||||
|
||||
const successfulTransmissions = function(body, installations) {
|
||||
|
||||
@@ -35,7 +36,7 @@ describe('PushController', () => {
|
||||
var validPushTypes = ['ios', 'android'];
|
||||
|
||||
expect(function(){
|
||||
PushController.validatePushType(where, validPushTypes);
|
||||
validatePushType(where, validPushTypes);
|
||||
}).not.toThrow();
|
||||
done();
|
||||
});
|
||||
@@ -48,7 +49,7 @@ describe('PushController', () => {
|
||||
var validPushTypes = ['ios', 'android'];
|
||||
|
||||
expect(function(){
|
||||
PushController.validatePushType(where, validPushTypes);
|
||||
validatePushType(where, validPushTypes);
|
||||
}).not.toThrow();
|
||||
done();
|
||||
});
|
||||
@@ -63,7 +64,7 @@ describe('PushController', () => {
|
||||
var validPushTypes = ['ios', 'android'];
|
||||
|
||||
expect(function(){
|
||||
PushController.validatePushType(where, validPushTypes);
|
||||
validatePushType(where, validPushTypes);
|
||||
}).not.toThrow();
|
||||
done();
|
||||
});
|
||||
@@ -76,7 +77,7 @@ describe('PushController', () => {
|
||||
var validPushTypes = ['ios', 'android'];
|
||||
|
||||
expect(function(){
|
||||
PushController.validatePushType(where, validPushTypes);
|
||||
validatePushType(where, validPushTypes);
|
||||
}).toThrow();
|
||||
done();
|
||||
});
|
||||
@@ -89,7 +90,7 @@ describe('PushController', () => {
|
||||
var validPushTypes = ['ios', 'android'];
|
||||
|
||||
expect(function(){
|
||||
PushController.validatePushType(where, validPushTypes);
|
||||
validatePushType(where, validPushTypes);
|
||||
}).toThrow();
|
||||
done();
|
||||
});
|
||||
@@ -131,7 +132,23 @@ describe('PushController', () => {
|
||||
});
|
||||
|
||||
it('properly increment badges', (done) => {
|
||||
|
||||
var pushAdapter = {
|
||||
send: function(body, installations) {
|
||||
var badge = body.data.badge;
|
||||
installations.forEach((installation) => {
|
||||
if (installation.deviceType == "ios") {
|
||||
expect(installation.badge).toEqual(badge);
|
||||
expect(installation.originalBadge + 1).toEqual(installation.badge);
|
||||
} else {
|
||||
expect(installation.badge).toBeUndefined();
|
||||
}
|
||||
})
|
||||
return successfulTransmissions(body, installations);
|
||||
},
|
||||
getValidPushTypes: function() {
|
||||
return ["ios", "android"];
|
||||
}
|
||||
}
|
||||
var payload = {data:{
|
||||
alert: "Hello World!",
|
||||
badge: "Increment",
|
||||
@@ -154,32 +171,17 @@ describe('PushController', () => {
|
||||
installation.set("deviceType", "android");
|
||||
installations.push(installation);
|
||||
}
|
||||
|
||||
var pushAdapter = {
|
||||
send: function(body, installations) {
|
||||
var badge = body.data.badge;
|
||||
installations.forEach((installation) => {
|
||||
if (installation.deviceType == "ios") {
|
||||
expect(installation.badge).toEqual(badge);
|
||||
expect(installation.originalBadge + 1).toEqual(installation.badge);
|
||||
} else {
|
||||
expect(installation.badge).toBeUndefined();
|
||||
}
|
||||
})
|
||||
return successfulTransmissions(body, installations);
|
||||
},
|
||||
getValidPushTypes: function() {
|
||||
return ["ios", "android"];
|
||||
}
|
||||
}
|
||||
|
||||
var config = new Config(Parse.applicationId);
|
||||
var auth = {
|
||||
isMaster: true
|
||||
}
|
||||
|
||||
var pushController = new PushController(pushAdapter, Parse.applicationId, defaultConfiguration.push);
|
||||
Parse.Object.saveAll(installations).then(() => {
|
||||
var pushController = new PushController();
|
||||
reconfigureServer({
|
||||
push: { adapter: pushAdapter }
|
||||
}).then(() => {
|
||||
return Parse.Object.saveAll(installations)
|
||||
}).then(() => {
|
||||
return pushController.sendPush(payload, {}, config, auth);
|
||||
}).then(() => {
|
||||
done();
|
||||
@@ -187,11 +189,24 @@ describe('PushController', () => {
|
||||
jfail(err);
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('properly set badges to 1', (done) => {
|
||||
|
||||
var pushAdapter = {
|
||||
send: function(body, installations) {
|
||||
var badge = body.data.badge;
|
||||
installations.forEach((installation) => {
|
||||
expect(installation.badge).toEqual(badge);
|
||||
expect(1).toEqual(installation.badge);
|
||||
})
|
||||
return successfulTransmissions(body, installations);
|
||||
},
|
||||
getValidPushTypes: function() {
|
||||
return ["ios"];
|
||||
}
|
||||
}
|
||||
|
||||
var payload = {data: {
|
||||
alert: "Hello World!",
|
||||
badge: 1,
|
||||
@@ -207,27 +222,17 @@ describe('PushController', () => {
|
||||
installations.push(installation);
|
||||
}
|
||||
|
||||
var pushAdapter = {
|
||||
send: function(body, installations) {
|
||||
var badge = body.data.badge;
|
||||
installations.forEach((installation) => {
|
||||
expect(installation.badge).toEqual(badge);
|
||||
expect(1).toEqual(installation.badge);
|
||||
})
|
||||
return successfulTransmissions(body, installations);
|
||||
},
|
||||
getValidPushTypes: function() {
|
||||
return ["ios"];
|
||||
}
|
||||
}
|
||||
|
||||
var config = new Config(Parse.applicationId);
|
||||
var auth = {
|
||||
isMaster: true
|
||||
}
|
||||
|
||||
var pushController = new PushController(pushAdapter, Parse.applicationId, defaultConfiguration.push);
|
||||
Parse.Object.saveAll(installations).then(() => {
|
||||
var pushController = new PushController();
|
||||
reconfigureServer({
|
||||
push: { adapter: pushAdapter }
|
||||
}).then(() => {
|
||||
return Parse.Object.saveAll(installations)
|
||||
}).then(() => {
|
||||
return pushController.sendPush(payload, {}, config, auth);
|
||||
}).then(() => {
|
||||
done();
|
||||
@@ -235,7 +240,6 @@ describe('PushController', () => {
|
||||
fail("should not fail");
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('properly set badges to 1 with complex query #2903 #3022', (done) => {
|
||||
@@ -276,9 +280,14 @@ describe('PushController', () => {
|
||||
var auth = {
|
||||
isMaster: true
|
||||
}
|
||||
|
||||
var pushController = new PushController(pushAdapter, Parse.applicationId, defaultConfiguration.push);
|
||||
Parse.Object.saveAll(installations).then((installations) => {
|
||||
var pushController = new PushController();
|
||||
reconfigureServer({
|
||||
push: {
|
||||
adapter: pushAdapter
|
||||
}
|
||||
}).then(() => {
|
||||
return Parse.Object.saveAll(installations)
|
||||
}).then((installations) => {
|
||||
const objectIds = installations.map(installation => {
|
||||
return installation.id;
|
||||
})
|
||||
@@ -286,6 +295,10 @@ describe('PushController', () => {
|
||||
objectId: {'$in': objectIds.slice(0, 5)}
|
||||
}
|
||||
return pushController.sendPush(payload, where, config, auth);
|
||||
}).then(() => {
|
||||
return new Promise((res) => {
|
||||
setTimeout(res, 300);
|
||||
});
|
||||
}).then(() => {
|
||||
expect(matchedInstallationsCount).toBe(5);
|
||||
const query = new Parse.Query(Parse.Installation);
|
||||
@@ -338,46 +351,50 @@ describe('PushController', () => {
|
||||
var auth = {
|
||||
isMaster: true
|
||||
}
|
||||
|
||||
var pushController = new PushController(pushAdapter, Parse.applicationId, defaultConfiguration.push);
|
||||
Parse.Object.saveAll(installations).then(() => {
|
||||
return pushController.sendPush(payload, {}, config, auth);
|
||||
var pushController = new PushController();
|
||||
reconfigureServer({
|
||||
push: { adapter: pushAdapter }
|
||||
}).then(() => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve();
|
||||
}, 1000);
|
||||
});
|
||||
}).then(() => {
|
||||
const query = new Parse.Query('_PushStatus');
|
||||
return query.find({useMasterKey: true});
|
||||
}).then((results) => {
|
||||
expect(results.length).toBe(1);
|
||||
const result = results[0];
|
||||
expect(result.createdAt instanceof Date).toBe(true);
|
||||
expect(result.updatedAt instanceof Date).toBe(true);
|
||||
expect(result.id.length).toBe(10);
|
||||
expect(result.get('source')).toEqual('rest');
|
||||
expect(result.get('query')).toEqual(JSON.stringify({}));
|
||||
expect(typeof result.get('payload')).toEqual("string");
|
||||
expect(JSON.parse(result.get('payload'))).toEqual(payload.data);
|
||||
expect(result.get('status')).toEqual('succeeded');
|
||||
expect(result.get('numSent')).toEqual(10);
|
||||
expect(result.get('sentPerType')).toEqual({
|
||||
'ios': 10 // 10 ios
|
||||
});
|
||||
expect(result.get('numFailed')).toEqual(5);
|
||||
expect(result.get('failedPerType')).toEqual({
|
||||
'android': 5 // android
|
||||
});
|
||||
return Parse.Object.saveAll(installations)
|
||||
})
|
||||
.then(() => {
|
||||
return pushController.sendPush(payload, {}, config, auth);
|
||||
}).then(() => {
|
||||
// it is enqueued so it can take time
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve();
|
||||
}, 1000);
|
||||
});
|
||||
}).then(() => {
|
||||
const query = new Parse.Query('_PushStatus');
|
||||
return query.find({useMasterKey: true});
|
||||
}).then((results) => {
|
||||
expect(results.length).toBe(1);
|
||||
const result = results[0];
|
||||
expect(result.createdAt instanceof Date).toBe(true);
|
||||
expect(result.updatedAt instanceof Date).toBe(true);
|
||||
expect(result.id.length).toBe(10);
|
||||
expect(result.get('source')).toEqual('rest');
|
||||
expect(result.get('query')).toEqual(JSON.stringify({}));
|
||||
expect(typeof result.get('payload')).toEqual("string");
|
||||
expect(JSON.parse(result.get('payload'))).toEqual(payload.data);
|
||||
expect(result.get('status')).toEqual('succeeded');
|
||||
expect(result.get('numSent')).toEqual(10);
|
||||
expect(result.get('sentPerType')).toEqual({
|
||||
'ios': 10 // 10 ios
|
||||
});
|
||||
expect(result.get('numFailed')).toEqual(5);
|
||||
expect(result.get('failedPerType')).toEqual({
|
||||
'android': 5 // android
|
||||
});
|
||||
// Try to get it without masterKey
|
||||
const query = new Parse.Query('_PushStatus');
|
||||
return query.find();
|
||||
}).then((results) => {
|
||||
expect(results.length).toBe(0);
|
||||
done();
|
||||
});
|
||||
|
||||
const query = new Parse.Query('_PushStatus');
|
||||
return query.find();
|
||||
}).then((results) => {
|
||||
expect(results.length).toBe(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should properly report failures in _PushStatus', (done) => {
|
||||
@@ -404,8 +421,12 @@ describe('PushController', () => {
|
||||
var auth = {
|
||||
isMaster: true
|
||||
}
|
||||
var pushController = new PushController(pushAdapter, Parse.applicationId, defaultConfiguration.push);
|
||||
pushController.sendPush(payload, where, config, auth).then(() => {
|
||||
var pushController = new PushController();
|
||||
reconfigureServer({
|
||||
push: { adapter: pushAdapter }
|
||||
}).then(() => {
|
||||
return pushController.sendPush(payload, where, config, auth)
|
||||
}).then(() => {
|
||||
fail('should not succeed');
|
||||
done();
|
||||
}).catch(() => {
|
||||
@@ -416,7 +437,7 @@ describe('PushController', () => {
|
||||
expect(pushStatus.get('status')).toBe('failed');
|
||||
done();
|
||||
});
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
it('should support full RESTQuery for increment', (done) => {
|
||||
@@ -433,7 +454,6 @@ describe('PushController', () => {
|
||||
return ["ios"];
|
||||
}
|
||||
}
|
||||
|
||||
var config = new Config(Parse.applicationId);
|
||||
var auth = {
|
||||
isMaster: true
|
||||
@@ -450,8 +470,12 @@ describe('PushController', () => {
|
||||
}
|
||||
}
|
||||
|
||||
var pushController = new PushController(pushAdapter, Parse.applicationId, defaultConfiguration.push);
|
||||
pushController.sendPush(payload, where, config, auth).then(() => {
|
||||
var pushController = new PushController();
|
||||
reconfigureServer({
|
||||
push: { adapter: pushAdapter }
|
||||
}).then(() => {
|
||||
return pushController.sendPush(payload, where, config, auth);
|
||||
}).then(() => {
|
||||
done();
|
||||
}).catch((err) => {
|
||||
jfail(err);
|
||||
@@ -491,8 +515,12 @@ describe('PushController', () => {
|
||||
}
|
||||
}
|
||||
|
||||
var pushController = new PushController(pushAdapter, Parse.applicationId, defaultConfiguration.push);
|
||||
pushController.sendPush(payload, where, config, auth).then(() => {
|
||||
var pushController = new PushController();
|
||||
reconfigureServer({
|
||||
push: { adapter: pushAdapter }
|
||||
}).then(() => {
|
||||
pushController.sendPush(payload, where, config, auth)
|
||||
}).then(() => {
|
||||
done();
|
||||
}).catch(() => {
|
||||
fail('should not fail');
|
||||
|
||||
57
spec/PushWorker.spec.js
Normal file
57
spec/PushWorker.spec.js
Normal file
@@ -0,0 +1,57 @@
|
||||
var PushWorker = require('../src').PushWorker;
|
||||
var Config = require('../src/Config');
|
||||
|
||||
describe('PushWorker', () => {
|
||||
it('should run with small batch', (done) => {
|
||||
const batchSize = 3;
|
||||
var sendCount = 0;
|
||||
reconfigureServer({
|
||||
push: {
|
||||
queueOptions: {
|
||||
disablePushWorker: true,
|
||||
batchSize
|
||||
}
|
||||
}
|
||||
}).then(() => {
|
||||
expect(new Config('test').pushWorker).toBeUndefined();
|
||||
new PushWorker({
|
||||
send: (body, installations) => {
|
||||
expect(installations.length <= batchSize).toBe(true);
|
||||
sendCount += installations.length;
|
||||
return Promise.resolve();
|
||||
},
|
||||
getValidPushTypes: function() {
|
||||
return ['ios', 'android']
|
||||
}
|
||||
});
|
||||
var installations = [];
|
||||
while(installations.length != 10) {
|
||||
var installation = new Parse.Object("_Installation");
|
||||
installation.set("installationId", "installation_" + installations.length);
|
||||
installation.set("deviceToken","device_token_" + installations.length)
|
||||
installation.set("badge", 1);
|
||||
installation.set("deviceType", "ios");
|
||||
installations.push(installation);
|
||||
}
|
||||
return Parse.Object.saveAll(installations);
|
||||
}).then(() => {
|
||||
return Parse.Push.send({
|
||||
where: {
|
||||
deviceType: 'ios'
|
||||
},
|
||||
data: {
|
||||
alert: 'Hello world!'
|
||||
}
|
||||
}, {useMasterKey: true})
|
||||
}).then(() => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, 500);
|
||||
});
|
||||
}).then(() => {
|
||||
expect(sendCount).toBe(10);
|
||||
done();
|
||||
}).catch(err => {
|
||||
jfail(err);
|
||||
})
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user