Skip to content

Commit c680edb

Browse files
committed
Add test 'bloom filter should correctly encode special unicode characters' to query.test.ts
1 parent b59dbb5 commit c680edb

File tree

1 file changed

+117
-0
lines changed

1 file changed

+117
-0
lines changed

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

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import {
3232
doc,
3333
DocumentChange,
3434
DocumentChangeType,
35+
DocumentData,
3536
documentId,
3637
enableNetwork,
3738
endAt,
@@ -2197,6 +2198,122 @@ apiDescribe('Queries', persistence => {
21972198
});
21982199
});
21992200
}).timeout('90s');
2201+
2202+
// TODO(b/270731363): Re-enable this test once the Firestore emulator is fixed
2203+
// to send an existence filter.
2204+
// eslint-disable-next-line no-restricted-properties
2205+
(USE_EMULATOR ? it.skip : it.only)(
2206+
'bloom filter should correctly encode special unicode characters',
2207+
async () => {
2208+
const testDocIds = [
2209+
'DocumentToDelete',
2210+
'LowercaseEWithAcuteAccent_\u00E9',
2211+
'LowercaseEWithAcuteAccent_\u0065\u0301',
2212+
'LowercaseEWithMultipleAccents_\u0065\u0301\u0327',
2213+
'LowercaseEWithMultipleAccents_\u0065\u0327\u0301',
2214+
'Smiley_\u{1F600}'
2215+
];
2216+
const testDocs = testDocIds.reduce((map, docId) => {
2217+
map[docId] = { foo: 42 };
2218+
return map;
2219+
}, {} as { [key: string]: DocumentData });
2220+
2221+
// Ensure that the local cache is configured to use LRU garbage
2222+
// collection (rather than eager garbage collection) so that the resume
2223+
// token and document data does not get prematurely evicted.
2224+
const lruPersistence = persistence.toLruGc();
2225+
2226+
return withRetry(async attemptNumber => {
2227+
return withTestCollection(
2228+
lruPersistence,
2229+
testDocs,
2230+
async (coll, db) => {
2231+
// Run a query to populate the local cache with documents that have
2232+
// names with complex Unicode characters.
2233+
const snapshot1 = await getDocs(coll);
2234+
const snapshot1DocumentIds = snapshot1.docs.map(
2235+
documentSnapshot => documentSnapshot.id
2236+
);
2237+
expect(
2238+
snapshot1DocumentIds,
2239+
'snapshot1DocumentIds'
2240+
).to.have.members(testDocIds);
2241+
2242+
// Delete one of the documents so that the next call to getDocs() will
2243+
// experience an existence filter mismatch. Do this deletion in a
2244+
// transaction, rather than using deleteDoc(), to avoid affecting the
2245+
// local cache.
2246+
await runTransaction(db, async txn => {
2247+
const snapshotOfDocumentToDelete = await txn.get(
2248+
doc(coll, 'DocumentToDelete')
2249+
);
2250+
expect(
2251+
snapshotOfDocumentToDelete.exists(),
2252+
'snapshotOfDocumentToDelete.exists()'
2253+
).to.be.true;
2254+
txn.delete(snapshotOfDocumentToDelete.ref);
2255+
});
2256+
2257+
// Wait for 10 seconds, during which Watch will stop tracking the
2258+
// query and will send an existence filter rather than "delete" events
2259+
// when the query is resumed.
2260+
await new Promise(resolve => setTimeout(resolve, 10000));
2261+
2262+
// Resume the query and save the resulting snapshot for verification.
2263+
// Use some internal testing hooks to "capture" the existence filter
2264+
// mismatches.
2265+
const [existenceFilterMismatches, snapshot2] =
2266+
await captureExistenceFilterMismatches(() => getDocs(coll));
2267+
const snapshot2DocumentIds = snapshot2.docs.map(
2268+
documentSnapshot => documentSnapshot.id
2269+
);
2270+
const testDocIdsMinusDeletedDocId = testDocIds.filter(
2271+
documentId => documentId !== 'DocumentToDelete'
2272+
);
2273+
expect(
2274+
snapshot2DocumentIds,
2275+
'snapshot2DocumentIds'
2276+
).to.have.members(testDocIdsMinusDeletedDocId);
2277+
2278+
// Verify that Watch sent an existence filter with the correct counts.
2279+
expect(
2280+
existenceFilterMismatches,
2281+
'existenceFilterMismatches'
2282+
).to.have.length(1);
2283+
const { localCacheCount, existenceFilterCount, bloomFilter } =
2284+
existenceFilterMismatches[0];
2285+
expect(localCacheCount, 'localCacheCount').to.equal(
2286+
testDocIds.length
2287+
);
2288+
expect(existenceFilterCount, 'existenceFilterCount').to.equal(
2289+
testDocIds.length - 1
2290+
);
2291+
2292+
// Verify that Watch sent a valid bloom filter.
2293+
if (!bloomFilter) {
2294+
expect.fail(
2295+
'The existence filter should have specified a bloom filter ' +
2296+
'in its `unchanged_names` field.'
2297+
);
2298+
throw new Error('should never get here');
2299+
}
2300+
2301+
// Verify that the bloom filter was successfully used to avert a full
2302+
// requery. If a false positive occurred, which is statistically rare,
2303+
// but technically possible, then retry the entire test.
2304+
if (attemptNumber === 1 && !bloomFilter.applied) {
2305+
throw new RetryError();
2306+
}
2307+
2308+
expect(
2309+
bloomFilter.applied,
2310+
`bloomFilter.applied with attemptNumber=${attemptNumber}`
2311+
).to.be.true;
2312+
}
2313+
);
2314+
});
2315+
}
2316+
).timeout('90s');
22002317
});
22012318

22022319
function verifyDocumentChange<T>(

0 commit comments

Comments
 (0)