@@ -7,7 +7,11 @@ const fft = require('firebase-functions-test')(
77) ;
88const test = require ( 'ava' ) ;
99const { integrify } = require ( '../lib' ) ;
10- const { replaceReferencesWith, getPrimaryKey } = require ( '../lib/common' ) ;
10+ const {
11+ replaceReferencesWith,
12+ getPrimaryKey,
13+ isSubCollection,
14+ } = require ( '../lib/common' ) ;
1115const { getState, setState } = require ( './functions/stateMachine' ) ;
1216
1317const admin = require ( 'firebase-admin' ) ;
@@ -53,6 +57,8 @@ testsuites.forEach(testsuite => {
5357 } ) ;
5458 test ( `[${ name } ] test get primary key` , async t =>
5559 testPrimaryKey ( sut , t , name ) ) ;
60+ test ( `[${ name } ] test sub-collection check` , async t =>
61+ testSubCollection ( sut , t , name ) ) ;
5662 test ( `[${ name } ] test target collection parameter swap` , async t =>
5763 testTargetVariableSwap ( sut , t , name ) ) ;
5864 test ( `[${ name } ] test replicate attributes` , async t =>
@@ -69,6 +75,14 @@ testsuites.forEach(testsuite => {
6975 testDeleteMissingSourceCollectionKey ( sut , t , name ) ) ;
7076 test ( `[${ name } ] test delete with missing snapshot fields in target reference` , async t =>
7177 testDeleteMissingFieldsReferences ( sut , t , name ) ) ;
78+
79+ test ( `[${ name } ] test delete all sub-collections in target reference` , async t =>
80+ testDeleteAllSubCollections ( sut , t , name ) ) ;
81+ test ( `[${ name } ] test delete all sub-collections in target reference error` , async t =>
82+ testDeleteAllSubCollectionsError ( sut , t , name ) ) ;
83+
84+ test ( `[${ name } ] test delete missing arguments error` , async t =>
85+ testDeleteMissingArgumentsError ( sut , t , name ) ) ;
7286} ) ;
7387
7488async function testPrimaryKey ( sut , t , name ) {
@@ -94,6 +108,44 @@ async function testPrimaryKey(sut, t, name) {
94108 await t . pass ( ) ;
95109}
96110
111+ async function testSubCollection ( sut , t , name ) {
112+ // Test zero key
113+ let result = isSubCollection ( '' ) ;
114+ t . false ( result ) ;
115+
116+ // Test one key
117+ result = isSubCollection ( 'collection' ) ;
118+ t . false ( result ) ;
119+
120+ // Test two keys
121+ result = isSubCollection ( 'collection1/collection2' ) ;
122+ t . false ( result ) ;
123+
124+ // Test three keys
125+ result = isSubCollection ( 'collection1/collection2/collection3' ) ;
126+ t . true ( result ) ;
127+
128+ // Test four keys
129+ result = isSubCollection ( 'collection1/collection2/collection3/collection4' ) ;
130+ t . false ( result ) ;
131+
132+ // Test five keys
133+ result = isSubCollection (
134+ 'collection1/collection2/collection3/collection4/collection5'
135+ ) ;
136+ t . true ( result ) ;
137+
138+ // Check incorrect format of collection
139+ result = isSubCollection ( 'collection1//' ) ;
140+ t . false ( result ) ;
141+ result = isSubCollection ( '//collection1//' ) ;
142+ t . false ( result ) ;
143+ result = isSubCollection ( 'collection1///collection2///collection3' ) ;
144+ t . true ( result ) ;
145+
146+ await t . pass ( ) ;
147+ }
148+
97149async function testTargetVariableSwap ( sut , t , name ) {
98150 // test no fields
99151 let collectionId = makeid ( ) ;
@@ -517,6 +569,175 @@ async function testDeleteMissingFieldsReferences(sut, t, name) {
517569 t . pass ( ) ;
518570}
519571
572+ async function testDeleteAllSubCollections ( sut , t , name ) {
573+ // Create some docs referencing master doc
574+ const randomId = makeid ( ) ;
575+ const testId = makeid ( ) ;
576+ const nestedDocRef = db . collection ( 'somecoll' ) . doc ( testId ) ;
577+ await nestedDocRef . set ( {
578+ x : 1 ,
579+ } ) ;
580+ await nestedDocRef . collection ( 'detail2' ) . add ( {
581+ randomId : randomId ,
582+ } ) ;
583+ await nestedDocRef . collection ( 'detail3' ) . add ( {
584+ randomId : randomId ,
585+ } ) ;
586+ await assertQuerySizeEventually (
587+ db
588+ . collection ( 'somecoll' )
589+ . doc ( testId )
590+ . collection ( 'detail2' )
591+ . where ( 'randomId' , '==' , randomId ) ,
592+ 1
593+ ) ;
594+ await assertQuerySizeEventually (
595+ db
596+ . collection ( 'somecoll' )
597+ . doc ( testId )
598+ . collection ( 'detail3' )
599+ . where ( 'randomId' , '==' , randomId ) ,
600+ 1
601+ ) ;
602+
603+ // Trigger function to delete references
604+ const snap = fft . firestore . makeDocumentSnapshot (
605+ { testId } ,
606+ `master/${ randomId } `
607+ ) ;
608+ const wrapped = fft . wrap ( sut . deleteReferencesDeleteAllSubCollections ) ;
609+ setState ( {
610+ snap : null ,
611+ context : null ,
612+ } ) ;
613+ await wrapped ( snap , {
614+ params : {
615+ randomId : randomId ,
616+ testId : testId ,
617+ } ,
618+ } ) ;
619+
620+ // Assert pre-hook was called (only for rules-in-situ)
621+ if ( name === 'rules-in-situ' ) {
622+ const state = getState ( ) ;
623+ t . truthy ( state . snap ) ;
624+ t . truthy ( state . context ) ;
625+ t . is ( state . context . params . randomId , randomId ) ;
626+ }
627+
628+ // Assert referencing docs were deleted
629+ await assertQuerySizeEventually (
630+ db
631+ . collection ( 'somecoll' )
632+ . doc ( testId )
633+ . collection ( 'detail2' )
634+ . where ( 'randomId' , '==' , randomId ) ,
635+ 0
636+ ) ;
637+ await assertQuerySizeEventually (
638+ db
639+ . collection ( 'somecoll' )
640+ . doc ( testId )
641+ . collection ( 'detail3' )
642+ . where ( 'randomId' , '==' , randomId ) ,
643+ 1
644+ ) ;
645+
646+ t . pass ( ) ;
647+ }
648+
649+ async function testDeleteAllSubCollectionsError ( sut , t , name ) {
650+ // Create some docs referencing master doc
651+ const randomId = makeid ( ) ;
652+ const testId = makeid ( ) ;
653+ const nestedDocRef = db . collection ( 'somecoll' ) . doc ( testId ) ;
654+ await nestedDocRef . set ( {
655+ x : 1 ,
656+ } ) ;
657+ await nestedDocRef . collection ( 'detail2' ) . add ( {
658+ randomId : randomId ,
659+ } ) ;
660+ await assertQuerySizeEventually (
661+ db
662+ . collection ( 'somecoll' )
663+ . doc ( testId )
664+ . collection ( 'detail2' )
665+ . where ( 'randomId' , '==' , randomId ) ,
666+ 1
667+ ) ;
668+
669+ // Trigger function to delete references
670+ const snap = fft . firestore . makeDocumentSnapshot (
671+ {
672+ testId,
673+ } ,
674+ `master/${ randomId } `
675+ ) ;
676+ const wrapped = fft . wrap ( sut . deleteReferencesDeleteAllSubCollectionErrors ) ;
677+ setState ( {
678+ snap : null ,
679+ context : null ,
680+ } ) ;
681+ const error = await t . throwsAsync ( async ( ) => {
682+ await wrapped ( snap , {
683+ params : {
684+ randomId : randomId ,
685+ testId : testId ,
686+ } ,
687+ } ) ;
688+ } ) ;
689+ t . is (
690+ error . message ,
691+ `integrify: [master/details] is an invalid sub-collection`
692+ ) ;
693+
694+ // Assert pre-hook was called (only for rules-in-situ)
695+ if ( name === 'rules-in-situ' ) {
696+ const state = getState ( ) ;
697+ t . truthy ( state . snap ) ;
698+ t . truthy ( state . context ) ;
699+ t . is ( state . context . params . randomId , randomId ) ;
700+ }
701+
702+ // Assert referencing docs were deleted
703+ await assertQuerySizeEventually (
704+ db
705+ . collection ( 'somecoll' )
706+ . doc ( testId )
707+ . collection ( 'detail2' )
708+ . where ( 'randomId' , '==' , randomId ) ,
709+ 1
710+ ) ;
711+
712+ t . pass ( ) ;
713+ }
714+
715+ async function testDeleteMissingArgumentsError ( sut , t , name ) {
716+ // Create some docs referencing master doc
717+ const randomId = makeid ( ) ;
718+
719+ // Trigger function to delete references
720+ const snap = fft . firestore . makeDocumentSnapshot ( { } , `master/${ randomId } ` ) ;
721+ const wrapped = fft . wrap ( sut . deleteReferencesMissingArgumentsErrors ) ;
722+ setState ( {
723+ snap : null ,
724+ context : null ,
725+ } ) ;
726+ const error = await t . throwsAsync ( async ( ) => {
727+ await wrapped ( snap , {
728+ params : {
729+ randomId : randomId ,
730+ } ,
731+ } ) ;
732+ } ) ;
733+ t . is (
734+ error . message ,
735+ 'integrify: missing foreign key or set deleteAll to true'
736+ ) ;
737+
738+ t . pass ( ) ;
739+ }
740+
520741async function testMaintainCount ( sut , t ) {
521742 // Create an article to be favorited
522743 const articleId = makeid ( ) ;
0 commit comments