Files
kami-parse-server/spec/PushController.spec.js
Florent Vilmart 305e4ba445 Removes need to use babel-register (#4865)
* Removes need to use babel-register

- Adds watch to watch changes when running the test to regenerate
- Tests are now pure node 8

* Adds timing to helper.js

* Update contribution guide

* Adds inline sourcemaps generation to restore coverage

* nits
2018-08-12 17:49:09 -04:00

1339 lines
42 KiB
JavaScript

"use strict";
const PushController = require('../lib/Controllers/PushController').PushController;
const StatusHandler = require('../lib/StatusHandler');
const Config = require('../lib/Config');
const validatePushType = require('../lib/Push/utils').validatePushType;
const successfulTransmissions = function(body, installations) {
const promises = installations.map((device) => {
return Promise.resolve({
transmitted: true,
device: device,
})
});
return Promise.all(promises);
}
const successfulIOS = function(body, installations) {
const promises = installations.map((device) => {
return Promise.resolve({
transmitted: device.deviceType == "ios",
device: device,
})
});
return Promise.all(promises);
}
describe('PushController', () => {
it('can validate device type when no device type is set', (done) => {
// Make query condition
const where = {
};
const validPushTypes = ['ios', 'android'];
expect(function(){
validatePushType(where, validPushTypes);
}).not.toThrow();
done();
});
it('can validate device type when single valid device type is set', (done) => {
// Make query condition
const where = {
'deviceType': 'ios'
};
const validPushTypes = ['ios', 'android'];
expect(function(){
validatePushType(where, validPushTypes);
}).not.toThrow();
done();
});
it('can validate device type when multiple valid device types are set', (done) => {
// Make query condition
const where = {
'deviceType': {
'$in': ['android', 'ios']
}
};
const validPushTypes = ['ios', 'android'];
expect(function(){
validatePushType(where, validPushTypes);
}).not.toThrow();
done();
});
it('can throw on validateDeviceType when single invalid device type is set', (done) => {
// Make query condition
const where = {
'deviceType': 'osx'
};
const validPushTypes = ['ios', 'android'];
expect(function(){
validatePushType(where, validPushTypes);
}).toThrow();
done();
});
it('can throw on validateDeviceType when single invalid device type is set', (done) => {
// Make query condition
const where = {
'deviceType': 'osx'
};
const validPushTypes = ['ios', 'android'];
expect(function(){
validatePushType(where, validPushTypes);
}).toThrow();
done();
});
it('can get expiration time in string format', (done) => {
// Make mock request
const timeStr = '2015-03-19T22:05:08Z';
const body = {
'expiration_time': timeStr
}
const time = PushController.getExpirationTime(body);
expect(time).toEqual(new Date(timeStr).valueOf());
done();
});
it('can get expiration time in number format', (done) => {
// Make mock request
const timeNumber = 1426802708;
const body = {
'expiration_time': timeNumber
}
const time = PushController.getExpirationTime(body);
expect(time).toEqual(timeNumber * 1000);
done();
});
it('can throw on getExpirationTime in invalid format', (done) => {
// Make mock request
const body = {
'expiration_time': 'abcd'
}
expect(function(){
PushController.getExpirationTime(body);
}).toThrow();
done();
});
it('can get push time in string format', (done) => {
// Make mock request
const timeStr = '2015-03-19T22:05:08Z';
const body = {
'push_time': timeStr
}
const { date } = PushController.getPushTime(body);
expect(date).toEqual(new Date(timeStr));
done();
});
it('can get push time in number format', (done) => {
// Make mock request
const timeNumber = 1426802708;
const body = {
'push_time': timeNumber
}
const { date } = PushController.getPushTime(body);
expect(date.valueOf()).toEqual(timeNumber * 1000);
done();
});
it('can throw on getPushTime in invalid format', (done) => {
// Make mock request
const body = {
'push_time': 'abcd'
}
expect(function(){
PushController.getPushTime(body);
}).toThrow();
done();
});
it('properly increment badges', (done) => {
const pushAdapter = {
send: function(body, installations) {
const badge = body.data.badge;
installations.forEach((installation) => {
expect(installation.badge).toEqual(badge);
expect(installation.originalBadge + 1).toEqual(installation.badge);
})
return successfulTransmissions(body, installations);
},
getValidPushTypes: function() {
return ["ios", "android"];
}
}
const payload = {data:{
alert: "Hello World!",
badge: "Increment",
}}
const installations = [];
while(installations.length != 10) {
const installation = new Parse.Object("_Installation");
installation.set("installationId", "installation_" + installations.length);
installation.set("deviceToken","device_token_" + installations.length)
installation.set("badge", installations.length);
installation.set("originalBadge", installations.length);
installation.set("deviceType", "ios");
installations.push(installation);
}
while(installations.length != 15) {
const installation = new Parse.Object("_Installation");
installation.set("installationId", "installation_" + installations.length);
installation.set("deviceToken","device_token_" + installations.length);
installation.set("badge", installations.length);
installation.set("originalBadge", installations.length);
installation.set("deviceType", "android");
installations.push(installation);
}
const config = Config.get(Parse.applicationId);
const auth = {
isMaster: true
}
const pushController = new PushController();
reconfigureServer({
push: { adapter: pushAdapter }
}).then(() => {
return Parse.Object.saveAll(installations)
}).then(() => {
return pushController.sendPush(payload, {}, config, auth);
}).then(() => {
// Wait so the push is completed.
return new Promise((resolve) => { setTimeout(() => { resolve(); }, 1000); });
}).then(() => {
// Check we actually sent 15 pushes.
const query = new Parse.Query('_PushStatus');
return query.find({ useMasterKey: true })
}).then((results) => {
expect(results.length).toBe(1);
const pushStatus = results[0];
expect(pushStatus.get('numSent')).toBe(15);
}).then(() => {
// Check that the installations were actually updated.
const query = new Parse.Query('_Installation');
return query.find({ useMasterKey: true })
}).then((results) => {
expect(results.length).toBe(15);
for (let i = 0; i < 15; i++) {
const installation = results[i];
expect(installation.get('badge')).toBe(parseInt(installation.get('originalBadge')) + 1);
}
done()
}).catch((err) => {
jfail(err);
done();
});
});
it('properly set badges to 1', (done) => {
const pushAdapter = {
send: function(body, installations) {
const 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"];
}
}
const payload = {data: {
alert: "Hello World!",
badge: 1,
}}
const installations = [];
while(installations.length != 10) {
const installation = new Parse.Object("_Installation");
installation.set("installationId", "installation_" + installations.length);
installation.set("deviceToken","device_token_" + installations.length)
installation.set("badge", installations.length);
installation.set("originalBadge", installations.length);
installation.set("deviceType", "ios");
installations.push(installation);
}
const config = Config.get(Parse.applicationId);
const auth = {
isMaster: true
}
const pushController = new PushController();
reconfigureServer({
push: { adapter: pushAdapter }
}).then(() => {
return Parse.Object.saveAll(installations)
}).then(() => {
return pushController.sendPush(payload, {}, config, auth);
}).then(() => {
// Wait so the push is completed.
return new Promise((resolve) => { setTimeout(() => { resolve(); }, 1000); });
}).then(() => {
// Check we actually sent the pushes.
const query = new Parse.Query('_PushStatus');
return query.find({ useMasterKey: true })
}).then((results) => {
expect(results.length).toBe(1);
const pushStatus = results[0];
expect(pushStatus.get('numSent')).toBe(10);
}).then(() => {
// Check that the installations were actually updated.
const query = new Parse.Query('_Installation');
return query.find({ useMasterKey: true })
}).then((results) => {
expect(results.length).toBe(10);
for (let i = 0; i < 10; i++) {
const installation = results[i];
expect(installation.get('badge')).toBe(1);
}
done()
}).catch((err) => {
jfail(err);
done();
});
});
it('properly set badges to 1 with complex query #2903 #3022', (done) => {
const payload = {
data: {
alert: "Hello World!",
badge: 1,
}
}
const installations = [];
while(installations.length != 10) {
const installation = new Parse.Object("_Installation");
installation.set("installationId", "installation_" + installations.length);
installation.set("deviceToken","device_token_" + installations.length)
installation.set("badge", installations.length);
installation.set("originalBadge", installations.length);
installation.set("deviceType", "ios");
installations.push(installation);
}
let matchedInstallationsCount = 0;
const pushAdapter = {
send: function(body, installations) {
matchedInstallationsCount += installations.length;
const 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"];
}
}
const config = Config.get(Parse.applicationId);
const auth = {
isMaster: true
}
const pushController = new PushController();
reconfigureServer({
push: {
adapter: pushAdapter
}
}).then(() => {
return Parse.Object.saveAll(installations)
}).then((installations) => {
const objectIds = installations.map(installation => {
return installation.id;
})
const where = {
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);
query.equalTo('badge', 1);
return query.find({useMasterKey: true});
}).then((installations) => {
expect(installations.length).toBe(5);
done();
}).catch(() => {
fail("should not fail");
done();
});
});
it('properly creates _PushStatus', (done) => {
const pushStatusAfterSave = {
handler: function() {}
};
const spy = spyOn(pushStatusAfterSave, 'handler').and.callThrough();
Parse.Cloud.afterSave('_PushStatus', pushStatusAfterSave.handler);
const installations = [];
while(installations.length != 10) {
const installation = new Parse.Object("_Installation");
installation.set("installationId", "installation_" + installations.length);
installation.set("deviceToken","device_token_" + installations.length)
installation.set("badge", installations.length);
installation.set("originalBadge", installations.length);
installation.set("deviceType", "ios");
installations.push(installation);
}
while(installations.length != 15) {
const installation = new Parse.Object("_Installation");
installation.set("installationId", "installation_" + installations.length);
installation.set("deviceToken","device_token_" + installations.length)
installation.set("deviceType", "android");
installations.push(installation);
}
const payload = {data: {
alert: "Hello World!",
badge: 1,
}}
const pushAdapter = {
send: function(body, installations) {
return successfulIOS(body, installations);
},
getValidPushTypes: function() {
return ["ios"];
}
}
const config = Config.get(Parse.applicationId);
const auth = {
isMaster: true
}
const pushController = new PushController();
reconfigureServer({
push: { adapter: pushAdapter }
}).then(() => {
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();
}).catch((error) => {
expect(error.code).toBe(119);
})
.then(() => {
function getPushStatus(callIndex) {
return spy.calls.all()[callIndex].args[0].object;
}
expect(spy).toHaveBeenCalled();
expect(spy.calls.count()).toBe(4);
const allCalls = spy.calls.all();
allCalls.forEach((call) => {
expect(call.args.length).toBe(2);
const object = call.args[0].object;
expect(object instanceof Parse.Object).toBe(true);
});
expect(getPushStatus(0).get('status')).toBe('pending');
expect(getPushStatus(1).get('status')).toBe('running');
expect(getPushStatus(1).get('numSent')).toBe(0);
expect(getPushStatus(2).get('status')).toBe('running');
expect(getPushStatus(2).get('numSent')).toBe(10);
expect(getPushStatus(2).get('numFailed')).toBe(5);
// Those are updated from a nested . operation, this would
// not render correctly before
expect(getPushStatus(2).get('failedPerType')).toEqual({
android: 5
});
expect(getPushStatus(2).get('sentPerType')).toEqual({
ios: 10
});
expect(getPushStatus(3).get('status')).toBe('succeeded');
})
.then(done).catch(done.fail);
});
it('properly creates _PushStatus without serverURL', (done) => {
const pushStatusAfterSave = {
handler: function() {}
};
Parse.Cloud.afterSave('_PushStatus', pushStatusAfterSave.handler);
const installation = new Parse.Object("_Installation");
installation.set("installationId", "installation");
installation.set("deviceToken","device_token")
installation.set("badge", 0);
installation.set("originalBadge", 0);
installation.set("deviceType", "ios");
const payload = {data: {
alert: "Hello World!",
badge: 1,
}}
const pushAdapter = {
send: function(body, installations) {
return successfulIOS(body, installations);
},
getValidPushTypes: function() {
return ["ios"];
}
}
const config = Config.get(Parse.applicationId);
const auth = {
isMaster: true
}
const pushController = new PushController();
return installation.save().then(() => {
return reconfigureServer({
serverURL: 'http://localhost:8378/', // server with borked URL
push: { adapter: pushAdapter }
})
})
.then(() => {
return pushController.sendPush(payload, {}, config, auth);
}).then(() => {
// it is enqueued so it can take time
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, 1000);
});
}).then(() => {
Parse.serverURL = 'http://localhost:8378/1'; // GOOD url
const query = new Parse.Query('_PushStatus');
return query.find({useMasterKey: true});
}).then((results) => {
expect(results.length).toBe(1);
})
.then(done).catch(done.fail);
});
it('should properly report failures in _PushStatus', (done) => {
const pushAdapter = {
send: function(body, installations) {
return installations.map((installation) => {
return Promise.resolve({
deviceType: installation.deviceType
})
})
},
getValidPushTypes: function() {
return ["ios"];
}
}
const where = { 'channels': {
'$ins': ['Giants', 'Mets']
}};
const payload = {data: {
alert: "Hello World!",
badge: 1,
}}
const config = Config.get(Parse.applicationId);
const auth = {
isMaster: true
}
const pushController = new PushController();
reconfigureServer({
push: { adapter: pushAdapter }
}).then(() => {
return pushController.sendPush(payload, where, config, auth)
}).then(() => {
fail('should not succeed');
done();
}).catch(() => {
const query = new Parse.Query('_PushStatus');
query.find({useMasterKey: true}).then((results) => {
expect(results.length).toBe(1);
const pushStatus = results[0];
expect(pushStatus.get('status')).toBe('failed');
done();
});
});
});
it('should support full RESTQuery for increment', (done) => {
const payload = {data: {
alert: "Hello World!",
badge: 'Increment',
}}
const pushAdapter = {
send: function(body, installations) {
return successfulTransmissions(body, installations);
},
getValidPushTypes: function() {
return ["ios"];
}
}
const config = Config.get(Parse.applicationId);
const auth = {
isMaster: true
}
const where = {
'deviceToken': {
'$in': ['device_token_0', 'device_token_1', 'device_token_2']
}
}
const pushController = new PushController();
reconfigureServer({
push: { adapter: pushAdapter }
}).then(() => {
const installations = [];
while (installations.length != 5) {
const installation = new Parse.Object("_Installation");
installation.set("installationId", "installation_" + installations.length);
installation.set("deviceToken", "device_token_" + installations.length)
installation.set("badge", installations.length);
installation.set("originalBadge", installations.length);
installation.set("deviceType", "ios");
installations.push(installation);
}
return Parse.Object.saveAll(installations);
}).then(() => {
return pushController.sendPush(payload, where, config, auth);
}).then(() => {
// Wait so the push is completed.
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 pushStatus = results[0];
expect(pushStatus.get('numSent')).toBe(3);
done();
}).catch((err) => {
jfail(err);
done();
});
});
it('should support object type for alert', (done) => {
const payload = {data: {
alert: {
'loc-key': 'hello_world',
},
}}
const pushAdapter = {
send: function(body, installations) {
return successfulTransmissions(body, installations);
},
getValidPushTypes: function() {
return ["ios"];
}
}
const config = Config.get(Parse.applicationId);
const auth = {
isMaster: true
}
const where = {
'deviceType': 'ios'
}
const pushController = new PushController();
reconfigureServer({
push: { adapter: pushAdapter }
}).then(() => {
const installations = [];
while (installations.length != 5) {
const installation = new Parse.Object("_Installation");
installation.set("installationId", "installation_" + installations.length);
installation.set("deviceToken", "device_token_" + installations.length)
installation.set("badge", installations.length);
installation.set("originalBadge", installations.length);
installation.set("deviceType", "ios");
installations.push(installation);
}
return Parse.Object.saveAll(installations);
}).then(() => {
return pushController.sendPush(payload, where, config, auth)
}).then(() => {
// Wait so the push is completed.
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 pushStatus = results[0];
expect(pushStatus.get('numSent')).toBe(5);
done();
}).catch(() => {
fail('should not fail');
done();
});
});
it('should flatten', () => {
const res = StatusHandler.flatten([1, [2], [[3, 4], 5], [[[6]]]])
expect(res).toEqual([1,2,3,4,5,6]);
});
it('properly transforms push time', () => {
expect(PushController.getPushTime()).toBe(undefined);
expect(PushController.getPushTime({
'push_time': 1000
}).date).toEqual(new Date(1000 * 1000));
expect(PushController.getPushTime({
'push_time': '2017-01-01'
}).date).toEqual(new Date('2017-01-01'));
expect(() => {PushController.getPushTime({
'push_time': 'gibberish-time'
})}).toThrow();
expect(() => {PushController.getPushTime({
'push_time': Number.NaN
})}).toThrow();
expect(PushController.getPushTime({
push_time: '2017-09-06T13:42:48.369Z'
})).toEqual({
date: new Date('2017-09-06T13:42:48.369Z'),
isLocalTime: false,
});
expect(PushController.getPushTime({
push_time: '2007-04-05T12:30-02:00',
})).toEqual({
date: new Date('2007-04-05T12:30-02:00'),
isLocalTime: false,
});
expect(PushController.getPushTime({
push_time: '2007-04-05T12:30',
})).toEqual({
date: new Date('2007-04-05T12:30'),
isLocalTime: true,
});
});
it('should not schedule push when not configured', (done) => {
const config = Config.get(Parse.applicationId);
const auth = {
isMaster: true
}
const pushAdapter = {
send: function(body, installations) {
return successfulTransmissions(body, installations);
},
getValidPushTypes: function() {
return ["ios"];
}
}
const pushController = new PushController();
const payload = {
data: {
alert: 'hello',
},
push_time: new Date().getTime()
}
const installations = [];
while(installations.length != 10) {
const installation = new Parse.Object("_Installation");
installation.set("installationId", "installation_" + installations.length);
installation.set("deviceToken","device_token_" + installations.length)
installation.set("badge", installations.length);
installation.set("originalBadge", installations.length);
installation.set("deviceType", "ios");
installations.push(installation);
}
reconfigureServer({
push: { adapter: pushAdapter }
}).then(() => {
return Parse.Object.saveAll(installations).then(() => {
return pushController.sendPush(payload, {}, config, auth);
}).then(() => new Promise(resolve => setTimeout(resolve, 300)));
}).then(() => {
const query = new Parse.Query('_PushStatus');
return query.find({useMasterKey: true}).then((results) => {
expect(results.length).toBe(1);
const pushStatus = results[0];
expect(pushStatus.get('status')).not.toBe('scheduled');
done();
});
}).catch((err) => {
console.error(err);
fail('should not fail');
done();
});
});
it('should schedule push when configured', (done) => {
const auth = {
isMaster: true
}
const pushAdapter = {
send: function(body, installations) {
const promises = installations.map((device) => {
if (!device.deviceToken) {
// Simulate error when device token is not set
return Promise.reject();
}
return Promise.resolve({
transmitted: true,
device: device,
})
});
return Promise.all(promises);
},
getValidPushTypes: function() {
return ["ios"];
}
}
const pushController = new PushController();
const payload = {
data: {
alert: 'hello',
},
push_time: new Date().getTime() / 1000
}
const installations = [];
while(installations.length != 10) {
const installation = new Parse.Object("_Installation");
installation.set("installationId", "installation_" + installations.length);
installation.set("deviceToken","device_token_" + installations.length)
installation.set("badge", installations.length);
installation.set("originalBadge", installations.length);
installation.set("deviceType", "ios");
installations.push(installation);
}
reconfigureServer({
push: { adapter: pushAdapter },
scheduledPush: true
}).then(() => {
const config = Config.get(Parse.applicationId);
return Parse.Object.saveAll(installations).then(() => {
return pushController.sendPush(payload, {}, config, auth);
}).then(() => new Promise(resolve => setTimeout(resolve, 300)));
}).then(() => {
const query = new Parse.Query('_PushStatus');
return query.find({useMasterKey: true}).then((results) => {
expect(results.length).toBe(1);
const pushStatus = results[0];
expect(pushStatus.get('status')).toBe('scheduled');
});
}).then(done).catch(done.err);
});
it('should not enqueue push when device token is not set', (done) => {
const auth = {
isMaster: true
}
const pushAdapter = {
send: function(body, installations) {
const promises = installations.map((device) => {
if (!device.deviceToken) {
// Simulate error when device token is not set
return Promise.reject();
}
return Promise.resolve({
transmitted: true,
device: device,
})
});
return Promise.all(promises);
},
getValidPushTypes: function() {
return ["ios"];
}
}
const pushController = new PushController();
const payload = {
data: {
alert: 'hello',
},
push_time: new Date().getTime() / 1000
}
const installations = [];
while(installations.length != 5) {
const installation = new Parse.Object("_Installation");
installation.set("installationId", "installation_" + installations.length);
installation.set("deviceToken","device_token_" + installations.length)
installation.set("badge", installations.length);
installation.set("originalBadge", installations.length);
installation.set("deviceType", "ios");
installations.push(installation);
}
while(installations.length != 15) {
const installation = new Parse.Object("_Installation");
installation.set("installationId", "installation_" + installations.length);
installation.set("badge", installations.length);
installation.set("originalBadge", installations.length);
installation.set("deviceType", "ios");
installations.push(installation);
}
reconfigureServer({
push: { adapter: pushAdapter }
}).then(() => {
const config = Config.get(Parse.applicationId);
return Parse.Object.saveAll(installations).then(() => {
return pushController.sendPush(payload, {}, config, auth);
}).then(() => new Promise(resolve => setTimeout(resolve, 100)));
}).then(() => {
const query = new Parse.Query('_PushStatus');
return query.find({useMasterKey: true}).then((results) => {
expect(results.length).toBe(1);
const pushStatus = results[0];
expect(pushStatus.get('numSent')).toBe(5);
expect(pushStatus.get('status')).toBe('succeeded');
done();
});
}).catch((err) => {
console.error(err);
fail('should not fail');
done();
});
});
it('should not mark the _PushStatus as failed when audience has no deviceToken', (done) => {
const auth = {
isMaster: true
}
const pushAdapter = {
send: function(body, installations) {
const promises = installations.map((device) => {
if (!device.deviceToken) {
// Simulate error when device token is not set
return Promise.reject();
}
return Promise.resolve({
transmitted: true,
device: device,
})
});
return Promise.all(promises);
},
getValidPushTypes: function() {
return ["ios"];
}
}
const pushController = new PushController();
const payload = {
data: {
alert: 'hello',
},
push_time: new Date().getTime() / 1000
}
const installations = [];
while(installations.length != 5) {
const installation = new Parse.Object("_Installation");
installation.set("installationId", "installation_" + installations.length);
installation.set("badge", installations.length);
installation.set("originalBadge", installations.length);
installation.set("deviceType", "ios");
installations.push(installation);
}
reconfigureServer({
push: { adapter: pushAdapter }
}).then(() => {
const config = Config.get(Parse.applicationId);
return Parse.Object.saveAll(installations).then(() => {
return pushController.sendPush(payload, {}, config, auth)
}).then(() => new Promise(resolve => setTimeout(resolve, 100)));
}).then(() => {
const query = new Parse.Query('_PushStatus');
return query.find({useMasterKey: true}).then((results) => {
expect(results.length).toBe(1);
const pushStatus = results[0];
expect(pushStatus.get('numSent')).toBe(0);
expect(pushStatus.get('status')).toBe('succeeded');
done();
});
}).catch((err) => {
console.error(err);
fail('should not fail');
done();
});
});
it('should support localized payload data', (done) => {
const payload = {data: {
alert: 'Hello!',
'alert-fr': 'Bonjour',
'alert-es': 'Ola'
}}
const pushAdapter = {
send: function(body, installations) {
return successfulTransmissions(body, installations);
},
getValidPushTypes: function() {
return ["ios"];
}
}
const config = Config.get(Parse.applicationId);
const auth = {
isMaster: true
}
const where = {
'deviceType': 'ios'
}
spyOn(pushAdapter, 'send').and.callThrough();
const pushController = new PushController();
reconfigureServer({
push: { adapter: pushAdapter }
}).then(() => {
const installations = [];
while (installations.length != 5) {
const installation = new Parse.Object("_Installation");
installation.set("installationId", "installation_" + installations.length);
installation.set("deviceToken", "device_token_" + installations.length)
installation.set("badge", installations.length);
installation.set("originalBadge", installations.length);
installation.set("deviceType", "ios");
installations.push(installation);
}
installations[0].set('localeIdentifier', 'fr-CA');
installations[1].set('localeIdentifier', 'fr-FR');
installations[2].set('localeIdentifier', 'en-US');
return Parse.Object.saveAll(installations);
}).then(() => {
return pushController.sendPush(payload, where, config, auth)
}).then(() => {
// Wait so the push is completed.
return new Promise((resolve) => { setTimeout(() => { resolve(); }, 1000); });
}).then(() => {
expect(pushAdapter.send.calls.count()).toBe(2);
const firstCall = pushAdapter.send.calls.first();
expect(firstCall.args[0].data).toEqual({
alert: 'Hello!'
});
expect(firstCall.args[1].length).toBe(3); // 3 installations
const lastCall = pushAdapter.send.calls.mostRecent();
expect(lastCall.args[0].data).toEqual({
alert: 'Bonjour'
});
expect(lastCall.args[1].length).toBe(2); // 2 installations
// No installation is in es so only 1 call for fr, and another for default
done();
}).catch(done.fail);
});
it('should update audiences', (done) => {
const pushAdapter = {
send: function(body, installations) {
return successfulTransmissions(body, installations);
},
getValidPushTypes: function() {
return ["ios"];
}
}
const config = Config.get(Parse.applicationId);
const auth = {
isMaster: true
}
let audienceId = null;
const now = new Date();
let timesUsed = 0;
const where = {
'deviceType': 'ios'
}
spyOn(pushAdapter, 'send').and.callThrough();
const pushController = new PushController();
reconfigureServer({
push: { adapter: pushAdapter }
}).then(() => {
const installations = [];
while (installations.length != 5) {
const installation = new Parse.Object("_Installation");
installation.set("installationId", "installation_" + installations.length);
installation.set("deviceToken","device_token_" + installations.length)
installation.set("badge", installations.length);
installation.set("originalBadge", installations.length);
installation.set("deviceType", "ios");
installations.push(installation);
}
return Parse.Object.saveAll(installations);
}).then(() => {
// Create an audience
const query = new Parse.Query("_Audience");
query.descending("createdAt");
query.equalTo("query", JSON.stringify(where));
const parseResults = (results) => {
if (results.length > 0) {
audienceId = results[0].id;
timesUsed = results[0].get('timesUsed');
if (!isFinite(timesUsed)) {
timesUsed = 0;
}
}
}
const audience = new Parse.Object("_Audience");
audience.set("name", "testAudience")
audience.set("query", JSON.stringify(where));
return Parse.Object.saveAll(audience).then(() => {
return query.find({ useMasterKey: true }).then(parseResults);
});
}).then(() => {
const body = {
data: { alert: 'hello' },
audience_id: audienceId
}
return pushController.sendPush(body, where, config, auth)
}).then(() => {
// Wait so the push is completed.
return new Promise((resolve) => { setTimeout(() => { resolve(); }, 1000); });
}).then(() => {
expect(pushAdapter.send.calls.count()).toBe(1);
const firstCall = pushAdapter.send.calls.first();
expect(firstCall.args[0].data).toEqual({
alert: 'hello'
});
expect(firstCall.args[1].length).toBe(5);
}).then(() => {
// Get the audience we used above.
const query = new Parse.Query("_Audience");
query.equalTo("objectId", audienceId);
return query.find({ useMasterKey: true })
}).then((results) => {
const audience = results[0];
expect(audience.get('query')).toBe(JSON.stringify(where));
expect(audience.get('timesUsed')).toBe(timesUsed + 1);
expect(audience.get('lastUsed')).not.toBeLessThan(now);
}).then(() => {
done();
}).catch(done.fail);
});
describe('pushTimeHasTimezoneComponent', () => {
it('should be accurate', () => {
expect(PushController.pushTimeHasTimezoneComponent('2017-09-06T17:14:01.048Z'))
.toBe(true, 'UTC time');
expect(PushController.pushTimeHasTimezoneComponent('2007-04-05T12:30-02:00'))
.toBe(true, 'Timezone offset');
expect(PushController.pushTimeHasTimezoneComponent('2007-04-05T12:30:00.000Z-02:00'))
.toBe(true, 'Seconds + Milliseconds + Timezone offset');
expect(PushController.pushTimeHasTimezoneComponent('2017-09-06T17:14:01.048'))
.toBe(false, 'No timezone');
expect(PushController.pushTimeHasTimezoneComponent('2017-09-06'))
.toBe(false, 'YY-MM-DD');
});
});
describe('formatPushTime', () => {
it('should format as ISO string', () => {
expect(PushController.formatPushTime({
date: new Date('2017-09-06T17:14:01.048Z'),
isLocalTime: false,
})).toBe('2017-09-06T17:14:01.048Z', 'UTC time');
expect(PushController.formatPushTime({
date: new Date('2007-04-05T12:30-02:00'),
isLocalTime: false
})).toBe('2007-04-05T14:30:00.000Z', 'Timezone offset');
const noTimezone = new Date('2017-09-06T17:14:01.048')
const expectedHour = 17 + noTimezone.getTimezoneOffset() / 60;
expect(PushController.formatPushTime({
date: noTimezone,
isLocalTime: true,
})).toBe(`2017-09-06T${expectedHour}:14:01.048`, 'No timezone');
expect(PushController.formatPushTime({
date: new Date('2017-09-06'),
isLocalTime: true
})).toBe('2017-09-06T00:00:00.000', 'YY-MM-DD');
});
});
describe('Scheduling pushes in local time', () => {
it('should preserve the push time', (done) => {
const auth = {isMaster: true};
const pushAdapter = {
send(body, installations) {
return successfulTransmissions(body, installations);
},
getValidPushTypes() {
return ["ios"];
}
};
const pushTime = '2017-09-06T17:14:01.048';
const expectedHour = 17 + new Date(pushTime).getTimezoneOffset() / 60;
reconfigureServer({
push: {adapter: pushAdapter},
scheduledPush: true
})
.then(() => {
const config = Config.get(Parse.applicationId);
return new Promise((resolve, reject) => {
const pushController = new PushController();
pushController.sendPush({
data: {
alert: "Hello World!",
badge: "Increment",
},
push_time: pushTime
}, {}, config, auth, resolve)
.catch(reject);
})
})
.then((pushStatusId) => {
const q = new Parse.Query('_PushStatus');
return q.get(pushStatusId, {useMasterKey: true});
})
.then((pushStatus) => {
expect(pushStatus.get('status')).toBe('scheduled');
expect(pushStatus.get('pushTime')).toBe(`2017-09-06T${expectedHour}:14:01.048`);
})
.then(done, done.fail);
});
});
describe('With expiration defined', () => {
const auth = {isMaster: true};
const pushController = new PushController();
let config = Config.get(Parse.applicationId);
const pushes = [];
const pushAdapter = {
send(body, installations) {
pushes.push(body);
return successfulTransmissions(body, installations);
},
getValidPushTypes() {
return ["ios"];
}
};
beforeEach((done) => {
reconfigureServer({
push: {adapter: pushAdapter},
})
.then(() => {
config = Config.get(Parse.applicationId);
})
.then(done, done.fail);
});
it('should throw if both expiration_time and expiration_interval are set', () => {
expect(() => pushController.sendPush({
expiration_time: '2017-09-25T13:21:20.841Z',
expiration_interval: 1000,
}, {}, config, auth)).toThrow()
});
it('should throw on invalid expiration_interval', () => {
expect(() => pushController.sendPush({
expiration_interval: -1
}, {}, config, auth)).toThrow();
expect(() => pushController.sendPush({
expiration_interval: '',
}, {}, config, auth)).toThrow();
expect(() => pushController.sendPush({
expiration_time: {},
}, {}, config, auth)).toThrow();
});
describe('For immediate pushes',() => {
it('should transform the expiration_interval into an absolute time', (done) => {
const now = new Date('2017-09-25T13:30:10.452Z');
reconfigureServer({
push: {adapter: pushAdapter},
})
.then(() =>
new Promise((resolve) => {
pushController.sendPush({
data: {
alert: 'immediate push',
},
expiration_interval: 20 * 60, // twenty minutes
}, {}, Config.get(Parse.applicationId), auth, resolve, now)
}))
.then((pushStatusId) => {
const p = new Parse.Object('_PushStatus');
p.id = pushStatusId;
return p.fetch({useMasterKey: true});
})
.then((pushStatus) => {
expect(pushStatus.get('expiry')).toBeDefined('expiry must be set');
expect(pushStatus.get('expiry'))
.toEqual(new Date('2017-09-25T13:50:10.452Z').valueOf());
expect(pushStatus.get('expiration_interval')).toBeDefined('expiration_interval must be defined');
expect(pushStatus.get('expiration_interval')).toBe(20 * 60);
})
.then(done, done.fail);
});
});
});
});