fix: route v2 callables through wrapV2 when platform is gcfv2#311
fix: route v2 callables through wrapV2 when platform is gcfv2#311IzaakGough wants to merge 3 commits into
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces support for identifying and wrapping GCFv2 callable functions by checking the platform field in the endpoint metadata. Key changes include exporting the isCallableV2Function utility, updating the wrap function to prioritize v2 callable detection, and adding a corresponding test case in spec/main.spec.ts to ensure correct routing. There are no review comments to address, and I have no further feedback to provide.
cabljac
left a comment
There was a problem hiding this comment.
This looks great. The __endpoint.platform === 'gcfv2' check is a much more stable signal than counting function parameters, and the regression test covers the routing path nicely. A couple of small questions below, neither blocking.
Fixes #310
Summary
Fixes
wrap()misrouting v2onCallfunctions to the v1 test helper when used withfirebase-functions@7.x.wrap()previously chose betweenwrapV2andwrapV1usingFunction.lengthi.e.In
firebase-functions@7, v2 callables are wrapped with rest-parameter helpers (withInit,wrapTraceContext), so both lengths are0.wrap()fell through towrapV1, which invokesrun(data, context)instead ofrun({ data, auth, ... }), breaking auth and otherCallableRequestfields in unit tests.Affected versions:
firebase-functions-test@3.4.1,3.5.0(and any release using the arity-only heuristic without this fix).Issue reproduced here: MRE
Problem
With
firebase-functions@7+firebase-functions-test@3.4.1/3.5.0:onCall()that checksrequest.authwrap()and call it with an auth contextrequest.auth→ spuriouspermission-deniederrorsRoot cause:
isV2CloudFunctionreturnsfalsefor real v2 callables, sowrapV1is used for a v2 handler.Solution
wrapV2before the arity check, using stable SDK metadata:__endpoint.callableTrigger__endpoint.platform === 'gcfv2'(v1 callables also havecallableTriggerbut usegcfv1)isCallableV2Functionfromsrc/v2.tsfor reuse and testsisV2CloudFunctionto also returntruewhenplatform === 'gcfv2', so other v2 triggers (Pub/Sub, alerts, etc.) are not misrouted when.length === 0What changed vs original
wrap()Original behavior
isV2CloudFunctionwas only:New behavior
isCallableV2Function(new, exported fromsrc/v2.ts):isV2CloudFunction(updated):Routing flow
onCall+wrap(fn)({ data, auth })wrapV1→run(data, context)→ auth brokenwrapV2→run({ data, auth })→ auth worksonCall(callableTrigger,gcfv1)wrapV1→(data, { auth })length === 0wrapV1platform === 'gcfv2'→wrapV2For v2 callables,
wrapV2invokes:The
as CallableFunctioncast is for TypeScript only (wrap()accepts a broad union;wrapV2has separate overloads for callables vs event functions).Files changed
src/main.tsisCallableV2Functionbranch inwrap();isV2CloudFunctionchecksplatform === 'gcfv2'src/v2.tsisCallableV2FunctionwithcallableTrigger+gcfv2platform guardspec/main.spec.tslength === 0routes to v2 pathTest plan
npm test(all 119 tests pass)Backward compatibility
wrapV1(platformisgcfv1or absent)wrapV2single-argumentCallableRequestAPIplatform === 'gcfv2'and arity heuristic fails