From 7f009e20321cb518ac0dfcfabc9fd2846d1af35c Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Wed, 28 Jun 2023 14:09:24 -0400 Subject: [PATCH 1/6] query.test.ts: use LRUGC in bloom filter tests so they can run even when persistence=false --- .../test/integration/api/query.test.ts | 21 ++++++------ .../test/integration/util/helpers.ts | 34 +++++++++++-------- 2 files changed, 30 insertions(+), 25 deletions(-) diff --git a/packages/firestore/test/integration/api/query.test.ts b/packages/firestore/test/integration/api/query.test.ts index bb2f35a6365..2e433a76ad2 100644 --- a/packages/firestore/test/integration/api/query.test.ts +++ b/packages/firestore/test/integration/api/query.test.ts @@ -36,6 +36,8 @@ import { enableNetwork, endAt, endBefore, + memoryLocalCache, + memoryLruGarbageCollector, Firestore, GeoPoint, getDocs, @@ -46,6 +48,7 @@ import { onSnapshot, or, orderBy, + persistentLocalCache, Query, query, QuerySnapshot, @@ -2128,16 +2131,6 @@ apiDescribe('Queries', (persistence: boolean) => { ); } - // Skip the verification of the existence filter mismatch when persistence - // is disabled because without persistence there is no resume token - // specified in the subsequent call to getDocs(), and, therefore, Watch - // will _not_ send an existence filter. - // TODO(b/272754156) Re-write this test using a snapshot listener instead - // of calls to getDocs() and remove this check for disabled persistence. - if (!persistence) { - return 'passed'; - } - // Skip the verification of the existence filter mismatch when testing // against the Firestore emulator because the Firestore emulator fails to // to send an existence filter at all. @@ -2193,12 +2186,18 @@ apiDescribe('Queries', (persistence: boolean) => { return 'passed'; }; + // Use LRU memory cache so that the resume token will not be deleted between + // calls to `getDocs()`. + const localCache = persistence + ? persistentLocalCache() + : memoryLocalCache({ garbageCollector: memoryLruGarbageCollector() }); + // Run the test let attemptNumber = 0; while (true) { attemptNumber++; const iterationResult = await withTestCollection( - persistence, + localCache, testDocs, runTestIteration ); diff --git a/packages/firestore/test/integration/util/helpers.ts b/packages/firestore/test/integration/util/helpers.ts index 0dc74698729..d816a9f3e16 100644 --- a/packages/firestore/test/integration/util/helpers.ts +++ b/packages/firestore/test/integration/util/helpers.ts @@ -25,10 +25,12 @@ import { DocumentData, DocumentReference, Firestore, + MemoryLocalCache, memoryLocalCache, memoryLruGarbageCollector, newTestApp, newTestFirestore, + PersistentLocalCache, persistentLocalCache, PrivateSettings, QuerySnapshot, @@ -137,7 +139,7 @@ export function toIds(docSet: QuerySnapshot): string[] { } export function withTestDb( - persistence: boolean, + persistence: boolean | PersistentLocalCache | MemoryLocalCache, fn: (db: Firestore) => Promise ): Promise { return withTestDbs(persistence, 1, ([db]) => { @@ -160,7 +162,7 @@ export function withEnsuredEagerGcTestDb( } export function withEnsuredLruGcTestDb( - persistence: boolean, + persistence: boolean | PersistentLocalCache | MemoryLocalCache, fn: (db: Firestore) => Promise ): Promise { const newSettings = { ...DEFAULT_SETTINGS }; @@ -188,7 +190,7 @@ export function withEnsuredLruGcTestDb( /** Runs provided fn with a db for an alternate project id. */ export function withAlternateTestDb( - persistence: boolean, + persistence: boolean | PersistentLocalCache | MemoryLocalCache, fn: (db: Firestore) => Promise ): Promise { return withTestDbsSettings( @@ -203,7 +205,7 @@ export function withAlternateTestDb( } export function withTestDbs( - persistence: boolean, + persistence: boolean | PersistentLocalCache | MemoryLocalCache, numDbs: number, fn: (db: Firestore[]) => Promise ): Promise { @@ -216,7 +218,7 @@ export function withTestDbs( ); } export async function withTestDbsSettings( - persistence: boolean, + persistence: boolean | PersistentLocalCache | MemoryLocalCache, projectId: string, settings: PrivateSettings, numDbs: number, @@ -250,7 +252,7 @@ export async function withTestDbsSettings( } export async function withNamedTestDbsOrSkipUnlessUsingEmulator( - persistence: boolean, + persistence: boolean | PersistentLocalCache | MemoryLocalCache, dbNames: string[], fn: (db: Firestore[]) => Promise ): Promise { @@ -265,8 +267,12 @@ export async function withNamedTestDbsOrSkipUnlessUsingEmulator( const dbs: Firestore[] = []; for (const dbName of dbNames) { const newSettings = { ...DEFAULT_SETTINGS }; - if (persistence) { - newSettings.localCache = persistentLocalCache(); + if (typeof persistence === 'boolean') { + if (persistence) { + newSettings.localCache = persistentLocalCache(); + } + } else { + newSettings.localCache = persistence; } const db = newTestFirestore(app, newSettings, dbName); dbs.push(db); @@ -285,7 +291,7 @@ export async function withNamedTestDbsOrSkipUnlessUsingEmulator( } export function withTestDoc( - persistence: boolean, + persistence: boolean | PersistentLocalCache | MemoryLocalCache, fn: (doc: DocumentReference, db: Firestore) => Promise ): Promise { return withTestDb(persistence, db => { @@ -294,7 +300,7 @@ export function withTestDoc( } export function withTestDocAndSettings( - persistence: boolean, + persistence: boolean | PersistentLocalCache | MemoryLocalCache, settings: PrivateSettings, fn: (doc: DocumentReference) => Promise ): Promise { @@ -315,7 +321,7 @@ export function withTestDocAndSettings( // `withTestDoc(..., docRef => { setDoc(docRef, initialData) ...});` that // otherwise is quite common. export function withTestDocAndInitialData( - persistence: boolean, + persistence: boolean | PersistentLocalCache | MemoryLocalCache, initialData: DocumentData | null, fn: (doc: DocumentReference, db: Firestore) => Promise ): Promise { @@ -330,7 +336,7 @@ export function withTestDocAndInitialData( } export function withTestCollection( - persistence: boolean, + persistence: boolean | PersistentLocalCache | MemoryLocalCache, docs: { [key: string]: DocumentData }, fn: (collection: CollectionReference, db: Firestore) => Promise ): Promise { @@ -338,7 +344,7 @@ export function withTestCollection( } export function withEmptyTestCollection( - persistence: boolean, + persistence: boolean | PersistentLocalCache | MemoryLocalCache, fn: (collection: CollectionReference, db: Firestore) => Promise ): Promise { return withTestCollection(persistence, {}, fn); @@ -347,7 +353,7 @@ export function withEmptyTestCollection( // TODO(mikelehen): Once we wipe the database between tests, we can probably // return the same collection every time. export function withTestCollectionSettings( - persistence: boolean, + persistence: boolean | PersistentLocalCache | MemoryLocalCache, settings: PrivateSettings, docs: { [key: string]: DocumentData }, fn: (collection: CollectionReference, db: Firestore) => Promise From b4a72f9ab532f23d6a25cf62fb8debae11542dfe Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Wed, 28 Jun 2023 16:14:00 -0400 Subject: [PATCH 2/6] Improve readability of persistece parameter to integration tests. --- .../test/integration/api/aggregation.test.ts | 6 +- .../integration/api/array_transforms.test.ts | 10 +- .../test/integration/api/batch_writes.test.ts | 4 +- .../test/integration/api/bundle.test.ts | 2 +- .../test/integration/api/cursor.test.ts | 2 +- .../test/integration/api/database.test.ts | 26 ++- .../test/integration/api/fields.test.ts | 8 +- .../test/integration/api/get_options.test.ts | 10 +- .../api/index_configuration.test.ts | 4 +- .../api/numeric_transforms.test.ts | 2 +- .../test/integration/api/query.test.ts | 8 +- .../integration/api/server_timestamp.test.ts | 2 +- .../test/integration/api/smoke.test.ts | 2 +- .../test/integration/api/transactions.test.ts | 4 +- .../test/integration/api/type.test.ts | 2 +- .../test/integration/api/validation.test.ts | 21 ++- .../integration/api_internal/database.test.ts | 9 +- .../api_internal/idle_timeout.test.ts | 2 +- .../api_internal/transaction.test.ts | 2 +- .../integration/browser/indexeddb.test.ts | 8 +- .../test/integration/prime_backend.test.ts | 4 +- .../test/integration/util/helpers.ts | 177 +++++++++++------- .../test/integration/util/internal_helpers.ts | 4 +- 23 files changed, 184 insertions(+), 135 deletions(-) diff --git a/packages/firestore/test/integration/api/aggregation.test.ts b/packages/firestore/test/integration/api/aggregation.test.ts index d9d59799ee8..8080cdfd43e 100644 --- a/packages/firestore/test/integration/api/aggregation.test.ts +++ b/packages/firestore/test/integration/api/aggregation.test.ts @@ -42,7 +42,7 @@ import { } from '../util/helpers'; import { USE_EMULATOR } from '../util/settings'; -apiDescribe('Count queries', (persistence: boolean) => { +apiDescribe('Count queries', persistence => { it('can run count query getCountFromServer', () => { const testDocs = { a: { author: 'authorA', title: 'titleA' }, @@ -147,7 +147,7 @@ apiDescribe('Count queries', (persistence: boolean) => { ); }); -apiDescribe('Aggregation queries', (persistence: boolean) => { +apiDescribe('Aggregation queries', persistence => { it('can run count query getAggregationFromServer', () => { const testDocs = { a: { author: 'authorA', title: 'titleA' }, @@ -356,7 +356,7 @@ apiDescribe('Aggregation queries', (persistence: boolean) => { // TODO (sum/avg) enable these tests when sum/avg is supported by the backend apiDescribe.skip( 'Aggregation queries - sum / average', - (persistence: boolean) => { + persistence => { it('can run sum query getAggregationFromServer', () => { const testDocs = { a: { author: 'authorA', title: 'titleA', pages: 100 }, diff --git a/packages/firestore/test/integration/api/array_transforms.test.ts b/packages/firestore/test/integration/api/array_transforms.test.ts index 7019ebfd62d..3ad8c5690a3 100644 --- a/packages/firestore/test/integration/api/array_transforms.test.ts +++ b/packages/firestore/test/integration/api/array_transforms.test.ts @@ -32,7 +32,11 @@ import { arrayUnion, FirestoreError } from '../util/firebase_export'; -import { apiDescribe, withTestDb, withTestDoc } from '../util/helpers'; +import { + apiDescribe, + withTestDb, + withTestDoc +} from '../util/helpers'; addEqualityMatcher(); @@ -42,7 +46,7 @@ addEqualityMatcher(); * together, etc.) and so these tests mostly focus on the array transform * semantics. */ -apiDescribe('Array Transforms:', (persistence: boolean) => { +apiDescribe('Array Transforms:', persistence => { // A document reference to read and write to. let docRef: DocumentReference; @@ -164,7 +168,7 @@ apiDescribe('Array Transforms:', (persistence: boolean) => { * being enabled so documents remain in the cache after the write. */ // eslint-disable-next-line no-restricted-properties - (persistence ? describe : describe.skip)('Server Application: ', () => { + (persistence.gc === 'lru' ? describe : describe.skip)('Server Application: ', () => { it('set() with no cached base doc', async () => { await withTestDoc(persistence, async docRef => { await setDoc(docRef, { array: arrayUnion(1, 2) }); diff --git a/packages/firestore/test/integration/api/batch_writes.test.ts b/packages/firestore/test/integration/api/batch_writes.test.ts index 7dd10208815..0baf112d47a 100644 --- a/packages/firestore/test/integration/api/batch_writes.test.ts +++ b/packages/firestore/test/integration/api/batch_writes.test.ts @@ -41,7 +41,7 @@ import { withTestDoc } from '../util/helpers'; -apiDescribe('Database batch writes', (persistence: boolean) => { +apiDescribe('Database batch writes', persistence => { it('supports empty batches', () => { return withTestDb(persistence, db => { return writeBatch(db).commit(); @@ -329,7 +329,7 @@ apiDescribe('Database batch writes', (persistence: boolean) => { // PORTING NOTE: These tests are for FirestoreDataConverter support and apply // only to web. - apiDescribe('withConverter() support', (persistence: boolean) => { + apiDescribe('withConverter() support', persistence => { class Post { constructor(readonly title: string, readonly author: string) {} byline(): string { diff --git a/packages/firestore/test/integration/api/bundle.test.ts b/packages/firestore/test/integration/api/bundle.test.ts index 3dcc0186e18..96d186bba1b 100644 --- a/packages/firestore/test/integration/api/bundle.test.ts +++ b/packages/firestore/test/integration/api/bundle.test.ts @@ -69,7 +69,7 @@ const BUNDLE_TEMPLATE = [ '{"document":{"name":"projects/{0}/databases/(default)/documents/coll-1/b","createTime":{"seconds":1,"nanos":9},"updateTime":{"seconds":1,"nanos":9},"fields":{"k":{"stringValue":"b"},"bar":{"integerValue":2}}}}' ]; -apiDescribe('Bundles', (persistence: boolean) => { +apiDescribe('Bundles', persistence => { function verifySnapEqualsTestDocs(snap: QuerySnapshot): void { expect(toDataArray(snap)).to.deep.equal([ { k: 'a', bar: 1 }, diff --git a/packages/firestore/test/integration/api/cursor.test.ts b/packages/firestore/test/integration/api/cursor.test.ts index f63d537e742..1d2ffa68941 100644 --- a/packages/firestore/test/integration/api/cursor.test.ts +++ b/packages/firestore/test/integration/api/cursor.test.ts @@ -44,7 +44,7 @@ import { withTestDbs } from '../util/helpers'; -apiDescribe('Cursors', (persistence: boolean) => { +apiDescribe('Cursors', persistence => { it('can page through items', () => { const testDocs = { a: { v: 'a' }, diff --git a/packages/firestore/test/integration/api/database.test.ts b/packages/firestore/test/integration/api/database.test.ts index 0b22720d67a..43116a439fa 100644 --- a/packages/firestore/test/integration/api/database.test.ts +++ b/packages/firestore/test/integration/api/database.test.ts @@ -67,21 +67,19 @@ import { } from '../util/firebase_export'; import { apiDescribe, - withEnsuredLruGcTestDb, withTestCollection, withTestDbsSettings, withTestDb, withTestDbs, withTestDoc, withTestDocAndInitialData, - withNamedTestDbsOrSkipUnlessUsingEmulator, - withEnsuredEagerGcTestDb + withNamedTestDbsOrSkipUnlessUsingEmulator } from '../util/helpers'; import { DEFAULT_SETTINGS, DEFAULT_PROJECT_ID } from '../util/settings'; use(chaiAsPromised); -apiDescribe('Database', (persistence: boolean) => { +apiDescribe('Database', persistence => { it('can set a document', () => { return withTestDoc(persistence, docRef => { return setDoc(docRef, { @@ -153,7 +151,7 @@ apiDescribe('Database', (persistence: boolean) => { }); // eslint-disable-next-line no-restricted-properties - (persistence ? it : it.skip)('can update an unknown document', () => { + (persistence.gc === 'lru' ? it : it.skip)('can update an unknown document', () => { return withTestDbs(persistence, 2, async ([reader, writer]) => { const writerRef = doc(collection(writer, 'collection')); const readerRef = doc(collection(reader, 'collection'), writerRef.id); @@ -638,7 +636,7 @@ apiDescribe('Database', (persistence: boolean) => { }); }); - apiDescribe('Queries are validated client-side', (persistence: boolean) => { + apiDescribe('Queries are validated client-side', persistence => { // NOTE: Failure cases are validated in validation_test.ts it('same inequality fields works', () => { @@ -1095,7 +1093,7 @@ apiDescribe('Database', (persistence: boolean) => { }); // eslint-disable-next-line no-restricted-properties - (persistence ? it : it.skip)('offline writes are sent after restart', () => { + (persistence.gc === 'lru' ? it : it.skip)('offline writes are sent after restart', () => { return withTestDoc(persistence, async (docRef, firestore) => { const app = firestore.app; const name = app.name; @@ -1141,7 +1139,7 @@ apiDescribe('Database', (persistence: boolean) => { }); // eslint-disable-next-line no-restricted-properties - (persistence ? it : it.skip)( + (persistence.gc === 'lru' ? it : it.skip)( 'maintains persistence after restarting app', async () => { await withTestDoc(persistence, async docRef => { @@ -1164,7 +1162,7 @@ apiDescribe('Database', (persistence: boolean) => { ); // eslint-disable-next-line no-restricted-properties - (persistence ? it : it.skip)( + (persistence.gc === 'lru' ? it : it.skip)( 'can clear persistence if the client has been terminated', async () => { await withTestDoc(persistence, async (docRef, firestore) => { @@ -1188,7 +1186,7 @@ apiDescribe('Database', (persistence: boolean) => { ); // eslint-disable-next-line no-restricted-properties - (persistence ? it : it.skip)( + (persistence.gc === 'lru' ? it : it.skip)( 'can clear persistence if the client has not been initialized', async () => { await withTestDoc(persistence, async docRef => { @@ -1212,7 +1210,7 @@ apiDescribe('Database', (persistence: boolean) => { ); // eslint-disable-next-line no-restricted-properties - (persistence ? it : it.skip)( + (persistence.gc === 'lru' ? it : it.skip)( 'cannot clear persistence if the client has been initialized', async () => { await withTestDoc(persistence, async (docRef, firestore) => { @@ -1340,7 +1338,7 @@ apiDescribe('Database', (persistence: boolean) => { // PORTING NOTE: These tests are for FirestoreDataConverter support and apply // only to web. - apiDescribe('withConverter() support', (persistence: boolean) => { + apiDescribe('withConverter() support', persistence => { class Post { constructor( readonly title: string, @@ -1757,7 +1755,7 @@ apiDescribe('Database', (persistence: boolean) => { it('Cannot get document from cache with eager GC enabled.', () => { const initialData = { key: 'value' }; - return withEnsuredEagerGcTestDb(async db => { + return withTestDb(persistence.toEagerGc(), async db => { const docRef = doc(collection(db, 'test-collection')); await setDoc(docRef, initialData); await expect(getDocFromCache(docRef)).to.be.rejectedWith('Failed to get'); @@ -1766,7 +1764,7 @@ apiDescribe('Database', (persistence: boolean) => { it('Can get document from cache with Lru GC enabled.', () => { const initialData = { key: 'value' }; - return withEnsuredLruGcTestDb(persistence, async db => { + return withTestDb(persistence.toLruGc(), async db => { const docRef = doc(collection(db, 'test-collection')); await setDoc(docRef, initialData); return getDocFromCache(docRef).then(doc => { diff --git a/packages/firestore/test/integration/api/fields.test.ts b/packages/firestore/test/integration/api/fields.test.ts index 0456ad5e48d..974264921b3 100644 --- a/packages/firestore/test/integration/api/fields.test.ts +++ b/packages/firestore/test/integration/api/fields.test.ts @@ -43,7 +43,7 @@ import { DEFAULT_SETTINGS } from '../util/settings'; // eslint-disable-next-line @typescript-eslint/no-explicit-any type AnyTestData = any; -apiDescribe('Nested Fields', (persistence: boolean) => { +apiDescribe('Nested Fields', persistence => { const testData = (n?: number): AnyTestData => { n = n || 1; return { @@ -240,7 +240,7 @@ apiDescribe('Nested Fields', (persistence: boolean) => { // NOTE(mikelehen): I originally combined these tests with the above ones, but // Datastore currently prohibits having nested fields and fields with dots in // the same entity, so I'm separating them. -apiDescribe('Fields with special characters', (persistence: boolean) => { +apiDescribe('Fields with special characters', persistence => { const testData = (n?: number): AnyTestData => { n = n || 1; return { @@ -340,7 +340,7 @@ apiDescribe('Fields with special characters', (persistence: boolean) => { }); }); -apiDescribe('Timestamp Fields in snapshots', (persistence: boolean) => { +apiDescribe('Timestamp Fields in snapshots', persistence => { // Figure out how to pass in the Timestamp type // eslint-disable-next-line @typescript-eslint/no-explicit-any const testDataWithTimestamps = (ts: any): AnyTestData => { @@ -379,7 +379,7 @@ apiDescribe('Timestamp Fields in snapshots', (persistence: boolean) => { }); }); -apiDescribe('`undefined` properties', (persistence: boolean) => { +apiDescribe('`undefined` properties', persistence => { const settings = { ...DEFAULT_SETTINGS }; settings.ignoreUndefinedProperties = true; diff --git a/packages/firestore/test/integration/api/get_options.test.ts b/packages/firestore/test/integration/api/get_options.test.ts index 000d3899307..ede036aff90 100644 --- a/packages/firestore/test/integration/api/get_options.test.ts +++ b/packages/firestore/test/integration/api/get_options.test.ts @@ -36,10 +36,10 @@ import { apiDescribe, withTestCollection, withTestDocAndInitialData, - withEnsuredLruGcTestDb + withTestDb } from '../util/helpers'; -apiDescribe('GetOptions', (persistence: boolean) => { +apiDescribe('GetOptions', persistence => { it('get document while online with default get options', () => { const initialData = { key: 'value' }; return withTestDocAndInitialData(persistence, initialData, docRef => { @@ -70,8 +70,8 @@ apiDescribe('GetOptions', (persistence: boolean) => { it('get document while offline with default get options', () => { const initialData = { key: 'value' }; - // Use an instance with Gc turned on. - return withEnsuredLruGcTestDb(persistence, async db => { + // Use an instance with LRU GC. + return withTestDb(persistence.toLruGc(), async db => { const docRef = doc(collection(db, 'test-collection')); await setDoc(docRef, initialData); return getDoc(docRef) @@ -498,7 +498,7 @@ apiDescribe('GetOptions', (persistence: boolean) => { // We need the deleted doc to stay in cache, so only run this with persistence. // eslint-disable-next-line no-restricted-properties, - (persistence ? it : it.skip)( + (persistence.gc === 'lru' ? it : it.skip)( 'get deleted doc while offline with source=cache', () => { return withTestDocAndInitialData(persistence, null, (docRef, db) => { diff --git a/packages/firestore/test/integration/api/index_configuration.test.ts b/packages/firestore/test/integration/api/index_configuration.test.ts index f75f065d263..1cab94d202e 100644 --- a/packages/firestore/test/integration/api/index_configuration.test.ts +++ b/packages/firestore/test/integration/api/index_configuration.test.ts @@ -20,7 +20,7 @@ import { expect } from 'chai'; import { setIndexConfiguration } from '../util/firebase_export'; import { apiDescribe, withTestDb } from '../util/helpers'; -apiDescribe('Index Configuration:', (persistence: boolean) => { +apiDescribe('Index Configuration:', persistence => { it('supports JSON', () => { return withTestDb(persistence, async db => { return setIndexConfiguration( @@ -80,7 +80,7 @@ apiDescribe('Index Configuration:', (persistence: boolean) => { it('bad JSON does not crash client', () => { return withTestDb(persistence, async db => { const action = (): Promise => setIndexConfiguration(db, '{,}'); - if (persistence) { + if (persistence.storage === 'indexeddb') { expect(action).to.throw(/Failed to parse JSON/); } else { // Silently do nothing. Parsing is not done and therefore no error is thrown. diff --git a/packages/firestore/test/integration/api/numeric_transforms.test.ts b/packages/firestore/test/integration/api/numeric_transforms.test.ts index e456e2ffe2d..374be8e5e71 100644 --- a/packages/firestore/test/integration/api/numeric_transforms.test.ts +++ b/packages/firestore/test/integration/api/numeric_transforms.test.ts @@ -37,7 +37,7 @@ import { apiDescribe, withTestDoc } from '../util/helpers'; const DOUBLE_EPSILON = 0.000001; -apiDescribe('Numeric Transforms:', (persistence: boolean) => { +apiDescribe('Numeric Transforms:', persistence => { // A document reference to read and write to. let docRef: DocumentReference; diff --git a/packages/firestore/test/integration/api/query.test.ts b/packages/firestore/test/integration/api/query.test.ts index bb2f35a6365..5ddad38d9d6 100644 --- a/packages/firestore/test/integration/api/query.test.ts +++ b/packages/firestore/test/integration/api/query.test.ts @@ -70,7 +70,7 @@ import { import { USE_EMULATOR } from '../util/settings'; import { captureExistenceFilterMismatches } from '../util/testing_hooks_util'; -apiDescribe('Queries', (persistence: boolean) => { +apiDescribe('Queries', persistence => { addEqualityMatcher(); it('can issue limit queries', () => { @@ -1336,7 +1336,7 @@ apiDescribe('Queries', (persistence: boolean) => { // OR Query tests only run when the SDK is configured for persistence // because they validate that the result from server and cache match. // eslint-disable-next-line no-restricted-properties - (persistence ? describe : describe.skip)('OR Queries', () => { + (persistence.gc === 'lru' ? describe : describe.skip)('OR Queries', () => { it('can use query overloads', () => { const testDocs = { doc1: { a: 1, b: 0 }, @@ -1652,7 +1652,7 @@ apiDescribe('Queries', (persistence: boolean) => { // because it results in a 'missing index' error. The Firestore Emulator, // however, does serve these queries. // eslint-disable-next-line no-restricted-properties - (persistence && USE_EMULATOR ? describe : describe.skip)('OR Queries', () => { + (persistence.gc === 'lru' && USE_EMULATOR ? describe : describe.skip)('OR Queries', () => { it('can use query overloads', () => { const testDocs = { doc1: { a: 1, b: 0 }, @@ -2027,7 +2027,7 @@ apiDescribe('Queries', (persistence: boolean) => { // Reproduces https://github.com/firebase/firebase-js-sdk/issues/5873 // eslint-disable-next-line no-restricted-properties - (persistence ? describe : describe.skip)('Caching empty results', () => { + (persistence.gc === 'lru' ? describe : describe.skip)('Caching empty results', () => { it('can raise initial snapshot from cache, even if it is empty', () => { return withTestCollection(persistence, {}, async coll => { const snapshot1 = await getDocs(coll); // Populate the cache. diff --git a/packages/firestore/test/integration/api/server_timestamp.test.ts b/packages/firestore/test/integration/api/server_timestamp.test.ts index 01467b21bc8..7d3c369e677 100644 --- a/packages/firestore/test/integration/api/server_timestamp.test.ts +++ b/packages/firestore/test/integration/api/server_timestamp.test.ts @@ -38,7 +38,7 @@ import { apiDescribe, withTestDoc } from '../util/helpers'; // eslint-disable-next-line @typescript-eslint/no-explicit-any type AnyTestData = any; -apiDescribe('Server Timestamps', (persistence: boolean) => { +apiDescribe('Server Timestamps', persistence => { // Data written in tests via set(). const setData = { a: 42, diff --git a/packages/firestore/test/integration/api/smoke.test.ts b/packages/firestore/test/integration/api/smoke.test.ts index 1ce592aee8f..5cd544c98db 100644 --- a/packages/firestore/test/integration/api/smoke.test.ts +++ b/packages/firestore/test/integration/api/smoke.test.ts @@ -40,7 +40,7 @@ import { withTestDoc } from '../util/helpers'; -apiDescribe('Smoke Test', (persistence: boolean) => { +apiDescribe('Smoke Test', persistence => { it('can write a single document', () => { return withTestDoc(persistence, ref => { return setDoc(ref, { diff --git a/packages/firestore/test/integration/api/transactions.test.ts b/packages/firestore/test/integration/api/transactions.test.ts index df7e520fccd..67a13acdde1 100644 --- a/packages/firestore/test/integration/api/transactions.test.ts +++ b/packages/firestore/test/integration/api/transactions.test.ts @@ -35,7 +35,7 @@ import { } from '../util/firebase_export'; import { apiDescribe, withTestDb } from '../util/helpers'; -apiDescribe('Database transactions', (persistence: boolean) => { +apiDescribe('Database transactions', persistence => { type TransactionStage = ( transaction: Transaction, docRef: DocumentReference @@ -704,7 +704,7 @@ apiDescribe('Database transactions', (persistence: boolean) => { // PORTING NOTE: These tests are for FirestoreDataConverter support and apply // only to web. - apiDescribe('withConverter() support', (persistence: boolean) => { + apiDescribe('withConverter() support', persistence => { class Post { constructor(readonly title: string, readonly author: string) {} byline(): string { diff --git a/packages/firestore/test/integration/api/type.test.ts b/packages/firestore/test/integration/api/type.test.ts index de7614459fa..0fd9c19ccad 100644 --- a/packages/firestore/test/integration/api/type.test.ts +++ b/packages/firestore/test/integration/api/type.test.ts @@ -37,7 +37,7 @@ import { } from '../util/firebase_export'; import { apiDescribe, withTestDb, withTestDoc } from '../util/helpers'; -apiDescribe('Firestore', (persistence: boolean) => { +apiDescribe('Firestore', persistence => { addEqualityMatcher(); async function expectRoundtrip( diff --git a/packages/firestore/test/integration/api/validation.test.ts b/packages/firestore/test/integration/api/validation.test.ts index f422eb4ad07..0497e710576 100644 --- a/packages/firestore/test/integration/api/validation.test.ts +++ b/packages/firestore/test/integration/api/validation.test.ts @@ -58,7 +58,8 @@ import { apiDescribe, withAlternateTestDb, withTestCollection, - withTestDb + withTestDb, + PersistenceMode } from '../util/helpers'; import { ALT_PROJECT_ID, DEFAULT_PROJECT_ID } from '../util/settings'; @@ -67,17 +68,17 @@ import { ALT_PROJECT_ID, DEFAULT_PROJECT_ID } from '../util/settings'; interface ValidationIt { ( - persistence: boolean, + persistence: PersistenceMode, message: string, testFunction: (db: Firestore) => void | Promise ): void; skip: ( - persistence: boolean, + persistence: PersistenceMode, message: string, testFunction: (db: Firestore) => void | Promise ) => void; only: ( - persistence: boolean, + persistence: PersistenceMode, message: string, testFunction: (db: Firestore) => void | Promise ) => void; @@ -87,7 +88,7 @@ interface ValidationIt { // we have a helper wrapper around it() and withTestDb() to optimize for that. const validationIt: ValidationIt = Object.assign( ( - persistence: boolean, + persistence: PersistenceMode, message: string, testFunction: (db: Firestore) => void | Promise ) => { @@ -102,7 +103,7 @@ const validationIt: ValidationIt = Object.assign( }, { skip( - persistence: boolean, + persistence: PersistenceMode, message: string, _: (db: Firestore) => void | Promise ): void { @@ -110,7 +111,7 @@ const validationIt: ValidationIt = Object.assign( it.skip(message, () => {}); }, only( - persistence: boolean, + persistence: PersistenceMode, message: string, testFunction: (db: Firestore) => void | Promise ): void { @@ -132,13 +133,13 @@ class TestClass { constructor(readonly property: string) {} } -apiDescribe('Validation:', (persistence: boolean) => { +apiDescribe('Validation:', persistence => { describe('FirestoreSettings', () => { // Enabling persistence counts as a use of the firestore instance, meaning // that it will be impossible to verify that a set of settings don't throw, // and additionally that some exceptions happen for specific reasons, rather // than persistence having already been enabled. - if (persistence) { + if (persistence.gc === 'lru') { return; } @@ -230,7 +231,7 @@ apiDescribe('Validation:', (persistence: boolean) => { }); describe('Firestore', () => { - (persistence ? validationIt : validationIt.skip)( + (persistence.gc === 'lru' ? validationIt : validationIt.skip)( persistence, 'disallows calling enablePersistence after use', db => { diff --git a/packages/firestore/test/integration/api_internal/database.test.ts b/packages/firestore/test/integration/api_internal/database.test.ts index 2344530438a..5d7b2585493 100644 --- a/packages/firestore/test/integration/api_internal/database.test.ts +++ b/packages/firestore/test/integration/api_internal/database.test.ts @@ -28,14 +28,17 @@ import { setDoc, waitForPendingWrites } from '../util/firebase_export'; -import { apiDescribe, withTestDoc } from '../util/helpers'; +import { + apiDescribe, + withTestDoc +} from '../util/helpers'; import { withMockCredentialProviderTestDb } from '../util/internal_helpers'; use(chaiAsPromised); -apiDescribe('Database (with internal API)', (persistence: boolean) => { +apiDescribe('Database (with internal API)', persistence => { // eslint-disable-next-line no-restricted-properties - (persistence ? it : it.skip)( + (persistence.gc === 'lru' ? it : it.skip)( 'will reject the promise if clear persistence fails', async () => { await withTestDoc(persistence, async (docRef, firestore) => { diff --git a/packages/firestore/test/integration/api_internal/idle_timeout.test.ts b/packages/firestore/test/integration/api_internal/idle_timeout.test.ts index d0a31ee9461..906b50d1fed 100644 --- a/packages/firestore/test/integration/api_internal/idle_timeout.test.ts +++ b/packages/firestore/test/integration/api_internal/idle_timeout.test.ts @@ -21,7 +21,7 @@ import { collection, doc, onSnapshot, setDoc } from '../util/firebase_export'; import { apiDescribe, withTestDb } from '../util/helpers'; import { asyncQueue } from '../util/internal_helpers'; -apiDescribe('Idle Timeout', (persistence: boolean) => { +apiDescribe('Idle Timeout', persistence => { it('can write document after idle timeout', () => { return withTestDb(persistence, db => { const docRef = doc(collection(db, 'test-collection')); diff --git a/packages/firestore/test/integration/api_internal/transaction.test.ts b/packages/firestore/test/integration/api_internal/transaction.test.ts index 54cca274e98..59753a81293 100644 --- a/packages/firestore/test/integration/api_internal/transaction.test.ts +++ b/packages/firestore/test/integration/api_internal/transaction.test.ts @@ -34,7 +34,7 @@ import { asyncQueue } from '../util/internal_helpers'; apiDescribe( 'Database transactions (with internal API)', - (persistence: boolean) => { + persistence => { it('should increment transactionally', async () => { // A set of concurrent transactions. const transactionPromises: Array> = []; diff --git a/packages/firestore/test/integration/browser/indexeddb.test.ts b/packages/firestore/test/integration/browser/indexeddb.test.ts index b00fd7fbb83..ee987d20d97 100644 --- a/packages/firestore/test/integration/browser/indexeddb.test.ts +++ b/packages/firestore/test/integration/browser/indexeddb.test.ts @@ -24,7 +24,7 @@ import { FirestoreError, setDoc } from '../util/firebase_export'; -import { isPersistenceAvailable, withTestDb } from '../util/helpers'; +import { IndexedDbPersistenceMode, MemoryEagerPersistenceMode, isPersistenceAvailable, withTestDb } from '../util/helpers'; describe('where indexeddb is not available: ', () => { // Only test on platforms where persistence is *not* available (e.g. Edge, @@ -36,7 +36,7 @@ describe('where indexeddb is not available: ', () => { it('fails with code unimplemented', () => { // withTestDb will fail the test if persistence is requested but it fails // so we'll enable persistence here instead. - return withTestDb(/* persistence= */ false, db => { + return withTestDb(new MemoryEagerPersistenceMode(), db => { return enableIndexedDbPersistence(db).then( () => expect.fail('enablePersistence should not have succeeded!'), (error: FirestoreError) => { @@ -47,7 +47,7 @@ describe('where indexeddb is not available: ', () => { }); it('falls back without requiring a wait for the promise', () => { - return withTestDb(/* persistence= */ false, db => { + return withTestDb(new MemoryEagerPersistenceMode(), db => { const persistenceFailedPromise = enableIndexedDbPersistence(db).catch( (err: FirestoreError) => { expect(err.code).to.equal('unimplemented'); @@ -65,7 +65,7 @@ describe('where indexeddb is not available: ', () => { it('fails back to memory cache with initializeFirestore too', () => { // withTestDb will fail the test if persistence is requested but it fails // so we'll enable persistence here instead. - return withTestDb(/* persistence= */ true, db => { + return withTestDb(new IndexedDbPersistenceMode(), db => { // Do the set immediately without waiting on the promise. const testDoc = doc(collection(db, 'test-collection')); return setDoc(testDoc, { foo: 'bar' }); diff --git a/packages/firestore/test/integration/prime_backend.test.ts b/packages/firestore/test/integration/prime_backend.test.ts index 0be6b91d38e..c1c121e9a0f 100644 --- a/packages/firestore/test/integration/prime_backend.test.ts +++ b/packages/firestore/test/integration/prime_backend.test.ts @@ -23,7 +23,7 @@ import { onSnapshot, runTransaction } from './util/firebase_export'; -import { withTestDoc } from './util/helpers'; +import { MemoryEagerPersistenceMode, withTestDoc } from './util/helpers'; // Firestore databases can be subject to a ~30s "cold start" delay if they have not been used // recently, so before any tests run we "prime" the backend. @@ -35,7 +35,7 @@ before( function (): Promise { this.timeout(PRIMING_TIMEOUT_MS); - return withTestDoc(/*persistence=*/ false, async (doc, db) => { + return withTestDoc(new MemoryEagerPersistenceMode(), async (doc, db) => { const accumulator = new EventsAccumulator(); const unsubscribe = onSnapshot(doc, accumulator.storeEvent); diff --git a/packages/firestore/test/integration/util/helpers.ts b/packages/firestore/test/integration/util/helpers.ts index 0dc74698729..852ee677956 100644 --- a/packages/firestore/test/integration/util/helpers.ts +++ b/packages/firestore/test/integration/util/helpers.ts @@ -25,10 +25,13 @@ import { DocumentData, DocumentReference, Firestore, + MemoryLocalCache, + memoryEagerGarbageCollector, memoryLocalCache, memoryLruGarbageCollector, newTestApp, newTestFirestore, + PersistentLocalCache, persistentLocalCache, PrivateSettings, QuerySnapshot, @@ -47,6 +50,90 @@ import { /* eslint-disable no-restricted-globals */ +export interface PersistenceMode { + readonly name: string; + readonly storage: 'memory' | 'indexeddb'; + readonly gc: 'eager' | 'lru'; + + /** + * Creates and returns a new `PersistenceMode` object that is the nearest + * equivalent to this persistence mode but uses eager garbage collection. + */ + toEagerGc(): PersistenceMode; + + /** + * Creates and returns a new `PersistenceMode` object that is the nearest + * equivalent to this persistence mode but uses LRU garbage collection. + */ + toLruGc(): PersistenceMode; + + /** + * Creates and returns a new "local cache" object corresponding to this + * persistence type. + */ + toLocalCache(): MemoryLocalCache | PersistentLocalCache; +} + +export class MemoryEagerPersistenceMode implements PersistenceMode { + readonly name = 'memory'; + readonly storage = 'memory'; + readonly gc = 'eager'; + + toEagerGc(): MemoryEagerPersistenceMode { + return new MemoryEagerPersistenceMode(); + } + + toLruGc(): MemoryLruPersistenceMode { + return new MemoryLruPersistenceMode(); + } + + toLocalCache(): MemoryLocalCache { + return memoryLocalCache({garbageCollector: memoryEagerGarbageCollector()}); + } + +} + +export class MemoryLruPersistenceMode implements PersistenceMode { + readonly name = 'memory_lru_gc'; + readonly storage = 'memory'; + readonly gc = 'lru'; + + toEagerGc(): MemoryEagerPersistenceMode { + return new MemoryEagerPersistenceMode(); + } + + toLruGc(): MemoryLruPersistenceMode { + return new MemoryLruPersistenceMode(); + } + + toLocalCache(): MemoryLocalCache { + return memoryLocalCache({garbageCollector: memoryLruGarbageCollector()}); + } + +} + +export class IndexedDbPersistenceMode implements PersistenceMode { + readonly name = 'indexeddb'; + readonly storage = 'indexeddb' + readonly gc = 'lru'; + + toEagerGc(): MemoryEagerPersistenceMode { + return new MemoryEagerPersistenceMode(); + } + + toLruGc(): IndexedDbPersistenceMode { + return new IndexedDbPersistenceMode(); + } + + toLocalCache(): PersistentLocalCache { + if (this.gc != 'lru') { + throw new Error(`unsupported gc: ${this.gc}`); + } + return persistentLocalCache(); + } + +} + function isIeOrEdge(): boolean { if (!window.navigator) { return false; @@ -77,24 +164,27 @@ export function isPersistenceAvailable(): boolean { function apiDescribeInternal( describeFn: Mocha.PendingSuiteFunction, message: string, - testSuite: (persistence: boolean) => void + testSuite: (persistence: PersistenceMode) => void ): void { - const persistenceModes = [false]; + const persistenceModes: PersistenceMode[] = [new MemoryEagerPersistenceMode()]; if (isPersistenceAvailable()) { - persistenceModes.push(true); + persistenceModes.push(new IndexedDbPersistenceMode()); } - for (const enabled of persistenceModes) { - describeFn(`(Persistence=${enabled}) ${message}`, () => testSuite(enabled)); + for (const persistenceMode of persistenceModes) { + // Freeze the persistence mode so that tests don't modify the persistence + // mode, which is shared between tests. + const frozenPersistenceMode = Object.freeze(persistenceMode); + describeFn(`(Persistence=${persistenceMode.name}) ${message}`, () => testSuite(frozenPersistenceMode)); } } type ApiSuiteFunction = ( message: string, - testSuite: (persistence: boolean) => void + testSuite: (persistence: PersistenceMode) => void ) => void; interface ApiDescribe { - (message: string, testSuite: (persistence: boolean) => void): void; + (message: string, testSuite: (persistence: PersistenceMode) => void): void; skip: ApiSuiteFunction; only: ApiSuiteFunction; } @@ -137,7 +227,7 @@ export function toIds(docSet: QuerySnapshot): string[] { } export function withTestDb( - persistence: boolean, + persistence: PersistenceMode, fn: (db: Firestore) => Promise ): Promise { return withTestDbs(persistence, 1, ([db]) => { @@ -145,50 +235,9 @@ export function withTestDb( }); } -export function withEnsuredEagerGcTestDb( - fn: (db: Firestore) => Promise -): Promise { - return withTestDbsSettings( - false, - DEFAULT_PROJECT_ID, - { ...DEFAULT_SETTINGS, cacheSizeBytes: 1 * 1024 * 1024 }, - 1, - async ([db]) => { - return fn(db); - } - ); -} - -export function withEnsuredLruGcTestDb( - persistence: boolean, - fn: (db: Firestore) => Promise -): Promise { - const newSettings = { ...DEFAULT_SETTINGS }; - if (persistence) { - newSettings.localCache = persistentLocalCache({ - cacheSizeBytes: 1 * 1024 * 1024 - }); - } else { - newSettings.localCache = memoryLocalCache({ - garbageCollector: memoryLruGarbageCollector({ - cacheSizeBytes: 1 * 1024 * 1024 - }) - }); - } - return withTestDbsSettings( - persistence, - DEFAULT_PROJECT_ID, - newSettings, - 1, - async ([db]) => { - return fn(db); - } - ); -} - /** Runs provided fn with a db for an alternate project id. */ export function withAlternateTestDb( - persistence: boolean, + persistence: PersistenceMode, fn: (db: Firestore) => Promise ): Promise { return withTestDbsSettings( @@ -203,7 +252,7 @@ export function withAlternateTestDb( } export function withTestDbs( - persistence: boolean, + persistence: PersistenceMode, numDbs: number, fn: (db: Firestore[]) => Promise ): Promise { @@ -216,7 +265,7 @@ export function withTestDbs( ); } export async function withTestDbsSettings( - persistence: boolean, + persistence: PersistenceMode, projectId: string, settings: PrivateSettings, numDbs: number, @@ -229,10 +278,7 @@ export async function withTestDbsSettings( const dbs: Firestore[] = []; for (let i = 0; i < numDbs; i++) { - const newSettings = { ...settings }; - if (persistence) { - newSettings.localCache = persistentLocalCache(); - } + const newSettings = { ...settings, localCache: persistence.toLocalCache() }; const db = newTestFirestore(newTestApp(projectId), newSettings); dbs.push(db); } @@ -250,7 +296,7 @@ export async function withTestDbsSettings( } export async function withNamedTestDbsOrSkipUnlessUsingEmulator( - persistence: boolean, + persistence: PersistenceMode, dbNames: string[], fn: (db: Firestore[]) => Promise ): Promise { @@ -264,10 +310,7 @@ export async function withNamedTestDbsOrSkipUnlessUsingEmulator( const app = newTestApp(DEFAULT_PROJECT_ID); const dbs: Firestore[] = []; for (const dbName of dbNames) { - const newSettings = { ...DEFAULT_SETTINGS }; - if (persistence) { - newSettings.localCache = persistentLocalCache(); - } + const newSettings = { ...DEFAULT_SETTINGS, localCache: persistence.toLocalCache() }; const db = newTestFirestore(app, newSettings, dbName); dbs.push(db); } @@ -285,7 +328,7 @@ export async function withNamedTestDbsOrSkipUnlessUsingEmulator( } export function withTestDoc( - persistence: boolean, + persistence: PersistenceMode, fn: (doc: DocumentReference, db: Firestore) => Promise ): Promise { return withTestDb(persistence, db => { @@ -294,7 +337,7 @@ export function withTestDoc( } export function withTestDocAndSettings( - persistence: boolean, + persistence: PersistenceMode, settings: PrivateSettings, fn: (doc: DocumentReference) => Promise ): Promise { @@ -315,7 +358,7 @@ export function withTestDocAndSettings( // `withTestDoc(..., docRef => { setDoc(docRef, initialData) ...});` that // otherwise is quite common. export function withTestDocAndInitialData( - persistence: boolean, + persistence: PersistenceMode, initialData: DocumentData | null, fn: (doc: DocumentReference, db: Firestore) => Promise ): Promise { @@ -330,7 +373,7 @@ export function withTestDocAndInitialData( } export function withTestCollection( - persistence: boolean, + persistence: PersistenceMode, docs: { [key: string]: DocumentData }, fn: (collection: CollectionReference, db: Firestore) => Promise ): Promise { @@ -338,7 +381,7 @@ export function withTestCollection( } export function withEmptyTestCollection( - persistence: boolean, + persistence: PersistenceMode, fn: (collection: CollectionReference, db: Firestore) => Promise ): Promise { return withTestCollection(persistence, {}, fn); @@ -347,7 +390,7 @@ export function withEmptyTestCollection( // TODO(mikelehen): Once we wipe the database between tests, we can probably // return the same collection every time. export function withTestCollectionSettings( - persistence: boolean, + persistence: PersistenceMode, settings: PrivateSettings, docs: { [key: string]: DocumentData }, fn: (collection: CollectionReference, db: Firestore) => Promise diff --git a/packages/firestore/test/integration/util/internal_helpers.ts b/packages/firestore/test/integration/util/internal_helpers.ts index 4abfcf78096..86ded6af3c1 100644 --- a/packages/firestore/test/integration/util/internal_helpers.ts +++ b/packages/firestore/test/integration/util/internal_helpers.ts @@ -42,7 +42,7 @@ import { limit, limitToLast } from './firebase_export'; -import { withTestDbsSettings } from './helpers'; +import { withTestDbsSettings, PersistenceMode } from './helpers'; import { DEFAULT_PROJECT_ID, DEFAULT_SETTINGS } from './settings'; export function asyncQueue(db: Firestore): AsyncQueueImpl { @@ -101,7 +101,7 @@ export class MockAuthCredentialsProvider extends EmptyAuthCredentialsProvider { } export function withMockCredentialProviderTestDb( - persistence: boolean, + persistence: PersistenceMode, fn: ( db: Firestore, mockCredential: MockAuthCredentialsProvider From 8d1853698a2467da71ac2e8ed603c1951466bb8b Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Wed, 28 Jun 2023 16:33:09 -0400 Subject: [PATCH 3/6] fixups --- packages/firestore/test/integration/api/query.test.ts | 2 +- packages/firestore/test/integration/api/validation.test.ts | 2 +- packages/firestore/test/integration/util/helpers.ts | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/firestore/test/integration/api/query.test.ts b/packages/firestore/test/integration/api/query.test.ts index 5ddad38d9d6..090077f2a85 100644 --- a/packages/firestore/test/integration/api/query.test.ts +++ b/packages/firestore/test/integration/api/query.test.ts @@ -2134,7 +2134,7 @@ apiDescribe('Queries', persistence => { // will _not_ send an existence filter. // TODO(b/272754156) Re-write this test using a snapshot listener instead // of calls to getDocs() and remove this check for disabled persistence. - if (!persistence) { + if (persistence.gc === 'eager') { return 'passed'; } diff --git a/packages/firestore/test/integration/api/validation.test.ts b/packages/firestore/test/integration/api/validation.test.ts index 0497e710576..a37ec36d988 100644 --- a/packages/firestore/test/integration/api/validation.test.ts +++ b/packages/firestore/test/integration/api/validation.test.ts @@ -237,7 +237,7 @@ apiDescribe('Validation:', persistence => { db => { // Calling `enablePersistence()` itself counts as use, so we should only // need this method when persistence is not enabled. - if (!persistence) { + if (persistence.storage === 'memory') { doc(db, 'foo/bar'); } expect(() => enableIndexedDbPersistence(db)).to.throw( diff --git a/packages/firestore/test/integration/util/helpers.ts b/packages/firestore/test/integration/util/helpers.ts index 852ee677956..719c8624662 100644 --- a/packages/firestore/test/integration/util/helpers.ts +++ b/packages/firestore/test/integration/util/helpers.ts @@ -288,7 +288,7 @@ export async function withTestDbsSettings( } finally { for (const db of dbs) { await terminate(db); - if (persistence) { + if (persistence.storage === 'indexeddb') { await clearIndexedDbPersistence(db); } } @@ -320,7 +320,7 @@ export async function withNamedTestDbsOrSkipUnlessUsingEmulator( } finally { for (const db of dbs) { await terminate(db); - if (persistence) { + if (persistence.storage === 'indexeddb') { await clearIndexedDbPersistence(db); } } From 5e375b27675baca44559cf9bbf478aa4871b741b Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Wed, 28 Jun 2023 16:33:45 -0400 Subject: [PATCH 4/6] yarn prettier --- .../test/integration/api/aggregation.test.ts | 2160 ++++++++--------- .../integration/api/array_transforms.test.ts | 135 +- .../test/integration/api/database.test.ts | 94 +- .../test/integration/api/query.test.ts | 749 +++--- .../integration/api_internal/database.test.ts | 5 +- .../api_internal/transaction.test.ts | 299 ++- .../integration/browser/indexeddb.test.ts | 7 +- .../test/integration/util/helpers.ts | 24 +- 8 files changed, 1742 insertions(+), 1731 deletions(-) diff --git a/packages/firestore/test/integration/api/aggregation.test.ts b/packages/firestore/test/integration/api/aggregation.test.ts index 8080cdfd43e..319e22f31a4 100644 --- a/packages/firestore/test/integration/api/aggregation.test.ts +++ b/packages/firestore/test/integration/api/aggregation.test.ts @@ -354,1201 +354,1193 @@ apiDescribe('Aggregation queries', persistence => { }); // TODO (sum/avg) enable these tests when sum/avg is supported by the backend -apiDescribe.skip( - 'Aggregation queries - sum / average', - persistence => { - it('can run sum query getAggregationFromServer', () => { - const testDocs = { - a: { author: 'authorA', title: 'titleA', pages: 100 }, - b: { author: 'authorB', title: 'titleB', pages: 50 } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - totalPages: sum('pages') - }); - expect(snapshot.data().totalPages).to.equal(150); +apiDescribe.skip('Aggregation queries - sum / average', persistence => { + it('can run sum query getAggregationFromServer', () => { + const testDocs = { + a: { author: 'authorA', title: 'titleA', pages: 100 }, + b: { author: 'authorB', title: 'titleB', pages: 50 } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalPages: sum('pages') }); + expect(snapshot.data().totalPages).to.equal(150); }); + }); - it('can run average query getAggregationFromServer', () => { - const testDocs = { - a: { author: 'authorA', title: 'titleA', pages: 100 }, - b: { author: 'authorB', title: 'titleB', pages: 50 } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - averagePages: average('pages') - }); - expect(snapshot.data().averagePages).to.equal(75); + it('can run average query getAggregationFromServer', () => { + const testDocs = { + a: { author: 'authorA', title: 'titleA', pages: 100 }, + b: { author: 'authorB', title: 'titleB', pages: 50 } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + averagePages: average('pages') }); + expect(snapshot.data().averagePages).to.equal(75); }); + }); - it('can get multiple aggregations using getAggregationFromServer', () => { - const testDocs = { - a: { author: 'authorA', title: 'titleA', pages: 100 }, - b: { author: 'authorB', title: 'titleB', pages: 50 } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - totalPages: sum('pages'), - averagePages: average('pages'), - count: count() - }); - expect(snapshot.data().totalPages).to.equal(150); - expect(snapshot.data().averagePages).to.equal(75); - expect(snapshot.data().count).to.equal(2); + it('can get multiple aggregations using getAggregationFromServer', () => { + const testDocs = { + a: { author: 'authorA', title: 'titleA', pages: 100 }, + b: { author: 'authorB', title: 'titleB', pages: 50 } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalPages: sum('pages'), + averagePages: average('pages'), + count: count() }); + expect(snapshot.data().totalPages).to.equal(150); + expect(snapshot.data().averagePages).to.equal(75); + expect(snapshot.data().count).to.equal(2); }); + }); - it('can get duplicate aggregations using getAggregationFromServer', () => { - const testDocs = { - a: { author: 'authorA', title: 'titleA', pages: 100 }, - b: { author: 'authorB', title: 'titleB', pages: 50 } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - totalPages: sum('pages'), - averagePages: average('pages'), - totalPagesX: sum('pages'), - averagePagesY: average('pages') - }); - expect(snapshot.data().totalPages).to.equal(150); - expect(snapshot.data().averagePages).to.equal(75); - expect(snapshot.data().totalPagesX).to.equal(150); - expect(snapshot.data().averagePagesY).to.equal(75); + it('can get duplicate aggregations using getAggregationFromServer', () => { + const testDocs = { + a: { author: 'authorA', title: 'titleA', pages: 100 }, + b: { author: 'authorB', title: 'titleB', pages: 50 } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalPages: sum('pages'), + averagePages: average('pages'), + totalPagesX: sum('pages'), + averagePagesY: average('pages') }); + expect(snapshot.data().totalPages).to.equal(150); + expect(snapshot.data().averagePages).to.equal(75); + expect(snapshot.data().totalPagesX).to.equal(150); + expect(snapshot.data().averagePagesY).to.equal(75); }); + }); - it('can perform max (5) aggregations using getAggregationFromServer', () => { - const testDocs = { - a: { author: 'authorA', title: 'titleA', pages: 100 }, - b: { author: 'authorB', title: 'titleB', pages: 50 } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - totalPages: sum('pages'), - averagePages: average('pages'), - count: count(), - totalPagesX: sum('pages'), - averagePagesY: average('pages') - }); - expect(snapshot.data().totalPages).to.equal(150); - expect(snapshot.data().averagePages).to.equal(75); - expect(snapshot.data().count).to.equal(2); - expect(snapshot.data().totalPagesX).to.equal(150); - expect(snapshot.data().averagePagesY).to.equal(75); + it('can perform max (5) aggregations using getAggregationFromServer', () => { + const testDocs = { + a: { author: 'authorA', title: 'titleA', pages: 100 }, + b: { author: 'authorB', title: 'titleB', pages: 50 } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalPages: sum('pages'), + averagePages: average('pages'), + count: count(), + totalPagesX: sum('pages'), + averagePagesY: average('pages') }); + expect(snapshot.data().totalPages).to.equal(150); + expect(snapshot.data().averagePages).to.equal(75); + expect(snapshot.data().count).to.equal(2); + expect(snapshot.data().totalPagesX).to.equal(150); + expect(snapshot.data().averagePagesY).to.equal(75); }); + }); - it('fails when exceeding the max (5) aggregations using getAggregationFromServer', () => { - const testDocs = { - a: { author: 'authorA', title: 'titleA', pages: 100 }, - b: { author: 'authorB', title: 'titleB', pages: 50 } - }; - return withTestCollection(persistence, testDocs, async coll => { - const promise = getAggregateFromServer(coll, { - totalPages: sum('pages'), - averagePages: average('pages'), - count: count(), - totalPagesX: sum('pages'), - averagePagesY: average('pages'), - countZ: count() - }); - - await expect(promise).to.eventually.be.rejectedWith( - /maximum number of aggregations/ - ); + it('fails when exceeding the max (5) aggregations using getAggregationFromServer', () => { + const testDocs = { + a: { author: 'authorA', title: 'titleA', pages: 100 }, + b: { author: 'authorB', title: 'titleB', pages: 50 } + }; + return withTestCollection(persistence, testDocs, async coll => { + const promise = getAggregateFromServer(coll, { + totalPages: sum('pages'), + averagePages: average('pages'), + count: count(), + totalPagesX: sum('pages'), + averagePagesY: average('pages'), + countZ: count() }); + + await expect(promise).to.eventually.be.rejectedWith( + /maximum number of aggregations/ + ); }); + }); - it('aggregate query supports collection groups', () => { - return withTestDb(persistence, async db => { - const collectionGroupId = doc(collection(db, 'aggregateQueryTest')).id; - const docPaths = [ - `${collectionGroupId}/cg-doc1`, - `abc/123/${collectionGroupId}/cg-doc2`, - `zzz${collectionGroupId}/cg-doc3`, - `abc/123/zzz${collectionGroupId}/cg-doc4`, - `abc/123/zzz/${collectionGroupId}` - ]; - const batch = writeBatch(db); - for (const docPath of docPaths) { - batch.set(doc(db, docPath), { x: 2 }); + it('aggregate query supports collection groups', () => { + return withTestDb(persistence, async db => { + const collectionGroupId = doc(collection(db, 'aggregateQueryTest')).id; + const docPaths = [ + `${collectionGroupId}/cg-doc1`, + `abc/123/${collectionGroupId}/cg-doc2`, + `zzz${collectionGroupId}/cg-doc3`, + `abc/123/zzz${collectionGroupId}/cg-doc4`, + `abc/123/zzz/${collectionGroupId}` + ]; + const batch = writeBatch(db); + for (const docPath of docPaths) { + batch.set(doc(db, docPath), { x: 2 }); + } + await batch.commit(); + const snapshot = await getAggregateFromServer( + collectionGroup(db, collectionGroupId), + { + count: count(), + sum: sum('x'), + avg: average('x') } - await batch.commit(); - const snapshot = await getAggregateFromServer( - collectionGroup(db, collectionGroupId), - { - count: count(), - sum: sum('x'), - avg: average('x') - } - ); - expect(snapshot.data().count).to.equal(2); - expect(snapshot.data().sum).to.equal(4); - expect(snapshot.data().avg).to.equal(2); - }); + ); + expect(snapshot.data().count).to.equal(2); + expect(snapshot.data().sum).to.equal(4); + expect(snapshot.data().avg).to.equal(2); }); + }); - it('performs aggregations on documents with all aggregated fields using getAggregationFromServer', () => { - const testDocs = { - a: { author: 'authorA', title: 'titleA', pages: 100, year: 1980 }, - b: { author: 'authorB', title: 'titleB', pages: 50, year: 2020 }, - c: { author: 'authorC', title: 'titleC', pages: 150, year: 2021 }, - d: { author: 'authorD', title: 'titleD', pages: 50 } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - totalPages: sum('pages'), - averagePages: average('pages'), - averageYear: average('year'), - count: count() - }); - expect(snapshot.data().totalPages).to.equal(300); - expect(snapshot.data().averagePages).to.equal(100); - expect(snapshot.data().averageYear).to.equal(2007); - expect(snapshot.data().count).to.equal(3); + it('performs aggregations on documents with all aggregated fields using getAggregationFromServer', () => { + const testDocs = { + a: { author: 'authorA', title: 'titleA', pages: 100, year: 1980 }, + b: { author: 'authorB', title: 'titleB', pages: 50, year: 2020 }, + c: { author: 'authorC', title: 'titleC', pages: 150, year: 2021 }, + d: { author: 'authorD', title: 'titleD', pages: 50 } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalPages: sum('pages'), + averagePages: average('pages'), + averageYear: average('year'), + count: count() }); + expect(snapshot.data().totalPages).to.equal(300); + expect(snapshot.data().averagePages).to.equal(100); + expect(snapshot.data().averageYear).to.equal(2007); + expect(snapshot.data().count).to.equal(3); }); + }); - it('performs aggregates on multiple fields where one aggregate could cause short-circuit due to NaN using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: 5 - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: 4 - }, - c: { - author: 'authorC', - title: 'titleC', - pages: 100, - year: 1980, - rating: Number.NaN - }, - d: { - author: 'authorD', - title: 'titleD', - pages: 50, - year: 2020, - rating: 0 - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - totalRating: sum('rating'), - totalPages: sum('pages'), - averageYear: average('year') - }); - expect(snapshot.data().totalRating).to.be.NaN; - expect(snapshot.data().totalPages).to.equal(300); - expect(snapshot.data().averageYear).to.equal(2000); + it('performs aggregates on multiple fields where one aggregate could cause short-circuit due to NaN using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: 5 + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 4 + }, + c: { + author: 'authorC', + title: 'titleC', + pages: 100, + year: 1980, + rating: Number.NaN + }, + d: { + author: 'authorD', + title: 'titleD', + pages: 50, + year: 2020, + rating: 0 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalRating: sum('rating'), + totalPages: sum('pages'), + averageYear: average('year') }); + expect(snapshot.data().totalRating).to.be.NaN; + expect(snapshot.data().totalPages).to.equal(300); + expect(snapshot.data().averageYear).to.equal(2000); }); + }); - it('returns undefined when getting the result of an unrequested aggregation', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: 5 - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: 4 - }, - c: { - author: 'authorC', - title: 'titleC', - pages: 100, - year: 1980, - rating: 3 - }, - d: { - author: 'authorD', - title: 'titleD', - pages: 50, - year: 2020, - rating: 0 + it('returns undefined when getting the result of an unrequested aggregation', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: 5 + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 4 + }, + c: { + author: 'authorC', + title: 'titleC', + pages: 100, + year: 1980, + rating: 3 + }, + d: { + author: 'authorD', + title: 'titleD', + pages: 50, + year: 2020, + rating: 0 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer( + query(coll, where('pages', '>', 200)), + { + totalRating: sum('rating'), + averageRating: average('rating') } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer( - query(coll, where('pages', '>', 200)), - { - totalRating: sum('rating'), - averageRating: average('rating') - } - ); + ); - // @ts-expect-error - const totalPages = snapshot.data().totalPages; - expect(totalPages).to.equal(undefined); - }); + // @ts-expect-error + const totalPages = snapshot.data().totalPages; + expect(totalPages).to.equal(undefined); }); + }); - it('performs aggregates when using `in` operator getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: 5 - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: 4 - }, - c: { - author: 'authorC', - title: 'titleC', - pages: 100, - year: 1980, - rating: 3 - }, - d: { - author: 'authorD', - title: 'titleD', - pages: 50, - year: 2020, - rating: 0 + it('performs aggregates when using `in` operator getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: 5 + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 4 + }, + c: { + author: 'authorC', + title: 'titleC', + pages: 100, + year: 1980, + rating: 3 + }, + d: { + author: 'authorD', + title: 'titleD', + pages: 50, + year: 2020, + rating: 0 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer( + query(coll, where('rating', 'in', [5, 3])), + { + totalRating: sum('rating'), + averageRating: average('rating'), + totalPages: sum('pages'), + averagePages: average('pages'), + countOfDocs: count() } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer( - query(coll, where('rating', 'in', [5, 3])), - { - totalRating: sum('rating'), - averageRating: average('rating'), - totalPages: sum('pages'), - averagePages: average('pages'), - countOfDocs: count() - } - ); - expect(snapshot.data().totalRating).to.equal(8); - expect(snapshot.data().averageRating).to.equal(4); - expect(snapshot.data().totalPages).to.equal(200); - expect(snapshot.data().averagePages).to.equal(100); - expect(snapshot.data().countOfDocs).to.equal(2); - }); + ); + expect(snapshot.data().totalRating).to.equal(8); + expect(snapshot.data().averageRating).to.equal(4); + expect(snapshot.data().totalPages).to.equal(200); + expect(snapshot.data().averagePages).to.equal(100); + expect(snapshot.data().countOfDocs).to.equal(2); }); + }); - it('performs aggregates when using `array-contains-any` operator getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: [5, 1000] - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: [4] - }, - c: { - author: 'authorC', - title: 'titleC', - pages: 100, - year: 1980, - rating: [2222, 3] - }, - d: { - author: 'authorD', - title: 'titleD', - pages: 50, - year: 2020, - rating: [0] + it('performs aggregates when using `array-contains-any` operator getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: [5, 1000] + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: [4] + }, + c: { + author: 'authorC', + title: 'titleC', + pages: 100, + year: 1980, + rating: [2222, 3] + }, + d: { + author: 'authorD', + title: 'titleD', + pages: 50, + year: 2020, + rating: [0] + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer( + query(coll, where('rating', 'array-contains-any', [5, 3])), + { + totalRating: sum('rating'), + averageRating: average('rating'), + totalPages: sum('pages'), + averagePages: average('pages'), + countOfDocs: count() } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer( - query(coll, where('rating', 'array-contains-any', [5, 3])), - { - totalRating: sum('rating'), - averageRating: average('rating'), - totalPages: sum('pages'), - averagePages: average('pages'), - countOfDocs: count() - } - ); - expect(snapshot.data().totalRating).to.equal(0); - expect(snapshot.data().averageRating).to.be.null; - expect(snapshot.data().totalPages).to.equal(200); - expect(snapshot.data().averagePages).to.equal(100); - expect(snapshot.data().countOfDocs).to.equal(2); - }); + ); + expect(snapshot.data().totalRating).to.equal(0); + expect(snapshot.data().averageRating).to.be.null; + expect(snapshot.data().totalPages).to.equal(200); + expect(snapshot.data().averagePages).to.equal(100); + expect(snapshot.data().countOfDocs).to.equal(2); }); + }); - it('performs aggregations on nested map values using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - metadata: { pages: 100, rating: { critic: 2, user: 5 } } - }, - b: { - author: 'authorB', - title: 'titleB', - metadata: { pages: 50, rating: { critic: 4, user: 4 } } - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - totalPages: sum('metadata.pages'), - averagePages: average('metadata.pages'), - averageCriticRating: average('metadata.rating.critic'), - totalUserRating: sum('metadata.rating.user'), - count: count() - }); - expect(snapshot.data().totalPages).to.equal(150); - expect(snapshot.data().averagePages).to.equal(75); - expect(snapshot.data().averageCriticRating).to.equal(3); - expect(snapshot.data().totalUserRating).to.equal(9); - expect(snapshot.data().count).to.equal(2); + it('performs aggregations on nested map values using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + metadata: { pages: 100, rating: { critic: 2, user: 5 } } + }, + b: { + author: 'authorB', + title: 'titleB', + metadata: { pages: 50, rating: { critic: 4, user: 4 } } + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalPages: sum('metadata.pages'), + averagePages: average('metadata.pages'), + averageCriticRating: average('metadata.rating.critic'), + totalUserRating: sum('metadata.rating.user'), + count: count() }); + expect(snapshot.data().totalPages).to.equal(150); + expect(snapshot.data().averagePages).to.equal(75); + expect(snapshot.data().averageCriticRating).to.equal(3); + expect(snapshot.data().totalUserRating).to.equal(9); + expect(snapshot.data().count).to.equal(2); }); + }); - it('performs sum that results in float using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: 5 - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: 4.5 - }, - c: { - author: 'authorB', - title: 'titleB', - pages: 150, - year: 2021, - rating: 3 - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - totalRating: sum('rating') - }); - expect(snapshot.data().totalRating).to.equal(12.5); + it('performs sum that results in float using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: 5 + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 4.5 + }, + c: { + author: 'authorB', + title: 'titleB', + pages: 150, + year: 2021, + rating: 3 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalRating: sum('rating') }); + expect(snapshot.data().totalRating).to.equal(12.5); }); + }); - it('performs sum of ints and floats that results in an int using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: 5 - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: 4.5 - }, - c: { - author: 'authorB', - title: 'titleB', - pages: 150, - year: 2021, - rating: 3.5 - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - totalRating: sum('rating') - }); - expect(snapshot.data().totalRating).to.equal(13); + it('performs sum of ints and floats that results in an int using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: 5 + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 4.5 + }, + c: { + author: 'authorB', + title: 'titleB', + pages: 150, + year: 2021, + rating: 3.5 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalRating: sum('rating') }); + expect(snapshot.data().totalRating).to.equal(13); }); + }); - it('performs sum that overflows max int using getAggregationFromServer', () => { - // A large value that will be represented as a Long on the server, but - // doubling (2x) this value must overflow Long and force the result to be - // represented as a Double type on the server. - const maxLong = Math.pow(2, 63) - 1; - - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: maxLong - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: maxLong - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - totalRating: sum('rating') - }); - expect(snapshot.data().totalRating).to.equal(maxLong + maxLong); + it('performs sum that overflows max int using getAggregationFromServer', () => { + // A large value that will be represented as a Long on the server, but + // doubling (2x) this value must overflow Long and force the result to be + // represented as a Double type on the server. + const maxLong = Math.pow(2, 63) - 1; + + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: maxLong + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: maxLong + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalRating: sum('rating') }); + expect(snapshot.data().totalRating).to.equal(maxLong + maxLong); }); + }); - it('performs sum that can overflow integer values during accumulation using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: Number.MAX_SAFE_INTEGER - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: 1 - }, - c: { - author: 'authorC', - title: 'titleC', - pages: 50, - year: 2020, - rating: -101 - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - totalRating: sum('rating') - }); - expect(snapshot.data().totalRating).to.equal( - Number.MAX_SAFE_INTEGER - 100 - ); + it('performs sum that can overflow integer values during accumulation using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: Number.MAX_SAFE_INTEGER + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 1 + }, + c: { + author: 'authorC', + title: 'titleC', + pages: 50, + year: 2020, + rating: -101 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalRating: sum('rating') }); + expect(snapshot.data().totalRating).to.equal( + Number.MAX_SAFE_INTEGER - 100 + ); }); + }); - it('performs sum that is negative using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: Number.MAX_SAFE_INTEGER - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: Number.MIN_SAFE_INTEGER - }, - c: { - author: 'authorC', - title: 'titleC', - pages: 50, - year: 2020, - rating: -101 - }, - d: { - author: 'authorD', - title: 'titleD', - pages: 50, - year: 2020, - rating: -10000 - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - totalRating: sum('rating') - }); - expect(snapshot.data().totalRating).to.equal(-10101); + it('performs sum that is negative using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: Number.MAX_SAFE_INTEGER + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: Number.MIN_SAFE_INTEGER + }, + c: { + author: 'authorC', + title: 'titleC', + pages: 50, + year: 2020, + rating: -101 + }, + d: { + author: 'authorD', + title: 'titleD', + pages: 50, + year: 2020, + rating: -10000 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalRating: sum('rating') }); + expect(snapshot.data().totalRating).to.equal(-10101); }); + }); - it('performs sum that is positive infinity using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: Number.MAX_VALUE - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: Number.MAX_VALUE - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - totalRating: sum('rating') - }); - expect(snapshot.data().totalRating).to.equal(Number.POSITIVE_INFINITY); + it('performs sum that is positive infinity using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: Number.MAX_VALUE + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: Number.MAX_VALUE + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalRating: sum('rating') }); + expect(snapshot.data().totalRating).to.equal(Number.POSITIVE_INFINITY); }); + }); - it('performs sum that is positive infinity using getAggregationFromServer v2', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: Number.MAX_VALUE - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: 1e293 - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - totalRating: sum('rating') - }); - expect(snapshot.data().totalRating).to.equal(Number.POSITIVE_INFINITY); + it('performs sum that is positive infinity using getAggregationFromServer v2', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: Number.MAX_VALUE + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 1e293 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalRating: sum('rating') }); + expect(snapshot.data().totalRating).to.equal(Number.POSITIVE_INFINITY); }); + }); - it('performs sum that is negative infinity using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: -Number.MAX_VALUE - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: -Number.MAX_VALUE - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - totalRating: sum('rating') - }); - expect(snapshot.data().totalRating).to.equal(Number.NEGATIVE_INFINITY); + it('performs sum that is negative infinity using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: -Number.MAX_VALUE + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: -Number.MAX_VALUE + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalRating: sum('rating') }); + expect(snapshot.data().totalRating).to.equal(Number.NEGATIVE_INFINITY); }); + }); - it('performs sum that is valid but could overflow during aggregation using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: Number.MAX_VALUE - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: Number.MAX_VALUE - }, - c: { - author: 'authorC', - title: 'titleC', - pages: 100, - year: 1980, - rating: -Number.MAX_VALUE - }, - d: { - author: 'authorD', - title: 'titleD', - pages: 50, - year: 2020, - rating: -Number.MAX_VALUE - }, - e: { - author: 'authorE', - title: 'titleE', - pages: 100, - year: 1980, - rating: Number.MAX_VALUE - }, - f: { - author: 'authorF', - title: 'titleF', - pages: 50, - year: 2020, - rating: -Number.MAX_VALUE - }, - g: { - author: 'authorG', - title: 'titleG', - pages: 100, - year: 1980, - rating: -Number.MAX_VALUE - }, - h: { - author: 'authorH', - title: 'titleDH', - pages: 50, - year: 2020, - rating: Number.MAX_VALUE - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - totalRating: sum('rating') - }); - expect(snapshot.data().totalRating).to.equal(0); + it('performs sum that is valid but could overflow during aggregation using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: Number.MAX_VALUE + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: Number.MAX_VALUE + }, + c: { + author: 'authorC', + title: 'titleC', + pages: 100, + year: 1980, + rating: -Number.MAX_VALUE + }, + d: { + author: 'authorD', + title: 'titleD', + pages: 50, + year: 2020, + rating: -Number.MAX_VALUE + }, + e: { + author: 'authorE', + title: 'titleE', + pages: 100, + year: 1980, + rating: Number.MAX_VALUE + }, + f: { + author: 'authorF', + title: 'titleF', + pages: 50, + year: 2020, + rating: -Number.MAX_VALUE + }, + g: { + author: 'authorG', + title: 'titleG', + pages: 100, + year: 1980, + rating: -Number.MAX_VALUE + }, + h: { + author: 'authorH', + title: 'titleDH', + pages: 50, + year: 2020, + rating: Number.MAX_VALUE + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalRating: sum('rating') }); + expect(snapshot.data().totalRating).to.equal(0); }); + }); - it('performs sum that includes NaN using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: 5 - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: 4 - }, - c: { - author: 'authorC', - title: 'titleC', - pages: 100, - year: 1980, - rating: Number.NaN - }, - d: { - author: 'authorD', - title: 'titleD', - pages: 50, - year: 2020, - rating: 0 - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - totalRating: sum('rating') - }); - expect(snapshot.data().totalRating).to.be.NaN; + it('performs sum that includes NaN using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: 5 + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 4 + }, + c: { + author: 'authorC', + title: 'titleC', + pages: 100, + year: 1980, + rating: Number.NaN + }, + d: { + author: 'authorD', + title: 'titleD', + pages: 50, + year: 2020, + rating: 0 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalRating: sum('rating') }); + expect(snapshot.data().totalRating).to.be.NaN; }); + }); - it('performs sum over a result set of zero documents using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: 5 - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: 4 - }, - c: { - author: 'authorC', - title: 'titleC', - pages: 100, - year: 1980, - rating: 3 - }, - d: { - author: 'authorD', - title: 'titleD', - pages: 50, - year: 2020, - rating: 0 + it('performs sum over a result set of zero documents using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: 5 + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 4 + }, + c: { + author: 'authorC', + title: 'titleC', + pages: 100, + year: 1980, + rating: 3 + }, + d: { + author: 'authorD', + title: 'titleD', + pages: 50, + year: 2020, + rating: 0 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer( + query(coll, where('pages', '>', 200)), + { + totalRating: sum('rating') } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer( - query(coll, where('pages', '>', 200)), - { - totalRating: sum('rating') - } - ); - expect(snapshot.data().totalRating).to.equal(0); - }); + ); + expect(snapshot.data().totalRating).to.equal(0); }); + }); - it('performs sum only on numeric fields using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: 5 - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: 4 - }, - c: { - author: 'authorC', - title: 'titleC', - pages: 100, - year: 1980, - rating: '3' - }, - d: { - author: 'authorD', - title: 'titleD', - pages: 50, - year: 2020, - rating: 1 - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - totalRating: sum('rating'), - countOfDocs: count() - }); - expect(snapshot.data().totalRating).to.equal(10); - expect(snapshot.data().countOfDocs).to.equal(4); + it('performs sum only on numeric fields using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: 5 + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 4 + }, + c: { + author: 'authorC', + title: 'titleC', + pages: 100, + year: 1980, + rating: '3' + }, + d: { + author: 'authorD', + title: 'titleD', + pages: 50, + year: 2020, + rating: 1 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalRating: sum('rating'), + countOfDocs: count() }); + expect(snapshot.data().totalRating).to.equal(10); + expect(snapshot.data().countOfDocs).to.equal(4); }); + }); - it('performs sum of min IEEE754 using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: Number.MIN_VALUE - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - totalRating: sum('rating') - }); - expect(snapshot.data().totalRating).to.equal(Number.MIN_VALUE); + it('performs sum of min IEEE754 using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: Number.MIN_VALUE + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + totalRating: sum('rating') }); + expect(snapshot.data().totalRating).to.equal(Number.MIN_VALUE); }); + }); - it('performs average of ints that results in an int using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: 10 - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: 5 - }, - c: { - author: 'authorB', - title: 'titleB', - pages: 150, - year: 2021, - rating: 0 - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - averageRating: average('rating') - }); - expect(snapshot.data().averageRating).to.equal(5); + it('performs average of ints that results in an int using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: 10 + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 5 + }, + c: { + author: 'authorB', + title: 'titleB', + pages: 150, + year: 2021, + rating: 0 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + averageRating: average('rating') }); + expect(snapshot.data().averageRating).to.equal(5); }); + }); - it('performs average of floats that results in an int using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: 10.5 - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: 9.5 - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - averageRating: average('rating') - }); - expect(snapshot.data().averageRating).to.equal(10); + it('performs average of floats that results in an int using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: 10.5 + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 9.5 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + averageRating: average('rating') }); + expect(snapshot.data().averageRating).to.equal(10); }); + }); - it('performs average of floats and ints that results in an int using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: 10 - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: 9.5 - }, - c: { - author: 'authorC', - title: 'titleC', - pages: 150, - year: 2021, - rating: 10.5 - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - averageRating: average('rating') - }); - expect(snapshot.data().averageRating).to.equal(10); + it('performs average of floats and ints that results in an int using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: 10 + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 9.5 + }, + c: { + author: 'authorC', + title: 'titleC', + pages: 150, + year: 2021, + rating: 10.5 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + averageRating: average('rating') }); + expect(snapshot.data().averageRating).to.equal(10); }); + }); - it('performs average of float that results in float using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: 5.5 - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: 4.5 - }, - c: { - author: 'authorB', - title: 'titleB', - pages: 150, - year: 2021, - rating: 3.5 - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - averageRating: average('rating') - }); - expect(snapshot.data().averageRating).to.equal(4.5); + it('performs average of float that results in float using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: 5.5 + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 4.5 + }, + c: { + author: 'authorB', + title: 'titleB', + pages: 150, + year: 2021, + rating: 3.5 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + averageRating: average('rating') }); + expect(snapshot.data().averageRating).to.equal(4.5); }); + }); - it('performs average of floats and ints that results in a float using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: 8.6 - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: 9 - }, - c: { - author: 'authorC', - title: 'titleC', - pages: 150, - year: 2021, - rating: 10 - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - averageRating: average('rating') - }); - expect(snapshot.data().averageRating).to.be.approximately( - 9.2, - 0.0000001 - ); + it('performs average of floats and ints that results in a float using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: 8.6 + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 9 + }, + c: { + author: 'authorC', + title: 'titleC', + pages: 150, + year: 2021, + rating: 10 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + averageRating: average('rating') }); + expect(snapshot.data().averageRating).to.be.approximately(9.2, 0.0000001); }); + }); - it('performs average of ints that results in a float using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: 10 - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: 9 - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - averageRating: average('rating') - }); - expect(snapshot.data().averageRating).to.equal(9.5); + it('performs average of ints that results in a float using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: 10 + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 9 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + averageRating: average('rating') }); + expect(snapshot.data().averageRating).to.equal(9.5); }); + }); - it('performs average causing underflow using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: Number.MIN_VALUE - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: 0 - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - averageRating: average('rating') - }); - expect(snapshot.data().averageRating).to.equal(0); + it('performs average causing underflow using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: Number.MIN_VALUE + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 0 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + averageRating: average('rating') }); + expect(snapshot.data().averageRating).to.equal(0); }); + }); - it('performs average of min IEEE754 using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: Number.MIN_VALUE - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - averageRating: average('rating') - }); - expect(snapshot.data().averageRating).to.equal(Number.MIN_VALUE); + it('performs average of min IEEE754 using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: Number.MIN_VALUE + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + averageRating: average('rating') }); + expect(snapshot.data().averageRating).to.equal(Number.MIN_VALUE); }); + }); - it('performs average that overflows IEEE754 during accumulation using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: Number.MAX_VALUE - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: Number.MAX_VALUE - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - averageRating: average('rating') - }); - expect(snapshot.data().averageRating).to.equal( - Number.POSITIVE_INFINITY - ); + it('performs average that overflows IEEE754 during accumulation using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: Number.MAX_VALUE + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: Number.MAX_VALUE + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + averageRating: average('rating') }); + expect(snapshot.data().averageRating).to.equal(Number.POSITIVE_INFINITY); }); + }); - it('performs average that includes NaN using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: 5 - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: 4 - }, - c: { - author: 'authorC', - title: 'titleC', - pages: 100, - year: 1980, - rating: Number.NaN - }, - d: { - author: 'authorD', - title: 'titleD', - pages: 50, - year: 2020, - rating: 0 - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - averageRating: average('rating') - }); - expect(snapshot.data().averageRating).to.be.NaN; + it('performs average that includes NaN using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: 5 + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 4 + }, + c: { + author: 'authorC', + title: 'titleC', + pages: 100, + year: 1980, + rating: Number.NaN + }, + d: { + author: 'authorD', + title: 'titleD', + pages: 50, + year: 2020, + rating: 0 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + averageRating: average('rating') }); + expect(snapshot.data().averageRating).to.be.NaN; }); + }); - it('performs average over a result set of zero documents using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: 5 - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: 4 - }, - c: { - author: 'authorC', - title: 'titleC', - pages: 100, - year: 1980, - rating: 3 - }, - d: { - author: 'authorD', - title: 'titleD', - pages: 50, - year: 2020, - rating: 0 + it('performs average over a result set of zero documents using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: 5 + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 4 + }, + c: { + author: 'authorC', + title: 'titleC', + pages: 100, + year: 1980, + rating: 3 + }, + d: { + author: 'authorD', + title: 'titleD', + pages: 50, + year: 2020, + rating: 0 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer( + query(coll, where('pages', '>', 200)), + { + averageRating: average('rating') } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer( - query(coll, where('pages', '>', 200)), - { - averageRating: average('rating') - } - ); - expect(snapshot.data().averageRating).to.be.null; - }); + ); + expect(snapshot.data().averageRating).to.be.null; }); + }); - it('performs average only on numeric fields using getAggregationFromServer', () => { - const testDocs = { - a: { - author: 'authorA', - title: 'titleA', - pages: 100, - year: 1980, - rating: 5 - }, - b: { - author: 'authorB', - title: 'titleB', - pages: 50, - year: 2020, - rating: 4 - }, - c: { - author: 'authorC', - title: 'titleC', - pages: 100, - year: 1980, - rating: '3' - }, - d: { - author: 'authorD', - title: 'titleD', - pages: 50, - year: 2020, - rating: 6 - } - }; - return withTestCollection(persistence, testDocs, async coll => { - const snapshot = await getAggregateFromServer(coll, { - averageRating: average('rating'), - countOfDocs: count() - }); - expect(snapshot.data().averageRating).to.equal(5); - expect(snapshot.data().countOfDocs).to.equal(4); + it('performs average only on numeric fields using getAggregationFromServer', () => { + const testDocs = { + a: { + author: 'authorA', + title: 'titleA', + pages: 100, + year: 1980, + rating: 5 + }, + b: { + author: 'authorB', + title: 'titleB', + pages: 50, + year: 2020, + rating: 4 + }, + c: { + author: 'authorC', + title: 'titleC', + pages: 100, + year: 1980, + rating: '3' + }, + d: { + author: 'authorD', + title: 'titleD', + pages: 50, + year: 2020, + rating: 6 + } + }; + return withTestCollection(persistence, testDocs, async coll => { + const snapshot = await getAggregateFromServer(coll, { + averageRating: average('rating'), + countOfDocs: count() }); + expect(snapshot.data().averageRating).to.equal(5); + expect(snapshot.data().countOfDocs).to.equal(4); }); - } -); + }); +}); diff --git a/packages/firestore/test/integration/api/array_transforms.test.ts b/packages/firestore/test/integration/api/array_transforms.test.ts index 3ad8c5690a3..4f66639b239 100644 --- a/packages/firestore/test/integration/api/array_transforms.test.ts +++ b/packages/firestore/test/integration/api/array_transforms.test.ts @@ -32,11 +32,7 @@ import { arrayUnion, FirestoreError } from '../util/firebase_export'; -import { - apiDescribe, - withTestDb, - withTestDoc -} from '../util/helpers'; +import { apiDescribe, withTestDb, withTestDoc } from '../util/helpers'; addEqualityMatcher(); @@ -168,76 +164,79 @@ apiDescribe('Array Transforms:', persistence => { * being enabled so documents remain in the cache after the write. */ // eslint-disable-next-line no-restricted-properties - (persistence.gc === 'lru' ? describe : describe.skip)('Server Application: ', () => { - it('set() with no cached base doc', async () => { - await withTestDoc(persistence, async docRef => { - await setDoc(docRef, { array: arrayUnion(1, 2) }); - const snapshot = await getDocFromCache(docRef); - expect(snapshot.data()).to.deep.equal({ array: [1, 2] }); - }); - }); - - it('update() with no cached base doc', async () => { - let path: string | null = null; - // Write an initial document in an isolated Firestore instance so it's not - // stored in our cache - await withTestDoc(persistence, async docRef => { - path = docRef.path; - await setDoc(docRef, { array: [42] }); - }); - - await withTestDb(persistence, async db => { - const docRef = doc(db, path!); - await updateDoc(docRef, { array: arrayUnion(1, 2) }); - - // Nothing should be cached since it was an update and we had no base - // doc. - let errCaught = false; - try { - await getDocFromCache(docRef); - } catch (err) { - expect((err as FirestoreError).code).to.equal('unavailable'); - errCaught = true; - } - expect(errCaught).to.be.true; + (persistence.gc === 'lru' ? describe : describe.skip)( + 'Server Application: ', + () => { + it('set() with no cached base doc', async () => { + await withTestDoc(persistence, async docRef => { + await setDoc(docRef, { array: arrayUnion(1, 2) }); + const snapshot = await getDocFromCache(docRef); + expect(snapshot.data()).to.deep.equal({ array: [1, 2] }); + }); }); - }); - it('set(..., {merge}) with no cached based doc', async () => { - let path: string | null = null; - // Write an initial document in an isolated Firestore instance so it's not - // stored in our cache - await withTestDoc(persistence, async docRef => { - path = docRef.path; - await setDoc(docRef, { array: [42] }); + it('update() with no cached base doc', async () => { + let path: string | null = null; + // Write an initial document in an isolated Firestore instance so it's not + // stored in our cache + await withTestDoc(persistence, async docRef => { + path = docRef.path; + await setDoc(docRef, { array: [42] }); + }); + + await withTestDb(persistence, async db => { + const docRef = doc(db, path!); + await updateDoc(docRef, { array: arrayUnion(1, 2) }); + + // Nothing should be cached since it was an update and we had no base + // doc. + let errCaught = false; + try { + await getDocFromCache(docRef); + } catch (err) { + expect((err as FirestoreError).code).to.equal('unavailable'); + errCaught = true; + } + expect(errCaught).to.be.true; + }); }); - await withTestDb(persistence, async db => { - const docRef = doc(db, path!); - await setDoc(docRef, { array: arrayUnion(1, 2) }, { merge: true }); - - // Document will be cached but we'll be missing 42. - const snapshot = await getDocFromCache(docRef); - expect(snapshot.data()).to.deep.equal({ array: [1, 2] }); + it('set(..., {merge}) with no cached based doc', async () => { + let path: string | null = null; + // Write an initial document in an isolated Firestore instance so it's not + // stored in our cache + await withTestDoc(persistence, async docRef => { + path = docRef.path; + await setDoc(docRef, { array: [42] }); + }); + + await withTestDb(persistence, async db => { + const docRef = doc(db, path!); + await setDoc(docRef, { array: arrayUnion(1, 2) }, { merge: true }); + + // Document will be cached but we'll be missing 42. + const snapshot = await getDocFromCache(docRef); + expect(snapshot.data()).to.deep.equal({ array: [1, 2] }); + }); }); - }); - it('update() with cached base doc using arrayUnion()', async () => { - await withTestDoc(persistence, async docRef => { - await setDoc(docRef, { array: [42] }); - await updateDoc(docRef, { array: arrayUnion(1, 2) }); - const snapshot = await getDocFromCache(docRef); - expect(snapshot.data()).to.deep.equal({ array: [42, 1, 2] }); + it('update() with cached base doc using arrayUnion()', async () => { + await withTestDoc(persistence, async docRef => { + await setDoc(docRef, { array: [42] }); + await updateDoc(docRef, { array: arrayUnion(1, 2) }); + const snapshot = await getDocFromCache(docRef); + expect(snapshot.data()).to.deep.equal({ array: [42, 1, 2] }); + }); }); - }); - it('update() with cached base doc using arrayRemove()', async () => { - await withTestDoc(persistence, async docRef => { - await setDoc(docRef, { array: [42, 1, 2] }); - await updateDoc(docRef, { array: arrayRemove(1, 2) }); - const snapshot = await getDocFromCache(docRef); - expect(snapshot.data()).to.deep.equal({ array: [42] }); + it('update() with cached base doc using arrayRemove()', async () => { + await withTestDoc(persistence, async docRef => { + await setDoc(docRef, { array: [42, 1, 2] }); + await updateDoc(docRef, { array: arrayRemove(1, 2) }); + const snapshot = await getDocFromCache(docRef); + expect(snapshot.data()).to.deep.equal({ array: [42] }); + }); }); - }); - }); + } + ); }); diff --git a/packages/firestore/test/integration/api/database.test.ts b/packages/firestore/test/integration/api/database.test.ts index 43116a439fa..211e4913c5d 100644 --- a/packages/firestore/test/integration/api/database.test.ts +++ b/packages/firestore/test/integration/api/database.test.ts @@ -151,29 +151,32 @@ apiDescribe('Database', persistence => { }); // eslint-disable-next-line no-restricted-properties - (persistence.gc === 'lru' ? it : it.skip)('can update an unknown document', () => { - return withTestDbs(persistence, 2, async ([reader, writer]) => { - const writerRef = doc(collection(writer, 'collection')); - const readerRef = doc(collection(reader, 'collection'), writerRef.id); - await setDoc(writerRef, { a: 'a' }); - await updateDoc(readerRef, { b: 'b' }); - await getDocFromCache(writerRef).then( - doc => expect(doc.exists()).to.be.true - ); - await getDocFromCache(readerRef).then( - () => { - expect.fail('Expected cache miss'); - }, - err => expect(err.code).to.be.equal('unavailable') - ); - await getDoc(writerRef).then(doc => - expect(doc.data()).to.deep.equal({ a: 'a', b: 'b' }) - ); - await getDoc(readerRef).then(doc => - expect(doc.data()).to.deep.equal({ a: 'a', b: 'b' }) - ); - }); - }); + (persistence.gc === 'lru' ? it : it.skip)( + 'can update an unknown document', + () => { + return withTestDbs(persistence, 2, async ([reader, writer]) => { + const writerRef = doc(collection(writer, 'collection')); + const readerRef = doc(collection(reader, 'collection'), writerRef.id); + await setDoc(writerRef, { a: 'a' }); + await updateDoc(readerRef, { b: 'b' }); + await getDocFromCache(writerRef).then( + doc => expect(doc.exists()).to.be.true + ); + await getDocFromCache(readerRef).then( + () => { + expect.fail('Expected cache miss'); + }, + err => expect(err.code).to.be.equal('unavailable') + ); + await getDoc(writerRef).then(doc => + expect(doc.data()).to.deep.equal({ a: 'a', b: 'b' }) + ); + await getDoc(readerRef).then(doc => + expect(doc.data()).to.deep.equal({ a: 'a', b: 'b' }) + ); + }); + } + ); it('can merge data with an existing document using set', () => { return withTestDoc(persistence, doc => { @@ -1093,32 +1096,35 @@ apiDescribe('Database', persistence => { }); // eslint-disable-next-line no-restricted-properties - (persistence.gc === 'lru' ? it : it.skip)('offline writes are sent after restart', () => { - return withTestDoc(persistence, async (docRef, firestore) => { - const app = firestore.app; - const name = app.name; - const options = app.options; + (persistence.gc === 'lru' ? it : it.skip)( + 'offline writes are sent after restart', + () => { + return withTestDoc(persistence, async (docRef, firestore) => { + const app = firestore.app; + const name = app.name; + const options = app.options; - await disableNetwork(firestore); + await disableNetwork(firestore); - // We are merely adding to the cache. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - setDoc(docRef, { foo: 'bar' }); + // We are merely adding to the cache. + // eslint-disable-next-line @typescript-eslint/no-floating-promises + setDoc(docRef, { foo: 'bar' }); - await deleteApp(app); + await deleteApp(app); - const firestore2 = newTestFirestore( - newTestApp(options.projectId!, name), - DEFAULT_SETTINGS - ); - await enableIndexedDbPersistence(firestore2); - await waitForPendingWrites(firestore2); - const doc2 = await getDoc(doc(firestore2, docRef.path)); + const firestore2 = newTestFirestore( + newTestApp(options.projectId!, name), + DEFAULT_SETTINGS + ); + await enableIndexedDbPersistence(firestore2); + await waitForPendingWrites(firestore2); + const doc2 = await getDoc(doc(firestore2, docRef.path)); - expect(doc2.exists()).to.be.true; - expect(doc2.metadata.hasPendingWrites).to.be.false; - }); - }); + expect(doc2.exists()).to.be.true; + expect(doc2.metadata.hasPendingWrites).to.be.false; + }); + } + ); it('rejects subsequent method calls after terminate() is called', async () => { return withTestDb(persistence, db => { diff --git a/packages/firestore/test/integration/api/query.test.ts b/packages/firestore/test/integration/api/query.test.ts index 090077f2a85..b2715358fd1 100644 --- a/packages/firestore/test/integration/api/query.test.ts +++ b/packages/firestore/test/integration/api/query.test.ts @@ -1652,417 +1652,426 @@ apiDescribe('Queries', persistence => { // because it results in a 'missing index' error. The Firestore Emulator, // however, does serve these queries. // eslint-disable-next-line no-restricted-properties - (persistence.gc === 'lru' && USE_EMULATOR ? describe : describe.skip)('OR Queries', () => { - it('can use query overloads', () => { - const testDocs = { - doc1: { a: 1, b: 0 }, - doc2: { a: 2, b: 1 }, - doc3: { a: 3, b: 2 }, - doc4: { a: 1, b: 3 }, - doc5: { a: 1, b: 1 } - }; - - return withTestCollection(persistence, testDocs, async coll => { - // a == 1, limit 2, b - desc - await checkOnlineAndOfflineResultsMatch( - query(coll, where('a', '==', 1), limit(2), orderBy('b', 'desc')), - 'doc4', - 'doc5' - ); + (persistence.gc === 'lru' && USE_EMULATOR ? describe : describe.skip)( + 'OR Queries', + () => { + it('can use query overloads', () => { + const testDocs = { + doc1: { a: 1, b: 0 }, + doc2: { a: 2, b: 1 }, + doc3: { a: 3, b: 2 }, + doc4: { a: 1, b: 3 }, + doc5: { a: 1, b: 1 } + }; + + return withTestCollection(persistence, testDocs, async coll => { + // a == 1, limit 2, b - desc + await checkOnlineAndOfflineResultsMatch( + query(coll, where('a', '==', 1), limit(2), orderBy('b', 'desc')), + 'doc4', + 'doc5' + ); + }); }); - }); - it('can use or queries', () => { - const testDocs = { - doc1: { a: 1, b: 0 }, - doc2: { a: 2, b: 1 }, - doc3: { a: 3, b: 2 }, - doc4: { a: 1, b: 3 }, - doc5: { a: 1, b: 1 } - }; - - return withTestCollection(persistence, testDocs, async coll => { - // with one inequality: a>2 || b==1. - await checkOnlineAndOfflineResultsMatch( - query(coll, or(where('a', '>', 2), where('b', '==', 1))), - 'doc5', - 'doc2', - 'doc3' - ); + it('can use or queries', () => { + const testDocs = { + doc1: { a: 1, b: 0 }, + doc2: { a: 2, b: 1 }, + doc3: { a: 3, b: 2 }, + doc4: { a: 1, b: 3 }, + doc5: { a: 1, b: 1 } + }; + + return withTestCollection(persistence, testDocs, async coll => { + // with one inequality: a>2 || b==1. + await checkOnlineAndOfflineResultsMatch( + query(coll, or(where('a', '>', 2), where('b', '==', 1))), + 'doc5', + 'doc2', + 'doc3' + ); - // Test with limits (implicit order by ASC): (a==1) || (b > 0) LIMIT 2 - await checkOnlineAndOfflineResultsMatch( - query(coll, or(where('a', '==', 1), where('b', '>', 0)), limit(2)), - 'doc1', - 'doc2' - ); + // Test with limits (implicit order by ASC): (a==1) || (b > 0) LIMIT 2 + await checkOnlineAndOfflineResultsMatch( + query(coll, or(where('a', '==', 1), where('b', '>', 0)), limit(2)), + 'doc1', + 'doc2' + ); - // Test with limits (explicit order by): (a==1) || (b > 0) LIMIT_TO_LAST 2 - // Note: The public query API does not allow implicit ordering when limitToLast is used. - await checkOnlineAndOfflineResultsMatch( - query( - coll, - or(where('a', '==', 1), where('b', '>', 0)), - limitToLast(2), - orderBy('b') - ), - 'doc3', - 'doc4' - ); + // Test with limits (explicit order by): (a==1) || (b > 0) LIMIT_TO_LAST 2 + // Note: The public query API does not allow implicit ordering when limitToLast is used. + await checkOnlineAndOfflineResultsMatch( + query( + coll, + or(where('a', '==', 1), where('b', '>', 0)), + limitToLast(2), + orderBy('b') + ), + 'doc3', + 'doc4' + ); - // Test with limits (explicit order by ASC): (a==2) || (b == 1) ORDER BY a LIMIT 1 - await checkOnlineAndOfflineResultsMatch( - query( - coll, - or(where('a', '==', 2), where('b', '==', 1)), - limit(1), - orderBy('a') - ), - 'doc5' - ); + // Test with limits (explicit order by ASC): (a==2) || (b == 1) ORDER BY a LIMIT 1 + await checkOnlineAndOfflineResultsMatch( + query( + coll, + or(where('a', '==', 2), where('b', '==', 1)), + limit(1), + orderBy('a') + ), + 'doc5' + ); - // Test with limits (explicit order by DESC): (a==2) || (b == 1) ORDER BY a LIMIT_TO_LAST 1 - await checkOnlineAndOfflineResultsMatch( - query( - coll, - or(where('a', '==', 2), where('b', '==', 1)), - limitToLast(1), - orderBy('a') - ), - 'doc2' - ); + // Test with limits (explicit order by DESC): (a==2) || (b == 1) ORDER BY a LIMIT_TO_LAST 1 + await checkOnlineAndOfflineResultsMatch( + query( + coll, + or(where('a', '==', 2), where('b', '==', 1)), + limitToLast(1), + orderBy('a') + ), + 'doc2' + ); + }); }); - }); - - it('can use or queries with not-in', () => { - const testDocs = { - doc1: { a: 1, b: 0 }, - doc2: { b: 1 }, - doc3: { a: 3, b: 2 }, - doc4: { a: 1, b: 3 }, - doc5: { a: 1 }, - doc6: { a: 2 } - }; - return withTestCollection(persistence, testDocs, async coll => { - // a==2 || b not-in [2,3] - // Has implicit orderBy b. - await checkOnlineAndOfflineResultsMatch( - query(coll, or(where('a', '==', 2), where('b', 'not-in', [2, 3]))), - 'doc1', - 'doc2' - ); + it('can use or queries with not-in', () => { + const testDocs = { + doc1: { a: 1, b: 0 }, + doc2: { b: 1 }, + doc3: { a: 3, b: 2 }, + doc4: { a: 1, b: 3 }, + doc5: { a: 1 }, + doc6: { a: 2 } + }; + + return withTestCollection(persistence, testDocs, async coll => { + // a==2 || b not-in [2,3] + // Has implicit orderBy b. + await checkOnlineAndOfflineResultsMatch( + query(coll, or(where('a', '==', 2), where('b', 'not-in', [2, 3]))), + 'doc1', + 'doc2' + ); + }); }); - }); - - // eslint-disable-next-line no-restricted-properties - it('supports order by equality', () => { - const testDocs = { - doc1: { a: 1, b: [0] }, - doc2: { b: [1] }, - doc3: { a: 3, b: [2, 7], c: 10 }, - doc4: { a: 1, b: [3, 7] }, - doc5: { a: 1 }, - doc6: { a: 2, c: 20 } - }; - return withTestCollection(persistence, testDocs, async coll => { - await checkOnlineAndOfflineResultsMatch( - query(coll, where('a', '==', 1), orderBy('a')), - 'doc1', - 'doc4', - 'doc5' - ); + // eslint-disable-next-line no-restricted-properties + it('supports order by equality', () => { + const testDocs = { + doc1: { a: 1, b: [0] }, + doc2: { b: [1] }, + doc3: { a: 3, b: [2, 7], c: 10 }, + doc4: { a: 1, b: [3, 7] }, + doc5: { a: 1 }, + doc6: { a: 2, c: 20 } + }; + + return withTestCollection(persistence, testDocs, async coll => { + await checkOnlineAndOfflineResultsMatch( + query(coll, where('a', '==', 1), orderBy('a')), + 'doc1', + 'doc4', + 'doc5' + ); - await checkOnlineAndOfflineResultsMatch( - query(coll, where('a', 'in', [2, 3]), orderBy('a')), - 'doc6', - 'doc3' - ); + await checkOnlineAndOfflineResultsMatch( + query(coll, where('a', 'in', [2, 3]), orderBy('a')), + 'doc6', + 'doc3' + ); + }); }); - }); - // eslint-disable-next-line no-restricted-properties - it('supports multiple in ops', () => { - const testDocs = { - doc1: { a: 1, b: 0 }, - doc2: { b: 1 }, - doc3: { a: 3, b: 2 }, - doc4: { a: 1, b: 3 }, - doc5: { a: 1 }, - doc6: { a: 2 } - }; - - return withTestCollection(persistence, testDocs, async coll => { - // Two IN operations on different fields with disjunction. - await checkOnlineAndOfflineResultsMatch( - query( - coll, - or(where('a', 'in', [2, 3]), where('b', 'in', [0, 2])), - orderBy('a') - ), - 'doc1', - 'doc6', - 'doc3' - ); + // eslint-disable-next-line no-restricted-properties + it('supports multiple in ops', () => { + const testDocs = { + doc1: { a: 1, b: 0 }, + doc2: { b: 1 }, + doc3: { a: 3, b: 2 }, + doc4: { a: 1, b: 3 }, + doc5: { a: 1 }, + doc6: { a: 2 } + }; + + return withTestCollection(persistence, testDocs, async coll => { + // Two IN operations on different fields with disjunction. + await checkOnlineAndOfflineResultsMatch( + query( + coll, + or(where('a', 'in', [2, 3]), where('b', 'in', [0, 2])), + orderBy('a') + ), + 'doc1', + 'doc6', + 'doc3' + ); - // Two IN operations on different fields with conjunction. - await checkOnlineAndOfflineResultsMatch( - query( - coll, - and(where('a', 'in', [2, 3]), where('b', 'in', [0, 2])), - orderBy('a') - ), - 'doc3' - ); + // Two IN operations on different fields with conjunction. + await checkOnlineAndOfflineResultsMatch( + query( + coll, + and(where('a', 'in', [2, 3]), where('b', 'in', [0, 2])), + orderBy('a') + ), + 'doc3' + ); - // Two IN operations on the same field. - // a IN [1,2,3] && a IN [0,1,4] should result in "a==1". - await checkOnlineAndOfflineResultsMatch( - query( - coll, - and(where('a', 'in', [1, 2, 3]), where('a', 'in', [0, 1, 4])) - ), - 'doc1', - 'doc4', - 'doc5' - ); + // Two IN operations on the same field. + // a IN [1,2,3] && a IN [0,1,4] should result in "a==1". + await checkOnlineAndOfflineResultsMatch( + query( + coll, + and(where('a', 'in', [1, 2, 3]), where('a', 'in', [0, 1, 4])) + ), + 'doc1', + 'doc4', + 'doc5' + ); - // a IN [2,3] && a IN [0,1,4] is never true and so the result should be an - // empty set. - await checkOnlineAndOfflineResultsMatch( - query( - coll, - and(where('a', 'in', [2, 3]), where('a', 'in', [0, 1, 4])) - ) - ); + // a IN [2,3] && a IN [0,1,4] is never true and so the result should be an + // empty set. + await checkOnlineAndOfflineResultsMatch( + query( + coll, + and(where('a', 'in', [2, 3]), where('a', 'in', [0, 1, 4])) + ) + ); - // a IN [0,3] || a IN [0,2] should union them (similar to: a IN [0,2,3]). - await checkOnlineAndOfflineResultsMatch( - query(coll, or(where('a', 'in', [0, 3]), where('a', 'in', [0, 2]))), - 'doc3', - 'doc6' - ); + // a IN [0,3] || a IN [0,2] should union them (similar to: a IN [0,2,3]). + await checkOnlineAndOfflineResultsMatch( + query(coll, or(where('a', 'in', [0, 3]), where('a', 'in', [0, 2]))), + 'doc3', + 'doc6' + ); - // Nested composite filter on the same field. - await checkOnlineAndOfflineResultsMatch( - query( - coll, - and( - where('a', 'in', [1, 3]), - or( - where('a', 'in', [0, 2]), - and(where('b', '>=', 1), where('a', 'in', [1, 3])) + // Nested composite filter on the same field. + await checkOnlineAndOfflineResultsMatch( + query( + coll, + and( + where('a', 'in', [1, 3]), + or( + where('a', 'in', [0, 2]), + and(where('b', '>=', 1), where('a', 'in', [1, 3])) + ) ) - ) - ), - 'doc3', - 'doc4' - ); + ), + 'doc3', + 'doc4' + ); - // Nested composite filter on the different fields. - await checkOnlineAndOfflineResultsMatch( - query( - coll, - and( - where('b', 'in', [0, 3]), - or( - where('b', 'in', [1]), - and(where('b', 'in', [2, 3]), where('a', 'in', [1, 3])) + // Nested composite filter on the different fields. + await checkOnlineAndOfflineResultsMatch( + query( + coll, + and( + where('b', 'in', [0, 3]), + or( + where('b', 'in', [1]), + and(where('b', 'in', [2, 3]), where('a', 'in', [1, 3])) + ) ) - ) - ), - 'doc4' - ); + ), + 'doc4' + ); + }); }); - }); - - // eslint-disable-next-line no-restricted-properties - it('supports using in with array contains any', () => { - const testDocs = { - doc1: { a: 1, b: [0] }, - doc2: { b: [1] }, - doc3: { a: 3, b: [2, 7], c: 10 }, - doc4: { a: 1, b: [3, 7] }, - doc5: { a: 1 }, - doc6: { a: 2, c: 20 } - }; - return withTestCollection(persistence, testDocs, async coll => { - await checkOnlineAndOfflineResultsMatch( - query( - coll, - or( - where('a', 'in', [2, 3]), - where('b', 'array-contains-any', [0, 7]) - ) - ), - 'doc1', - 'doc3', - 'doc4', - 'doc6' - ); + // eslint-disable-next-line no-restricted-properties + it('supports using in with array contains any', () => { + const testDocs = { + doc1: { a: 1, b: [0] }, + doc2: { b: [1] }, + doc3: { a: 3, b: [2, 7], c: 10 }, + doc4: { a: 1, b: [3, 7] }, + doc5: { a: 1 }, + doc6: { a: 2, c: 20 } + }; + + return withTestCollection(persistence, testDocs, async coll => { + await checkOnlineAndOfflineResultsMatch( + query( + coll, + or( + where('a', 'in', [2, 3]), + where('b', 'array-contains-any', [0, 7]) + ) + ), + 'doc1', + 'doc3', + 'doc4', + 'doc6' + ); - await checkOnlineAndOfflineResultsMatch( - query( - coll, - and( - where('a', 'in', [2, 3]), - where('b', 'array-contains-any', [0, 7]) - ) - ), - 'doc3' - ); + await checkOnlineAndOfflineResultsMatch( + query( + coll, + and( + where('a', 'in', [2, 3]), + where('b', 'array-contains-any', [0, 7]) + ) + ), + 'doc3' + ); - await checkOnlineAndOfflineResultsMatch( - query( - coll, - or( - and(where('a', 'in', [2, 3]), where('c', '==', 10)), - where('b', 'array-contains-any', [0, 7]) - ) - ), - 'doc1', - 'doc3', - 'doc4' - ); + await checkOnlineAndOfflineResultsMatch( + query( + coll, + or( + and(where('a', 'in', [2, 3]), where('c', '==', 10)), + where('b', 'array-contains-any', [0, 7]) + ) + ), + 'doc1', + 'doc3', + 'doc4' + ); - await checkOnlineAndOfflineResultsMatch( - query( - coll, - and( - where('a', 'in', [2, 3]), - or(where('b', 'array-contains-any', [0, 7]), where('c', '==', 20)) - ) - ), - 'doc3', - 'doc6' - ); + await checkOnlineAndOfflineResultsMatch( + query( + coll, + and( + where('a', 'in', [2, 3]), + or( + where('b', 'array-contains-any', [0, 7]), + where('c', '==', 20) + ) + ) + ), + 'doc3', + 'doc6' + ); + }); }); - }); - - // eslint-disable-next-line no-restricted-properties - it('supports using in with array contains', () => { - const testDocs = { - doc1: { a: 1, b: [0] }, - doc2: { b: [1] }, - doc3: { a: 3, b: [2, 7] }, - doc4: { a: 1, b: [3, 7] }, - doc5: { a: 1 }, - doc6: { a: 2 } - }; - return withTestCollection(persistence, testDocs, async coll => { - await checkOnlineAndOfflineResultsMatch( - query( - coll, - or(where('a', 'in', [2, 3]), where('b', 'array-contains', 3)) - ), - 'doc3', - 'doc4', - 'doc6' - ); + // eslint-disable-next-line no-restricted-properties + it('supports using in with array contains', () => { + const testDocs = { + doc1: { a: 1, b: [0] }, + doc2: { b: [1] }, + doc3: { a: 3, b: [2, 7] }, + doc4: { a: 1, b: [3, 7] }, + doc5: { a: 1 }, + doc6: { a: 2 } + }; + + return withTestCollection(persistence, testDocs, async coll => { + await checkOnlineAndOfflineResultsMatch( + query( + coll, + or(where('a', 'in', [2, 3]), where('b', 'array-contains', 3)) + ), + 'doc3', + 'doc4', + 'doc6' + ); - await checkOnlineAndOfflineResultsMatch( - query( - coll, - and(where('a', 'in', [2, 3]), where('b', 'array-contains', 7)) - ), - 'doc3' - ); + await checkOnlineAndOfflineResultsMatch( + query( + coll, + and(where('a', 'in', [2, 3]), where('b', 'array-contains', 7)) + ), + 'doc3' + ); - await checkOnlineAndOfflineResultsMatch( - query( - coll, - or( - where('a', 'in', [2, 3]), - and(where('b', 'array-contains', 3), where('a', '==', 1)) - ) - ), - 'doc3', - 'doc4', - 'doc6' - ); + await checkOnlineAndOfflineResultsMatch( + query( + coll, + or( + where('a', 'in', [2, 3]), + and(where('b', 'array-contains', 3), where('a', '==', 1)) + ) + ), + 'doc3', + 'doc4', + 'doc6' + ); - await checkOnlineAndOfflineResultsMatch( - query( - coll, - and( - where('a', 'in', [2, 3]), - or(where('b', 'array-contains', 7), where('a', '==', 1)) - ) - ), - 'doc3' - ); + await checkOnlineAndOfflineResultsMatch( + query( + coll, + and( + where('a', 'in', [2, 3]), + or(where('b', 'array-contains', 7), where('a', '==', 1)) + ) + ), + 'doc3' + ); + }); }); - }); - // eslint-disable-next-line no-restricted-properties - it('supports order by equality', () => { - const testDocs = { - doc1: { a: 1, b: [0] }, - doc2: { b: [1] }, - doc3: { a: 3, b: [2, 7], c: 10 }, - doc4: { a: 1, b: [3, 7] }, - doc5: { a: 1 }, - doc6: { a: 2, c: 20 } - }; - - return withTestCollection(persistence, testDocs, async coll => { - await checkOnlineAndOfflineResultsMatch( - query(coll, where('a', '==', 1), orderBy('a')), - 'doc1', - 'doc4', - 'doc5' - ); + // eslint-disable-next-line no-restricted-properties + it('supports order by equality', () => { + const testDocs = { + doc1: { a: 1, b: [0] }, + doc2: { b: [1] }, + doc3: { a: 3, b: [2, 7], c: 10 }, + doc4: { a: 1, b: [3, 7] }, + doc5: { a: 1 }, + doc6: { a: 2, c: 20 } + }; + + return withTestCollection(persistence, testDocs, async coll => { + await checkOnlineAndOfflineResultsMatch( + query(coll, where('a', '==', 1), orderBy('a')), + 'doc1', + 'doc4', + 'doc5' + ); - await checkOnlineAndOfflineResultsMatch( - query(coll, where('a', 'in', [2, 3]), orderBy('a')), - 'doc6', - 'doc3' - ); + await checkOnlineAndOfflineResultsMatch( + query(coll, where('a', 'in', [2, 3]), orderBy('a')), + 'doc6', + 'doc3' + ); + }); }); - }); - }); + } + ); // Reproduces https://github.com/firebase/firebase-js-sdk/issues/5873 // eslint-disable-next-line no-restricted-properties - (persistence.gc === 'lru' ? describe : describe.skip)('Caching empty results', () => { - it('can raise initial snapshot from cache, even if it is empty', () => { - return withTestCollection(persistence, {}, async coll => { - const snapshot1 = await getDocs(coll); // Populate the cache. - expect(snapshot1.metadata.fromCache).to.be.false; - expect(toDataArray(snapshot1)).to.deep.equal([]); // Precondition check. - - // Add a snapshot listener whose first event should be raised from cache. - const storeEvent = new EventsAccumulator(); - onSnapshot(coll, storeEvent.storeEvent); - const snapshot2 = await storeEvent.awaitEvent(); - expect(snapshot2.metadata.fromCache).to.be.true; - expect(toDataArray(snapshot2)).to.deep.equal([]); + (persistence.gc === 'lru' ? describe : describe.skip)( + 'Caching empty results', + () => { + it('can raise initial snapshot from cache, even if it is empty', () => { + return withTestCollection(persistence, {}, async coll => { + const snapshot1 = await getDocs(coll); // Populate the cache. + expect(snapshot1.metadata.fromCache).to.be.false; + expect(toDataArray(snapshot1)).to.deep.equal([]); // Precondition check. + + // Add a snapshot listener whose first event should be raised from cache. + const storeEvent = new EventsAccumulator(); + onSnapshot(coll, storeEvent.storeEvent); + const snapshot2 = await storeEvent.awaitEvent(); + expect(snapshot2.metadata.fromCache).to.be.true; + expect(toDataArray(snapshot2)).to.deep.equal([]); + }); }); - }); - it('can raise initial snapshot from cache, even if it has become empty', () => { - const testDocs = { - a: { key: 'a' } - }; - return withTestCollection(persistence, testDocs, async coll => { - // Populate the cache. - const snapshot1 = await getDocs(coll); - expect(snapshot1.metadata.fromCache).to.be.false; - expect(toDataArray(snapshot1)).to.deep.equal([{ key: 'a' }]); - // Empty the collection. - void deleteDoc(doc(coll, 'a')); - - const storeEvent = new EventsAccumulator(); - onSnapshot(coll, storeEvent.storeEvent); - const snapshot2 = await storeEvent.awaitEvent(); - expect(snapshot2.metadata.fromCache).to.be.true; - expect(toDataArray(snapshot2)).to.deep.equal([]); + it('can raise initial snapshot from cache, even if it has become empty', () => { + const testDocs = { + a: { key: 'a' } + }; + return withTestCollection(persistence, testDocs, async coll => { + // Populate the cache. + const snapshot1 = await getDocs(coll); + expect(snapshot1.metadata.fromCache).to.be.false; + expect(toDataArray(snapshot1)).to.deep.equal([{ key: 'a' }]); + // Empty the collection. + void deleteDoc(doc(coll, 'a')); + + const storeEvent = new EventsAccumulator(); + onSnapshot(coll, storeEvent.storeEvent); + const snapshot2 = await storeEvent.awaitEvent(); + expect(snapshot2.metadata.fromCache).to.be.true; + expect(toDataArray(snapshot2)).to.deep.equal([]); + }); }); - }); - }); + } + ); it('resuming a query should use bloom filter to avoid full requery', async () => { // Prepare the names and contents of the 100 documents to create. diff --git a/packages/firestore/test/integration/api_internal/database.test.ts b/packages/firestore/test/integration/api_internal/database.test.ts index 5d7b2585493..fe7c645d9d2 100644 --- a/packages/firestore/test/integration/api_internal/database.test.ts +++ b/packages/firestore/test/integration/api_internal/database.test.ts @@ -28,10 +28,7 @@ import { setDoc, waitForPendingWrites } from '../util/firebase_export'; -import { - apiDescribe, - withTestDoc -} from '../util/helpers'; +import { apiDescribe, withTestDoc } from '../util/helpers'; import { withMockCredentialProviderTestDb } from '../util/internal_helpers'; use(chaiAsPromised); diff --git a/packages/firestore/test/integration/api_internal/transaction.test.ts b/packages/firestore/test/integration/api_internal/transaction.test.ts index 59753a81293..a15eef62aca 100644 --- a/packages/firestore/test/integration/api_internal/transaction.test.ts +++ b/packages/firestore/test/integration/api_internal/transaction.test.ts @@ -32,116 +32,152 @@ import { import { apiDescribe, withTestDb } from '../util/helpers'; import { asyncQueue } from '../util/internal_helpers'; -apiDescribe( - 'Database transactions (with internal API)', - persistence => { - it('should increment transactionally', async () => { - // A set of concurrent transactions. - const transactionPromises: Array> = []; - const readPromises: Array> = []; - // A barrier to make sure every transaction reaches the same spot. - const barrier = new Deferred(); - let started = 0; +apiDescribe('Database transactions (with internal API)', persistence => { + it('should increment transactionally', async () => { + // A set of concurrent transactions. + const transactionPromises: Array> = []; + const readPromises: Array> = []; + // A barrier to make sure every transaction reaches the same spot. + const barrier = new Deferred(); + let started = 0; - await withTestDb(persistence, async db => { - asyncQueue(db).skipDelaysForTimerId(TimerId.TransactionRetry); - const docRef = doc(collection(db, 'counters')); - await setDoc(docRef, { count: 5 }); - // Make 3 transactions that will all increment. - for (let i = 0; i < 3; i++) { - const resolveRead = new Deferred(); - readPromises.push(resolveRead.promise); - transactionPromises.push( - runTransaction(db, async transaction => { - const snapshot = await transaction.get(docRef); - expect(snapshot).to.exist; - started += 1; - resolveRead.resolve(); - await barrier.promise; - transaction.set(docRef, { - count: snapshot.data()!['count'] + 1 - }); - }) - ); - } + await withTestDb(persistence, async db => { + asyncQueue(db).skipDelaysForTimerId(TimerId.TransactionRetry); + const docRef = doc(collection(db, 'counters')); + await setDoc(docRef, { count: 5 }); + // Make 3 transactions that will all increment. + for (let i = 0; i < 3; i++) { + const resolveRead = new Deferred(); + readPromises.push(resolveRead.promise); + transactionPromises.push( + runTransaction(db, async transaction => { + const snapshot = await transaction.get(docRef); + expect(snapshot).to.exist; + started += 1; + resolveRead.resolve(); + await barrier.promise; + transaction.set(docRef, { + count: snapshot.data()!['count'] + 1 + }); + }) + ); + } - // Let all of the transactions fetch the old value and stop once. - await Promise.all(readPromises); + // Let all of the transactions fetch the old value and stop once. + await Promise.all(readPromises); - // Let all of the transactions continue and wait for them to - // finish. - expect(started).to.equal(3); - barrier.resolve(); - await Promise.all(transactionPromises); + // Let all of the transactions continue and wait for them to + // finish. + expect(started).to.equal(3); + barrier.resolve(); + await Promise.all(transactionPromises); - // Now all transaction should be completed, so check the result. - const snapshot = await getDoc(docRef); - expect(snapshot).to.exist; - expect(snapshot.data()!['count']).to.equal(8); - }); + // Now all transaction should be completed, so check the result. + const snapshot = await getDoc(docRef); + expect(snapshot).to.exist; + expect(snapshot.data()!['count']).to.equal(8); }); + }); - it('should update transactionally', async () => { - // A set of concurrent transactions. - const transactionPromises: Array> = []; - const readPromises: Array> = []; - // A barrier to make sure every transaction reaches the same spot. - const barrier = new Deferred(); - let counter = 0; + it('should update transactionally', async () => { + // A set of concurrent transactions. + const transactionPromises: Array> = []; + const readPromises: Array> = []; + // A barrier to make sure every transaction reaches the same spot. + const barrier = new Deferred(); + let counter = 0; - await withTestDb(persistence, async db => { - asyncQueue(db).skipDelaysForTimerId(TimerId.TransactionRetry); - const docRef = doc(collection(db, 'counters')); - await setDoc(docRef, { - count: 5, - other: 'yes' - }); - // Make 3 transactions that will all increment. - for (let i = 0; i < 3; i++) { - const resolveRead = new Deferred(); - readPromises.push(resolveRead.promise); - transactionPromises.push( - runTransaction(db, async transaction => { - const snapshot = await transaction.get(docRef); - expect(snapshot).to.exist; - counter += 1; - resolveRead.resolve(); - await barrier.promise; - await transaction.update(docRef, { - count: snapshot.data()!['count'] + 1 - }); - }) - ); - } + await withTestDb(persistence, async db => { + asyncQueue(db).skipDelaysForTimerId(TimerId.TransactionRetry); + const docRef = doc(collection(db, 'counters')); + await setDoc(docRef, { + count: 5, + other: 'yes' + }); + // Make 3 transactions that will all increment. + for (let i = 0; i < 3; i++) { + const resolveRead = new Deferred(); + readPromises.push(resolveRead.promise); + transactionPromises.push( + runTransaction(db, async transaction => { + const snapshot = await transaction.get(docRef); + expect(snapshot).to.exist; + counter += 1; + resolveRead.resolve(); + await barrier.promise; + await transaction.update(docRef, { + count: snapshot.data()!['count'] + 1 + }); + }) + ); + } - // Let all of the transactions fetch the old value and stop once. - await Promise.all(readPromises); + // Let all of the transactions fetch the old value and stop once. + await Promise.all(readPromises); - // Let all of the transactions continue and wait for them to - // finish. There should be 3 initial transaction runs. - expect(counter).to.equal(3); - barrier.resolve(); - await Promise.all(transactionPromises); + // Let all of the transactions continue and wait for them to + // finish. There should be 3 initial transaction runs. + expect(counter).to.equal(3); + barrier.resolve(); + await Promise.all(transactionPromises); - // Now all transaction should be completed, so check the result. - // There should be a maximum of 3 retries: once for the 2nd update, - // and twice for the 3rd update. - expect(counter).to.be.lessThan(7); - const snapshot = await getDoc(docRef); - expect(snapshot).to.exist; - expect(snapshot.data()!['count']).to.equal(8); - expect(snapshot.data()!['other']).to.equal('yes'); - }); + // Now all transaction should be completed, so check the result. + // There should be a maximum of 3 retries: once for the 2nd update, + // and twice for the 3rd update. + expect(counter).to.be.lessThan(7); + const snapshot = await getDoc(docRef); + expect(snapshot).to.exist; + expect(snapshot.data()!['count']).to.equal(8); + expect(snapshot.data()!['other']).to.equal('yes'); + }); + }); + + it('should fail transaction (maxAttempts: default) when reading a doc twice with different versions', async () => { + await withTestDb(persistence, async db => { + asyncQueue(db).skipDelaysForTimerId(TimerId.TransactionRetry); + const docRef = doc(collection(db, 'counters')); + let counter = 0; + await setDoc(docRef, { count: 15 }); + try { + await runTransaction(db, async transaction => { + counter++; + // Get the docRef once. + await transaction.get(docRef); + // Do a write outside of the transaction. Because the transaction + // will retry, set the document to a different value each time. + await setDoc(docRef, { count: 1234 + counter }); + // Get the docRef again in the transaction with the new + // version. + await transaction.get(docRef); + // Now try to update the docRef from within the transaction. + // This should fail, because we read 15 earlier. + await transaction.set(docRef, { count: 16 }); + }); + expect.fail('transaction should fail'); + } catch (e) { + const err = e as FirestoreError; + expect(err).to.exist; + expect(err.code).to.equal('aborted'); + } + const snapshot = await getDoc(docRef); + expect(snapshot.data()!['count']).to.equal(1234 + counter); + expect(counter).to.equal(DEFAULT_TRANSACTION_OPTIONS.maxAttempts); }); + }); - it('should fail transaction (maxAttempts: default) when reading a doc twice with different versions', async () => { - await withTestDb(persistence, async db => { - asyncQueue(db).skipDelaysForTimerId(TimerId.TransactionRetry); - const docRef = doc(collection(db, 'counters')); - let counter = 0; - await setDoc(docRef, { count: 15 }); - try { - await runTransaction(db, async transaction => { + it('should fail transaction (maxAttempts: 1) when reading a doc twice with different versions', async () => { + await withTestDb(persistence, async db => { + asyncQueue(db).skipDelaysForTimerId(TimerId.TransactionRetry); + const docRef = doc(collection(db, 'counters')); + const options: TransactionOptions = { + maxAttempts: 1 + }; + let counter = 0; + await setDoc(docRef, { count: 15 }); + try { + await runTransaction( + db, + async transaction => { counter++; // Get the docRef once. await transaction.get(docRef); @@ -154,57 +190,18 @@ apiDescribe( // Now try to update the docRef from within the transaction. // This should fail, because we read 15 earlier. await transaction.set(docRef, { count: 16 }); - }); - expect.fail('transaction should fail'); - } catch (e) { - const err = e as FirestoreError; - expect(err).to.exist; - expect(err.code).to.equal('aborted'); - } - const snapshot = await getDoc(docRef); - expect(snapshot.data()!['count']).to.equal(1234 + counter); - expect(counter).to.equal(DEFAULT_TRANSACTION_OPTIONS.maxAttempts); - }); - }); - - it('should fail transaction (maxAttempts: 1) when reading a doc twice with different versions', async () => { - await withTestDb(persistence, async db => { - asyncQueue(db).skipDelaysForTimerId(TimerId.TransactionRetry); - const docRef = doc(collection(db, 'counters')); - const options: TransactionOptions = { - maxAttempts: 1 - }; - let counter = 0; - await setDoc(docRef, { count: 15 }); - try { - await runTransaction( - db, - async transaction => { - counter++; - // Get the docRef once. - await transaction.get(docRef); - // Do a write outside of the transaction. Because the transaction - // will retry, set the document to a different value each time. - await setDoc(docRef, { count: 1234 + counter }); - // Get the docRef again in the transaction with the new - // version. - await transaction.get(docRef); - // Now try to update the docRef from within the transaction. - // This should fail, because we read 15 earlier. - await transaction.set(docRef, { count: 16 }); - }, - options - ); - expect.fail('transaction should fail'); - } catch (e) { - const err = e as FirestoreError; - expect(err).to.exist; - expect(err.code).to.equal('aborted'); - } - const snapshot = await getDoc(docRef); - expect(snapshot.data()!['count']).to.equal(1234 + counter); - expect(counter).to.equal(options.maxAttempts); - }); + }, + options + ); + expect.fail('transaction should fail'); + } catch (e) { + const err = e as FirestoreError; + expect(err).to.exist; + expect(err.code).to.equal('aborted'); + } + const snapshot = await getDoc(docRef); + expect(snapshot.data()!['count']).to.equal(1234 + counter); + expect(counter).to.equal(options.maxAttempts); }); - } -); + }); +}); diff --git a/packages/firestore/test/integration/browser/indexeddb.test.ts b/packages/firestore/test/integration/browser/indexeddb.test.ts index ee987d20d97..ec2667866cf 100644 --- a/packages/firestore/test/integration/browser/indexeddb.test.ts +++ b/packages/firestore/test/integration/browser/indexeddb.test.ts @@ -24,7 +24,12 @@ import { FirestoreError, setDoc } from '../util/firebase_export'; -import { IndexedDbPersistenceMode, MemoryEagerPersistenceMode, isPersistenceAvailable, withTestDb } from '../util/helpers'; +import { + IndexedDbPersistenceMode, + MemoryEagerPersistenceMode, + isPersistenceAvailable, + withTestDb +} from '../util/helpers'; describe('where indexeddb is not available: ', () => { // Only test on platforms where persistence is *not* available (e.g. Edge, diff --git a/packages/firestore/test/integration/util/helpers.ts b/packages/firestore/test/integration/util/helpers.ts index 719c8624662..b3f5dec641d 100644 --- a/packages/firestore/test/integration/util/helpers.ts +++ b/packages/firestore/test/integration/util/helpers.ts @@ -88,9 +88,10 @@ export class MemoryEagerPersistenceMode implements PersistenceMode { } toLocalCache(): MemoryLocalCache { - return memoryLocalCache({garbageCollector: memoryEagerGarbageCollector()}); + return memoryLocalCache({ + garbageCollector: memoryEagerGarbageCollector() + }); } - } export class MemoryLruPersistenceMode implements PersistenceMode { @@ -107,14 +108,13 @@ export class MemoryLruPersistenceMode implements PersistenceMode { } toLocalCache(): MemoryLocalCache { - return memoryLocalCache({garbageCollector: memoryLruGarbageCollector()}); + return memoryLocalCache({ garbageCollector: memoryLruGarbageCollector() }); } - } export class IndexedDbPersistenceMode implements PersistenceMode { readonly name = 'indexeddb'; - readonly storage = 'indexeddb' + readonly storage = 'indexeddb'; readonly gc = 'lru'; toEagerGc(): MemoryEagerPersistenceMode { @@ -131,7 +131,6 @@ export class IndexedDbPersistenceMode implements PersistenceMode { } return persistentLocalCache(); } - } function isIeOrEdge(): boolean { @@ -166,7 +165,9 @@ function apiDescribeInternal( message: string, testSuite: (persistence: PersistenceMode) => void ): void { - const persistenceModes: PersistenceMode[] = [new MemoryEagerPersistenceMode()]; + const persistenceModes: PersistenceMode[] = [ + new MemoryEagerPersistenceMode() + ]; if (isPersistenceAvailable()) { persistenceModes.push(new IndexedDbPersistenceMode()); } @@ -175,7 +176,9 @@ function apiDescribeInternal( // Freeze the persistence mode so that tests don't modify the persistence // mode, which is shared between tests. const frozenPersistenceMode = Object.freeze(persistenceMode); - describeFn(`(Persistence=${persistenceMode.name}) ${message}`, () => testSuite(frozenPersistenceMode)); + describeFn(`(Persistence=${persistenceMode.name}) ${message}`, () => + testSuite(frozenPersistenceMode) + ); } } @@ -310,7 +313,10 @@ export async function withNamedTestDbsOrSkipUnlessUsingEmulator( const app = newTestApp(DEFAULT_PROJECT_ID); const dbs: Firestore[] = []; for (const dbName of dbNames) { - const newSettings = { ...DEFAULT_SETTINGS, localCache: persistence.toLocalCache() }; + const newSettings = { + ...DEFAULT_SETTINGS, + localCache: persistence.toLocalCache() + }; const db = newTestFirestore(app, newSettings, dbName); dbs.push(db); } From 2141204d39da171f6d7cc92530c562cce0a3ef55 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Thu, 29 Jun 2023 11:26:39 -0400 Subject: [PATCH 5/6] wip [skip actions] --- .../integration/api/array_transforms.test.ts | 5 ++- .../test/integration/api/database.test.ts | 10 ++--- .../test/integration/api/get_options.test.ts | 3 +- .../test/integration/api/query.test.ts | 33 +++++++++------- .../test/integration/api/validation.test.ts | 38 +++++++++++-------- .../test/integration/util/helpers.ts | 11 ++++-- 6 files changed, 59 insertions(+), 41 deletions(-) diff --git a/packages/firestore/test/integration/api/array_transforms.test.ts b/packages/firestore/test/integration/api/array_transforms.test.ts index 4f66639b239..e1a628435e8 100644 --- a/packages/firestore/test/integration/api/array_transforms.test.ts +++ b/packages/firestore/test/integration/api/array_transforms.test.ts @@ -160,8 +160,9 @@ apiDescribe('Array Transforms:', persistence => { * Unlike the withTestSetup() tests above, these tests intentionally avoid * having any ongoing listeners so that we can test what gets stored in the * offline cache based purely on the write acknowledgement (without receiving - * an updated document via watch). As such they also rely on persistence - * being enabled so documents remain in the cache after the write. + * an updated document via watch). As such they also rely on persistence with + * LRU garbage collection (rather than eager garbage collection) so documents + * remain in the cache after the write. */ // eslint-disable-next-line no-restricted-properties (persistence.gc === 'lru' ? describe : describe.skip)( diff --git a/packages/firestore/test/integration/api/database.test.ts b/packages/firestore/test/integration/api/database.test.ts index 211e4913c5d..76194e3ea6b 100644 --- a/packages/firestore/test/integration/api/database.test.ts +++ b/packages/firestore/test/integration/api/database.test.ts @@ -1096,7 +1096,7 @@ apiDescribe('Database', persistence => { }); // eslint-disable-next-line no-restricted-properties - (persistence.gc === 'lru' ? it : it.skip)( + (persistence.storage === 'indexeddb' ? it : it.skip)( 'offline writes are sent after restart', () => { return withTestDoc(persistence, async (docRef, firestore) => { @@ -1145,7 +1145,7 @@ apiDescribe('Database', persistence => { }); // eslint-disable-next-line no-restricted-properties - (persistence.gc === 'lru' ? it : it.skip)( + (persistence.storage === 'indexeddb' ? it : it.skip)( 'maintains persistence after restarting app', async () => { await withTestDoc(persistence, async docRef => { @@ -1168,7 +1168,7 @@ apiDescribe('Database', persistence => { ); // eslint-disable-next-line no-restricted-properties - (persistence.gc === 'lru' ? it : it.skip)( + (persistence.storage === 'indexeddb' ? it : it.skip)( 'can clear persistence if the client has been terminated', async () => { await withTestDoc(persistence, async (docRef, firestore) => { @@ -1192,7 +1192,7 @@ apiDescribe('Database', persistence => { ); // eslint-disable-next-line no-restricted-properties - (persistence.gc === 'lru' ? it : it.skip)( + (persistence.storage === 'indexeddb' ? it : it.skip)( 'can clear persistence if the client has not been initialized', async () => { await withTestDoc(persistence, async docRef => { @@ -1216,7 +1216,7 @@ apiDescribe('Database', persistence => { ); // eslint-disable-next-line no-restricted-properties - (persistence.gc === 'lru' ? it : it.skip)( + (persistence.storage === 'indexeddb' ? it : it.skip)( 'cannot clear persistence if the client has been initialized', async () => { await withTestDoc(persistence, async (docRef, firestore) => { diff --git a/packages/firestore/test/integration/api/get_options.test.ts b/packages/firestore/test/integration/api/get_options.test.ts index ede036aff90..dd2289a3a8c 100644 --- a/packages/firestore/test/integration/api/get_options.test.ts +++ b/packages/firestore/test/integration/api/get_options.test.ts @@ -496,7 +496,8 @@ apiDescribe('GetOptions', persistence => { }); }); - // We need the deleted doc to stay in cache, so only run this with persistence. + // We need the deleted doc to stay in cache, so only run this test with + // persistence with LRU garbage collection (rather than eager GC). // eslint-disable-next-line no-restricted-properties, (persistence.gc === 'lru' ? it : it.skip)( 'get deleted doc while offline with source=cache', diff --git a/packages/firestore/test/integration/api/query.test.ts b/packages/firestore/test/integration/api/query.test.ts index b2715358fd1..ab7b15c00ea 100644 --- a/packages/firestore/test/integration/api/query.test.ts +++ b/packages/firestore/test/integration/api/query.test.ts @@ -1333,8 +1333,9 @@ apiDescribe('Queries', persistence => { }); }); - // OR Query tests only run when the SDK is configured for persistence - // because they validate that the result from server and cache match. + // OR Query tests only run when the SDK is configured with persistence that + // uses LRU garbage collection (rather than eager garbage collection) because + // they validate that the result from server and cache match. // eslint-disable-next-line no-restricted-properties (persistence.gc === 'lru' ? describe : describe.skip)('OR Queries', () => { it('can use query overloads', () => { @@ -1646,11 +1647,12 @@ apiDescribe('Queries', persistence => { }); }); - // OR Query tests only run when the SDK is configured for persistence - // because they validate that the result from server and cache match - // Additionally these tests must be skipped if running against production - // because it results in a 'missing index' error. The Firestore Emulator, - // however, does serve these queries. + // OR Query tests only run when the SDK is configured with persistence that + // uses LRU garbage collection (rather than eager garbage collection) because + // they validate that the result from server and cache match. Additionally, + // these tests must be skipped if running against production because it + // results in a 'missing index' error. The Firestore Emulator, however, does + // serve these queries. // eslint-disable-next-line no-restricted-properties (persistence.gc === 'lru' && USE_EMULATOR ? describe : describe.skip)( 'OR Queries', @@ -2032,12 +2034,13 @@ apiDescribe('Queries', persistence => { ); // Reproduces https://github.com/firebase/firebase-js-sdk/issues/5873 - // eslint-disable-next-line no-restricted-properties - (persistence.gc === 'lru' ? describe : describe.skip)( + describe( 'Caching empty results', () => { it('can raise initial snapshot from cache, even if it is empty', () => { - return withTestCollection(persistence, {}, async coll => { + // Use persistence with LRU garbage collection so that the cached resume + // token and document data do not get cleared. + return withTestCollection(persistence.toLruGc(), {}, async coll => { const snapshot1 = await getDocs(coll); // Populate the cache. expect(snapshot1.metadata.fromCache).to.be.false; expect(toDataArray(snapshot1)).to.deep.equal([]); // Precondition check. @@ -2055,7 +2058,9 @@ apiDescribe('Queries', persistence => { const testDocs = { a: { key: 'a' } }; - return withTestCollection(persistence, testDocs, async coll => { + // Use persistence with LRU garbage collection so that the cached resume + // token and document data do not get cleared. + return withTestCollection(persistence.toLruGc(), testDocs, async coll => { // Populate the cache. const snapshot1 = await getDocs(coll); expect(snapshot1.metadata.fromCache).to.be.false; @@ -2138,9 +2143,9 @@ apiDescribe('Queries', persistence => { } // Skip the verification of the existence filter mismatch when persistence - // is disabled because without persistence there is no resume token - // specified in the subsequent call to getDocs(), and, therefore, Watch - // will _not_ send an existence filter. + // uses eager garbage collection because with eager GC there is no resume + // token specified in the subsequent call to getDocs(), and, therefore, + // Watch will _not_ send an existence filter. // TODO(b/272754156) Re-write this test using a snapshot listener instead // of calls to getDocs() and remove this check for disabled persistence. if (persistence.gc === 'eager') { diff --git a/packages/firestore/test/integration/api/validation.test.ts b/packages/firestore/test/integration/api/validation.test.ts index a37ec36d988..1ae47698da2 100644 --- a/packages/firestore/test/integration/api/validation.test.ts +++ b/packages/firestore/test/integration/api/validation.test.ts @@ -133,15 +133,19 @@ class TestClass { constructor(readonly property: string) {} } -apiDescribe('Validation:', persistence => { +apiDescribe.only('Validation:', persistence => { describe('FirestoreSettings', () => { - // Enabling persistence counts as a use of the firestore instance, meaning - // that it will be impossible to verify that a set of settings don't throw, - // and additionally that some exceptions happen for specific reasons, rather - // than persistence having already been enabled. - if (persistence.gc === 'lru') { - return; - } + validationIt( + persistence, + 'precondition check: verify the Firestore settings are not frozen', + async db => { + // This should not throw an exception. This test just verifies that the + // Firestore instance's initial state does not have its settings frozen, + // because if it did then the following tests would not be testing the + // behavior they intend to test. + connectFirestoreEmulator(db, 'something-else.example.com', 8080) + } + ); validationIt( persistence, @@ -231,15 +235,19 @@ apiDescribe('Validation:', persistence => { }); describe('Firestore', () => { - (persistence.gc === 'lru' ? validationIt : validationIt.skip)( + validationIt( persistence, - 'disallows calling enablePersistence after use', + 'allows calling enableIndexedDbPersistence() when persistence configured in the settings', + async db => { + await enableIndexedDbPersistence(db); + } + ); + + validationIt( + persistence, + 'disallows calling enableIndexedDbPersistence() after use', db => { - // Calling `enablePersistence()` itself counts as use, so we should only - // need this method when persistence is not enabled. - if (persistence.storage === 'memory') { - doc(db, 'foo/bar'); - } + doc(db, 'foo/bar'); expect(() => enableIndexedDbPersistence(db)).to.throw( 'SDK cache is already specified.' ); diff --git a/packages/firestore/test/integration/util/helpers.ts b/packages/firestore/test/integration/util/helpers.ts index b3f5dec641d..39f11ed43e4 100644 --- a/packages/firestore/test/integration/util/helpers.ts +++ b/packages/firestore/test/integration/util/helpers.ts @@ -230,7 +230,7 @@ export function toIds(docSet: QuerySnapshot): string[] { } export function withTestDb( - persistence: PersistenceMode, + persistence: PersistenceMode | null, fn: (db: Firestore) => Promise ): Promise { return withTestDbs(persistence, 1, ([db]) => { @@ -255,7 +255,7 @@ export function withAlternateTestDb( } export function withTestDbs( - persistence: PersistenceMode, + persistence: PersistenceMode | null, numDbs: number, fn: (db: Firestore[]) => Promise ): Promise { @@ -268,7 +268,7 @@ export function withTestDbs( ); } export async function withTestDbsSettings( - persistence: PersistenceMode, + persistence: PersistenceMode | null, projectId: string, settings: PrivateSettings, numDbs: number, @@ -281,7 +281,10 @@ export async function withTestDbsSettings( const dbs: Firestore[] = []; for (let i = 0; i < numDbs; i++) { - const newSettings = { ...settings, localCache: persistence.toLocalCache() }; + const newSettings = { ...settings }; + if (persistence !== null) { + newSettings.localCache = persistence.toLocalCache(); + } const db = newTestFirestore(newTestApp(projectId), newSettings); dbs.push(db); } From 7afbb0d3abf527fc3d4fbc25b8fe2615d2f86efb Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 30 Jun 2023 13:26:19 -0400 Subject: [PATCH 6/6] cleanup --- .../test/integration/api/get_options.test.ts | 4 +- .../test/integration/api/query.test.ts | 93 +++++++++---------- .../test/integration/api/validation.test.ts | 48 ++++------ .../integration/api_internal/database.test.ts | 2 +- .../test/integration/util/helpers.ts | 39 ++++---- 5 files changed, 85 insertions(+), 101 deletions(-) diff --git a/packages/firestore/test/integration/api/get_options.test.ts b/packages/firestore/test/integration/api/get_options.test.ts index dd2289a3a8c..9eec596861f 100644 --- a/packages/firestore/test/integration/api/get_options.test.ts +++ b/packages/firestore/test/integration/api/get_options.test.ts @@ -496,8 +496,8 @@ apiDescribe('GetOptions', persistence => { }); }); - // We need the deleted doc to stay in cache, so only run this test with - // persistence with LRU garbage collection (rather than eager GC). + // We need the deleted doc to stay in cache, so only run this test when the + // local cache is configured with LRU GC (as opposed to eager GC). // eslint-disable-next-line no-restricted-properties, (persistence.gc === 'lru' ? it : it.skip)( 'get deleted doc while offline with source=cache', diff --git a/packages/firestore/test/integration/api/query.test.ts b/packages/firestore/test/integration/api/query.test.ts index ab7b15c00ea..7514fa836f9 100644 --- a/packages/firestore/test/integration/api/query.test.ts +++ b/packages/firestore/test/integration/api/query.test.ts @@ -1333,8 +1333,8 @@ apiDescribe('Queries', persistence => { }); }); - // OR Query tests only run when the SDK is configured with persistence that - // uses LRU garbage collection (rather than eager garbage collection) because + // OR Query tests only run when the SDK's local cache is configured to use + // LRU garbage collection (rather than eager garbage collection) because // they validate that the result from server and cache match. // eslint-disable-next-line no-restricted-properties (persistence.gc === 'lru' ? describe : describe.skip)('OR Queries', () => { @@ -1647,8 +1647,8 @@ apiDescribe('Queries', persistence => { }); }); - // OR Query tests only run when the SDK is configured with persistence that - // uses LRU garbage collection (rather than eager garbage collection) because + // OR Query tests only run when the SDK's local cache is configured to use + // LRU garbage collection (rather than eager garbage collection) because // they validate that the result from server and cache match. Additionally, // these tests must be skipped if running against production because it // results in a 'missing index' error. The Firestore Emulator, however, does @@ -2034,49 +2034,46 @@ apiDescribe('Queries', persistence => { ); // Reproduces https://github.com/firebase/firebase-js-sdk/issues/5873 - describe( - 'Caching empty results', - () => { - it('can raise initial snapshot from cache, even if it is empty', () => { - // Use persistence with LRU garbage collection so that the cached resume - // token and document data do not get cleared. - return withTestCollection(persistence.toLruGc(), {}, async coll => { - const snapshot1 = await getDocs(coll); // Populate the cache. - expect(snapshot1.metadata.fromCache).to.be.false; - expect(toDataArray(snapshot1)).to.deep.equal([]); // Precondition check. - - // Add a snapshot listener whose first event should be raised from cache. - const storeEvent = new EventsAccumulator(); - onSnapshot(coll, storeEvent.storeEvent); - const snapshot2 = await storeEvent.awaitEvent(); - expect(snapshot2.metadata.fromCache).to.be.true; - expect(toDataArray(snapshot2)).to.deep.equal([]); - }); + describe('Caching empty results', () => { + it('can raise initial snapshot from cache, even if it is empty', () => { + // Use persistence with LRU garbage collection so the resume token and + // document data do not get prematurely deleted from the local cache. + return withTestCollection(persistence.toLruGc(), {}, async coll => { + const snapshot1 = await getDocs(coll); // Populate the cache. + expect(snapshot1.metadata.fromCache).to.be.false; + expect(toDataArray(snapshot1)).to.deep.equal([]); // Precondition check. + + // Add a snapshot listener whose first event should be raised from cache. + const storeEvent = new EventsAccumulator(); + onSnapshot(coll, storeEvent.storeEvent); + const snapshot2 = await storeEvent.awaitEvent(); + expect(snapshot2.metadata.fromCache).to.be.true; + expect(toDataArray(snapshot2)).to.deep.equal([]); }); + }); - it('can raise initial snapshot from cache, even if it has become empty', () => { - const testDocs = { - a: { key: 'a' } - }; - // Use persistence with LRU garbage collection so that the cached resume - // token and document data do not get cleared. - return withTestCollection(persistence.toLruGc(), testDocs, async coll => { - // Populate the cache. - const snapshot1 = await getDocs(coll); - expect(snapshot1.metadata.fromCache).to.be.false; - expect(toDataArray(snapshot1)).to.deep.equal([{ key: 'a' }]); - // Empty the collection. - void deleteDoc(doc(coll, 'a')); - - const storeEvent = new EventsAccumulator(); - onSnapshot(coll, storeEvent.storeEvent); - const snapshot2 = await storeEvent.awaitEvent(); - expect(snapshot2.metadata.fromCache).to.be.true; - expect(toDataArray(snapshot2)).to.deep.equal([]); - }); + it('can raise initial snapshot from cache, even if it has become empty', () => { + const testDocs = { + a: { key: 'a' } + }; + // Use persistence with LRU garbage collection so the resume token and + // document data do not get prematurely deleted from the local cache. + return withTestCollection(persistence.toLruGc(), testDocs, async coll => { + // Populate the cache. + const snapshot1 = await getDocs(coll); + expect(snapshot1.metadata.fromCache).to.be.false; + expect(toDataArray(snapshot1)).to.deep.equal([{ key: 'a' }]); + // Empty the collection. + void deleteDoc(doc(coll, 'a')); + + const storeEvent = new EventsAccumulator(); + onSnapshot(coll, storeEvent.storeEvent); + const snapshot2 = await storeEvent.awaitEvent(); + expect(snapshot2.metadata.fromCache).to.be.true; + expect(toDataArray(snapshot2)).to.deep.equal([]); }); - } - ); + }); + }); it('resuming a query should use bloom filter to avoid full requery', async () => { // Prepare the names and contents of the 100 documents to create. @@ -2142,10 +2139,10 @@ apiDescribe('Queries', persistence => { ); } - // Skip the verification of the existence filter mismatch when persistence - // uses eager garbage collection because with eager GC there is no resume - // token specified in the subsequent call to getDocs(), and, therefore, - // Watch will _not_ send an existence filter. + // Skip the verification of the existence filter mismatch when the local + // cache is configured to use eager garbage collection because with eager + // GC there is no resume token specified in the subsequent call to + // getDocs(), and, therefore, Watch will _not_ send an existence filter. // TODO(b/272754156) Re-write this test using a snapshot listener instead // of calls to getDocs() and remove this check for disabled persistence. if (persistence.gc === 'eager') { diff --git a/packages/firestore/test/integration/api/validation.test.ts b/packages/firestore/test/integration/api/validation.test.ts index 1ae47698da2..1aaf70e9452 100644 --- a/packages/firestore/test/integration/api/validation.test.ts +++ b/packages/firestore/test/integration/api/validation.test.ts @@ -56,10 +56,10 @@ import { } from '../util/firebase_export'; import { apiDescribe, + PersistenceMode, withAlternateTestDb, withTestCollection, - withTestDb, - PersistenceMode + withTestDb } from '../util/helpers'; import { ALT_PROJECT_ID, DEFAULT_PROJECT_ID } from '../util/settings'; @@ -133,20 +133,8 @@ class TestClass { constructor(readonly property: string) {} } -apiDescribe.only('Validation:', persistence => { +apiDescribe('Validation:', persistence => { describe('FirestoreSettings', () => { - validationIt( - persistence, - 'precondition check: verify the Firestore settings are not frozen', - async db => { - // This should not throw an exception. This test just verifies that the - // Firestore instance's initial state does not have its settings frozen, - // because if it did then the following tests would not be testing the - // behavior they intend to test. - connectFirestoreEmulator(db, 'something-else.example.com', 8080) - } - ); - validationIt( persistence, 'disallows changing settings after use', @@ -175,15 +163,19 @@ apiDescribe.only('Validation:', persistence => { }); }); - validationIt(persistence, 'useEmulator can set host and port', () => { - const db = newTestFirestore(newTestApp('test-project')); - // Verify that this doesn't throw. - connectFirestoreEmulator(db, 'localhost', 9000); - }); + validationIt( + persistence, + 'connectFirestoreEmulator() can set host and port', + () => { + const db = newTestFirestore(newTestApp('test-project')); + // Verify that this doesn't throw. + connectFirestoreEmulator(db, 'localhost', 9000); + } + ); validationIt( persistence, - 'disallows calling useEmulator after use', + 'disallows calling connectFirestoreEmulator() after use', async db => { const errorMsg = 'Firestore has already been started and its settings can no longer be changed.'; @@ -197,7 +189,7 @@ apiDescribe.only('Validation:', persistence => { validationIt( persistence, - 'useEmulator can set mockUserToken object', + 'connectFirestoreEmulator() can set mockUserToken object', () => { const db = newTestFirestore(newTestApp('test-project')); // Verify that this doesn't throw. @@ -209,7 +201,7 @@ apiDescribe.only('Validation:', persistence => { validationIt( persistence, - 'useEmulator can set mockUserToken string', + 'connectFirestoreEmulator() can set mockUserToken string', () => { const db = newTestFirestore(newTestApp('test-project')); // Verify that this doesn't throw. @@ -235,19 +227,11 @@ apiDescribe.only('Validation:', persistence => { }); describe('Firestore', () => { - validationIt( - persistence, - 'allows calling enableIndexedDbPersistence() when persistence configured in the settings', - async db => { - await enableIndexedDbPersistence(db); - } - ); - validationIt( persistence, 'disallows calling enableIndexedDbPersistence() after use', db => { - doc(db, 'foo/bar'); + //doc(db, 'foo/bar'); expect(() => enableIndexedDbPersistence(db)).to.throw( 'SDK cache is already specified.' ); diff --git a/packages/firestore/test/integration/api_internal/database.test.ts b/packages/firestore/test/integration/api_internal/database.test.ts index fe7c645d9d2..93ccbbd2390 100644 --- a/packages/firestore/test/integration/api_internal/database.test.ts +++ b/packages/firestore/test/integration/api_internal/database.test.ts @@ -35,7 +35,7 @@ use(chaiAsPromised); apiDescribe('Database (with internal API)', persistence => { // eslint-disable-next-line no-restricted-properties - (persistence.gc === 'lru' ? it : it.skip)( + (persistence.storage === 'indexeddb' ? it : it.skip)( 'will reject the promise if clear persistence fails', async () => { await withTestDoc(persistence, async (docRef, firestore) => { diff --git a/packages/firestore/test/integration/util/helpers.ts b/packages/firestore/test/integration/util/helpers.ts index 39f11ed43e4..ac52cdb01c9 100644 --- a/packages/firestore/test/integration/util/helpers.ts +++ b/packages/firestore/test/integration/util/helpers.ts @@ -71,7 +71,7 @@ export interface PersistenceMode { * Creates and returns a new "local cache" object corresponding to this * persistence type. */ - toLocalCache(): MemoryLocalCache | PersistentLocalCache; + asLocalCacheFirestoreSettings(): MemoryLocalCache | PersistentLocalCache; } export class MemoryEagerPersistenceMode implements PersistenceMode { @@ -87,7 +87,7 @@ export class MemoryEagerPersistenceMode implements PersistenceMode { return new MemoryLruPersistenceMode(); } - toLocalCache(): MemoryLocalCache { + asLocalCacheFirestoreSettings(): MemoryLocalCache { return memoryLocalCache({ garbageCollector: memoryEagerGarbageCollector() }); @@ -107,7 +107,7 @@ export class MemoryLruPersistenceMode implements PersistenceMode { return new MemoryLruPersistenceMode(); } - toLocalCache(): MemoryLocalCache { + asLocalCacheFirestoreSettings(): MemoryLocalCache { return memoryLocalCache({ garbageCollector: memoryLruGarbageCollector() }); } } @@ -125,9 +125,12 @@ export class IndexedDbPersistenceMode implements PersistenceMode { return new IndexedDbPersistenceMode(); } - toLocalCache(): PersistentLocalCache { - if (this.gc != 'lru') { - throw new Error(`unsupported gc: ${this.gc}`); + asLocalCacheFirestoreSettings(): PersistentLocalCache { + if (this.gc !== 'lru') { + throw new Error( + `PersistentLocalCache does not support the given ` + + `garbage collector: ${this.gc}` + ); } return persistentLocalCache(); } @@ -173,11 +176,11 @@ function apiDescribeInternal( } for (const persistenceMode of persistenceModes) { - // Freeze the persistence mode so that tests don't modify the persistence - // mode, which is shared between tests. - const frozenPersistenceMode = Object.freeze(persistenceMode); describeFn(`(Persistence=${persistenceMode.name}) ${message}`, () => - testSuite(frozenPersistenceMode) + // Freeze the properties of the `PersistenceMode` object specified to the + // test suite so that it cannot (accidentally or intentionally) change + // its properties, and affect all subsequent test suites. + testSuite(Object.freeze(persistenceMode)) ); } } @@ -230,7 +233,7 @@ export function toIds(docSet: QuerySnapshot): string[] { } export function withTestDb( - persistence: PersistenceMode | null, + persistence: PersistenceMode, fn: (db: Firestore) => Promise ): Promise { return withTestDbs(persistence, 1, ([db]) => { @@ -255,7 +258,7 @@ export function withAlternateTestDb( } export function withTestDbs( - persistence: PersistenceMode | null, + persistence: PersistenceMode, numDbs: number, fn: (db: Firestore[]) => Promise ): Promise { @@ -268,7 +271,7 @@ export function withTestDbs( ); } export async function withTestDbsSettings( - persistence: PersistenceMode | null, + persistence: PersistenceMode, projectId: string, settings: PrivateSettings, numDbs: number, @@ -281,10 +284,10 @@ export async function withTestDbsSettings( const dbs: Firestore[] = []; for (let i = 0; i < numDbs; i++) { - const newSettings = { ...settings }; - if (persistence !== null) { - newSettings.localCache = persistence.toLocalCache(); - } + const newSettings = { + ...settings, + localCache: persistence.asLocalCacheFirestoreSettings() + }; const db = newTestFirestore(newTestApp(projectId), newSettings); dbs.push(db); } @@ -318,7 +321,7 @@ export async function withNamedTestDbsOrSkipUnlessUsingEmulator( for (const dbName of dbNames) { const newSettings = { ...DEFAULT_SETTINGS, - localCache: persistence.toLocalCache() + localCache: persistence.asLocalCacheFirestoreSettings() }; const db = newTestFirestore(app, newSettings, dbName); dbs.push(db);