Skip to content

Commit 012e3a5

Browse files
Add test for NoDocument assert (#2205)
1 parent b4ee745 commit 012e3a5

File tree

1 file changed

+63
-0
lines changed

1 file changed

+63
-0
lines changed

packages/firestore/test/unit/specs/limbo_spec.test.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@ import { Query } from '../../../src/core/query';
1919
import { deletedDoc, doc, filter, path } from '../../util/helpers';
2020

2121
import { TimerId } from '../../../src/util/async_queue';
22+
import { Code } from '../../../src/util/error';
2223
import { describeSpec, specTest } from './describe_spec';
2324
import { client, spec } from './spec_builder';
25+
import { RpcError } from './spec_rpc_error';
2426

2527
describeSpec('Limbo Documents:', [], () => {
2628
specTest(
@@ -322,6 +324,67 @@ describeSpec('Limbo Documents:', [], () => {
322324
}
323325
);
324326

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+
325388
specTest(
326389
'Limbo docs are resolved by primary client',
327390
['multi-client'],

0 commit comments

Comments
 (0)