From 3365a38966d1820f788575bee4191f4ffec4dc63 Mon Sep 17 00:00:00 2001 From: Thomas Bouldin Date: Tue, 30 Jan 2024 17:22:41 -0800 Subject: [PATCH 1/5] checkpoint --- spec/v1/cloud-functions.spec.ts | 29 ++++++++++++ spec/v1/providers/analytics.spec.ts | 7 ++- spec/v1/providers/auth.spec.ts | 26 +++++++++++ spec/v1/providers/database.spec.ts | 32 +++++++++---- spec/v1/providers/firestore.spec.ts | 36 +++++++++++---- spec/v1/providers/https.spec.ts | 45 ++++++++++++++++++- spec/v1/providers/pubsub.spec.ts | 8 +++- spec/v1/providers/remoteConfig.spec.ts | 14 +++--- spec/v2/providers/alerts/alerts.spec.ts | 19 +++++++- .../providers/alerts/appDistribution.spec.ts | 21 +++++++++ spec/v2/providers/alerts/billing.spec.ts | 31 +++++++++++++ spec/v2/providers/alerts/crashlytics.spec.ts | 11 +++++ spec/v2/providers/pubsub.spec.ts | 4 +- spec/v2/providers/remoteConfig.spec.ts | 8 ++-- src/common/onInit.ts | 36 +++++++++++++++ src/v1/cloud-functions.ts | 2 + src/v1/index.ts | 2 + src/v1/providers/https.ts | 27 ++++++----- src/v2/core.ts | 3 +- src/v2/index.ts | 2 +- src/v2/providers/alerts/alerts.ts | 3 +- src/v2/providers/alerts/appDistribution.ts | 5 ++- src/v2/providers/alerts/billing.ts | 3 +- src/v2/providers/alerts/crashlytics.ts | 3 +- src/v2/providers/alerts/performance.ts | 4 +- src/v2/providers/database.ts | 11 +++-- src/v2/providers/eventarc.ts | 5 ++- src/v2/providers/firestore.ts | 5 ++- src/v2/providers/https.ts | 14 +++--- src/v2/providers/identity.ts | 3 +- src/v2/providers/pubsub.ts | 3 +- src/v2/providers/remoteConfig.ts | 4 +- src/v2/providers/scheduler.ts | 3 +- src/v2/providers/storage.ts | 3 +- src/v2/providers/tasks.ts | 3 +- src/v2/providers/testLab.ts | 3 +- 36 files changed, 363 insertions(+), 75 deletions(-) create mode 100644 src/common/onInit.ts diff --git a/spec/v1/cloud-functions.spec.ts b/spec/v1/cloud-functions.spec.ts index 6ee3abc41..ae145fb8a 100644 --- a/spec/v1/cloud-functions.spec.ts +++ b/spec/v1/cloud-functions.spec.ts @@ -23,6 +23,7 @@ import { expect } from "chai"; import { + onInit, Event, EventContext, makeCloudFunction, @@ -41,6 +42,34 @@ describe("makeCloudFunction", () => { legacyEventType: "providers/provider/eventTypes/event", }; + it("should call the onInit callback", async () => { + const test: Event = { + context: { + eventId: "00000", + timestamp: "2016-11-04T21:29:03.496Z", + eventType: "provider.event", + resource: { + service: "provider", + name: "resource", + }, + }, + data: "data", + }; + const cf = makeCloudFunction({ + provider: "mock.provider", + eventType: "mock.event", + service: "service", + triggerResource: () => "resource", + handler: () => null, + }); + + let hello; + onInit(() => (hello = "world")); + expect(hello).is.undefined; + await cf(test.data, test.context); + expect(hello).equals("world"); + }); + it("should put a __trigger/__endpoint on the returned CloudFunction", () => { const cf = makeCloudFunction({ provider: "mock.provider", diff --git a/spec/v1/providers/analytics.spec.ts b/spec/v1/providers/analytics.spec.ts index 90a617686..9b12eb7ff 100644 --- a/spec/v1/providers/analytics.spec.ts +++ b/spec/v1/providers/analytics.spec.ts @@ -85,7 +85,7 @@ describe("Analytics Functions", () => { }); describe("#dataConstructor", () => { - it("should handle an event with the appropriate fields", () => { + it("should handle an event with the appropriate fields", async () => { const cloudFunction = analytics .event("first_open") .onLog((data: analytics.AnalyticsEvent) => data); @@ -109,13 +109,16 @@ describe("Analytics Functions", () => { }, }; - return expect(cloudFunction(event.data, event.context)).to.eventually.deep.equal({ + let hello; + functions.onInit(() => hello = "world"); + await expect(cloudFunction(event.data, event.context)).to.eventually.deep.equal({ params: {}, user: { userId: "hi!", userProperties: {}, }, }); + expect(hello).equals("world"); }); it("should remove xValues", () => { diff --git a/spec/v1/providers/auth.spec.ts b/spec/v1/providers/auth.spec.ts index f5f6a806d..90087e82c 100644 --- a/spec/v1/providers/auth.spec.ts +++ b/spec/v1/providers/auth.spec.ts @@ -339,4 +339,30 @@ describe("Auth Functions", () => { expect(cf.run).to.not.throw(Error); }); }); + + describe("onInit", () => { + beforeEach(() => { + functions.onInit(() => {}); + process.env.GCLOUD_PROJECT = "project"; + }); + + after(() => { + functions.onInit(null); + delete process.env.GCLOUD_PROJECT; + }) + + it("initailizes before onCreate", async () => { + let hello; + functions.onInit(() => hello = "world"); + await auth.user().onCreate(() => null)(event.data, event.context); + expect(hello).equals("world"); + }); + + it("initailizes before onDelete", async () => { + let hello; + functions.onInit(() => hello = "world"); + await auth.user().onDelete(() => null)(event.data, event.context); + expect(hello).equals("world"); + }); + }); }); diff --git a/spec/v1/providers/database.spec.ts b/spec/v1/providers/database.spec.ts index 18d973b1d..e33a3e2e2 100644 --- a/spec/v1/providers/database.spec.ts +++ b/spec/v1/providers/database.spec.ts @@ -116,7 +116,7 @@ describe("Database Functions", () => { ); }); - it("should return a handler that emits events with a proper DataSnapshot", () => { + it("should return a handler that emits events with a proper DataSnapshot", async () => { const event = { data: { data: null, @@ -133,7 +133,11 @@ describe("Database Functions", () => { expect(change.after.val()).to.deep.equal({ foo: "bar" }); }); - return handler(event.data, event.context); + let hello; + functions.onInit(() => (hello = "world")); + expect(hello).is.undefined; + await handler(event.data, event.context); + expect(hello).equals("world"); }); it("Should have params of the correct type", () => { @@ -177,7 +181,7 @@ describe("Database Functions", () => { ); }); - it("should return a handler that emits events with a proper DataSnapshot", () => { + it("should return a handler that emits events with a proper DataSnapshot", async () => { const event = { data: { data: null, @@ -195,7 +199,11 @@ describe("Database Functions", () => { expect(data.val()).to.deep.equal({ foo: "bar" }); }); - return handler(event.data, event.context); + let hello; + functions.onInit(() => (hello = "world")); + expect(hello).to.be.undefined; + await handler(event.data, event.context); + expect(hello).equals("world"); }); it("Should have params of the correct type", () => { @@ -239,7 +247,7 @@ describe("Database Functions", () => { ); }); - it("should return a handler that emits events with a proper DataSnapshot", () => { + it("should return a handler that emits events with a proper DataSnapshot", async () => { const event = { data: { data: null, @@ -257,7 +265,11 @@ describe("Database Functions", () => { expect(change.after.val()).to.deep.equal({ foo: "bar" }); }); - return handler(event.data, event.context); + let hello; + functions.onInit(() => (hello = "world")); + expect(hello).is.undefined; + await handler(event.data, event.context); + expect(hello).equals("world"); }); it("Should have params of the correct type", () => { @@ -301,7 +313,7 @@ describe("Database Functions", () => { ); }); - it("should return a handler that emits events with a proper DataSnapshot", () => { + it("should return a handler that emits events with a proper DataSnapshot", async () => { const event = { data: { data: { foo: "bar" }, @@ -319,7 +331,11 @@ describe("Database Functions", () => { expect(data.val()).to.deep.equal({ foo: "bar" }); }); - return handler(event.data, event.context); + let hello; + functions.onInit(() => (hello = "world")); + expect(hello).is.undefined; + await handler(event.data, event.context); + expect(hello).equals("world"); }); it("Should have params of the correct type", () => { diff --git a/spec/v1/providers/firestore.spec.ts b/spec/v1/providers/firestore.spec.ts index f8f4288db..5c7fb3746 100644 --- a/spec/v1/providers/firestore.spec.ts +++ b/spec/v1/providers/firestore.spec.ts @@ -234,7 +234,7 @@ describe("Firestore Functions", () => { delete process.env.GCLOUD_PROJECT; }); - it('constructs appropriate fields and getters for event.data on "document.write" events', () => { + it('constructs appropriate fields and getters for event.data on "document.write" events', async () => { const testFunction = firestore.document("path").onWrite((change) => { expect(change.before.data()).to.deep.equal({ key1: false, @@ -246,20 +246,30 @@ describe("Firestore Functions", () => { return true; // otherwise will get warning about returning undefined }); const event = constructEvent(createOldValue(), createValue()); - return testFunction(event.data, event.context); + + let hello; + functions.onInit(() => (hello = "world")); + expect(hello).is.undefined; + await testFunction(event.data, event.context); + expect(hello).equals("world"); }).timeout(5000); - it('constructs appropriate fields and getters for event.data on "document.create" events', () => { + it('constructs appropriate fields and getters for event.data on "document.create" events', async () => { const testFunction = firestore.document("path").onCreate((data) => { expect(data.data()).to.deep.equal({ key1: true, key2: 123 }); expect(data.get("key1")).to.equal(true); return true; // otherwise will get warning about returning undefined }); const event = constructEvent({}, createValue()); - return testFunction(event.data, event.context); + + let hello; + functions.onInit(() => (hello = "world")); + expect(hello).is.undefined; + await testFunction(event.data, event.context); + expect(hello).equals("world"); }).timeout(5000); - it('constructs appropriate fields and getters for event.data on "document.update" events', () => { + it('constructs appropriate fields and getters for event.data on "document.update" events', async () => { const testFunction = firestore.document("path").onUpdate((change) => { expect(change.before.data()).to.deep.equal({ key1: false, @@ -271,17 +281,27 @@ describe("Firestore Functions", () => { return true; // otherwise will get warning about returning undefined }); const event = constructEvent(createOldValue(), createValue()); - return testFunction(event.data, event.context); + + let hello; + functions.onInit(() => (hello = "world")); + expect(hello).is.undefined; + await testFunction(event.data, event.context); + expect(hello).equals("world"); }).timeout(5000); - it('constructs appropriate fields and getters for event.data on "document.delete" events', () => { + it('constructs appropriate fields and getters for event.data on "document.delete" events', async () => { const testFunction = firestore.document("path").onDelete((data) => { expect(data.data()).to.deep.equal({ key1: false, key2: 111 }); expect(data.get("key1")).to.equal(false); return true; // otherwise will get warning about returning undefined }); const event = constructEvent(createOldValue(), {}); - return testFunction(event.data, event.context); + + let hello; + functions.onInit(() => (hello = "world")); + expect(hello).is.undefined; + await testFunction(event.data, event.context); + expect(hello).equals("world"); }).timeout(5000); }); diff --git a/spec/v1/providers/https.spec.ts b/spec/v1/providers/https.spec.ts index 3fc736952..1f983709b 100644 --- a/spec/v1/providers/https.spec.ts +++ b/spec/v1/providers/https.spec.ts @@ -35,6 +35,7 @@ import { import { runHandler } from "../../helper"; import { MINIMAL_V1_ENDPOINT } from "../../fixtures"; import { CALLABLE_AUTH_HEADER, ORIGINAL_AUTH_HEADER } from "../../../src/common/providers/https"; +import { onInit } from "../../../src/v1"; describe("CloudHttpsBuilder", () => { describe("#onRequest", () => { @@ -70,6 +71,26 @@ describe("CloudHttpsBuilder", () => { expect(fn.__endpoint.timeoutSeconds).to.deep.equal(90); expect(fn.__endpoint.httpsTrigger.invoker).to.deep.equal(["private"]); }); + + it("should call initializer", async () => { + let hello; + onInit(() => (hello = "world")); + expect(hello).to.be.undefined; + const fn = functions.https.onRequest(() => null); + const req = new MockRequest( + { + data: { foo: "bar" }, + }, + { + "content-type": "application/json", + } + ); + req.method = "POST"; + // We don't really have test infrastructure to fake requests. Luckily we + // don't touch much of the request in boilerplate, just trace context. + await fn({headers: []} as any, null as any); + expect(hello).to.equal("world"); + }); }); }); @@ -114,7 +135,7 @@ describe("#onCall", () => { expect(fn.__endpoint.timeoutSeconds).to.deep.equal(90); }); - it("has a .run method", () => { + it("has a .run method", async () => { const cf = https.onCall((d, c) => { return { data: d, context: c }; }); @@ -127,7 +148,8 @@ describe("#onCall", () => { token: "token", }, }; - expect(cf.run(data, context)).to.deep.equal({ data, context }); + + await expect(cf.run(data, context)).to.eventually.deep.equal({ data, context }); }); // Regression test for firebase-functions#947 @@ -152,6 +174,25 @@ describe("#onCall", () => { expect(gotData).to.deep.equal({ foo: "bar" }); }); + it("should call initializer", async () => { + const func = https.onCall(() => null); + const req = new MockRequest( + { + data: {}, + }, + { + "content-type": "application/json", + } + ); + req.method = "POST"; + + let hello; + onInit(() => (hello = "world")); + expect(hello).to.be.undefined; + await runHandler(func, req as any); + expect(hello).to.equal("world"); + }); + // Test for firebase-tools#5210 it("should create context.auth for v1 emulated functions", async () => { sinon.stub(debug, "isDebugFeatureEnabled").withArgs("skipTokenVerification").returns(true); diff --git a/spec/v1/providers/pubsub.spec.ts b/spec/v1/providers/pubsub.spec.ts index 0a7b89ad6..bd3394ef4 100644 --- a/spec/v1/providers/pubsub.spec.ts +++ b/spec/v1/providers/pubsub.spec.ts @@ -123,7 +123,7 @@ describe("Pubsub Functions", () => { expect(() => pubsub.topic("bad/topic/format")).to.throw(Error); }); - it("should properly handle a new-style event", () => { + it("should properly handle a new-style event", async () => { const raw = new Buffer('{"hello":"world"}', "utf8").toString("base64"); const event: Event = { data: { @@ -151,11 +151,15 @@ describe("Pubsub Functions", () => { }; }); - return expect(result(event.data, event.context)).to.eventually.deep.equal({ + let hello; + functions.onInit(() => (hello = "world")); + expect(hello).is.undefined; + await expect(result(event.data, event.context)).to.eventually.deep.equal({ raw, json: { hello: "world" }, attributes: { foo: "bar" }, }); + expect(hello).equals("world"); }); }); diff --git a/spec/v1/providers/remoteConfig.spec.ts b/spec/v1/providers/remoteConfig.spec.ts index f5fb427e6..e207b5de3 100644 --- a/spec/v1/providers/remoteConfig.spec.ts +++ b/spec/v1/providers/remoteConfig.spec.ts @@ -122,12 +122,14 @@ describe("RemoteConfig Functions", () => { delete process.env.GCLOUD_PROJECT; }); - it("should unwrap the version in the event", () => { - return Promise.all([ - cloudFunctionUpdate(event.data, event.context).then((data: any) => { - expect(data).to.deep.equal(constructVersion()); - }), - ]); + it("should unwrap the version in the event", async () => { + let hello; + functions.onInit(() => (hello = "world")); + expect(hello).to.be.undefined; + await cloudFunctionUpdate(event.data, event.context).then((data: any) => { + expect(data).to.deep.equal(constructVersion()); + }); + expect(hello).to.equal("world"); }); }); }); diff --git a/spec/v2/providers/alerts/alerts.spec.ts b/spec/v2/providers/alerts/alerts.spec.ts index 4476e121e..9f69f0555 100644 --- a/spec/v2/providers/alerts/alerts.spec.ts +++ b/spec/v2/providers/alerts/alerts.spec.ts @@ -1,5 +1,5 @@ import { expect } from "chai"; -import { CloudEvent } from "../../../../src/v2"; +import { CloudEvent, onInit } from "../../../../src/v2"; import * as options from "../../../../src/v2/options"; import * as alerts from "../../../../src/v2/providers/alerts"; import { FULL_OPTIONS } from "../fixtures"; @@ -211,4 +211,21 @@ describe("alerts", () => { }); }); }); + + it("calls init function", async () => { + const event: CloudEvent = { + specversion: "1.0", + id: "id", + source: "source", + type: "type", + time: "now", + data: "data", + }; + + let hello; + onInit(() => (hello = "world")); + expect(hello).to.be.undefined; + await alerts.onAlertPublished("alert", () => null)(event); + expect(hello).to.equal("world"); + }); }); diff --git a/spec/v2/providers/alerts/appDistribution.spec.ts b/spec/v2/providers/alerts/appDistribution.spec.ts index 7e2b7d0c6..739cb0ae2 100644 --- a/spec/v2/providers/alerts/appDistribution.spec.ts +++ b/spec/v2/providers/alerts/appDistribution.spec.ts @@ -3,6 +3,7 @@ import * as alerts from "../../../../src/v2/providers/alerts"; import * as appDistribution from "../../../../src/v2/providers/alerts/appDistribution"; import { FULL_OPTIONS } from "../fixtures"; import { FULL_ENDPOINT, MINIMAL_V2_ENDPOINT } from "../../../fixtures"; +import { onInit } from "../../../../src/v2/core"; const APPID = "123456789"; const myHandler = () => 42; @@ -91,6 +92,16 @@ describe("appDistribution", () => { expect(res).to.equal("input"); }); + + it("should call the initializer", async () => { + const func = appDistribution.onNewTesterIosDevicePublished(APPID, (event) => event); + + let hello; + onInit(() => (hello = "world")); + expect(hello).to.be.undefined; + await func({ data: "test" } as any); + expect(hello).to.equal("world"); + }); }); describe("onInAppfeedbackPublished", () => { @@ -172,6 +183,16 @@ describe("appDistribution", () => { expect(res).to.equal("input"); }); + + it("should call the initializer", async () => { + const func = appDistribution.onInAppFeedbackPublished(APPID, (event) => event); + + let hello; + onInit(() => (hello = "world")); + expect(hello).to.be.undefined; + await func({ data: "test" } as any); + expect(hello).to.equal("world"); + }); }); describe("getOptsAndApp", () => { diff --git a/spec/v2/providers/alerts/billing.spec.ts b/spec/v2/providers/alerts/billing.spec.ts index d4be3403d..bf75d019e 100644 --- a/spec/v2/providers/alerts/billing.spec.ts +++ b/spec/v2/providers/alerts/billing.spec.ts @@ -3,6 +3,7 @@ import * as alerts from "../../../../src/v2/providers/alerts"; import * as billing from "../../../../src/v2/providers/alerts/billing"; import { FULL_OPTIONS } from "../fixtures"; import { FULL_ENDPOINT, MINIMAL_V2_ENDPOINT } from "../../../fixtures"; +import { onInit } from "../../../../src/v2/core"; const ALERT_TYPE = "new-alert-type"; const myHandler = () => 42; @@ -41,6 +42,16 @@ describe("billing", () => { }, }); }); + + it("should call the initializer", async () => { + const func = billing.onPlanAutomatedUpdatePublished((event) => event); + + let hello; + onInit(() => (hello = "world")); + expect(hello).to.be.undefined; + await func({ data: "test" } as any); + expect(hello).to.equal("world"); + }); }); describe("onPlanAutomatedUpdatePublished", () => { @@ -76,6 +87,16 @@ describe("billing", () => { }, }); }); + + it("should call the initializer", async () => { + const func = billing.onPlanAutomatedUpdatePublished((event) => event); + + let hello; + onInit(() => (hello = "world")); + expect(hello).to.be.undefined; + await func({ data: "test" } as any); + expect(hello).to.equal("world"); + }); }); describe("onOperation", () => { @@ -119,5 +140,15 @@ describe("billing", () => { expect(res).to.equal("input"); }); + + it("should call the initializer", async () => { + const func = billing.onOperation(ALERT_TYPE, (event) => event, undefined); + + let hello; + onInit(() => (hello = "world")); + expect(hello).to.be.undefined; + await func({ data: "test" } as any); + expect(hello).to.equal("world"); + }); }); }); diff --git a/spec/v2/providers/alerts/crashlytics.spec.ts b/spec/v2/providers/alerts/crashlytics.spec.ts index fd4984b76..7a8da3df9 100644 --- a/spec/v2/providers/alerts/crashlytics.spec.ts +++ b/spec/v2/providers/alerts/crashlytics.spec.ts @@ -3,6 +3,7 @@ import * as alerts from "../../../../src/v2/providers/alerts"; import * as crashlytics from "../../../../src/v2/providers/alerts/crashlytics"; import { FULL_OPTIONS } from "../fixtures"; import { FULL_ENDPOINT, MINIMAL_V2_ENDPOINT } from "../../../fixtures"; +import { onInit } from "../../../../src/v2/core"; const ALERT_TYPE = "new-alert-type"; const APPID = "123456789"; @@ -104,6 +105,16 @@ describe("crashlytics", () => { }, }); }); + + it("should call initializer", async () => { + const func = crashlytics[method](APPID, myHandler); + + let hello; + onInit(() => (hello = "world")); + expect(hello).to.be.undefined; + await func({ data: "crash" } as any); + expect(hello).to.equal("world"); + }) }); } diff --git a/spec/v2/providers/pubsub.spec.ts b/spec/v2/providers/pubsub.spec.ts index 3b712044e..d498b1b42 100644 --- a/spec/v2/providers/pubsub.spec.ts +++ b/spec/v2/providers/pubsub.spec.ts @@ -134,7 +134,7 @@ describe("onMessagePublished", () => { expect(res).to.equal("input"); }); - it("should parse pubsub messages", () => { + it("should parse pubsub messages", async () => { let json: unknown; const messageJSON = { messageId: "uuid", @@ -161,7 +161,7 @@ describe("onMessagePublished", () => { return event; }); - const eventAgain = func(event); + const eventAgain = await func(event); // Deep equal uses JSON equality, so we'll still match even though // Message is a class and we passed an interface. diff --git a/spec/v2/providers/remoteConfig.spec.ts b/spec/v2/providers/remoteConfig.spec.ts index 5faf907ec..ae29ecbc7 100644 --- a/spec/v2/providers/remoteConfig.spec.ts +++ b/spec/v2/providers/remoteConfig.spec.ts @@ -30,7 +30,7 @@ describe("onConfigUpdated", () => { options.setGlobalOptions({}); }); - it("should create a function with a handler", () => { + it("should create a function with a handler", async () => { const fn = remoteConfig.onConfigUpdated(() => 2); expect(fn.__endpoint).to.deep.eq({ @@ -43,10 +43,10 @@ describe("onConfigUpdated", () => { retry: false, }, }); - expect(fn.run(1 as any)).to.eq(2); + await expect(fn.run(1 as any)).to.eventually.eq(2); }); - it("should create a function with opts and a handler", () => { + it("should create a function with opts and a handler", async () => { options.setGlobalOptions({ memory: "512MiB", region: "us-west1", @@ -72,6 +72,6 @@ describe("onConfigUpdated", () => { retry: true, }, }); - expect(fn.run(1 as any)).to.eq(2); + await expect(fn.run(1 as any)).to.eventually.eq(2); }); }); diff --git a/src/common/onInit.ts b/src/common/onInit.ts new file mode 100644 index 000000000..cf87ecbf4 --- /dev/null +++ b/src/common/onInit.ts @@ -0,0 +1,36 @@ + +import * as logger from "../logger"; + +let initCallback: (() => any) | null = null; +let didInit = false; + +/** + * Registers a callback that should be run when in a production environment + * before executing any functions code. + * Calling this function more than once leads to undefined behavior. + * @param callback initialization callback to be run before any function executes. + */ +export function onInit(callback: () => any) { + if (initCallback) { + logger.warn( + "Setting onInit callback more than once. Only the most recent callback will be called" + ); + } + initCallback = callback; + didInit = false; +} + +type Resolved = T extends Promise ? V : T; + +/** @internal */ +export function withInit any>(func: T) { + return async (...args: Parameters): Promise>> => { + if (!didInit) { + if (initCallback) { + await initCallback(); + } + didInit = true; + } + return func(...args); + }; +} diff --git a/src/v1/cloud-functions.ts b/src/v1/cloud-functions.ts index 7909fc10d..d5bd3d015 100644 --- a/src/v1/cloud-functions.ts +++ b/src/v1/cloud-functions.ts @@ -44,6 +44,7 @@ import { } from "../runtime/manifest"; import { ResetValue } from "../common/options"; import { SecretParam } from "../params/types"; +import { withInit } from "../common/onInit"; export { Change } from "../common/change"; @@ -403,6 +404,7 @@ export function makeCloudFunction({ context.params = context.params || _makeParams(context, triggerResource); } + handler = withInit(handler); let promise; if (labels && labels["deployment-scheduled"]) { // Scheduled function do not have meaningful data, so exclude it diff --git a/src/v1/index.ts b/src/v1/index.ts index 8e75bff8d..7f3f9e10b 100644 --- a/src/v1/index.ts +++ b/src/v1/index.ts @@ -59,3 +59,5 @@ export * from "./function-configuration"; // NOTE: Equivalent to `export * as params from "../params"` but api-extractor doesn't support that syntax. import * as params from "../params"; export { params }; + +export { onInit } from "../common/onInit"; diff --git a/src/v1/providers/https.ts b/src/v1/providers/https.ts index 3c2340071..f72d61307 100644 --- a/src/v1/providers/https.ts +++ b/src/v1/providers/https.ts @@ -33,6 +33,8 @@ import { import { HttpsFunction, optionsToEndpoint, optionsToTrigger, Runnable } from "../cloud-functions"; import { DeploymentOptions } from "../function-configuration"; import { initV1Endpoint } from "../../runtime/manifest"; +import { withInit } from "../../common/onInit"; +import { wrapTraceContext } from "../../v2/trace"; export { Request, CallableContext, FunctionsErrorCode, HttpsError }; @@ -64,7 +66,7 @@ export function _onRequestWithOptions( ): HttpsFunction { // lets us add __endpoint without altering handler: const cloudFunction: any = (req: Request, res: express.Response) => { - return handler(req, res); + return wrapTraceContext(withInit(handler))(req, res); }; cloudFunction.__trigger = { ...optionsToTrigger(options), @@ -103,15 +105,18 @@ export function _onCallWithOptions( // onCallHandler sniffs the function length of the passed-in callback // and the user could have only tried to listen to data. Wrap their handler // in another handler to avoid accidentally triggering the v2 API - const fixedLen = (data: any, context: CallableContext) => handler(data, context); - const func: any = onCallHandler( - { - enforceAppCheck: options.enforceAppCheck, - consumeAppCheckToken: options.consumeAppCheckToken, - cors: { origin: true, methods: "POST" }, - }, - fixedLen - ); + const fixedLen = (data: any, context: CallableContext) => { + return withInit(handler)(data, context); + }; + const func: any = wrapTraceContext( + onCallHandler( + { + enforceAppCheck: options.enforceAppCheck, + consumeAppCheckToken: options.consumeAppCheckToken, + cors: { origin: true, methods: "POST" }, + }, + fixedLen + )); func.__trigger = { labels: {}, @@ -128,7 +133,7 @@ export function _onCallWithOptions( callableTrigger: {}, }; - func.run = handler; + func.run = fixedLen; return func; } diff --git a/src/v2/core.ts b/src/v2/core.ts index fb7fc1e32..60afb7f20 100644 --- a/src/v2/core.ts +++ b/src/v2/core.ts @@ -31,6 +31,7 @@ import { ManifestEndpoint } from "../runtime/manifest"; export { Change }; export { ParamsOf } from "../common/params"; +export { onInit } from "../common/onInit"; /** @internal */ export interface TriggerAnnotation { @@ -116,4 +117,4 @@ export interface CloudFunction> { * @beta */ run(event: EventType): any | Promise; -} +} \ No newline at end of file diff --git a/src/v2/index.ts b/src/v2/index.ts index 7a1d89ef6..4a1b34263 100644 --- a/src/v2/index.ts +++ b/src/v2/index.ts @@ -68,7 +68,7 @@ export { EventHandlerOptions, } from "./options"; -export { CloudFunction, CloudEvent, ParamsOf } from "./core"; +export { CloudFunction, CloudEvent, ParamsOf, onInit } from "./core"; export { Change } from "../common/change"; // NOTE: Equivalent to `export * as params from "../params"` but api-extractor doesn't support that syntax. import * as params from "../params"; diff --git a/src/v2/providers/alerts/alerts.ts b/src/v2/providers/alerts/alerts.ts index 5dbde4f74..09e0885ca 100644 --- a/src/v2/providers/alerts/alerts.ts +++ b/src/v2/providers/alerts/alerts.ts @@ -27,6 +27,7 @@ import { Expression } from "../../../params"; import { wrapTraceContext } from "../../trace"; import * as options from "../../options"; import { SecretParam } from "../../../params/types"; +import { withInit } from "../../../common/onInit"; /** * The CloudEvent data emitted by Firebase Alerts. @@ -215,7 +216,7 @@ export function onAlertPublished( const [opts, alertType, appId] = getOptsAndAlertTypeAndApp(alertTypeOrOpts); const func = (raw: CloudEvent) => { - return wrapTraceContext(handler)(convertAlertAndApp(raw) as AlertEvent); + return wrapTraceContext(withInit(handler))(convertAlertAndApp(raw) as AlertEvent); }; func.run = handler; diff --git a/src/v2/providers/alerts/appDistribution.ts b/src/v2/providers/alerts/appDistribution.ts index 239233d73..d4ebdfe2e 100644 --- a/src/v2/providers/alerts/appDistribution.ts +++ b/src/v2/providers/alerts/appDistribution.ts @@ -32,6 +32,7 @@ import { wrapTraceContext } from "../../trace"; import { convertAlertAndApp, FirebaseAlertData, getEndpointAnnotation } from "./alerts"; import * as options from "../../options"; import { SecretParam } from "../../../params/types"; +import { withInit } from "../../../common/onInit"; /** * The internal payload object for adding a new tester device to app distribution. @@ -250,7 +251,7 @@ export function onNewTesterIosDevicePublished( const [opts, appId] = getOptsAndApp(appIdOrOptsOrHandler); const func = (raw: CloudEvent) => { - return wrapTraceContext(handler)( + return wrapTraceContext(withInit(handler))( convertAlertAndApp(raw) as AppDistributionEvent ); }; @@ -315,7 +316,7 @@ export function onInAppFeedbackPublished( const [opts, appId] = getOptsAndApp(appIdOrOptsOrHandler); const func = (raw: CloudEvent) => { - return wrapTraceContext(handler)( + return wrapTraceContext(withInit(handler))( convertAlertAndApp(raw) as AppDistributionEvent ); }; diff --git a/src/v2/providers/alerts/billing.ts b/src/v2/providers/alerts/billing.ts index 3b2af87b5..8bdb10d3d 100644 --- a/src/v2/providers/alerts/billing.ts +++ b/src/v2/providers/alerts/billing.ts @@ -29,6 +29,7 @@ import { CloudEvent, CloudFunction } from "../../core"; import { wrapTraceContext } from "../../trace"; import { convertAlertAndApp, FirebaseAlertData, getEndpointAnnotation } from "./alerts"; import * as options from "../../options"; +import { withInit } from "../../../common/onInit"; /** * The internal payload object for billing plan updates. @@ -152,7 +153,7 @@ export function onOperation( } const func = (raw: CloudEvent) => { - return wrapTraceContext(handler)(convertAlertAndApp(raw) as BillingEvent); + return wrapTraceContext(withInit(handler))(convertAlertAndApp(raw) as BillingEvent); }; func.run = handler; diff --git a/src/v2/providers/alerts/crashlytics.ts b/src/v2/providers/alerts/crashlytics.ts index 0e9cb7d8d..07aa7d892 100644 --- a/src/v2/providers/alerts/crashlytics.ts +++ b/src/v2/providers/alerts/crashlytics.ts @@ -32,6 +32,7 @@ import { wrapTraceContext } from "../../trace"; import { convertAlertAndApp, FirebaseAlertData, getEndpointAnnotation } from "./alerts"; import * as options from "../../options"; import { SecretParam } from "../../../params/types"; +import { withInit } from "../../../common/onInit"; /** Generic Crashlytics issue interface */ export interface Issue { @@ -581,7 +582,7 @@ export function onOperation( const [opts, appId] = getOptsAndApp(appIdOrOptsOrHandler); const func = (raw: CloudEvent) => { - return wrapTraceContext(handler(convertAlertAndApp(raw) as CrashlyticsEvent)); + return wrapTraceContext(withInit(handler))(convertAlertAndApp(raw) as CrashlyticsEvent); }; func.run = handler; diff --git a/src/v2/providers/alerts/performance.ts b/src/v2/providers/alerts/performance.ts index 56912d596..9ee3f7beb 100644 --- a/src/v2/providers/alerts/performance.ts +++ b/src/v2/providers/alerts/performance.ts @@ -25,8 +25,10 @@ * @packageDocumentation */ +import { withInit } from "../../../common/onInit"; import { CloudEvent, CloudFunction } from "../../core"; import { EventHandlerOptions } from "../../options"; +import { wrapTraceContext } from "../../trace"; import { convertAlertAndApp, FirebaseAlertData, getEndpointAnnotation } from "./alerts"; /** @@ -137,7 +139,7 @@ export function onThresholdAlertPublished( const event = convertAlertAndApp(raw) as PerformanceEvent; const convertedPayload = convertPayload(event.data.payload); event.data.payload = convertedPayload; - return handler(event); + return wrapTraceContext(withInit(handler(event))); }; func.run = handler; diff --git a/src/v2/providers/database.ts b/src/v2/providers/database.ts index c3318855c..b93d9b53d 100644 --- a/src/v2/providers/database.ts +++ b/src/v2/providers/database.ts @@ -34,6 +34,7 @@ import { Expression } from "../../params"; import { wrapTraceContext } from "../trace"; import * as options from "../options"; import { SecretParam } from "../../params/types"; +import { withInit } from "../../common/onInit"; export { DataSnapshot }; @@ -463,12 +464,14 @@ export function onChangedOperation( const instancePattern = new PathPattern(instance); // wrap the handler - const func = (raw: CloudEvent) => { + const func = async (raw: CloudEvent) => { const event = raw as RawRTDBCloudEvent; const instanceUrl = getInstance(event); const params = makeParams(event, pathPattern, instancePattern) as unknown as ParamsOf; const databaseEvent = makeChangedDatabaseEvent(event, instanceUrl, params); - return wrapTraceContext(handler)(databaseEvent); + // Intentionally put init in the context of traces in case there is something + // expensive to observe. + return wrapTraceContext(withInit(handler))(databaseEvent); }; func.run = handler; @@ -490,13 +493,13 @@ export function onOperation( const instancePattern = new PathPattern(instance); // wrap the handler - const func = (raw: CloudEvent) => { + const func = async (raw: CloudEvent) => { const event = raw as RawRTDBCloudEvent; const instanceUrl = getInstance(event); const params = makeParams(event, pathPattern, instancePattern) as unknown as ParamsOf; const data = eventType === deletedEventType ? event.data.data : event.data.delta; const databaseEvent = makeDatabaseEvent(event, data, instanceUrl, params); - return handler(databaseEvent); + return wrapTraceContext(withInit(handler))(databaseEvent); }; func.run = handler; diff --git a/src/v2/providers/eventarc.ts b/src/v2/providers/eventarc.ts index 6d980e2f9..9f97f9a30 100644 --- a/src/v2/providers/eventarc.ts +++ b/src/v2/providers/eventarc.ts @@ -33,6 +33,7 @@ import { wrapTraceContext } from "../trace"; import { Expression } from "../../params"; import * as options from "../options"; import { SecretParam } from "../../params/types"; +import { withInit } from "../../common/onInit"; /** Options that can be set on an Eventarc trigger. */ export interface EventarcTriggerOptions extends options.EventHandlerOptions { @@ -193,8 +194,8 @@ export function onCustomEventPublished( } else if (typeof eventTypeOrOpts === "object") { opts = eventTypeOrOpts; } - const func = (raw: CloudEvent) => { - return wrapTraceContext(handler)(raw as CloudEvent); + const func = async (raw: CloudEvent) => { + return wrapTraceContext(withInit(handler))(raw as CloudEvent); }; func.run = handler; diff --git a/src/v2/providers/firestore.ts b/src/v2/providers/firestore.ts index 619e089e3..dd3c461ff 100644 --- a/src/v2/providers/firestore.ts +++ b/src/v2/providers/firestore.ts @@ -35,6 +35,7 @@ import { createSnapshotFromProtobuf, } from "../../common/providers/firestore"; import { wrapTraceContext } from "../trace"; +import { withInit } from "../../common/onInit"; export { Change }; @@ -446,7 +447,7 @@ export function onOperation( const event = raw as RawFirestoreEvent; const params = makeParams(event.document, documentPattern) as unknown as ParamsOf; const firestoreEvent = makeFirestoreEvent(eventType, event, params); - return wrapTraceContext(handler)(firestoreEvent); + return wrapTraceContext(withInit(handler))(firestoreEvent); }; func.run = handler; @@ -473,7 +474,7 @@ export function onChangedOperation( const event = raw as RawFirestoreEvent; const params = makeParams(event.document, documentPattern) as unknown as ParamsOf; const firestoreEvent = makeChangedFirestoreEvent(event, params); - return handler(firestoreEvent); + return wrapTraceContext(withInit(handler))(firestoreEvent); }; func.run = handler; diff --git a/src/v2/providers/https.ts b/src/v2/providers/https.ts index 178ee929a..b7448eb65 100644 --- a/src/v2/providers/https.ts +++ b/src/v2/providers/https.ts @@ -43,6 +43,7 @@ import { GlobalOptions, SupportedRegion } from "../options"; import { Expression } from "../../params"; import { SecretParam } from "../../params/types"; import * as options from "../options"; +import { withInit } from "../../common/onInit"; export { Request, CallableRequest, FunctionsErrorCode, HttpsError }; @@ -276,7 +277,7 @@ export function onRequest( }; } - handler = wrapTraceContext(handler); + handler = wrapTraceContext(withInit(handler)); Object.defineProperty(handler, "__trigger", { get: () => { @@ -340,7 +341,8 @@ export function onRequest( export function onCall>( opts: CallableOptions, handler: (request: CallableRequest) => Return -): CallableFunction; +): CallableFunction ? Return : Promise>; + /** * Declares a callable method for clients to call using a Firebase SDK. * @param handler - A function that takes a {@link https.CallableRequest}. @@ -348,11 +350,11 @@ export function onCall>( */ export function onCall>( handler: (request: CallableRequest) => Return -): CallableFunction; +): CallableFunction ? Return : Promise>; export function onCall>( optsOrHandler: CallableOptions | ((request: CallableRequest) => Return), handler?: (request: CallableRequest) => Return -): CallableFunction { +): CallableFunction ? Return : Promise> { let opts: CallableOptions; if (arguments.length === 1) { opts = {}; @@ -365,7 +367,7 @@ export function onCall>( // onCallHandler sniffs the function length to determine which API to present. // fix the length to prevent api versions from being mismatched. - const fixedLen = (req: CallableRequest) => handler(req); + const fixedLen = (req: CallableRequest) => withInit(handler)(req); let func: any = onCallHandler( { cors: { origin, methods: "POST" }, @@ -415,6 +417,6 @@ export function onCall>( callableTrigger: {}, }; - func.run = handler; + func.run = withInit(handler); return func; } diff --git a/src/v2/providers/identity.ts b/src/v2/providers/identity.ts index 3a0b1b7fc..aa93edc2f 100644 --- a/src/v2/providers/identity.ts +++ b/src/v2/providers/identity.ts @@ -40,6 +40,7 @@ import { Expression } from "../../params"; import { initV2Endpoint } from "../../runtime/manifest"; import * as options from "../options"; import { SecretParam } from "../../params/types"; +import { withInit } from "../../common/onInit"; export { AuthUserRecord, AuthBlockingEvent, HttpsError }; @@ -282,7 +283,7 @@ export function beforeOperation( // Create our own function that just calls the provided function so we know for sure that // handler takes one argument. This is something common/providers/identity depends on. const wrappedHandler = (event: AuthBlockingEvent) => handler(event); - const func: any = wrapTraceContext(wrapHandler(eventType, wrappedHandler)); + const func: any = wrapTraceContext(withInit(wrapHandler(eventType, wrappedHandler))); const legacyEventType = `providers/cloud.auth/eventTypes/user.${eventType}`; diff --git a/src/v2/providers/pubsub.ts b/src/v2/providers/pubsub.ts index 19e368d41..0cee195ea 100644 --- a/src/v2/providers/pubsub.ts +++ b/src/v2/providers/pubsub.ts @@ -33,6 +33,7 @@ import { wrapTraceContext } from "../trace"; import { Expression } from "../../params"; import * as options from "../options"; import { SecretParam } from "../../params/types"; +import { withInit } from "../../common/onInit"; /** * Google Cloud Pub/Sub is a globally distributed message bus that automatically scales as you need it. @@ -303,7 +304,7 @@ export function onMessagePublished( subscription: string; }; messagePublishedData.message = new Message(messagePublishedData.message); - return wrapTraceContext(handler)(raw as CloudEvent>); + return wrapTraceContext(withInit(handler))(raw as CloudEvent>); }; func.run = handler; diff --git a/src/v2/providers/remoteConfig.ts b/src/v2/providers/remoteConfig.ts index f6ff45172..3018113a7 100644 --- a/src/v2/providers/remoteConfig.ts +++ b/src/v2/providers/remoteConfig.ts @@ -20,9 +20,11 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +import { withInit } from "../../common/onInit"; import { initV2Endpoint, ManifestEndpoint } from "../../runtime/manifest"; import { CloudEvent, CloudFunction } from "../core"; import { EventHandlerOptions, getGlobalOptions, optionsToEndpoint } from "../options"; +import { wrapTraceContext } from "../trace"; /** @internal */ export const eventType = "google.firebase.remoteconfig.remoteConfig.v1.updated"; @@ -131,7 +133,7 @@ export function onConfigUpdated( const func: any = (raw: CloudEvent) => { return handler(raw as CloudEvent); }; - func.run = handler; + func.run = wrapTraceContext(withInit(handler)); const ep: ManifestEndpoint = { ...initV2Endpoint(getGlobalOptions(), optsOrHandler), diff --git a/src/v2/providers/scheduler.ts b/src/v2/providers/scheduler.ts index 9346c451c..1f8f33c31 100644 --- a/src/v2/providers/scheduler.ts +++ b/src/v2/providers/scheduler.ts @@ -36,6 +36,7 @@ import { wrapTraceContext } from "../trace"; import { Expression } from "../../params"; import * as logger from "../../logger"; import * as options from "../options"; +import { withInit } from "../../common/onInit"; /** @hidden */ interface SeparatedOpts { @@ -176,7 +177,7 @@ export function onSchedule( res.status(500).send(); } }; - const func: any = wrapTraceContext(httpFunc); + const func: any = wrapTraceContext(withInit(httpFunc)); func.run = handler; const globalOpts = options.getGlobalOptions(); diff --git a/src/v2/providers/storage.ts b/src/v2/providers/storage.ts index e66f6b813..582a3db7e 100644 --- a/src/v2/providers/storage.ts +++ b/src/v2/providers/storage.ts @@ -34,6 +34,7 @@ import { wrapTraceContext } from "../trace"; import { Expression } from "../../params"; import * as options from "../options"; import { SecretParam } from "../../params/types"; +import { withInit } from "../../common/onInit"; /** * An object within Google Cloud Storage. @@ -573,7 +574,7 @@ export function onOperation( const [opts, bucket] = getOptsAndBucket(bucketOrOptsOrHandler); const func = (raw: CloudEvent) => { - return wrapTraceContext(handler)(raw as StorageEvent); + return wrapTraceContext(withInit(handler))(raw as StorageEvent); }; func.run = handler; diff --git a/src/v2/providers/tasks.ts b/src/v2/providers/tasks.ts index 795939e1b..49d73bb39 100644 --- a/src/v2/providers/tasks.ts +++ b/src/v2/providers/tasks.ts @@ -40,6 +40,7 @@ import { HttpsFunction } from "./https"; import { Expression } from "../../params"; import { SecretParam } from "../../params/types"; import { initV2Endpoint, initTaskQueueTrigger } from "../../runtime/manifest"; +import { withInit } from "../../common/onInit"; export { AuthData, Request, RateLimits, RetryConfig }; @@ -210,7 +211,7 @@ export function onTaskDispatched( // onDispatchHandler sniffs the function length to determine which API to present. // fix the length to prevent api versions from being mismatched. const fixedLen = (req: Request) => handler(req); - const func: any = wrapTraceContext(onDispatchHandler(fixedLen)); + const func: any = wrapTraceContext(withInit(onDispatchHandler(fixedLen))); Object.defineProperty(func, "__trigger", { get: () => { diff --git a/src/v2/providers/testLab.ts b/src/v2/providers/testLab.ts index 0283a0280..cdf0c85f1 100644 --- a/src/v2/providers/testLab.ts +++ b/src/v2/providers/testLab.ts @@ -20,6 +20,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +import { withInit } from "../../common/onInit"; import { initV2Endpoint, ManifestEndpoint } from "../../runtime/manifest"; import { CloudEvent, CloudFunction } from "../core"; import { EventHandlerOptions, getGlobalOptions, optionsToEndpoint } from "../options"; @@ -190,7 +191,7 @@ export function onTestMatrixCompleted( const specificOpts = optionsToEndpoint(optsOrHandler); const func: any = (raw: CloudEvent) => { - return wrapTraceContext(handler)(raw as CloudEvent); + return wrapTraceContext(withInit(handler))(raw as CloudEvent); }; func.run = handler; From 3ddc043d6c91784c1844d6ee009f7f59a1a8e219 Mon Sep 17 00:00:00 2001 From: Thomas Bouldin Date: Fri, 1 Mar 2024 17:40:19 -0800 Subject: [PATCH 2/5] Finish missing tests; remove redundant v1 tests --- spec/v1/cloud-functions.spec.ts | 2 +- spec/v1/providers/analytics.spec.ts | 3 - spec/v1/providers/auth.spec.ts | 26 ------- spec/v1/providers/database.spec.ts | 32 ++------ spec/v1/providers/firestore.spec.ts | 36 ++------- spec/v1/providers/https.spec.ts | 10 +-- spec/v1/providers/pubsub.spec.ts | 8 +- .../providers/alerts/appDistribution.spec.ts | 4 +- spec/v2/providers/alerts/billing.spec.ts | 6 +- spec/v2/providers/alerts/crashlytics.spec.ts | 2 +- spec/v2/providers/alerts/performance.spec.ts | 18 +++++ spec/v2/providers/database.spec.ts | 69 +++++++++++++++++ spec/v2/providers/eventarc.spec.ts | 18 +++++ spec/v2/providers/firestore.spec.ts | 74 ++++++++++++++++++- spec/v2/providers/https.spec.ts | 48 +++++++++++- spec/v2/providers/identity.spec.ts | 45 +++++++++++ spec/v2/providers/remoteConfig.spec.ts | 23 +++++- spec/v2/providers/scheduler.spec.ts | 24 ++++++ spec/v2/providers/storage.spec.ts | 69 +++++++++++++++++ spec/v2/providers/tasks.spec.ts | 22 ++++++ spec/v2/providers/testLab.spec.ts | 18 +++++ src/common/onInit.ts | 10 ++- src/v2/providers/remoteConfig.ts | 10 ++- 23 files changed, 465 insertions(+), 112 deletions(-) diff --git a/spec/v1/cloud-functions.spec.ts b/spec/v1/cloud-functions.spec.ts index ae145fb8a..d85afbe2f 100644 --- a/spec/v1/cloud-functions.spec.ts +++ b/spec/v1/cloud-functions.spec.ts @@ -42,7 +42,7 @@ describe("makeCloudFunction", () => { legacyEventType: "providers/provider/eventTypes/event", }; - it("should call the onInit callback", async () => { + it("calls init function", async () => { const test: Event = { context: { eventId: "00000", diff --git a/spec/v1/providers/analytics.spec.ts b/spec/v1/providers/analytics.spec.ts index 9b12eb7ff..98db1702f 100644 --- a/spec/v1/providers/analytics.spec.ts +++ b/spec/v1/providers/analytics.spec.ts @@ -109,8 +109,6 @@ describe("Analytics Functions", () => { }, }; - let hello; - functions.onInit(() => hello = "world"); await expect(cloudFunction(event.data, event.context)).to.eventually.deep.equal({ params: {}, user: { @@ -118,7 +116,6 @@ describe("Analytics Functions", () => { userProperties: {}, }, }); - expect(hello).equals("world"); }); it("should remove xValues", () => { diff --git a/spec/v1/providers/auth.spec.ts b/spec/v1/providers/auth.spec.ts index 90087e82c..f5f6a806d 100644 --- a/spec/v1/providers/auth.spec.ts +++ b/spec/v1/providers/auth.spec.ts @@ -339,30 +339,4 @@ describe("Auth Functions", () => { expect(cf.run).to.not.throw(Error); }); }); - - describe("onInit", () => { - beforeEach(() => { - functions.onInit(() => {}); - process.env.GCLOUD_PROJECT = "project"; - }); - - after(() => { - functions.onInit(null); - delete process.env.GCLOUD_PROJECT; - }) - - it("initailizes before onCreate", async () => { - let hello; - functions.onInit(() => hello = "world"); - await auth.user().onCreate(() => null)(event.data, event.context); - expect(hello).equals("world"); - }); - - it("initailizes before onDelete", async () => { - let hello; - functions.onInit(() => hello = "world"); - await auth.user().onDelete(() => null)(event.data, event.context); - expect(hello).equals("world"); - }); - }); }); diff --git a/spec/v1/providers/database.spec.ts b/spec/v1/providers/database.spec.ts index e33a3e2e2..18d973b1d 100644 --- a/spec/v1/providers/database.spec.ts +++ b/spec/v1/providers/database.spec.ts @@ -116,7 +116,7 @@ describe("Database Functions", () => { ); }); - it("should return a handler that emits events with a proper DataSnapshot", async () => { + it("should return a handler that emits events with a proper DataSnapshot", () => { const event = { data: { data: null, @@ -133,11 +133,7 @@ describe("Database Functions", () => { expect(change.after.val()).to.deep.equal({ foo: "bar" }); }); - let hello; - functions.onInit(() => (hello = "world")); - expect(hello).is.undefined; - await handler(event.data, event.context); - expect(hello).equals("world"); + return handler(event.data, event.context); }); it("Should have params of the correct type", () => { @@ -181,7 +177,7 @@ describe("Database Functions", () => { ); }); - it("should return a handler that emits events with a proper DataSnapshot", async () => { + it("should return a handler that emits events with a proper DataSnapshot", () => { const event = { data: { data: null, @@ -199,11 +195,7 @@ describe("Database Functions", () => { expect(data.val()).to.deep.equal({ foo: "bar" }); }); - let hello; - functions.onInit(() => (hello = "world")); - expect(hello).to.be.undefined; - await handler(event.data, event.context); - expect(hello).equals("world"); + return handler(event.data, event.context); }); it("Should have params of the correct type", () => { @@ -247,7 +239,7 @@ describe("Database Functions", () => { ); }); - it("should return a handler that emits events with a proper DataSnapshot", async () => { + it("should return a handler that emits events with a proper DataSnapshot", () => { const event = { data: { data: null, @@ -265,11 +257,7 @@ describe("Database Functions", () => { expect(change.after.val()).to.deep.equal({ foo: "bar" }); }); - let hello; - functions.onInit(() => (hello = "world")); - expect(hello).is.undefined; - await handler(event.data, event.context); - expect(hello).equals("world"); + return handler(event.data, event.context); }); it("Should have params of the correct type", () => { @@ -313,7 +301,7 @@ describe("Database Functions", () => { ); }); - it("should return a handler that emits events with a proper DataSnapshot", async () => { + it("should return a handler that emits events with a proper DataSnapshot", () => { const event = { data: { data: { foo: "bar" }, @@ -331,11 +319,7 @@ describe("Database Functions", () => { expect(data.val()).to.deep.equal({ foo: "bar" }); }); - let hello; - functions.onInit(() => (hello = "world")); - expect(hello).is.undefined; - await handler(event.data, event.context); - expect(hello).equals("world"); + return handler(event.data, event.context); }); it("Should have params of the correct type", () => { diff --git a/spec/v1/providers/firestore.spec.ts b/spec/v1/providers/firestore.spec.ts index 5c7fb3746..f8f4288db 100644 --- a/spec/v1/providers/firestore.spec.ts +++ b/spec/v1/providers/firestore.spec.ts @@ -234,7 +234,7 @@ describe("Firestore Functions", () => { delete process.env.GCLOUD_PROJECT; }); - it('constructs appropriate fields and getters for event.data on "document.write" events', async () => { + it('constructs appropriate fields and getters for event.data on "document.write" events', () => { const testFunction = firestore.document("path").onWrite((change) => { expect(change.before.data()).to.deep.equal({ key1: false, @@ -246,30 +246,20 @@ describe("Firestore Functions", () => { return true; // otherwise will get warning about returning undefined }); const event = constructEvent(createOldValue(), createValue()); - - let hello; - functions.onInit(() => (hello = "world")); - expect(hello).is.undefined; - await testFunction(event.data, event.context); - expect(hello).equals("world"); + return testFunction(event.data, event.context); }).timeout(5000); - it('constructs appropriate fields and getters for event.data on "document.create" events', async () => { + it('constructs appropriate fields and getters for event.data on "document.create" events', () => { const testFunction = firestore.document("path").onCreate((data) => { expect(data.data()).to.deep.equal({ key1: true, key2: 123 }); expect(data.get("key1")).to.equal(true); return true; // otherwise will get warning about returning undefined }); const event = constructEvent({}, createValue()); - - let hello; - functions.onInit(() => (hello = "world")); - expect(hello).is.undefined; - await testFunction(event.data, event.context); - expect(hello).equals("world"); + return testFunction(event.data, event.context); }).timeout(5000); - it('constructs appropriate fields and getters for event.data on "document.update" events', async () => { + it('constructs appropriate fields and getters for event.data on "document.update" events', () => { const testFunction = firestore.document("path").onUpdate((change) => { expect(change.before.data()).to.deep.equal({ key1: false, @@ -281,27 +271,17 @@ describe("Firestore Functions", () => { return true; // otherwise will get warning about returning undefined }); const event = constructEvent(createOldValue(), createValue()); - - let hello; - functions.onInit(() => (hello = "world")); - expect(hello).is.undefined; - await testFunction(event.data, event.context); - expect(hello).equals("world"); + return testFunction(event.data, event.context); }).timeout(5000); - it('constructs appropriate fields and getters for event.data on "document.delete" events', async () => { + it('constructs appropriate fields and getters for event.data on "document.delete" events', () => { const testFunction = firestore.document("path").onDelete((data) => { expect(data.data()).to.deep.equal({ key1: false, key2: 111 }); expect(data.get("key1")).to.equal(false); return true; // otherwise will get warning about returning undefined }); const event = constructEvent(createOldValue(), {}); - - let hello; - functions.onInit(() => (hello = "world")); - expect(hello).is.undefined; - await testFunction(event.data, event.context); - expect(hello).equals("world"); + return testFunction(event.data, event.context); }).timeout(5000); }); diff --git a/spec/v1/providers/https.spec.ts b/spec/v1/providers/https.spec.ts index 1f983709b..c3a7671c0 100644 --- a/spec/v1/providers/https.spec.ts +++ b/spec/v1/providers/https.spec.ts @@ -72,11 +72,13 @@ describe("CloudHttpsBuilder", () => { expect(fn.__endpoint.httpsTrigger.invoker).to.deep.equal(["private"]); }); - it("should call initializer", async () => { + it("calls init function", async () => { let hello; onInit(() => (hello = "world")); expect(hello).to.be.undefined; - const fn = functions.https.onRequest(() => null); + const fn = functions.https.onRequest((_req, res) => { + res.send(200); + }); const req = new MockRequest( { data: { foo: "bar" }, @@ -86,9 +88,7 @@ describe("CloudHttpsBuilder", () => { } ); req.method = "POST"; - // We don't really have test infrastructure to fake requests. Luckily we - // don't touch much of the request in boilerplate, just trace context. - await fn({headers: []} as any, null as any); + await runHandler(fn, req as any); expect(hello).to.equal("world"); }); }); diff --git a/spec/v1/providers/pubsub.spec.ts b/spec/v1/providers/pubsub.spec.ts index bd3394ef4..0a7b89ad6 100644 --- a/spec/v1/providers/pubsub.spec.ts +++ b/spec/v1/providers/pubsub.spec.ts @@ -123,7 +123,7 @@ describe("Pubsub Functions", () => { expect(() => pubsub.topic("bad/topic/format")).to.throw(Error); }); - it("should properly handle a new-style event", async () => { + it("should properly handle a new-style event", () => { const raw = new Buffer('{"hello":"world"}', "utf8").toString("base64"); const event: Event = { data: { @@ -151,15 +151,11 @@ describe("Pubsub Functions", () => { }; }); - let hello; - functions.onInit(() => (hello = "world")); - expect(hello).is.undefined; - await expect(result(event.data, event.context)).to.eventually.deep.equal({ + return expect(result(event.data, event.context)).to.eventually.deep.equal({ raw, json: { hello: "world" }, attributes: { foo: "bar" }, }); - expect(hello).equals("world"); }); }); diff --git a/spec/v2/providers/alerts/appDistribution.spec.ts b/spec/v2/providers/alerts/appDistribution.spec.ts index 739cb0ae2..045b84448 100644 --- a/spec/v2/providers/alerts/appDistribution.spec.ts +++ b/spec/v2/providers/alerts/appDistribution.spec.ts @@ -93,7 +93,7 @@ describe("appDistribution", () => { expect(res).to.equal("input"); }); - it("should call the initializer", async () => { + it("calls init function", async () => { const func = appDistribution.onNewTesterIosDevicePublished(APPID, (event) => event); let hello; @@ -184,7 +184,7 @@ describe("appDistribution", () => { expect(res).to.equal("input"); }); - it("should call the initializer", async () => { + it("calls init function", async () => { const func = appDistribution.onInAppFeedbackPublished(APPID, (event) => event); let hello; diff --git a/spec/v2/providers/alerts/billing.spec.ts b/spec/v2/providers/alerts/billing.spec.ts index bf75d019e..a0020f83b 100644 --- a/spec/v2/providers/alerts/billing.spec.ts +++ b/spec/v2/providers/alerts/billing.spec.ts @@ -43,7 +43,7 @@ describe("billing", () => { }); }); - it("should call the initializer", async () => { + it("calls init function", async () => { const func = billing.onPlanAutomatedUpdatePublished((event) => event); let hello; @@ -88,7 +88,7 @@ describe("billing", () => { }); }); - it("should call the initializer", async () => { + it("calls init function", async () => { const func = billing.onPlanAutomatedUpdatePublished((event) => event); let hello; @@ -141,7 +141,7 @@ describe("billing", () => { expect(res).to.equal("input"); }); - it("should call the initializer", async () => { + it("calls init function", async () => { const func = billing.onOperation(ALERT_TYPE, (event) => event, undefined); let hello; diff --git a/spec/v2/providers/alerts/crashlytics.spec.ts b/spec/v2/providers/alerts/crashlytics.spec.ts index 7a8da3df9..b70ed0644 100644 --- a/spec/v2/providers/alerts/crashlytics.spec.ts +++ b/spec/v2/providers/alerts/crashlytics.spec.ts @@ -106,7 +106,7 @@ describe("crashlytics", () => { }); }); - it("should call initializer", async () => { + it("calls init function", async () => { const func = crashlytics[method](APPID, myHandler); let hello; diff --git a/spec/v2/providers/alerts/performance.spec.ts b/spec/v2/providers/alerts/performance.spec.ts index 667aa7ba5..01004e3f6 100644 --- a/spec/v2/providers/alerts/performance.spec.ts +++ b/spec/v2/providers/alerts/performance.spec.ts @@ -3,6 +3,7 @@ import * as alerts from "../../../../src/v2/providers/alerts"; import * as performance from "../../../../src/v2/providers/alerts/performance"; import { FULL_OPTIONS } from "../fixtures"; import { FULL_ENDPOINT, MINIMAL_V2_ENDPOINT } from "../../../fixtures"; +import { CloudEvent, onInit } from "../../../../src/v2/core"; const APPID = "123456789"; const myHandler = () => 42; @@ -45,6 +46,23 @@ describe("performance", () => { retry: false, }, }); + + it("calls init function", async () => { + const event: CloudEvent = { + specversion: "1.0", + id: "id", + source: "source", + type: "type", + time: "now", + data: "data", + }; + + let hello: string; + onInit(() => (hello = "world")); + expect(hello).to.be.undefined; + await performance.onThresholdAlertPublished(() => null)(event); + expect(hello).to.equal("world"); + }); }); it("should create a function with appid in opts", () => { diff --git a/spec/v2/providers/database.spec.ts b/spec/v2/providers/database.spec.ts index acd87644d..c5e16f747 100644 --- a/spec/v2/providers/database.spec.ts +++ b/spec/v2/providers/database.spec.ts @@ -25,6 +25,7 @@ import { PathPattern } from "../../../src/common/utilities/path-pattern"; import * as database from "../../../src/v2/providers/database"; import { expectType } from "../../common/metaprogramming"; import { MINIMAL_V2_ENDPOINT } from "../../fixtures"; +import { CloudEvent, onInit } from "../../../src/v2/core"; const RAW_RTDB_EVENT: database.RawRTDBCloudEvent = { data: { @@ -409,6 +410,23 @@ describe("database", () => { }, }); }); + + it("calls init function", async () => { + const event: CloudEvent = { + specversion: "1.0", + id: "id", + source: "source", + type: "type", + time: "now", + data: "data", + }; + + let hello; + onInit(() => (hello = "world")); + expect(hello).to.be.undefined; + await database.onValueWritten("path", () => null)(event); + expect(hello).to.equal("world"); + }); }); describe("onValueCreated", () => { @@ -469,6 +487,23 @@ describe("database", () => { }, }); }); + + it("calls init function", async () => { + const event: CloudEvent = { + specversion: "1.0", + id: "id", + source: "source", + type: "type", + time: "now", + data: "data", + }; + + let hello; + onInit(() => (hello = "world")); + expect(hello).to.be.undefined; + await database.onValueCreated("path", () => null)(event); + expect(hello).to.equal("world"); + }); }); describe("onValueUpdated", () => { @@ -526,6 +561,23 @@ describe("database", () => { }, }); }); + + it("calls init function", async () => { + const event: CloudEvent = { + specversion: "1.0", + id: "id", + source: "source", + type: "type", + time: "now", + data: "data", + }; + + let hello; + onInit(() => (hello = "world")); + expect(hello).to.be.undefined; + await database.onValueUpdated("path", () => null)(event); + expect(hello).to.equal("world"); + }); }); describe("onValueDeleted", () => { @@ -583,5 +635,22 @@ describe("database", () => { }, }); }); + + it("calls init function", async () => { + const event: CloudEvent = { + specversion: "1.0", + id: "id", + source: "source", + type: "type", + time: "now", + data: "data", + }; + + let hello; + onInit(() => (hello = "world")); + expect(hello).to.be.undefined; + await database.onValueDeleted("path", () => null)(event); + expect(hello).to.equal("world"); + }); }); }); diff --git a/spec/v2/providers/eventarc.spec.ts b/spec/v2/providers/eventarc.spec.ts index feb42b458..28696319a 100644 --- a/spec/v2/providers/eventarc.spec.ts +++ b/spec/v2/providers/eventarc.spec.ts @@ -25,6 +25,7 @@ import * as options from "../../../src/v2/options"; import * as eventarc from "../../../src/v2/providers/eventarc"; import { FULL_OPTIONS } from "./fixtures"; import { FULL_ENDPOINT, MINIMAL_V2_ENDPOINT } from "../../fixtures"; +import { CloudEvent, onInit } from "../../../src/v2/core"; const ENDPOINT_EVENT_TRIGGER = { eventType: "event-type", @@ -149,5 +150,22 @@ describe("v2/eventarc", () => { }, }); }); + + it("calls init function", async () => { + const event: CloudEvent = { + specversion: "1.0", + id: "id", + source: "source", + type: "type", + time: "now", + data: "data", + }; + + let hello; + onInit(() => (hello = "world")); + expect(hello).to.be.undefined; + await eventarc.onCustomEventPublished("type", () => null)(event); + expect(hello).to.equal("world"); + }); }); }); diff --git a/spec/v2/providers/firestore.spec.ts b/spec/v2/providers/firestore.spec.ts index 555b2fd3b..4a5e40163 100644 --- a/spec/v2/providers/firestore.spec.ts +++ b/spec/v2/providers/firestore.spec.ts @@ -25,6 +25,7 @@ import { google } from "../../../protos/compiledFirestore"; import { Timestamp } from "firebase-admin/firestore"; import * as firestore from "../../../src/v2/providers/firestore"; import { PathPattern } from "../../../src/common/utilities/path-pattern"; +import { onInit } from "../../../src/v2/core"; /** static-complied protobuf */ const DocumentEventData = google.events.cloud.firestore.v1.DocumentEventData; @@ -39,9 +40,9 @@ const eventBase = { dataschema: "https://github.com/googleapis/google-cloudevents/blob/main/proto/google/events/cloud/firestore/v1/data.proto", id: "379ad868-5ef9-4c84-a8ba-f75f1b056663", - source: "//firestore.googleapis.com/projects/my-project/databases/my-db", + source: "projects/my-project/databases/my-db/documents/d", subject: "documents/foo/fGRodw71mHutZ4wGDuT8", - specversion: "1.0", + specversion: "1.0" as const, time: "2023-03-10T18:20:43.677647Z", type: "google.cloud.firestore.document.v1.created", }; @@ -192,6 +193,23 @@ describe("firestore", () => { expect(func.run(true as any)).to.eq(2); expect(func.__endpoint).to.deep.eq(expectedEp); }); + + it("calls init function", async () => { + const event: firestore.RawFirestoreEvent = { + ...eventBase, + datacontenttype: "application/json", + data: { + oldValue: null, + value: null, + }, + }; + + let hello; + onInit(() => (hello = "world")); + expect(hello).to.be.undefined; + await firestore.onDocumentWritten("path", () => null)(event); + expect(hello).to.equal("world"); + }); }); describe("onDocumentCreated", () => { @@ -239,6 +257,23 @@ describe("firestore", () => { expect(func.run(true as any)).to.eq(2); expect(func.__endpoint).to.deep.eq(expectedEp); }); + + it("calls init function", async () => { + const event: firestore.RawFirestoreEvent = { + ...eventBase, + datacontenttype: "application/json", + data: { + oldValue: null, + value: null, + }, + }; + + let hello; + onInit(() => (hello = "world")); + expect(hello).to.be.undefined; + await firestore.onDocumentCreated("type", () => null)(event); + expect(hello).to.equal("world"); + }); }); describe("onDocumentUpdated", () => { @@ -286,6 +321,24 @@ describe("firestore", () => { expect(func.run(true as any)).to.eq(2); expect(func.__endpoint).to.deep.eq(expectedEp); }); + + it("calls init function", async () => { + const event: firestore.RawFirestoreEvent = { + ...eventBase, + datacontenttype: "application/json", + data: { + oldValue: null, + value: null, + }, + }; + + + let hello; + onInit(() => (hello = "world")); + expect(hello).to.be.undefined; + await firestore.onDocumentUpdated("path", () => null)(event); + expect(hello).to.equal("world"); + }); }); describe("onDocumentDeleted", () => { @@ -333,6 +386,23 @@ describe("firestore", () => { expect(func.run(true as any)).to.eq(2); expect(func.__endpoint).to.deep.eq(expectedEp); }); + + it("calls init function", async () => { + const event: firestore.RawFirestoreEvent = { + ...eventBase, + datacontenttype: "application/json", + data: { + oldValue: null, + value: null, + }, + }; + + let hello; + onInit(() => (hello = "world")); + expect(hello).to.be.undefined; + await firestore.onDocumentDeleted("path", () => null)(event); + expect(hello).to.equal("world"); + }); }); describe("getOpts", () => { diff --git a/spec/v2/providers/https.spec.ts b/spec/v2/providers/https.spec.ts index 64a37c7eb..643044338 100644 --- a/spec/v2/providers/https.spec.ts +++ b/spec/v2/providers/https.spec.ts @@ -29,6 +29,7 @@ import * as https from "../../../src/v2/providers/https"; import { expectedResponseHeaders, MockRequest } from "../../fixtures/mockrequest"; import { runHandler } from "../../helper"; import { FULL_ENDPOINT, MINIMAL_V2_ENDPOINT, FULL_OPTIONS, FULL_TRIGGER } from "./fixtures"; +import { onInit } from "../../../src/v2/core"; describe("onRequest", () => { beforeEach(() => { @@ -270,6 +271,28 @@ describe("onRequest", () => { sinon.restore(); }); + + it("calls init function", async () => { + const func = https.onRequest((req, res) => { + res.status(200).send("Good"); + }); + const req = new MockRequest( + { + data: {}, + }, + { + "Access-Control-Request-Method": "POST", + "Access-Control-Request-Headers": "origin", + origin: "example.com", + } + ); + req.method = "OPTIONS"; + let hello; + onInit(() => (hello = "world")); + expect(hello).to.be.undefined; + await runHandler(func, req as any); + expect(hello).to.equal("world"); + }); }); describe("onCall", () => { @@ -363,7 +386,7 @@ describe("onCall", () => { }); }); - it("has a .run method", () => { + it("has a .run method", async () => { const cf = https.onCall((request) => { return request; }); @@ -376,7 +399,7 @@ describe("onCall", () => { token: "token", }, }; - expect(cf.run(request)).to.deep.equal(request); + await expect(cf.run(request)).to.eventually.deep.equal(request); }); it("should be an express handler", async () => { @@ -487,4 +510,25 @@ describe("onCall", () => { https.onCall((request: https.CallableRequest) => `Hello, ${request.data}`); https.onCall((request: https.CallableRequest) => `Hello, ${request.data}`); }); + + it("calls init function", async () => { + const func = https.onCall(() => 42); + + const req = new MockRequest( + { + data: {}, + }, + { + "content-type": "application/json", + origin: "example.com", + } + ); + req.method = "POST"; + + let hello; + onInit(() => (hello = "world")); + expect(hello).to.be.undefined; + await runHandler(func, req as any); + expect(hello).to.equal("world"); + }); }); diff --git a/spec/v2/providers/identity.spec.ts b/spec/v2/providers/identity.spec.ts index 7559a4133..bcd416d29 100644 --- a/spec/v2/providers/identity.spec.ts +++ b/spec/v2/providers/identity.spec.ts @@ -22,6 +22,9 @@ import { expect } from "chai"; import * as identity from "../../../src/v2/providers/identity"; import { MINIMAL_V2_ENDPOINT } from "../../fixtures"; +import { onInit } from "../../../src/v2/core"; +import { MockRequest } from "../../fixtures/mockrequest"; +import { runHandler } from "../../helper"; const BEFORE_CREATE_TRIGGER = { eventType: "providers/cloud.auth/eventTypes/user.beforeCreate", @@ -91,6 +94,27 @@ describe("identity", () => { }, ]); }); + + it("calls init function", async () => { + const func = identity.beforeUserCreated(() => null); + + const req = new MockRequest( + { + data: {}, + }, + { + "content-type": "application/json", + origin: "example.com", + } + ); + req.method = "POST"; + + let hello; + onInit(() => (hello = "world")); + expect(hello).to.be.undefined; + await runHandler(func, req as any); + expect(hello).to.equal("world"); + }); }); describe("beforeUserSignedIn", () => { @@ -135,6 +159,27 @@ describe("identity", () => { }, ]); }); + + it("calls init function", async () => { + const func = identity.beforeUserSignedIn(() => null); + + const req = new MockRequest( + { + data: {}, + }, + { + "content-type": "application/json", + origin: "example.com", + } + ); + req.method = "POST"; + + let hello; + onInit(() => (hello = "world")); + expect(hello).to.be.undefined; + await runHandler(func, req as any); + expect(hello).to.equal("world"); + }); }); describe("beforeOperation", () => { diff --git a/spec/v2/providers/remoteConfig.spec.ts b/spec/v2/providers/remoteConfig.spec.ts index ae29ecbc7..ce9a7da48 100644 --- a/spec/v2/providers/remoteConfig.spec.ts +++ b/spec/v2/providers/remoteConfig.spec.ts @@ -24,6 +24,8 @@ import { expect } from "chai"; import * as remoteConfig from "../../../src/v2/providers/remoteConfig"; import * as options from "../../../src/v2/options"; import { MINIMAL_V2_ENDPOINT } from "../../fixtures"; +import { CloudEvent } from "../../../lib/v2/core"; +import { onInit } from "../../../src/v2/core"; describe("onConfigUpdated", () => { afterEach(() => { @@ -43,7 +45,7 @@ describe("onConfigUpdated", () => { retry: false, }, }); - await expect(fn.run(1 as any)).to.eventually.eq(2); + await expect(fn(1 as any)).to.eventually.eq(2); }); it("should create a function with opts and a handler", async () => { @@ -72,6 +74,23 @@ describe("onConfigUpdated", () => { retry: true, }, }); - await expect(fn.run(1 as any)).to.eventually.eq(2); + await expect(fn(1 as any)).to.eventually.eq(2); + }); + + it("calls init function", async () => { + const event: CloudEvent = { + specversion: "1.0", + id: "id", + source: "source", + type: "type", + time: "now", + data: "data", + }; + + let hello; + onInit(() => (hello = "world")); + expect(hello).to.be.undefined; + await remoteConfig.onConfigUpdated(() => null)(event); + expect(hello).to.equal("world"); }); }); diff --git a/spec/v2/providers/scheduler.spec.ts b/spec/v2/providers/scheduler.spec.ts index 4f3e6f984..fcd03cf1f 100644 --- a/spec/v2/providers/scheduler.spec.ts +++ b/spec/v2/providers/scheduler.spec.ts @@ -25,6 +25,9 @@ import { ManifestEndpoint } from "../../../src/runtime/manifest"; import * as options from "../../../src/v2/options"; import * as schedule from "../../../src/v2/providers/scheduler"; import { MINIMAL_V2_ENDPOINT } from "../../fixtures"; +import { onInit } from "../../../src/v2/core"; +import { MockRequest } from "../../fixtures/mockrequest"; +import { runHandler } from "../../helper"; const MINIMAL_SCHEDULE_TRIGGER: ManifestEndpoint["scheduleTrigger"] = { schedule: "", @@ -187,5 +190,26 @@ describe("schedule", () => { foo: "newBar", }); }); + + it("calls init function", async () => { + const func = schedule.onSchedule("* * * * *", () => null); + + const req = new MockRequest( + { + data: {}, + }, + { + "content-type": "application/json", + origin: "example.com", + } + ); + req.method = "POST"; + + let hello; + onInit(() => (hello = "world")); + expect(hello).to.be.undefined; + await runHandler(func, req as any); + expect(hello).to.equal("world"); + }); }); }); diff --git a/spec/v2/providers/storage.spec.ts b/spec/v2/providers/storage.spec.ts index d5a699d70..06324e9ab 100644 --- a/spec/v2/providers/storage.spec.ts +++ b/spec/v2/providers/storage.spec.ts @@ -3,6 +3,7 @@ import * as config from "../../../src/common/config"; import * as options from "../../../src/v2/options"; import * as storage from "../../../src/v2/providers/storage"; import { FULL_ENDPOINT, MINIMAL_V2_ENDPOINT, FULL_OPTIONS, FULL_TRIGGER } from "./fixtures"; +import { CloudEvent, onInit } from "../../../src/v2/core"; const EVENT_TRIGGER = { eventType: "event-type", @@ -312,6 +313,23 @@ describe("v2/storage", () => { region: ["us-west1"], }); }); + + it("calls init function", async () => { + const event: CloudEvent = { + specversion: "1.0", + id: "id", + source: "source", + type: "type", + time: "now", + data: "data", + }; + + let hello; + onInit(() => (hello = "world")); + expect(hello).to.be.undefined; + await storage.onObjectArchived("bucket", () => null)(event); + expect(hello).to.equal("world"); + }); }); describe("onObjectFinalized", () => { @@ -429,6 +447,23 @@ describe("v2/storage", () => { region: ["us-west1"], }); }); + + it("calls init function", async () => { + const event: CloudEvent = { + specversion: "1.0", + id: "id", + source: "source", + type: "type", + time: "now", + data: "data", + }; + + let hello; + onInit(() => (hello = "world")); + expect(hello).to.be.undefined; + await storage.onObjectFinalized("bucket", () => null)(event); + expect(hello).to.equal("world"); + }); }); describe("onObjectDeleted", () => { @@ -543,6 +578,23 @@ describe("v2/storage", () => { region: ["us-west1"], }); }); + + it("calls init function", async () => { + const event: CloudEvent = { + specversion: "1.0", + id: "id", + source: "source", + type: "type", + time: "now", + data: "data", + }; + + let hello; + onInit(() => (hello = "world")); + expect(hello).to.be.undefined; + await storage.onObjectDeleted("bucket", () => null)(event); + expect(hello).to.equal("world"); + }); }); describe("onObjectMetadataUpdated", () => { @@ -661,5 +713,22 @@ describe("v2/storage", () => { region: ["us-west1"], }); }); + + it("calls init function", async () => { + const event: CloudEvent = { + specversion: "1.0", + id: "id", + source: "source", + type: "type", + time: "now", + data: "data", + }; + + let hello; + onInit(() => (hello = "world")); + expect(hello).to.be.undefined; + await storage.onObjectMetadataUpdated("bucket", () => null)(event); + expect(hello).to.equal("world"); + }); }); }); diff --git a/spec/v2/providers/tasks.spec.ts b/spec/v2/providers/tasks.spec.ts index 1597a1947..46ffd7a0a 100644 --- a/spec/v2/providers/tasks.spec.ts +++ b/spec/v2/providers/tasks.spec.ts @@ -28,6 +28,7 @@ import { onTaskDispatched, Request } from "../../../src/v2/providers/tasks"; import { MockRequest } from "../../fixtures/mockrequest"; import { runHandler } from "../../helper"; import { FULL_ENDPOINT, MINIMAL_V2_ENDPOINT, FULL_OPTIONS, FULL_TRIGGER } from "./fixtures"; +import { onInit } from "../../../src/v2/core"; const MINIMIAL_TASK_QUEUE_TRIGGER: ManifestEndpoint["taskQueueTrigger"] = { rateLimits: { @@ -299,4 +300,25 @@ describe("onTaskDispatched", () => { console.log(`Hello, ${request.data}`); }); }); + + it("calls init function", async () => { + const func = onTaskDispatched(() => null); + + const req = new MockRequest( + { + data: {}, + }, + { + "content-type": "application/json", + origin: "example.com", + } + ); + req.method = "POST"; + + let hello; + onInit(() => (hello = "world")); + expect(hello).to.be.undefined; + await runHandler(func, req as any); + expect(hello).to.equal("world"); + }); }); diff --git a/spec/v2/providers/testLab.spec.ts b/spec/v2/providers/testLab.spec.ts index 15ff77248..15d649d44 100644 --- a/spec/v2/providers/testLab.spec.ts +++ b/spec/v2/providers/testLab.spec.ts @@ -24,6 +24,7 @@ import { expect } from "chai"; import * as testLab from "../../../src/v2/providers/testLab"; import * as options from "../../../src/v2/options"; import { MINIMAL_V2_ENDPOINT } from "../../fixtures"; +import { CloudEvent, onInit } from "../../../src/v2/core"; describe("onTestMatrixCompleted", () => { afterEach(() => { @@ -74,4 +75,21 @@ describe("onTestMatrixCompleted", () => { }); expect(fn.run(1 as any)).to.eq(2); }); + + it("calls init function", async () => { + const event: CloudEvent = { + specversion: "1.0", + id: "id", + source: "source", + type: "type", + time: "now", + data: "data", + }; + + let hello; + onInit(() => (hello = "world")); + expect(hello).to.be.undefined; + await testLab.onTestMatrixCompleted(() => null)(event); + expect(hello).to.equal("world"); + }); }); diff --git a/src/common/onInit.ts b/src/common/onInit.ts index cf87ecbf4..bfd147193 100644 --- a/src/common/onInit.ts +++ b/src/common/onInit.ts @@ -10,7 +10,7 @@ let didInit = false; * Calling this function more than once leads to undefined behavior. * @param callback initialization callback to be run before any function executes. */ -export function onInit(callback: () => any) { +export function onInit(callback: () => unknown) { if (initCallback) { logger.warn( "Setting onInit callback more than once. Only the most recent callback will be called" @@ -23,7 +23,7 @@ export function onInit(callback: () => any) { type Resolved = T extends Promise ? V : T; /** @internal */ -export function withInit any>(func: T) { +export function withInit unknown>(func: T) { return async (...args: Parameters): Promise>> => { if (!didInit) { if (initCallback) { @@ -31,6 +31,10 @@ export function withInit any>(func: T) { } didInit = true; } - return func(...args); + + // Note: This cast is actually inaccurate because it may be a promise, but + // it doesn't actually matter because the async function will promisify + // non-promises and forward promises. + return func(...args) as Resolved>; }; } diff --git a/src/v2/providers/remoteConfig.ts b/src/v2/providers/remoteConfig.ts index 3018113a7..23707dea2 100644 --- a/src/v2/providers/remoteConfig.ts +++ b/src/v2/providers/remoteConfig.ts @@ -130,10 +130,12 @@ export function onConfigUpdated( const baseOpts = optionsToEndpoint(getGlobalOptions()); const specificOpts = optionsToEndpoint(optsOrHandler); - const func: any = (raw: CloudEvent) => { - return handler(raw as CloudEvent); - }; - func.run = wrapTraceContext(withInit(handler)); + const func: any = wrapTraceContext( + withInit((raw: CloudEvent) => { + return handler(raw as CloudEvent); + }) + ); + func.run = handler; const ep: ManifestEndpoint = { ...initV2Endpoint(getGlobalOptions(), optsOrHandler), From 4a1adeb5afb123cb9a09908d8318358d55327e40 Mon Sep 17 00:00:00 2001 From: Thomas Bouldin Date: Wed, 6 Mar 2024 15:54:08 -0800 Subject: [PATCH 3/5] Fix linter error --- spec/v2/providers/alerts/crashlytics.spec.ts | 2 +- spec/v2/providers/firestore.spec.ts | 1 - src/common/onInit.ts | 3 +-- src/v1/providers/https.ts | 3 ++- src/v2/core.ts | 2 +- src/v2/providers/database.ts | 4 ++-- src/v2/providers/eventarc.ts | 2 +- 7 files changed, 8 insertions(+), 9 deletions(-) diff --git a/spec/v2/providers/alerts/crashlytics.spec.ts b/spec/v2/providers/alerts/crashlytics.spec.ts index b70ed0644..496f6f10c 100644 --- a/spec/v2/providers/alerts/crashlytics.spec.ts +++ b/spec/v2/providers/alerts/crashlytics.spec.ts @@ -114,7 +114,7 @@ describe("crashlytics", () => { expect(hello).to.be.undefined; await func({ data: "crash" } as any); expect(hello).to.equal("world"); - }) + }); }); } diff --git a/spec/v2/providers/firestore.spec.ts b/spec/v2/providers/firestore.spec.ts index 4a5e40163..7e734c671 100644 --- a/spec/v2/providers/firestore.spec.ts +++ b/spec/v2/providers/firestore.spec.ts @@ -332,7 +332,6 @@ describe("firestore", () => { }, }; - let hello; onInit(() => (hello = "world")); expect(hello).to.be.undefined; diff --git a/src/common/onInit.ts b/src/common/onInit.ts index bfd147193..e1b32ca64 100644 --- a/src/common/onInit.ts +++ b/src/common/onInit.ts @@ -1,7 +1,6 @@ - import * as logger from "../logger"; -let initCallback: (() => any) | null = null; +let initCallback: (() => unknown) | null = null; let didInit = false; /** diff --git a/src/v1/providers/https.ts b/src/v1/providers/https.ts index f72d61307..e9cd5d132 100644 --- a/src/v1/providers/https.ts +++ b/src/v1/providers/https.ts @@ -116,7 +116,8 @@ export function _onCallWithOptions( cors: { origin: true, methods: "POST" }, }, fixedLen - )); + ) + ); func.__trigger = { labels: {}, diff --git a/src/v2/core.ts b/src/v2/core.ts index 60afb7f20..3d2e33748 100644 --- a/src/v2/core.ts +++ b/src/v2/core.ts @@ -117,4 +117,4 @@ export interface CloudFunction> { * @beta */ run(event: EventType): any | Promise; -} \ No newline at end of file +} diff --git a/src/v2/providers/database.ts b/src/v2/providers/database.ts index b93d9b53d..50400bdcf 100644 --- a/src/v2/providers/database.ts +++ b/src/v2/providers/database.ts @@ -464,7 +464,7 @@ export function onChangedOperation( const instancePattern = new PathPattern(instance); // wrap the handler - const func = async (raw: CloudEvent) => { + const func = (raw: CloudEvent) => { const event = raw as RawRTDBCloudEvent; const instanceUrl = getInstance(event); const params = makeParams(event, pathPattern, instancePattern) as unknown as ParamsOf; @@ -493,7 +493,7 @@ export function onOperation( const instancePattern = new PathPattern(instance); // wrap the handler - const func = async (raw: CloudEvent) => { + const func = (raw: CloudEvent) => { const event = raw as RawRTDBCloudEvent; const instanceUrl = getInstance(event); const params = makeParams(event, pathPattern, instancePattern) as unknown as ParamsOf; diff --git a/src/v2/providers/eventarc.ts b/src/v2/providers/eventarc.ts index 9f97f9a30..3ad523b6d 100644 --- a/src/v2/providers/eventarc.ts +++ b/src/v2/providers/eventarc.ts @@ -194,7 +194,7 @@ export function onCustomEventPublished( } else if (typeof eventTypeOrOpts === "object") { opts = eventTypeOrOpts; } - const func = async (raw: CloudEvent) => { + const func = (raw: CloudEvent) => { return wrapTraceContext(withInit(handler))(raw as CloudEvent); }; From fd2c4ae6fda7c4474d36fe91224191dfcd48c04d Mon Sep 17 00:00:00 2001 From: Thomas Bouldin Date: Wed, 6 Mar 2024 15:57:16 -0800 Subject: [PATCH 4/5] Fix import error --- spec/v2/providers/remoteConfig.spec.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/spec/v2/providers/remoteConfig.spec.ts b/spec/v2/providers/remoteConfig.spec.ts index ce9a7da48..3b32ed111 100644 --- a/spec/v2/providers/remoteConfig.spec.ts +++ b/spec/v2/providers/remoteConfig.spec.ts @@ -24,8 +24,7 @@ import { expect } from "chai"; import * as remoteConfig from "../../../src/v2/providers/remoteConfig"; import * as options from "../../../src/v2/options"; import { MINIMAL_V2_ENDPOINT } from "../../fixtures"; -import { CloudEvent } from "../../../lib/v2/core"; -import { onInit } from "../../../src/v2/core"; +import { CloudEvent, onInit } from "../../../src/v2/core"; describe("onConfigUpdated", () => { afterEach(() => { From a622deddb844533eca0737ab7086ca34b935854b Mon Sep 17 00:00:00 2001 From: Thomas Bouldin Date: Wed, 6 Mar 2024 16:02:36 -0800 Subject: [PATCH 5/5] Add changelog; fix exports of v2/core --- CHANGELOG.md | 1 + package.json | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb..129f480b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +Add onInit callback function for global variable initialization (#1531) diff --git a/package.json b/package.json index 5fa70a2a5..771cc0aea 100644 --- a/package.json +++ b/package.json @@ -110,6 +110,9 @@ "v2": [ "lib/v2" ], + "v2/core": [ + "lib/v2/core" + ], "v2/alerts": [ "lib/v2/providers/alerts" ], @@ -250,4 +253,4 @@ "engines": { "node": ">=14.10.0" } -} +} \ No newline at end of file