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,327 +2,230 @@
const request = require('../lib/request'); const request = require('../lib/request');
const delayPromise = delay => { const pushCompleted = async pushId => {
return new Promise(resolve => { let result = await Parse.Push.getPushStatus(pushId);
setTimeout(resolve, delay); 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'];
},
};
const setup = function () {
const sendToInstallationSpy = jasmine.createSpy();
const pushAdapter = {
send: function (body, installations) {
const badge = body.data.badge;
const promises = installations.map(installation => {
sendToInstallationSpy(installation);
if (installation.deviceType == 'ios') {
expect(installation.badge).toEqual(badge);
expect(installation.originalBadge + 1).toEqual(installation.badge);
} else {
expect(installation.badge).toBeUndefined();
}
return Promise.resolve({
err: null,
device: installation,
transmitted: true,
});
});
return Promise.all(promises);
},
getValidPushTypes: function () {
return ['ios', 'android'];
},
};
return reconfigureServer({
appId: Parse.applicationId,
masterKey: Parse.masterKey,
serverURL: Parse.serverURL,
push: {
adapter: pushAdapter,
},
})
.then(() => {
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);
}
return Parse.Object.saveAll(installations);
})
.then(() => {
return {
sendToInstallationSpy,
};
});
}; };
describe('Parse.Push', () => { describe('Parse.Push', () => {
const setup = function () { it('should properly send push', async () => {
const sendToInstallationSpy = jasmine.createSpy(); const { sendToInstallationSpy } = await setup();
const pushStatusId = await Parse.Push.send({
const pushAdapter = { where: {
send: function (body, installations) { deviceType: 'ios',
const badge = body.data.badge;
const promises = installations.map(installation => {
sendToInstallationSpy(installation);
if (installation.deviceType == 'ios') {
expect(installation.badge).toEqual(badge);
expect(installation.originalBadge + 1).toEqual(installation.badge);
} else {
expect(installation.badge).toBeUndefined();
}
return Promise.resolve({
err: null,
device: installation,
transmitted: true,
});
});
return Promise.all(promises);
}, },
getValidPushTypes: function () { data: {
return ['ios', 'android']; badge: 'Increment',
alert: 'Hello world!',
}, },
};
return reconfigureServer({
appId: Parse.applicationId,
masterKey: Parse.masterKey,
serverURL: Parse.serverURL,
push: {
adapter: pushAdapter,
},
})
.then(() => {
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);
}
return Parse.Object.saveAll(installations);
})
.then(() => {
return {
sendToInstallationSpy,
};
})
.catch(err => {
console.error(err);
throw err;
});
};
it('should properly send push', done => {
return setup()
.then(({ sendToInstallationSpy }) => {
return Parse.Push.send(
{
where: {
deviceType: 'ios',
},
data: {
badge: 'Increment',
alert: 'Hello world!',
},
},
{ useMasterKey: true }
)
.then(() => {
return delayPromise(500);
})
.then(() => {
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(
{
where: {
deviceType: 'ios',
},
data: {
badge: 'increment',
alert: 'Hello world!',
},
},
{ useMasterKey: true }
);
})
.then(() => {
return delayPromise(500);
})
.then(() => {
done();
})
.catch(err => {
jfail(err);
done();
});
});
it('should not allow clients to query _PushStatus', done => {
setup()
.then(() =>
Parse.Push.send(
{
where: {
deviceType: 'ios',
},
data: {
badge: 'increment',
alert: 'Hello world!',
},
},
{ useMasterKey: true }
)
)
.then(() => delayPromise(500))
.then(() => {
request({
url: 'http://localhost:8378/1/classes/_PushStatus',
json: true,
headers: {
'X-Parse-Application-Id': 'test',
},
}).then(fail, 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(
{
where: {
deviceType: 'ios',
},
data: {
badge: 'increment',
alert: 'Hello world!',
},
},
{ useMasterKey: true }
)
)
.then(() => delayPromise(500)) // put a delay as we keep writing
.then(() => {
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(
{
where: {
deviceType: 'ios',
},
data: {
badge: 'increment',
alert: 'Hello world!',
},
},
{ useMasterKey: true }
);
})
.then(
() => {
fail('should not succeed');
},
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,
});
}); });
await pushCompleted(pushStatusId);
expect(sendToInstallationSpy.calls.count()).toEqual(10);
});
return Promise.all(promises); it('should properly send push with lowercaseIncrement', async () => {
}; await setup();
const pushStatusId = await Parse.Push.send({
where: {
deviceType: 'ios',
},
data: {
badge: 'increment',
alert: 'Hello world!',
},
});
await pushCompleted(pushStatusId);
});
const provideInstallations = function (num) { it('should not allow clients to query _PushStatus', async () => {
if (!num) { await setup();
num = 2; const pushStatusId = await Parse.Push.send({
where: {
deviceType: 'ios',
},
data: {
badge: 'increment',
alert: 'Hello world!',
},
});
await pushCompleted(pushStatusId);
try {
await request({
url: 'http://localhost:8378/1/classes/_PushStatus',
json: true,
headers: {
'X-Parse-Application-Id': 'test',
},
});
fail();
} catch (response) {
expect(response.data.error).toEqual('unauthorized');
} }
});
const installations = []; it('should allow master key to query _PushStatus', async () => {
while (installations.length !== num) { await setup();
// add Android installations const pushStatusId = await Parse.Push.send({
const installation = new Parse.Object('_Installation'); where: {
installation.set('installationId', 'installation_' + installations.length); deviceType: 'ios',
installation.set('deviceToken', 'device_token_' + installations.length); },
installation.set('deviceType', 'android'); data: {
installations.push(installation); badge: 'increment',
alert: 'Hello world!',
},
});
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',
},
});
const body = response.data;
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!"}');
});
it('should throw error if missing push configuration', async () => {
await reconfigureServer({ push: null });
try {
await Parse.Push.send({
where: {
deviceType: 'ios',
},
data: {
badge: 'increment',
alert: 'Hello world!',
},
});
fail();
} catch (err) {
expect(err.code).toEqual(Parse.Error.PUSH_MISCONFIGURED);
} }
});
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 * Verifies that _PushStatus cannot get stuck in a 'running' state
* Simulates a simple push where 1 installation is removed between _PushStatus * Simulates a simple push where 1 installation is removed between _PushStatus
* count being set and the pushes being sent * count being set and the pushes being sent
*/ */
it("does not get stuck with _PushStatus 'running' on 1 installation lost", done => { it("does not get stuck with _PushStatus 'running' on 1 installation lost", async () => {
reconfigureServer({ await reconfigureServer({
push: { adapter: losingAdapter }, push: { adapter: losingAdapter },
}) });
.then(() => { await Parse.Object.saveAll(provideInstallations());
return Parse.Object.saveAll(provideInstallations()); const pushStatusId = await Parse.Push.send({
}) data: { alert: 'We fixed our status!' },
.then(() => { where: { deviceType: 'android' },
return Parse.Push.send( });
{ await pushCompleted(pushStatusId);
data: { alert: 'We fixed our status!' }, const result = await Parse.Push.getPushStatus(pushStatusId);
where: { deviceType: 'android' }, expect(result.get('status')).toEqual('succeeded');
}, expect(result.get('numSent')).toEqual(1);
{ useMasterKey: true } expect(result.get('count')).toEqual(undefined);
);
})
.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];
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 * Simulates a simple push where 1 installation is added between _PushStatus
* count being set and the pushes being sent * 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(); const installations = provideInstallations();
// add 1 iOS installation which we will omit & add later on // add 1 iOS installation which we will omit & add later on
@@ -340,14 +243,13 @@ describe('Parse.Push', () => {
iOSInstallation.set('deviceType', 'ios'); iOSInstallation.set('deviceType', 'ios');
installations.push(iOSInstallation); installations.push(iOSInstallation);
reconfigureServer({ await reconfigureServer({
push: { push: {
adapter: { adapter: {
send: function (body, installations) { send: function (body, installations) {
// simulate having added an installation before this was called // simulate having added an installation before this was called
// thus invalidating our 'count' in _PushStatus // thus invalidating our 'count' in _PushStatus
installations.push(iOSInstallation); installations.push(iOSInstallation);
return successfulAny(body, installations); return successfulAny(body, installations);
}, },
getValidPushTypes: function () { getValidPushTypes: function () {
@@ -355,41 +257,17 @@ describe('Parse.Push', () => {
}, },
}, },
}, },
}) });
.then(() => { await Parse.Object.saveAll(installations);
return Parse.Object.saveAll(installations); const pushStatusId = await Parse.Push.send({
}) data: { alert: 'We fixed our status!' },
.then(() => { where: { deviceType: { $ne: 'random' } },
return Parse.Push.send( });
{ await pushCompleted(pushStatusId);
data: { alert: 'We fixed our status!' }, const result = await Parse.Push.getPushStatus(pushStatusId);
where: { deviceType: { $ne: 'random' } }, expect(result.get('status')).toEqual('succeeded');
}, expect(result.get('numSent')).toEqual(3);
{ useMasterKey: true } expect(result.get('count')).toEqual(undefined);
);
})
.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];
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, * Simulates an extended push, where some installations may be removed,
* resulting in a non-zero count * 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 devices = 1000;
const installations = provideInstallations(devices); const installations = provideInstallations(devices);
reconfigureServer({ await reconfigureServer({
push: { adapter: losingAdapter }, push: { adapter: losingAdapter },
}) });
.then(() => { await Parse.Object.saveAll(installations);
return Parse.Object.saveAll(installations); const pushStatusId = await Parse.Push.send({
}) data: { alert: 'We fixed our status!' },
.then(() => { where: { deviceType: 'android' },
return Parse.Push.send( });
{ await pushCompleted(pushStatusId);
data: { alert: 'We fixed our status!' }, const result = await Parse.Push.getPushStatus(pushStatusId);
where: { deviceType: 'android' }, expect(result.get('status')).toEqual('succeeded');
}, // expect # less than # of batches used, assuming each batch is 100 pushes
{ useMasterKey: true } expect(result.get('numSent')).toEqual(devices - devices / 100);
); expect(result.get('count')).toEqual(undefined);
})
.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];
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, * Simulates an extended push, where some installations may be added,
* resulting in a non-zero count * 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 devices = 1000;
const installations = provideInstallations(devices); const installations = provideInstallations(devices);
// add 1 iOS installation which we will omit & add later on // add 1 iOS installation which we will omit & add later on
const iOSInstallations = []; const iOSInstallations = [];
while (iOSInstallations.length !== devices / 100) { while (iOSInstallations.length !== devices / 100) {
const iOSInstallation = new Parse.Object('_Installation'); const iOSInstallation = new Parse.Object('_Installation');
iOSInstallation.set('installationId', 'installation_' + installations.length); iOSInstallation.set('installationId', 'installation_' + installations.length);
@@ -461,15 +314,13 @@ describe('Parse.Push', () => {
installations.push(iOSInstallation); installations.push(iOSInstallation);
iOSInstallations.push(iOSInstallation); iOSInstallations.push(iOSInstallation);
} }
await reconfigureServer({
reconfigureServer({
push: { push: {
adapter: { adapter: {
send: function (body, installations) { send: function (body, installations) {
// simulate having added an installation before this was called // simulate having added an installation before this was called
// thus invalidating our 'count' in _PushStatus // thus invalidating our 'count' in _PushStatus
installations.push(iOSInstallations.pop()); installations.push(iOSInstallations.pop());
return successfulAny(body, installations); return successfulAny(body, installations);
}, },
getValidPushTypes: function () { getValidPushTypes: function () {
@@ -477,41 +328,18 @@ describe('Parse.Push', () => {
}, },
}, },
}, },
}) });
.then(() => { await Parse.Object.saveAll(installations);
return Parse.Object.saveAll(installations);
}) const pushStatusId = await Parse.Push.send({
.then(() => { data: { alert: 'We fixed our status!' },
return Parse.Push.send( where: { deviceType: { $ne: 'random' } },
{ });
data: { alert: 'We fixed our status!' }, await pushCompleted(pushStatusId);
where: { deviceType: { $ne: 'random' } }, const result = await Parse.Push.getPushStatus(pushStatusId);
}, expect(result.get('status')).toEqual('succeeded');
{ useMasterKey: true } // 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);
.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];
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();
});
}); });
}); });

File diff suppressed because it is too large Load Diff