@@ -23,6 +23,7 @@ import {
2323 QueryDocumentSnapshot ,
2424 Transaction ,
2525 collection ,
26+ deleteDoc ,
2627 doc ,
2728 DocumentReference ,
2829 DocumentSnapshot ,
@@ -87,6 +88,16 @@ apiDescribe('Database transactions', (persistence: boolean) => {
8788 await transaction . get ( docRef ) ;
8889 }
8990
91+ enum FromDocumentType {
92+ // The operation will be performed on a document that exists.
93+ EXISTING = 'existing' ,
94+ // The operation will be performed on a document that has never existed.
95+ NON_EXISTENT = 'non_existent' ,
96+ // The operation will be performed on a document that existed, but was
97+ // deleted.
98+ DELETED = 'deleted'
99+ }
100+
90101 /**
91102 * Used for testing that all possible combinations of executing transactions
92103 * result in the desired document value or error.
@@ -101,16 +112,21 @@ apiDescribe('Database transactions', (persistence: boolean) => {
101112 constructor ( readonly db : Firestore ) { }
102113
103114 private docRef ! : DocumentReference ;
104- private fromExistingDoc : boolean = false ;
115+ private fromDocumentType : FromDocumentType = FromDocumentType . NON_EXISTENT ;
105116 private stages : TransactionStage [ ] = [ ] ;
106117
107118 withExistingDoc ( ) : this {
108- this . fromExistingDoc = true ;
119+ this . fromDocumentType = FromDocumentType . EXISTING ;
109120 return this ;
110121 }
111122
112123 withNonexistentDoc ( ) : this {
113- this . fromExistingDoc = false ;
124+ this . fromDocumentType = FromDocumentType . NON_EXISTENT ;
125+ return this ;
126+ }
127+
128+ withDeletedDoc ( ) : this {
129+ this . fromDocumentType = FromDocumentType . DELETED ;
114130 return this ;
115131 }
116132
@@ -176,8 +192,19 @@ apiDescribe('Database transactions', (persistence: boolean) => {
176192
177193 private async prepareDoc ( ) : Promise < void > {
178194 this . docRef = doc ( collection ( this . db , 'tester-docref' ) ) ;
179- if ( this . fromExistingDoc ) {
180- await setDoc ( this . docRef , { foo : 'bar0' } ) ;
195+ switch ( this . fromDocumentType ) {
196+ case FromDocumentType . EXISTING :
197+ await setDoc ( this . docRef , { foo : 'bar0' } ) ;
198+ break ;
199+ case FromDocumentType . NON_EXISTENT :
200+ // Nothing to do; document does not exist.
201+ break ;
202+ case FromDocumentType . DELETED :
203+ await setDoc ( this . docRef , { foo : 'bar0' } ) ;
204+ await deleteDoc ( this . docRef ) ;
205+ break ;
206+ default :
207+ throw new Error ( `invalid fromDocumentType: ${ this . fromDocumentType } ` ) ;
181208 }
182209 }
183210
@@ -289,6 +316,47 @@ apiDescribe('Database transactions', (persistence: boolean) => {
289316 } ) ;
290317 } ) ;
291318
319+ // This test is identical to the test above, except that withNonexistentDoc()
320+ // is replaced by withDeletedDoc(), to guard against regression of
321+ // https://github.com/firebase/firebase-js-sdk/issues/5871, where transactions
322+ // would incorrectly fail with FAILED_PRECONDITION when operations were
323+ // performed on a deleted document (rather than a non-existent document).
324+ it ( 'runs transactions after getting a deleted document' , async ( ) => {
325+ return withTestDb ( persistence , async db => {
326+ const tt = new TransactionTester ( db ) ;
327+
328+ await tt . withDeletedDoc ( ) . run ( get , delete1 , delete1 ) . expectNoDoc ( ) ;
329+ await tt
330+ . withDeletedDoc ( )
331+ . run ( get , delete1 , update2 )
332+ . expectError ( 'invalid-argument' ) ;
333+ await tt
334+ . withDeletedDoc ( )
335+ . run ( get , delete1 , set2 )
336+ . expectDoc ( { foo : 'bar2' } ) ;
337+
338+ await tt
339+ . withDeletedDoc ( )
340+ . run ( get , update1 , delete1 )
341+ . expectError ( 'invalid-argument' ) ;
342+ await tt
343+ . withDeletedDoc ( )
344+ . run ( get , update1 , update2 )
345+ . expectError ( 'invalid-argument' ) ;
346+ await tt
347+ . withDeletedDoc ( )
348+ . run ( get , update1 , set1 )
349+ . expectError ( 'invalid-argument' ) ;
350+
351+ await tt . withDeletedDoc ( ) . run ( get , set1 , delete1 ) . expectNoDoc ( ) ;
352+ await tt
353+ . withDeletedDoc ( )
354+ . run ( get , set1 , update2 )
355+ . expectDoc ( { foo : 'bar2' } ) ;
356+ await tt . withDeletedDoc ( ) . run ( get , set1 , set2 ) . expectDoc ( { foo : 'bar2' } ) ;
357+ } ) ;
358+ } ) ;
359+
292360 it ( 'runs transactions on existing document' , async ( ) => {
293361 return withTestDb ( persistence , async db => {
294362 const tt = new TransactionTester ( db ) ;
0 commit comments