From 79b04937537b90422e051086112f6b43c2880cdb Mon Sep 17 00:00:00 2001 From: Sam Stern Date: Fri, 16 Oct 2020 07:19:39 -0400 Subject: [PATCH] Implement useEmulator for Firestore (#3909) --- .changeset/short-icons-travel.md | 7 ++++++ packages/firebase/index.d.ts | 10 ++++++++ packages/firestore-types/index.d.ts | 2 ++ packages/firestore/exp/test/shim.ts | 4 +++ packages/firestore/src/api/database.ts | 25 +++++++++++++++++-- .../test/integration/api/validation.test.ts | 20 ++++++++++++++- .../firestore/test/unit/api/database.test.ts | 19 ++++++++++++++ 7 files changed, 84 insertions(+), 3 deletions(-) create mode 100644 .changeset/short-icons-travel.md diff --git a/.changeset/short-icons-travel.md b/.changeset/short-icons-travel.md new file mode 100644 index 00000000000..eca27d87bdc --- /dev/null +++ b/.changeset/short-icons-travel.md @@ -0,0 +1,7 @@ +--- +'firebase': minor +'@firebase/firestore': minor +'@firebase/firestore-types': minor +--- + +Add a useEmulator(host, port) method to Firestore diff --git a/packages/firebase/index.d.ts b/packages/firebase/index.d.ts index 17862f58609..c0d71742d59 100644 --- a/packages/firebase/index.d.ts +++ b/packages/firebase/index.d.ts @@ -8030,6 +8030,16 @@ declare namespace firebase.firestore { */ settings(settings: Settings): void; + /** + * Modify this instance to communicate with the Cloud Firestore emulator. + * + *

Note: this must be called before this instance has been used to do any operations. + * + * @param host the emulator host (ex: localhost). + * @param port the emulator port (ex: 9000). + */ + useEmulator(host: string, port: number): void; + /** * Attempts to enable persistent storage, if possible. * diff --git a/packages/firestore-types/index.d.ts b/packages/firestore-types/index.d.ts index bec92576778..2c94ea2842f 100644 --- a/packages/firestore-types/index.d.ts +++ b/packages/firestore-types/index.d.ts @@ -61,6 +61,8 @@ export class FirebaseFirestore { settings(settings: Settings): void; + useEmulator(host: string, port: number): void; + enablePersistence(settings?: PersistenceSettings): Promise; collection(collectionPath: string): CollectionReference; diff --git a/packages/firestore/exp/test/shim.ts b/packages/firestore/exp/test/shim.ts index 4dcbbd61843..400e84a8e03 100644 --- a/packages/firestore/exp/test/shim.ts +++ b/packages/firestore/exp/test/shim.ts @@ -99,6 +99,10 @@ export class FirebaseFirestore initializeFirestore(this.app._delegate, settings); } + useEmulator(host: string, port: number): void { + this.settings({ host: `${host}:${port}`, ssl: false, merge: true }); + } + enablePersistence(settings?: legacy.PersistenceSettings): Promise { return settings?.synchronizeTabs ? enableMultiTabIndexedDbPersistence(this._delegate) diff --git a/packages/firestore/src/api/database.ts b/packages/firestore/src/api/database.ts index 22d81890db5..bf64f8970e9 100644 --- a/packages/firestore/src/api/database.ts +++ b/packages/firestore/src/api/database.ts @@ -111,7 +111,10 @@ import { valueDescription, validateIsNotUsedTogether } from '../util/input_validation'; -import { logError, setLogLevel as setClientLogLevel } from '../util/log'; +import { + setLogLevel as setClientLogLevel, + logWarn +} from '../util/log'; import { AutoId } from '../util/misc'; import { Deferred } from '../util/promise'; import { FieldPath as ExternalFieldPath } from './field_path'; @@ -503,7 +506,7 @@ export class Firestore implements PublicFirestore, FirebaseService { throw new FirestoreError( Code.FAILED_PRECONDITION, 'Firestore has already been started and its settings can no longer ' + - 'be changed. You can only call settings() before calling any other ' + + 'be changed. You can only modify settings before calling any other ' + 'methods on a Firestore object.' ); } @@ -514,6 +517,24 @@ export class Firestore implements PublicFirestore, FirebaseService { } } + useEmulator(host: string, port: number): void { + validateExactNumberOfArgs('Firestore.useEmulator', arguments, 2); + validateArgType('Firestore.useEmulator', 'string', 1, host); + validateArgType('Firestore.useEmulator', 'number', 2, port); + + if (this._settings.host !== DEFAULT_HOST) { + logWarn( + 'Host has been set in both settings() and useEmulator(), emulator host will be used' + ); + } + + this.settings({ + host: `${host}:${port}`, + ssl: false, + merge: true + }); + } + enableNetwork(): Promise { this.ensureClientConfigured(); return this._firestoreClient!.enableNetwork(); diff --git a/packages/firestore/test/integration/api/validation.test.ts b/packages/firestore/test/integration/api/validation.test.ts index ca5238686d9..223081dad95 100644 --- a/packages/firestore/test/integration/api/validation.test.ts +++ b/packages/firestore/test/integration/api/validation.test.ts @@ -159,7 +159,7 @@ apiDescribe('Validation:', (persistence: boolean) => { 'getFirestore()'; } else { errorMsg += - 'You can only call settings() before calling any other ' + + 'You can only modify settings before calling any other ' + 'methods on a Firestore object.'; } @@ -182,6 +182,24 @@ apiDescribe('Validation:', (persistence: boolean) => { // Verify that this doesn't throw. db.settings({ cacheSizeBytes: /* CACHE_SIZE_UNLIMITED= */ -1 }); }); + + validationIt(persistence, 'useEmulator can set host and port', () => { + const db = newTestFirestore('test-project'); + // Verify that this doesn't throw. + db.useEmulator('localhost', 9000); + }); + + validationIt( + persistence, + 'disallows calling useEmulator after use', + async db => { + const errorMsg = + 'Firestore has already been started and its settings can no longer be changed.'; + + await db.doc('foo/bar').set({}); + expect(() => db.useEmulator('localhost', 9000)).to.throw(errorMsg); + } + ); }); describe('Firestore', () => { diff --git a/packages/firestore/test/unit/api/database.test.ts b/packages/firestore/test/unit/api/database.test.ts index 9d55b1b56cf..725ecb51b22 100644 --- a/packages/firestore/test/unit/api/database.test.ts +++ b/packages/firestore/test/unit/api/database.test.ts @@ -195,4 +195,23 @@ describe('Settings', () => { expect(firestoreClient._getSettings().ignoreUndefinedProperties).to.be.true; expect(firestoreClient._getSettings().host).to.equal('other.host'); }); + + it('gets settings from useEmulator', () => { + // Use a new instance of Firestore in order to configure settings. + const firestoreClient = newTestFirestore(); + firestoreClient.useEmulator('localhost', 9000); + + expect(firestoreClient._getSettings().host).to.equal('localhost:9000'); + expect(firestoreClient._getSettings().ssl).to.be.false; + }); + + it('prefers host from useEmulator to host from settings', () => { + // Use a new instance of Firestore in order to configure settings. + const firestoreClient = newTestFirestore(); + firestoreClient.settings({ host: 'other.host' }); + firestoreClient.useEmulator('localhost', 9000); + + expect(firestoreClient._getSettings().host).to.equal('localhost:9000'); + expect(firestoreClient._getSettings().ssl).to.be.false; + }); });