From cbfd14cfb27cda8a6de74be5d138ea9e6de09fe9 Mon Sep 17 00:00:00 2001 From: Mila <107142260+milaGGL@users.noreply.github.com> Date: Wed, 4 Oct 2023 10:26:47 -0400 Subject: [PATCH] Testing composite index queries against production (#7632) --- .../test-changed-firestore-integration.yml | 22 +- .gitignore | 8 +- packages/firestore/firestore_index_config.tf | 104 ++++++++ packages/firestore/main.tf | 41 ++++ .../api/composite_index_query.test.ts | 141 +++++++++++ .../test/integration/api/query.test.ts | 121 +--------- .../util/composite_index_test_helper.ts | 225 ++++++++++++++++++ .../test/integration/util/helpers.ts | 49 +++- .../test/integration/util/settings.ts | 3 + 9 files changed, 588 insertions(+), 126 deletions(-) create mode 100644 packages/firestore/firestore_index_config.tf create mode 100644 packages/firestore/main.tf create mode 100644 packages/firestore/test/integration/api/composite_index_query.test.ts create mode 100644 packages/firestore/test/integration/util/composite_index_test_helper.ts diff --git a/.github/workflows/test-changed-firestore-integration.yml b/.github/workflows/test-changed-firestore-integration.yml index c45a706d80d..10ae7866ce7 100644 --- a/.github/workflows/test-changed-firestore-integration.yml +++ b/.github/workflows/test-changed-firestore-integration.yml @@ -13,6 +13,24 @@ jobs: with: # This makes Actions fetch all Git history so run-changed script can diff properly. fetch-depth: 0 + - uses: 'google-github-actions/auth@v0' + with: + credentials_json: '${{ secrets.JSSDK_ACTIONS_SA_KEY }}' + # create composite indexes with Terraform + - name: Setup Terraform + uses: hashicorp/setup-terraform@v2 + - name: Terraform Init + run: | + cp config/ci.config.json config/project.json + cd packages/firestore + terraform init + continue-on-error: true + - name: Terraform Apply + if: github.event_name == 'pull_request' + run: | + cd packages/firestore + terraform apply -var-file=../../config/project.json -auto-approve + continue-on-error: true - name: Set up Node (16) uses: actions/setup-node@v3 with: @@ -24,9 +42,7 @@ jobs: - name: Bump Node memory limit run: echo "NODE_OPTIONS=--max_old_space_size=4096" >> $GITHUB_ENV - name: Test setup and yarn install - run: | - cp config/ci.config.json config/project.json - yarn + run: yarn - name: build run: yarn build:changed firestore-integration - name: Run tests if firestore or its dependencies has changed diff --git a/.gitignore b/.gitignore index 89ef5a65fa6..6fa0085c80d 100644 --- a/.gitignore +++ b/.gitignore @@ -91,4 +91,10 @@ tsdoc-metadata.json # generated html docs docs-rut/ docs/ -toc/ \ No newline at end of file +toc/ + +# generated Terraform docs +.terraform/* +.terraform.lock.hcl +*.tfstate +*.tfstate.* \ No newline at end of file diff --git a/packages/firestore/firestore_index_config.tf b/packages/firestore/firestore_index_config.tf new file mode 100644 index 00000000000..7fafeae6fbf --- /dev/null +++ b/packages/firestore/firestore_index_config.tf @@ -0,0 +1,104 @@ +locals { + indexes = { + index1 = [ + { + field_path = "testId" + order = "ASCENDING" + }, + { + field_path = "a" + order = "ASCENDING" + }, + ] + index2 = [ + { + field_path = "testId" + order = "ASCENDING" + }, + { + field_path = "b" + order = "ASCENDING" + }, + ] + index3 = [ + { + field_path = "testId" + order = "ASCENDING" + }, + { + field_path = "b" + order = "DESCENDING" + }, + ] + index4 = [ + { + field_path = "a" + order = "ASCENDING" + }, + { + field_path = "testId" + order = "ASCENDING" + }, + { + field_path = "b" + order = "ASCENDING" + }, + ] + index5 = [ + { + field_path = "a" + order = "ASCENDING" + }, + { + field_path = "testId" + order = "ASCENDING" + }, + { + field_path = "b" + order = "DESCENDING" + }, + ] + index6 = [ + { + field_path = "a" + order = "ASCENDING" + }, + { + field_path = "testId" + order = "ASCENDING" + }, + { + field_path = "a" + order = "DESCENDING" + }, + ] + index7 = [ + { + field_path = "b" + order = "ASCENDING" + }, + { + field_path = "testId" + order = "ASCENDING" + }, + { + field_path = "a" + order = "ASCENDING" + }, + ] + index8 = [ + { + field_path = "b" + order = "ASCENDING" + }, + { + field_path = "testId" + order = "ASCENDING" + }, + { + field_path = "a" + order = "DESCENDING" + }, + ] + } +} diff --git a/packages/firestore/main.tf b/packages/firestore/main.tf new file mode 100644 index 00000000000..397c4d96ebd --- /dev/null +++ b/packages/firestore/main.tf @@ -0,0 +1,41 @@ +variable "projectId" {} + +provider "google" { + project = var.projectId +} + +resource "google_firestore_index" "default-db-index" { + collection = "composite-index-test-collection" + + for_each = local.indexes + dynamic "fields" { + for_each = distinct(flatten([for k, v in local.indexes : [ + for i in each.value : { + field_path = i.field_path + order = i.order + }]])) + content { + field_path = lookup(fields.value, "field_path", null) + order = lookup(fields.value, "order", null) + } + } + +} + +resource "google_firestore_index" "named-db-index" { + collection = "composite-index-test-collection" + database = "test-db" + + for_each = local.indexes + dynamic "fields" { + for_each = distinct(flatten([for k, v in local.indexes : [ + for i in each.value : { + field_path = i.field_path + order = i.order + }]])) + content { + field_path = lookup(fields.value, "field_path", null) + order = lookup(fields.value, "order", null) + } + } +} diff --git a/packages/firestore/test/integration/api/composite_index_query.test.ts b/packages/firestore/test/integration/api/composite_index_query.test.ts new file mode 100644 index 00000000000..4d7257d0fbd --- /dev/null +++ b/packages/firestore/test/integration/api/composite_index_query.test.ts @@ -0,0 +1,141 @@ +/** + * @license + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { CompositeIndexTestHelper } from '../util/composite_index_test_helper'; +import { + where, + orderBy, + limit, + limitToLast, + or +} from '../util/firebase_export'; +import { apiDescribe } from '../util/helpers'; + +/* + * Guidance for Creating Tests: + * ---------------------------- + * When creating tests that require composite indexes, it is recommended to utilize the + * "CompositeIndexTestHelper" class. This utility class provides methods for creating + * and setting test documents and running queries with ease, ensuring proper data + * isolation and query construction. + * + * Please remember to update the main index configuration file (firestore_index_config.tf) + * with any new composite indexes needed for the tests. This ensures synchronization with + * other testing environments, including CI. You can generate the required index link by + * clicking on the Firebase console link in the error message while running tests locally. + */ + +apiDescribe('Composite Index Queries', persistence => { + // 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', () => { + 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 } + }; + const testHelper = new CompositeIndexTestHelper(); + return testHelper.withTestDocs(persistence, testDocs, async coll => { + // a == 1, limit 2, b - desc + await testHelper.assertOnlineAndOfflineResultsMatch( + testHelper.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 } + }; + const testHelper = new CompositeIndexTestHelper(); + return testHelper.withTestDocs(persistence, testDocs, async coll => { + // with one inequality: a>2 || b==1. + await testHelper.assertOnlineAndOfflineResultsMatch( + testHelper.compositeQuery( + 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 testHelper.assertOnlineAndOfflineResultsMatch( + testHelper.compositeQuery( + 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 testHelper.assertOnlineAndOfflineResultsMatch( + testHelper.compositeQuery( + 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 testHelper.assertOnlineAndOfflineResultsMatch( + testHelper.compositeQuery( + 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 testHelper.assertOnlineAndOfflineResultsMatch( + testHelper.compositeQuery( + coll, + or(where('a', '==', 2), where('b', '==', 1)), + limitToLast(1), + orderBy('a') + ), + 'doc2' + ); + }); + }); + }); +}); diff --git a/packages/firestore/test/integration/api/query.test.ts b/packages/firestore/test/integration/api/query.test.ts index ed2ad61a337..767853bebde 100644 --- a/packages/firestore/test/integration/api/query.test.ts +++ b/packages/firestore/test/integration/api/query.test.ts @@ -43,14 +43,11 @@ import { getAggregateFromServer, getCountFromServer, getDocs, - getDocsFromCache, - getDocsFromServer, limit, limitToLast, onSnapshot, or, orderBy, - Query, query, QuerySnapshot, setDoc, @@ -72,7 +69,8 @@ import { withEmptyTestCollection, withRetry, withTestCollection, - withTestDb + withTestDb, + checkOnlineAndOfflineResultsMatch } from '../util/helpers'; import { USE_EMULATOR } from '../util/settings'; import { captureExistenceFilterMismatches } from '../util/testing_hooks_util'; @@ -2329,99 +2327,6 @@ apiDescribe('Queries', persistence => { }); }); - // 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 - // serve these queries. - // eslint-disable-next-line no-restricted-properties - (persistence.gc === 'lru' && USE_EMULATOR ? describe : describe.skip)( - 'OR Queries That Need Composite Indexes', - () => { - 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' - ); - - // 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 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' - ); - }); - }); - } - ); - // 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', () => { @@ -2900,25 +2805,3 @@ function verifyDocumentChange( expect(change.oldIndex).to.equal(oldIndex); expect(change.newIndex).to.equal(newIndex); } - -/** - * Checks that running the query while online (against the backend/emulator) results in the same - * documents as running the query while offline. If `expectedDocs` is provided, it also checks - * that both online and offline query result is equal to the expected documents. - * - * @param query The query to check - * @param expectedDocs Ordered list of document keys that are expected to match the query - */ -async function checkOnlineAndOfflineResultsMatch( - query: Query, - ...expectedDocs: string[] -): Promise { - const docsFromServer = await getDocsFromServer(query); - - if (expectedDocs.length !== 0) { - expect(expectedDocs).to.deep.equal(toIds(docsFromServer)); - } - - const docsFromCache = await getDocsFromCache(query); - expect(toIds(docsFromServer)).to.deep.equal(toIds(docsFromCache)); -} diff --git a/packages/firestore/test/integration/util/composite_index_test_helper.ts b/packages/firestore/test/integration/util/composite_index_test_helper.ts new file mode 100644 index 00000000000..292f52fa09e --- /dev/null +++ b/packages/firestore/test/integration/util/composite_index_test_helper.ts @@ -0,0 +1,225 @@ +/** + * @license + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { and } from '../../../src/lite-api/query'; +import { AutoId } from '../../../src/util/misc'; +import { field } from '../../util/helpers'; + +import { + query as internalQuery, + CollectionReference, + DocumentData, + Firestore, + Query, + QueryConstraint, + where, + WithFieldValue, + DocumentReference, + addDoc as addDocument, + setDoc as setDocument, + QueryCompositeFilterConstraint, + QueryNonFilterConstraint, + Timestamp, + DocumentSnapshot, + getDoc as getDocument, + updateDoc as updateDocument, + UpdateData, + getDocs as getDocuments, + QuerySnapshot, + deleteDoc as deleteDocument, + doc +} from './firebase_export'; +import { + batchCommitDocsToCollection, + checkOnlineAndOfflineResultsMatch, + PERSISTENCE_MODE_UNSPECIFIED, + PersistenceMode +} from './helpers'; +import { COMPOSITE_INDEX_TEST_COLLECTION, DEFAULT_SETTINGS } from './settings'; + +/** + * This helper class is designed to facilitate integration testing of Firestore queries that + * require composite indexes within a controlled testing environment. + * + *

Key Features: + * + *

+ */ +export class CompositeIndexTestHelper { + private readonly testId: string; + private readonly TEST_ID_FIELD: string = 'testId'; + private readonly TTL_FIELD: string = 'expireAt'; + + // Creates a new instance of the CompositeIndexTestHelper class, with a unique test + // identifier for data isolation. + constructor() { + this.testId = 'test-id-' + AutoId.newId(); + } + + // Runs a test with specified documents in the COMPOSITE_INDEX_TEST_COLLECTION. + async withTestDocs( + persistence: PersistenceMode | typeof PERSISTENCE_MODE_UNSPECIFIED, + docs: { [key: string]: DocumentData }, + fn: (collection: CollectionReference, db: Firestore) => Promise + ): Promise { + return batchCommitDocsToCollection( + persistence, + DEFAULT_SETTINGS, + this.prepareTestDocuments(docs), + COMPOSITE_INDEX_TEST_COLLECTION, + fn + ); + } + + // Hash the document key with testId. + private toHashedId(docId: string): string { + return docId + '-' + this.testId; + } + + private toHashedIds(docs: string[]): string[] { + return docs.map(docId => this.toHashedId(docId)); + } + + // Adds test-specific fields to a document, including the testId and expiration date. + private addTestSpecificFieldsToDoc(doc: DocumentData): DocumentData { + return { + ...doc, + [this.TEST_ID_FIELD]: this.testId, + [this.TTL_FIELD]: new Timestamp( // Expire test data after 24 hours + Timestamp.now().seconds + 24 * 60 * 60, + Timestamp.now().nanoseconds + ) + }; + } + + // Remove test-specific fields from a document, including the testId and expiration date. + private removeTestSpecificFieldsFromDoc(doc: DocumentData): void { + doc._document?.data?.delete(field(this.TTL_FIELD)); + doc._document?.data?.delete(field(this.TEST_ID_FIELD)); + } + + // Helper method to hash document keys and add test-specific fields for the provided documents. + private prepareTestDocuments(docs: { [key: string]: DocumentData }): { + [key: string]: DocumentData; + } { + const result: { [key: string]: DocumentData } = {}; + for (const key in docs) { + if (docs.hasOwnProperty(key)) { + result[this.toHashedId(key)] = this.addTestSpecificFieldsToDoc( + docs[key] + ); + } + } + return result; + } + + // Asserts that the result of running the query while online (against the backend/emulator) is + // the same as running it while offline. The expected document Ids are hashed to match the + // actual document IDs created by the test helper. + async assertOnlineAndOfflineResultsMatch( + query: Query, + ...expectedDocs: string[] + ): Promise { + return checkOnlineAndOfflineResultsMatch( + query, + ...this.toHashedIds(expectedDocs) + ); + } + + // Adds a filter on test id for a query. + query(query_: Query, ...queryConstraints: QueryConstraint[]): Query { + return internalQuery( + query_, + where(this.TEST_ID_FIELD, '==', this.testId), + ...queryConstraints + ); + } + + // Adds a filter on test id for a composite query. + compositeQuery( + query_: Query, + compositeFilter: QueryCompositeFilterConstraint, + ...queryConstraints: QueryNonFilterConstraint[] + ): Query { + return internalQuery( + query_, + and(where(this.TEST_ID_FIELD, '==', this.testId), compositeFilter), + ...queryConstraints + ); + } + // Get document reference from a document key. + getDocRef( + coll: CollectionReference, + docId: string + ): DocumentReference { + if (!docId.includes('test-id-')) { + docId = this.toHashedId(docId); + } + return doc(coll, docId); + } + + // Adds a document to a Firestore collection with test-specific fields. + addDoc( + reference: CollectionReference, + data: object + ): Promise> { + const processedData = this.addTestSpecificFieldsToDoc( + data + ) as WithFieldValue; + return addDocument(reference, processedData); + } + + // Sets a document in Firestore with test-specific fields. + setDoc(reference: DocumentReference, data: object): Promise { + const processedData = this.addTestSpecificFieldsToDoc( + data + ) as WithFieldValue; + return setDocument(reference, processedData); + } + + updateDoc( + reference: DocumentReference, + data: UpdateData + ): Promise { + return updateDocument(reference, data); + } + + deleteDoc(reference: DocumentReference): Promise { + return deleteDocument(reference); + } + + // Retrieves a single document from Firestore with test-specific fields removed. + async getDoc(docRef: DocumentReference): Promise> { + const docSnapshot = await getDocument(docRef); + this.removeTestSpecificFieldsFromDoc(docSnapshot); + return docSnapshot; + } + + // Retrieves multiple documents from Firestore with test-specific fields removed. + async getDocs(query_: Query): Promise> { + const querySnapshot = await getDocuments(this.query(query_)); + querySnapshot.forEach(doc => { + this.removeTestSpecificFieldsFromDoc(doc); + }); + return querySnapshot; + } +} diff --git a/packages/firestore/test/integration/util/helpers.ts b/packages/firestore/test/integration/util/helpers.ts index 9f97ec6bf79..6c46eb51a4c 100644 --- a/packages/firestore/test/integration/util/helpers.ts +++ b/packages/firestore/test/integration/util/helpers.ts @@ -16,6 +16,9 @@ */ import { isIndexedDBAvailable } from '@firebase/util'; +import { expect } from 'chai'; + +import { AutoId } from '../../../src/util/misc'; import { clearIndexedDbPersistence, @@ -39,7 +42,10 @@ import { SnapshotListenOptions, terminate, WriteBatch, - writeBatch + writeBatch, + Query, + getDocsFromServer, + getDocsFromCache } from './firebase_export'; import { ALT_PROJECT_ID, @@ -441,6 +447,23 @@ export function withTestCollectionSettings( settings: PrivateSettings, docs: { [key: string]: DocumentData }, fn: (collection: CollectionReference, db: Firestore) => Promise +): Promise { + const collectionId = AutoId.newId(); + return batchCommitDocsToCollection( + persistence, + settings, + docs, + collectionId, + fn + ); +} + +export function batchCommitDocsToCollection( + persistence: PersistenceMode | typeof PERSISTENCE_MODE_UNSPECIFIED, + settings: PrivateSettings, + docs: { [key: string]: DocumentData }, + collectionId: string, + fn: (collection: CollectionReference, db: Firestore) => Promise ): Promise { return withTestDbsSettings( persistence, @@ -448,8 +471,6 @@ export function withTestCollectionSettings( settings, 2, ([testDb, setupDb]) => { - // Abuse .doc() to get a random ID. - const collectionId = 'test-collection-' + doc(collection(testDb, 'x')).id; const testCollection = collection(testDb, collectionId); const setupCollection = collection(setupDb, collectionId); @@ -517,3 +538,25 @@ export function partitionedTestDocs(partitions: { return testDocs; } + +/** + * Checks that running the query while online (against the backend/emulator) results in the same + * documents as running the query while offline. If `expectedDocs` is provided, it also checks + * that both online and offline query result is equal to the expected documents. + * + * @param query The query to check + * @param expectedDocs Ordered list of document keys that are expected to match the query + */ +export async function checkOnlineAndOfflineResultsMatch( + query: Query, + ...expectedDocs: string[] +): Promise { + const docsFromServer = await getDocsFromServer(query); + + if (expectedDocs.length !== 0) { + expect(expectedDocs).to.deep.equal(toIds(docsFromServer)); + } + + const docsFromCache = await getDocsFromCache(query); + expect(toIds(docsFromServer)).to.deep.equal(toIds(docsFromCache)); +} diff --git a/packages/firestore/test/integration/util/settings.ts b/packages/firestore/test/integration/util/settings.ts index e57c896d977..65318363cd6 100644 --- a/packages/firestore/test/integration/util/settings.ts +++ b/packages/firestore/test/integration/util/settings.ts @@ -116,3 +116,6 @@ export const DEFAULT_PROJECT_ID = USE_EMULATOR ? process.env.FIRESTORE_EMULATOR_PROJECT_ID || 'test-emulator' : PROJECT_CONFIG.projectId; export const ALT_PROJECT_ID = 'test-db2'; + +export const COMPOSITE_INDEX_TEST_COLLECTION = + 'composite-index-test-collection';