@@ -203,4 +203,261 @@ describe('Parse.Push', () => {
203203 done ( ) ;
204204 } ) ;
205205 } ) ;
206+
207+ const successfulAny = function ( body , installations ) {
208+ const promises = installations . map ( ( device ) => {
209+ return Promise . resolve ( {
210+ transmitted : true ,
211+ device : device ,
212+ } )
213+ } ) ;
214+
215+ return Promise . all ( promises ) ;
216+ } ;
217+
218+ const provideInstallations = function ( num ) {
219+ if ( ! num ) {
220+ num = 2 ;
221+ }
222+
223+ const installations = [ ] ;
224+ while ( installations . length !== num ) {
225+ // add Android installations
226+ const installation = new Parse . Object ( "_Installation" ) ;
227+ installation . set ( "installationId" , "installation_" + installations . length ) ;
228+ installation . set ( "deviceToken" , "device_token_" + installations . length ) ;
229+ installation . set ( "deviceType" , "android" ) ;
230+ installations . push ( installation ) ;
231+ }
232+
233+ return installations ;
234+ } ;
235+
236+ const losingAdapter = {
237+ send : function ( body , installations ) {
238+ // simulate having lost an installation before this was called
239+ // thus invalidating our 'count' in _PushStatus
240+ installations . pop ( ) ;
241+
242+ return successfulAny ( body , installations ) ;
243+ } ,
244+ getValidPushTypes : function ( ) {
245+ return [ "android" ] ;
246+ }
247+ } ;
248+
249+ /**
250+ * Verifies that _PushStatus cannot get stuck in a 'running' state
251+ * Simulates a simple push where 1 installation is removed between _PushStatus
252+ * count being set and the pushes being sent
253+ */
254+ it ( 'does not get stuck with _PushStatus \'running\' on 1 installation lost' , ( done ) => {
255+ reconfigureServer ( {
256+ push : { adapter : losingAdapter }
257+ } ) . then ( ( ) => {
258+ return Parse . Object . saveAll ( provideInstallations ( ) ) ;
259+ } ) . then ( ( ) => {
260+ return Parse . Push . send (
261+ {
262+ data : { alert : "We fixed our status!" } ,
263+ where : { deviceType : 'android' }
264+ } ,
265+ { useMasterKey : true }
266+ ) ;
267+ } ) . then ( ( ) => {
268+ // it is enqueued so it can take time
269+ return new Promise ( ( resolve ) => {
270+ setTimeout ( ( ) => {
271+ resolve ( ) ;
272+ } , 1000 ) ;
273+ } ) ;
274+ } ) . then ( ( ) => {
275+ // query for push status
276+ const query = new Parse . Query ( '_PushStatus' ) ;
277+ return query . find ( { useMasterKey : true } ) ;
278+ } ) . then ( ( results ) => {
279+ // verify status is NOT broken
280+ expect ( results . length ) . toBe ( 1 ) ;
281+ const result = results [ 0 ] ;
282+ expect ( result . get ( 'status' ) ) . toEqual ( 'succeeded' ) ;
283+ expect ( result . get ( 'numSent' ) ) . toEqual ( 1 ) ;
284+ expect ( result . get ( 'count' ) ) . toEqual ( undefined ) ;
285+ done ( ) ;
286+ } ) ;
287+ } ) ;
288+
289+ /**
290+ * Verifies that _PushStatus cannot get stuck in a 'running' state
291+ * Simulates a simple push where 1 installation is added between _PushStatus
292+ * count being set and the pushes being sent
293+ */
294+ it ( 'does not get stuck with _PushStatus \'running\' on 1 installation added' , ( done ) => {
295+ const installations = provideInstallations ( ) ;
296+
297+ // add 1 iOS installation which we will omit & add later on
298+ const iOSInstallation = new Parse . Object ( "_Installation" ) ;
299+ iOSInstallation . set ( "installationId" , "installation_" + installations . length ) ;
300+ iOSInstallation . set ( "deviceToken" , "device_token_" + installations . length ) ;
301+ iOSInstallation . set ( "deviceType" , "ios" ) ;
302+ installations . push ( iOSInstallation ) ;
303+
304+ reconfigureServer ( {
305+ push : {
306+ adapter : {
307+ send : function ( body , installations ) {
308+ // simulate having added an installation before this was called
309+ // thus invalidating our 'count' in _PushStatus
310+ installations . push ( iOSInstallation ) ;
311+
312+ return successfulAny ( body , installations ) ;
313+ } ,
314+ getValidPushTypes : function ( ) {
315+ return [ "android" ] ;
316+ }
317+ }
318+ }
319+ } ) . then ( ( ) => {
320+ return Parse . Object . saveAll ( installations ) ;
321+ } ) . then ( ( ) => {
322+ return Parse . Push . send (
323+ {
324+ data : { alert : "We fixed our status!" } ,
325+ where : { deviceType : { '$ne' : 'random' } }
326+ } ,
327+ { useMasterKey : true }
328+ ) ;
329+ } ) . then ( ( ) => {
330+ // it is enqueued so it can take time
331+ return new Promise ( ( resolve ) => {
332+ setTimeout ( ( ) => {
333+ resolve ( ) ;
334+ } , 1000 ) ;
335+ } ) ;
336+ } ) . then ( ( ) => {
337+ // query for push status
338+ const query = new Parse . Query ( '_PushStatus' ) ;
339+ return query . find ( { useMasterKey : true } ) ;
340+ } ) . then ( ( results ) => {
341+ // verify status is NOT broken
342+ expect ( results . length ) . toBe ( 1 ) ;
343+ const result = results [ 0 ] ;
344+ expect ( result . get ( 'status' ) ) . toEqual ( 'succeeded' ) ;
345+ expect ( result . get ( 'numSent' ) ) . toEqual ( 3 ) ;
346+ expect ( result . get ( 'count' ) ) . toEqual ( undefined ) ;
347+ done ( ) ;
348+ } ) ;
349+ } ) ;
350+
351+ /**
352+ * Verifies that _PushStatus cannot get stuck in a 'running' state
353+ * Simulates an extended push, where some installations may be removed,
354+ * resulting in a non-zero count
355+ */
356+ it ( 'does not get stuck with _PushStatus \'running\' on many installations removed' , ( done ) => {
357+ const devices = 1000 ;
358+ const installations = provideInstallations ( devices ) ;
359+
360+ reconfigureServer ( {
361+ push : { adapter : losingAdapter }
362+ } ) . then ( ( ) => {
363+ return Parse . Object . saveAll ( installations ) ;
364+ } ) . then ( ( ) => {
365+ return Parse . Push . send (
366+ {
367+ data : { alert : "We fixed our status!" } ,
368+ where : { deviceType : 'android' }
369+ } ,
370+ { useMasterKey : true }
371+ ) ;
372+ } ) . then ( ( ) => {
373+ // it is enqueued so it can take time
374+ return new Promise ( ( resolve ) => {
375+ setTimeout ( ( ) => {
376+ resolve ( ) ;
377+ } , 1000 ) ;
378+ } ) ;
379+ } ) . then ( ( ) => {
380+ // query for push status
381+ const query = new Parse . Query ( '_PushStatus' ) ;
382+ return query . find ( { useMasterKey : true } ) ;
383+ } ) . then ( ( results ) => {
384+ // verify status is NOT broken
385+ expect ( results . length ) . toBe ( 1 ) ;
386+ const result = results [ 0 ] ;
387+ expect ( result . get ( 'status' ) ) . toEqual ( 'succeeded' ) ;
388+ // expect # less than # of batches used, assuming each batch is 100 pushes
389+ expect ( result . get ( 'numSent' ) ) . toEqual ( devices - ( devices / 100 ) ) ;
390+ expect ( result . get ( 'count' ) ) . toEqual ( undefined ) ;
391+ done ( ) ;
392+ } ) ;
393+ } ) ;
394+
395+ /**
396+ * Verifies that _PushStatus cannot get stuck in a 'running' state
397+ * Simulates an extended push, where some installations may be added,
398+ * resulting in a non-zero count
399+ */
400+ it ( 'does not get stuck with _PushStatus \'running\' on many installations added' , ( done ) => {
401+ const devices = 1000 ;
402+ const installations = provideInstallations ( devices ) ;
403+
404+ // add 1 iOS installation which we will omit & add later on
405+ const iOSInstallations = [ ] ;
406+
407+ while ( iOSInstallations . length !== ( devices / 100 ) ) {
408+ const iOSInstallation = new Parse . Object ( "_Installation" ) ;
409+ iOSInstallation . set ( "installationId" , "installation_" + installations . length ) ;
410+ iOSInstallation . set ( "deviceToken" , "device_token_" + installations . length ) ;
411+ iOSInstallation . set ( "deviceType" , "ios" ) ;
412+ installations . push ( iOSInstallation ) ;
413+ iOSInstallations . push ( iOSInstallation ) ;
414+ }
415+
416+ reconfigureServer ( {
417+ push : {
418+ adapter : {
419+ send : function ( body , installations ) {
420+ // simulate having added an installation before this was called
421+ // thus invalidating our 'count' in _PushStatus
422+ installations . push ( iOSInstallations . pop ( ) ) ;
423+
424+ return successfulAny ( body , installations ) ;
425+ } ,
426+ getValidPushTypes : function ( ) {
427+ return [ "android" ] ;
428+ }
429+ }
430+ }
431+ } ) . then ( ( ) => {
432+ return Parse . Object . saveAll ( installations ) ;
433+ } ) . then ( ( ) => {
434+ return Parse . Push . send (
435+ {
436+ data : { alert : "We fixed our status!" } ,
437+ where : { deviceType : { '$ne' : 'random' } }
438+ } ,
439+ { useMasterKey : true }
440+ ) ;
441+ } ) . then ( ( ) => {
442+ // it is enqueued so it can take time
443+ return new Promise ( ( resolve ) => {
444+ setTimeout ( ( ) => {
445+ resolve ( ) ;
446+ } , 1000 ) ;
447+ } ) ;
448+ } ) . then ( ( ) => {
449+ // query for push status
450+ const query = new Parse . Query ( '_PushStatus' ) ;
451+ return query . find ( { useMasterKey : true } ) ;
452+ } ) . then ( ( results ) => {
453+ // verify status is NOT broken
454+ expect ( results . length ) . toBe ( 1 ) ;
455+ const result = results [ 0 ] ;
456+ expect ( result . get ( 'status' ) ) . toEqual ( 'succeeded' ) ;
457+ // expect # less than # of batches used, assuming each batch is 100 pushes
458+ expect ( result . get ( 'numSent' ) ) . toEqual ( devices + ( devices / 100 ) ) ;
459+ expect ( result . get ( 'count' ) ) . toEqual ( undefined ) ;
460+ done ( ) ;
461+ } ) ;
462+ } ) ;
206463} ) ;
0 commit comments