Skip to content

Commit b26c072

Browse files
dconeybedwyfrequency
authored andcommitted
Firestore: query.test.ts: add a test that resumes a query with existence filter (#7134)
1 parent cc3c93a commit b26c072

File tree

2 files changed

+88
-6
lines changed

2 files changed

+88
-6
lines changed

packages/firestore/test/integration/api/query.test.ts

+57
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import {
4747
Query,
4848
query,
4949
QuerySnapshot,
50+
runTransaction,
5051
setDoc,
5152
startAfter,
5253
startAt,
@@ -2059,6 +2060,62 @@ apiDescribe('Queries', (persistence: boolean) => {
20592060
});
20602061
});
20612062
});
2063+
2064+
it('resuming a query should use existence filter to detect deletes', async () => {
2065+
// Prepare the names and contents of the 100 documents to create.
2066+
const testDocs: { [key: string]: object } = {};
2067+
for (let i = 0; i < 100; i++) {
2068+
testDocs['doc' + (1000 + i)] = { key: 42 };
2069+
}
2070+
2071+
return withTestCollection(persistence, testDocs, async (coll, db) => {
2072+
// Run a query to populate the local cache with the 100 documents and a
2073+
// resume token.
2074+
const snapshot1 = await getDocs(coll);
2075+
expect(snapshot1.size, 'snapshot1.size').to.equal(100);
2076+
const createdDocuments = snapshot1.docs.map(snapshot => snapshot.ref);
2077+
2078+
// Delete 50 of the 100 documents. Do this in a transaction, rather than
2079+
// deleteDoc(), to avoid affecting the local cache.
2080+
const deletedDocumentIds = new Set<string>();
2081+
await runTransaction(db, async txn => {
2082+
for (let i = 0; i < createdDocuments.length; i += 2) {
2083+
const documentToDelete = createdDocuments[i];
2084+
txn.delete(documentToDelete);
2085+
deletedDocumentIds.add(documentToDelete.id);
2086+
}
2087+
});
2088+
2089+
// Wait for 10 seconds, during which Watch will stop tracking the query
2090+
// and will send an existence filter rather than "delete" events when the
2091+
// query is resumed.
2092+
await new Promise(resolve => setTimeout(resolve, 10000));
2093+
2094+
// Resume the query and save the resulting snapshot for verification.
2095+
const snapshot2 = await getDocs(coll);
2096+
2097+
// Verify that the snapshot from the resumed query contains the expected
2098+
// documents; that is, that it contains the 50 documents that were _not_
2099+
// deleted.
2100+
// TODO(b/270731363): Remove the "if" condition below once the
2101+
// Firestore Emulator is fixed to send an existence filter. At the time of
2102+
// writing, the Firestore emulator fails to send an existence filter,
2103+
// resulting in the client including the deleted documents in the snapshot
2104+
// of the resumed query.
2105+
if (!(USE_EMULATOR && snapshot2.size === 100)) {
2106+
const actualDocumentIds = snapshot2.docs
2107+
.map(documentSnapshot => documentSnapshot.ref.id)
2108+
.sort();
2109+
const expectedDocumentIds = createdDocuments
2110+
.filter(documentRef => !deletedDocumentIds.has(documentRef.id))
2111+
.map(documentRef => documentRef.id)
2112+
.sort();
2113+
expect(actualDocumentIds, 'snapshot2.docs').to.deep.equal(
2114+
expectedDocumentIds
2115+
);
2116+
}
2117+
});
2118+
}).timeout('90s');
20622119
});
20632120

20642121
function verifyDocumentChange<T>(

packages/firestore/test/integration/util/helpers.ts

+31-6
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@ import {
3232
PrivateSettings,
3333
SnapshotListenOptions,
3434
newTestFirestore,
35-
newTestApp
35+
newTestApp,
36+
writeBatch,
37+
WriteBatch
3638
} from './firebase_export';
3739
import {
3840
ALT_PROJECT_ID,
@@ -317,11 +319,34 @@ export function withTestCollectionSettings(
317319
const collectionId = 'test-collection-' + doc(collection(testDb, 'x')).id;
318320
const testCollection = collection(testDb, collectionId);
319321
const setupCollection = collection(setupDb, collectionId);
320-
const sets: Array<Promise<void>> = [];
321-
Object.keys(docs).forEach(key => {
322-
sets.push(setDoc(doc(setupCollection, key), docs[key]));
323-
});
324-
return Promise.all(sets).then(() => fn(testCollection, testDb));
322+
323+
const writeBatchCommits: Array<Promise<void>> = [];
324+
let writeBatch_: WriteBatch | null = null;
325+
let writeBatchSize = 0;
326+
327+
for (const key of Object.keys(docs)) {
328+
if (writeBatch_ === null) {
329+
writeBatch_ = writeBatch(setupDb);
330+
}
331+
332+
writeBatch_.set(doc(setupCollection, key), docs[key]);
333+
writeBatchSize++;
334+
335+
// Write batches are capped at 500 writes. Use 400 just to be safe.
336+
if (writeBatchSize === 400) {
337+
writeBatchCommits.push(writeBatch_.commit());
338+
writeBatch_ = null;
339+
writeBatchSize = 0;
340+
}
341+
}
342+
343+
if (writeBatch_ !== null) {
344+
writeBatchCommits.push(writeBatch_.commit());
345+
}
346+
347+
return Promise.all(writeBatchCommits).then(() =>
348+
fn(testCollection, testDb)
349+
);
325350
}
326351
);
327352
}

0 commit comments

Comments
 (0)