diff --git a/spec/main.spec.ts b/spec/main.spec.ts index d34d79c..867cc50 100644 --- a/spec/main.spec.ts +++ b/spec/main.spec.ts @@ -39,11 +39,13 @@ describe('main', () => { set(cloudFunction, 'run', (data, context) => { return { data, context }; }); - set(cloudFunction, '__trigger', { + set(cloudFunction, '__endpoint', { eventTrigger: { - resource: 'ref/{wildcard}/nested/{anotherWildcard}', + eventFilters: { + resource: 'ref/{wildcard}/nested/{anotherWildcard}', + }, eventType: eventType || 'event', - service: 'service', + retry: false, }, }); return cloudFunction as functions.CloudFunction; @@ -57,7 +59,9 @@ describe('main', () => { it('should generate the appropriate context if no fields specified', () => { const context = wrap(constructBackgroundCF())('data').context; expect(typeof context.eventId).to.equal('string'); - expect(context.resource.service).to.equal('service'); + expect(context.resource.service).to.equal( + 'unknown-service.googleapis.com' + ); expect( /ref\/wildcard[1-9]\/nested\/anotherWildcard[1-9]/.test( context.resource.name @@ -153,7 +157,7 @@ describe('main', () => { const cf = constructBackgroundCF( 'google.firebase.database.ref.create' ); - cf.__trigger.eventTrigger.resource = + cf.__endpoint.eventTrigger.eventFilters.resource = 'companies/{company}/users/{user}'; const wrapped = wrap(cf); const context = wrapped( @@ -173,7 +177,7 @@ describe('main', () => { it('should extract the appropriate params for Firestore function trigger', () => { const cf = constructBackgroundCF('google.firestore.document.create'); - cf.__trigger.eventTrigger.resource = + cf.__endpoint.eventTrigger.eventFilters.resource = 'databases/(default)/documents/companies/{company}/users/{user}'; const wrapped = wrap(cf); const context = wrapped( @@ -195,7 +199,7 @@ describe('main', () => { const cf = constructBackgroundCF( 'google.firebase.database.ref.create' ); - cf.__trigger.eventTrigger.resource = + cf.__endpoint.eventTrigger.eventFilters.resource = 'companies/{company}/users/{user}'; const wrapped = wrap(cf); const context = wrapped( @@ -243,11 +247,8 @@ describe('main', () => { set(cloudFunction, 'run', (data, context) => { return { data, context }; }); - set(cloudFunction, '__trigger', { - labels: { - 'deployment-callable': 'true', - }, - httpsTrigger: {}, + set(cloudFunction, '__endpoint', { + callableTrigger: {}, }); wrappedCF = wrap(cloudFunction as functions.CloudFunction); }); @@ -340,11 +341,5 @@ describe('main', () => { expect(functions.config()).to.deep.equal(config); }); - - it('should not throw an error when functions.config.singleton is missing', () => { - delete functions.config.singleton; - - expect(() => mockConfig(config)).to.not.throw(Error); - }); }); }); diff --git a/spec/v2.spec.ts b/spec/v2.spec.ts index f66bd4c..e10fa0d 100644 --- a/spec/v2.spec.ts +++ b/spec/v2.spec.ts @@ -33,7 +33,7 @@ import { eventarc, https, } from 'firebase-functions/v2'; -import { defineString } from 'firebase-functions/v2/params'; +import { defineString } from 'firebase-functions/params'; import { makeDataSnapshot } from '../src/providers/database'; describe('v2', () => { diff --git a/src/cloudevent/mocks/database/helpers.ts b/src/cloudevent/mocks/database/helpers.ts index 5fd76ae..f107176 100644 --- a/src/cloudevent/mocks/database/helpers.ts +++ b/src/cloudevent/mocks/database/helpers.ts @@ -1,5 +1,4 @@ import { CloudFunction, database } from 'firebase-functions/v2'; -import { Expression } from 'firebase-functions/v2/params'; import { DeepPartial } from '../../types'; import { exampleDataSnapshot, diff --git a/src/cloudevent/mocks/helpers.ts b/src/cloudevent/mocks/helpers.ts index 3c8563b..aed664b 100644 --- a/src/cloudevent/mocks/helpers.ts +++ b/src/cloudevent/mocks/helpers.ts @@ -1,22 +1,25 @@ -import { CloudEvent, CloudFunction } from 'firebase-functions/v2'; -import { Expression } from 'firebase-functions/v2/params'; +import * as v1 from 'firebase-functions'; +import * as v2 from 'firebase-functions/v2'; +import { Expression } from 'firebase-functions/params'; export const APP_ID = '__APP_ID__'; export const PROJECT_ID = '42'; export const FILENAME = 'file_name'; -export function getEventType(cloudFunction: CloudFunction): string { +type CloudFunction = v1.CloudFunction | v2.CloudFunction; + +export function getEventType(cloudFunction: CloudFunction): string { return cloudFunction?.__endpoint?.eventTrigger?.eventType || ''; } export function getEventFilters( - cloudFunction: CloudFunction + cloudFunction: CloudFunction ): Record> { return cloudFunction?.__endpoint?.eventTrigger?.eventFilters || {}; } -export function getBaseCloudEvent>( - cloudFunction: CloudFunction +export function getBaseCloudEvent>( + cloudFunction: v2.CloudFunction ): EventType { return { specversion: '1.0', diff --git a/src/v1.ts b/src/v1.ts index e159e20..abd8d03 100644 --- a/src/v1.ts +++ b/src/v1.ts @@ -32,7 +32,15 @@ import { firestore, HttpsFunction, Runnable, + // @ts-ignore + resetCache, } from 'firebase-functions'; +import { Expression } from 'firebase-functions/params'; +import { + getEventFilters, + getEventType, + resolveStringExpression, +} from './cloudevent/mocks/helpers'; /** Fields of the event context that can be overridden/customized. */ export type EventContextOptions = { @@ -116,13 +124,13 @@ export function wrapV1( export function wrapV1( cloudFunction: CloudFunction ): WrappedScheduledFunction | WrappedFunction> { - if (!has(cloudFunction, '__trigger')) { + if (!has(cloudFunction, '__endpoint')) { throw new Error( 'Wrap can only be called on functions written with the firebase-functions SDK.' ); } - if (get(cloudFunction, '__trigger.labels.deployment-scheduled') === 'true') { + if (has(cloudFunction, '__endpoint.scheduleTrigger')) { const scheduledWrapped: WrappedScheduledFunction = ( options: ContextOptions ) => { @@ -139,10 +147,7 @@ export function wrapV1( return scheduledWrapped; } - if ( - has(cloudFunction, '__trigger.httpsTrigger') && - get(cloudFunction, '__trigger.labels.deployment-callable') !== 'true' - ) { + if (has(cloudFunction, '__endpoint.httpsTrigger')) { throw new Error( 'Wrap function is only available for `onCall` HTTP functions, not `onRequest`.' ); @@ -154,8 +159,7 @@ export function wrapV1( ); } - const isCallableFunction = - get(cloudFunction, '__trigger.labels.deployment-callable') === 'true'; + const isCallableFunction = has(cloudFunction, '__endpoint.callableTrigger'); let wrapped: WrappedFunction = (data, options) => { // Although in Typescript we require `options` some of our JS samples do not pass it. @@ -197,11 +201,12 @@ export function wrapV1( /** @internal */ export function _makeResourceName( - triggerResource: string, + triggerResource: string | Expression, params = {} ): string { + const resource = resolveStringExpression(triggerResource); const wildcardRegex = new RegExp('{[^/{}]*}', 'g'); - let resourceName = triggerResource.replace(wildcardRegex, (wildcard) => { + let resourceName = resource.replace(wildcardRegex, (wildcard) => { let wildcardNoBraces = wildcard.slice(1, -1); // .slice removes '{' and '}' from wildcard let sub = get(params, wildcardNoBraces); return sub || wildcardNoBraces + random(1, 9); @@ -244,8 +249,8 @@ function _makeDefaultContext( triggerData?: T ): EventContext { let eventContextOptions = options as EventContextOptions; - const eventResource = cloudFunction.__trigger.eventTrigger?.resource; - const eventType = cloudFunction.__trigger.eventTrigger?.eventType; + const eventType = getEventType(cloudFunction); + const eventResource = getEventFilters(cloudFunction).resource; const optionsParams = eventContextOptions.params ?? {}; let triggerParams = {}; @@ -281,7 +286,7 @@ function _makeDefaultContext( const defaultContext: EventContext = { eventId: _makeEventId(), resource: eventResource && { - service: cloudFunction.__trigger.eventTrigger?.service, + service: serviceFromEventType(eventType), name: _makeResourceName(eventResource, params), }, eventType, @@ -292,20 +297,22 @@ function _makeDefaultContext( } function _extractDatabaseParams( - triggerResource: string, + triggerResource: string | Expression, data: database.DataSnapshot ): EventContext['params'] { + const resource = resolveStringExpression(triggerResource); const path = data.ref.toString().replace(data.ref.root.toString(), ''); - return _extractParams(triggerResource, path); + return _extractParams(resource, path); } function _extractFirestoreDocumentParams( - triggerResource: string, + triggerResource: string | Expression, data: firestore.DocumentSnapshot ): EventContext['params'] { + const resource = resolveStringExpression(triggerResource); // Resource format: databases/(default)/documents/ return _extractParams( - triggerResource.replace(/^databases\/[^\/]+\/documents\//, ''), + resource.replace(/^databases\/[^\/]+\/documents\//, ''), data.ref.path ); } @@ -338,6 +345,29 @@ export function _extractParams( return params; } +function serviceFromEventType(eventType?: string): string { + if (eventType) { + const providerToService: Array<[string, string]> = [ + ['google.analytics', 'app-measurement.com'], + ['google.firebase.auth', 'firebaseauth.googleapis.com'], + ['google.firebase.database', 'firebaseio.com'], + ['google.firestore', 'firestore.googleapis.com'], + ['google.pubsub', 'pubsub.googleapis.com'], + ['google.firebase.remoteconfig', 'firebaseremoteconfig.googleapis.com'], + ['google.storage', 'storage.googleapis.com'], + ['google.testing', 'testing.googleapis.com'], + ]; + + const match = providerToService.find(([provider]) => { + eventType.includes(provider); + }); + if (match) { + return match[1]; + } + } + return 'unknown-service.googleapis.com'; +} + /** Make a Change object to be used as test data for Firestore and real time database onWrite and onUpdate functions. */ export function makeChange(before: T, after: T): Change { return Change.fromObjects(before, after); @@ -345,9 +375,8 @@ export function makeChange(before: T, after: T): Change { /** Mock values returned by `functions.config()`. */ export function mockConfig(conf: { [key: string]: { [key: string]: any } }) { - if (config.singleton) { - delete config.singleton; + if (resetCache) { + resetCache(); } - process.env.CLOUD_RUNTIME_CONFIG = JSON.stringify(conf); }