From 215e5abfd633079478d56b85c67e456593b96aff Mon Sep 17 00:00:00 2001 From: Hiranya Jayathilaka Date: Thu, 18 Feb 2021 11:00:41 -0800 Subject: [PATCH 1/2] fix: Updated integration tests to use the modular API surface --- test/integration/app.spec.ts | 2 +- test/integration/auth.spec.ts | 318 +++++++++++++++++----------------- 2 files changed, 162 insertions(+), 158 deletions(-) diff --git a/test/integration/app.spec.ts b/test/integration/app.spec.ts index cce88a52aa..f7e43d800a 100644 --- a/test/integration/app.spec.ts +++ b/test/integration/app.spec.ts @@ -30,7 +30,7 @@ describe('admin', () => { it('does not load RTDB by default', () => { const firebaseRtdb = require.cache[require.resolve('@firebase/database')]; expect(firebaseRtdb).to.be.undefined; - const rtdbInternal = require.cache[require.resolve('../../lib/database/database-internal')]; + const rtdbInternal = require.cache[require.resolve('../../lib/database/database')]; expect(rtdbInternal).to.be.undefined; }); diff --git a/test/integration/auth.spec.ts b/test/integration/auth.spec.ts index 41e278f821..f16b7546fe 100644 --- a/test/integration/auth.spec.ts +++ b/test/integration/auth.spec.ts @@ -14,21 +14,25 @@ * limitations under the License. */ -import * as admin from '../../lib/index'; -import * as chai from 'chai'; -import * as chaiAsPromised from 'chai-as-promised'; +import url = require('url'); import * as crypto from 'crypto'; import * as bcrypt from 'bcrypt'; +import * as chai from 'chai'; +import * as chaiAsPromised from 'chai-as-promised'; import firebase from '@firebase/app'; import '@firebase/auth'; import { clone } from 'lodash'; +import { User, FirebaseAuth } from '@firebase/auth-types'; import { generateRandomString, projectId, apiKey, noServiceAccountApp, cmdArgs, } from './setup'; -import url = require('url'); import * as mocks from '../resources/mocks'; import { deepExtend, deepCopy } from '../../src/utils/deep-copy'; -import { User, FirebaseAuth } from '@firebase/auth-types'; +import { + AuthProviderConfig, CreateTenantRequest, DeleteUsersResult, PhoneMultiFactorInfo, + TenantAwareAuth, UpdatePhoneMultiFactorInfoRequest, UpdateTenantRequest, UserImportOptions, + UserImportRecord, UserRecord, getAuth, +} from '../../lib/auth/index'; const chalk = require('chalk'); // eslint-disable-line @typescript-eslint/no-var-requires @@ -72,7 +76,7 @@ let deleteQueue = Promise.resolve(); interface UserImportTest { name: string; - importOptions: admin.auth.UserImportOptions; + importOptions: UserImportOptions; rawPassword: string; rawSalt?: string; computePasswordHash(userImportTest: UserImportTest): Buffer; @@ -113,7 +117,7 @@ describe('admin.auth', () => { const newUserData = clone(mockUserData); newUserData.email = generateRandomString(20).toLowerCase() + '@example.com'; newUserData.phoneNumber = testPhoneNumber2; - return admin.auth().createUser(newUserData) + return getAuth().createUser(newUserData) .then((userRecord) => { uidFromCreateUserWithoutUid = userRecord.uid; expect(typeof userRecord.uid).to.equal('string'); @@ -127,7 +131,7 @@ describe('admin.auth', () => { it('createUser() creates a new user with the specified UID', () => { const newUserData: any = clone(mockUserData); newUserData.uid = newUserUid; - return admin.auth().createUser(newUserData) + return getAuth().createUser(newUserData) .then((userRecord) => { expect(userRecord.uid).to.equal(newUserUid); // Confirm expected email. @@ -159,7 +163,7 @@ describe('admin.auth', () => { enrolledFactors, }, }; - return admin.auth().createUser(newUserData) + return getAuth().createUser(newUserData) .then((userRecord) => { expect(userRecord.uid).to.equal(newMultiFactorUserUid); // Confirm expected email. @@ -170,7 +174,7 @@ describe('admin.auth', () => { const firstMultiFactor = userRecord.multiFactor!.enrolledFactors[0]; expect(firstMultiFactor.uid).not.to.be.undefined; expect(firstMultiFactor.enrollmentTime).not.to.be.undefined; - expect((firstMultiFactor as admin.auth.PhoneMultiFactorInfo).phoneNumber).to.equal( + expect((firstMultiFactor as PhoneMultiFactorInfo).phoneNumber).to.equal( enrolledFactors[0].phoneNumber); expect(firstMultiFactor.displayName).to.equal(enrolledFactors[0].displayName); expect(firstMultiFactor.factorId).to.equal(enrolledFactors[0].factorId); @@ -178,7 +182,7 @@ describe('admin.auth', () => { const secondMultiFactor = userRecord.multiFactor!.enrolledFactors[1]; expect(secondMultiFactor.uid).not.to.be.undefined; expect(secondMultiFactor.enrollmentTime).not.to.be.undefined; - expect((secondMultiFactor as admin.auth.PhoneMultiFactorInfo).phoneNumber).to.equal( + expect((secondMultiFactor as PhoneMultiFactorInfo).phoneNumber).to.equal( enrolledFactors[1].phoneNumber); expect(secondMultiFactor.displayName).to.equal(enrolledFactors[1].displayName); expect(secondMultiFactor.factorId).to.equal(enrolledFactors[1].factorId); @@ -188,26 +192,26 @@ describe('admin.auth', () => { it('createUser() fails when the UID is already in use', () => { const newUserData: any = clone(mockUserData); newUserData.uid = newUserUid; - return admin.auth().createUser(newUserData) + return getAuth().createUser(newUserData) .should.eventually.be.rejected.and.have.property('code', 'auth/uid-already-exists'); }); it('getUser() returns a user record with the matching UID', () => { - return admin.auth().getUser(newUserUid) + return getAuth().getUser(newUserUid) .then((userRecord) => { expect(userRecord.uid).to.equal(newUserUid); }); }); it('getUserByEmail() returns a user record with the matching email', () => { - return admin.auth().getUserByEmail(mockUserData.email) + return getAuth().getUserByEmail(mockUserData.email) .then((userRecord) => { expect(userRecord.uid).to.equal(newUserUid); }); }); it('getUserByPhoneNumber() returns a user record with the matching phone number', () => { - return admin.auth().getUserByPhoneNumber(mockUserData.phoneNumber) + return getAuth().getUserByPhoneNumber(mockUserData.phoneNumber) .then((userRecord) => { expect(userRecord.uid).to.equal(newUserUid); }); @@ -232,7 +236,7 @@ describe('admin.auth', () => { // Also create a user with a provider config. (You can't create a user with // a provider config. But you *can* import one.) - const importUser1: admin.auth.UserImportRecord = { + const importUser1: UserImportRecord = { uid: 'uid4', email: 'user4@example.com', phoneNumber: '+15555550004', @@ -262,8 +266,8 @@ describe('admin.auth', () => { await deleteUsersWithDelay(uidsToDelete); // Create/import users required by these tests - await Promise.all(usersToCreate.map((user) => admin.auth().createUser(user))); - await admin.auth().importUsers([importUser1]); + await Promise.all(usersToCreate.map((user) => getAuth().createUser(user))); + await getAuth().importUsers([importUser1]); }); after(async () => { @@ -273,7 +277,7 @@ describe('admin.auth', () => { }); it('returns users by various identifier types in a single call', async () => { - const users = await admin.auth().getUsers([ + const users = await getAuth().getUsers([ { uid: 'uid1' }, { email: 'user2@example.com' }, { phoneNumber: '+15555550003' }, @@ -286,7 +290,7 @@ describe('admin.auth', () => { }); it('returns found users and ignores non-existing users', async () => { - const users = await admin.auth().getUsers([ + const users = await getAuth().getUsers([ { uid: 'uid1' }, { uid: 'uid_that_doesnt_exist' }, { uid: 'uid3' }, @@ -299,14 +303,14 @@ describe('admin.auth', () => { it('returns nothing when queried for only non-existing users', async () => { const notFoundIds = [{ uid: 'non-existing user' }]; - const users = await admin.auth().getUsers(notFoundIds); + const users = await getAuth().getUsers(notFoundIds); expect(users.users).to.be.empty; expect(users.notFound).to.deep.equal(notFoundIds); }); it('de-dups duplicate users', async () => { - const users = await admin.auth().getUsers([ + const users = await getAuth().getUsers([ { uid: 'uid1' }, { uid: 'uid1' }, ]) @@ -321,7 +325,7 @@ describe('admin.auth', () => { return new Date(s).toUTCString() === s; }; - const newUserRecord = await admin.auth().createUser({ + const newUserRecord = await getAuth().createUser({ uid: 'lastRefreshTimeUser', email: 'lastRefreshTimeUser@example.com', password: 'p4ssword', @@ -341,7 +345,7 @@ describe('admin.auth', () => { let userRecord = null; for (let i = 0; i < 3; i++) { - userRecord = await admin.auth().getUser('lastRefreshTimeUser'); + userRecord = await getAuth().getUser('lastRefreshTimeUser'); if (userRecord.metadata.lastRefreshTime) { break; } @@ -360,25 +364,25 @@ describe('admin.auth', () => { expect(lastRefreshTime).lte(creationTime + 3600 * 1000); }); } finally { - admin.auth().deleteUser('lastRefreshTimeUser'); + getAuth().deleteUser('lastRefreshTimeUser'); } }); }); it('listUsers() returns up to the specified number of users', () => { - const promises: Array> = []; + const promises: Array> = []; uids.forEach((uid) => { const tempUserData = { uid, password: 'password', }; - promises.push(admin.auth().createUser(tempUserData)); + promises.push(getAuth().createUser(tempUserData)); }); return Promise.all(promises) .then(() => { // Return 2 users with the provided page token. // This test will fail if other users are created in between. - return admin.auth().listUsers(2, uids[0]); + return getAuth().listUsers(2, uids[0]); }) .then((listUsersResult) => { // Confirm expected number of users. @@ -424,22 +428,22 @@ describe('admin.auth', () => { .then((idToken) => { currentIdToken = idToken; // Verify that user's ID token while checking for revocation. - return admin.auth().verifyIdToken(currentIdToken, true); + return getAuth().verifyIdToken(currentIdToken, true); }) .then((decodedIdToken) => { // Verification should succeed. Revoke that user's session. return new Promise((resolve) => setTimeout(() => resolve( - admin.auth().revokeRefreshTokens(decodedIdToken.sub), + getAuth().revokeRefreshTokens(decodedIdToken.sub), ), 1000)); }) .then(() => { // verifyIdToken without checking revocation should still succeed. - return admin.auth().verifyIdToken(currentIdToken) + return getAuth().verifyIdToken(currentIdToken) .should.eventually.be.fulfilled; }) .then(() => { // verifyIdToken while checking for revocation should fail. - return admin.auth().verifyIdToken(currentIdToken, true) + return getAuth().verifyIdToken(currentIdToken, true) .should.eventually.be.rejected.and.have.property('code', 'auth/id-token-revoked'); }) .then(() => { @@ -459,16 +463,16 @@ describe('admin.auth', () => { }) .then((idToken) => { // ID token for new session should be valid even with revocation check. - return admin.auth().verifyIdToken(idToken, true) + return getAuth().verifyIdToken(idToken, true) .should.eventually.be.fulfilled; }); }); it('setCustomUserClaims() sets claims that are accessible via user\'s ID token', () => { // Set custom claims on the user. - return admin.auth().setCustomUserClaims(newUserUid, customClaims) + return getAuth().setCustomUserClaims(newUserUid, customClaims) .then(() => { - return admin.auth().getUser(newUserUid); + return getAuth().getUser(newUserUid); }) .then((userRecord) => { // Confirm custom claims set on the UserRecord. @@ -484,7 +488,7 @@ describe('admin.auth', () => { }) .then((idToken) => { // Verify ID token contents. - return admin.auth().verifyIdToken(idToken); + return getAuth().verifyIdToken(idToken); }) .then((decodedIdToken: {[key: string]: any}) => { // Confirm expected claims set on the user's ID token. @@ -494,10 +498,10 @@ describe('admin.auth', () => { } } // Test clearing of custom claims. - return admin.auth().setCustomUserClaims(newUserUid, null); + return getAuth().setCustomUserClaims(newUserUid, null); }) .then(() => { - return admin.auth().getUser(newUserUid); + return getAuth().getUser(newUserUid); }) .then((userRecord) => { // Custom claims should be cleared. @@ -508,7 +512,7 @@ describe('admin.auth', () => { }) .then((idToken) => { // Verify ID token contents. - return admin.auth().verifyIdToken(idToken); + return getAuth().verifyIdToken(idToken); }) .then((decodedIdToken: {[key: string]: any}) => { // Confirm all custom claims are cleared. @@ -540,7 +544,7 @@ describe('admin.auth', () => { enrollmentTime: now, }, ]; - return admin.auth().updateUser(newUserUid, { + return getAuth().updateUser(newUserUid, { email: updatedEmail, phoneNumber: updatedPhone, emailVerified: true, @@ -561,7 +565,7 @@ describe('admin.auth', () => { expect(actualUserRecord.multiFactor.enrolledFactors.length).to.equal(2); expect(actualUserRecord.multiFactor.enrolledFactors).to.deep.equal(enrolledFactors); // Update list of second factors. - return admin.auth().updateUser(newUserUid, { + return getAuth().updateUser(newUserUid, { multiFactor: { enrolledFactors: [enrolledFactors[0]], }, @@ -572,7 +576,7 @@ describe('admin.auth', () => { const actualUserRecord: {[key: string]: any} = userRecord.toJSON(); expect(actualUserRecord.multiFactor.enrolledFactors[0]).to.deep.equal(enrolledFactors[0]); // Remove all second factors. - return admin.auth().updateUser(newUserUid, { + return getAuth().updateUser(newUserUid, { multiFactor: { enrolledFactors: null, }, @@ -585,33 +589,33 @@ describe('admin.auth', () => { }); it('getUser() fails when called with a non-existing UID', () => { - return admin.auth().getUser(nonexistentUid) + return getAuth().getUser(nonexistentUid) .should.eventually.be.rejected.and.have.property('code', 'auth/user-not-found'); }); it('getUserByEmail() fails when called with a non-existing email', () => { - return admin.auth().getUserByEmail(nonexistentUid + '@example.com') + return getAuth().getUserByEmail(nonexistentUid + '@example.com') .should.eventually.be.rejected.and.have.property('code', 'auth/user-not-found'); }); it('getUserByPhoneNumber() fails when called with a non-existing phone number', () => { - return admin.auth().getUserByPhoneNumber(nonexistentPhoneNumber) + return getAuth().getUserByPhoneNumber(nonexistentPhoneNumber) .should.eventually.be.rejected.and.have.property('code', 'auth/user-not-found'); }); it('updateUser() fails when called with a non-existing UID', () => { - return admin.auth().updateUser(nonexistentUid, { + return getAuth().updateUser(nonexistentUid, { emailVerified: true, }).should.eventually.be.rejected.and.have.property('code', 'auth/user-not-found'); }); it('deleteUser() fails when called with a non-existing UID', () => { - return admin.auth().deleteUser(nonexistentUid) + return getAuth().deleteUser(nonexistentUid) .should.eventually.be.rejected.and.have.property('code', 'auth/user-not-found'); }); it('createCustomToken() mints a JWT that can be used to sign in', () => { - return admin.auth().createCustomToken(newUserUid, { + return getAuth().createCustomToken(newUserUid, { isAdmin: true, }) .then((customToken) => { @@ -622,7 +626,7 @@ describe('admin.auth', () => { return user!.getIdToken(); }) .then((idToken) => { - return admin.auth().verifyIdToken(idToken); + return getAuth().verifyIdToken(idToken); }) .then((token) => { expect(token.uid).to.equal(newUserUid); @@ -631,7 +635,7 @@ describe('admin.auth', () => { }); it('createCustomToken() can mint JWTs without a service account', () => { - return admin.auth(noServiceAccountApp).createCustomToken(newUserUid, { + return getAuth(noServiceAccountApp).createCustomToken(newUserUid, { isAdmin: true, }) .then((customToken) => { @@ -642,7 +646,7 @@ describe('admin.auth', () => { return user!.getIdToken(); }) .then((idToken) => { - return admin.auth(noServiceAccountApp).verifyIdToken(idToken); + return getAuth(noServiceAccountApp).verifyIdToken(idToken); }) .then((token) => { expect(token.uid).to.equal(newUserUid); @@ -651,7 +655,7 @@ describe('admin.auth', () => { }); it('verifyIdToken() fails when called with an invalid token', () => { - return admin.auth().verifyIdToken('invalid-token') + return getAuth().verifyIdToken('invalid-token') .should.eventually.be.rejected.and.have.property('code', 'auth/argument-error'); }); @@ -668,7 +672,7 @@ describe('admin.auth', () => { // Create the test user before running this suite of tests. before(() => { - return admin.auth().createUser(userData); + return getAuth().createUser(userData); }); // Sign out after each test. @@ -683,9 +687,9 @@ describe('admin.auth', () => { it('generatePasswordResetLink() should return a password reset link', () => { // Ensure old password set on created user. - return admin.auth().updateUser(uid, { password: 'password' }) + return getAuth().updateUser(uid, { password: 'password' }) .then(() => { - return admin.auth().generatePasswordResetLink(email, actionCodeSettings); + return getAuth().generatePasswordResetLink(email, actionCodeSettings); }) .then((link) => { const code = getActionCode(link); @@ -705,10 +709,10 @@ describe('admin.auth', () => { it('generateEmailVerificationLink() should return a verification link', () => { // Ensure the user's email is unverified. - return admin.auth().updateUser(uid, { password: 'password', emailVerified: false }) + return getAuth().updateUser(uid, { password: 'password', emailVerified: false }) .then((userRecord) => { expect(userRecord.emailVerified).to.be.false; - return admin.auth().generateEmailVerificationLink(email, actionCodeSettings); + return getAuth().generateEmailVerificationLink(email, actionCodeSettings); }) .then((link) => { const code = getActionCode(link); @@ -726,7 +730,7 @@ describe('admin.auth', () => { }); it('generateSignInWithEmailLink() should return a sign-in link', () => { - return admin.auth().generateSignInWithEmailLink(email, actionCodeSettings) + return getAuth().generateSignInWithEmailLink(email, actionCodeSettings) .then((link) => { expect(getContinueUrl(link)).equal(actionCodeSettings.url); return clientAuth().signInWithEmailLink(email, link); @@ -742,7 +746,7 @@ describe('admin.auth', () => { describe('Tenant management operations', () => { let createdTenantId: string; const createdTenants: string[] = []; - const tenantOptions: admin.auth.CreateTenantRequest = { + const tenantOptions: CreateTenantRequest = { displayName: 'testTenant1', emailSignInConfig: { enabled: true, @@ -819,14 +823,14 @@ describe('admin.auth', () => { const promises: Array> = []; createdTenants.forEach((tenantId) => { promises.push( - admin.auth().tenantManager().deleteTenant(tenantId) + getAuth().tenantManager().deleteTenant(tenantId) .catch(() => {/** Ignore. */})); }); return Promise.all(promises); }); it('createTenant() should resolve with a new tenant', () => { - return admin.auth().tenantManager().createTenant(tenantOptions) + return getAuth().tenantManager().createTenant(tenantOptions) .then((actualTenant) => { createdTenantId = actualTenant.tenantId; createdTenants.push(createdTenantId); @@ -838,7 +842,7 @@ describe('admin.auth', () => { // Sanity check user management + email link generation + custom attribute APIs. // TODO: Confirm behavior in client SDK when it starts supporting it. describe('supports user management, email link generation, custom attribute and token revocation APIs', () => { - let tenantAwareAuth: admin.auth.TenantAwareAuth; + let tenantAwareAuth: TenantAwareAuth; let createdUserUid: string; let lastValidSinceTime: number; const newUserData = clone(mockUserData); @@ -857,7 +861,7 @@ describe('admin.auth', () => { if (!createdTenantId) { this.skip(); } else { - tenantAwareAuth = admin.auth().tenantManager().authForTenant(createdTenantId); + tenantAwareAuth = getAuth().tenantManager().authForTenant(createdTenantId); } }); @@ -1018,7 +1022,7 @@ describe('admin.auth', () => { // Sanity check OIDC/SAML config management API. describe('SAML management APIs', () => { - let tenantAwareAuth: admin.auth.TenantAwareAuth; + let tenantAwareAuth: TenantAwareAuth; const authProviderConfig = { providerId: randomSamlProviderId(), displayName: 'SAML_DISPLAY_NAME1', @@ -1045,7 +1049,7 @@ describe('admin.auth', () => { if (!createdTenantId) { this.skip(); } else { - tenantAwareAuth = admin.auth().tenantManager().authForTenant(createdTenantId); + tenantAwareAuth = getAuth().tenantManager().authForTenant(createdTenantId); } }); @@ -1084,7 +1088,7 @@ describe('admin.auth', () => { }); describe('OIDC management APIs', () => { - let tenantAwareAuth: admin.auth.TenantAwareAuth; + let tenantAwareAuth: TenantAwareAuth; const authProviderConfig = { providerId: randomOidcProviderId(), displayName: 'OIDC_DISPLAY_NAME1', @@ -1103,7 +1107,7 @@ describe('admin.auth', () => { if (!createdTenantId) { this.skip(); } else { - tenantAwareAuth = admin.auth().tenantManager().authForTenant(createdTenantId); + tenantAwareAuth = getAuth().tenantManager().authForTenant(createdTenantId); } }); @@ -1142,7 +1146,7 @@ describe('admin.auth', () => { }); it('getTenant() should resolve with expected tenant', () => { - return admin.auth().tenantManager().getTenant(createdTenantId) + return getAuth().tenantManager().getTenant(createdTenantId) .then((actualTenant) => { expect(actualTenant.toJSON()).to.deep.equal(expectedCreatedTenant); }); @@ -1151,7 +1155,7 @@ describe('admin.auth', () => { it('updateTenant() should resolve with the updated tenant', () => { expectedUpdatedTenant.tenantId = createdTenantId; expectedUpdatedTenant2.tenantId = createdTenantId; - const updatedOptions: admin.auth.UpdateTenantRequest = { + const updatedOptions: UpdateTenantRequest = { displayName: expectedUpdatedTenant.displayName, emailSignInConfig: { enabled: false, @@ -1159,7 +1163,7 @@ describe('admin.auth', () => { multiFactorConfig: deepCopy(expectedUpdatedTenant.multiFactorConfig), testPhoneNumbers: deepCopy(expectedUpdatedTenant.testPhoneNumbers), }; - const updatedOptions2: admin.auth.UpdateTenantRequest = { + const updatedOptions2: UpdateTenantRequest = { emailSignInConfig: { enabled: true, passwordRequired: false, @@ -1168,10 +1172,10 @@ describe('admin.auth', () => { // Test clearing of phone numbers. testPhoneNumbers: null, }; - return admin.auth().tenantManager().updateTenant(createdTenantId, updatedOptions) + return getAuth().tenantManager().updateTenant(createdTenantId, updatedOptions) .then((actualTenant) => { expect(actualTenant.toJSON()).to.deep.equal(expectedUpdatedTenant); - return admin.auth().tenantManager().updateTenant(createdTenantId, updatedOptions2); + return getAuth().tenantManager().updateTenant(createdTenantId, updatedOptions2); }) .then((actualTenant) => { expect(actualTenant.toJSON()).to.deep.equal(expectedUpdatedTenant2); @@ -1183,7 +1187,7 @@ describe('admin.auth', () => { const tenantOptions2 = deepCopy(tenantOptions); tenantOptions2.displayName = 'testTenant2'; const listAllTenantIds = (tenantIds: string[], nextPageToken?: string): Promise => { - return admin.auth().tenantManager().listTenants(100, nextPageToken) + return getAuth().tenantManager().listTenants(100, nextPageToken) .then((result) => { result.tenants.forEach((tenant) => { tenantIds.push(tenant.tenantId); @@ -1193,7 +1197,7 @@ describe('admin.auth', () => { } }); }; - return admin.auth().tenantManager().createTenant(tenantOptions2) + return getAuth().tenantManager().createTenant(tenantOptions2) .then((actualTenant) => { createdTenants.push(actualTenant.tenantId); // Test listTenants returns the expected tenants. @@ -1208,9 +1212,9 @@ describe('admin.auth', () => { }); it('deleteTenant() should successfully delete the provided tenant', () => { - return admin.auth().tenantManager().deleteTenant(createdTenantId) + return getAuth().tenantManager().deleteTenant(createdTenantId) .then(() => { - return admin.auth().tenantManager().getTenant(createdTenantId); + return getAuth().tenantManager().getTenant(createdTenantId); }) .then(() => { throw new Error('unexpected success'); @@ -1247,14 +1251,14 @@ describe('admin.auth', () => { const removeTempConfigs = (): Promise => { return Promise.all([ - admin.auth().deleteProviderConfig(authProviderConfig1.providerId).catch(() => {/* empty */}), - admin.auth().deleteProviderConfig(authProviderConfig2.providerId).catch(() => {/* empty */}), + getAuth().deleteProviderConfig(authProviderConfig1.providerId).catch(() => {/* empty */}), + getAuth().deleteProviderConfig(authProviderConfig2.providerId).catch(() => {/* empty */}), ]); }; // Clean up temp configurations used for test. before(() => { - return removeTempConfigs().then(() => admin.auth().createProviderConfig(authProviderConfig1)); + return removeTempConfigs().then(() => getAuth().createProviderConfig(authProviderConfig1)); }); after(() => { @@ -1262,25 +1266,25 @@ describe('admin.auth', () => { }); it('createProviderConfig() successfully creates a SAML config', () => { - return admin.auth().createProviderConfig(authProviderConfig2) + return getAuth().createProviderConfig(authProviderConfig2) .then((config) => { assertDeepEqualUnordered(authProviderConfig2, config); }); }); it('getProviderConfig() successfully returns the expected SAML config', () => { - return admin.auth().getProviderConfig(authProviderConfig1.providerId) + return getAuth().getProviderConfig(authProviderConfig1.providerId) .then((config) => { assertDeepEqualUnordered(authProviderConfig1, config); }); }); it('listProviderConfig() successfully returns the list of SAML providers', () => { - const configs: admin.auth.AuthProviderConfig[] = []; + const configs: AuthProviderConfig[] = []; const listProviders: any = (type: 'saml' | 'oidc', maxResults?: number, pageToken?: string) => { - return admin.auth().listProviderConfigs({ type, maxResults, pageToken }) + return getAuth().listProviderConfigs({ type, maxResults, pageToken }) .then((result) => { - result.providerConfigs.forEach((config: admin.auth.AuthProviderConfig) => { + result.providerConfigs.forEach((config: AuthProviderConfig) => { configs.push(config); }); if (result.pageToken) { @@ -1317,7 +1321,7 @@ describe('admin.auth', () => { callbackURL: 'https://projectId3.firebaseapp.com/__/auth/handler', enableRequestSigning: false, }; - return admin.auth().updateProviderConfig(authProviderConfig1.providerId, modifiedConfigOptions) + return getAuth().updateProviderConfig(authProviderConfig1.providerId, modifiedConfigOptions) .then((config) => { const modifiedConfig = deepExtend( { providerId: authProviderConfig1.providerId }, modifiedConfigOptions); @@ -1345,7 +1349,7 @@ describe('admin.auth', () => { callbackURL: 'https://projectId3.firebaseapp.com/__/auth/handler', enableRequestSigning: false, }; - return admin.auth().updateProviderConfig(authProviderConfig1.providerId, deltaChanges) + return getAuth().updateProviderConfig(authProviderConfig1.providerId, deltaChanges) .then((config) => { const modifiedConfig = deepExtend( { providerId: authProviderConfig1.providerId }, modifiedConfigOptions); @@ -1354,8 +1358,8 @@ describe('admin.auth', () => { }); it('deleteProviderConfig() successfully deletes an existing SAML config', () => { - return admin.auth().deleteProviderConfig(authProviderConfig1.providerId).then(() => { - return admin.auth().getProviderConfig(authProviderConfig1.providerId) + return getAuth().deleteProviderConfig(authProviderConfig1.providerId).then(() => { + return getAuth().getProviderConfig(authProviderConfig1.providerId) .should.eventually.be.rejected.and.have.property('code', 'auth/configuration-not-found'); }); }); @@ -1379,14 +1383,14 @@ describe('admin.auth', () => { const removeTempConfigs = (): Promise => { return Promise.all([ - admin.auth().deleteProviderConfig(authProviderConfig1.providerId).catch(() => {/* empty */}), - admin.auth().deleteProviderConfig(authProviderConfig2.providerId).catch(() => {/* empty */}), + getAuth().deleteProviderConfig(authProviderConfig1.providerId).catch(() => {/* empty */}), + getAuth().deleteProviderConfig(authProviderConfig2.providerId).catch(() => {/* empty */}), ]); }; // Clean up temp configurations used for test. before(() => { - return removeTempConfigs().then(() => admin.auth().createProviderConfig(authProviderConfig1)); + return removeTempConfigs().then(() => getAuth().createProviderConfig(authProviderConfig1)); }); after(() => { @@ -1394,25 +1398,25 @@ describe('admin.auth', () => { }); it('createProviderConfig() successfully creates an OIDC config', () => { - return admin.auth().createProviderConfig(authProviderConfig2) + return getAuth().createProviderConfig(authProviderConfig2) .then((config) => { assertDeepEqualUnordered(authProviderConfig2, config); }); }); it('getProviderConfig() successfully returns the expected OIDC config', () => { - return admin.auth().getProviderConfig(authProviderConfig1.providerId) + return getAuth().getProviderConfig(authProviderConfig1.providerId) .then((config) => { assertDeepEqualUnordered(authProviderConfig1, config); }); }); it('listProviderConfig() successfully returns the list of OIDC providers', () => { - const configs: admin.auth.AuthProviderConfig[] = []; + const configs: AuthProviderConfig[] = []; const listProviders: any = (type: 'saml' | 'oidc', maxResults?: number, pageToken?: string) => { - return admin.auth().listProviderConfigs({ type, maxResults, pageToken }) + return getAuth().listProviderConfigs({ type, maxResults, pageToken }) .then((result) => { - result.providerConfigs.forEach((config: admin.auth.AuthProviderConfig) => { + result.providerConfigs.forEach((config: AuthProviderConfig) => { configs.push(config); }); if (result.pageToken) { @@ -1445,7 +1449,7 @@ describe('admin.auth', () => { issuer: 'https://oidc.com/issuer3', clientId: 'CLIENT_ID3', }; - return admin.auth().updateProviderConfig(authProviderConfig1.providerId, modifiedConfigOptions) + return getAuth().updateProviderConfig(authProviderConfig1.providerId, modifiedConfigOptions) .then((config) => { const modifiedConfig = deepExtend( { providerId: authProviderConfig1.providerId }, modifiedConfigOptions); @@ -1465,7 +1469,7 @@ describe('admin.auth', () => { issuer: 'https://oidc.com/issuer4', clientId: 'CLIENT_ID3', }; - return admin.auth().updateProviderConfig(authProviderConfig1.providerId, deltaChanges) + return getAuth().updateProviderConfig(authProviderConfig1.providerId, deltaChanges) .then((config) => { const modifiedConfig = deepExtend( { providerId: authProviderConfig1.providerId }, modifiedConfigOptions); @@ -1474,8 +1478,8 @@ describe('admin.auth', () => { }); it('deleteProviderConfig() successfully deletes an existing OIDC config', () => { - return admin.auth().deleteProviderConfig(authProviderConfig1.providerId).then(() => { - return admin.auth().getProviderConfig(authProviderConfig1.providerId) + return getAuth().deleteProviderConfig(authProviderConfig1.providerId).then(() => { + return getAuth().getProviderConfig(authProviderConfig1.providerId) .should.eventually.be.rejected.and.have.property('code', 'auth/configuration-not-found'); }); }); @@ -1483,17 +1487,17 @@ describe('admin.auth', () => { it('deleteUser() deletes the user with the given UID', () => { return Promise.all([ - admin.auth().deleteUser(newUserUid), - admin.auth().deleteUser(newMultiFactorUserUid), - admin.auth().deleteUser(uidFromCreateUserWithoutUid), + getAuth().deleteUser(newUserUid), + getAuth().deleteUser(newMultiFactorUserUid), + getAuth().deleteUser(uidFromCreateUserWithoutUid), ]).should.eventually.be.fulfilled; }); describe('deleteUsers()', () => { it('deletes users', async () => { - const uid1 = await admin.auth().createUser({}).then((ur) => ur.uid); - const uid2 = await admin.auth().createUser({}).then((ur) => ur.uid); - const uid3 = await admin.auth().createUser({}).then((ur) => ur.uid); + const uid1 = await getAuth().createUser({}).then((ur) => ur.uid); + const uid2 = await getAuth().createUser({}).then((ur) => ur.uid); + const uid3 = await getAuth().createUser({}).then((ur) => ur.uid); const ids = [{ uid: uid1 }, { uid: uid2 }, { uid: uid3 }]; return deleteUsersWithDelay([uid1, uid2, uid3]) @@ -1502,7 +1506,7 @@ describe('admin.auth', () => { expect(deleteUsersResult.failureCount).to.equal(0); expect(deleteUsersResult.errors).to.have.length(0); - return admin.auth().getUsers(ids); + return getAuth().getUsers(ids); }) .then((getUsersResult) => { expect(getUsersResult.users).to.have.length(0); @@ -1511,7 +1515,7 @@ describe('admin.auth', () => { }); it('deletes users that exist even when non-existing users also specified', async () => { - const uid1 = await admin.auth().createUser({}).then((ur) => ur.uid); + const uid1 = await getAuth().createUser({}).then((ur) => ur.uid); const uid2 = 'uid-that-doesnt-exist'; const ids = [{ uid: uid1 }, { uid: uid2 }]; @@ -1521,7 +1525,7 @@ describe('admin.auth', () => { expect(deleteUsersResult.failureCount).to.equal(0); expect(deleteUsersResult.errors).to.have.length(0); - return admin.auth().getUsers(ids); + return getAuth().getUsers(ids); }) .then((getUsersResult) => { expect(getUsersResult.users).to.have.length(0); @@ -1530,7 +1534,7 @@ describe('admin.auth', () => { }); it('is idempotent', async () => { - const uid = await admin.auth().createUser({}).then((ur) => ur.uid); + const uid = await getAuth().createUser({}).then((ur) => ur.uid); return deleteUsersWithDelay([uid]) .then((deleteUsersResult) => { @@ -1557,7 +1561,7 @@ describe('admin.auth', () => { const uid3 = sessionCookieUids[2]; it('creates a valid Firebase session cookie', () => { - return admin.auth().createCustomToken(uid, { admin: true, groupId: '1234' }) + return getAuth().createCustomToken(uid, { admin: true, groupId: '1234' }) .then((customToken) => clientAuth().signInWithCustomToken(customToken)) .then(({ user }) => { expect(user).to.exist; @@ -1565,7 +1569,7 @@ describe('admin.auth', () => { }) .then((idToken) => { currentIdToken = idToken; - return admin.auth().verifyIdToken(idToken); + return getAuth().verifyIdToken(idToken); }).then((decodedIdTokenClaims) => { expectedExp = Math.floor((new Date().getTime() + expiresIn) / 1000); payloadClaims = decodedIdTokenClaims; @@ -1575,9 +1579,9 @@ describe('admin.auth', () => { delete payloadClaims.iat; expectedIat = Math.floor(new Date().getTime() / 1000); // One day long session cookie. - return admin.auth().createSessionCookie(currentIdToken, { expiresIn }); + return getAuth().createSessionCookie(currentIdToken, { expiresIn }); }) - .then((sessionCookie) => admin.auth().verifySessionCookie(sessionCookie)) + .then((sessionCookie) => getAuth().verifySessionCookie(sessionCookie)) .then((decodedIdToken) => { // Check for expected expiration with +/-5 seconds of variation. expect(decodedIdToken.exp).to.be.within(expectedExp - 5, expectedExp + 5); @@ -1593,7 +1597,7 @@ describe('admin.auth', () => { it('creates a revocable session cookie', () => { let currentSessionCookie: string; - return admin.auth().createCustomToken(uid2) + return getAuth().createCustomToken(uid2) .then((customToken) => clientAuth().signInWithCustomToken(customToken)) .then(({ user }) => { expect(user).to.exist; @@ -1601,26 +1605,26 @@ describe('admin.auth', () => { }) .then((idToken) => { // One day long session cookie. - return admin.auth().createSessionCookie(idToken, { expiresIn }); + return getAuth().createSessionCookie(idToken, { expiresIn }); }) .then((sessionCookie) => { currentSessionCookie = sessionCookie; return new Promise((resolve) => setTimeout(() => resolve( - admin.auth().revokeRefreshTokens(uid2), + getAuth().revokeRefreshTokens(uid2), ), 1000)); }) .then(() => { - return admin.auth().verifySessionCookie(currentSessionCookie) + return getAuth().verifySessionCookie(currentSessionCookie) .should.eventually.be.fulfilled; }) .then(() => { - return admin.auth().verifySessionCookie(currentSessionCookie, true) + return getAuth().verifySessionCookie(currentSessionCookie, true) .should.eventually.be.rejected.and.have.property('code', 'auth/session-cookie-revoked'); }); }); it('fails when called with a revoked ID token', () => { - return admin.auth().createCustomToken(uid3, { admin: true, groupId: '1234' }) + return getAuth().createCustomToken(uid3, { admin: true, groupId: '1234' }) .then((customToken) => clientAuth().signInWithCustomToken(customToken)) .then(({ user }) => { expect(user).to.exist; @@ -1629,11 +1633,11 @@ describe('admin.auth', () => { .then((idToken) => { currentIdToken = idToken; return new Promise((resolve) => setTimeout(() => resolve( - admin.auth().revokeRefreshTokens(uid3), + getAuth().revokeRefreshTokens(uid3), ), 1000)); }) .then(() => { - return admin.auth().createSessionCookie(currentIdToken, { expiresIn }) + return getAuth().createSessionCookie(currentIdToken, { expiresIn }) .should.eventually.be.rejected.and.have.property('code', 'auth/id-token-expired'); }); }); @@ -1643,19 +1647,19 @@ describe('admin.auth', () => { describe('verifySessionCookie()', () => { const uid = sessionCookieUids[0]; it('fails when called with an invalid session cookie', () => { - return admin.auth().verifySessionCookie('invalid-token') + return getAuth().verifySessionCookie('invalid-token') .should.eventually.be.rejected.and.have.property('code', 'auth/argument-error'); }); it('fails when called with a Firebase ID token', () => { - return admin.auth().createCustomToken(uid) + return getAuth().createCustomToken(uid) .then((customToken) => clientAuth().signInWithCustomToken(customToken)) .then(({ user }) => { expect(user).to.exist; return user!.getIdToken(); }) .then((idToken) => { - return admin.auth().verifySessionCookie(idToken) + return getAuth().verifySessionCookie(idToken) .should.eventually.be.rejected.and.have.property('code', 'auth/argument-error'); }); }); @@ -1663,7 +1667,7 @@ describe('admin.auth', () => { describe('importUsers()', () => { const randomUid = 'import_' + generateRandomString(20).toLowerCase(); - let importUserRecord: admin.auth.UserImportRecord; + let importUserRecord: UserImportRecord; const rawPassword = 'password'; const rawSalt = 'NaCl'; // Simulate a user stored using SCRYPT being migrated to Firebase Auth via importUsers. @@ -1875,12 +1879,12 @@ describe('admin.auth', () => { ], }; uids.push(importUserRecord.uid); - return admin.auth().importUsers([importUserRecord]) + return getAuth().importUsers([importUserRecord]) .then((result) => { expect(result.failureCount).to.equal(0); expect(result.successCount).to.equal(1); expect(result.errors.length).to.equal(0); - return admin.auth().getUser(uid); + return getAuth().getUser(uid); }).then((userRecord) => { // The phone number provider will be appended to the list of accounts. importUserRecord.providerData?.push({ @@ -1900,7 +1904,7 @@ describe('admin.auth', () => { const uid = generateRandomString(20).toLowerCase(); const email = uid + '@example.com'; const now = new Date(1476235905000).toUTCString(); - const enrolledFactors: admin.auth.UpdatePhoneMultiFactorInfoRequest[] = [ + const enrolledFactors: UpdatePhoneMultiFactorInfoRequest[] = [ { uid: 'mfaUid1', phoneNumber: '+16505550001', @@ -1941,12 +1945,12 @@ describe('admin.auth', () => { }; uids.push(importUserRecord.uid); - return admin.auth().importUsers([importUserRecord]) + return getAuth().importUsers([importUserRecord]) .then((result) => { expect(result.failureCount).to.equal(0); expect(result.successCount).to.equal(1); expect(result.errors.length).to.equal(0); - return admin.auth().getUser(uid); + return getAuth().getUser(uid); }).then((userRecord) => { // Confirm second factors added to user. const actualUserRecord: {[key: string]: any} = userRecord.toJSON(); @@ -1963,7 +1967,7 @@ describe('admin.auth', () => { { uid: generateRandomString(20).toLowerCase(), phoneNumber: '+1invalid' }, { uid: generateRandomString(20).toLowerCase(), emailVerified: 'invalid' } as any, ]; - return admin.auth().importUsers(users) + return getAuth().importUsers(users) .then((result) => { expect(result.successCount).to.equal(0); expect(result.failureCount).to.equal(4); @@ -1985,18 +1989,18 @@ describe('admin.auth', () => { * Imports the provided user record with the specified hashing options and then * validates the import was successful by signing in to the imported account using * the corresponding plain text password. - * @param {admin.auth.UserImportRecord} importUserRecord The user record to import. - * @param {admin.auth.UserImportOptions} importOptions The import hashing options. - * @param {string} rawPassword The plain unhashed password string. - * @retunr {Promise} A promise that resolved on success. + * @param importUserRecord The user record to import. + * @param importOptions The import hashing options. + * @param rawPassword The plain unhashed password string. + * @retunr A promise that resolved on success. */ function testImportAndSignInUser( - importUserRecord: admin.auth.UserImportRecord, + importUserRecord: UserImportRecord, importOptions: any, rawPassword: string): Promise { const users = [importUserRecord]; // Import the user record. - return admin.auth().importUsers(users, importOptions) + return getAuth().importUsers(users, importOptions) .then((result) => { // Verify the import result. expect(result.failureCount).to.equal(0); @@ -2017,12 +2021,12 @@ function testImportAndSignInUser( /** * Helper function that deletes the user with the specified phone number * if it exists. - * @param {string} phoneNumber The phone number of the user to delete. - * @return {Promise} A promise that resolves when the user is deleted + * @param phoneNumber The phone number of the user to delete. + * @return A promise that resolves when the user is deleted * or is found not to exist. */ function deletePhoneNumberUser(phoneNumber: string): Promise { - return admin.auth().getUserByPhoneNumber(phoneNumber) + return getAuth().getUserByPhoneNumber(phoneNumber) .then((userRecord) => { return safeDelete(userRecord.uid); }) @@ -2038,7 +2042,7 @@ function deletePhoneNumberUser(phoneNumber: string): Promise { * Runs cleanup routine that could affect outcome of tests and removes any * intermediate users created. * - * @return {Promise} A promise that resolves when test preparations are ready. + * @return A promise that resolves when test preparations are ready. */ function cleanup(): Promise { // Delete any existing users that could affect the test outcome. @@ -2061,8 +2065,8 @@ function cleanup(): Promise { /** * Returns the action code corresponding to the link. * - * @param {string} link The link to parse for the action code. - * @return {string} The link's corresponding action code. + * @param link The link to parse for the action code. + * @return The link's corresponding action code. */ function getActionCode(link: string): string { const parsedUrl = new url.URL(link); @@ -2074,8 +2078,8 @@ function getActionCode(link: string): string { /** * Returns the continue URL corresponding to the link. * - * @param {string} link The link to parse for the continue URL. - * @return {string} The link's corresponding continue URL. + * @param link The link to parse for the continue URL. + * @return The link's corresponding continue URL. */ function getContinueUrl(link: string): string { const parsedUrl = new url.URL(link); @@ -2087,8 +2091,8 @@ function getContinueUrl(link: string): string { /** * Returns the tenant ID corresponding to the link. * - * @param {string} link The link to parse for the tenant ID. - * @return {string} The link's corresponding tenant ID. + * @param link The link to parse for the tenant ID. + * @return The link's corresponding tenant ID. */ function getTenantId(link: string): string { const parsedUrl = new url.URL(link); @@ -2102,14 +2106,14 @@ function getTenantId(link: string): string { * requests and throttles them as the Auth backend rate limits this endpoint. * A bulk delete API is being designed to help solve this issue. * - * @param {string} uid The identifier of the user to delete. - * @return {Promise} A promise that resolves when delete operation resolves. + * @param uid The identifier of the user to delete. + * @return A promise that resolves when delete operation resolves. */ function safeDelete(uid: string): Promise { // Wait for delete queue to empty. const deletePromise = deleteQueue .then(() => { - return admin.auth().deleteUser(uid); + return getAuth().deleteUser(uid); }) .catch((error) => { // Suppress user not found error. @@ -2129,14 +2133,14 @@ function safeDelete(uid: string): Promise { * API is rate limited at 1 QPS, and therefore this helper function staggers * subsequent invocations by adding 1 second delay to each call. * - * @param {string[]} uids The list of user identifiers to delete. - * @return {Promise} A promise that resolves when delete operation resolves. + * @param uids The list of user identifiers to delete. + * @return A promise that resolves when delete operation resolves. */ -function deleteUsersWithDelay(uids: string[]): Promise { +function deleteUsersWithDelay(uids: string[]): Promise { return new Promise((resolve) => { setTimeout(resolve, 1000); }).then(() => { - return admin.auth().deleteUsers(uids); + return getAuth().deleteUsers(uids); }); } @@ -2144,8 +2148,8 @@ function deleteUsersWithDelay(uids: string[]): Promise Date: Thu, 18 Feb 2021 17:22:12 -0800 Subject: [PATCH 2/2] fix: Updated integration tests to use new module entry points --- test/integration/app.spec.ts | 109 +++++++++++--- test/integration/database.spec.ts | 67 +++++---- test/integration/firestore.spec.ts | 48 ++++--- test/integration/instance-id.spec.ts | 4 +- test/integration/machine-learning.spec.ts | 149 ++++++++++---------- test/integration/messaging.spec.ts | 46 +++--- test/integration/project-management.spec.ts | 56 ++++---- test/integration/remote-config.spec.ts | 40 +++--- test/integration/security-rules.spec.ts | 77 +++++----- test/integration/setup.ts | 43 +++--- test/integration/storage.spec.ts | 8 +- 11 files changed, 368 insertions(+), 279 deletions(-) diff --git a/test/integration/app.spec.ts b/test/integration/app.spec.ts index f7e43d800a..fcbe9d341b 100644 --- a/test/integration/app.spec.ts +++ b/test/integration/app.spec.ts @@ -15,6 +15,8 @@ */ import * as admin from '../../lib/index'; +import { App, deleteApp, getApp, initializeApp } from '../../lib/app/index'; +import { getAuth } from '../../lib/auth/index'; import { expect } from 'chai'; import { defaultApp, nullApp, nonNullApp, databaseUrl, projectId, storageBucket, @@ -27,30 +29,56 @@ describe('admin', () => { expect(storageBucket).to.be.not.empty; }); - it('does not load RTDB by default', () => { - const firebaseRtdb = require.cache[require.resolve('@firebase/database')]; - expect(firebaseRtdb).to.be.undefined; - const rtdbInternal = require.cache[require.resolve('../../lib/database/database')]; - expect(rtdbInternal).to.be.undefined; - }); + describe('Dependency lazy loading', () => { + const tempCache: {[key: string]: any} = {}; + const dependencies = ['@firebase/database', '@google-cloud/firestore']; + let lazyLoadingApp: App; - it('loads RTDB when calling admin.database', () => { - const rtdbNamespace = admin.database; - expect(rtdbNamespace).to.not.be.null; - const firebaseRtdb = require.cache[require.resolve('@firebase/database')]; - expect(firebaseRtdb).to.not.be.undefined; - }); + before(() => { + // Unload dependencies if already loaded. Some of the other test files have imports + // to firebase-admin/database and firebase-admin/firestore, which cause the corresponding + // dependencies to get loaded before the tests are executed. + dependencies.forEach((name) => { + const resolvedName = require.resolve(name); + tempCache[name] = require.cache[resolvedName]; + delete require.cache[resolvedName]; + }); - it('does not load Firestore by default', () => { - const gcloud = require.cache[require.resolve('@google-cloud/firestore')]; - expect(gcloud).to.be.undefined; - }); + // Initialize the SDK + lazyLoadingApp = initializeApp(defaultApp.options, 'lazyLoadingApp'); + }); + + it('does not load RTDB by default', () => { + const firebaseRtdb = require.cache[require.resolve('@firebase/database')]; + expect(firebaseRtdb).to.be.undefined; + }); + + it('loads RTDB when calling admin.database', () => { + const rtdbNamespace = admin.database; + expect(rtdbNamespace).to.not.be.null; + const firebaseRtdb = require.cache[require.resolve('@firebase/database')]; + expect(firebaseRtdb).to.not.be.undefined; + }); - it('loads Firestore when calling admin.firestore', () => { - const firestoreNamespace = admin.firestore; - expect(firestoreNamespace).to.not.be.null; - const gcloud = require.cache[require.resolve('@google-cloud/firestore')]; - expect(gcloud).to.not.be.undefined; + it('does not load Firestore by default', () => { + const gcloud = require.cache[require.resolve('@google-cloud/firestore')]; + expect(gcloud).to.be.undefined; + }); + + it('loads Firestore when calling admin.firestore', () => { + const firestoreNamespace = admin.firestore; + expect(firestoreNamespace).to.not.be.null; + const gcloud = require.cache[require.resolve('@google-cloud/firestore')]; + expect(gcloud).to.not.be.undefined; + }); + + after(() => { + dependencies.forEach((name) => { + const resolvedName = require.resolve(name); + require.cache[resolvedName] = tempCache[name]; + }); + return deleteApp(lazyLoadingApp); + }) }); }); @@ -98,3 +126,42 @@ describe('admin.app', () => { expect(admin.storage(app).app).to.deep.equal(app); }); }); + +describe('getApp', () => { + it('getApp() returns the default App', () => { + const app = getApp(); + expect(app).to.deep.equal(defaultApp); + expect(app.name).to.equal('[DEFAULT]'); + expect(app.options.databaseURL).to.equal(databaseUrl); + expect(app.options.databaseAuthVariableOverride).to.be.undefined; + expect(app.options.storageBucket).to.equal(storageBucket); + }); + + it('getApp("null") returns the App named "null"', () => { + const app = getApp('null'); + expect(app).to.deep.equal(nullApp); + expect(app.name).to.equal('null'); + expect(app.options.databaseURL).to.equal(databaseUrl); + expect(app.options.databaseAuthVariableOverride).to.be.null; + expect(app.options.storageBucket).to.equal(storageBucket); + }); + + it('getApp("nonNull") returns the App named "nonNull"', () => { + const app = getApp('nonNull'); + expect(app).to.deep.equal(nonNullApp); + expect(app.name).to.equal('nonNull'); + expect(app.options.databaseURL).to.equal(databaseUrl); + expect((app.options.databaseAuthVariableOverride as any).uid).to.be.ok; + expect(app.options.storageBucket).to.equal(storageBucket); + }); + + it('namespace services are attached to the default App', () => { + const app = getApp(); + expect(getAuth(app).app).to.deep.equal(app); + }); + + it('namespace services are attached to the named App', () => { + const app = getApp('null'); + expect(getAuth(app).app).to.deep.equal(app); + }); +}); diff --git a/test/integration/database.spec.ts b/test/integration/database.spec.ts index 708c3a153a..0e522103ad 100644 --- a/test/integration/database.spec.ts +++ b/test/integration/database.spec.ts @@ -14,10 +14,13 @@ * limitations under the License. */ -import * as admin from '../../lib/index'; import * as chai from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; import { defaultApp, nullApp, nonNullApp, cmdArgs, databaseUrl } from './setup'; +import * as admin from '../../lib/index'; +import { + Database, DataSnapshot, EventType, Reference, ServerValue, getDatabase, getDatabaseWithUrl, +} from '../../lib/database/index'; // eslint-disable-next-line @typescript-eslint/no-var-requires const chalk = require('chalk'); @@ -46,12 +49,18 @@ describe('admin.database', () => { '.write': 'auth != null', }, }; - return admin.database().setRules(defaultRules); + return getDatabase().setRules(defaultRules); + }); + + it('getDatabase() returns a database client', () => { + const db: Database = getDatabase(); + expect(db).to.not.be.undefined; }); it('admin.database() returns a database client', () => { - const db = admin.database(); - expect(db).to.be.instanceOf((admin.database as any).Database); + const db: admin.database.Database = admin.database(); + expect(db).to.not.be.undefined; + expect(db).to.equal(getDatabase()); }); it('admin.database.ServerValue type is defined', () => { @@ -60,37 +69,36 @@ describe('admin.database', () => { }); it('default App is not blocked by security rules', () => { - return defaultApp.database().ref('blocked').set(admin.database.ServerValue.TIMESTAMP) + return getDatabase(defaultApp).ref('blocked').set(ServerValue.TIMESTAMP) .should.eventually.be.fulfilled; }); it('App with null auth overrides is blocked by security rules', () => { - return nullApp.database().ref('blocked').set(admin.database.ServerValue.TIMESTAMP) + return getDatabase(nullApp).ref('blocked').set(ServerValue.TIMESTAMP) .should.eventually.be.rejectedWith('PERMISSION_DENIED: Permission denied'); }); it('App with non-null auth override is not blocked by security rules', () => { - return nonNullApp.database().ref('blocked').set(admin.database.ServerValue.TIMESTAMP) + return getDatabase(nonNullApp).ref('blocked').set(ServerValue.TIMESTAMP) .should.eventually.be.fulfilled; }); - describe('admin.database().ref()', () => { - let ref: admin.database.Reference; + describe('Reference', () => { + let ref: Reference; before(() => { - ref = admin.database().ref(path); + ref = getDatabase().ref(path); }); it('ref() can be called with ref', () => { - const copy = admin.database().ref(ref); - expect(copy).to.be.instanceof((admin.database as any).Reference); + const copy: Reference = getDatabase().ref(ref); expect(copy.key).to.equal(ref.key); }); it('set() completes successfully', () => { return ref.set({ success: true, - timestamp: admin.database.ServerValue.TIMESTAMP, + timestamp: ServerValue.TIMESTAMP, }).should.eventually.be.fulfilled; }); @@ -115,24 +123,29 @@ describe('admin.database', () => { }); }); - describe('app.database(url).ref()', () => { + describe('getDatabaseWithUrl()', () => { - let refWithUrl: admin.database.Reference; + let refWithUrl: Reference; before(() => { - const app = admin.app(); - refWithUrl = app.database(databaseUrl).ref(path); + refWithUrl = getDatabaseWithUrl(databaseUrl).ref(path); + }); + + it('getDatabaseWithUrl(url) returns a Database client for URL', () => { + const db: Database = getDatabaseWithUrl(databaseUrl); + expect(db).to.not.be.undefined; }); it('app.database(url) returns a Database client for URL', () => { - const db = admin.app().database(databaseUrl); - expect(db).to.be.instanceOf((admin.database as any).Database); + const db: Database = admin.app().database(databaseUrl); + expect(db).to.not.be.undefined; + expect(db).to.equal(getDatabaseWithUrl(databaseUrl)); }); it('set() completes successfully', () => { return refWithUrl.set({ success: true, - timestamp: admin.database.ServerValue.TIMESTAMP, + timestamp: ServerValue.TIMESTAMP, }).should.eventually.be.fulfilled; }); @@ -157,14 +170,14 @@ describe('admin.database', () => { }); }); - it('admin.database().getRules() returns currently defined rules as a string', () => { - return admin.database().getRules().then((result) => { + it('getDatabase().getRules() returns currently defined rules as a string', () => { + return getDatabase().getRules().then((result) => { return expect(result).to.be.not.empty; }); }); - it('admin.database().getRulesJSON() returns currently defined rules as an object', () => { - return admin.database().getRulesJSON().then((result) => { + it('getDatabase().getRulesJSON() returns currently defined rules as an object', () => { + return getDatabase().getRulesJSON().then((result) => { return expect(result).to.be.not.undefined; }); }); @@ -174,8 +187,8 @@ describe('admin.database', () => { // will trigger a TS compilation failure if the RTDB typings were not loaded // correctly. (Marked as export to avoid compilation warning.) export function addValueEventListener( - db: admin.database.Database, - callback: (s: admin.database.DataSnapshot | null) => any): void { - const eventType: admin.database.EventType = 'value'; + db: Database, + callback: (s: DataSnapshot | null) => any): void { + const eventType: EventType = 'value'; db.ref().on(eventType, callback); } diff --git a/test/integration/firestore.spec.ts b/test/integration/firestore.spec.ts index 13ef9131dd..1695bab9af 100644 --- a/test/integration/firestore.spec.ts +++ b/test/integration/firestore.spec.ts @@ -14,10 +14,14 @@ * limitations under the License. */ -import * as admin from '../../lib/index'; import * as chai from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; import { clone } from 'lodash'; +import * as admin from '../../lib/index'; +import { + DocumentReference, DocumentSnapshot, FieldValue, Firestore, FirestoreDataConverter, + QueryDocumentSnapshot, Timestamp, getFirestore, setLogFunction, +} from '../../lib/firestore/index'; chai.should(); chai.use(chaiAsPromised); @@ -31,21 +35,27 @@ const mountainView = { describe('admin.firestore', () => { - let reference: admin.firestore.DocumentReference; + let reference: DocumentReference; before(() => { - const db = admin.firestore(); + const db = getFirestore(); reference = db.collection('cities').doc(); }); + it('getFirestore() returns a Firestore client', () => { + const firestore: Firestore = getFirestore(); + expect(firestore).to.not.be.undefined; + }); + it('admin.firestore() returns a Firestore client', () => { - const firestore = admin.firestore(); - expect(firestore).to.be.instanceOf(admin.firestore.Firestore); + const firestore: admin.firestore.Firestore = admin.firestore(); + expect(firestore).to.not.be.undefined; + expect(firestore).to.equal(getFirestore()); }); it('app.firestore() returns a Firestore client', () => { - const firestore = admin.app().firestore(); - expect(firestore).to.be.instanceOf(admin.firestore.Firestore); + const firestore: admin.firestore.Firestore = admin.app().firestore(); + expect(firestore).to.not.be.undefined; }); it('supports basic data access', () => { @@ -66,9 +76,9 @@ describe('admin.firestore', () => { }); }); - it('admin.firestore.FieldValue.serverTimestamp() provides a server-side timestamp', () => { + it('FieldValue.serverTimestamp() provides a server-side timestamp', () => { const expected: any = clone(mountainView); - expected.timestamp = admin.firestore.FieldValue.serverTimestamp(); + expected.timestamp = FieldValue.serverTimestamp(); return reference.set(expected) .then(() => { return reference.get(); @@ -77,7 +87,7 @@ describe('admin.firestore', () => { const data = snapshot.data(); expect(data).to.exist; expect(data!.timestamp).is.not.null; - expect(data!.timestamp).to.be.instanceOf(admin.firestore.Timestamp); + expect(data!.timestamp).to.be.instanceOf(Timestamp); return reference.delete(); }) .should.eventually.be.fulfilled; @@ -118,20 +128,20 @@ describe('admin.firestore', () => { }); it('supports operations with custom type converters', () => { - const converter: admin.firestore.FirestoreDataConverter = { + const converter: FirestoreDataConverter = { toFirestore: (city: City) => { return { name: city.localId, population: city.people, }; }, - fromFirestore: (snap: admin.firestore.QueryDocumentSnapshot) => { + fromFirestore: (snap: QueryDocumentSnapshot) => { return new City(snap.data().name, snap.data().population); } }; const expected: City = new City('Sunnyvale', 153185); - const refWithConverter: admin.firestore.DocumentReference = admin.firestore() + const refWithConverter: DocumentReference = getFirestore() .collection('cities') .doc() .withConverter(converter); @@ -139,15 +149,15 @@ describe('admin.firestore', () => { .then(() => { return refWithConverter.get(); }) - .then((snapshot: admin.firestore.DocumentSnapshot) => { + .then((snapshot: DocumentSnapshot) => { expect(snapshot.data()).to.be.instanceOf(City); return refWithConverter.delete(); }); }); it('supports saving references in documents', () => { - const source = admin.firestore().collection('cities').doc(); - const target = admin.firestore().collection('cities').doc(); + const source = getFirestore().collection('cities').doc(); + const target = getFirestore().collection('cities').doc(); return source.set(mountainView) .then(() => { return target.set({ name: 'Palo Alto', sisterCity: source }); @@ -167,10 +177,10 @@ describe('admin.firestore', () => { .should.eventually.be.fulfilled; }); - it('admin.firestore.setLogFunction() enables logging for the Firestore module', () => { + it('setLogFunction() enables logging for the Firestore module', () => { const logs: string[] = []; - const source = admin.firestore().collection('cities').doc(); - admin.firestore.setLogFunction((log) => { + const source = getFirestore().collection('cities').doc(); + setLogFunction((log) => { logs.push(log); }); return source.set({ name: 'San Francisco' }) diff --git a/test/integration/instance-id.spec.ts b/test/integration/instance-id.spec.ts index a7d6eb6df2..841d110772 100644 --- a/test/integration/instance-id.spec.ts +++ b/test/integration/instance-id.spec.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -import * as admin from '../../lib/index'; import * as chai from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; +import { getInstanceId } from '../../lib/instance-id/index'; chai.should(); chai.use(chaiAsPromised); @@ -24,7 +24,7 @@ chai.use(chaiAsPromised); describe('admin.instanceId', () => { it('deleteInstanceId() fails when called with fictive-ID0 instance ID', () => { // instance ids have to conform to /[cdef][A-Za-z0-9_-]{9}[AEIMQUYcgkosw048]/ - return admin.instanceId().deleteInstanceId('fictive-ID0') + return getInstanceId().deleteInstanceId('fictive-ID0') .should.eventually.be .rejectedWith('Instance ID "fictive-ID0": Failed to find the instance ID.'); }); diff --git a/test/integration/machine-learning.spec.ts b/test/integration/machine-learning.spec.ts index f767b7d0ad..900bbaa699 100644 --- a/test/integration/machine-learning.spec.ts +++ b/test/integration/machine-learning.spec.ts @@ -17,12 +17,12 @@ import path = require('path'); import * as chai from 'chai'; -import * as admin from '../../lib/index'; import { projectId } from './setup'; import { Bucket } from '@google-cloud/storage'; - -import AutoMLTfliteModelOptions = admin.machineLearning.AutoMLTfliteModelOptions; -import GcsTfliteModelOptions = admin.machineLearning.GcsTfliteModelOptions; +import { getStorage } from '../../lib/storage/index'; +import { + AutoMLTfliteModelOptions, GcsTfliteModelOptions, Model, ModelOptions, getMachineLearning, +} from '../../lib/machine-learning/index'; const expect = chai.expect; @@ -30,32 +30,31 @@ describe('admin.machineLearning', () => { const modelsToDelete: string[] = []; - function scheduleForDelete(model: admin.machineLearning.Model): void { + function scheduleForDelete(model: Model): void { modelsToDelete.push(model.modelId); } - function unscheduleForDelete(model: admin.machineLearning.Model): void { + function unscheduleForDelete(model: Model): void { modelsToDelete.splice(modelsToDelete.indexOf(model.modelId), 1); } function deleteTempModels(): Promise { const promises: Array> = []; modelsToDelete.forEach((modelId) => { - promises.push(admin.machineLearning().deleteModel(modelId)); + promises.push(getMachineLearning().deleteModel(modelId)); }); modelsToDelete.splice(0, modelsToDelete.length); // Clear out the array. return Promise.all(promises); } - function createTemporaryModel(options?: admin.machineLearning.ModelOptions): - Promise { - let modelOptions: admin.machineLearning.ModelOptions = { + function createTemporaryModel(options?: ModelOptions): Promise { + let modelOptions: ModelOptions = { displayName: 'nodejs_integration_temp_model', }; if (options) { modelOptions = options; } - return admin.machineLearning().createModel(modelOptions) + return getMachineLearning().createModel(modelOptions) .then((model) => { scheduleForDelete(model); return model; @@ -63,7 +62,7 @@ describe('admin.machineLearning', () => { } function uploadModelToGcs(localFileName: string, gcsFileName: string): Promise { - const bucket: Bucket = admin.storage().bucket(); + const bucket: Bucket = getStorage().bucket(); const tfliteFileName = path.join(__dirname, `../resources/${localFileName}`); return bucket.upload(tfliteFileName, { destination: gcsFileName }) .then(() => { @@ -77,11 +76,11 @@ describe('admin.machineLearning', () => { describe('createModel()', () => { it('creates a new Model without ModelFormat', () => { - const modelOptions: admin.machineLearning.ModelOptions = { + const modelOptions: ModelOptions = { displayName: 'node-integ-test-create-1', tags: ['tag123', 'tag345'] }; - return admin.machineLearning().createModel(modelOptions) + return getMachineLearning().createModel(modelOptions) .then((model) => { scheduleForDelete(model); verifyModel(model, modelOptions); @@ -89,7 +88,7 @@ describe('admin.machineLearning', () => { }); it('creates a new Model with valid GCS TFLite ModelFormat', () => { - const modelOptions: admin.machineLearning.ModelOptions = { + const modelOptions: ModelOptions = { displayName: 'node-integ-test-create-2', tags: ['tag234', 'tag456'], tfliteModel: { gcsTfliteUri: 'this will be replaced below' }, @@ -97,7 +96,7 @@ describe('admin.machineLearning', () => { return uploadModelToGcs('model1.tflite', 'valid_model.tflite') .then((fileName: string) => { modelOptions.tfliteModel!.gcsTfliteUri = fileName; - return admin.machineLearning().createModel(modelOptions) + return getMachineLearning().createModel(modelOptions) .then((model) => { scheduleForDelete(model); verifyModel(model, modelOptions); @@ -114,12 +113,12 @@ describe('admin.machineLearning', () => { this.skip(); return; } - const modelOptions: admin.machineLearning.ModelOptions = { + const modelOptions: ModelOptions = { displayName: 'node-integ-test-create-automl', tags: ['tagAutoml'], tfliteModel: { automlModel: automlRef } }; - return admin.machineLearning().createModel(modelOptions) + return getMachineLearning().createModel(modelOptions) .then((model) => { return model.waitForUnlocked(55000) .then(() => { @@ -132,7 +131,7 @@ describe('admin.machineLearning', () => { it('creates a new Model with invalid ModelFormat', () => { // Upload a file to default gcs bucket - const modelOptions: admin.machineLearning.ModelOptions = { + const modelOptions: ModelOptions = { displayName: 'node-integ-test-create-3', tags: ['tag234', 'tag456'], tfliteModel: { gcsTfliteUri: 'this will be replaced below' }, @@ -140,7 +139,7 @@ describe('admin.machineLearning', () => { return uploadModelToGcs('invalid_model.tflite', 'invalid_model.tflite') .then((fileName: string) => { modelOptions.tfliteModel!.gcsTfliteUri = fileName; - return admin.machineLearning().createModel(modelOptions) + return getMachineLearning().createModel(modelOptions) .then((model) => { scheduleForDelete(model); verifyModel(model, modelOptions); @@ -149,39 +148,39 @@ describe('admin.machineLearning', () => { }); it ('rejects with invalid-argument when modelOptions are invalid', () => { - const modelOptions: admin.machineLearning.ModelOptions = { + const modelOptions: ModelOptions = { displayName: 'Invalid Name#*^!', }; - return admin.machineLearning().createModel(modelOptions) + return getMachineLearning().createModel(modelOptions) .should.eventually.be.rejected.and.have.property('code', 'machine-learning/invalid-argument'); }); }); describe('updateModel()', () => { - const UPDATE_NAME: admin.machineLearning.ModelOptions = { + const UPDATE_NAME: ModelOptions = { displayName: 'update-model-new-name', }; it('rejects with not-found when the Model does not exist', () => { const nonExistingId = '00000000'; - return admin.machineLearning().updateModel(nonExistingId, UPDATE_NAME) + return getMachineLearning().updateModel(nonExistingId, UPDATE_NAME) .should.eventually.be.rejected.and.have.property( 'code', 'machine-learning/not-found'); }); it('rejects with invalid-argument when the ModelId is invalid', () => { - return admin.machineLearning().updateModel('invalid-model-id', UPDATE_NAME) + return getMachineLearning().updateModel('invalid-model-id', UPDATE_NAME) .should.eventually.be.rejected.and.have.property( 'code', 'machine-learning/invalid-argument'); }); it ('rejects with invalid-argument when modelOptions are invalid', () => { - const modelOptions: admin.machineLearning.ModelOptions = { + const modelOptions: ModelOptions = { displayName: 'Invalid Name#*^!', }; return createTemporaryModel({ displayName: 'node-integ-invalid-argument' }) - .then((model) => admin.machineLearning().updateModel(model.modelId, modelOptions) + .then((model) => getMachineLearning().updateModel(model.modelId, modelOptions) .should.eventually.be.rejected.and.have.property( 'code', 'machine-learning/invalid-argument')); }); @@ -190,10 +189,10 @@ describe('admin.machineLearning', () => { const DISPLAY_NAME = 'node-integ-test-update-1b'; return createTemporaryModel({ displayName: 'node-integ-test-update-1a' }) .then((model) => { - const modelOptions: admin.machineLearning.ModelOptions = { + const modelOptions: ModelOptions = { displayName: DISPLAY_NAME, }; - return admin.machineLearning().updateModel(model.modelId, modelOptions) + return getMachineLearning().updateModel(model.modelId, modelOptions) .then((updatedModel) => { verifyModel(updatedModel, modelOptions); }); @@ -208,10 +207,10 @@ describe('admin.machineLearning', () => { displayName: 'node-integ-test-update-2', tags: ORIGINAL_TAGS, }).then((expectedModel) => { - const modelOptions: admin.machineLearning.ModelOptions = { + const modelOptions: ModelOptions = { tags: NEW_TAGS, }; - return admin.machineLearning().updateModel(expectedModel.modelId, modelOptions) + return getMachineLearning().updateModel(expectedModel.modelId, modelOptions) .then((actualModel) => { expect(actualModel.tags!.length).to.equal(2); expect(actualModel.tags).to.have.same.members(NEW_TAGS); @@ -224,10 +223,10 @@ describe('admin.machineLearning', () => { createTemporaryModel(), uploadModelToGcs('model1.tflite', 'valid_model.tflite')]) .then(([model, fileName]) => { - const modelOptions: admin.machineLearning.ModelOptions = { + const modelOptions: ModelOptions = { tfliteModel: { gcsTfliteUri: fileName }, }; - return admin.machineLearning().updateModel(model.modelId, modelOptions) + return getMachineLearning().updateModel(model.modelId, modelOptions) .then((updatedModel) => { verifyModel(updatedModel, modelOptions); }); @@ -247,10 +246,10 @@ describe('admin.machineLearning', () => { this.skip(); return; } - const modelOptions: admin.machineLearning.ModelOptions = { + const modelOptions: ModelOptions = { tfliteModel: { automlModel: automlRef }, }; - return admin.machineLearning().updateModel(model.modelId, modelOptions) + return getMachineLearning().updateModel(model.modelId, modelOptions) .then((updatedModel) => { return updatedModel.waitForUnlocked(55000) .then(() => { @@ -266,11 +265,11 @@ describe('admin.machineLearning', () => { const TAGS = ['node-integ-tag-1', 'node-integ-tag-2']; return createTemporaryModel({ displayName: 'node-integ-test-update-3a' }) .then((model) => { - const modelOptions: admin.machineLearning.ModelOptions = { + const modelOptions: ModelOptions = { displayName: DISPLAY_NAME, tags: TAGS, }; - return admin.machineLearning().updateModel(model.modelId, modelOptions) + return getMachineLearning().updateModel(model.modelId, modelOptions) .then((updatedModel) => { expect(updatedModel.displayName).to.equal(DISPLAY_NAME); expect(updatedModel.tags).to.have.same.members(TAGS); @@ -282,19 +281,19 @@ describe('admin.machineLearning', () => { describe('publishModel()', () => { it('should reject when model does not exist', () => { const nonExistingName = '00000000'; - return admin.machineLearning().publishModel(nonExistingName) + return getMachineLearning().publishModel(nonExistingName) .should.eventually.be.rejected.and.have.property( 'code', 'machine-learning/not-found'); }); it('rejects with invalid-argument when the ModelId is invalid', () => { - return admin.machineLearning().publishModel('invalid-model-id') + return getMachineLearning().publishModel('invalid-model-id') .should.eventually.be.rejected.and.have.property( 'code', 'machine-learning/invalid-argument'); }); it('publishes the model successfully', () => { - const modelOptions: admin.machineLearning.ModelOptions = { + const modelOptions: ModelOptions = { displayName: 'node-integ-test-publish-1', tfliteModel: { gcsTfliteUri: 'this will be replaced below' }, }; @@ -305,7 +304,7 @@ describe('admin.machineLearning', () => { .then((createdModel) => { expect(createdModel.validationError).to.be.undefined; expect(createdModel.published).to.be.false; - return admin.machineLearning().publishModel(createdModel.modelId) + return getMachineLearning().publishModel(createdModel.modelId) .then((publishedModel) => { expect(publishedModel.published).to.be.true; }); @@ -317,19 +316,19 @@ describe('admin.machineLearning', () => { describe('unpublishModel()', () => { it('should reject when model does not exist', () => { const nonExistingName = '00000000'; - return admin.machineLearning().unpublishModel(nonExistingName) + return getMachineLearning().unpublishModel(nonExistingName) .should.eventually.be.rejected.and.have.property( 'code', 'machine-learning/not-found'); }); it('rejects with invalid-argument when the ModelId is invalid', () => { - return admin.machineLearning().unpublishModel('invalid-model-id') + return getMachineLearning().unpublishModel('invalid-model-id') .should.eventually.be.rejected.and.have.property( 'code', 'machine-learning/invalid-argument'); }); it('unpublishes the model successfully', () => { - const modelOptions: admin.machineLearning.ModelOptions = { + const modelOptions: ModelOptions = { displayName: 'node-integ-test-unpublish-1', tfliteModel: { gcsTfliteUri: 'this will be replaced below' }, }; @@ -340,10 +339,10 @@ describe('admin.machineLearning', () => { .then((createdModel) => { expect(createdModel.validationError).to.be.undefined; expect(createdModel.published).to.be.false; - return admin.machineLearning().publishModel(createdModel.modelId) + return getMachineLearning().publishModel(createdModel.modelId) .then((publishedModel) => { expect(publishedModel.published).to.be.true; - return admin.machineLearning().unpublishModel(publishedModel.modelId) + return getMachineLearning().unpublishModel(publishedModel.modelId) .then((unpublishedModel) => { expect(unpublishedModel.published).to.be.false; }); @@ -357,13 +356,13 @@ describe('admin.machineLearning', () => { describe('getModel()', () => { it('rejects with not-found when the Model does not exist', () => { const nonExistingName = '00000000'; - return admin.machineLearning().getModel(nonExistingName) + return getMachineLearning().getModel(nonExistingName) .should.eventually.be.rejected.and.have.property( 'code', 'machine-learning/not-found'); }); it('rejects with invalid-argument when the ModelId is invalid', () => { - return admin.machineLearning().getModel('invalid-model-id') + return getMachineLearning().getModel('invalid-model-id') .should.eventually.be.rejected.and.have.property( 'code', 'machine-learning/invalid-argument'); }); @@ -371,7 +370,7 @@ describe('admin.machineLearning', () => { it('resolves with existing Model', () => { return createTemporaryModel() .then((expectedModel) => - admin.machineLearning().getModel(expectedModel.modelId) + getMachineLearning().getModel(expectedModel.modelId) .then((actualModel) => { expect(actualModel).to.deep.equal(expectedModel); }), @@ -380,25 +379,25 @@ describe('admin.machineLearning', () => { }); describe('listModels()', () => { - let model1: admin.machineLearning.Model; - let model2: admin.machineLearning.Model; - let model3: admin.machineLearning.Model; + let model1: Model; + let model2: Model; + let model3: Model; before(() => { return Promise.all([ - admin.machineLearning().createModel({ + getMachineLearning().createModel({ displayName: 'node-integ-list1', tags: ['node-integ-tag-1'], }), - admin.machineLearning().createModel({ + getMachineLearning().createModel({ displayName: 'node-integ-list2', tags: ['node-integ-tag-1'], }), - admin.machineLearning().createModel({ + getMachineLearning().createModel({ displayName: 'node-integ-list3', tags: ['node-integ-tag-1'], })]) - .then(([m1, m2, m3]: admin.machineLearning.Model[]) => { + .then(([m1, m2, m3]: Model[]) => { model1 = m1; model2 = m2; model3 = m3; @@ -407,14 +406,14 @@ describe('admin.machineLearning', () => { after(() => { return Promise.all([ - admin.machineLearning().deleteModel(model1.modelId), - admin.machineLearning().deleteModel(model2.modelId), - admin.machineLearning().deleteModel(model3.modelId), + getMachineLearning().deleteModel(model1.modelId), + getMachineLearning().deleteModel(model2.modelId), + getMachineLearning().deleteModel(model3.modelId), ]); }); it('resolves with a list of models', () => { - return admin.machineLearning().listModels({ pageSize: 100 }) + return getMachineLearning().listModels({ pageSize: 100 }) .then((modelList) => { expect(modelList.models.length).to.be.at.least(2); expect(modelList.models).to.deep.include(model1); @@ -424,7 +423,7 @@ describe('admin.machineLearning', () => { }); it('respects page size', () => { - return admin.machineLearning().listModels({ pageSize: 2 }) + return getMachineLearning().listModels({ pageSize: 2 }) .then((modelList) => { expect(modelList.models.length).to.equal(2); expect(modelList.pageToken).not.to.be.empty; @@ -432,7 +431,7 @@ describe('admin.machineLearning', () => { }); it('filters by exact displayName', () => { - return admin.machineLearning().listModels({ filter: 'displayName=node-integ-list1' }) + return getMachineLearning().listModels({ filter: 'displayName=node-integ-list1' }) .then((modelList) => { expect(modelList.models.length).to.equal(1); expect(modelList.models[0]).to.deep.equal(model1); @@ -441,7 +440,7 @@ describe('admin.machineLearning', () => { }); it('filters by displayName prefix', () => { - return admin.machineLearning().listModels({ filter: 'displayName:node-integ-list*', pageSize: 100 }) + return getMachineLearning().listModels({ filter: 'displayName:node-integ-list*', pageSize: 100 }) .then((modelList) => { expect(modelList.models.length).to.be.at.least(3); expect(modelList.models).to.deep.include(model1); @@ -452,7 +451,7 @@ describe('admin.machineLearning', () => { }); it('filters by tag', () => { - return admin.machineLearning().listModels({ filter: 'tags:node-integ-tag-1', pageSize: 100 }) + return getMachineLearning().listModels({ filter: 'tags:node-integ-tag-1', pageSize: 100 }) .then((modelList) => { expect(modelList.models.length).to.be.at.least(3); expect(modelList.models).to.deep.include(model1); @@ -463,11 +462,11 @@ describe('admin.machineLearning', () => { }); it('handles pageTokens properly', () => { - return admin.machineLearning().listModels({ filter: 'displayName:node-integ-list*', pageSize: 2 }) + return getMachineLearning().listModels({ filter: 'displayName:node-integ-list*', pageSize: 2 }) .then((modelList) => { expect(modelList.models.length).to.equal(2); expect(modelList.pageToken).not.to.be.undefined; - return admin.machineLearning().listModels({ + return getMachineLearning().listModels({ filter: 'displayName:node-integ-list*', pageSize: 2, pageToken: modelList.pageToken @@ -480,7 +479,7 @@ describe('admin.machineLearning', () => { }); it('successfully returns an empty list of models', () => { - return admin.machineLearning().listModels({ filter: 'displayName=non-existing-model' }) + return getMachineLearning().listModels({ filter: 'displayName=non-existing-model' }) .then((modelList) => { expect(modelList.models.length).to.equal(0); expect(modelList.pageToken).to.be.undefined; @@ -488,7 +487,7 @@ describe('admin.machineLearning', () => { }); it('rejects with invalid argument if the filter is invalid', () => { - return admin.machineLearning().listModels({ filter: 'invalidFilterItem=foo' }) + return getMachineLearning().listModels({ filter: 'invalidFilterItem=foo' }) .should.eventually.be.rejected.and.have.property( 'code', 'machine-learning/invalid-argument'); }); @@ -497,22 +496,22 @@ describe('admin.machineLearning', () => { describe('deleteModel()', () => { it('rejects with not-found when the Model does not exist', () => { const nonExistingName = '00000000'; - return admin.machineLearning().deleteModel(nonExistingName) + return getMachineLearning().deleteModel(nonExistingName) .should.eventually.be.rejected.and.have.property( 'code', 'machine-learning/not-found'); }); it('rejects with invalid-argument when the Model ID is invalid', () => { - return admin.machineLearning().deleteModel('invalid-model-id') + return getMachineLearning().deleteModel('invalid-model-id') .should.eventually.be.rejected.and.have.property( 'code', 'machine-learning/invalid-argument'); }); it('deletes existing Model', () => { return createTemporaryModel().then((model) => { - return admin.machineLearning().deleteModel(model.modelId) + return getMachineLearning().deleteModel(model.modelId) .then(() => { - return admin.machineLearning().getModel(model.modelId) + return getMachineLearning().getModel(model.modelId) .should.eventually.be.rejected.and.have.property('code', 'machine-learning/not-found'); }) .then(() => { @@ -524,7 +523,7 @@ describe('admin.machineLearning', () => { }); -function verifyModel(model: admin.machineLearning.Model, expectedOptions: admin.machineLearning.ModelOptions): void { +function verifyModel(model: Model, expectedOptions: ModelOptions): void { if (expectedOptions.displayName) { expect(model.displayName).to.equal(expectedOptions.displayName); } else { @@ -548,7 +547,7 @@ function verifyModel(model: admin.machineLearning.Model, expectedOptions: admin. } } -function verifyGcsTfliteModel(model: admin.machineLearning.Model, expectedOptions: GcsTfliteModelOptions): void { +function verifyGcsTfliteModel(model: Model, expectedOptions: GcsTfliteModelOptions): void { const expectedGcsTfliteUri = expectedOptions.tfliteModel.gcsTfliteUri; expect(model.tfliteModel!.gcsTfliteUri).to.equal(expectedGcsTfliteUri); if (expectedGcsTfliteUri.endsWith('invalid_model.tflite')) { @@ -560,7 +559,7 @@ function verifyGcsTfliteModel(model: admin.machineLearning.Model, expectedOption } } -function verifyAutomlTfliteModel(model: admin.machineLearning.Model, expectedOptions: AutoMLTfliteModelOptions): void { +function verifyAutomlTfliteModel(model: Model, expectedOptions: AutoMLTfliteModelOptions): void { const expectedAutomlReference = expectedOptions.tfliteModel.automlModel; expect(model.tfliteModel!.automlModel).to.equal(expectedAutomlReference); expect(model.validationError).to.be.undefined; diff --git a/test/integration/messaging.spec.ts b/test/integration/messaging.spec.ts index e67a81602b..f1c027a935 100644 --- a/test/integration/messaging.spec.ts +++ b/test/integration/messaging.spec.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -import * as admin from '../../lib/index'; import * as chai from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; +import { Message, MulticastMessage, getMessaging } from '../../lib/messaging/index'; chai.should(); chai.use(chaiAsPromised); @@ -38,7 +38,7 @@ const condition = '"test0" in topics || ("test1" in topics && "test2" in topics) const invalidTopic = 'topic-$%#^'; -const message: admin.messaging.Message = { +const message: Message = { data: { foo: 'bar', }, @@ -102,15 +102,15 @@ const options = { describe('admin.messaging', () => { it('send(message, dryRun) returns a message ID', () => { - return admin.messaging().send(message, true) + return getMessaging().send(message, true) .then((name) => { expect(name).matches(/^projects\/.*\/messages\/.*$/); }); }); it('sendAll()', () => { - const messages: admin.messaging.Message[] = [message, message, message]; - return admin.messaging().sendAll(messages, true) + const messages: Message[] = [message, message, message]; + return getMessaging().sendAll(messages, true) .then((response) => { expect(response.responses.length).to.equal(messages.length); expect(response.successCount).to.equal(messages.length); @@ -123,11 +123,11 @@ describe('admin.messaging', () => { }); it('sendAll(500)', () => { - const messages: admin.messaging.Message[] = []; + const messages: Message[] = []; for (let i = 0; i < 500; i++) { messages.push({ topic: `foo-bar-${i % 10}` }); } - return admin.messaging().sendAll(messages, true) + return getMessaging().sendAll(messages, true) .then((response) => { expect(response.responses.length).to.equal(messages.length); expect(response.successCount).to.equal(messages.length); @@ -140,12 +140,12 @@ describe('admin.messaging', () => { }); it('sendMulticast()', () => { - const multicastMessage: admin.messaging.MulticastMessage = { + const multicastMessage: MulticastMessage = { data: message.data, android: message.android, tokens: ['not-a-token', 'also-not-a-token'], }; - return admin.messaging().sendMulticast(multicastMessage, true) + return getMessaging().sendMulticast(multicastMessage, true) .then((response) => { expect(response.responses.length).to.equal(2); expect(response.successCount).to.equal(0); @@ -159,86 +159,86 @@ describe('admin.messaging', () => { }); it('sendToDevice(token) returns a response with multicast ID', () => { - return admin.messaging().sendToDevice(registrationToken, payload, options) + return getMessaging().sendToDevice(registrationToken, payload, options) .then((response) => { expect(typeof response.multicastId).to.equal('number'); }); }); it('sendToDevice(token-list) returns a response with multicat ID', () => { - return admin.messaging().sendToDevice(registrationTokens, payload, options) + return getMessaging().sendToDevice(registrationTokens, payload, options) .then((response) => { expect(typeof response.multicastId).to.equal('number'); }); }); it('sendToDeviceGroup() returns a response with success count', () => { - return admin.messaging().sendToDeviceGroup(notificationKey, payload, options) + return getMessaging().sendToDeviceGroup(notificationKey, payload, options) .then((response) => { expect(typeof response.successCount).to.equal('number'); }); }); it('sendToTopic() returns a response with message ID', () => { - return admin.messaging().sendToTopic(topic, payload, options) + return getMessaging().sendToTopic(topic, payload, options) .then((response) => { expect(typeof response.messageId).to.equal('number'); }); }); it('sendToCondition() returns a response with message ID', () => { - return admin.messaging().sendToCondition(condition, payload, options) + return getMessaging().sendToCondition(condition, payload, options) .then((response) => { expect(typeof response.messageId).to.equal('number'); }); }); it('sendToDevice(token) fails when called with invalid payload', () => { - return admin.messaging().sendToDevice(registrationToken, invalidPayload, options) + return getMessaging().sendToDevice(registrationToken, invalidPayload, options) .should.eventually.be.rejected.and.have.property('code', 'messaging/invalid-payload'); }); it('sendToDevice(token-list) fails when called with invalid payload', () => { - return admin.messaging().sendToDevice(registrationTokens, invalidPayload, options) + return getMessaging().sendToDevice(registrationTokens, invalidPayload, options) .should.eventually.be.rejected.and.have.property('code', 'messaging/invalid-payload'); }); it('sendToDeviceGroup() fails when called with invalid payload', () => { - return admin.messaging().sendToDeviceGroup(notificationKey, invalidPayload, options) + return getMessaging().sendToDeviceGroup(notificationKey, invalidPayload, options) .should.eventually.be.rejected.and.have.property('code', 'messaging/invalid-payload'); }); it('sendToTopic() fails when called with invalid payload', () => { - return admin.messaging().sendToTopic(topic, invalidPayload, options) + return getMessaging().sendToTopic(topic, invalidPayload, options) .should.eventually.be.rejected.and.have.property('code', 'messaging/invalid-payload'); }); it('sendToCondition() fails when called with invalid payload', () => { - return admin.messaging().sendToCondition(condition, invalidPayload, options) + return getMessaging().sendToCondition(condition, invalidPayload, options) .should.eventually.be.rejected.and.have.property('code', 'messaging/invalid-payload'); }); it('subscribeToTopic() returns a response with success count', () => { - return admin.messaging().subscribeToTopic(registrationToken, topic) + return getMessaging().subscribeToTopic(registrationToken, topic) .then((response) => { expect(typeof response.successCount).to.equal('number'); }); }); it('unsubscribeFromTopic() returns a response with success count', () => { - return admin.messaging().unsubscribeFromTopic(registrationToken, topic) + return getMessaging().unsubscribeFromTopic(registrationToken, topic) .then((response) => { expect(typeof response.successCount).to.equal('number'); }); }); it('subscribeToTopic() fails when called with invalid topic', () => { - return admin.messaging().subscribeToTopic(registrationToken, invalidTopic) + return getMessaging().subscribeToTopic(registrationToken, invalidTopic) .should.eventually.be.rejected.and.have.property('code', 'messaging/invalid-argument'); }); it('unsubscribeFromTopic() fails when called with invalid topic', () => { - return admin.messaging().unsubscribeFromTopic(registrationToken, invalidTopic) + return getMessaging().unsubscribeFromTopic(registrationToken, invalidTopic) .should.eventually.be.rejected.and.have.property('code', 'messaging/invalid-argument'); }); }); diff --git a/test/integration/project-management.spec.ts b/test/integration/project-management.spec.ts index de26f4aaf2..0d24566394 100644 --- a/test/integration/project-management.spec.ts +++ b/test/integration/project-management.spec.ts @@ -17,8 +17,10 @@ import * as _ from 'lodash'; import * as chai from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; -import * as admin from '../../lib/index'; import { projectId } from './setup'; +import { + AndroidApp, IosApp, ShaCertificate, getProjectManagement, +} from '../../lib/project-management/index'; const APP_NAMESPACE_PREFIX = 'com.adminsdkintegrationtest.a'; const APP_NAMESPACE_SUFFIX_LENGTH = 15; @@ -37,8 +39,8 @@ chai.use(chaiAsPromised); describe('admin.projectManagement', () => { - let androidApp: admin.projectManagement.AndroidApp; - let iosApp: admin.projectManagement.IosApp; + let androidApp: AndroidApp; + let iosApp: IosApp; before(() => { const androidPromise = ensureAndroidApp() @@ -55,7 +57,7 @@ describe('admin.projectManagement', () => { describe('listAndroidApps()', () => { it('successfully lists Android apps', () => { - return admin.projectManagement().listAndroidApps() + return getProjectManagement().listAndroidApps() .then((apps) => Promise.all(apps.map((app) => app.getMetadata()))) .then((metadatas) => { expect(metadatas.length).to.be.at.least(1); @@ -69,7 +71,7 @@ describe('admin.projectManagement', () => { describe('listIosApps()', () => { it('successfully lists iOS apps', () => { - return admin.projectManagement().listIosApps() + return getProjectManagement().listIosApps() .then((apps) => Promise.all(apps.map((app) => app.getMetadata()))) .then((metadatas) => { expect(metadatas.length).to.be.at.least(1); @@ -86,14 +88,14 @@ describe('admin.projectManagement', () => { const newDisplayName = generateUniqueProjectDisplayName(); // TODO(caot): verify that project name has been renamed successfully after adding the ability // to get project metadata. - return admin.projectManagement().setDisplayName(newDisplayName) + return getProjectManagement().setDisplayName(newDisplayName) .should.eventually.be.fulfilled; }); }); describe('listAppMetadata()', () => { it('successfully lists metadata of all apps', () => { - return admin.projectManagement().listAppMetadata() + return getProjectManagement().listAppMetadata() .then((metadatas) => { expect(metadatas.length).to.be.at.least(2); const testAppMetadatas = metadatas.filter((metadata) => @@ -158,7 +160,7 @@ describe('admin.projectManagement', () => { .then((certs) => { expect(certs.length).to.equal(0); - const shaCertificate = admin.projectManagement().shaCertificate(SHA_256_HASH); + const shaCertificate = getProjectManagement().shaCertificate(SHA_256_HASH); return androidApp.addShaCertificate(shaCertificate); }) .then(() => androidApp.getShaCertificates()) @@ -179,7 +181,7 @@ describe('admin.projectManagement', () => { it('add a cert and then remove it fails due to missing resourceName', () => { const shaCertificate = - admin.projectManagement().shaCertificate(SHA_256_HASH); + getProjectManagement().shaCertificate(SHA_256_HASH); return androidApp.addShaCertificate(shaCertificate) .then(() => androidApp.deleteShaCertificate(shaCertificate)) .should.eventually.be @@ -211,20 +213,20 @@ describe('admin.projectManagement', () => { /** * Ensures that an Android app owned by these integration tests exist. If not one will be created. * - * @return {Promise} Android app owned by these integration tests. + * @return Android app owned by these integration tests. */ -function ensureAndroidApp(): Promise { - return admin.projectManagement().listAndroidApps() +function ensureAndroidApp(): Promise { + return getProjectManagement().listAndroidApps() .then((apps) => Promise.all(apps.map((app) => app.getMetadata()))) .then((metadatas) => { const metadataOwnedByTest = metadatas.find((metadata) => isIntegrationTestApp(metadata.packageName)); if (metadataOwnedByTest) { - return admin.projectManagement().androidApp(metadataOwnedByTest.appId); + return getProjectManagement().androidApp(metadataOwnedByTest.appId); } // If no Android app owned by these integration tests was found, then create one. - return admin.projectManagement() + return getProjectManagement() .createAndroidApp(generateUniqueAppNamespace(), generateUniqueAppDisplayName()); }); } @@ -232,20 +234,20 @@ function ensureAndroidApp(): Promise { /** * Ensures that an iOS app owned by these integration tests exist. If not one will be created. * - * @return {Promise} iOS app owned by these integration tests. + * @return iOS app owned by these integration tests. */ -function ensureIosApp(): Promise { - return admin.projectManagement().listIosApps() +function ensureIosApp(): Promise { + return getProjectManagement().listIosApps() .then((apps) => Promise.all(apps.map((app) => app.getMetadata()))) .then((metadatas) => { const metadataOwnedByTest = metadatas.find((metadata) => isIntegrationTestApp(metadata.bundleId)); if (metadataOwnedByTest) { - return admin.projectManagement().iosApp(metadataOwnedByTest.appId); + return getProjectManagement().iosApp(metadataOwnedByTest.appId); } // If no iOS app owned by these integration tests was found, then create one. - return admin.projectManagement() + return getProjectManagement() .createIosApp(generateUniqueAppNamespace(), generateUniqueAppDisplayName()); }); } @@ -253,51 +255,51 @@ function ensureIosApp(): Promise { /** * Deletes all SHA certificates from the specified Android app. */ -function deleteAllShaCertificates(androidApp: admin.projectManagement.AndroidApp): Promise { +function deleteAllShaCertificates(androidApp: AndroidApp): Promise { return androidApp.getShaCertificates() - .then((shaCertificates: admin.projectManagement.ShaCertificate[]) => { + .then((shaCertificates: ShaCertificate[]) => { return Promise.all(shaCertificates.map((cert) => androidApp.deleteShaCertificate(cert))); }) .then(() => undefined); } /** - * @return {string} Dot-separated string that can be used as a unique package name or bundle ID. + * @return Dot-separated string that can be used as a unique package name or bundle ID. */ function generateUniqueAppNamespace(): string { return APP_NAMESPACE_PREFIX + generateRandomString(APP_NAMESPACE_SUFFIX_LENGTH); } /** - * @return {string} Dot-separated string that can be used as a unique app display name. + * @return Dot-separated string that can be used as a unique app display name. */ function generateUniqueAppDisplayName(): string { return APP_DISPLAY_NAME_PREFIX + generateRandomString(APP_DISPLAY_NAME_SUFFIX_LENGTH); } /** - * @return {string} string that can be used as a unique project display name. + * @return string that can be used as a unique project display name. */ function generateUniqueProjectDisplayName(): string { return PROJECT_DISPLAY_NAME_PREFIX + generateRandomString(PROJECT_DISPLAY_NAME_SUFFIX_LENGTH); } /** - * @return {boolean} True if the specified appNamespace belongs to these integration tests. + * @return True if the specified appNamespace belongs to these integration tests. */ function isIntegrationTestApp(appNamespace: string): boolean { return appNamespace ? appNamespace.startsWith(APP_NAMESPACE_PREFIX) : false; } /** - * @return {boolean} True if the specified appDisplayName belongs to these integration tests. + * @return True if the specified appDisplayName belongs to these integration tests. */ function isIntegrationTestAppDisplayName(appDisplayName: string | undefined): boolean { return appDisplayName ? appDisplayName.startsWith(APP_DISPLAY_NAME_PREFIX) : false; } /** - * @return {string} A randomly generated alphanumeric string, of the specified length. + * @return A randomly generated alphanumeric string, of the specified length. */ function generateRandomString(stringLength: number): string { return _.times(stringLength, () => _.random(35).toString(36)).join(''); diff --git a/test/integration/remote-config.spec.ts b/test/integration/remote-config.spec.ts index f8773c1b3c..7a4c6c9a3c 100644 --- a/test/integration/remote-config.spec.ts +++ b/test/integration/remote-config.spec.ts @@ -14,10 +14,10 @@ * limitations under the License. */ -import * as admin from '../../lib/index'; import * as chai from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; import { deepCopy } from '../../src/utils/deep-copy'; +import { RemoteConfigCondition, RemoteConfigTemplate, getRemoteConfig, } from '../../lib/remote-config/index'; chai.should(); chai.use(chaiAsPromised); @@ -57,7 +57,7 @@ const VALID_PARAMETER_GROUPS = { }, }; -const VALID_CONDITIONS: admin.remoteConfig.RemoteConfigCondition[] = [ +const VALID_CONDITIONS: RemoteConfigCondition[] = [ { name: 'ios', expression: 'device.os == \'ios\'', @@ -74,12 +74,12 @@ const VALID_VERSION = { description: `template description ${Date.now()}`, } -let currentTemplate: admin.remoteConfig.RemoteConfigTemplate; +let currentTemplate: RemoteConfigTemplate; describe('admin.remoteConfig', () => { before(async () => { // obtain the most recent template (etag) to perform operations - currentTemplate = await admin.remoteConfig().getTemplate(); + currentTemplate = await getRemoteConfig().getTemplate(); }); it('verify that the etag is read-only', () => { @@ -95,7 +95,7 @@ describe('admin.remoteConfig', () => { currentTemplate.parameters = VALID_PARAMETERS; currentTemplate.parameterGroups = VALID_PARAMETER_GROUPS; currentTemplate.version = VALID_VERSION; - return admin.remoteConfig().validateTemplate(currentTemplate) + return getRemoteConfig().validateTemplate(currentTemplate) .then((template) => { expect(template.etag).matches(/^etag-[0-9]*-[0-9]*$/); expect(template.conditions.length).to.equal(2); @@ -113,7 +113,7 @@ describe('admin.remoteConfig', () => { currentTemplate.parameters = VALID_PARAMETERS; currentTemplate.parameterGroups = VALID_PARAMETER_GROUPS; currentTemplate.version = VALID_VERSION; - return admin.remoteConfig().validateTemplate(currentTemplate) + return getRemoteConfig().validateTemplate(currentTemplate) .should.eventually.be.rejected.and.have.property('code', 'remote-config/invalid-argument'); }); }); @@ -125,7 +125,7 @@ describe('admin.remoteConfig', () => { currentTemplate.parameters = VALID_PARAMETERS; currentTemplate.parameterGroups = VALID_PARAMETER_GROUPS; currentTemplate.version = VALID_VERSION; - return admin.remoteConfig().publishTemplate(currentTemplate) + return getRemoteConfig().publishTemplate(currentTemplate) .then((template) => { expect(template.etag).matches(/^etag-[0-9]*-[0-9]*$/); expect(template.conditions.length).to.equal(2); @@ -143,14 +143,14 @@ describe('admin.remoteConfig', () => { currentTemplate.parameters = VALID_PARAMETERS; currentTemplate.parameterGroups = VALID_PARAMETER_GROUPS; currentTemplate.version = VALID_VERSION; - return admin.remoteConfig().publishTemplate(currentTemplate) + return getRemoteConfig().publishTemplate(currentTemplate) .should.eventually.be.rejected.and.have.property('code', 'remote-config/invalid-argument'); }); }); describe('getTemplate', () => { it('should return the most recently published template', () => { - return admin.remoteConfig().getTemplate() + return getRemoteConfig().getTemplate() .then((template) => { expect(template.etag).matches(/^etag-[0-9]*-[0-9]*$/); expect(template.conditions.length).to.equal(2); @@ -171,23 +171,23 @@ describe('admin.remoteConfig', () => { describe('getTemplateAtVersion', () => { before(async () => { // obtain the current active template - let activeTemplate = await admin.remoteConfig().getTemplate(); + let activeTemplate = await getRemoteConfig().getTemplate(); // publish a new template to create a new version number activeTemplate.version = { description: versionOneDescription }; - activeTemplate = await admin.remoteConfig().publishTemplate(activeTemplate) + activeTemplate = await getRemoteConfig().publishTemplate(activeTemplate) expect(activeTemplate.version).to.be.not.undefined; versionOneNumber = activeTemplate.version!.versionNumber!; // publish another template to create a second version number activeTemplate.version = { description: versionTwoDescription }; - activeTemplate = await admin.remoteConfig().publishTemplate(activeTemplate) + activeTemplate = await getRemoteConfig().publishTemplate(activeTemplate) expect(activeTemplate.version).to.be.not.undefined; versionTwoNumber = activeTemplate.version!.versionNumber!; }); it('should return the requested template version v1', () => { - return admin.remoteConfig().getTemplateAtVersion(versionOneNumber) + return getRemoteConfig().getTemplateAtVersion(versionOneNumber) .then((template) => { expect(template.etag).matches(/^etag-[0-9]*-[0-9]*$/); expect(template.version).to.be.not.undefined; @@ -199,7 +199,7 @@ describe('admin.remoteConfig', () => { describe('listVersions', () => { it('should return the most recently published 2 versions', () => { - return admin.remoteConfig().listVersions({ + return getRemoteConfig().listVersions({ pageSize: 2, }) .then((response) => { @@ -215,7 +215,7 @@ describe('admin.remoteConfig', () => { describe('rollback', () => { it('verify the most recent template version before rollback to the one prior', () => { - return admin.remoteConfig().getTemplate() + return getRemoteConfig().getTemplate() .then((template) => { expect(template.version).to.be.not.undefined; expect(template.version!.versionNumber).equals(versionTwoNumber); @@ -223,7 +223,7 @@ describe('admin.remoteConfig', () => { }); it('should rollback to the requested version', () => { - return admin.remoteConfig().rollback(versionOneNumber) + return getRemoteConfig().rollback(versionOneNumber) .then((template) => { expect(template.version).to.be.not.undefined; expect(template.version!.updateType).equals('ROLLBACK'); @@ -238,14 +238,14 @@ describe('admin.remoteConfig', () => { INVALID_STRINGS.forEach((invalidJson) => { it(`should throw if the json string is ${JSON.stringify(invalidJson)}`, () => { - expect(() => admin.remoteConfig().createTemplateFromJSON(invalidJson)) + expect(() => getRemoteConfig().createTemplateFromJSON(invalidJson)) .to.throw('JSON string must be a valid non-empty string'); }); }); INVALID_JSON_STRINGS.forEach((invalidJson) => { it(`should throw if the json string is ${JSON.stringify(invalidJson)}`, () => { - expect(() => admin.remoteConfig().createTemplateFromJSON(invalidJson)) + expect(() => getRemoteConfig().createTemplateFromJSON(invalidJson)) .to.throw(/Failed to parse the JSON string/); }); }); @@ -263,14 +263,14 @@ describe('admin.remoteConfig', () => { invalidEtagTemplate.etag = invalidEtag; const jsonString = JSON.stringify(invalidEtagTemplate); it(`should throw if the ETag is ${JSON.stringify(invalidEtag)}`, () => { - expect(() => admin.remoteConfig().createTemplateFromJSON(jsonString)) + expect(() => getRemoteConfig().createTemplateFromJSON(jsonString)) .to.throw(`Invalid Remote Config template: ${jsonString}`); }); }); it('should succeed when a valid json string is provided', () => { const jsonString = JSON.stringify(sourceTemplate); - const newTemplate = admin.remoteConfig().createTemplateFromJSON(jsonString); + const newTemplate = getRemoteConfig().createTemplateFromJSON(jsonString); expect(newTemplate.etag).to.equal(sourceTemplate.etag); expect(() => { (currentTemplate as any).etag = 'new-etag'; diff --git a/test/integration/security-rules.spec.ts b/test/integration/security-rules.spec.ts index 648b74c787..eda5f000f4 100644 --- a/test/integration/security-rules.spec.ts +++ b/test/integration/security-rules.spec.ts @@ -15,8 +15,7 @@ */ import * as chai from 'chai'; - -import * as admin from '../../lib/index'; +import { Ruleset, RulesetMetadata, getSecurityRules } from '../../lib/security-rules/index'; const expect = chai.expect; @@ -47,27 +46,27 @@ describe('admin.securityRules', () => { const rulesetsToDelete: string[] = []; - function scheduleForDelete(ruleset: admin.securityRules.Ruleset): void { + function scheduleForDelete(ruleset: Ruleset): void { rulesetsToDelete.push(ruleset.name); } - function unscheduleForDelete(ruleset: admin.securityRules.Ruleset): void { + function unscheduleForDelete(ruleset: Ruleset): void { rulesetsToDelete.splice(rulesetsToDelete.indexOf(ruleset.name), 1); } function deleteTempRulesets(): Promise { const promises: Array> = []; rulesetsToDelete.forEach((rs) => { - promises.push(admin.securityRules().deleteRuleset(rs)); + promises.push(getSecurityRules().deleteRuleset(rs)); }); rulesetsToDelete.splice(0, rulesetsToDelete.length); // Clear out the array. return Promise.all(promises); } - function createTemporaryRuleset(): Promise { + function createTemporaryRuleset(): Promise { const name = 'firestore.rules'; - const rulesFile = admin.securityRules().createRulesFileFromSource(name, SAMPLE_FIRESTORE_RULES); - return admin.securityRules().createRuleset(rulesFile) + const rulesFile = getSecurityRules().createRulesFileFromSource(name, SAMPLE_FIRESTORE_RULES); + return getSecurityRules().createRuleset(rulesFile) .then((ruleset) => { scheduleForDelete(ruleset); return ruleset; @@ -80,14 +79,14 @@ describe('admin.securityRules', () => { describe('createRulesFileFromSource()', () => { it('creates a RulesFile from the source string', () => { - const rulesFile = admin.securityRules().createRulesFileFromSource( + const rulesFile = getSecurityRules().createRulesFileFromSource( RULES_FILE_NAME, SAMPLE_FIRESTORE_RULES); expect(rulesFile.name).to.equal(RULES_FILE_NAME); expect(rulesFile.content).to.equal(SAMPLE_FIRESTORE_RULES); }); it('creates a RulesFile from the source Buffer', () => { - const rulesFile = admin.securityRules().createRulesFileFromSource( + const rulesFile = getSecurityRules().createRulesFileFromSource( 'firestore.rules', Buffer.from(SAMPLE_FIRESTORE_RULES, 'utf-8')); expect(rulesFile.name).to.equal(RULES_FILE_NAME); expect(rulesFile.content).to.equal(SAMPLE_FIRESTORE_RULES); @@ -96,9 +95,9 @@ describe('admin.securityRules', () => { describe('createRuleset()', () => { it('creates a new Ruleset from a given RulesFile', () => { - const rulesFile = admin.securityRules().createRulesFileFromSource( + const rulesFile = getSecurityRules().createRulesFileFromSource( RULES_FILE_NAME, SAMPLE_FIRESTORE_RULES); - return admin.securityRules().createRuleset(rulesFile) + return getSecurityRules().createRuleset(rulesFile) .then((ruleset) => { scheduleForDelete(ruleset); verifyFirestoreRuleset(ruleset); @@ -106,9 +105,9 @@ describe('admin.securityRules', () => { }); it('rejects with invalid-argument when the source is invalid', () => { - const rulesFile = admin.securityRules().createRulesFileFromSource( + const rulesFile = getSecurityRules().createRulesFileFromSource( RULES_FILE_NAME, 'invalid syntax'); - return admin.securityRules().createRuleset(rulesFile) + return getSecurityRules().createRuleset(rulesFile) .should.eventually.be.rejected.and.have.property('code', 'security-rules/invalid-argument'); }); }); @@ -116,19 +115,19 @@ describe('admin.securityRules', () => { describe('getRuleset()', () => { it('rejects with not-found when the Ruleset does not exist', () => { const nonExistingName = '00000000-1111-2222-3333-444444444444'; - return admin.securityRules().getRuleset(nonExistingName) + return getSecurityRules().getRuleset(nonExistingName) .should.eventually.be.rejected.and.have.property('code', 'security-rules/not-found'); }); it('rejects with invalid-argument when the Ruleset name is invalid', () => { - return admin.securityRules().getRuleset('invalid uuid') + return getSecurityRules().getRuleset('invalid uuid') .should.eventually.be.rejected.and.have.property('code', 'security-rules/invalid-argument'); }); it('resolves with existing Ruleset', () => { return createTemporaryRuleset() .then((expectedRuleset) => - admin.securityRules().getRuleset(expectedRuleset.name) + getSecurityRules().getRuleset(expectedRuleset.name) .then((actualRuleset) => { expect(actualRuleset).to.deep.equal(expectedRuleset); }), @@ -137,15 +136,15 @@ describe('admin.securityRules', () => { }); describe('Cloud Firestore', () => { - let oldRuleset: admin.securityRules.Ruleset | null = null; - let newRuleset: admin.securityRules.Ruleset | null = null; + let oldRuleset: Ruleset | null = null; + let newRuleset: Ruleset | null = null; function revertFirestoreRulesetIfModified(): Promise { if (!newRuleset || !oldRuleset) { return Promise.resolve(); } - return admin.securityRules().releaseFirestoreRuleset(oldRuleset); + return getSecurityRules().releaseFirestoreRuleset(oldRuleset); } afterEach(() => { @@ -153,7 +152,7 @@ describe('admin.securityRules', () => { }); it('getFirestoreRuleset() returns the Ruleset currently in effect', () => { - return admin.securityRules().getFirestoreRuleset() + return getSecurityRules().getFirestoreRuleset() .then((ruleset) => { expect(ruleset.name).to.match(RULESET_NAME_PATTERN); const createTime = new Date(ruleset.createTime); @@ -164,10 +163,10 @@ describe('admin.securityRules', () => { }); it('releaseFirestoreRulesetFromSource() applies the specified Ruleset to Firestore', () => { - return admin.securityRules().getFirestoreRuleset() + return getSecurityRules().getFirestoreRuleset() .then((ruleset) => { oldRuleset = ruleset; - return admin.securityRules().releaseFirestoreRulesetFromSource(SAMPLE_FIRESTORE_RULES); + return getSecurityRules().releaseFirestoreRulesetFromSource(SAMPLE_FIRESTORE_RULES); }) .then((ruleset) => { scheduleForDelete(ruleset); @@ -175,7 +174,7 @@ describe('admin.securityRules', () => { expect(ruleset.name).to.not.equal(oldRuleset!.name); verifyFirestoreRuleset(ruleset); - return admin.securityRules().getFirestoreRuleset(); + return getSecurityRules().getFirestoreRuleset(); }) .then((ruleset) => { expect(ruleset.name).to.equal(newRuleset!.name); @@ -185,15 +184,15 @@ describe('admin.securityRules', () => { }); describe('Cloud Storage', () => { - let oldRuleset: admin.securityRules.Ruleset | null = null; - let newRuleset: admin.securityRules.Ruleset | null = null; + let oldRuleset: Ruleset | null = null; + let newRuleset: Ruleset | null = null; function revertStorageRulesetIfModified(): Promise { if (!newRuleset || !oldRuleset) { return Promise.resolve(); } - return admin.securityRules().releaseStorageRuleset(oldRuleset); + return getSecurityRules().releaseStorageRuleset(oldRuleset); } afterEach(() => { @@ -201,7 +200,7 @@ describe('admin.securityRules', () => { }); it('getStorageRuleset() returns the currently applied Storage rules', () => { - return admin.securityRules().getStorageRuleset() + return getSecurityRules().getStorageRuleset() .then((ruleset) => { expect(ruleset.name).to.match(RULESET_NAME_PATTERN); const createTime = new Date(ruleset.createTime); @@ -212,10 +211,10 @@ describe('admin.securityRules', () => { }); it('releaseStorageRulesetFromSource() applies the specified Ruleset to Storage', () => { - return admin.securityRules().getStorageRuleset() + return getSecurityRules().getStorageRuleset() .then((ruleset) => { oldRuleset = ruleset; - return admin.securityRules().releaseStorageRulesetFromSource(SAMPLE_STORAGE_RULES); + return getSecurityRules().releaseStorageRulesetFromSource(SAMPLE_STORAGE_RULES); }) .then((ruleset) => { scheduleForDelete(ruleset); @@ -225,7 +224,7 @@ describe('admin.securityRules', () => { expect(ruleset.name).to.match(RULESET_NAME_PATTERN); const createTime = new Date(ruleset.createTime); expect(ruleset.createTime).equals(createTime.toUTCString()); - return admin.securityRules().getStorageRuleset(); + return getSecurityRules().getStorageRuleset(); }) .then((ruleset) => { expect(ruleset.name).to.equal(newRuleset!.name); @@ -235,12 +234,10 @@ describe('admin.securityRules', () => { describe('listRulesetMetadata()', () => { it('lists all available Rulesets in pages', () => { - type RulesetMetadata = admin.securityRules.RulesetMetadata; - function listAllRulesets( pageToken?: string, results: RulesetMetadata[] = []): Promise { - return admin.securityRules().listRulesetMetadata(100, pageToken) + return getSecurityRules().listRulesetMetadata(100, pageToken) .then((page) => { results.push(...page.rulesets); if (page.nextPageToken) { @@ -262,7 +259,7 @@ describe('admin.securityRules', () => { }); it('lists the specified number of Rulesets', () => { - return admin.securityRules().listRulesetMetadata(2) + return getSecurityRules().listRulesetMetadata(2) .then((page) => { expect(page.rulesets.length).to.be.at.most(2); expect(page.rulesets.length).to.be.at.least(1); @@ -273,20 +270,20 @@ describe('admin.securityRules', () => { describe('deleteRuleset()', () => { it('rejects with not-found when the Ruleset does not exist', () => { const nonExistingName = '00000000-1111-2222-3333-444444444444'; - return admin.securityRules().deleteRuleset(nonExistingName) + return getSecurityRules().deleteRuleset(nonExistingName) .should.eventually.be.rejected.and.have.property('code', 'security-rules/not-found'); }); it('rejects with invalid-argument when the Ruleset name is invalid', () => { - return admin.securityRules().deleteRuleset('invalid uuid') + return getSecurityRules().deleteRuleset('invalid uuid') .should.eventually.be.rejected.and.have.property('code', 'security-rules/invalid-argument'); }); it('deletes existing Ruleset', () => { return createTemporaryRuleset().then((ruleset) => { - return admin.securityRules().deleteRuleset(ruleset.name) + return getSecurityRules().deleteRuleset(ruleset.name) .then(() => { - return admin.securityRules().getRuleset(ruleset.name) + return getSecurityRules().getRuleset(ruleset.name) .should.eventually.be.rejected.and.have.property('code', 'security-rules/not-found'); }) .then(() => { @@ -296,7 +293,7 @@ describe('admin.securityRules', () => { }); }); - function verifyFirestoreRuleset(ruleset: admin.securityRules.Ruleset): void { + function verifyFirestoreRuleset(ruleset: Ruleset): void { expect(ruleset.name).to.match(RULESET_NAME_PATTERN); const createTime = new Date(ruleset.createTime); expect(ruleset.createTime).equals(createTime.toUTCString()); diff --git a/test/integration/setup.ts b/test/integration/setup.ts index c70362fa89..85bfe5703b 100644 --- a/test/integration/setup.ts +++ b/test/integration/setup.ts @@ -14,12 +14,13 @@ * limitations under the License. */ -import * as admin from '../../lib/index'; import fs = require('fs'); import minimist = require('minimist'); import path = require('path'); import { random } from 'lodash'; -import { GoogleOAuthAccessToken } from '../../src/credential/index'; +import { + App, Credential, GoogleOAuthAccessToken, cert, deleteApp, initializeApp, +} from '../../lib/app/index' // eslint-disable-next-line @typescript-eslint/no-var-requires const chalk = require('chalk'); @@ -29,10 +30,10 @@ export let storageBucket: string; export let projectId: string; export let apiKey: string; -export let defaultApp: admin.app.App; -export let nullApp: admin.app.App; -export let nonNullApp: admin.app.App; -export let noServiceAccountApp: admin.app.App; +export let defaultApp: App; +export let nullApp: App; +export let nonNullApp: App; +export let noServiceAccountApp: App; export let cmdArgs: any; @@ -66,21 +67,21 @@ before(() => { databaseUrl = 'https://' + projectId + '.firebaseio.com'; storageBucket = projectId + '.appspot.com'; - defaultApp = admin.initializeApp({ - credential: admin.credential.cert(serviceAccount), + defaultApp = initializeApp({ + credential: cert(serviceAccount), databaseURL: databaseUrl, storageBucket, }); - nullApp = admin.initializeApp({ - credential: admin.credential.cert(serviceAccount), + nullApp = initializeApp({ + credential: cert(serviceAccount), databaseURL: databaseUrl, databaseAuthVariableOverride: null, storageBucket, }, 'null'); - nonNullApp = admin.initializeApp({ - credential: admin.credential.cert(serviceAccount), + nonNullApp = initializeApp({ + credential: cert(serviceAccount), databaseURL: databaseUrl, databaseAuthVariableOverride: { uid: generateRandomString(20), @@ -88,8 +89,8 @@ before(() => { storageBucket, }, 'nonNull'); - noServiceAccountApp = admin.initializeApp({ - credential: new CertificatelessCredential(admin.credential.cert(serviceAccount)), + noServiceAccountApp = initializeApp({ + credential: new CertificatelessCredential(cert(serviceAccount)), serviceAccountId: serviceAccount.client_email, projectId, }, 'noServiceAccount'); @@ -99,17 +100,17 @@ before(() => { after(() => { return Promise.all([ - defaultApp.delete(), - nullApp.delete(), - nonNullApp.delete(), - noServiceAccountApp.delete(), + deleteApp(defaultApp), + deleteApp(nullApp), + deleteApp(nonNullApp), + deleteApp(noServiceAccountApp), ]); }); -class CertificatelessCredential implements admin.credential.Credential { - private readonly delegate: admin.credential.Credential; +class CertificatelessCredential implements Credential { + private readonly delegate: Credential; - constructor(delegate: admin.credential.Credential) { + constructor(delegate: Credential) { this.delegate = delegate; } diff --git a/test/integration/storage.spec.ts b/test/integration/storage.spec.ts index 25b7a39e43..f7467ac9fc 100644 --- a/test/integration/storage.spec.ts +++ b/test/integration/storage.spec.ts @@ -14,12 +14,12 @@ * limitations under the License. */ -import * as admin from '../../lib/index'; import * as chai from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; import { Bucket, File } from '@google-cloud/storage'; import { projectId } from './setup'; +import { getStorage } from '../../lib/storage/index'; chai.should(); chai.use(chaiAsPromised); @@ -28,19 +28,19 @@ const expect = chai.expect; describe('admin.storage', () => { it('bucket() returns a handle to the default bucket', () => { - const bucket: Bucket = admin.storage().bucket(); + const bucket: Bucket = getStorage().bucket(); return verifyBucket(bucket, 'storage().bucket()') .should.eventually.be.fulfilled; }); it('bucket(string) returns a handle to the specified bucket', () => { - const bucket: Bucket = admin.storage().bucket(projectId + '.appspot.com'); + const bucket: Bucket = getStorage().bucket(projectId + '.appspot.com'); return verifyBucket(bucket, 'storage().bucket(string)') .should.eventually.be.fulfilled; }); it('bucket(non-existing) returns a handle which can be queried for existence', () => { - const bucket: Bucket = admin.storage().bucket('non.existing'); + const bucket: Bucket = getStorage().bucket('non.existing'); return bucket.exists() .then((data) => { expect(data[0]).to.be.false;