@@ -19,8 +19,10 @@ import { Query } from '../../../src/core/query';
19
19
import { deletedDoc , doc , filter , path } from '../../util/helpers' ;
20
20
21
21
import { TimerId } from '../../../src/util/async_queue' ;
22
+ import { Code } from '../../../src/util/error' ;
22
23
import { describeSpec , specTest } from './describe_spec' ;
23
24
import { client , spec } from './spec_builder' ;
25
+ import { RpcError } from './spec_rpc_error' ;
24
26
25
27
describeSpec ( 'Limbo Documents:' , [ ] , ( ) => {
26
28
specTest (
@@ -322,6 +324,67 @@ describeSpec('Limbo Documents:', [], () => {
322
324
}
323
325
) ;
324
326
327
+ specTest ( 'Failed limbo resolution removes document from view' , [ ] , ( ) => {
328
+ // This test reproduces a customer issue where a failed limbo resolution
329
+ // triggered an assert because we added a document to the cache with a
330
+ // read time of zero.
331
+ const filteredQuery = Query . atPath ( path ( 'collection' ) ) . addFilter (
332
+ filter ( 'matches' , '==' , true )
333
+ ) ;
334
+ const fullQuery = Query . atPath ( path ( 'collection' ) ) ;
335
+ const remoteDoc = doc ( 'collection/a' , 1000 , { matches : true } ) ;
336
+ const localDoc = doc (
337
+ 'collection/a' ,
338
+ 1000 ,
339
+ { matches : true , modified : true } ,
340
+ { hasLocalMutations : true }
341
+ ) ;
342
+ return (
343
+ spec ( )
344
+ . userListens ( filteredQuery )
345
+ . watchAcksFull ( filteredQuery , 1000 , remoteDoc )
346
+ . expectEvents ( filteredQuery , { added : [ remoteDoc ] } )
347
+ // We add a local mutation to prevent the document from getting garbage
348
+ // collected when we unlisten from the current query.
349
+ . userPatches ( 'collection/a' , { modified : true } )
350
+ . expectEvents ( filteredQuery , {
351
+ modified : [ localDoc ] ,
352
+ hasPendingWrites : true
353
+ } )
354
+ . userUnlistens ( filteredQuery )
355
+ // Start a new query, but don't include the document in the backend
356
+ // results (it might have been removed by another client).
357
+ . userListens ( fullQuery )
358
+ . expectEvents ( fullQuery , {
359
+ added : [ localDoc ] ,
360
+ hasPendingWrites : true ,
361
+ fromCache : true
362
+ } )
363
+ . watchAcksFull ( fullQuery , 1001 )
364
+ . expectEvents ( fullQuery , { hasPendingWrites : true } )
365
+ // Fail the write and remove the pending mutation. The document should
366
+ // now be in Limbo.
367
+ . failWrite (
368
+ 'collection/a' ,
369
+ new RpcError (
370
+ Code . FAILED_PRECONDITION ,
371
+ 'Document to update does not exist'
372
+ )
373
+ )
374
+ . expectEvents ( fullQuery , { modified : [ remoteDoc ] , fromCache : true } )
375
+ . expectLimboDocs ( remoteDoc . key )
376
+ // Fail the Limbo resolution which removes the document from the view.
377
+ // This is internally propagated as a NoDocument with
378
+ // SnapshotVersion.MIN and a read time of zero.
379
+ . watchRemoves (
380
+ Query . atPath ( path ( 'collection/a' ) ) ,
381
+ new RpcError ( Code . PERMISSION_DENIED , 'Permission denied' )
382
+ )
383
+ . expectEvents ( fullQuery , { removed : [ remoteDoc ] } )
384
+ . expectLimboDocs ( )
385
+ ) ;
386
+ } ) ;
387
+
325
388
specTest (
326
389
'Limbo docs are resolved by primary client' ,
327
390
[ 'multi-client' ] ,
0 commit comments