diff --git a/spec/main.spec.ts b/spec/main.spec.ts index 703495c..54ead9a 100644 --- a/spec/main.spec.ts +++ b/spec/main.spec.ts @@ -239,6 +239,64 @@ describe('main', () => { }); }); + describe('v2 callable functions', () => { + it('should route to v2 path when outer and run have length 0', () => { + const cloudFunction = (..._args: unknown[]) => {}; + set(cloudFunction, 'run', (...args: unknown[]) => { + const [req] = args; + return { auth: (req as any).auth }; + }); + set(cloudFunction, '__endpoint', { + platform: 'gcfv2', + callableTrigger: {}, + }); + const wrapped = wrap(cloudFunction as functions.CloudFunction); + const result = wrapped({ + data: { ping: true }, + auth: { uid: 'test-user' }, + }); + expect(result.auth).to.deep.equal({ uid: 'test-user' }); + }); + }); + + describe('v2 onRequest functions', () => { + it('should throw the onCall-only error', () => { + const cloudFunction = (_req, _res) => {}; + set(cloudFunction, '__endpoint', { + platform: 'gcfv2', + httpsTrigger: {}, + }); + expect(() => wrap(cloudFunction as any)).to.throw( + 'Wrap function is only available for `onCall` HTTP functions, not `onRequest`.' + ); + }); + }); + + describe('v1 callable functions with endpoint metadata', () => { + it('should route v1 callables with callableTrigger metadata to wrapV1', () => { + const cloudFunction = (input) => input; + set(cloudFunction, 'run', (data, context) => { + return { data, context }; + }); + set(cloudFunction, '__endpoint', { + platform: 'gcfv1', + callableTrigger: {}, + }); + const wrapped: any = wrap(cloudFunction as any); + const result = wrapped('data', { + auth: { uid: 'abc' }, + app: { appId: 'efg' }, + instanceIdToken: '123', + rawRequest: { body: 'hello' }, + }); + expect(result.data).to.equal('data'); + expect(result.context.auth).to.deep.equal({ uid: 'abc' }); + expect(result.context.app).to.deep.equal({ appId: 'efg' }); + expect(result.context.instanceIdToken).to.equal('123'); + expect(result.context.rawRequest).to.deep.equal({ body: 'hello' }); + }); + }); + describe('callable functions', () => { let wrappedCF; diff --git a/src/main.ts b/src/main.ts index 13a23bb..681479b 100644 --- a/src/main.ts +++ b/src/main.ts @@ -38,7 +38,12 @@ import { import { wrapV1, WrappedFunction, WrappedScheduledFunction } from './v1'; -import { wrapV2, WrappedV2Function, WrappedV2CallableFunction } from './v2'; +import { + wrapV2, + WrappedV2Function, + WrappedV2CallableFunction, + isCallableV2Function, +} from './v2'; type HttpsFunctionOrCloudFunctionV1 = U extends HttpsFunction & Runnable @@ -80,6 +85,11 @@ export function wrap>( | WrappedFunction | WrappedV2Function | WrappedV2CallableFunction { + // v2 callables are checked by trigger rather than arity, since + // firebase-functions@7 wraps them in rest-param functions of length 0. + if (isCallableV2Function(cloudFunction)) { + return wrapV2(cloudFunction); + } if (isV2CloudFunction(cloudFunction)) { return wrapV2(cloudFunction as CloudFunctionV2); } diff --git a/src/v2.ts b/src/v2.ts index e3fef18..41ad077 100644 --- a/src/v2.ts +++ b/src/v2.ts @@ -38,10 +38,12 @@ export type WrappedV2CallableFunction = ( data: CallableRequest ) => T | Promise; -function isCallableV2Function>( - cf: CloudFunction | CallableFunction -): cf is CallableFunction { - return !!cf?.__endpoint?.callableTrigger; +export function isCallableV2Function(cf: { + __endpoint?: { platform?: string; callableTrigger?: unknown }; +}): cf is CallableFunction { + return ( + !!cf?.__endpoint?.callableTrigger && cf.__endpoint.platform === 'gcfv2' + ); } function assertIsCloudFunction>(