@@ -23,6 +23,7 @@ import {
23
23
QueryDocumentSnapshot ,
24
24
Transaction ,
25
25
collection ,
26
+ deleteDoc ,
26
27
doc ,
27
28
DocumentReference ,
28
29
DocumentSnapshot ,
@@ -87,6 +88,16 @@ apiDescribe('Database transactions', (persistence: boolean) => {
87
88
await transaction . get ( docRef ) ;
88
89
}
89
90
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
+
90
101
/**
91
102
* Used for testing that all possible combinations of executing transactions
92
103
* result in the desired document value or error.
@@ -101,16 +112,21 @@ apiDescribe('Database transactions', (persistence: boolean) => {
101
112
constructor ( readonly db : Firestore ) { }
102
113
103
114
private docRef ! : DocumentReference ;
104
- private fromExistingDoc : boolean = false ;
115
+ private fromDocumentType : FromDocumentType = FromDocumentType . NON_EXISTENT ;
105
116
private stages : TransactionStage [ ] = [ ] ;
106
117
107
118
withExistingDoc ( ) : this {
108
- this . fromExistingDoc = true ;
119
+ this . fromDocumentType = FromDocumentType . EXISTING ;
109
120
return this ;
110
121
}
111
122
112
123
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 ;
114
130
return this ;
115
131
}
116
132
@@ -176,8 +192,19 @@ apiDescribe('Database transactions', (persistence: boolean) => {
176
192
177
193
private async prepareDoc ( ) : Promise < void > {
178
194
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 } ` ) ;
181
208
}
182
209
}
183
210
@@ -289,6 +316,47 @@ apiDescribe('Database transactions', (persistence: boolean) => {
289
316
} ) ;
290
317
} ) ;
291
318
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
+
292
360
it ( 'runs transactions on existing document' , async ( ) => {
293
361
return withTestDb ( persistence , async db => {
294
362
const tt = new TransactionTester ( db ) ;
0 commit comments