Skip to content

Commit

Permalink
feat(firebase-v2): added support for gen2 of firebase functions
Browse files Browse the repository at this point in the history
  • Loading branch information
H4ad committed Jan 4, 2023
1 parent e4d9806 commit 90eba3f
Show file tree
Hide file tree
Showing 4 changed files with 198 additions and 1 deletion.
68 changes: 68 additions & 0 deletions src/handlers/firebase/http-firebase-v2.handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
//#region Imports

// eslint-disable-next-line import/no-unresolved
import { https } from 'firebase-functions/v2';
import { FrameworkContract, HandlerContract } from '../../contracts';
import { RawRequest } from '../base';

//#endregion

/**
* The class that implements a handler for Firebase Https Events
*
* @remarks Read more about Https Events {@link https://firebase.google.com/docs/functions/http-events | here}
*
* @breadcrumb Handlers / HttpFirebaseHandler
* @public
*/
export class HttpFirebaseV2Handler<TApp>
extends RawRequest<TApp>
implements
HandlerContract<TApp, never, never, never, void, void | Promise<void>>
{
//#region Constructor

/**
* Construtor padrão
*/
constructor(protected readonly options?: https.HttpsOptions) {
super();
}

//#endregion

//#region Public Methods

/**
* {@inheritDoc}
*/
public getHandler(
app: TApp,
framework: FrameworkContract<TApp>,
): ReturnType<HttpFirebaseV2Handler<TApp>['onRequestCallback']> {
if (this.options) {
return this.onRequestWithOptions(
this.options,
this.onRequestCallback(app, framework),
);
}

return https.onRequest(this.onRequestCallback(app, framework));
}

//#endregion

//#region Protected Method

/**
* Wrapper method around onRequest for better testability
*/
protected onRequestWithOptions(
options: https.HttpsOptions,
callback: ReturnType<HttpFirebaseV2Handler<TApp>['onRequestCallback']>,
): ReturnType<HttpFirebaseV2Handler<TApp>['onRequestCallback']> {
return https.onRequest(options, callback);
}

//#endregion
}
2 changes: 1 addition & 1 deletion src/handlers/firebase/http-firebase.handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { IncomingMessage, ServerResponse } from 'http';
import { https } from 'firebase-functions';
import { FrameworkContract, HandlerContract } from '../../contracts';
import { RawRequest } from '../base/index';
import { RawRequest } from '../base';

//#endregion
/**
Expand Down
1 change: 1 addition & 0 deletions src/handlers/firebase/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './http-firebase.handler';
export * from './http-firebase-v2.handler';
128 changes: 128 additions & 0 deletions test/handlers/http-firebase-v2.handler.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import { HttpsOptions } from 'firebase-functions/lib/v2/providers/https';
import {
FrameworkContract,
ServerlessRequest,
ServerlessResponse,
waitForStreamComplete,
} from '../../src';
import { HttpFirebaseV2Handler } from '../../src/handlers/firebase/http-firebase-v2.handler';
import { FrameworkMock } from '../mocks/framework.mock';

jest.mock('firebase-admin', () => {
const packages = {
'12.x': 'firebase-admin-8',
latest: 'firebase-admin',
};
const version = process.env.TEST_NODE_VERSION || 'latest';

// Require the original module.
const originalModule = jest.requireActual(packages[version]);

return {
__esModule: true,
...originalModule,
};
});

describe(HttpFirebaseV2Handler.name, () => {
it('should forward correctly the request to framework', async () => {
const handlerFactory = new HttpFirebaseV2Handler();

const method = 'POST';
const url = '/users/batata';
const headers = { 'Content-Type': 'application/json' };
const remoteAddress = '168.16.0.1';
const body = Buffer.from('{"test": true}', 'utf-8');

const request = new ServerlessRequest({
method,
url,
headers,
remoteAddress,
body,
});

const response = new ServerlessResponse({
method,
});

const responseBody = { batata: true };
const responseStatus = 200;
const framework = new FrameworkMock(responseStatus, responseBody);

const handler = handlerFactory.getHandler(null, framework);

handler(request, response);

await waitForStreamComplete(response);

expect(response.statusCode).toBe(responseStatus);
expect(ServerlessResponse.body(response).toString()).toStrictEqual(
JSON.stringify(responseBody),
);
});

it('should handle weird body types', () => {
const handlerFactory = new HttpFirebaseV2Handler();

const method = 'POST';
const url = '/users/batata';
const headers = { 'Content-Type': 'application/json' };
const remoteAddress = '168.16.0.1';
const options = [{ potato: true }, [{ test: true }]];

for (const option of options) {
const request = new ServerlessRequest({
method,
url,
headers,
remoteAddress,
body: option as any,
});

const response = new ServerlessResponse({
method,
});

const framework: FrameworkContract<unknown> = {
sendRequest: jest.fn(
async (
app: null,
req: ServerlessRequest,
res: ServerlessResponse,
) => {
expect(req.body?.toString()).toEqual(JSON.stringify(option));
expect(req.headers['content-length']).toEqual(
Buffer.byteLength(JSON.stringify(option)).toString(),
);

req.pipe(res);

await waitForStreamComplete(res);

expect(ServerlessResponse.body(res).toString()).toEqual(
JSON.stringify(option),
);
},
),
};

const handler = handlerFactory.getHandler(null, framework);

handler(request, response);
}
});

it('should forward the properties to https.onRequest', () => {
const options: HttpsOptions = {
concurrency: 400,
};
const factory = new HttpFirebaseV2Handler(options);

const spyMethod = jest.spyOn(factory, 'onRequestWithOptions' as any);

factory.getHandler(null, new FrameworkMock(200, {}));

expect(spyMethod).toHaveBeenCalledWith(options, expect.any(Function));
});
});

0 comments on commit 90eba3f

Please sign in to comment.