Skip to content

Commit

Permalink
feat: added firebaseStorageContextFactory
Browse files Browse the repository at this point in the history
  • Loading branch information
dereekb committed Jul 2, 2022
1 parent a2524b7 commit e940579
Show file tree
Hide file tree
Showing 20 changed files with 206 additions and 39 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 { describeFirestoreQueryDriverTests, describeFirestoreAccessorDriverTests } from '@dereekb/firebase/test';
import { adminTestWithMockItemCollection } from '@dereekb/firebase-server/test';
import { dbxComponentsAdminTestWithMockItemCollection } from '@dereekb/firebase-server/test';

jest.setTimeout(9000);

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

adminTestWithMockItemCollection((f) => {
dbxComponentsAdminTestWithMockItemCollection((f) => {
describeFirestoreQueryDriverTests(f);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import { DocumentSnapshot } from '@google-cloud/firestore';
import { first } from 'rxjs';
import { FirestoreDocumentAccessor } from '@dereekb/firebase';
import { MockItem, MockItemDocument, MockItemFirestoreCollection, mockItemFirestoreCollection } from '@dereekb/firebase/test';
import { adminTestWithMockItemCollection } from '@dereekb/firebase-server/test';
import { dbxComponentsAdminTestWithMockItemCollection } from '@dereekb/firebase-server/test';

describe('FirestoreDocumentDataAccessor', () => {
adminTestWithMockItemCollection((f) => {
dbxComponentsAdminTestWithMockItemCollection((f) => {
let firestoreCollection: MockItemFirestoreCollection;
let documentAccessor: FirestoreDocumentAccessor<MockItem, MockItemDocument>;
let document: MockItemDocument;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { DocumentSnapshot } from '@google-cloud/firestore';
import { MockItem, MockItemDocument, MockItemFirestoreCollection, mockItemFirestoreCollection } from '@dereekb/firebase/test';
import { adminTestWithMockItemCollection } from '@dereekb/firebase-server/test';
import { dbxComponentsAdminTestWithMockItemCollection } from '@dereekb/firebase-server/test';
import { FirestoreDocumentAccessor } from '@dereekb/firebase';

describe('FirestoreDocumentAccessor', () => {
adminTestWithMockItemCollection((f) => {
dbxComponentsAdminTestWithMockItemCollection((f) => {
let firestoreCollection: MockItemFirestoreCollection;
let documentAccessor: FirestoreDocumentAccessor<MockItem, MockItemDocument>;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import { DocumentReference, Transaction, Firestore } from '@google-cloud/firesto
import { DocumentSnapshot, makeFirestoreCollection } from '@dereekb/firebase';
import { mockItemIdentity, MockItem, mockItemCollectionReference, MockItemDocument, MockItemFirestoreCollection, mockItemConverter } from '@dereekb/firebase/test';
import { Maybe } from '@dereekb/util';
import { adminTestWithMockItemCollection } from '@dereekb/firebase-server/test';
import { dbxComponentsAdminTestWithMockItemCollection } from '@dereekb/firebase-server/test';
import { googleCloudFirestoreDrivers } from './driver';

describe('FirestoreCollection', () => {
adminTestWithMockItemCollection((f) => {
dbxComponentsAdminTestWithMockItemCollection((f) => {
let firestore: Firestore;
let firestoreCollection: MockItemFirestoreCollection;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { describeFirestoreIterationTests } from '@dereekb/firebase/test';
import { adminTestWithMockItemCollection } from '@dereekb/firebase-server/test';
import { dbxComponentsAdminTestWithMockItemCollection } from '@dereekb/firebase-server/test';

adminTestWithMockItemCollection((f) => {
dbxComponentsAdminTestWithMockItemCollection((f) => {
describeFirestoreIterationTests(f);
});
8 changes: 8 additions & 0 deletions packages/firebase-server/src/lib/storage/driver.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { authorizedTestWithMockItemStorage, describeFirebaseStorageAccessorDriverTests } from '@dereekb/firebase/test';
import { dbxComponentsAdminTestWithMockItemCollection } from '@dereekb/firebase-server/test';

describe('firebase storage server', () => {
authorizedTestWithMockItemStorage((f) => {
describeFirebaseStorageAccessorDriverTests(f);
});
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { JestTestContextFactory } from '@dereekb/util/test';
import { GoogleCloudTestFirestoreContextFixture, googleCloudTestFirestoreBuilder } from './firestore';
import { testWithMockItemCollectionFixture } from '@dereekb/firebase/test';

export type GoogleFirebaseFirestoreTestContextFactory = JestTestContextFactory<GoogleCloudTestFirestoreContextFixture>;

Expand All @@ -12,3 +13,10 @@ export const adminFirestoreFactory: GoogleFirebaseFirestoreTestContextFactory =
host: 'localhost',
port: 9904
});

/**
* Convenience mock instance for tests within an authorized context.
*
* Uses @google-cloud/firestore
*/
export const dbxComponentsAdminTestWithMockItemCollection = testWithMockItemCollectionFixture()(adminFirestoreFactory);

This file was deleted.

3 changes: 1 addition & 2 deletions packages/firebase-server/test/src/lib/firestore/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
// export * from './firestore.admin'; // not exported. Only for the firebase-server since we defined the config in there.
export * from './firestore.fixture.admin';
export * from './firestore.admin';
export * from './firestore';
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
@@ -1 +1,2 @@
export * from './storage';
export * from './storage.admin';
22 changes: 22 additions & 0 deletions packages/firebase-server/test/src/lib/storage/storage.admin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { testWithMockItemStorageFixture } from '@dereekb/firebase/test';
import { JestTestContextFactory } from '@dereekb/util/test';
import { GoogleCloudTestFirebaseStorageContextFixture, googleCloudTestFirebaseStorageBuilder } from './storage';

export type GoogleFirebaseStorageTestContextFactory = JestTestContextFactory<GoogleCloudTestFirebaseStorageContextFixture>;

/**
* Default firestore admin factory.
*
* Host of localhost, port 9906
*/
export const adminFirebaseStorageFactory: GoogleFirebaseStorageTestContextFactory = googleCloudTestFirebaseStorageBuilder({
host: 'localhost',
port: 9906
});

/**
* Convenience mock instance for tests within an authorized context.
*
* Uses @google-cloud/firestore
*/
export const dbxComponentsAdminTestWithMockItemStorage = testWithMockItemStorageFixture()(adminFirebaseStorageFactory);
11 changes: 6 additions & 5 deletions packages/firebase-server/test/src/lib/storage/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ export interface GoogleCloudTestFirebaseStorageConfig {

export type GoogleCloudTestFirebaseStorageContext = TestFirebaseStorageContext;

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

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

Expand Down Expand Up @@ -58,7 +58,8 @@ export const googleCloudTestFirebaseStorageBuilder = jestTestContextBuilder<Goog
apiEndpoint: `${config.host}:${config.port}`
});

return new GoogleCloudTestFirebaseStorageInstance(drivers, firebaseStorage);
const defaultBucketId = projectId;
return new GoogleCloudTestFirebaseStorageInstance(drivers, firebaseStorage, defaultBucketId);
},
teardownInstance: async (instance, config) => {
// nothing to teardown
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export function firebaseStorageClientAccessorFolder(storage: ClientFirebaseStora

export function firebaseStorageClientAccessorDriver(): FirebaseStorageAccessorDriver {
return {
defaultBucket: (storage: FirebaseStorage) => (storage as ClientFirebaseStorage).app.options.storageBucket ?? '',
file: (storage: FirebaseStorage, path: StoragePath) => firebaseStorageClientAccessorFile(storage as ClientFirebaseStorage, path),
folder: (storage: FirebaseStorage, path: StoragePath) => firebaseStorageClientAccessorFolder(storage as ClientFirebaseStorage, path)
};
Expand Down
37 changes: 34 additions & 3 deletions packages/firebase/src/lib/common/storage/context.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { FirebaseStorageAccessorFile, FirebaseStorageAccessorFolder } from './driver/accessor';
import { FirebaseStorageDrivers } from './driver/driver';
import { StorageBucketId, StoragePath, storagePathFactory, StoragePathFactory, StoragePathInput, StorageSlashPathRef } from './storage';
import { FirebaseStorage } from './types';

/**
Expand All @@ -7,12 +9,26 @@ import { FirebaseStorage } from './types';
export interface FirebaseStorageContext<F extends FirebaseStorage = FirebaseStorage> {
readonly storage: F;
readonly drivers: FirebaseStorageDrivers;
defaultBucket: () => StorageBucketId;
file(path: StoragePathInput): FirebaseStorageAccessorFile;
folder(path: StoragePathInput): FirebaseStorageAccessorFolder;
}

/**
* Factory function for generating a FirebaseStorageContext given the input FirebaseStorage.
*/
export type FirebaseStorageContextFactory<F extends FirebaseStorage = FirebaseStorage> = (firebaseStorage: F) => FirebaseStorageContext;
export type FirebaseStorageContextFactory<F extends FirebaseStorage = FirebaseStorage> = (firebaseStorage: F, config?: FirebaseStorageContextFactoryConfig) => FirebaseStorageContext;

export interface FirebaseStorageContextFactoryConfig {
/**
* The default bucket
*/
defaultBucketId?: StorageBucketId;
/**
* Whether or not to force using the default bucket id.
*/
forceBucket?: boolean;
}

/**
* Creates a new FirebaseStorageContextFactory given the input FirebaseStorageDrivers.
Expand All @@ -21,10 +37,25 @@ export type FirebaseStorageContextFactory<F extends FirebaseStorage = FirebaseSt
* @returns
*/
export function firebaseStorageContextFactory<F extends FirebaseStorage = FirebaseStorage>(drivers: FirebaseStorageDrivers): FirebaseStorageContextFactory<F> {
return (firebaseStorage: F) => {
return (firebaseStorage: F, config?: FirebaseStorageContextFactoryConfig) => {
const { defaultBucketId: inputDefaultBucketId, forceBucket = false } = config ?? {};
const defaultBucketId: StorageBucketId = inputDefaultBucketId || drivers.storageAccessorDriver.defaultBucket?.(firebaseStorage) || '';

if (!defaultBucketId) {
throw new Error('Could not resolve a default bucket id for the firebaseStorageContextFactory(). Supply a defaultBucketId.');
}

const storagePathBuilder: StoragePathFactory = storagePathFactory({
bucketId: defaultBucketId,
replaceBucket: forceBucket
});

const context: FirebaseStorageContext<F> = {
storage: firebaseStorage,
drivers
drivers,
defaultBucket: () => defaultBucketId,
file: (path: StoragePathInput) => drivers.storageAccessorDriver.file(firebaseStorage, storagePathBuilder(path)),
folder: (path: StoragePathInput) => drivers.storageAccessorDriver.folder(firebaseStorage, storagePathBuilder(path))
};

return context;
Expand Down
7 changes: 7 additions & 0 deletions packages/firebase/src/lib/common/storage/driver/accessor.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { StorageBucketId } from './../storage';
import { StoragePath, StoragePathRef } from '../storage';
import { FirebaseStorage, StorageDownloadUrl } from '../types';
import { Maybe } from '@dereekb/util';

/**
* Generic interface for accessing data from a file at the given path.
Expand All @@ -20,13 +22,18 @@ export interface FirebaseStorageAccessorFolder<R extends unknown = unknown> exte
// todo: list files, etc.
}

export type FirebaseStorageAccessorDriverDefaultBucketFunction = (storage: FirebaseStorage) => Maybe<StorageBucketId>;
export type FirebaseStorageAccessorDriverFileFunction<R extends unknown = unknown> = (storage: FirebaseStorage, path: StoragePath) => FirebaseStorageAccessorFile<R>;
export type FirebaseStorageAccessorDriverFolderFunction<R extends unknown = unknown> = (storage: FirebaseStorage, path: StoragePath) => FirebaseStorageAccessorFolder<R>;

/**
* A driver to use for storage functionality.
*/
export interface FirebaseStorageAccessorDriver {
/**
* Returns the default bucketId for the input storage.
*/
readonly defaultBucket?: FirebaseStorageAccessorDriverDefaultBucketFunction;
readonly file: FirebaseStorageAccessorDriverFileFunction;
readonly folder: FirebaseStorageAccessorDriverFolderFunction;
}
Expand Down
46 changes: 45 additions & 1 deletion packages/firebase/src/lib/common/storage/storage.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { toRelativeSlashPathStartType, SlashPath } from '@dereekb/util';
import { toRelativeSlashPathStartType, SlashPath, FactoryWithRequiredInput } from '@dereekb/util';

/**
* Storage bucket identifier.
Expand Down Expand Up @@ -33,6 +33,50 @@ export interface StorageSlashPathRef {
*/
export interface StoragePath extends StorageBucketIdRef, StorageSlashPathRef {}

/**
* Storage-Path related input.
*/
export type StoragePathInput = StorageSlashPath | StoragePath | StorageSlashPathRef;

/**
* Converts the input to a StoragePath
*/
export type StoragePathFactory = FactoryWithRequiredInput<StoragePath, StoragePathInput>;

export interface StoragePathFactoryConfig extends StorageBucketIdRef {
/**
* Whether or not to replace the bucketId on input that has it.
*
* False by default.
*/
replaceBucket?: boolean;
}

/**
* Creates a StoragePathFactory.
*
* @param config
* @returns
*/
export function storagePathFactory(config: StoragePathFactoryConfig): StoragePathFactory {
const { replaceBucket = false, bucketId } = config;
return (input: StoragePathInput) => {
const { pathString, bucketId: inputBucketId } = typeof input === 'string' ? { pathString: input, bucketId: undefined } : (input as StoragePath);

if (replaceBucket) {
return {
pathString,
bucketId
};
} else {
return {
pathString,
bucketId: inputBucketId || bucketId
};
}
};
}

/**
* A reference to a StoragePath
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ export function makeTestingFirestoreDrivers(drivers: FirestoreDrivers): TestingF

// MARK: Test Firestore Context
export interface TestingFirestoreContextExtension {
drivers: TestingFirestoreDrivers;
readonly drivers: TestingFirestoreDrivers;
}

export type TestFirestoreContext<C = FirestoreContext> = C & TestingFirestoreContextExtension;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,33 @@ import { CollectionReference } from '@dereekb/firebase';
import { AbstractWrappedFixtureWithInstance, JestTestWrappedContextFactoryBuilder, instanceWrapJestTestContextFactory } from '@dereekb/util/test';
import { MockItemFirestoreCollection, MockItem } from './mock.item';
import { TestFirestoreContextFixture } from '../firestore/firestore.instance';
import { TestFirebaseStorageContextFixture } from '../storage/storage.instance';
import { TestFirebaseStorageContextFixture, TestFirebaseStorageInstance } from '../storage/storage.instance';

// MARK: Test Item Testing Fixture
export class MockItemStorageFixtureInstance {
export class MockItemStorageFixtureInstance implements TestFirebaseStorageInstance {
constructor(readonly fixture: MockItemStorageFixture) {}

get storage() {
return this.fixture.parent.storage;
}

get storageContext() {
return this.fixture.parent.storageContext;
}
}

/**
* Used to expose a CollectionReference to MockItem for simple tests.
*/
export class MockItemStorageFixture extends AbstractWrappedFixtureWithInstance<MockItemStorageFixtureInstance, TestFirebaseStorageContextFixture> {}
export class MockItemStorageFixture extends AbstractWrappedFixtureWithInstance<MockItemStorageFixtureInstance, TestFirebaseStorageContextFixture> implements TestFirebaseStorageInstance {
get storage() {
return this.instance.storage;
}

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

export interface MockItemStorageFirebaseStorageContextConfig {}

Expand Down
Loading

0 comments on commit e940579

Please sign in to comment.