@@ -20,7 +20,7 @@ import { client, spec } from './spec_builder';
20
20
import { TimerId } from '../../../src/util/async_queue' ;
21
21
import { Query } from '../../../src/core/query' ;
22
22
import { Code } from '../../../src/util/error' ;
23
- import { doc , filter , path } from '../../util/helpers' ;
23
+ import { deletedDoc , doc , filter , path } from '../../util/helpers' ;
24
24
import { RpcError } from './spec_rpc_error' ;
25
25
26
26
describeSpec ( 'Persistence Recovery' , [ 'no-ios' , 'no-android' ] , ( ) => {
@@ -124,24 +124,34 @@ describeSpec('Persistence Recovery', ['no-ios', 'no-android'], () => {
124
124
) ;
125
125
126
126
specTest ( 'Recovers when write cannot be persisted' , [ ] , ( ) => {
127
- return spec ( )
128
- . userSets ( 'collection/key1' , { foo : 'a' } )
129
- . expectNumOutstandingWrites ( 1 )
130
- . failDatabaseTransactions ( {
131
- 'Locally write mutations' : true ,
132
- notifyLocalViewChanges : true ,
133
- 'Get next mutation batch' : true ,
134
- 'Get last stream token' : true
135
- } )
136
- . userSets ( 'collection/key2' , { bar : 'b' } )
137
- . expectUserCallbacks ( { rejected : [ 'collection/key2' ] } )
138
- . recoverDatabase ( )
139
- . expectNumOutstandingWrites ( 1 )
140
- . userSets ( 'collection/key3' , { baz : 'c' } )
141
- . expectNumOutstandingWrites ( 2 )
142
- . writeAcks ( 'collection/key1' , 1 )
143
- . writeAcks ( 'collection/key3' , 2 )
144
- . expectNumOutstandingWrites ( 0 ) ;
127
+ return (
128
+ spec ( )
129
+ . userSets ( 'collection/key1' , { foo : 'a' } )
130
+ . expectNumOutstandingWrites ( 1 )
131
+ // We fail the write if we cannot persist the local mutation (via
132
+ // 'Locally write mutations').
133
+ . failDatabaseTransactions ( {
134
+ 'Locally write mutations' : true
135
+ } )
136
+ . userSets ( 'collection/key2' , { bar : 'b' } )
137
+ . expectUserCallbacks ( { rejected : [ 'collection/key2' ] } )
138
+ // The write is considered successful if we can persist the local mutation
139
+ // but fail to update view assignments (via 'notifyLocalViewChanges').
140
+ . failDatabaseTransactions ( {
141
+ 'Locally write mutations' : false ,
142
+ notifyLocalViewChanges : true ,
143
+ 'Get next mutation batch' : false
144
+ } )
145
+ . userSets ( 'collection/key3' , { bar : 'b' } )
146
+ . recoverDatabase ( )
147
+ . expectNumOutstandingWrites ( 2 )
148
+ . userSets ( 'collection/key4' , { baz : 'c' } )
149
+ . expectNumOutstandingWrites ( 3 )
150
+ . writeAcks ( 'collection/key1' , 1 )
151
+ . writeAcks ( 'collection/key3' , 2 )
152
+ . writeAcks ( 'collection/key4' , 3 )
153
+ . expectNumOutstandingWrites ( 0 )
154
+ ) ;
145
155
} ) ;
146
156
147
157
specTest ( 'Does not surface non-persisted writes' , [ ] , ( ) => {
@@ -188,6 +198,82 @@ describeSpec('Persistence Recovery', ['no-ios', 'no-android'], () => {
188
198
. expectEvents ( query , { metadata : [ doc1 , doc3 ] } ) ;
189
199
} ) ;
190
200
201
+ specTest (
202
+ 'Surfaces local documents if notifyLocalViewChanges fails' ,
203
+ [ ] ,
204
+ ( ) => {
205
+ const query = Query . atPath ( path ( 'collection' ) ) ;
206
+ const doc1Local = doc (
207
+ 'collection/key1' ,
208
+ 0 ,
209
+ { foo : 'a' } ,
210
+ { hasLocalMutations : true }
211
+ ) ;
212
+ const doc1 = doc ( 'collection/key1' , 1 , { foo : 'a' } ) ;
213
+ const doc2 = doc ( 'collection/key2' , 2 , { foo : 'b' } ) ;
214
+ return spec ( )
215
+ . userListens ( query )
216
+ . failDatabaseTransactions ( {
217
+ 'Locally write mutations' : false ,
218
+ notifyLocalViewChanges : true ,
219
+ 'Get next mutation batch' : false ,
220
+ 'Set last stream token' : false
221
+ } )
222
+ . userSets ( 'collection/key1' , { foo : 'a' } )
223
+ . expectEvents ( query , {
224
+ added : [ doc1Local ] ,
225
+ fromCache : true ,
226
+ hasPendingWrites : true
227
+ } )
228
+ . recoverDatabase ( )
229
+ . runTimer ( TimerId . AsyncQueueRetry )
230
+ . writeAcks ( 'collection/key1' , 1 )
231
+ . failDatabaseTransactions ( {
232
+ 'Apply remote event' : false ,
233
+ notifyLocalViewChanges : true ,
234
+ 'Get last remote snapshot version' : false
235
+ } )
236
+ . watchAcksFull ( query , 1000 , doc1 , doc2 )
237
+ . expectEvents ( query , {
238
+ metadata : [ doc1 ] ,
239
+ added : [ doc2 ]
240
+ } ) ;
241
+ }
242
+ ) ;
243
+
244
+ specTest (
245
+ 'Excludes documents from future queries even if notifyLocalViewChanges fails' ,
246
+ [ ] ,
247
+ ( ) => {
248
+ const query = Query . atPath ( path ( 'collection' ) ) ;
249
+ const doc1 = doc ( 'collection/key1' , 1000 , { foo : 'a' } ) ;
250
+ const deletedDoc1 = deletedDoc ( 'collection/key1' , 2000 ) ;
251
+ return (
252
+ spec ( )
253
+ . withGCEnabled ( false )
254
+ . userListens ( query )
255
+ . watchAcksFull ( query , 1000 , doc1 )
256
+ . expectEvents ( query , {
257
+ added : [ doc1 ]
258
+ } )
259
+ . failDatabaseTransactions ( {
260
+ 'Apply remote event' : false ,
261
+ notifyLocalViewChanges : true ,
262
+ 'Get last remote snapshot version' : false
263
+ } )
264
+ . watchSends ( { removed : [ query ] } , deletedDoc1 )
265
+ . watchSnapshots ( 2000 )
266
+ . expectEvents ( query , {
267
+ removed : [ doc1 ]
268
+ } )
269
+ . recoverDatabase ( )
270
+ . userUnlistens ( query )
271
+ // No event since the document was removed
272
+ . userListens ( query , 'resume-token-1000' )
273
+ ) ;
274
+ }
275
+ ) ;
276
+
191
277
specTest ( 'Fails targets that cannot be allocated' , [ ] , ( ) => {
192
278
const query1 = Query . atPath ( path ( 'collection1' ) ) ;
193
279
const query2 = Query . atPath ( path ( 'collection2' ) ) ;
0 commit comments