Fix Parse.Push flaky tests (#7263)

* Fix Push Notification Flaky Tests

* handle all tests
This commit is contained in:
Diamond Lewis
2021-03-12 17:50:13 -06:00
committed by GitHub
parent 687f4b7cf2
commit 8b0e8cd02c
2 changed files with 598 additions and 1056 deletions

View File

@@ -2,13 +2,55 @@
const request = require('../lib/request');
const delayPromise = delay => {
return new Promise(resolve => {
setTimeout(resolve, delay);
});
const pushCompleted = async pushId => {
let result = await Parse.Push.getPushStatus(pushId);
while (!(result && result.get('status') === 'succeeded')) {
result = await Parse.Push.getPushStatus(pushId);
}
};
const successfulAny = function (body, installations) {
const promises = installations.map(device => {
return Promise.resolve({
transmitted: true,
device: device,
});
});
return Promise.all(promises);
};
const provideInstallations = function (num) {
if (!num) {
num = 2;
}
const installations = [];
while (installations.length !== num) {
// add Android installations
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);
}
return installations;
};
const losingAdapter = {
send: function (body, installations) {
// simulate having lost an installation before this was called
// thus invalidating our 'count' in _PushStatus
installations.pop();
return successfulAny(body, installations);
},
getValidPushTypes: function () {
return ['android'];
},
};
describe('Parse.Push', () => {
const setup = function () {
const sendToInstallationSpy = jasmine.createSpy();
@@ -62,19 +104,13 @@ describe('Parse.Push', () => {
return {
sendToInstallationSpy,
};
})
.catch(err => {
console.error(err);
throw err;
});
};
it('should properly send push', done => {
return setup()
.then(({ sendToInstallationSpy }) => {
return Parse.Push.send(
{
describe('Parse.Push', () => {
it('should properly send push', async () => {
const { sendToInstallationSpy } = await setup();
const pushStatusId = await Parse.Push.send({
where: {
deviceType: 'ios',
},
@@ -82,30 +118,14 @@ describe('Parse.Push', () => {
badge: 'Increment',
alert: 'Hello world!',
},
},
{ useMasterKey: true }
)
.then(() => {
return delayPromise(500);
})
.then(() => {
});
await pushCompleted(pushStatusId);
expect(sendToInstallationSpy.calls.count()).toEqual(10);
});
})
.then(() => {
done();
})
.catch(err => {
jfail(err);
done();
});
});
it('should properly send push with lowercaseIncrement', done => {
return setup()
.then(() => {
return Parse.Push.send(
{
it('should properly send push with lowercaseIncrement', async () => {
await setup();
const pushStatusId = await Parse.Push.send({
where: {
deviceType: 'ios',
},
@@ -113,27 +133,13 @@ describe('Parse.Push', () => {
badge: 'increment',
alert: 'Hello world!',
},
},
{ useMasterKey: true }
);
})
.then(() => {
return delayPromise(500);
})
.then(() => {
done();
})
.catch(err => {
jfail(err);
done();
});
await pushCompleted(pushStatusId);
});
it('should not allow clients to query _PushStatus', done => {
setup()
.then(() =>
Parse.Push.send(
{
it('should not allow clients to query _PushStatus', async () => {
await setup();
const pushStatusId = await Parse.Push.send({
where: {
deviceType: 'ios',
},
@@ -141,34 +147,25 @@ describe('Parse.Push', () => {
badge: 'increment',
alert: 'Hello world!',
},
},
{ useMasterKey: true }
)
)
.then(() => delayPromise(500))
.then(() => {
request({
});
await pushCompleted(pushStatusId);
try {
await request({
url: 'http://localhost:8378/1/classes/_PushStatus',
json: true,
headers: {
'X-Parse-Application-Id': 'test',
},
}).then(fail, response => {
});
fail();
} catch (response) {
expect(response.data.error).toEqual('unauthorized');
done();
});
})
.catch(err => {
jfail(err);
done();
});
}
});
it('should allow master key to query _PushStatus', done => {
setup()
.then(() =>
Parse.Push.send(
{
it('should allow master key to query _PushStatus', async () => {
await setup();
const pushStatusId = await Parse.Push.send({
where: {
deviceType: 'ios',
},
@@ -176,42 +173,26 @@ describe('Parse.Push', () => {
badge: 'increment',
alert: 'Hello world!',
},
},
{ useMasterKey: true }
)
)
.then(() => delayPromise(500)) // put a delay as we keep writing
.then(() => {
request({
});
await pushCompleted(pushStatusId);
const response = await request({
url: 'http://localhost:8378/1/classes/_PushStatus',
json: true,
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-Master-Key': 'test',
},
}).then(response => {
});
const body = response.data;
try {
expect(body.results.length).toEqual(1);
expect(body.results[0].query).toEqual('{"deviceType":"ios"}');
expect(body.results[0].payload).toEqual('{"badge":"increment","alert":"Hello world!"}');
} catch (e) {
jfail(e);
}
done();
});
})
.catch(err => {
jfail(err);
done();
});
});
it('should throw error if missing push configuration', done => {
reconfigureServer({ push: null })
.then(() => {
return Parse.Push.send(
{
it('should throw error if missing push configuration', async () => {
await reconfigureServer({ push: null });
try {
await Parse.Push.send({
where: {
deviceType: 'ios',
},
@@ -219,110 +200,32 @@ describe('Parse.Push', () => {
badge: 'increment',
alert: 'Hello world!',
},
},
{ useMasterKey: true }
);
})
.then(
() => {
fail('should not succeed');
},
err => {
});
fail();
} catch (err) {
expect(err.code).toEqual(Parse.Error.PUSH_MISCONFIGURED);
done();
}
)
.catch(err => {
jfail(err);
done();
});
});
const successfulAny = function (body, installations) {
const promises = installations.map(device => {
return Promise.resolve({
transmitted: true,
device: device,
});
});
return Promise.all(promises);
};
const provideInstallations = function (num) {
if (!num) {
num = 2;
}
const installations = [];
while (installations.length !== num) {
// add Android installations
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);
}
return installations;
};
const losingAdapter = {
send: function (body, installations) {
// simulate having lost an installation before this was called
// thus invalidating our 'count' in _PushStatus
installations.pop();
return successfulAny(body, installations);
},
getValidPushTypes: function () {
return ['android'];
},
};
/**
* Verifies that _PushStatus cannot get stuck in a 'running' state
* Simulates a simple push where 1 installation is removed between _PushStatus
* count being set and the pushes being sent
*/
it("does not get stuck with _PushStatus 'running' on 1 installation lost", done => {
reconfigureServer({
it("does not get stuck with _PushStatus 'running' on 1 installation lost", async () => {
await reconfigureServer({
push: { adapter: losingAdapter },
})
.then(() => {
return Parse.Object.saveAll(provideInstallations());
})
.then(() => {
return Parse.Push.send(
{
});
await Parse.Object.saveAll(provideInstallations());
const pushStatusId = await Parse.Push.send({
data: { alert: 'We fixed our status!' },
where: { deviceType: 'android' },
},
{ useMasterKey: true }
);
})
.then(() => {
// it is enqueued so it can take time
return new Promise(resolve => {
setTimeout(() => {
resolve();
}, 1000);
});
})
.then(() => {
// query for push status
const query = new Parse.Query('_PushStatus');
return query.find({ useMasterKey: true });
})
.then(results => {
// verify status is NOT broken
expect(results.length).toBe(1);
const result = results[0];
await pushCompleted(pushStatusId);
const result = await Parse.Push.getPushStatus(pushStatusId);
expect(result.get('status')).toEqual('succeeded');
expect(result.get('numSent')).toEqual(1);
expect(result.get('count')).toEqual(undefined);
done();
});
});
/**
@@ -330,7 +233,7 @@ describe('Parse.Push', () => {
* Simulates a simple push where 1 installation is added between _PushStatus
* count being set and the pushes being sent
*/
it("does not get stuck with _PushStatus 'running' on 1 installation added", done => {
it("does not get stuck with _PushStatus 'running' on 1 installation added", async () => {
const installations = provideInstallations();
// add 1 iOS installation which we will omit & add later on
@@ -340,14 +243,13 @@ describe('Parse.Push', () => {
iOSInstallation.set('deviceType', 'ios');
installations.push(iOSInstallation);
reconfigureServer({
await reconfigureServer({
push: {
adapter: {
send: function (body, installations) {
// simulate having added an installation before this was called
// thus invalidating our 'count' in _PushStatus
installations.push(iOSInstallation);
return successfulAny(body, installations);
},
getValidPushTypes: function () {
@@ -355,41 +257,17 @@ describe('Parse.Push', () => {
},
},
},
})
.then(() => {
return Parse.Object.saveAll(installations);
})
.then(() => {
return Parse.Push.send(
{
});
await Parse.Object.saveAll(installations);
const pushStatusId = await Parse.Push.send({
data: { alert: 'We fixed our status!' },
where: { deviceType: { $ne: 'random' } },
},
{ useMasterKey: true }
);
})
.then(() => {
// it is enqueued so it can take time
return new Promise(resolve => {
setTimeout(() => {
resolve();
}, 1000);
});
})
.then(() => {
// query for push status
const query = new Parse.Query('_PushStatus');
return query.find({ useMasterKey: true });
})
.then(results => {
// verify status is NOT broken
expect(results.length).toBe(1);
const result = results[0];
await pushCompleted(pushStatusId);
const result = await Parse.Push.getPushStatus(pushStatusId);
expect(result.get('status')).toEqual('succeeded');
expect(result.get('numSent')).toEqual(3);
expect(result.get('count')).toEqual(undefined);
done();
});
});
/**
@@ -397,48 +275,24 @@ describe('Parse.Push', () => {
* Simulates an extended push, where some installations may be removed,
* resulting in a non-zero count
*/
it("does not get stuck with _PushStatus 'running' on many installations removed", done => {
it("does not get stuck with _PushStatus 'running' on many installations removed", async () => {
const devices = 1000;
const installations = provideInstallations(devices);
reconfigureServer({
await reconfigureServer({
push: { adapter: losingAdapter },
})
.then(() => {
return Parse.Object.saveAll(installations);
})
.then(() => {
return Parse.Push.send(
{
});
await Parse.Object.saveAll(installations);
const pushStatusId = await Parse.Push.send({
data: { alert: 'We fixed our status!' },
where: { deviceType: 'android' },
},
{ useMasterKey: true }
);
})
.then(() => {
// it is enqueued so it can take time
return new Promise(resolve => {
setTimeout(() => {
resolve();
}, 1000);
});
})
.then(() => {
// query for push status
const query = new Parse.Query('_PushStatus');
return query.find({ useMasterKey: true });
})
.then(results => {
// verify status is NOT broken
expect(results.length).toBe(1);
const result = results[0];
await pushCompleted(pushStatusId);
const result = await Parse.Push.getPushStatus(pushStatusId);
expect(result.get('status')).toEqual('succeeded');
// expect # less than # of batches used, assuming each batch is 100 pushes
expect(result.get('numSent')).toEqual(devices - devices / 100);
expect(result.get('count')).toEqual(undefined);
done();
});
});
/**
@@ -446,13 +300,12 @@ describe('Parse.Push', () => {
* Simulates an extended push, where some installations may be added,
* resulting in a non-zero count
*/
it("does not get stuck with _PushStatus 'running' on many installations added", done => {
it("does not get stuck with _PushStatus 'running' on many installations added", async () => {
const devices = 1000;
const installations = provideInstallations(devices);
// add 1 iOS installation which we will omit & add later on
const iOSInstallations = [];
while (iOSInstallations.length !== devices / 100) {
const iOSInstallation = new Parse.Object('_Installation');
iOSInstallation.set('installationId', 'installation_' + installations.length);
@@ -461,15 +314,13 @@ describe('Parse.Push', () => {
installations.push(iOSInstallation);
iOSInstallations.push(iOSInstallation);
}
reconfigureServer({
await reconfigureServer({
push: {
adapter: {
send: function (body, installations) {
// simulate having added an installation before this was called
// thus invalidating our 'count' in _PushStatus
installations.push(iOSInstallations.pop());
return successfulAny(body, installations);
},
getValidPushTypes: function () {
@@ -477,41 +328,18 @@ describe('Parse.Push', () => {
},
},
},
})
.then(() => {
return Parse.Object.saveAll(installations);
})
.then(() => {
return Parse.Push.send(
{
});
await Parse.Object.saveAll(installations);
const pushStatusId = await Parse.Push.send({
data: { alert: 'We fixed our status!' },
where: { deviceType: { $ne: 'random' } },
},
{ useMasterKey: true }
);
})
.then(() => {
// it is enqueued so it can take time
return new Promise(resolve => {
setTimeout(() => {
resolve();
}, 1000);
});
})
.then(() => {
// query for push status
const query = new Parse.Query('_PushStatus');
return query.find({ useMasterKey: true });
})
.then(results => {
// verify status is NOT broken
expect(results.length).toBe(1);
const result = results[0];
await pushCompleted(pushStatusId);
const result = await Parse.Push.getPushStatus(pushStatusId);
expect(result.get('status')).toEqual('succeeded');
// expect # less than # of batches used, assuming each batch is 100 pushes
expect(result.get('numSent')).toEqual(devices + devices / 100);
expect(result.get('count')).toEqual(undefined);
done();
});
});
});

View File

@@ -26,6 +26,20 @@ const successfulIOS = function (body, installations) {
return Promise.all(promises);
};
const pushCompleted = async pushId => {
let result = await Parse.Push.getPushStatus(pushId);
while (!(result && result.get('status') === 'succeeded')) {
result = await Parse.Push.getPushStatus(pushId);
}
};
const sendPush = (body, where, config, auth, now) => {
const pushController = new PushController();
return new Promise((resolve, reject) => {
pushController.sendPush(body, where, config, auth, resolve, now).catch(reject);
});
};
describe('PushController', () => {
it('can validate device type when no device type is set', done => {
// Make query condition
@@ -151,7 +165,7 @@ describe('PushController', () => {
done();
});
it('properly increment badges', done => {
it('properly increment badges', async () => {
const pushAdapter = {
send: function (body, installations) {
const badge = body.data.badge;
@@ -195,55 +209,28 @@ describe('PushController', () => {
const auth = {
isMaster: true,
};
const pushController = new PushController();
reconfigureServer({
await 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(() => {
await Parse.Object.saveAll(installations);
const pushStatusId = await sendPush(payload, {}, config, auth);
await pushCompleted(pushStatusId);
// 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];
const pushStatus = await Parse.Push.getPushStatus(pushStatusId);
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 => {
const results = await query.find({ useMasterKey: true });
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 increment badges by more than 1', done => {
it('properly increment badges by more than 1', async () => {
const pushAdapter = {
send: function (body, installations) {
const badge = body.data.badge;
@@ -287,55 +274,25 @@ describe('PushController', () => {
const auth = {
isMaster: true,
};
const pushController = new PushController();
reconfigureServer({
await 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];
await Parse.Object.saveAll(installations);
const pushStatusId = await sendPush(payload, {}, config, auth);
await pushCompleted(pushStatusId);
const pushStatus = await Parse.Push.getPushStatus(pushStatusId);
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 => {
const results = await query.find({ useMasterKey: true });
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')) + 3);
}
done();
})
.catch(err => {
jfail(err);
done();
});
});
it('properly set badges to 1', done => {
it('properly set badges to 1', async () => {
const pushAdapter = {
send: function (body, installations) {
const badge = body.data.badge;
@@ -371,55 +328,26 @@ describe('PushController', () => {
const auth = {
isMaster: true,
};
const pushController = new PushController();
reconfigureServer({
await 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];
await Parse.Object.saveAll(installations);
const pushStatusId = await sendPush(payload, {}, config, auth);
await pushCompleted(pushStatusId);
const pushStatus = await Parse.Push.getPushStatus(pushStatusId);
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 => {
const results = await query.find({ useMasterKey: true });
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 => {
it('properly set badges to 1 with complex query #2903 #3022', async () => {
const payload = {
data: {
alert: 'Hello World!',
@@ -456,46 +384,26 @@ describe('PushController', () => {
const auth = {
isMaster: true,
};
const pushController = new PushController();
reconfigureServer({
push: {
adapter: pushAdapter,
},
})
.then(() => {
return Parse.Object.saveAll(installations);
})
.then(installations => {
await reconfigureServer({
push: { adapter: pushAdapter },
});
await Parse.Object.saveAll(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(() => {
const pushStatusId = await sendPush(payload, where, config, auth);
await pushCompleted(pushStatusId);
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();
});
const results = await query.find({ useMasterKey: true });
expect(results.length).toBe(5);
});
it('properly creates _PushStatus', done => {
it('properly creates _PushStatus', async () => {
const pushStatusAfterSave = {
handler: function () {},
};
@@ -539,31 +447,13 @@ describe('PushController', () => {
const auth = {
isMaster: true,
};
const pushController = new PushController();
reconfigureServer({
await 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];
await Parse.Object.saveAll(installations);
const pushStatusId = await sendPush(payload, {}, config, auth);
await pushCompleted(pushStatusId);
const result = await Parse.Push.getPushStatus(pushStatusId);
expect(result.createdAt instanceof Date).toBe(true);
expect(result.updatedAt instanceof Date).toBe(true);
expect(result.id.length).toBe(10);
@@ -580,46 +470,55 @@ describe('PushController', () => {
expect(result.get('failedPerType')).toEqual({
android: 5, // android
});
try {
// Try to get it without masterKey
const query = new Parse.Query('_PushStatus');
return query.find();
})
.catch(error => {
await query.find();
fail();
} 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 => {
let pendingCount = 0;
let runningCount = 0;
let succeedCount = 0;
allCalls.forEach((call, index) => {
expect(call.args.length).toBe(1);
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({
const pushStatus = getPushStatus(index);
if (pushStatus.get('status') === 'pending') {
pendingCount += 1;
}
if (pushStatus.get('status') === 'running') {
runningCount += 1;
}
if (pushStatus.get('status') === 'succeeded') {
succeedCount += 1;
}
if (pushStatus.get('status') === 'running' && pushStatus.get('numSent') > 0) {
expect(pushStatus.get('numSent')).toBe(10);
expect(pushStatus.get('numFailed')).toBe(5);
expect(pushStatus.get('failedPerType')).toEqual({
android: 5,
});
expect(getPushStatus(2).get('sentPerType')).toEqual({
expect(pushStatus.get('sentPerType')).toEqual({
ios: 10,
});
expect(getPushStatus(3).get('status')).toBe('succeeded');
})
.then(done)
.catch(done.fail);
}
});
expect(pendingCount).toBe(1);
expect(runningCount).toBe(2);
expect(succeedCount).toBe(1);
});
it('properly creates _PushStatus without serverURL', done => {
it('properly creates _PushStatus without serverURL', async () => {
const pushStatusAfterSave = {
handler: function () {},
};
@@ -651,36 +550,22 @@ describe('PushController', () => {
const auth = {
isMaster: true,
};
const pushController = new PushController();
return installation
.save()
.then(() => {
return reconfigureServer({
await installation.save();
await reconfigureServer({
serverURL: 'http://localhost:8378/', // server with borked URL
push: { adapter: pushAdapter },
});
})
.then(() => {
return pushController.sendPush(payload, {}, config, auth);
})
.then(() => {
const pushStatusId = await sendPush(payload, {}, config, auth);
// it is enqueued so it can take time
return new Promise(resolve => {
await 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);
const result = await Parse.Push.getPushStatus(pushStatusId);
expect(result).toBeDefined();
await pushCompleted(pushStatusId);
});
it('should properly report failures in _PushStatus', done => {
@@ -696,6 +581,7 @@ describe('PushController', () => {
return ['ios'];
},
};
// $ins is invalid query
const where = {
channels: {
$ins: ['Giants', 'Mets'],
@@ -733,7 +619,7 @@ describe('PushController', () => {
});
});
it('should support full RESTQuery for increment', done => {
it('should support full RESTQuery for increment', async () => {
const payload = {
data: {
alert: 'Hello World!',
@@ -759,12 +645,9 @@ describe('PushController', () => {
$in: ['device_token_0', 'device_token_1', 'device_token_2'],
},
};
const pushController = new PushController();
reconfigureServer({
await reconfigureServer({
push: { adapter: pushAdapter },
})
.then(() => {
});
const installations = [];
while (installations.length != 5) {
const installation = new Parse.Object('_Installation');
@@ -775,36 +658,14 @@ describe('PushController', () => {
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];
await Parse.Object.saveAll(installations);
const pushStatusId = await sendPush(payload, where, config, auth);
await pushCompleted(pushStatusId);
const pushStatus = await Parse.Push.getPushStatus(pushStatusId);
expect(pushStatus.get('numSent')).toBe(3);
done();
})
.catch(err => {
jfail(err);
done();
});
});
it('should support object type for alert', done => {
it('should support object type for alert', async () => {
const payload = {
data: {
alert: {
@@ -826,16 +687,12 @@ describe('PushController', () => {
const auth = {
isMaster: true,
};
const where = {
deviceType: 'ios',
};
const pushController = new PushController();
reconfigureServer({
await reconfigureServer({
push: { adapter: pushAdapter },
})
.then(() => {
});
const installations = [];
while (installations.length != 5) {
const installation = new Parse.Object('_Installation');
@@ -846,33 +703,11 @@ describe('PushController', () => {
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];
await Parse.Object.saveAll(installations);
const pushStatusId = await sendPush(payload, where, config, auth);
await pushCompleted(pushStatusId);
const pushStatus = await Parse.Push.getPushStatus(pushStatusId);
expect(pushStatus.get('numSent')).toBe(5);
done();
})
.catch(() => {
fail('should not fail');
done();
});
});
it('should flatten', () => {
@@ -1012,7 +847,6 @@ describe('PushController', () => {
return ['ios'];
},
};
const pushController = new PushController();
const payload = {
data: {
@@ -1020,7 +854,6 @@ describe('PushController', () => {
},
push_time: new Date().getTime() / 1000,
};
const installations = [];
while (installations.length != 10) {
const installation = new Parse.Object('_Installation');
@@ -1031,7 +864,6 @@ describe('PushController', () => {
installation.set('deviceType', 'ios');
installations.push(installation);
}
reconfigureServer({
push: { adapter: pushAdapter },
scheduledPush: true,
@@ -1056,7 +888,7 @@ describe('PushController', () => {
.catch(done.err);
});
it('should not enqueue push when device token is not set', done => {
it('should not enqueue push when device token is not set', async () => {
const auth = {
isMaster: true,
};
@@ -1079,15 +911,12 @@ describe('PushController', () => {
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');
@@ -1098,7 +927,6 @@ describe('PushController', () => {
installation.set('deviceType', 'ios');
installations.push(installation);
}
while (installations.length != 15) {
const installation = new Parse.Object('_Installation');
installation.set('installationId', 'installation_' + installations.length);
@@ -1107,36 +935,19 @@ describe('PushController', () => {
installation.set('deviceType', 'ios');
installations.push(installation);
}
reconfigureServer({
await 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];
await Parse.Object.saveAll(installations);
const pushStatusId = await sendPush(payload, {}, config, auth);
await pushCompleted(pushStatusId);
const pushStatus = await Parse.Push.getPushStatus(pushStatusId);
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 => {
it('should not mark the _PushStatus as failed when audience has no deviceToken', async () => {
const auth = {
isMaster: true,
};
@@ -1159,15 +970,12 @@ describe('PushController', () => {
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');
@@ -1177,36 +985,18 @@ describe('PushController', () => {
installation.set('deviceType', 'ios');
installations.push(installation);
}
reconfigureServer({
await 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);
await Parse.Object.saveAll(installations);
const pushStatusId = await sendPush(payload, {}, config, auth);
await pushCompleted(pushStatusId);
const pushStatus = await Parse.Push.getPushStatus(pushStatusId);
expect(pushStatus.get('status')).toBe('succeeded');
done();
});
})
.catch(err => {
console.error(err);
fail('should not fail');
done();
});
});
it('should support localized payload data', done => {
it('should support localized payload data', async () => {
const payload = {
data: {
alert: 'Hello!',
@@ -1214,7 +1004,6 @@ describe('PushController', () => {
'alert-es': 'Ola',
},
};
const pushAdapter = {
send: function (body, installations) {
return successfulTransmissions(body, installations);
@@ -1223,21 +1012,13 @@ describe('PushController', () => {
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');
@@ -1251,20 +1032,15 @@ describe('PushController', () => {
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);
spyOn(pushAdapter, 'send').and.callThrough();
await reconfigureServer({
push: { adapter: pushAdapter },
});
})
.then(() => {
await Parse.Object.saveAll(installations);
const pushStatusId = await sendPush(payload, where, config, auth);
await pushCompleted(pushStatusId);
expect(pushAdapter.send.calls.count()).toBe(2);
const firstCall = pushAdapter.send.calls.first();
expect(firstCall.args[0].data).toEqual({
@@ -1278,12 +1054,9 @@ describe('PushController', () => {
});
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 => {
it('should update audiences', async () => {
const pushAdapter = {
send: function (body, installations) {
return successfulTransmissions(body, installations);
@@ -1297,20 +1070,12 @@ describe('PushController', () => {
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');
@@ -1321,9 +1086,12 @@ describe('PushController', () => {
installation.set('deviceType', 'ios');
installations.push(installation);
}
return Parse.Object.saveAll(installations);
})
.then(() => {
spyOn(pushAdapter, 'send').and.callThrough();
await reconfigureServer({
push: { adapter: pushAdapter },
});
await Parse.Object.saveAll(installations);
// Create an audience
const query = new Parse.Query('_Audience');
query.descending('createdAt');
@@ -1340,49 +1108,30 @@ describe('PushController', () => {
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(() => {
await Parse.Object.saveAll(audience);
await query.find({ useMasterKey: true }).then(parseResults);
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(() => {
const pushStatusId = await sendPush(body, where, config, auth);
await pushCompleted(pushStatusId);
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);
const audienceQuery = new Parse.Query('_Audience');
audienceQuery.equalTo('objectId', audienceId);
const results = await audienceQuery.find({ useMasterKey: true });
expect(results[0].get('query')).toBe(JSON.stringify(where));
expect(results[0].get('timesUsed')).toBe(timesUsed + 1);
expect(results[0].get('lastUsed')).not.toBeLessThan(now);
});
describe('pushTimeHasTimezoneComponent', () => {
@@ -1446,7 +1195,7 @@ describe('PushController', () => {
});
describe('Scheduling pushes in local time', () => {
it('should preserve the push time', done => {
it('should preserve the push time', async () => {
const auth = { isMaster: true };
const pushAdapter = {
send(body, installations) {
@@ -1456,7 +1205,6 @@ describe('PushController', () => {
return ['ios'];
},
};
const pushTime = '2017-09-06T17:14:01.048';
let expectedHour = 17 + new Date(pushTime).getTimezoneOffset() / 60;
let day = '06';
@@ -1464,43 +1212,24 @@ describe('PushController', () => {
expectedHour = expectedHour - 24;
day = '07';
}
reconfigureServer({
push: { adapter: pushAdapter },
scheduledPush: true,
})
.then(() => {
const config = Config.get(Parse.applicationId);
return new Promise((resolve, reject) => {
const pushController = new PushController();
pushController
.sendPush(
{
const payload = {
data: {
alert: 'Hello World!',
badge: 'Increment',
},
push_time: pushTime,
},
{},
config,
auth,
resolve
)
.catch(reject);
};
await reconfigureServer({
push: { adapter: pushAdapter },
scheduledPush: true,
});
})
.then(pushStatusId => {
const q = new Parse.Query('_PushStatus');
return q.get(pushStatusId, { useMasterKey: true });
})
.then(pushStatus => {
const config = Config.get(Parse.applicationId);
const pushStatusId = await sendPush(payload, {}, config, auth);
const pushStatus = await Parse.Push.getPushStatus(pushStatusId);
expect(pushStatus.get('status')).toBe('scheduled');
expect(pushStatus.get('pushTime')).toBe(
`2017-09-${day}T${expectedHour.toString().padStart(2, '0')}:14:01.048`
);
})
.then(done, done.fail);
});
});
@@ -1579,47 +1308,32 @@ describe('PushController', () => {
});
describe('For immediate pushes', () => {
it('should transform the expiration_interval into an absolute time', done => {
it('should transform the expiration_interval into an absolute time', async () => {
const now = new Date('2017-09-25T13:30:10.452Z');
reconfigureServer({
push: { adapter: pushAdapter },
})
.then(
() =>
new Promise(resolve => {
pushController.sendPush(
{
const payload = {
data: {
alert: 'immediate push',
},
expiration_interval: 20 * 60, // twenty minutes
},
};
await reconfigureServer({
push: { adapter: pushAdapter },
});
const pushStatusId = await sendPush(
payload,
{},
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 => {
const pushStatus = await Parse.Push.getPushStatus(pushStatusId);
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('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);
});
});
});