From 9c8f8be9e6552cb42d9fdcf8aefee63c01f7d307 Mon Sep 17 00:00:00 2001 From: Hiranya Jayathilaka Date: Tue, 26 Jan 2021 17:00:00 -0800 Subject: [PATCH 1/2] feat(rtdb): Added firebase-admin/database module entrypoint --- etc/firebase-admin.api.md | 67 +++++++++++--- gulpfile.js | 1 + src/app/firebase-app.ts | 12 +-- .../{database-internal.ts => database.ts} | 46 +++++++--- src/database/index.ts | 89 +++++++++++-------- test/unit/auth/index.spec.ts | 2 +- test/unit/database/database.spec.ts | 6 +- test/unit/database/index.spec.ts | 87 ++++++++++++++++++ test/unit/index.spec.ts | 1 + 9 files changed, 236 insertions(+), 75 deletions(-) rename src/database/{database-internal.ts => database.ts} (83%) create mode 100644 test/unit/database/index.spec.ts diff --git a/etc/firebase-admin.api.md b/etc/firebase-admin.api.md index 535bd8e883..fff4aa60ae 100644 --- a/etc/firebase-admin.api.md +++ b/etc/firebase-admin.api.md @@ -6,8 +6,15 @@ import { Agent } from 'http'; import { Bucket } from '@google-cloud/storage'; +import { DataSnapshot } from '@firebase/database-types'; +import { EventType } from '@firebase/database-types'; +import { FirebaseDatabase } from '@firebase/database-types'; import * as _firestore from '@google-cloud/firestore'; +import { OnDisconnect } from '@firebase/database-types'; +import { Query } from '@firebase/database-types'; +import { Reference } from '@firebase/database-types'; import * as rtdb from '@firebase/database-types'; +import { ThenableReference } from '@firebase/database-types'; // @public export interface ActionCodeSettings { @@ -274,27 +281,39 @@ export namespace credential { const refreshToken: typeof refreshToken; } +// @public (undocumented) +export interface Database extends FirebaseDatabase { + getRules(): Promise; + getRulesJSON(): Promise; + setRules(source: string | Buffer | object): Promise; +} + // @public -export function database(app?: app.App): database.Database; +export function database(app?: App): database.Database; // @public (undocumented) export namespace database { // (undocumented) - export interface Database extends rtdb.FirebaseDatabase { - getRules(): Promise; - getRulesJSON(): Promise; - setRules(source: string | Buffer | object): Promise; - } - import DataSnapshot = rtdb.DataSnapshot; - import EventType = rtdb.EventType; - import OnDisconnect = rtdb.OnDisconnect; - import Query = rtdb.Query; - import Reference = rtdb.Reference; - import ThenableReference = rtdb.ThenableReference; - import enableLogging = rtdb.enableLogging; + export type Database = Database; + // (undocumented) + export type DataSnapshot = rtdb.DataSnapshot; + // (undocumented) + export type EventType = rtdb.EventType; + // (undocumented) + export type OnDisconnect = rtdb.OnDisconnect; + // (undocumented) + export type Query = rtdb.Query; + // (undocumented) + export type Reference = rtdb.Reference; + // (undocumented) + export type ThenableReference = rtdb.ThenableReference; + const // (undocumented) + enableLogging: typeof rtdb.enableLogging; const ServerValue: rtdb.ServerValue; } +export { DataSnapshot } + // @public export interface DecodedIdToken { // (undocumented) @@ -344,6 +363,11 @@ export interface EmailSignInProviderConfig { passwordRequired?: boolean; } +// @public (undocumented) +export const enableLogging: typeof rtdb.enableLogging; + +export { EventType } + // @public export interface FirebaseArrayIndexError { error: FirebaseError; @@ -403,6 +427,12 @@ export function getApps(): App[]; // @public (undocumented) export function getAuth(app?: App): Auth; +// @public (undocumented) +export function getDatabase(app?: App): Database; + +// @public (undocumented) +export function getDatabaseWithUrl(url: string, app?: App): Database; + // @public (undocumented) export function getInstanceId(app?: App): InstanceId; @@ -866,6 +896,8 @@ export interface OIDCUpdateAuthProviderRequest { issuer?: string; } +export { OnDisconnect } + // @public export interface PhoneIdentifier { // (undocumented) @@ -951,6 +983,10 @@ export interface ProviderIdentifier { providerUid: string; } +export { Query } + +export { Reference } + // @public (undocumented) export function refreshToken(refreshTokenPathOrObject: string | object, httpAgent?: Agent): Credential; @@ -1100,6 +1136,9 @@ export namespace securityRules { } } +// @public (undocumented) +export const ServerValue: rtdb.ServerValue; + // @public (undocumented) export interface ServiceAccount { // (undocumented) @@ -1163,6 +1202,8 @@ export class TenantManager { updateTenant(tenantId: string, tenantOptions: UpdateTenantRequest): Promise; } +export { ThenableReference } + // @public export interface UidIdentifier { // (undocumented) diff --git a/gulpfile.js b/gulpfile.js index 9698d11431..7d3580d7cf 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -89,6 +89,7 @@ gulp.task('compile', function() { 'lib/core.d.ts', 'lib/app/*.d.ts', 'lib/auth/*.d.ts', + 'lib/database/*.d.ts', 'lib/instance-id/*.d.ts', '!lib/utils/index.d.ts', ]; diff --git a/src/app/firebase-app.ts b/src/app/firebase-app.ts index 4545a65c67..c1b748e132 100644 --- a/src/app/firebase-app.ts +++ b/src/app/firebase-app.ts @@ -27,8 +27,7 @@ import { Auth } from '../auth/auth'; import { MachineLearning } from '../machine-learning/machine-learning'; import { Messaging } from '../messaging/messaging'; import { Storage } from '../storage/storage'; -import { database } from '../database/index'; -import { DatabaseService } from '../database/database-internal'; +import { Database } from '../database/index'; import { Firestore } from '@google-cloud/firestore'; import { FirestoreService } from '../firestore/firestore-internal'; import { InstanceId } from '../instance-id/index'; @@ -36,8 +35,6 @@ import { ProjectManagement } from '../project-management/project-management'; import { SecurityRules } from '../security-rules/security-rules'; import { RemoteConfig } from '../remote-config/remote-config'; -import Database = database.Database; - /** * Type representing a callback which is called every time an app lifecycle event occurs. */ @@ -287,11 +284,8 @@ export class FirebaseApp implements app.App { * @return The Database service instance of this app. */ public database(url?: string): Database { - const service: DatabaseService = this.ensureService_('database', () => { - const dbService: typeof DatabaseService = require('../database/database-internal').DatabaseService; - return new dbService(this); - }); - return service.getDatabase(url); + const fn = require('../database/index').getDatabaseWithUrl; + return fn(url, this); } /** diff --git a/src/database/database-internal.ts b/src/database/database.ts similarity index 83% rename from src/database/database-internal.ts rename to src/database/database.ts index 6b7af1c10c..94a00335bd 100644 --- a/src/database/database-internal.ts +++ b/src/database/database.ts @@ -17,26 +17,52 @@ import { URL } from 'url'; import * as path from 'path'; -import { FirebaseApp } from '../app/firebase-app'; -import { FirebaseDatabaseError, AppErrorCodes, FirebaseAppError } from '../utils/error'; import { Database as DatabaseImpl } from '@firebase/database'; -import { database } from './index'; +import { FirebaseDatabase } from '@firebase/database-types'; +import { App } from '../app'; +import { FirebaseApp } from '../app/firebase-app'; +import { FirebaseDatabaseError, AppErrorCodes, FirebaseAppError } from '../utils/error'; import * as validator from '../utils/validator'; import { AuthorizedHttpClient, HttpRequestConfig, HttpError } from '../utils/api-request'; import { getSdkVersion } from '../utils/index'; -import Database = database.Database; +export interface Database extends FirebaseDatabase { + /** + * Gets the currently applied security rules as a string. The return value consists of + * the rules source including comments. + * + * @return A promise fulfilled with the rules as a raw string. + */ + getRules(): Promise; + + /** + * Gets the currently applied security rules as a parsed JSON object. Any comments in + * the original source are stripped away. + * + * @return A promise fulfilled with the parsed rules object. + */ + getRulesJSON(): Promise; + + /** + * Sets the specified rules on the Firebase Realtime Database instance. If the rules source is + * specified as a string or a Buffer, it may include comments. + * + * @param source Source of the rules to apply. Must not be `null` or empty. + * @return Resolves when the rules are set on the Realtime Database. + */ + setRules(source: string | Buffer | object): Promise; +} export class DatabaseService { - private readonly appInternal: FirebaseApp; + private readonly appInternal: App; private databases: { [dbUrl: string]: Database; } = {}; - constructor(app: FirebaseApp) { + constructor(app: App) { if (!validator.isNonNullObject(app) || !('options' in app)) { throw new FirebaseDatabaseError({ code: 'invalid-argument', @@ -63,9 +89,9 @@ export class DatabaseService { /** * Returns the app associated with this DatabaseService instance. * - * @return {FirebaseApp} The app associated with this DatabaseService instance. + * @return The app associated with this DatabaseService instance. */ - get app(): FirebaseApp { + get app(): App { return this.appInternal; } @@ -122,11 +148,11 @@ class DatabaseRulesClient { private readonly dbUrl: string; private readonly httpClient: AuthorizedHttpClient; - constructor(app: FirebaseApp, dbUrl: string) { + constructor(app: App, dbUrl: string) { const parsedUrl = new URL(dbUrl); parsedUrl.pathname = path.join(parsedUrl.pathname, RULES_URL_PATH); this.dbUrl = parsedUrl.toString(); - this.httpClient = new AuthorizedHttpClient(app); + this.httpClient = new AuthorizedHttpClient(app as FirebaseApp); } /** diff --git a/src/database/index.ts b/src/database/index.ts index dae13a3654..4bc350ee05 100644 --- a/src/database/index.ts +++ b/src/database/index.ts @@ -14,9 +14,48 @@ * limitations under the License. */ -import { app } from '../firebase-namespace-api'; -import { ServerValue as sv } from '@firebase/database'; import * as rtdb from '@firebase/database-types'; +import { + enableLogging as enableLoggingFunc, + ServerValue as serverValueConst, +} from '@firebase/database'; + +import { App, getApp } from '../app'; +import { FirebaseApp } from '../app/firebase-app'; +import { Database, DatabaseService } from './database'; +import { Database as TDatabase } from './database'; + +export { Database }; +export { + DataSnapshot, + EventType, + OnDisconnect, + Query, + Reference, + ThenableReference, +} from '@firebase/database-types'; + +export const enableLogging: typeof rtdb.enableLogging = enableLoggingFunc; +export const ServerValue: rtdb.ServerValue = serverValueConst; + +export function getDatabase(app?: App): Database { + return getDatabaseInstance({ app }); +} + +export function getDatabaseWithUrl(url: string, app?: App): Database { + return getDatabaseInstance({ url, app }); +} + +function getDatabaseInstance(options: { url?: string; app?: App }): Database { + let { app } = options; + if (typeof app === 'undefined') { + app = getApp(); + } + + const firebaseApp: FirebaseApp = app as FirebaseApp; + const dbService = firebaseApp.getOrInitService('database', (app) => new DatabaseService(app)); + return dbService.getDatabase(options.url); +} /** * Gets the {@link database.Database `Database`} service for the default @@ -49,49 +88,23 @@ import * as rtdb from '@firebase/database-types'; * @return The default `Database` service if no app * is provided or the `Database` service associated with the provided app. */ -export declare function database(app?: app.App): database.Database; +export declare function database(app?: App): database.Database; /* eslint-disable @typescript-eslint/no-namespace */ export namespace database { - export interface Database extends rtdb.FirebaseDatabase { - /** - * Gets the currently applied security rules as a string. The return value consists of - * the rules source including comments. - * - * @return A promise fulfilled with the rules as a raw string. - */ - getRules(): Promise; - - /** - * Gets the currently applied security rules as a parsed JSON object. Any comments in - * the original source are stripped away. - * - * @return A promise fulfilled with the parsed rules object. - */ - getRulesJSON(): Promise; - - /** - * Sets the specified rules on the Firebase Realtime Database instance. If the rules source is - * specified as a string or a Buffer, it may include comments. - * - * @param source Source of the rules to apply. Must not be `null` or empty. - * @return Resolves when the rules are set on the Realtime Database. - */ - setRules(source: string | Buffer | object): Promise; - } + export type Database = TDatabase; + export type DataSnapshot = rtdb.DataSnapshot; + export type EventType = rtdb.EventType; + export type OnDisconnect = rtdb.OnDisconnect; + export type Query = rtdb.Query; + export type Reference = rtdb.Reference; + export type ThenableReference = rtdb.ThenableReference; - /* eslint-disable @typescript-eslint/no-unused-vars */ - export import DataSnapshot = rtdb.DataSnapshot; - export import EventType = rtdb.EventType; - export import OnDisconnect = rtdb.OnDisconnect; - export import Query = rtdb.Query; - export import Reference = rtdb.Reference; - export import ThenableReference = rtdb.ThenableReference; - export import enableLogging = rtdb.enableLogging; + export declare const enableLogging: typeof rtdb.enableLogging; /** * [`ServerValue`](https://firebase.google.com/docs/reference/js/firebase.database.ServerValue) * module from the `@firebase/database` package. */ - export const ServerValue: rtdb.ServerValue = sv; + export declare const ServerValue: rtdb.ServerValue; } diff --git a/test/unit/auth/index.spec.ts b/test/unit/auth/index.spec.ts index 7d98b351c9..099bb00cd4 100644 --- a/test/unit/auth/index.spec.ts +++ b/test/unit/auth/index.spec.ts @@ -31,7 +31,7 @@ chai.use(chaiAsPromised); const expect = chai.expect; -describe('InstanceId', () => { +describe('Auth', () => { let mockApp: App; let mockCredentialApp: App; diff --git a/test/unit/database/database.spec.ts b/test/unit/database/database.spec.ts index c946d498de..af0fd747fa 100644 --- a/test/unit/database/database.spec.ts +++ b/test/unit/database/database.spec.ts @@ -23,13 +23,11 @@ import * as sinon from 'sinon'; import * as mocks from '../../resources/mocks'; import { FirebaseApp } from '../../../src/app/firebase-app'; -import { DatabaseService } from '../../../src/database/database-internal'; -import { database } from '../../../src/database/index'; +import { DatabaseService } from '../../../src/database/database'; +import { Database } from '../../../src/database/index'; import * as utils from '../utils'; import { HttpClient, HttpRequestConfig } from '../../../src/utils/api-request'; -import Database = database.Database; - describe('Database', () => { let mockApp: FirebaseApp; let database: DatabaseService; diff --git a/test/unit/database/index.spec.ts b/test/unit/database/index.spec.ts new file mode 100644 index 0000000000..8b7678f6a9 --- /dev/null +++ b/test/unit/database/index.spec.ts @@ -0,0 +1,87 @@ +/*! + * @license + * Copyright 2021 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. + */ + +'use strict'; + +import * as chai from 'chai'; +import * as sinonChai from 'sinon-chai'; +import * as chaiAsPromised from 'chai-as-promised'; + +import * as mocks from '../../resources/mocks'; +import { App } from '../../../src/app/index'; +import { getDatabase, getDatabaseWithUrl, Database } from '../../../src/database/index'; +import { FirebaseApp } from '../../../src/app/firebase-app'; + +chai.should(); +chai.use(sinonChai); +chai.use(chaiAsPromised); + +const expect = chai.expect; + +describe('Database', () => { + let mockApp: App; + + beforeEach(() => { + mockApp = mocks.app(); + }); + + afterEach(() => { + return (mockApp as FirebaseApp).delete(); + }); + + describe('getDatabase()', () => { + it('should throw when default app is not available', () => { + expect(() => { + return getDatabase(); + }).to.throw('The default Firebase app does not exist.'); + }); + + it('should not throw given a valid app', () => { + expect(() => { + return getDatabase(mockApp); + }).not.to.throw(); + }); + + it('should return the same instance for a given app instance', () => { + const db1: Database = getDatabase(mockApp); + const db2: Database = getDatabase(mockApp); + expect(db1).to.equal(db2); + }); + }); + + describe('getDatabaseWithUrl()', () => { + it('should throw when default app is not available', () => { + expect(() => { + return getDatabaseWithUrl('https://test.firebaseio.com'); + }).to.throw('The default Firebase app does not exist.'); + }); + + it('should not throw given a valid app', () => { + expect(() => { + return getDatabaseWithUrl('https://test.firebaseio.com', mockApp); + }).not.to.throw(); + }); + + it('should return the same instance for a given app instance', () => { + const db1: Database = getDatabaseWithUrl('https://test.firebaseio.com', mockApp); + const db2: Database = getDatabaseWithUrl('https://test.firebaseio.com', mockApp); + const db3: Database = getDatabaseWithUrl('https://other.firebaseio.com', mockApp); + expect(db1).to.equal(db2); + expect(db1).to.not.equal(db3); + }); + }); +}); diff --git a/test/unit/index.spec.ts b/test/unit/index.spec.ts index 316a41899c..256c2112aa 100644 --- a/test/unit/index.spec.ts +++ b/test/unit/index.spec.ts @@ -43,6 +43,7 @@ import './auth/tenant-manager.spec'; // Database import './database/database.spec'; +import './database/index.spec'; // Messaging import './messaging/messaging.spec'; From 7a071fcf90ed556e580a4616f850730e8ddd2558 Mon Sep 17 00:00:00 2001 From: Hiranya Jayathilaka Date: Tue, 26 Jan 2021 17:13:54 -0800 Subject: [PATCH 2/2] fix(rtdb): Added unit tests for ServerValue and enableLogging --- test/unit/database/index.spec.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/test/unit/database/index.spec.ts b/test/unit/database/index.spec.ts index 8b7678f6a9..382a1d96d3 100644 --- a/test/unit/database/index.spec.ts +++ b/test/unit/database/index.spec.ts @@ -23,7 +23,9 @@ import * as chaiAsPromised from 'chai-as-promised'; import * as mocks from '../../resources/mocks'; import { App } from '../../../src/app/index'; -import { getDatabase, getDatabaseWithUrl, Database } from '../../../src/database/index'; +import { + getDatabase, getDatabaseWithUrl, Database, ServerValue, enableLogging , +} from '../../../src/database/index'; import { FirebaseApp } from '../../../src/app/firebase-app'; chai.should(); @@ -84,4 +86,15 @@ describe('Database', () => { expect(db1).to.not.equal(db3); }); }); + + it('should expose ServerValue sentinel', () => { + expect(() => ServerValue.increment(1)).to.not.throw(); + }); + + it('should expose enableLogging global function', () => { + expect(() => { + enableLogging(console.log); + enableLogging(false); + }).to.not.throw(); + }); });