@@ -4,13 +4,30 @@ import {
4
4
getFirestore ,
5
5
Timestamp ,
6
6
CollectionReference ,
7
+ DocumentReference ,
7
8
} from "firebase-admin/firestore" ;
9
+ import { getMessaging } from "firebase-admin/messaging" ;
8
10
import functions from "firebase-functions-test" ;
9
11
10
12
import * as myFunctions from "../index" ;
11
13
import { UserRole } from "../../../src/backend/UserRoles" ;
12
- import { place } from "../../../src/backend/FirestoreModels.gen" ;
13
- import { NotificationEvent } from "../../../src/backend/NotificationEvents" ;
14
+ import {
15
+ keg ,
16
+ personsIndex ,
17
+ place ,
18
+ } from "../../../src/backend/FirestoreModels.gen" ;
19
+ import {
20
+ FreeTableMessage ,
21
+ FreshKegMessage ,
22
+ NotificationEvent ,
23
+ UpdateDeviceTokenMessage ,
24
+ } from "../../../src/backend/NotificationEvents" ;
25
+ import {
26
+ getNotificationTokensDoc ,
27
+ getPersonsIndexDoc ,
28
+ getPlacesCollection ,
29
+ NotificationTokensDocument ,
30
+ } from "../helpers" ;
14
31
15
32
const testEnv = functions (
16
33
{
@@ -27,12 +44,12 @@ afterAll(() => {
27
44
describe ( `deletePlaceSubcollection` , ( ) => {
28
45
const addPlace = async ( opts : { placeId : string ; withKegs : boolean } ) => {
29
46
const db = getFirestore ( ) ;
30
- const placeCollection = db . collection ( "places" ) ;
47
+ const placeCollection = getPlacesCollection ( db ) ;
31
48
const placeDoc = placeCollection . doc ( opts . placeId ) ;
32
49
await placeDoc . set ( {
33
50
createdAt : Timestamp . now ( ) ,
34
51
name : "Test Place" ,
35
- } ) ;
52
+ } as any ) ;
36
53
const result = {
37
54
kegsCollection : undefined as CollectionReference < any > | undefined ,
38
55
personsCollection : placeDoc . collection ( "persons" ) ,
@@ -220,4 +237,212 @@ describe(`truncateUserInDb`, () => {
220
237
undefined
221
238
) ;
222
239
} ) ;
240
+
241
+ it ( `should also delete the user from the document with notification tokens` , async ( ) => {
242
+ const userUid = `beatle` ;
243
+ const db = getFirestore ( ) ;
244
+ const notificationTokensDoc = getNotificationTokensDoc ( db ) ;
245
+ await notificationTokensDoc . set ( {
246
+ tokens : { [ userUid ] : `registrationToken` } ,
247
+ } ) ;
248
+ const notificationTokensBefore = (
249
+ await notificationTokensDoc . get ( )
250
+ ) . data ( ) ! ;
251
+ expect ( notificationTokensBefore . tokens [ userUid ] ) . toBe ( `registrationToken` ) ;
252
+ const wrapped = testEnv . wrap ( myFunctions . truncateUserInDb ) ;
253
+ await wrapped ( { uid : userUid } ) ;
254
+ const notificationTokensAfter = ( await notificationTokensDoc . get ( ) ) . data ( ) ! ;
255
+ expect ( notificationTokensAfter . tokens [ userUid ] ) . toBeUndefined ( ) ;
256
+ } ) ;
257
+ } ) ;
258
+
259
+ describe ( `updateNotificationToken` , ( ) => {
260
+ const db = getFirestore ( ) ;
261
+ const notificationTokensDoc = getNotificationTokensDoc ( db ) ;
262
+ it ( `should add a user to notification collection if not there` , async ( ) => {
263
+ const userUid = `eleanor` ;
264
+ const deviceToken = `testingDeviceToken` ;
265
+ await notificationTokensDoc . set ( { tokens : { } } ) ;
266
+ const wrapped = testEnv . wrap ( myFunctions . updateNotificationToken ) ;
267
+ await wrapped ( {
268
+ auth : { uid : userUid } ,
269
+ data : {
270
+ deviceToken,
271
+ } satisfies UpdateDeviceTokenMessage ,
272
+ } ) ;
273
+ const notificationTokens = ( await notificationTokensDoc . get ( ) ) . data ( ) ! ;
274
+ expect ( notificationTokens . tokens [ userUid ] ) . toBe ( deviceToken ) ;
275
+ } ) ;
276
+ it ( `should update the user token if already there` , async ( ) => {
277
+ const uid = `condor` ;
278
+ const oldDeviceToken = `oldDeviceToken` ;
279
+ const newDeviceToken = `newDeviceToken` ;
280
+ await notificationTokensDoc . set ( { tokens : { [ uid ] : oldDeviceToken } } ) ;
281
+ const wrapped = testEnv . wrap ( myFunctions . updateNotificationToken ) ;
282
+ await wrapped ( {
283
+ auth : { uid } ,
284
+ data : { deviceToken : newDeviceToken } satisfies UpdateDeviceTokenMessage ,
285
+ } ) ;
286
+ const notificationTokens = ( await notificationTokensDoc . get ( ) ) . data ( ) ! ;
287
+ expect ( notificationTokens . tokens [ uid ] ) . toBe ( newDeviceToken ) ;
288
+ } ) ;
289
+ } ) ;
290
+
291
+ describe ( `dispatchNotification` , ( ) => {
292
+ beforeEach ( ( ) => {
293
+ jest . clearAllMocks ( ) ;
294
+ } ) ;
295
+ it ( `should use jest automock for messaging` , ( ) => {
296
+ const messaging = getMessaging ( ) ;
297
+ expect ( messaging . sendEachForMulticast ) . toHaveBeenCalledTimes ( 0 ) ;
298
+ } ) ;
299
+
300
+ const createPlace = async ( opts : {
301
+ placeId : string ;
302
+ keg ?: {
303
+ beer : string ;
304
+ serial : number ;
305
+ } ;
306
+ users : Array < {
307
+ accountTuple : [ UserRole , NotificationEvent ] ;
308
+ name : string ;
309
+ registrationToken : string ;
310
+ uid : string ;
311
+ } > ;
312
+ } ) => {
313
+ const db = getFirestore ( ) ;
314
+ const placeCollection = getPlacesCollection ( db ) ;
315
+ const placeDoc = placeCollection . doc ( opts . placeId ) ;
316
+ const accounts : place [ "accounts" ] = opts . users . reduce ( ( acc , u ) => {
317
+ acc [ u . uid ] = u . accountTuple ;
318
+ return acc ;
319
+ } , { } as place [ "accounts" ] ) ;
320
+ await placeDoc . set ( {
321
+ accounts : accounts ,
322
+ createdAt : Timestamp . now ( ) ,
323
+ name : "Test Place" ,
324
+ } as any ) ;
325
+ const personsIndexDoc = getPersonsIndexDoc ( placeDoc ) ;
326
+ const personsIndexAll : personsIndex [ "all" ] = opts . users . reduce ( ( acc , u ) => {
327
+ acc [ u . uid ] = [ u . name , Timestamp . now ( ) , 0 , u . uid ] as any ;
328
+ return acc ;
329
+ } , { } as personsIndex [ "all" ] ) ;
330
+ await personsIndexDoc . set ( {
331
+ all : personsIndexAll ,
332
+ } ) ;
333
+ const notificationTokensDoc = getNotificationTokensDoc ( db ) ;
334
+ const tokens : NotificationTokensDocument [ "tokens" ] = opts . users . reduce (
335
+ ( acc , u ) => {
336
+ acc [ u . uid ] = u . registrationToken ;
337
+ return acc ;
338
+ } ,
339
+ { } as NotificationTokensDocument [ "tokens" ]
340
+ ) ;
341
+ await notificationTokensDoc . set ( { tokens } ) ;
342
+ let kegDoc : DocumentReference < keg > | undefined ;
343
+ if ( opts . keg ) {
344
+ const kegsCollection = placeDoc . collection (
345
+ "kegs"
346
+ ) as CollectionReference < keg > ;
347
+ kegDoc = await kegsCollection . add ( {
348
+ beer : opts . keg . beer ,
349
+ createdAt : Timestamp . now ( ) ,
350
+ serial : opts . keg . serial ,
351
+ } as any ) ;
352
+ }
353
+ return { kegDoc, placeDoc, personsIndexDoc, notificationTokensDoc } ;
354
+ } ;
355
+
356
+ it ( `should dispatch a notification for freeTable message to subscribed users` , async ( ) => {
357
+ const { placeDoc } = await createPlace ( {
358
+ placeId : `testDispatchNotification_freeTable` ,
359
+ users : [
360
+ {
361
+ accountTuple : [ UserRole . owner , NotificationEvent . freeTable ] ,
362
+ name : `Alice` ,
363
+ registrationToken : `registrationToken1` ,
364
+ uid : `user1` ,
365
+ } ,
366
+ {
367
+ accountTuple : [
368
+ UserRole . staff ,
369
+ NotificationEvent . freeTable | NotificationEvent . freshKeg ,
370
+ ] ,
371
+ name : `Bob` ,
372
+ registrationToken : `registrationToken2` ,
373
+ uid : `user2` ,
374
+ } ,
375
+ {
376
+ accountTuple : [ UserRole . admin , NotificationEvent . unsubscribed ] ,
377
+ name : `Dan` ,
378
+ registrationToken : `registrationToken3` ,
379
+ uid : `user3` ,
380
+ } ,
381
+ ] ,
382
+ } ) ;
383
+ const wrapped = testEnv . wrap ( myFunctions . dispatchNotification ) ;
384
+ await wrapped ( {
385
+ auth : { uid : `user1` } ,
386
+ data : {
387
+ place : placeDoc . path ,
388
+ tag : NotificationEvent . freeTable ,
389
+ } satisfies FreeTableMessage ,
390
+ } ) ;
391
+ const messaging = getMessaging ( ) ;
392
+ expect ( messaging . sendEachForMulticast ) . toHaveBeenCalledTimes ( 1 ) ;
393
+ const callArg = ( messaging . sendEachForMulticast as any ) . mock . calls [ 0 ] [ 0 ] ;
394
+ expect ( callArg . tokens ) . toEqual ( [
395
+ `registrationToken1` ,
396
+ `registrationToken2` ,
397
+ ] ) ;
398
+ expect ( callArg . notification . body . startsWith ( `Alice` ) ) . toBe ( true ) ;
399
+ } ) ;
400
+ it ( `should dispatch notification for freshKeg message to subscribed users` , async ( ) => {
401
+ const { kegDoc } = await createPlace ( {
402
+ keg : {
403
+ beer : `Test Beer` ,
404
+ serial : 1 ,
405
+ } ,
406
+ placeId : `testDispatchNotification_freshKeg` ,
407
+ users : [
408
+ {
409
+ accountTuple : [ UserRole . owner , NotificationEvent . freshKeg ] ,
410
+ name : `Alice` ,
411
+ registrationToken : `registrationToken1` ,
412
+ uid : `user1` ,
413
+ } ,
414
+ {
415
+ accountTuple : [
416
+ UserRole . staff ,
417
+ NotificationEvent . freeTable | NotificationEvent . freshKeg ,
418
+ ] ,
419
+ name : `Bob` ,
420
+ registrationToken : `registrationToken2` ,
421
+ uid : `user2` ,
422
+ } ,
423
+ {
424
+ accountTuple : [ UserRole . admin , NotificationEvent . unsubscribed ] ,
425
+ name : `Dan` ,
426
+ registrationToken : `registrationToken3` ,
427
+ uid : `user3` ,
428
+ } ,
429
+ ] ,
430
+ } ) ;
431
+ const wrapped = testEnv . wrap ( myFunctions . dispatchNotification ) ;
432
+ await wrapped ( {
433
+ auth : { uid : `user1` } ,
434
+ data : {
435
+ keg : kegDoc ! . path ,
436
+ tag : NotificationEvent . freshKeg ,
437
+ } satisfies FreshKegMessage ,
438
+ } ) ;
439
+ const messaging = getMessaging ( ) ;
440
+ expect ( messaging . sendEachForMulticast ) . toHaveBeenCalledTimes ( 1 ) ;
441
+ const callArg = ( messaging . sendEachForMulticast as any ) . mock . calls [ 0 ] [ 0 ] ;
442
+ expect ( callArg . tokens ) . toEqual ( [
443
+ `registrationToken1` ,
444
+ `registrationToken2` ,
445
+ ] ) ;
446
+ expect ( callArg . notification . body . includes ( `Test Beer` ) ) . toBe ( true ) ;
447
+ } ) ;
223
448
} ) ;
0 commit comments