Skip to content

Commit

Permalink
Merge pull request #79 from H4ad/feature/firebase-v2
Browse files Browse the repository at this point in the history
feat: firebase v2
  • Loading branch information
H4ad authored Jan 4, 2023
2 parents e4d9806 + 3a74353 commit 1e1c89b
Show file tree
Hide file tree
Showing 6 changed files with 212 additions and 7 deletions.
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));
});
});
18 changes: 13 additions & 5 deletions www/docs/main/handlers/firebase.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,27 @@ First, install the types for this adapter:
npm i --save firebase-functions firebase-admin
```

## V1 and V2

If you want to use Firebase Functions V1, use [HttpFirebaseHandler](../../api/Handlers/HttpFirebaseHandler).

But if you want to add support for V2, then use [HttpFirebaseV2Handler](../../api/Handlers/HttpFirebaseHandler/HttpFirebaseV2Handler).

## Integrating with Http Events

To use, you can import [HttpFirebaseHandler](../../api/Handlers/HttpFirebaseHandler) and call the method [setHandler](../../api/ServerlessAdapter#method-sethandler), as per the code below:
To use, you can import [HttpFirebaseV2Handler](../../api/Handlers/HttpFirebaseHandler/HttpFirebaseV2Handler) and call the method [setHandler](../../api/ServerlessAdapter#method-sethandler), as per the code below:

```ts title="index.ts"
import { ServerlessAdapter } from '@h4ad/serverless-adapter';
import { DummyAdapter } from '@h4ad/serverless-adapter/lib/adapters/dummy';
import { HttpFirebaseHandler } from '@h4ad/serverless-adapter/lib/handlers/firebase';
import { HttpFirebaseV2Handler } from '@h4ad/serverless-adapter/lib/handlers/firebase';
import { DummyResolver } from '@h4ad/serverless-adapter/lib/resolvers/dummy';
import app from './app';

export const helloWorld = ServerlessAdapter.new(app)
.setHandler(new HttpFirebaseHandler())
.setHandler(new HttpFirebaseV2Handler({
// you can pass custom properties here, like: concurrency.
}))
.setResolver(new DummyResolver())
// choose the framework of your app
// .setFramework(new ExpressFramework())
Expand All @@ -38,7 +46,7 @@ export const helloWorld = ServerlessAdapter.new(app)

// you can export more than one if you want
export const test = ServerlessAdapter.new(app)
.setHandler(new HttpFirebaseHandler())
.setHandler(new HttpFirebaseV2Handler())
.setResolver(new DummyResolver())
// choose the framework of your app
// .setFramework(new ExpressFramework())
Expand All @@ -48,7 +56,7 @@ export const test = ServerlessAdapter.new(app)

:::info About Resolver and Adapter

You should use DummyResolver and DummyAdapter because it's a requirement for the library, but HttpFirebaseHandler doesn't do anything with them,
You should use `DummyResolver` and `DummyAdapter` because it's a requirement for the library, but `HttpFirebaseV2Handler` doesn't do anything with them,
so you use those dummy versions just to satisfy the library requirements.

:::
Expand Down
2 changes: 1 addition & 1 deletion www/docs/main/intro.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ We support these event sources:
- Digital Ocean
- [Functions](https://docs.digitalocean.com/products/functions/quickstart/sample-functions/) by using [DigitalOceanHandler](./handlers/digital-ocean).
- Firebase
- [Http Events](https://firebase.google.com/docs/functions/http-events) using ([HttpFirebaseHandler](./handlers/firebase)).
- [Http Events](https://firebase.google.com/docs/functions/http-events) using ([HttpFirebaseHandler](./handlers/firebase) and [HttpFirebaseV2Handler](./handlers/firebase)).
- Google Cloud Functions (GCP)
- [Http Functions](https://cloud.google.com/functions/docs/console-quickstart) using ([GCPHandler](./handlers/gcp)).

Expand Down

0 comments on commit 1e1c89b

Please sign in to comment.