diff --git a/src/auth/auth.ts b/src/auth/auth.ts index 09c08c6d8e..8e35df82c4 100644 --- a/src/auth/auth.ts +++ b/src/auth/auth.ts @@ -25,7 +25,6 @@ import { AbstractAuthRequestHandler, AuthRequestHandler, TenantAwareAuthRequestHandler, useEmulator, } from './auth-api-request'; import { AuthClientErrorCode, FirebaseAuthError, ErrorInfo } from '../utils/error'; -import { FirebaseServiceInterface, FirebaseServiceInternalsInterface } from '../firebase-service'; import * as utils from '../utils/index'; import * as validator from '../utils/validator'; import { auth } from './index'; @@ -59,22 +58,6 @@ import BaseAuthInterface = auth.BaseAuth; import AuthInterface = auth.Auth; import TenantAwareAuthInterface = auth.TenantAwareAuth; -/** - * Internals of an Auth instance. - */ -class AuthInternals implements FirebaseServiceInternalsInterface { - /** - * Deletes the service and its associated resources. - * - * @return {Promise<()>} An empty Promise that will be fulfilled when the service is deleted. - */ - public delete(): Promise { - // There are no resources to clean up - return Promise.resolve(undefined); - } -} - - /** * Base Auth class. Mainly used for user management APIs. */ @@ -820,10 +803,8 @@ export class TenantAwareAuth * Auth service bound to the provided app. * An Auth instance can have multiple tenants. */ -export class Auth extends BaseAuth - implements FirebaseServiceInterface, AuthInterface { +export class Auth extends BaseAuth implements AuthInterface { - public INTERNAL: AuthInternals = new AuthInternals(); private readonly tenantManager_: TenantManager; private readonly app_: FirebaseApp; diff --git a/src/database/database-internal.ts b/src/database/database-internal.ts index b2d9e4e970..a469a41773 100644 --- a/src/database/database-internal.ts +++ b/src/database/database-internal.ts @@ -19,7 +19,6 @@ import * as path from 'path'; import { FirebaseApp } from '../firebase-app'; import { FirebaseDatabaseError, AppErrorCodes, FirebaseAppError } from '../utils/error'; -import { FirebaseServiceInterface, FirebaseServiceInternalsInterface } from '../firebase-service'; import { Database as DatabaseImpl } from '@firebase/database'; import { database } from './index'; @@ -29,35 +28,14 @@ import { getSdkVersion } from '../utils/index'; import Database = database.Database; -/** - * Internals of a Database instance. - */ -class DatabaseInternals implements FirebaseServiceInternalsInterface { +export class DatabaseService { + + private readonly appInternal: FirebaseApp; - public databases: { + private databases: { [dbUrl: string]: Database; } = {}; - /** - * Deletes the service and its associated resources. - * - * @return {Promise<()>} An empty Promise that will be fulfilled when the service is deleted. - */ - public delete(): Promise { - for (const dbUrl of Object.keys(this.databases)) { - const db: DatabaseImpl = ((this.databases[dbUrl] as any) as DatabaseImpl); - db.INTERNAL.delete(); - } - return Promise.resolve(undefined); - } -} - -export class DatabaseService implements FirebaseServiceInterface { - - public readonly INTERNAL: DatabaseInternals = new DatabaseInternals(); - - private readonly appInternal: FirebaseApp; - constructor(app: FirebaseApp) { if (!validator.isNonNullObject(app) || !('options' in app)) { throw new FirebaseDatabaseError({ @@ -68,6 +46,20 @@ export class DatabaseService implements FirebaseServiceInterface { this.appInternal = app; } + /** + * @internal + */ + public delete(): Promise { + const promises = []; + for (const dbUrl of Object.keys(this.databases)) { + const db: DatabaseImpl = ((this.databases[dbUrl] as any) as DatabaseImpl); + promises.push(db.INTERNAL.delete()); + } + return Promise.all(promises).then(() => { + this.databases = {}; + }); + } + /** * Returns the app associated with this DatabaseService instance. * @@ -86,7 +78,7 @@ export class DatabaseService implements FirebaseServiceInterface { }); } - let db: Database = this.INTERNAL.databases[dbUrl]; + let db: Database = this.databases[dbUrl]; if (typeof db === 'undefined') { const rtdb = require('@firebase/database'); // eslint-disable-line @typescript-eslint/no-var-requires db = rtdb.initStandalone(this.appInternal, dbUrl, getSdkVersion()).instance; @@ -102,7 +94,7 @@ export class DatabaseService implements FirebaseServiceInterface { return rulesClient.setRules(source); }; - this.INTERNAL.databases[dbUrl] = db; + this.databases[dbUrl] = db; } return db; } diff --git a/src/firebase-app.ts b/src/firebase-app.ts index 401c21046b..fb8ad8b0f5 100644 --- a/src/firebase-app.ts +++ b/src/firebase-app.ts @@ -19,8 +19,7 @@ import { AppOptions, app } from './firebase-namespace-api'; import { credential, GoogleOAuthAccessToken } from './credential/index'; import { getApplicationDefault } from './credential/credential-internal'; import * as validator from './utils/validator'; -import { deepCopy, deepExtend } from './utils/deep-copy'; -import { FirebaseServiceInterface } from './firebase-service'; +import { deepCopy } from './utils/deep-copy'; import { FirebaseNamespaceInternals } from './firebase-namespace'; import { AppErrorCodes, FirebaseAppError } from './utils/error'; @@ -238,7 +237,7 @@ export class FirebaseApp implements app.App { private name_: string; private options_: AppOptions; - private services_: {[name: string]: FirebaseServiceInterface} = {}; + private services_: {[name: string]: unknown} = {}; private isDeleted_ = false; constructor(options: AppOptions, name: string, private firebaseInternals_: FirebaseNamespaceInternals) { @@ -268,11 +267,6 @@ export class FirebaseApp implements app.App { ); } - Object.keys(firebaseInternals_.serviceFactories).forEach((serviceName) => { - // Defer calling createService() until the service is accessed - (this as {[key: string]: any})[serviceName] = this.getService_.bind(this, serviceName); - }); - this.INTERNAL = new FirebaseAppInternals(credential); } @@ -428,51 +422,24 @@ export class FirebaseApp implements app.App { this.INTERNAL.delete(); return Promise.all(Object.keys(this.services_).map((serviceName) => { - return this.services_[serviceName].INTERNAL.delete(); + const service = this.services_[serviceName]; + if (isStateful(service)) { + return service.delete(); + } + return Promise.resolve(); })).then(() => { this.services_ = {}; this.isDeleted_ = true; }); } - private ensureService_(serviceName: string, initializer: () => T): T { - this.checkDestroyed_(); - - let service: T; - if (serviceName in this.services_) { - service = this.services_[serviceName] as T; - } else { - service = initializer(); - this.services_[serviceName] = service; - } - return service; - } - - /** - * Returns the service instance associated with this FirebaseApp instance (creating it on demand - * if needed). This is used for looking up monkeypatched service instances. - * - * @param serviceName The name of the service instance to return. - * @return The service instance with the provided name. - */ - private getService_(serviceName: string): FirebaseServiceInterface { + private ensureService_(serviceName: string, initializer: () => T): T { this.checkDestroyed_(); - if (!(serviceName in this.services_)) { - this.services_[serviceName] = this.firebaseInternals_.serviceFactories[serviceName]( - this, - this.extendApp_.bind(this), - ); + this.services_[serviceName] = initializer(); } - return this.services_[serviceName]; - } - - /** - * Callback function used to extend an App instance at the time of service instance creation. - */ - private extendApp_(props: {[prop: string]: any}): void { - deepExtend(this, props); + return this.services_[serviceName] as T; } /** @@ -487,3 +454,11 @@ export class FirebaseApp implements app.App { } } } + +interface StatefulFirebaseService { + delete(): Promise; +} + +function isStateful(service: any): service is StatefulFirebaseService { + return typeof service.delete === 'function'; +} diff --git a/src/firebase-namespace.ts b/src/firebase-namespace.ts index 28fbbaf290..43b12a92c9 100644 --- a/src/firebase-namespace.ts +++ b/src/firebase-namespace.ts @@ -17,11 +17,9 @@ import fs = require('fs'); -import { deepExtend } from './utils/deep-copy'; import { AppErrorCodes, FirebaseAppError } from './utils/error'; import { AppOptions, app } from './firebase-namespace-api'; -import { AppHook, FirebaseApp } from './firebase-app'; -import { FirebaseServiceFactory, FirebaseServiceInterface } from './firebase-service'; +import { FirebaseApp } from './firebase-app'; import { cert, refreshToken, applicationDefault } from './credential/credential'; import { getApplicationDefault } from './credential/credential-internal'; @@ -69,11 +67,8 @@ export interface FirebaseServiceNamespace { * Internals of a FirebaseNamespace instance. */ export class FirebaseNamespaceInternals { - public serviceFactories: {[serviceName: string]: FirebaseServiceFactory} = {}; private apps_: {[appName: string]: App} = {}; - private appHooks_: {[service: string]: AppHook} = {}; - constructor(public firebase_: {[key: string]: any}) {} /** @@ -118,11 +113,7 @@ export class FirebaseNamespaceInternals { } const app = new FirebaseApp(options, appName, this); - this.apps_[appName] = app; - - this.callAppHooks_(app, 'create'); - return app; } @@ -170,80 +161,7 @@ export class FirebaseNamespaceInternals { } const appToRemove = this.app(appName); - this.callAppHooks_(appToRemove, 'delete'); - delete this.apps_[appName]; - } - - /** - * Registers a new service on this Firebase namespace. - * - * @param serviceName The name of the Firebase service to register. - * @param createService A factory method to generate an instance of the Firebase service. - * @param serviceProperties Optional properties to extend this Firebase namespace with. - * @param appHook Optional callback that handles app-related events like app creation and deletion. - * @return The Firebase service's namespace. - */ - public registerService( - serviceName: string, - createService: FirebaseServiceFactory, - serviceProperties?: object, - appHook?: AppHook): FirebaseServiceNamespace { - let errorMessage; - if (typeof serviceName === 'undefined') { - errorMessage = 'No service name provided. Service name must be a non-empty string.'; - } else if (typeof serviceName !== 'string' || serviceName === '') { - errorMessage = `Invalid service name "${serviceName}" provided. Service name must be a non-empty string.`; - } else if (serviceName in this.serviceFactories) { - errorMessage = `Firebase service named "${serviceName}" has already been registered.`; - } - - if (typeof errorMessage !== 'undefined') { - throw new FirebaseAppError( - AppErrorCodes.INTERNAL_ERROR, - `INTERNAL ASSERT FAILED: ${errorMessage}`, - ); - } - - this.serviceFactories[serviceName] = createService; - if (appHook) { - this.appHooks_[serviceName] = appHook; - } - - // The service namespace is an accessor function which takes a FirebaseApp instance - // or uses the default app if no FirebaseApp instance is provided - const serviceNamespace: FirebaseServiceNamespace = (appArg?: App) => { - if (typeof appArg === 'undefined') { - appArg = this.app(); - } - - // Forward service instance lookup to the FirebaseApp - return (appArg as any)[serviceName](); - }; - - // ... and a container for service-level properties. - if (serviceProperties !== undefined) { - deepExtend(serviceNamespace, serviceProperties); - } - - // Monkey-patch the service namespace onto the Firebase namespace - this.firebase_[serviceName] = serviceNamespace; - - return serviceNamespace; - } - - /** - * Calls the app hooks corresponding to the provided event name for each service within the - * provided App instance. - * - * @param app The App instance whose app hooks to call. - * @param eventName The event name representing which app hooks to call. - */ - private callAppHooks_(app: App, eventName: string): void { - Object.keys(this.serviceFactories).forEach((serviceName) => { - if (this.appHooks_[serviceName]) { - this.appHooks_[serviceName](eventName, app); - } - }); + delete this.apps_[appToRemove.name]; } /** diff --git a/src/firebase-service.ts b/src/firebase-service.ts deleted file mode 100644 index 321d91efb8..0000000000 --- a/src/firebase-service.ts +++ /dev/null @@ -1,43 +0,0 @@ -/*! - * @license - * Copyright 2017 Google Inc. - * - * 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 { app } from './firebase-namespace-api'; -import { FirebaseApp } from './firebase-app'; - -import App = app.App; - -/** - * Internals of a FirebaseService instance. - */ -export interface FirebaseServiceInternalsInterface { - delete(): Promise; -} - -/** - * Services are exposed through instances, each of which is associated with a FirebaseApp. - */ -export interface FirebaseServiceInterface { - app: App; - INTERNAL: FirebaseServiceInternalsInterface; -} - -/** - * Factory method to create FirebaseService instances given a FirebaseApp instance. Can optionally - * add properties and methods to each FirebaseApp instance via the extendApp() function. - */ -export type FirebaseServiceFactory = - (app: FirebaseApp, extendApp?: (props: object) => void) => FirebaseServiceInterface; diff --git a/src/firestore/firestore-internal.ts b/src/firestore/firestore-internal.ts index 5c2210fdc0..2188ec223a 100644 --- a/src/firestore/firestore-internal.ts +++ b/src/firestore/firestore-internal.ts @@ -17,30 +17,13 @@ import { FirebaseApp } from '../firebase-app'; import { FirebaseFirestoreError } from '../utils/error'; -import { FirebaseServiceInterface, FirebaseServiceInternalsInterface } from '../firebase-service'; import { ServiceAccountCredential, isApplicationDefault } from '../credential/credential-internal'; import { Firestore, Settings } from '@google-cloud/firestore'; import * as validator from '../utils/validator'; import * as utils from '../utils/index'; -/** - * Internals of a Firestore instance. - */ -class FirestoreInternals implements FirebaseServiceInternalsInterface { - /** - * Deletes the service and its associated resources. - * - * @return {Promise<()>} An empty Promise that will be fulfilled when the service is deleted. - */ - public delete(): Promise { - // There are no resources to clean up. - return Promise.resolve(); - } -} - -export class FirestoreService implements FirebaseServiceInterface { - public INTERNAL: FirestoreInternals = new FirestoreInternals(); +export class FirestoreService { private appInternal: FirebaseApp; private firestoreClient: Firestore; diff --git a/src/instance-id/instance-id.ts b/src/instance-id/instance-id.ts index e09c630440..80afaeae48 100644 --- a/src/instance-id/instance-id.ts +++ b/src/instance-id/instance-id.ts @@ -15,7 +15,6 @@ */ import { FirebaseApp } from '../firebase-app'; -import { FirebaseServiceInterface, FirebaseServiceInternalsInterface } from '../firebase-service'; import { FirebaseInstanceIdError, InstanceIdClientErrorCode } from '../utils/error'; import { FirebaseInstanceIdRequestHandler } from './instance-id-request-internal'; import { instanceId } from './index'; @@ -23,21 +22,6 @@ import * as validator from '../utils/validator'; import InstanceIdInterface = instanceId.InstanceId; -/** - * Internals of an InstanceId service instance. - */ -class InstanceIdInternals implements FirebaseServiceInternalsInterface { - /** - * Deletes the service and its associated resources. - * - * @return {Promise<()>} An empty Promise that will be fulfilled when the service is deleted. - */ - public delete(): Promise { - // There are no resources to clean up - return Promise.resolve(undefined); - } -} - /** * Gets the {@link InstanceId `InstanceId`} service for the * current app. @@ -52,8 +36,7 @@ class InstanceIdInternals implements FirebaseServiceInternalsInterface { * @return The `InstanceId` service for the * current app. */ -export class InstanceId implements FirebaseServiceInterface, InstanceIdInterface { - public INTERNAL: InstanceIdInternals = new InstanceIdInternals(); +export class InstanceId implements InstanceIdInterface { private app_: FirebaseApp; private requestHandler: FirebaseInstanceIdRequestHandler; diff --git a/src/machine-learning/machine-learning.ts b/src/machine-learning/machine-learning.ts index 9e9ca50b55..dcbb72caec 100644 --- a/src/machine-learning/machine-learning.ts +++ b/src/machine-learning/machine-learning.ts @@ -15,7 +15,6 @@ */ import { FirebaseApp } from '../firebase-app'; -import { FirebaseServiceInterface, FirebaseServiceInternalsInterface } from '../firebase-service'; import { MachineLearningApiClient, ModelResponse, ModelUpdateOptions, isGcsTfliteModelOptions } from './machine-learning-api-client'; @@ -33,27 +32,10 @@ import ModelInterface = machineLearning.Model; import ModelOptions = machineLearning.ModelOptions; import TFLiteModel = machineLearning.TFLiteModel; -/** - * Internals of an ML instance. - */ -class MachineLearningInternals implements FirebaseServiceInternalsInterface { - /** - * Deletes the service and its associated resources. - * - * @return {Promise} An empty Promise that will be resolved when the - * service is deleted. - */ - public delete(): Promise { - // There are no resources to clean up. - return Promise.resolve(); - } -} - /** * The Firebase Machine Learning class */ -export class MachineLearning implements FirebaseServiceInterface, MachineLearningInterface { - public readonly INTERNAL = new MachineLearningInternals(); +export class MachineLearning implements MachineLearningInterface { private readonly client: MachineLearningApiClient; private readonly appInternal: FirebaseApp; diff --git a/src/messaging/messaging.ts b/src/messaging/messaging.ts index 0501693504..88e66cf565 100644 --- a/src/messaging/messaging.ts +++ b/src/messaging/messaging.ts @@ -21,7 +21,6 @@ import { SubRequest } from './batch-request-internal'; import { validateMessage, BLACKLISTED_DATA_PAYLOAD_KEYS, BLACKLISTED_OPTIONS_KEYS } from './messaging-internal'; import { messaging } from './index'; import { FirebaseMessagingRequestHandler } from './messaging-api-request-internal'; -import { FirebaseServiceInterface, FirebaseServiceInternalsInterface } from '../firebase-service'; import { ErrorInfo, MessagingClientErrorCode, FirebaseMessagingError } from '../utils/error'; import * as utils from '../utils'; import * as validator from '../utils/validator'; @@ -186,28 +185,10 @@ function mapRawResponseToTopicManagementResponse(response: object): MessagingTop } -/** - * Internals of a Messaging instance. - */ -class MessagingInternals implements FirebaseServiceInternalsInterface { - /** - * Deletes the service and its associated resources. - * - * @return {Promise<()>} An empty Promise that will be fulfilled when the service is deleted. - */ - public delete(): Promise { - // There are no resources to clean up. - return Promise.resolve(undefined); - } -} - - /** * Messaging service bound to the provided app. */ -export class Messaging implements FirebaseServiceInterface, MessagingInterface { - - public INTERNAL: MessagingInternals = new MessagingInternals(); +export class Messaging implements MessagingInterface { private urlPath: string; private readonly appInternal: FirebaseApp; diff --git a/src/project-management/project-management.ts b/src/project-management/project-management.ts index 0f76912109..9dc8b29902 100644 --- a/src/project-management/project-management.ts +++ b/src/project-management/project-management.ts @@ -15,7 +15,6 @@ */ import { FirebaseApp } from '../firebase-app'; -import { FirebaseServiceInterface, FirebaseServiceInternalsInterface } from '../firebase-service'; import { FirebaseProjectManagementError } from '../utils/error'; import * as utils from '../utils/index'; import * as validator from '../utils/validator'; @@ -28,29 +27,13 @@ import AppMetadata = projectManagement.AppMetadata; import AppPlatform = projectManagement.AppPlatform; import ProjectManagementInterface = projectManagement.ProjectManagement; -/** - * Internals of a Project Management instance. - */ -class ProjectManagementInternals implements FirebaseServiceInternalsInterface { - /** - * Deletes the service and its associated resources. - * - * @return {Promise} An empty Promise that will be resolved when the service is deleted. - */ - public delete(): Promise { - // There are no resources to clean up. - return Promise.resolve(); - } -} - /** * The Firebase ProjectManagement service interface. * * Do not call this constructor directly. Instead, use * [`admin.projectManagement()`](projectManagement#projectManagement). */ -export class ProjectManagement implements FirebaseServiceInterface, ProjectManagementInterface { - public readonly INTERNAL: ProjectManagementInternals = new ProjectManagementInternals(); +export class ProjectManagement implements ProjectManagementInterface { private readonly requestHandler: ProjectManagementRequestHandler; private projectId: string; diff --git a/src/remote-config/remote-config.ts b/src/remote-config/remote-config.ts index 79a261687c..d0b0046832 100644 --- a/src/remote-config/remote-config.ts +++ b/src/remote-config/remote-config.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { FirebaseServiceInterface, FirebaseServiceInternalsInterface } from '../firebase-service'; import { FirebaseApp } from '../firebase-app'; import * as validator from '../utils/validator'; import { remoteConfig } from './index'; @@ -30,26 +29,10 @@ import RemoteConfigUser = remoteConfig.RemoteConfigUser; import Version = remoteConfig.Version; import RemoteConfigInterface = remoteConfig.RemoteConfig; -/** - * Internals of an RemoteConfig service instance. - */ -class RemoteConfigInternals implements FirebaseServiceInternalsInterface { - /** - * Deletes the service and its associated resources. - * - * @return {Promise<()>} An empty Promise that will be fulfilled when the service is deleted. - */ - public delete(): Promise { - // There are no resources to clean up - return Promise.resolve(undefined); - } -} - /** * Remote Config service bound to the provided app. */ -export class RemoteConfig implements FirebaseServiceInterface, RemoteConfigInterface { - public readonly INTERNAL: RemoteConfigInternals = new RemoteConfigInternals(); +export class RemoteConfig implements RemoteConfigInterface { private readonly client: RemoteConfigApiClient; diff --git a/src/security-rules/security-rules.ts b/src/security-rules/security-rules.ts index e6cf7ab1b1..2206410df0 100644 --- a/src/security-rules/security-rules.ts +++ b/src/security-rules/security-rules.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { FirebaseServiceInterface, FirebaseServiceInternalsInterface } from '../firebase-service'; import { FirebaseApp } from '../firebase-app'; import * as validator from '../utils/validator'; import { @@ -85,13 +84,11 @@ export class Ruleset implements RulesetInterface { * Do not call this constructor directly. Instead, use * [`admin.securityRules()`](securityRules#securityRules). */ -export class SecurityRules implements FirebaseServiceInterface, SecurityRulesInterface { +export class SecurityRules implements SecurityRulesInterface { private static readonly CLOUD_FIRESTORE = 'cloud.firestore'; private static readonly FIREBASE_STORAGE = 'firebase.storage'; - public readonly INTERNAL = new SecurityRulesInternals(); - private readonly client: SecurityRulesApiClient; /** @@ -352,12 +349,6 @@ export class SecurityRules implements FirebaseServiceInterface, SecurityRulesInt } } -class SecurityRulesInternals implements FirebaseServiceInternalsInterface { - public delete(): Promise { - return Promise.resolve(); - } -} - function stripProjectIdPrefix(name: string): string { return name.split('/').pop()!; } diff --git a/src/storage/storage.ts b/src/storage/storage.ts index c8c314f920..4364658a8c 100644 --- a/src/storage/storage.ts +++ b/src/storage/storage.ts @@ -17,7 +17,6 @@ import { FirebaseApp } from '../firebase-app'; import { FirebaseError } from '../utils/error'; -import { FirebaseServiceInterface, FirebaseServiceInternalsInterface } from '../firebase-service'; import { ServiceAccountCredential, isApplicationDefault } from '../credential/credential-internal'; import { Bucket, Storage as StorageClient } from '@google-cloud/storage'; import * as utils from '../utils/index'; @@ -26,28 +25,12 @@ import { storage } from './index'; import StorageInterface = storage.Storage; -/** - * Internals of a Storage instance. - */ -class StorageInternals implements FirebaseServiceInternalsInterface { - /** - * Deletes the service and its associated resources. - * - * @return {Promise<()>} An empty Promise that will be fulfilled when the service is deleted. - */ - public delete(): Promise { - // There are no resources to clean up. - return Promise.resolve(); - } -} - /** * The default `Storage` service if no * app is provided or the `Storage` service associated with the provided * app. */ -export class Storage implements FirebaseServiceInterface, StorageInterface { - public readonly INTERNAL: StorageInternals = new StorageInternals(); +export class Storage implements StorageInterface { private readonly appInternal: FirebaseApp; private readonly storageClient: StorageClient; diff --git a/test/resources/mocks.ts b/test/resources/mocks.ts index b90a0c83f8..5512756039 100644 --- a/test/resources/mocks.ts +++ b/test/resources/mocks.ts @@ -27,9 +27,7 @@ import * as jwt from 'jsonwebtoken'; import { AppOptions } from '../../src/firebase-namespace-api'; import { FirebaseNamespace } from '../../src/firebase-namespace'; -import { FirebaseServiceInterface } from '../../src/firebase-service'; import { FirebaseApp } from '../../src/firebase-app'; -import { app as _app } from '../../src/firebase-namespace-api'; import { credential as _credential, GoogleOAuthAccessToken } from '../../src/credential/index'; import { ServiceAccountCredential } from '../../src/credential/credential-internal'; @@ -229,19 +227,6 @@ export function generateSessionCookie(overrides?: object, expiresIn?: number): s return jwt.sign(developerClaims, certificateObject.private_key, options); } -/* eslint-disable @typescript-eslint/no-unused-vars */ -export function firebaseServiceFactory( - firebaseApp: _app.App, - _extendApp?: (props: object) => void, -): FirebaseServiceInterface { - const result = { - app: firebaseApp, - INTERNAL: {}, - }; - return result as FirebaseServiceInterface; -} -/* eslint-enable @typescript-eslint/no-unused-vars */ - /** Mock socket emitter class. */ export class MockSocketEmitter extends events.EventEmitter { public setTimeout: (_: number) => void = () => undefined; diff --git a/test/unit/auth/auth.spec.ts b/test/unit/auth/auth.spec.ts index 5219f55e5d..26521b30cd 100644 --- a/test/unit/auth/auth.spec.ts +++ b/test/unit/auth/auth.spec.ts @@ -3190,14 +3190,6 @@ AUTH_CONFIGS.forEach((testConfig) => { }); }); - if (testConfig.Auth === Auth) { - describe('INTERNAL.delete()', () => { - it('should delete Auth instance', () => { - (auth as Auth).INTERNAL.delete().should.eventually.be.fulfilled; - }); - }); - } - describe('auth emulator support', () => { let mockAuth = testConfig.init(mocks.app()); diff --git a/test/unit/database/database.spec.ts b/test/unit/database/database.spec.ts index 632f35bda9..4a4f969b1e 100644 --- a/test/unit/database/database.spec.ts +++ b/test/unit/database/database.spec.ts @@ -40,7 +40,7 @@ describe('Database', () => { }); afterEach(() => { - return database.INTERNAL.delete().then(() => { + return database.delete().then(() => { return mockApp.delete(); }); }); diff --git a/test/unit/firebase-app.spec.ts b/test/unit/firebase-app.spec.ts index c3b4c04e3e..49da6736c5 100644 --- a/test/unit/firebase-app.spec.ts +++ b/test/unit/firebase-app.spec.ts @@ -28,7 +28,6 @@ import * as mocks from '../resources/mocks'; import { GoogleOAuthAccessToken } from '../../src/credential/index'; import { ServiceAccountCredential } from '../../src/credential/credential-internal'; -import { FirebaseServiceInterface } from '../../src/firebase-service'; import { FirebaseApp, FirebaseAccessToken } from '../../src/firebase-app'; import { FirebaseNamespace, FirebaseNamespaceInternals, FIREBASE_CONFIG_VAR } from '../../src/firebase-namespace'; @@ -65,13 +64,14 @@ const ONE_HOUR_IN_SECONDS = 60 * 60; const ONE_MINUTE_IN_MILLISECONDS = 60 * 1000; const deleteSpy = sinon.spy(); -function mockServiceFactory(app: FirebaseApp): FirebaseServiceInterface { - return { - app, - INTERNAL: { - delete: deleteSpy.bind(null, app.name), - }, - }; + +class TestService { + public deleted = false; + + public delete(): Promise { + this.deleted = true; + return Promise.resolve(); + } } @@ -342,18 +342,15 @@ describe('FirebaseApp', () => { }); it('should call delete() on each service\'s internals', () => { - firebaseNamespace.INTERNAL.registerService(mocks.serviceName, mockServiceFactory); - firebaseNamespace.INTERNAL.registerService(mocks.serviceName + '2', mockServiceFactory); - const app = firebaseNamespace.initializeApp(mocks.appOptions, mocks.appName); - - (app as {[key: string]: any})[mocks.serviceName](); - (app as {[key: string]: any})[mocks.serviceName + '2'](); + const svc1 = new TestService(); + const svc2 = new TestService(); + (app as any).ensureService_(mocks.serviceName, () => svc1); + (app as any).ensureService_(mocks.serviceName + '2', () => svc2); return app.delete().then(() => { - expect(deleteSpy).to.have.been.calledTwice; - expect(deleteSpy.firstCall.args).to.deep.equal([mocks.appName]); - expect(deleteSpy.secondCall.args).to.deep.equal([mocks.appName]); + expect(svc1.deleted).to.be.true; + expect(svc2.deleted).to.be.true; }); }); }); @@ -675,45 +672,6 @@ describe('FirebaseApp', () => { }); }); - describe('#[service]()', () => { - it('should throw if the app has already been deleted', () => { - firebaseNamespace.INTERNAL.registerService(mocks.serviceName, mockServiceFactory); - - const app = firebaseNamespace.initializeApp(mocks.appOptions, mocks.appName); - - return app.delete().then(() => { - expect(() => { - return (app as {[key: string]: any})[mocks.serviceName](); - }).to.throw(`Firebase app named "${mocks.appName}" has already been deleted.`); - }); - }); - - it('should return the service namespace', () => { - firebaseNamespace.INTERNAL.registerService(mocks.serviceName, mockServiceFactory); - - const app = firebaseNamespace.initializeApp(mocks.appOptions, mocks.appName); - - const serviceNamespace = (app as {[key: string]: any})[mocks.serviceName](); - expect(serviceNamespace).to.have.keys(['app', 'INTERNAL']); - }); - - it('should return a cached version of the service on subsequent calls', () => { - const createServiceSpy = sinon.spy(); - firebaseNamespace.INTERNAL.registerService(mocks.serviceName, createServiceSpy); - - const app = firebaseNamespace.initializeApp(mocks.appOptions, mocks.appName); - - expect(createServiceSpy).to.not.have.been.called; - - const serviceNamespace1 = (app as {[key: string]: any})[mocks.serviceName](); - expect(createServiceSpy).to.have.been.calledOnce; - - const serviceNamespace2 = (app as {[key: string]: any})[mocks.serviceName](); - expect(createServiceSpy).to.have.been.calledOnce; - expect(serviceNamespace1).to.deep.equal(serviceNamespace2); - }); - }); - describe('INTERNAL.getToken()', () => { it('throws a custom credential implementation which returns invalid access tokens', () => { diff --git a/test/unit/firebase-namespace.spec.ts b/test/unit/firebase-namespace.spec.ts index d1dbbe7347..07e5a8ba78 100644 --- a/test/unit/firebase-namespace.spec.ts +++ b/test/unit/firebase-namespace.spec.ts @@ -19,7 +19,6 @@ import * as _ from 'lodash'; import * as chai from 'chai'; -import * as sinon from 'sinon'; import * as sinonChai from 'sinon-chai'; import * as chaiAsPromised from 'chai-as-promised'; @@ -259,15 +258,6 @@ describe('FirebaseNamespace', () => { const app = firebaseNamespace.initializeApp(mocks.appOptions, mocks.appName); expect(firebaseNamespace.app(mocks.appName)).to.deep.equal(app); }); - - it('should call the "create" app hook for the new app', () => { - const appHook = sinon.spy(); - firebaseNamespace.INTERNAL.registerService(mocks.serviceName, mocks.firebaseServiceFactory, undefined, appHook); - - const app = firebaseNamespace.initializeApp(mocks.appOptions, mocks.appName); - - expect(appHook).to.have.been.calledOnce.and.calledWith('create', app); - }); }); describe('#INTERNAL.removeApp()', () => { @@ -328,58 +318,6 @@ describe('FirebaseNamespace', () => { firebaseNamespace.INTERNAL.removeApp(mocks.appName); }).to.throw(`Firebase app named "${mocks.appName}" does not exist.`); }); - - it('should call the "delete" app hook for the deleted app', () => { - const appHook = sinon.spy(); - firebaseNamespace.INTERNAL.registerService(mocks.serviceName, mocks.firebaseServiceFactory, undefined, appHook); - - const app = firebaseNamespace.initializeApp(mocks.appOptions, mocks.appName); - - appHook.resetHistory(); - - firebaseNamespace.INTERNAL.removeApp(mocks.appName); - - expect(appHook).to.have.been.calledOnce.and.calledWith('delete', app); - }); - }); - - describe('#INTERNAL.registerService()', () => { - // TODO(jwenger): finish writing tests for regsiterService() to get more code coverage - - it('should throw given no service name', () => { - expect(() => { - firebaseNamespace.INTERNAL.registerService(undefined as unknown as string, mocks.firebaseServiceFactory); - }).to.throw('No service name provided. Service name must be a non-empty string.'); - }); - - const invalidServiceNames = [null, NaN, 0, 1, true, false, [], ['a'], {}, { a: 1 }, _.noop]; - invalidServiceNames.forEach((invalidServiceName) => { - it('should throw given non-string service name: ' + JSON.stringify(invalidServiceName), () => { - expect(() => { - firebaseNamespace.INTERNAL.registerService(invalidServiceName as any, mocks.firebaseServiceFactory); - }).to.throw(`Invalid service name "${invalidServiceName}" provided. Service name must be a non-empty string.`); - }); - }); - - it('should throw given an empty string service name', () => { - expect(() => { - firebaseNamespace.INTERNAL.registerService('', mocks.firebaseServiceFactory); - }).to.throw('Invalid service name "" provided. Service name must be a non-empty string.'); - }); - - it('should throw given a service name which has already been registered', () => { - firebaseNamespace.INTERNAL.registerService(mocks.serviceName, mocks.firebaseServiceFactory); - expect(() => { - firebaseNamespace.INTERNAL.registerService(mocks.serviceName, mocks.firebaseServiceFactory); - }).to.throw(`Firebase service named "${mocks.serviceName}" has already been registered.`); - }); - - it('should throw given a service name which has already been registered', () => { - firebaseNamespace.INTERNAL.registerService(mocks.serviceName, mocks.firebaseServiceFactory); - expect(() => { - firebaseNamespace.INTERNAL.registerService(mocks.serviceName, mocks.firebaseServiceFactory); - }).to.throw(`Firebase service named "${mocks.serviceName}" has already been registered.`); - }); }); describe('#auth()', () => { @@ -411,6 +349,13 @@ describe('FirebaseNamespace', () => { it('should return a reference to Auth type', () => { expect(firebaseNamespace.auth.Auth).to.be.deep.equal(AuthImpl); }); + + it('should return a cached version of Auth on subsequent calls', () => { + firebaseNamespace.initializeApp(mocks.appOptions); + const serviceNamespace1: Auth = firebaseNamespace.auth(); + const serviceNamespace2: Auth = firebaseNamespace.auth(); + expect(serviceNamespace1).to.equal(serviceNamespace2); + }); }); describe('#database()', () => { @@ -468,6 +413,14 @@ describe('FirebaseNamespace', () => { it('should return a reference to enableLogging function', () => { expect(firebaseNamespace.database.enableLogging).to.be.deep.equal(enableLogging); }); + + it('should return a cached version of Database on subsequent calls', () => { + const app = firebaseNamespace.initializeApp(mocks.appOptions); + const db1: Database = firebaseNamespace.database(); + const db2: Database = firebaseNamespace.database(); + expect(db1).to.equal(db2); + return app.delete(); + }); }); describe('#messaging()', () => { @@ -499,6 +452,13 @@ describe('FirebaseNamespace', () => { it('should return a reference to Messaging type', () => { expect(firebaseNamespace.messaging.Messaging).to.be.deep.equal(MessagingImpl); }); + + it('should return a cached version of Messaging on subsequent calls', () => { + firebaseNamespace.initializeApp(mocks.appOptions); + const serviceNamespace1: Messaging = firebaseNamespace.messaging(); + const serviceNamespace2: Messaging = firebaseNamespace.messaging(); + expect(serviceNamespace1).to.equal(serviceNamespace2); + }); }); describe('#machine-learning()', () => { @@ -531,6 +491,13 @@ describe('FirebaseNamespace', () => { expect(firebaseNamespace.machineLearning.MachineLearning) .to.be.deep.equal(MachineLearningImpl); }); + + it('should return a cached version of MachineLearning on subsequent calls', () => { + firebaseNamespace.initializeApp(mocks.appOptions); + const service1: MachineLearning = firebaseNamespace.machineLearning(); + const service2: MachineLearning = firebaseNamespace.machineLearning(); + expect(service1).to.equal(service2); + }); }); describe('#storage()', () => { @@ -562,6 +529,13 @@ describe('FirebaseNamespace', () => { it('should return a reference to Storage type', () => { expect(firebaseNamespace.storage.Storage).to.be.deep.equal(StorageImpl); }); + + it('should return a cached version of Storage on subsequent calls', () => { + firebaseNamespace.initializeApp(mocks.appOptions); + const serviceNamespace1: Storage = firebaseNamespace.storage(); + const serviceNamespace2: Storage = firebaseNamespace.storage(); + expect(serviceNamespace1).to.equal(serviceNamespace2); + }); }); describe('#firestore()', () => { @@ -617,6 +591,13 @@ describe('FirebaseNamespace', () => { it('should return a reference to the v1 namespace', () => { expect(firebaseNamespace.firestore.v1).to.be.deep.equal(v1); }); + + it('should return a cached version of Firestore on subsequent calls', () => { + firebaseNamespace.initializeApp(mocks.appOptions); + const service1: Firestore = firebaseNamespace.firestore(); + const service2: Firestore = firebaseNamespace.firestore(); + expect(service1).to.equal(service2); + }); }); describe('#instanceId()', () => { @@ -650,6 +631,13 @@ describe('FirebaseNamespace', () => { it('should return a reference to InstanceId type', () => { expect(firebaseNamespace.instanceId.InstanceId).to.be.deep.equal(InstanceIdImpl); }); + + it('should return a cached version of InstanceId on subsequent calls', () => { + firebaseNamespace.initializeApp(mocks.appOptions); + const service1: InstanceId = firebaseNamespace.instanceId(); + const service2: InstanceId = firebaseNamespace.instanceId(); + expect(service1).to.equal(service2); + }); }); describe('#projectManagement()', () => { @@ -684,6 +672,13 @@ describe('FirebaseNamespace', () => { expect(firebaseNamespace.projectManagement.ProjectManagement) .to.be.deep.equal(ProjectManagementImpl); }); + + it('should return a cached version of ProjectManagement on subsequent calls', () => { + firebaseNamespace.initializeApp(mocks.appOptions); + const service1: ProjectManagement = firebaseNamespace.projectManagement(); + const service2: ProjectManagement = firebaseNamespace.projectManagement(); + expect(service1).to.equal(service2); + }); }); describe('#securityRules()', () => { @@ -718,6 +713,13 @@ describe('FirebaseNamespace', () => { expect(firebaseNamespace.securityRules.SecurityRules) .to.be.deep.equal(SecurityRulesImpl); }); + + it('should return a cached version of SecurityRules on subsequent calls', () => { + firebaseNamespace.initializeApp(mocks.appOptions); + const service1: SecurityRules = firebaseNamespace.securityRules(); + const service2: SecurityRules = firebaseNamespace.securityRules(); + expect(service1).to.equal(service2); + }); }); describe('#remoteConfig()', () => { @@ -749,5 +751,12 @@ describe('FirebaseNamespace', () => { it('should return a reference to RemoteConfig type', () => { expect(firebaseNamespace.remoteConfig.RemoteConfig).to.be.deep.equal(RemoteConfigImpl); }); + + it('should return a cached version of RemoteConfig on subsequent calls', () => { + firebaseNamespace.initializeApp(mocks.appOptions); + const service1: RemoteConfig = firebaseNamespace.remoteConfig(); + const service2: RemoteConfig = firebaseNamespace.remoteConfig(); + expect(service1).to.equal(service2); + }); }); }); diff --git a/test/unit/messaging/messaging.spec.ts b/test/unit/messaging/messaging.spec.ts index 597cadf1ee..82f0973f67 100644 --- a/test/unit/messaging/messaging.spec.ts +++ b/test/unit/messaging/messaging.spec.ts @@ -4007,10 +4007,4 @@ describe('Messaging', () => { describe('unsubscribeFromTopic()', () => { tokenSubscriptionTests('unsubscribeFromTopic'); }); - - describe('INTERNAL.delete()', () => { - it('should delete Messaging instance', () => { - messaging.INTERNAL.delete().should.eventually.be.fulfilled; - }); - }); });