Skip to content

Commit

Permalink
feat: added firebase storage testing/mock components
Browse files Browse the repository at this point in the history
- refactored testing components to add storage testing components
- added testing components for storage
  • Loading branch information
dereekb committed Jul 2, 2022
1 parent 5a30d46 commit a2524b7
Show file tree
Hide file tree
Showing 53 changed files with 514 additions and 138 deletions.
6 changes: 3 additions & 3 deletions packages/firebase-server/src/lib/firestore/driver.spec.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { describeQueryDriverTests, describeAccessorDriverTests } from '@dereekb/firebase/test';
import { describeFirestoreQueryDriverTests, describeFirestoreAccessorDriverTests } from '@dereekb/firebase/test';
import { adminTestWithMockItemCollection } from '@dereekb/firebase-server/test';

jest.setTimeout(9000);

describe('firestore server', () => {
adminTestWithMockItemCollection((f) => {
describeAccessorDriverTests(f);
describeFirestoreAccessorDriverTests(f);
});

adminTestWithMockItemCollection((f) => {
describeQueryDriverTests(f);
describeFirestoreQueryDriverTests(f);
});
});
4 changes: 2 additions & 2 deletions packages/firebase-server/src/lib/firestore/driver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ export type GoogleCloudFirestoreDrivers = FirestoreDrivers;

export function googleCloudFirestoreDrivers(): GoogleCloudFirestoreDrivers {
return {
driverIdentifier: '@google-cloud/firestore',
driverType: 'production',
firestoreDriverIdentifier: '@google-cloud/firestore',
firestoreDriverType: 'production',
firestoreAccessorDriver: googleCloudFirestoreAccessorDriver(),
firestoreQueryDriver: googleCloudFirestoreQueryDriver()
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ describe('FirestoreDocumentDataAccessor', () => {
let document: MockItemDocument;

beforeEach(async () => {
firestoreCollection = mockItemFirestoreCollection(f.parent.context);
firestoreCollection = mockItemFirestoreCollection(f.parent.firestoreContext);
documentAccessor = firestoreCollection.documentAccessor();
document = documentAccessor.newDocument();
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ describe('FirestoreDocumentAccessor', () => {
let documentAccessor: FirestoreDocumentAccessor<MockItem, MockItemDocument>;

beforeEach(async () => {
firestoreCollection = mockItemFirestoreCollection(f.parent.context);
firestoreCollection = mockItemFirestoreCollection(f.parent.firestoreContext);
documentAccessor = firestoreCollection.documentAccessor();
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ describe('FirestoreCollection', () => {
firestoreCollection = makeFirestoreCollection({
converter: mockItemConverter,
modelIdentity: mockItemIdentity,
firestoreContext: f.parent.context,
firestoreContext: f.parent.firestoreContext,
itemsPerPage,
collection: mockItemCollectionReference(f.parent.context),
collection: mockItemCollectionReference(f.parent.firestoreContext),
makeDocument: (a, d) => new MockItemDocument(a, d),
...googleCloudFirestoreDrivers()
});
Expand Down
33 changes: 28 additions & 5 deletions packages/firebase-server/src/lib/storage/driver.accessor.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,36 @@
import { FirebaseStorage, FirebaseStorageAccessorDriver, FirebaseStorageAccessorDriverGetDownloadUrlFunction, GoogleCloudStorageFilePath, StorageFilePath } from '@dereekb/firebase';
import { SlashPathFolder } from '@dereekb/util';
import { Storage as GoogleCloudStorage } from '@google-cloud/storage';
import { FirebaseStorageAccessorDriver, FirebaseStorageAccessorFile, FirebaseStorageAccessorFolder, FirebaseStorage, StoragePath } from '@dereekb/firebase';
import { Storage as GoogleCloudStorage, File as GoogleCloudFile } from '@google-cloud/storage';

export function googleCloudStorageFileForStorageFilePath(storage: GoogleCloudStorage, path: StorageFilePath) {
export function googleCloudStorageFileForStorageFilePath(storage: GoogleCloudStorage, path: StoragePath) {
return storage.bucket(path.bucketId).file(path.pathString);
}

export interface GoogleCloudStorageAccessorFile extends FirebaseStorageAccessorFile<GoogleCloudFile> {}

export function googleCloudStorageAccessorFile(storage: GoogleCloudStorage, storagePath: StoragePath): GoogleCloudStorageAccessorFile {
const file = googleCloudStorageFileForStorageFilePath(storage, storagePath);

return {
reference: file,
storagePath,
getDownloadUrl: async () => file.publicUrl()
};
}

export interface GoogleCloudStorageAccessorFolder extends FirebaseStorageAccessorFolder<GoogleCloudFile> {}

export function googleCloudStorageAccessorFolder(storage: GoogleCloudStorage, storagePath: StoragePath): GoogleCloudStorageAccessorFolder {
const file = googleCloudStorageFileForStorageFilePath(storage, storagePath);

return {
reference: file,
storagePath
};
}

export function googleCloudStorageFirebaseStorageAccessorDriver(): FirebaseStorageAccessorDriver {
return {
getDownloadUrl: async (storage: FirebaseStorage, path: StorageFilePath) => googleCloudStorageFileForStorageFilePath(storage as GoogleCloudStorage, path).publicUrl()
file: (storage: FirebaseStorage, path: StoragePath) => googleCloudStorageAccessorFile(storage as GoogleCloudStorage, path),
folder: (storage: FirebaseStorage, path: StoragePath) => googleCloudStorageAccessorFolder(storage as GoogleCloudStorage, path)
};
}
4 changes: 2 additions & 2 deletions packages/firebase-server/src/lib/storage/driver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ export type GoogleCloudFirebaseStorageDrivers = FirebaseStorageDrivers;

export function googleCloudFirebaseStorageDrivers(): GoogleCloudFirebaseStorageDrivers {
return {
driverIdentifier: '@google-cloud/storage',
driverType: 'production',
storageDriverIdentifier: '@google-cloud/storage',
storageDriverType: 'production',
storageAccessorDriver: googleCloudStorageFirebaseStorageAccessorDriver()
};
}
16 changes: 16 additions & 0 deletions packages/firebase-server/src/lib/storage/storage.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,23 @@
import { FirebaseStorageContextFactory, firebaseStorageContextFactory, FirestoreContextFactory, firestoreContextFactory } from '@dereekb/firebase';
import { googleCloudFirebaseStorageDrivers } from './driver';
import { Storage as FirebaseAdminStorage } from 'firebase-admin/lib/storage/storage';
import { Storage as GoogleCloudStorage } from '@google-cloud/storage';

/**
* Creates a FirestoreContextFactory that uses the @google-cloud/storage package.
*/
export const googleCloudFirebaseStorageContextFactory: FirebaseStorageContextFactory = firebaseStorageContextFactory(googleCloudFirebaseStorageDrivers());

interface FirebaseAdminStorageRefLike {
readonly storageClient: GoogleCloudStorage;
}

/**
* Retrieves the GoogleCloudStorage object from the input FirebaseAdmin Storage type.
*
* @param storage
* @returns
*/
export function googleCloudStorageFromFirebaseAdminStorage(storage: FirebaseAdminStorage): GoogleCloudStorage {
return (storage as unknown as FirebaseAdminStorageRefLike).storageClient;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ import * as functions from 'firebase-functions-test';
import { Firestore } from '@google-cloud/firestore';
import { Auth } from 'firebase-admin/lib/auth/auth';
import { FeaturesList } from 'firebase-functions-test/lib/features';
import { TestFirestoreContext, TestFirestoreInstance } from '@dereekb/firebase/test';
import { TestFirebaseStorageContext, TestFirebaseStorageInstance, TestFirestoreContext, TestFirestoreInstance } from '@dereekb/firebase/test';
import { AbstractJestTestContextFixture, jestTestContextBuilder, JestTestContextFactory, JestTestContextFixture } from '@dereekb/util/test';
import { applyFirebaseGCloudTestProjectIdToFirebaseConfigEnv, getGCloudTestProjectId, isAdminEnvironmentInitialized, rollNewGCloudProjectEnvironmentVariable } from './firebase';
import { FirebaseAdminTestContext, FirebaseAdminTestContextInstance } from './firebase.admin';
import { Maybe, cachedGetter } from '@dereekb/util';
import { firebaseAdminCloudFunctionWrapper } from './firebase.function';
import { Storage as GoogleCloudStorage } from '@google-cloud/storage';

// MARK: FirebaseAdminFunctionTestBuilder
let functionsInitialized = false;
Expand Down Expand Up @@ -80,6 +81,18 @@ export class FirebaseAdminFunctionTestContextFixture extends AbstractJestTestCon
return this.instance.firestoreContext;
}

get storage(): GoogleCloudStorage {
return this.instance.storage;
}

get storageInstance(): TestFirebaseStorageInstance {
return this.instance.storageInstance;
}

get storageContext(): TestFirebaseStorageContext {
return this.instance.storageContext;
}

get fnWrapper() {
return this.instance.fnWrapper;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export function describeFirestoreTest(s: TestFirestoreContextFixture<TestFiresto
let collection: MockItemFirestoreCollection;

beforeEach(() => {
collection = mockItemFirestoreCollection(s.context);
collection = mockItemFirestoreCollection(s.firestoreContext);
});

describe('firestore', () => {
Expand Down
54 changes: 50 additions & 4 deletions packages/firebase-server/test/src/lib/firebase/firebase.admin.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import * as admin from 'firebase-admin';
import { Firestore } from '@google-cloud/firestore';
import { Auth } from 'firebase-admin/lib/auth/auth';
import { JestTestFirestoreContextFactory, makeTestingFirestoreDrivers, TestFirestoreContext, TestFirestoreContextFixture, TestFirestoreInstance } from '@dereekb/firebase/test';
import { JestTestFirestoreContextFactory, makeTestingFirestoreDrivers, TestFirestoreContext, TestFirestoreContextFixture, TestFirestoreInstance, makeTestingFirebaseStorageDrivers, TestFirebaseStorageContext, TestFirebaseStorageInstance } from '@dereekb/firebase/test';
import { AbstractJestTestContextFixture, JestBuildTestsWithContextFunction, jestTestContextBuilder, JestTestContextFactory, JestTestContextFixture, useJestContextFixture } from '@dereekb/util/test';
import { googleCloudFirebaseStorageDrivers } from '@dereekb/firebase-server';
import { googleCloudFirebaseStorageDrivers, googleCloudFirestoreDrivers, googleCloudStorageFromFirebaseAdminStorage } from '@dereekb/firebase-server';
import { GoogleCloudTestFirestoreInstance } from '../firestore/firestore';
import { generateNewProjectId, isAdminEnvironmentInitialized } from './firebase';
import { cachedGetter } from '@dereekb/util';
import { FirebaseAdminCloudFunctionWrapper, FirebaseAdminCloudFunctionWrapperSource } from './firebase.function';
import { Storage as GoogleCloudStorage } from '@google-cloud/storage';
import { GoogleCloudTestFirebaseStorageInstance } from '../storage/storage';

export interface FirebaseAdminTestConfig {}

Expand All @@ -17,6 +19,9 @@ export interface FirebaseAdminTestContext extends FirebaseAdminCloudFunctionWrap
readonly firestore: Firestore;
readonly firestoreInstance: TestFirestoreInstance;
readonly firestoreContext: TestFirestoreContext;
readonly storage: GoogleCloudStorage;
readonly storageInstance: TestFirebaseStorageInstance;
readonly storageContext: TestFirebaseStorageContext;
}

export class FirebaseAdminTestContextFixture extends AbstractJestTestContextFixture<FirebaseAdminTestContextInstance> implements FirebaseAdminTestContext {
Expand All @@ -41,6 +46,18 @@ export class FirebaseAdminTestContextFixture extends AbstractJestTestContextFixt
return this.instance.firestoreContext;
}

get storage(): GoogleCloudStorage {
return this.instance.storage;
}

get storageInstance(): TestFirebaseStorageInstance {
return this.instance.storageInstance;
}

get storageContext(): TestFirebaseStorageContext {
return this.instance.storageContext;
}

get fnWrapper() {
return this.instance.fnWrapper;
}
Expand All @@ -49,10 +66,15 @@ export class FirebaseAdminTestContextFixture extends AbstractJestTestContextFixt
// MARK: FirebaseAdminTestBuilder
export class FirebaseAdminTestContextInstance implements FirebaseAdminTestContext {
readonly getTestFirestoreInstance = cachedGetter(() => {
const drivers = makeTestingFirestoreDrivers(googleCloudFirebaseStorageDrivers());
const drivers = makeTestingFirestoreDrivers(googleCloudFirestoreDrivers());
return new GoogleCloudTestFirestoreInstance(drivers, this.firestore);
});

readonly getTestFirebaseStorageInstance = cachedGetter(() => {
const drivers = makeTestingFirebaseStorageDrivers(googleCloudFirebaseStorageDrivers());
return new GoogleCloudTestFirebaseStorageInstance(drivers, this.storage);
});

constructor(readonly app: admin.app.App) {}

get auth(): Auth {
Expand All @@ -68,7 +90,19 @@ export class FirebaseAdminTestContextInstance implements FirebaseAdminTestContex
}

get firestoreContext(): TestFirestoreContext {
return this.firestoreInstance.context;
return this.firestoreInstance.firestoreContext;
}

get storage(): GoogleCloudStorage {
return googleCloudStorageFromFirebaseAdminStorage(this.app.storage());
}

get storageInstance(): TestFirebaseStorageInstance {
return this.getTestFirebaseStorageInstance();
}

get storageContext(): TestFirebaseStorageContext {
return this.storageInstance.storageContext;
}

get fnWrapper(): FirebaseAdminCloudFunctionWrapper {
Expand Down Expand Up @@ -100,6 +134,18 @@ export abstract class AbstractFirebaseAdminTestContextInstanceChild<F extends Fi
return this.parent.firestoreContext;
}

get storage(): GoogleCloudStorage {
return this.parent.storage;
}

get storageInstance(): TestFirebaseStorageInstance {
return this.parent.storageInstance;
}

get storageContext(): TestFirebaseStorageContext {
return this.parent.storageContext;
}

get fnWrapper(): FirebaseAdminCloudFunctionWrapper {
return this.parent.fnWrapper;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { testWithMockItemFixture } from '@dereekb/firebase/test';
import { testWithMockItemCollectionFixture } from '@dereekb/firebase/test';
import { adminFirestoreFactory } from './firestore.admin';

/**
* Convenience mock instance for tests within an authorized context.
*
* Uses @google-cloud/firestore
*/
export const adminTestWithMockItemCollection = testWithMockItemFixture()(adminFirestoreFactory);
export const adminTestWithMockItemCollection = testWithMockItemCollectionFixture()(adminFirestoreFactory);
6 changes: 2 additions & 4 deletions packages/firebase-server/test/src/lib/firestore/firestore.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Firestore } from '@google-cloud/firestore';
import { TestFirestoreContext, TestFirestoreInstance, TestFirestoreContextFixture, TestingFirestoreDrivers, makeTestingFirestoreDrivers } from '@dereekb/firebase/test';
import { jestTestContextBuilder } from '@dereekb/util/test';
import { googleCloudFirebaseStorageDrivers } from '@dereekb/firebase-server';
import { googleCloudFirestoreDrivers } from '@dereekb/firebase-server';
import { firestoreContextFactory } from '@dereekb/firebase';

export interface GoogleCloudTestFirestoreConfig {
Expand All @@ -21,8 +21,6 @@ export class GoogleCloudTestFirestoreInstance extends TestFirestoreInstance {
constructor(drivers: TestingFirestoreDrivers, firestore: Firestore) {
super(makeGoogleFirestoreContext(drivers, firestore));
}

// TODO: Add storage
}

export class GoogleCloudTestFirestoreContextFixture extends TestFirestoreContextFixture<GoogleCloudTestFirestoreInstance> {}
Expand All @@ -47,7 +45,7 @@ export const googleCloudTestFirestoreBuilder = jestTestContextBuilder<GoogleClou
},
buildFixture: () => new GoogleCloudTestFirestoreContextFixture(),
setupInstance: async (config) => {
const drivers = makeTestingFirestoreDrivers(googleCloudFirebaseStorageDrivers());
const drivers = makeTestingFirestoreDrivers(googleCloudFirestoreDrivers());

const projectId = `firebase-server-test-${new Date().getTime()}-${COUNTER++}`;
const firestore = new Firestore({
Expand Down
1 change: 1 addition & 0 deletions packages/firebase-server/test/src/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './firebase';
export * from './firestore';
export * from './storage';
1 change: 1 addition & 0 deletions packages/firebase-server/test/src/lib/storage/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './storage';
66 changes: 66 additions & 0 deletions packages/firebase-server/test/src/lib/storage/storage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { Storage as FirebaseAdminStorage } from 'firebase-admin/lib/storage/storage';
import { Storage as GoogleCloudStorage } from '@google-cloud/storage';
import { TestFirebaseStorageContext, TestFirebaseStorageInstance, TestFirebaseStorageContextFixture, TestingFirebaseStorageDrivers, makeTestingFirebaseStorageDrivers } from '@dereekb/firebase/test';
import { jestTestContextBuilder } from '@dereekb/util/test';
import { googleCloudFirebaseStorageDrivers } from '@dereekb/firebase-server';
import { FirebaseStorage, firebaseStorageContextFactory } from '@dereekb/firebase';

export interface GoogleCloudTestFirebaseStorageConfig {
host: string;
port: number;
}

export type GoogleCloudTestFirebaseStorageContext = TestFirebaseStorageContext;

export function makeGoogleFirebaseStorageContext(drivers: TestingFirebaseStorageDrivers, firebaseStorage: FirebaseStorage): TestFirebaseStorageContext {
const context = firebaseStorageContextFactory(drivers)(firebaseStorage) as GoogleCloudTestFirebaseStorageContext;
context.drivers = drivers;
return context;
}

export class GoogleCloudTestFirebaseStorageInstance extends TestFirebaseStorageInstance {
constructor(drivers: TestingFirebaseStorageDrivers, firebaseStorage: FirebaseStorage) {
super(makeGoogleFirebaseStorageContext(drivers, firebaseStorage));
}
}

export class GoogleCloudTestFirebaseStorageContextFixture extends TestFirebaseStorageContextFixture<GoogleCloudTestFirebaseStorageInstance> {}

let COUNTER = 0;

/**
* A JestTestContextBuilderFunction for building firebase storage test context factories using @google-cloud/storage. This means SERVER TESTING ONLY. For client testing, look at @dereekb/firestore.
*
* This is used to build a @google-cloud/storage FirebaseStorage instance for testing and point it to the emulators.
*
* If you need all of Firebase (firebase-admin library), look at adminFirebaseAdminTestBuilder() instead.
*/
export const googleCloudTestFirebaseStorageBuilder = jestTestContextBuilder<GoogleCloudTestFirebaseStorageInstance, GoogleCloudTestFirebaseStorageContextFixture, GoogleCloudTestFirebaseStorageConfig>({
buildConfig: (input?: Partial<GoogleCloudTestFirebaseStorageConfig>) => {
const config: GoogleCloudTestFirebaseStorageConfig = {
host: input?.host ?? 'localhost',
port: input?.port ?? 0
};

if (!config.port) {
throw new Error('Port for host is required.');
}

return config;
},
buildFixture: () => new GoogleCloudTestFirebaseStorageContextFixture(),
setupInstance: async (config) => {
const drivers = makeTestingFirebaseStorageDrivers(googleCloudFirebaseStorageDrivers());

const projectId = `firebase-storage-server-test-${new Date().getTime()}-${COUNTER++}`;
const firebaseStorage = new GoogleCloudStorage({
projectId,
apiEndpoint: `${config.host}:${config.port}`
});

return new GoogleCloudTestFirebaseStorageInstance(drivers, firebaseStorage);
},
teardownInstance: async (instance, config) => {
// nothing to teardown
}
});
Loading

0 comments on commit a2524b7

Please sign in to comment.