From b903593b0854aeaf79109ed497afc78dab3e2ed6 Mon Sep 17 00:00:00 2001 From: Thomas Bouldin Date: Fri, 17 Jun 2022 16:57:02 -0700 Subject: [PATCH 1/8] Remove __trigger --- spec/v1/cloud-functions.spec.ts | 121 +++++++- spec/v1/function-builder.spec.ts | 83 +++--- spec/v1/providers/analytics.spec.ts | 99 +++++-- spec/v1/providers/auth.spec.ts | 157 ++++++++-- spec/v1/providers/database.spec.ts | 282 +++++++++++++++--- spec/v1/providers/firestore.spec.ts | 194 +++++++++++-- spec/v1/providers/https.spec.ts | 49 +++- spec/v1/providers/pubsub.spec.ts | 195 ++++++++++++- spec/v1/providers/remoteConfig.spec.ts | 62 +++- spec/v1/providers/storage.spec.ts | 326 +++++++++++++++++++-- spec/v1/providers/tasks.spec.ts | 42 ++- spec/v1/providers/testLab.spec.ts | 28 +- spec/v2/providers/fixtures.ts | 25 +- spec/v2/providers/https.spec.ts | 87 +++++- spec/v2/providers/pubsub.spec.ts | 49 +++- spec/v2/providers/storage.spec.ts | 269 ++++++++++++++++-- spec/v2/providers/tasks.spec.ts | 54 +++- src/v1/cloud-functions.ts | 229 ++++++++++++++- src/v1/handler-builder.ts | 379 +++++++++++++++++++++++++ src/v1/providers/auth.ts | 18 +- src/v1/providers/https.ts | 18 +- src/v1/providers/tasks.ts | 19 +- src/v2/core.ts | 33 ++- src/v2/options.ts | 76 ++++- src/v2/providers/https.ts | 33 ++- src/v2/providers/tasks.ts | 35 ++- 26 files changed, 2679 insertions(+), 283 deletions(-) diff --git a/spec/v1/cloud-functions.spec.ts b/spec/v1/cloud-functions.spec.ts index f68269030..8033ebaad 100644 --- a/spec/v1/cloud-functions.spec.ts +++ b/spec/v1/cloud-functions.spec.ts @@ -23,6 +23,7 @@ import { expect } from "chai"; import { + Change, Event, EventContext, makeCloudFunction, @@ -41,7 +42,7 @@ describe("makeCloudFunction", () => { legacyEventType: "providers/provider/eventTypes/event", }; - it("should put a __endpoint on the returned CloudFunction", () => { + it('should put a __trigger/__endpoint on the returned CloudFunction', () => { const cf = makeCloudFunction({ provider: "mock.provider", eventType: "mock.event", @@ -50,6 +51,14 @@ describe("makeCloudFunction", () => { handler: () => null, }); + expect(cf.__trigger).to.deep.equal({ + eventTrigger: { + eventType: 'mock.provider.mock.event', + resource: 'resource', + service: 'service', + }, + }); + expect(cf.__endpoint).to.deep.equal({ ...MINIMAL_V1_ENDPOINT, platform: "gcfv1", @@ -64,9 +73,17 @@ describe("makeCloudFunction", () => { }); }); - it("should have legacy event type in __endpoint if provided", () => { + it('should have legacy event type in __trigger/__endpoint if provided', () => { const cf = makeCloudFunction(cloudFunctionArgs); + expect(cf.__trigger).to.deep.equal({ + eventTrigger: { + eventType: 'providers/provider/eventTypes/event', + resource: 'resource', + service: 'service', + }, + }); + expect(cf.__endpoint).to.deep.equal({ ...MINIMAL_V1_ENDPOINT, platform: "gcfv1", @@ -339,10 +356,106 @@ describe("makeAuth and makeAuthType", () => { auth: { uid: "user", token: { - sub: "user", + sub: 'user', }, }, - authType: "USER", + authType: 'USER', + }); + }); +}); + +describe('Change', () => { + describe('applyFieldMask', () => { + const after = { + foo: 'bar', + num: 2, + obj: { + a: 1, + b: 2, + }, + }; + + it('should handle deleted values', () => { + const sparseBefore = { baz: 'qux' }; + const fieldMask = 'baz'; + expect( + Change.applyFieldMask(sparseBefore, after, fieldMask) + ).to.deep.equal({ + foo: 'bar', + num: 2, + obj: { + a: 1, + b: 2, + }, + baz: 'qux', + }); + }); + + it('should handle created values', () => { + const sparseBefore = {}; + const fieldMask = 'num,obj.a'; + expect( + Change.applyFieldMask(sparseBefore, after, fieldMask) + ).to.deep.equal({ + foo: 'bar', + obj: { + b: 2, + }, + }); + }); + + it('should handle mutated values', () => { + const sparseBefore = { + num: 3, + obj: { + a: 3, + }, + }; + const fieldMask = 'num,obj.a'; + expect( + Change.applyFieldMask(sparseBefore, after, fieldMask) + ).to.deep.equal({ + foo: 'bar', + num: 3, + obj: { + a: 3, + b: 2, + }, + }); + }); + }); + + describe('fromJSON', () => { + it('should create a Change object with a `before` and `after`', () => { + const created = Change.fromJSON({ + before: { foo: 'bar' }, + after: { foo: 'faz' }, + }); + expect(created instanceof Change).to.equal(true); + expect(created.before).to.deep.equal({ foo: 'bar' }); + expect(created.after).to.deep.equal({ foo: 'faz' }); + }); + + it('should apply the customizer function to `before` and `after`', () => { + function customizer(input: any) { + _.set(input, 'another', 'value'); + return input as T; + } + const created = Change.fromJSON( + { + before: { foo: 'bar' }, + after: { foo: 'faz' }, + }, + customizer + ); + expect(created.before).to.deep.equal({ + foo: 'bar', + another: 'value', + }); + expect(created.after).to.deep.equal({ + foo: 'faz', + another: 'value', + }); }); }); }); diff --git a/spec/v1/function-builder.spec.ts b/spec/v1/function-builder.spec.ts index da041e8f5..4ea9bb760 100644 --- a/spec/v1/function-builder.spec.ts +++ b/spec/v1/function-builder.spec.ts @@ -41,7 +41,7 @@ describe("FunctionBuilder", () => { .auth.user() .onCreate((user) => user); - expect(fn.__endpoint.region).to.deep.equal(["us-east1"]); + expect(fn.__trigger.regions).to.deep.equal(['us-east1']); }); it("should allow multiple supported regions to be set", () => { @@ -50,7 +50,7 @@ describe("FunctionBuilder", () => { .auth.user() .onCreate((user) => user); - expect(fn.__endpoint.region).to.deep.equal(["us-east1", "us-central1"]); + expect(fn.__trigger.regions).to.deep.equal(['us-east1', 'us-central1']); }); it("should allow all supported regions to be set", () => { @@ -68,15 +68,15 @@ describe("FunctionBuilder", () => { .auth.user() .onCreate((user) => user); - expect(fn.__endpoint.region).to.deep.equal([ - "us-central1", - "us-east1", - "us-east4", - "europe-west1", - "europe-west2", - "europe-west3", - "asia-east2", - "asia-northeast1", + expect(fn.__trigger.regions).to.deep.equal([ + 'us-central1', + 'us-east1', + 'us-east4', + 'europe-west1', + 'europe-west2', + 'europe-west3', + 'asia-east2', + 'asia-northeast1', ]); }); @@ -104,13 +104,9 @@ describe("FunctionBuilder", () => { .auth.user() .onCreate((user) => user); - expect(fn.__endpoint.secretEnvironmentVariables).to.deep.equal([ - { - key: "API_KEY", - }, - ]); - - clearParams(); + expect(fn.__trigger.availableMemoryMb).to.deep.equal(256); + expect(fn.__trigger.timeout).to.deep.equal('90s'); + expect(fn.__trigger.failurePolicy).to.deep.equal({ retry: {} }); }); it("should apply a default failure policy if it's aliased with `true`", () => { @@ -136,9 +132,9 @@ describe("FunctionBuilder", () => { .auth.user() .onCreate((user) => user); - expect(fn.__endpoint.region).to.deep.equal(["europe-west2"]); - expect(fn.__endpoint.availableMemoryMb).to.deep.equal(256); - expect(fn.__endpoint.timeoutSeconds).to.deep.equal(90); + expect(fn.__trigger.regions).to.deep.equal(['europe-west2']); + expect(fn.__trigger.availableMemoryMb).to.deep.equal(256); + expect(fn.__trigger.timeout).to.deep.equal('90s'); }); it("should allow both valid runtime options and supported region to be set in reverse order", () => { @@ -151,9 +147,9 @@ describe("FunctionBuilder", () => { .auth.user() .onCreate((user) => user); - expect(fn.__endpoint.region).to.deep.equal(["europe-west1"]); - expect(fn.__endpoint.availableMemoryMb).to.deep.equal(256); - expect(fn.__endpoint.timeoutSeconds).to.deep.equal(90); + expect(fn.__trigger.regions).to.deep.equal(['europe-west1']); + expect(fn.__trigger.availableMemoryMb).to.deep.equal(256); + expect(fn.__trigger.timeout).to.deep.equal('90s'); }); it("should fail if supported region but invalid runtime options are set (reverse order)", () => { @@ -223,7 +219,7 @@ describe("FunctionBuilder", () => { .runWith({ ingressSettings: "ALLOW_INTERNAL_ONLY" }) .https.onRequest(() => undefined); - expect(fn.__endpoint.ingressSettings).to.equal("ALLOW_INTERNAL_ONLY"); + expect(fn.__trigger.ingressSettings).to.equal('ALLOW_INTERNAL_ONLY'); }); it("should throw an error if user chooses an invalid ingressSettings", () => { @@ -245,11 +241,7 @@ describe("FunctionBuilder", () => { .auth.user() .onCreate((user) => user); - if (!(fn.__endpoint.vpc instanceof ResetValue)) { - expect(fn.__endpoint.vpc.connector).to.equal("test-connector"); - } else { - expect.fail("__endpoint.vpc unexpectedly set to RESET_VALUE"); - } + expect(fn.__trigger.vpcConnector).to.equal('test-connector'); }); it("should allow a vpcConnectorEgressSettings to be set", () => { @@ -261,11 +253,9 @@ describe("FunctionBuilder", () => { .auth.user() .onCreate((user) => user); - if (!(fn.__endpoint.vpc instanceof ResetValue)) { - expect(fn.__endpoint.vpc.egressSettings).to.equal("PRIVATE_RANGES_ONLY"); - } else { - expect.fail("__endpoint.vpc unexpectedly set to RESET_VALUE"); - } + expect(fn.__trigger.vpcConnectorEgressSettings).to.equal( + 'PRIVATE_RANGES_ONLY' + ); }); it("should throw an error if user chooses an invalid vpcConnectorEgressSettings", () => { @@ -294,8 +284,9 @@ describe("FunctionBuilder", () => { expect(fn.__endpoint.serviceAccountEmail).to.equal(serviceAccount); }); - it("should allow a serviceAccount to be set with generated service account email", () => { - const serviceAccount = "test-service-account@"; + it('should allow a serviceAccount to be set with generated service account email', () => { + const serviceAccount = 'test-service-account@'; + const projectId = process.env.GCLOUD_PROJECT; const fn = functions .runWith({ serviceAccount, @@ -303,7 +294,9 @@ describe("FunctionBuilder", () => { .auth.user() .onCreate((user) => user); - expect(fn.__endpoint.serviceAccountEmail).to.equal(`test-service-account@`); + expect(fn.__trigger.serviceAccountEmail).to.equal( + `test-service-account@${projectId}.iam.gserviceaccount.com` + ); }); it("should set a null serviceAccountEmail if service account is set to `default`", () => { @@ -315,7 +308,7 @@ describe("FunctionBuilder", () => { .auth.user() .onCreate((user) => user); - expect(fn.__endpoint.serviceAccountEmail).to.equal("default"); + expect(fn.__trigger.serviceAccountEmail).to.be.null; }); it("should throw an error if serviceAccount is set to an invalid value", () => { @@ -349,8 +342,8 @@ describe("FunctionBuilder", () => { .auth.user() .onCreate((user) => user); - expect(fn.__endpoint.labels).to.deep.equal({ - "valid-key": "valid-value", + expect(fn.__trigger.labels).to.deep.equal({ + 'valid-key': 'valid-value', }); }); @@ -504,11 +497,7 @@ describe("FunctionBuilder", () => { .auth.user() .onCreate((user) => user); - expect(fn.__endpoint.secretEnvironmentVariables).to.deep.equal([ - { - key: "API_KEY", - }, - ]); + expect(fn.__trigger.secrets).to.deep.equal(secrets); }); it("should throw error given secrets expressed with full resource name", () => { @@ -519,7 +508,9 @@ describe("FunctionBuilder", () => { secrets: ["projects/my-project/secrets/API_KEY"], }) ).to.throw(); + }); + it('should throw error given invalid secret config', () => { expect(() => functions.runWith({ secrets: [sp], diff --git a/spec/v1/providers/analytics.spec.ts b/spec/v1/providers/analytics.spec.ts index d7172360a..2fb04b7f4 100644 --- a/spec/v1/providers/analytics.spec.ts +++ b/spec/v1/providers/analytics.spec.ts @@ -23,9 +23,9 @@ import { expect } from "chai"; import * as functions from "../../../src/v1"; -import { Event } from "../../../src/v1/cloud-functions"; +import { Event, EventContext } from '../../../src/v1/cloud-functions'; import * as analytics from "../../../src/v1/providers/analytics"; -import * as analyticsSpecInput from "./analytics.spec.input"; +import * as analytics_spec_input from './analytics.spec.input'; import { MINIMAL_V1_ENDPOINT } from "../../fixtures"; describe("Analytics Functions", () => { @@ -48,14 +48,27 @@ describe("Analytics Functions", () => { .analytics.event("event") .onLog((event) => event); - expect(fn.__endpoint.region).to.deep.equal(["us-east1"]); + expect(fn.__trigger.regions).to.deep.equal(['us-east1']); + expect(fn.__trigger.availableMemoryMb).to.deep.equal(256); + expect(fn.__trigger.timeout).to.deep.equal('90s'); + + expect(fn.__endpoint.region).to.deep.equal(['us-east1']); expect(fn.__endpoint.availableMemoryMb).to.deep.equal(256); expect(fn.__endpoint.timeoutSeconds).to.deep.equal(90); }); - describe("#onLog", () => { - it("should return a trigger/endpoint with appropriate values", () => { - const cloudFunction = analytics.event("first_open").onLog(() => null); + describe('#onLog', () => { + it('should return a trigger/endpoint with appropriate values', () => { + const cloudFunction = analytics.event('first_open').onLog(() => null); + + expect(cloudFunction.__trigger).to.deep.equal({ + eventTrigger: { + eventType: + 'providers/google.firebase.analytics/eventTypes/event.log', + resource: 'projects/project1/events/first_open', + service: 'app-measurement.com', + }, + }); expect(cloudFunction.__endpoint).to.deep.equal({ ...MINIMAL_V1_ENDPOINT, @@ -282,27 +295,79 @@ describe("Analytics Functions", () => { .event("first_open") .onLog((data: analytics.AnalyticsEvent) => data); // The payload in analytics_spec_input contains all possible fields at least once. - const payloadData = analyticsSpecInput.fullPayload.data; - const payloadContext = analyticsSpecInput.fullPayload.context; + const payloadData = analytics_spec_input.fullPayload.data; + const payloadContext = analytics_spec_input.fullPayload.context; + + return expect( + cloudFunction(payloadData, payloadContext) + ).to.eventually.deep.equal(analytics_spec_input.data); + }); + }); + }); + + describe('handler namespace', () => { + describe('#onLog', () => { + it('should return an empty trigger/endpoint', () => { + const cloudFunction = functions.handler.analytics.event.onLog( + () => null + ); + expect(cloudFunction.__trigger).to.deep.equal({}); + expect(cloudFunction.__endpoint).to.be.undefined; + }); - return expect(cloudFunction(payloadData, payloadContext)).to.eventually.deep.equal( - analyticsSpecInput.data + it('should handle an event with the appropriate fields', () => { + const cloudFunction = functions.handler.analytics.event.onLog( + (data: analytics.AnalyticsEvent, context: EventContext) => data ); + + // The event data delivered over the wire will be the JSON for an AnalyticsEvent: + // https://firebase.google.com/docs/auth/admin/manage-users#retrieve_user_data + const event: Event = { + data: { + userDim: { + userId: 'hi!', + }, + }, + context: { + eventId: '70172329041928', + timestamp: '2018-04-09T07:56:12.975Z', + eventType: + 'providers/google.firebase.analytics/eventTypes/event.log', + resource: { + service: 'app-measurement.com', + name: 'projects/project1/events/first_open', + }, + }, + }; + + return expect( + cloudFunction(event.data, event.context) + ).to.eventually.deep.equal({ + params: {}, + user: { + userId: 'hi!', + userProperties: {}, + }, + }); }); }); }); - describe("process.env.GCLOUD_PROJECT not set", () => { - it("should not throw if __endpoint is not accessed", () => { - expect(() => analytics.event("event").onLog(() => null)).to.not.throw(Error); + describe('process.env.GCLOUD_PROJECT not set', () => { + it('should not throw if __trigger is not accessed', () => { + expect(() => analytics.event('event').onLog(() => null)).to.not.throw( + Error + ); }); - it("should throw when __endpoint is accessed", () => { - expect(() => analytics.event("event").onLog(() => null).__endpoint).to.throw(Error); + it('should throw when trigger is accessed', () => { + expect( + () => analytics.event('event').onLog(() => null).__trigger + ).to.throw(Error); }); - it("should not throw when #run is called", () => { - const cf = analytics.event("event").onLog(() => null); + it('should not throw when #run is called', () => { + const cf = analytics.event('event').onLog(() => null); expect(cf.run).to.not.throw(Error); }); diff --git a/spec/v1/providers/auth.spec.ts b/spec/v1/providers/auth.spec.ts index b9604c37a..7e8d10502 100644 --- a/spec/v1/providers/auth.spec.ts +++ b/spec/v1/providers/auth.spec.ts @@ -46,7 +46,17 @@ describe("Auth Functions", () => { }, }; - describe("AuthBuilder", () => { + describe('AuthBuilder', () => { + function expectedTrigger(project: string, eventType: string) { + return { + eventTrigger: { + resource: `projects/${project}`, + eventType: `providers/firebase.auth/eventTypes/${eventType}`, + service: 'firebaseauth.googleapis.com', + }, + }; + } + function expectedEndpoint(project: string, eventType: string) { return { ...MINIMAL_V1_ENDPOINT, @@ -86,9 +96,9 @@ describe("Auth Functions", () => { .auth.user() .onCreate(() => null); - expect(fn.__endpoint.region).to.deep.equal(["us-east1"]); - expect(fn.__endpoint.availableMemoryMb).to.deep.equal(256); - expect(fn.__endpoint.timeoutSeconds).to.deep.equal(90); + expect(fn.__trigger.regions).to.deep.equal(['us-east1']); + expect(fn.__trigger.availableMemoryMb).to.deep.equal(256); + expect(fn.__trigger.timeout).to.deep.equal('90s'); expect(fn.__endpoint.region).to.deep.equal(["us-east1"]); expect(fn.__endpoint.availableMemoryMb).to.deep.equal(256); @@ -99,7 +109,13 @@ describe("Auth Functions", () => { it("should return a trigger/endpoint with appropriate values", () => { const cloudFunction = auth.user().onCreate(() => null); - expect(cloudFunction.__endpoint).to.deep.equal(expectedEndpoint(project, "user.create")); + expect(cloudFunction.__trigger).to.deep.equal( + expectedTrigger(project, 'user.create') + ); + + expect(cloudFunction.__endpoint).to.deep.equal( + expectedEndpoint(project, 'user.create') + ); }); }); @@ -107,7 +123,13 @@ describe("Auth Functions", () => { it("should return a trigger/endpoint with appropriate values", () => { const cloudFunction = auth.user().onDelete(handler); - expect(cloudFunction.__endpoint).to.deep.equal(expectedEndpoint(project, "user.delete")); + expect(cloudFunction.__trigger).to.deep.equal( + expectedTrigger(project, 'user.delete') + ); + + expect(cloudFunction.__endpoint).to.deep.equal( + expectedEndpoint(project, 'user.delete') + ); }); }); @@ -115,6 +137,17 @@ describe("Auth Functions", () => { it("should create the function without options", () => { const fn = auth.user().beforeCreate(() => Promise.resolve()); + expect(fn.__trigger).to.deep.equal({ + labels: {}, + blockingTrigger: { + eventType: 'providers/cloud.auth/eventTypes/user.beforeCreate', + options: { + accessToken: false, + idToken: false, + refreshToken: false, + }, + }, + }); expect(fn.__endpoint).to.deep.equal({ ...MINIMAL_V1_ENDPOINT, platform: "gcfv1", @@ -149,8 +182,22 @@ describe("Auth Functions", () => { refreshToken: false, }, }) - .beforeCreate(() => Promise.resolve()); + .beforeCreate((u, c) => Promise.resolve()); + expect(fn.__trigger).to.deep.equal({ + labels: {}, + regions: ['us-east1'], + availableMemoryMb: 256, + timeout: '90s', + blockingTrigger: { + eventType: 'providers/cloud.auth/eventTypes/user.beforeCreate', + options: { + accessToken: true, + idToken: false, + refreshToken: false, + }, + }, + }); expect(fn.__endpoint).to.deep.equal({ ...MINIMAL_V1_ENDPOINT, platform: "gcfv1", @@ -180,6 +227,17 @@ describe("Auth Functions", () => { it("should create the function without options", () => { const fn = auth.user().beforeSignIn(() => Promise.resolve()); + expect(fn.__trigger).to.deep.equal({ + labels: {}, + blockingTrigger: { + eventType: 'providers/cloud.auth/eventTypes/user.beforeSignIn', + options: { + accessToken: false, + idToken: false, + refreshToken: false, + }, + }, + }); expect(fn.__endpoint).to.deep.equal({ ...MINIMAL_V1_ENDPOINT, platform: "gcfv1", @@ -214,8 +272,22 @@ describe("Auth Functions", () => { refreshToken: false, }, }) - .beforeSignIn(() => Promise.resolve()); + .beforeSignIn((u, c) => Promise.resolve()); + expect(fn.__trigger).to.deep.equal({ + labels: {}, + regions: ['us-east1'], + availableMemoryMb: 256, + timeout: '90s', + blockingTrigger: { + eventType: 'providers/cloud.auth/eventTypes/user.beforeSignIn', + options: { + accessToken: true, + idToken: false, + refreshToken: false, + }, + }, + }); expect(fn.__endpoint).to.deep.equal({ ...MINIMAL_V1_ENDPOINT, platform: "gcfv1", @@ -248,21 +320,74 @@ describe("Auth Functions", () => { cloudFunctionDelete = auth.user().onDelete((data: UserRecord) => data); }); - it("should handle wire format as of v5.0.0 of firebase-admin", () => { - return cloudFunctionDelete(event.data, event.context).then((data: any) => { - expect(data.metadata.creationTime).to.equal("2016-12-15T19:37:37.059Z"); - expect(data.metadata.lastSignInTime).to.equal("2017-01-01T00:00:00.000Z"); - }); + it('should handle wire format as of v5.0.0 of firebase-admin', () => { + return cloudFunctionDelete(event.data, event.context).then( + (data: any) => { + expect(data.metadata.creationTime).to.equal( + '2016-12-15T19:37:37.059Z' + ); + expect(data.metadata.lastSignInTime).to.equal( + '2017-01-01T00:00:00.000Z' + ); + } + ); }); }); }); - describe("process.env.GCLOUD_PROJECT not set", () => { - it("should not throw if __endpoint is not accessed", () => { + describe('handler namespace', () => { + describe('#onCreate', () => { + it('should return an empty trigger', () => { + const cloudFunction = functions.handler.auth.user.onCreate(() => null); + expect(cloudFunction.__trigger).to.deep.equal({}); + }); + + it('should return an empty endpoint', () => { + const cloudFunction = functions.handler.auth.user.onCreate(() => null); + expect(cloudFunction.__endpoint).to.be.undefined; + }); + }); + + describe('#onDelete', () => { + const cloudFunctionDelete: CloudFunction = functions.handler.auth.user.onDelete( + (data: UserRecord) => data + ); + + it('should return an empty trigger', () => { + const cloudFunction = functions.handler.auth.user.onDelete(() => null); + expect(cloudFunction.__trigger).to.deep.equal({}); + }); + + it('should return an empty endpoint', () => { + const cloudFunction = functions.handler.auth.user.onDelete(() => null); + expect(cloudFunction.__endpoint).to.be.undefined; + }); + + it('should handle wire format as of v5.0.0 of firebase-admin', () => { + return cloudFunctionDelete(event.data, event.context).then( + (data: any) => { + expect(data.metadata.creationTime).to.equal( + '2016-12-15T19:37:37.059Z' + ); + expect(data.metadata.lastSignInTime).to.equal( + '2017-01-01T00:00:00.000Z' + ); + } + ); + }); + }); + }); + + describe('process.env.GCLOUD_PROJECT not set', () => { + it('should not throw if __trigger is not accessed', () => { expect(() => auth.user().onCreate(() => null)).to.not.throw(Error); }); - it("should throw when endpoint is accessed", () => { + it('should throw when trigger is accessed', () => { + expect(() => auth.user().onCreate(() => null).__trigger).to.throw(Error); + }); + + it('should throw when endpoint is accessed', () => { expect(() => auth.user().onCreate(() => null).__endpoint).to.throw(Error); }); diff --git a/spec/v1/providers/database.spec.ts b/spec/v1/providers/database.spec.ts index 2d1368d59..3129f4243 100644 --- a/spec/v1/providers/database.spec.ts +++ b/spec/v1/providers/database.spec.ts @@ -33,6 +33,16 @@ describe("Database Functions", () => { describe("DatabaseBuilder", () => { // TODO add tests for building a data or change based on the type of operation + function expectedTrigger(resource: string, eventType: string) { + return { + eventTrigger: { + resource, + eventType: `providers/google.firebase.database/eventTypes/${eventType}`, + service: 'firebaseio.com', + }, + }; + } + function expectedEndpoint(resource: string, eventType: string) { return { ...MINIMAL_V1_ENDPOINT, @@ -52,6 +62,7 @@ describe("Database Functions", () => { config.resetCache({ databaseURL: "https://subdomain.apse.firebasedatabase.app", }); + appsNamespace.init(); }); after(() => { @@ -69,18 +80,24 @@ describe("Database Functions", () => { .database.ref("/") .onCreate((snap) => snap); - expect(fn.__endpoint.region).to.deep.equal(["us-east1"]); - expect(fn.__endpoint.availableMemoryMb).to.deep.equal(256); - expect(fn.__endpoint.timeoutSeconds).to.deep.equal(90); + expect(fn.__trigger.regions).to.deep.equal(['us-east1']); + expect(fn.__trigger.availableMemoryMb).to.deep.equal(256); + expect(fn.__trigger.timeout).to.deep.equal('90s'); - expect(fn.__endpoint.region).to.deep.equal(["us-east1"]); + expect(fn.__endpoint.region).to.deep.equal(['us-east1']); expect(fn.__endpoint.availableMemoryMb).to.deep.equal(256); expect(fn.__endpoint.timeoutSeconds).to.deep.equal(90); - }); - describe("#onWrite()", () => { - it("should return a endpoint with appropriate values", () => { - const func = database.ref("foo").onWrite(() => null); + describe('#onWrite()', () => { + it('should return a trigger/endpoint with appropriate values', () => { + const func = database.ref('foo').onWrite(() => null); + + expect(func.__trigger).to.deep.equal( + expectedTrigger( + 'projects/_/instances/subdomain/refs/foo', + 'ref.write' + ) + ); expect(func.__endpoint).to.deep.equal( expectedEndpoint("projects/_/instances/subdomain/refs/foo", "ref.write") @@ -93,6 +110,10 @@ describe("Database Functions", () => { .ref("foo") .onWrite(() => null); + expect(func.__trigger).to.deep.equal( + expectedTrigger('projects/_/instances/custom/refs/foo', 'ref.write') + ); + expect(func.__endpoint).to.deep.equal( expectedEndpoint("projects/_/instances/custom/refs/foo", "ref.write") ); @@ -131,9 +152,16 @@ describe("Database Functions", () => { }); }); - describe("#onCreate()", () => { - it("should return a trigger/endpoint with appropriate values", () => { - const func = database.ref("foo").onCreate(() => null); + describe('#onCreate()', () => { + it('should return a trigger/endpoint with appropriate values', () => { + const func = database.ref('foo').onCreate(() => null); + + expect(func.__trigger).to.deep.equal( + expectedTrigger( + 'projects/_/instances/subdomain/refs/foo', + 'ref.create' + ) + ); expect(func.__endpoint).to.deep.equal( expectedEndpoint("projects/_/instances/subdomain/refs/foo", "ref.create") @@ -146,6 +174,10 @@ describe("Database Functions", () => { .ref("foo") .onCreate(() => null); + expect(func.__trigger).to.deep.equal( + expectedTrigger('projects/_/instances/custom/refs/foo', 'ref.create') + ); + expect(func.__endpoint).to.deep.equal( expectedEndpoint("projects/_/instances/custom/refs/foo", "ref.create") ); @@ -185,9 +217,16 @@ describe("Database Functions", () => { }); }); - describe("#onUpdate()", () => { - it("should return a trigger/endpoint with appropriate values", () => { - const func = database.ref("foo").onUpdate(() => null); + describe('#onUpdate()', () => { + it('should return a trigger/endpoint with appropriate values', () => { + const func = database.ref('foo').onUpdate(() => null); + + expect(func.__trigger).to.deep.equal( + expectedTrigger( + 'projects/_/instances/subdomain/refs/foo', + 'ref.update' + ) + ); expect(func.__endpoint).to.deep.equal( expectedEndpoint("projects/_/instances/subdomain/refs/foo", "ref.update") @@ -200,6 +239,10 @@ describe("Database Functions", () => { .ref("foo") .onUpdate(() => null); + expect(func.__trigger).to.deep.equal( + expectedTrigger('projects/_/instances/custom/refs/foo', 'ref.update') + ); + expect(func.__endpoint).to.deep.equal( expectedEndpoint("projects/_/instances/custom/refs/foo", "ref.update") ); @@ -239,9 +282,16 @@ describe("Database Functions", () => { }); }); - describe("#onDelete()", () => { - it("should return a trigger/endpoint with appropriate values", () => { - const func = database.ref("foo").onDelete(() => null); + describe('#onDelete()', () => { + it('should return a trigger/endpoint with appropriate values', () => { + const func = database.ref('foo').onDelete(() => null); + + expect(func.__trigger).to.deep.equal( + expectedTrigger( + 'projects/_/instances/subdomain/refs/foo', + 'ref.delete' + ) + ); expect(func.__endpoint).to.deep.equal( expectedEndpoint("projects/_/instances/subdomain/refs/foo", "ref.delete") @@ -254,15 +304,179 @@ describe("Database Functions", () => { .ref("foo") .onDelete(() => null); + expect(func.__trigger).to.deep.equal( + expectedTrigger('projects/_/instances/custom/refs/foo', 'ref.delete') + ); + expect(func.__endpoint).to.deep.equal( expectedEndpoint("projects/_/instances/custom/refs/foo", "ref.delete") ); }); - it("should return a handler that emits events with a proper DataSnapshot", () => { + it('should return a handler that emits events with a proper DataSnapshot', () => { const event = { data: { - data: { foo: "bar" }, + data: { foo: 'bar' }, + delta: null, + }, + context: { + eventId: '70172329041928', + eventType: + 'providers/google.firebase.database/eventTypes/ref.delete', + timestamp: '2018-04-09T07:56:12.975Z', + resource: 'projects/_/instances/subdomains/refs/users', + }, + }; + + const handler = database + .ref('/users/{id}') + .onDelete((data, context) => { + expect(data.val()).to.deep.equal({ foo: 'bar' }); + }); + + return handler(event.data, event.context); + }); + }); + }); + + describe('handler namespace', () => { + describe('#onWrite()', () => { + it('correctly sets trigger to {}', () => { + const cf = functions.handler.database.ref.onWrite(() => null); + expect(cf.__trigger).to.deep.equal({}); + expect(cf.__endpoint).to.be.undefined; + }); + + it('should be able to use the instance entry point', () => { + const func = functions.handler.database.instance.ref.onWrite( + () => null + ); + expect(func.__trigger).to.deep.equal({}); + expect(func.__endpoint).to.be.undefined; + }); + + it('should return a handler that emits events with a proper DataSnapshot', () => { + const event = { + data: { + data: null, + delta: { foo: 'bar' }, + }, + context: { + eventId: '70172329041928', + eventType: + 'providers/google.firebase.database/eventTypes/ref.write', + timestamp: '2018-04-09T07:56:12.975Z', + resource: 'projects/_/instances/subdomains/refs/users', + }, + }; + + const handler = functions.handler.database.ref.onWrite( + (change, context) => { + return expect(change.after.val()).to.deep.equal({ foo: 'bar' }); + } + ); + + return handler(event.data, event.context); + }); + }); + + describe('#onCreate()', () => { + it('correctly sets trigger to {}', () => { + const cf = functions.handler.database.ref.onCreate(() => null); + expect(cf.__trigger).to.deep.equal({}); + expect(cf.__endpoint).to.be.undefined; + }); + + it('should be able to use the instance entry point', () => { + const func = functions.handler.database.instance.ref.onCreate( + () => null + ); + expect(func.__trigger).to.deep.equal({}); + expect(func.__endpoint).to.be.undefined; + }); + + it('should return a handler that emits events with a proper DataSnapshot', () => { + const event = { + data: { + data: null, + delta: { foo: 'bar' }, + }, + context: { + eventId: '70172329041928', + eventType: + 'providers/google.firebase.database/eventTypes/ref.create', + timestamp: '2018-04-09T07:56:12.975Z', + resource: 'projects/_/instances/subdomains/refs/users', + }, + }; + const handler = functions.handler.database.ref.onCreate( + (data, context) => { + return expect(data.val()).to.deep.equal({ foo: 'bar' }); + } + ); + + return handler(event.data, event.context); + }); + }); + + describe('#onUpdate()', () => { + it('correctly sets trigger to {}', () => { + const cf = functions.handler.database.ref.onUpdate(() => null); + expect(cf.__trigger).to.deep.equal({}); + expect(cf.__endpoint).to.be.undefined; + }); + + it('should be able to use the instance entry point', () => { + const func = functions.handler.database.instance.ref.onUpdate( + () => null + ); + expect(func.__trigger).to.deep.equal({}); + expect(func.__endpoint).to.be.undefined; + }); + + it('should return a handler that emits events with a proper DataSnapshot', () => { + const event = { + data: { + data: null, + delta: { foo: 'bar' }, + }, + context: { + eventId: '70172329041928', + eventType: + 'providers/google.firebase.database/eventTypes/ref.update', + timestamp: '2018-04-09T07:56:12.975Z', + resource: 'projects/_/instances/subdomains/refs/users', + }, + }; + const handler = functions.handler.database.ref.onUpdate( + (change, context) => { + return expect(change.after.val()).to.deep.equal({ foo: 'bar' }); + } + ); + + return handler(event.data, event.context); + }); + }); + + describe('#onDelete()', () => { + it('correctly sets trigger to {}', () => { + const cf = functions.handler.database.ref.onDelete(() => null); + expect(cf.__trigger).to.deep.equal({}); + expect(cf.__endpoint).to.be.undefined; + }); + + it('should be able to use the instance entry point', () => { + const func = functions.handler.database.instance.ref.onDelete( + () => null + ); + expect(func.__trigger).to.deep.equal({}); + expect(func.__endpoint).to.be.undefined; + }); + + it('should return a handler that emits events with a proper DataSnapshot', () => { + const event = { + data: { + data: { foo: 'bar' }, delta: null, }, context: { @@ -280,27 +494,26 @@ describe("Database Functions", () => { return handler(event.data, event.context); }); }); + }); - it("Should have params of the correct type", () => { - database.ref("foo").onDelete((event, context) => { - expectType>(context.params); - }); - database.ref("foo/{bar}").onDelete((event, context) => { - expectType<{ bar: string }>(context.params); - }); - database.ref("foo/{bar}/{baz}").onDelete((event, context) => { - expectType<{ bar: string; baz: string }>(context.params); - }); + describe('process.env.FIREBASE_CONFIG not set', () => { + it('should not throw if __trigger is not accessed', () => { + expect(() => database.ref('/path').onWrite(() => null)).to.not.throw( + Error + ); }); }); - describe("process.env.FIREBASE_CONFIG not set", () => { - it("should not throw if __endpoint is not accessed", () => { - expect(() => database.ref("/path").onWrite(() => null)).to.not.throw(Error); + it('should throw when trigger is accessed', () => { + expect( + () => database.ref('/path').onWrite(() => null).__trigger + ).to.throw(Error); }); - it("should throw when endpoint is accessed", () => { - expect(() => database.ref("/path").onWrite(() => null).__endpoint).to.throw(Error); + it('should throw when endpoint is accessed', () => { + expect( + () => database.ref('/path').onWrite(() => null).__endpoint + ).to.throw(Error); }); it("should not throw when #run is called", () => { @@ -385,6 +598,7 @@ describe("Database Functions", () => { describe("DataSnapshot", () => { let subject: any; + const apps = new appsNamespace.Apps(); const populate = (data: any) => { const [instance, path] = database.extractInstanceAndPath( diff --git a/spec/v1/providers/firestore.spec.ts b/spec/v1/providers/firestore.spec.ts index 1c66ec31b..7cd268fa8 100644 --- a/spec/v1/providers/firestore.spec.ts +++ b/spec/v1/providers/firestore.spec.ts @@ -91,7 +91,17 @@ describe("Firestore Functions", () => { }); } - describe("document builders and event types", () => { + describe('document builders and event types', () => { + function expectedTrigger(resource: string, eventType: string) { + return { + eventTrigger: { + resource, + eventType: `providers/cloud.firestore/eventTypes/${eventType}`, + service: 'firestore.googleapis.com', + }, + }; + } + function expectedEndpoint(resource: string, eventType: string) { return { ...MINIMAL_V1_ENDPOINT, @@ -127,36 +137,66 @@ describe("Firestore Functions", () => { it("should allow custom namespaces", () => { const resource = "projects/project1/databases/(default)/documents@v2/users/{uid}"; const cloudFunction = firestore - .namespace("v2") - .document("users/{uid}") - .onWrite((snap, context) => { - expectType<{ uid: string }>(context.params); - }); + .document('users/{uid}') + .onWrite(() => null); - expect(cloudFunction.__endpoint).to.deep.equal(expectedEndpoint(resource, "document.write")); + expect(cloudFunction.__trigger).to.deep.equal( + expectedTrigger(resource, 'document.write') + ); + + expect(cloudFunction.__endpoint).to.deep.equal( + expectedEndpoint(resource, 'document.write') + ); + }); + + it('should allow custom namespaces', () => { + const resource = + 'projects/project1/databases/(default)/documents@v2/users/{uid}'; + const cloudFunction = firestore + .namespace('v2') + .document('users/{uid}') + .onWrite(() => null); + + expect(cloudFunction.__trigger).to.deep.equal( + expectedTrigger(resource, 'document.write') + ); + + expect(cloudFunction.__endpoint).to.deep.equal( + expectedEndpoint(resource, 'document.write') + ); }); - it("should allow custom databases", () => { - const resource = "projects/project1/databases/myDB/documents/users/{uid}"; + it('should allow custom databases', () => { + const resource = 'projects/project1/databases/myDB/documents/users/{uid}'; const cloudFunction = firestore .database("myDB") .document("users/{uid}") .onWrite(() => null); - expect(cloudFunction.__endpoint).to.deep.equal(expectedEndpoint(resource, "document.write")); + expect(cloudFunction.__trigger).to.deep.equal( + expectedTrigger(resource, 'document.write') + ); + + expect(cloudFunction.__endpoint).to.deep.equal( + expectedEndpoint(resource, 'document.write') + ); }); it("should allow both custom database and namespace", () => { const resource = "projects/project1/databases/myDB/documents@v2/users/{uid}"; const cloudFunction = firestore - .database("myDB") - .namespace("v2") - .document("users/{uid}") - .onWrite((snap, context) => { - expectType<{ uid: string }>(context.params); - }); + .database('myDB') + .namespace('v2') + .document('users/{uid}') + .onWrite(() => null); - expect(cloudFunction.__endpoint).to.deep.equal(expectedEndpoint(resource, "document.write")); + expect(cloudFunction.__trigger).to.deep.equal( + expectedTrigger(resource, 'document.write') + ); + + expect(cloudFunction.__endpoint).to.deep.equal( + expectedEndpoint(resource, 'document.write') + ); }); it("should allow both region and runtime options to be set", () => { @@ -171,19 +211,33 @@ describe("Firestore Functions", () => { expectType>(context.params); }); - expect(fn.__endpoint.region).to.deep.equal(["us-east1"]); + expect(fn.__trigger.regions).to.deep.equal(['us-east1']); + expect(fn.__trigger.availableMemoryMb).to.deep.equal(256); + expect(fn.__trigger.timeout).to.deep.equal('90s'); + + expect(fn.__endpoint.region).to.deep.equal(['us-east1']); expect(fn.__endpoint.availableMemoryMb).to.deep.equal(256); expect(fn.__endpoint.timeoutSeconds).to.deep.equal(90); }); }); - describe("process.env.GCLOUD_PROJECT not set", () => { - it("should not throw if __endpoint is not accessed", () => { - expect(() => firestore.document("input").onCreate(() => null)).to.not.throw(Error); + describe('process.env.GCLOUD_PROJECT not set', () => { + it('should not throw if __trigger is not accessed', () => { + expect(() => + firestore.document('input').onCreate(() => null) + ).to.not.throw(Error); + }); + + it('should throw when trigger is accessed', () => { + expect( + () => firestore.document('input').onCreate(() => null).__trigger + ).to.throw(Error); }); - it("should throw when endpoint is accessed", () => { - expect(() => firestore.document("input").onCreate(() => null).__endpoint).to.throw(Error); + it('should throw when endpoint is accessed', () => { + expect( + () => firestore.document('input').onCreate(() => null).__endpoint + ).to.throw(Error); }); it("should not throw when #run is called", () => { @@ -242,12 +296,94 @@ describe("Firestore Functions", () => { }).timeout(5000); 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(), {}); + const testFunction = firestore + .document('path') + .onDelete((data, context) => { + 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(), {}, 'document.delete'); + return testFunction(event.data, event.context); + }).timeout(5000); + }); + + describe('handler namespace', () => { + before(() => { + process.env.GCLOUD_PROJECT = 'project1'; + }); + + after(() => { + delete process.env.GCLOUD_PROJECT; + }); + + it('constructs correct data type and sets trigger to {} on "document.write" events', () => { + const testFunction = functions.handler.firestore.document.onWrite( + (change, context) => { + expect(change.before.data()).to.deep.equal({ + key1: false, + key2: 111, + }); + expect(change.before.get('key1')).to.equal(false); + expect(change.after.data()).to.deep.equal({ key1: true, key2: 123 }); + expect(change.after.get('key1')).to.equal(true); + return true; // otherwise will get warning about returning undefined + } + ); + expect(testFunction.__trigger).to.deep.equal({}); + const event = constructEvent( + createOldValue(), + createValue(), + 'document.write' + ); + return testFunction(event.data, event.context); + }).timeout(5000); + + it('constructs correct data type and sets trigger to {} on "document.create" events', () => { + const testFunction = functions.handler.firestore.document.onCreate( + (data, context) => { + 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 + } + ); + expect(testFunction.__trigger).to.deep.equal({}); + const event = constructEvent({}, createValue(), 'document.create'); + return testFunction(event.data, event.context); + }).timeout(5000); + + it('constructs correct data type and sets trigger to {} on "document.update" events', () => { + const testFunction = functions.handler.firestore.document.onUpdate( + (change) => { + expect(change.before.data()).to.deep.equal({ + key1: false, + key2: 111, + }); + expect(change.before.get('key1')).to.equal(false); + expect(change.after.data()).to.deep.equal({ key1: true, key2: 123 }); + expect(change.after.get('key1')).to.equal(true); + return true; // otherwise will get warning about returning undefined + } + ); + expect(testFunction.__trigger).to.deep.equal({}); + const event = constructEvent( + createOldValue(), + createValue(), + 'document.update' + ); + return testFunction(event.data, event.context); + }).timeout(5000); + + it('constructs correct data type and sets trigger to {} on "document.delete" events', () => { + const testFunction = functions.handler.firestore.document.onDelete( + (data, context) => { + 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(), {}, 'document.delete'); + expect(testFunction.__trigger).to.deep.equal({}); 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 7d06a15c3..571968768 100644 --- a/spec/v1/providers/https.spec.ts +++ b/spec/v1/providers/https.spec.ts @@ -34,6 +34,7 @@ describe("CloudHttpsBuilder", () => { const result = https.onRequest((req, resp) => { resp.send(200); }); + expect(result.__trigger).to.deep.equal({ httpsTrigger: {} }); expect(result.__endpoint).to.deep.equal({ ...MINIMAL_V1_ENDPOINT, platform: "gcfv1", @@ -51,18 +52,48 @@ describe("CloudHttpsBuilder", () => { }) .https.onRequest(() => null); - expect(fn.__endpoint.region).to.deep.equal(["us-east1"]); + expect(fn.__trigger.regions).to.deep.equal(['us-east1']); + expect(fn.__trigger.availableMemoryMb).to.deep.equal(256); + expect(fn.__trigger.timeout).to.deep.equal('90s'); + expect(fn.__trigger.httpsTrigger.invoker).to.deep.equal(['private']); + + expect(fn.__endpoint.region).to.deep.equal(['us-east1']); expect(fn.__endpoint.availableMemoryMb).to.deep.equal(256); expect(fn.__endpoint.timeoutSeconds).to.deep.equal(90); - expect(fn.__endpoint.httpsTrigger.invoker).to.deep.equal(["private"]); + expect(fn.__endpoint.httpsTrigger.invoker).to.deep.equal(['private']); + }); + }); +}); + +describe('handler namespace', () => { + describe('#onRequest', () => { + it('should return an empty trigger', () => { + const result = functions.handler.https.onRequest((req, res) => { + res.send(200); + }); + expect(result.__trigger).to.deep.equal({}); + expect(result.__endpoint).to.be.undefined; + }); + }); + + describe('#onCall', () => { + it('should return an empty trigger', () => { + const result = functions.handler.https.onCall(() => null); + expect(result.__trigger).to.deep.equal({}); + expect(result.__endpoint).to.be.undefined; }); }); }); -describe("#onCall", () => { - it("should return a trigger/endpoint with appropriate values", () => { - const result = https.onCall(() => { - return "response"; +describe('#onCall', () => { + it('should return a trigger/endpoint with appropriate values', () => { + const result = https.onCall((data) => { + return 'response'; + }); + + expect(result.__trigger).to.deep.equal({ + httpsTrigger: {}, + labels: { 'deployment-callable': 'true' }, }); expect(result.__endpoint).to.deep.equal({ @@ -82,7 +113,11 @@ describe("#onCall", () => { }) .https.onCall(() => null); - expect(fn.__endpoint.region).to.deep.equal(["us-east1"]); + expect(fn.__trigger.regions).to.deep.equal(['us-east1']); + expect(fn.__trigger.availableMemoryMb).to.deep.equal(256); + expect(fn.__trigger.timeout).to.deep.equal('90s'); + + expect(fn.__endpoint.region).to.deep.equal(['us-east1']); expect(fn.__endpoint.availableMemoryMb).to.deep.equal(256); expect(fn.__endpoint.timeoutSeconds).to.deep.equal(90); }); diff --git a/spec/v1/providers/pubsub.spec.ts b/spec/v1/providers/pubsub.spec.ts index 573ac0f1c..f599d2fc0 100644 --- a/spec/v1/providers/pubsub.spec.ts +++ b/spec/v1/providers/pubsub.spec.ts @@ -83,7 +83,11 @@ describe("Pubsub Functions", () => { .pubsub.topic("toppy") .onPublish(() => null); - expect(fn.__endpoint.region).to.deep.equal(["us-east1"]); + expect(fn.__trigger.regions).to.deep.equal(['us-east1']); + expect(fn.__trigger.availableMemoryMb).to.deep.equal(256); + expect(fn.__trigger.timeout).to.deep.equal('90s'); + + expect(fn.__endpoint.region).to.deep.equal(['us-east1']); expect(fn.__endpoint.availableMemoryMb).to.deep.equal(256); expect(fn.__endpoint.timeoutSeconds).to.deep.equal(90); }); @@ -91,7 +95,15 @@ describe("Pubsub Functions", () => { describe("#onPublish", () => { it("should return a trigger/endpoint with appropriate values", () => { // Pick up project from process.env.GCLOUD_PROJECT - const result = pubsub.topic("toppy").onPublish(() => null); + const result = pubsub.topic('toppy').onPublish(() => null); + + expect(result.__trigger).to.deep.equal({ + eventTrigger: { + eventType: 'google.pubsub.topic.publish', + resource: 'projects/project1/topics/toppy', + service: 'pubsub.googleapis.com', + }, + }); expect(result.__endpoint).to.deep.equal({ ...MINIMAL_V1_ENDPOINT, @@ -147,9 +159,15 @@ describe("Pubsub Functions", () => { }); }); - describe("#schedule", () => { - it("should return a trigger/endpoint with schedule", () => { - const result = pubsub.schedule("every 5 minutes").onRun(() => null); + describe('#schedule', () => { + it('should return a trigger/endpoint with schedule', () => { + const result = pubsub + .schedule('every 5 minutes') + .onRun((context) => null); + + expect(result.__trigger.schedule).to.deep.equal({ + schedule: 'every 5 minutes', + }); expect(result.__endpoint.scheduleTrigger).to.deep.equal({ ...MINIMAL_SCHEDULE_TRIGGER, @@ -159,9 +177,14 @@ describe("Pubsub Functions", () => { it("should return a trigger/endpoint with schedule and timeZone when one is chosen", () => { const result = pubsub - .schedule("every 5 minutes") - .timeZone("America/New_York") - .onRun(() => null); + .schedule('every 5 minutes') + .timeZone('America/New_York') + .onRun((context) => null); + + expect(result.__trigger.schedule).to.deep.equal({ + schedule: 'every 5 minutes', + timeZone: 'America/New_York', + }); expect(result.__endpoint.scheduleTrigger).to.deep.equal({ ...MINIMAL_SCHEDULE_TRIGGER, @@ -183,6 +206,14 @@ describe("Pubsub Functions", () => { .retryConfig(retryConfig) .onRun(() => null); + expect(result.__trigger.schedule).to.deep.equal({ + schedule: 'every 5 minutes', + retryConfig, + }); + expect(result.__trigger.labels).to.deep.equal({ + 'deployment-scheduled': 'true', + }); + expect(result.__endpoint.scheduleTrigger).to.deep.equal({ ...MINIMAL_SCHEDULE_TRIGGER, schedule: "every 5 minutes", @@ -208,6 +239,15 @@ describe("Pubsub Functions", () => { .retryConfig(retryConfig) .onRun(() => null); + expect(result.__trigger.schedule).to.deep.equal({ + schedule: 'every 5 minutes', + retryConfig, + timeZone: 'America/New_York', + }); + expect(result.__trigger.labels).to.deep.equal({ + 'deployment-scheduled': 'true', + }); + expect(result.__endpoint.scheduleTrigger).to.deep.equal({ ...MINIMAL_SCHEDULE_TRIGGER, schedule: "every 5 minutes", @@ -227,6 +267,12 @@ describe("Pubsub Functions", () => { }) .pubsub.schedule("every 5 minutes") .onRun(() => null); + expect(result.__trigger.schedule).to.deep.equal({ + schedule: 'every 5 minutes', + }); + expect(result.__trigger.regions).to.deep.equal(['us-east1']); + expect(result.__trigger.availableMemoryMb).to.deep.equal(256); + expect(result.__trigger.timeout).to.deep.equal('90s'); expect(result.__endpoint.scheduleTrigger).to.deep.equal({ ...MINIMAL_SCHEDULE_TRIGGER, @@ -247,6 +293,13 @@ describe("Pubsub Functions", () => { .pubsub.schedule("every 5 minutes") .timeZone("America/New_York") .onRun(() => null); + expect(result.__trigger.schedule).to.deep.equal({ + schedule: 'every 5 minutes', + timeZone: 'America/New_York', + }); + expect(result.__trigger.regions).to.deep.equal(['us-east1']); + expect(result.__trigger.availableMemoryMb).to.deep.equal(256); + expect(result.__trigger.timeout).to.deep.equal('90s'); expect(result.__endpoint.scheduleTrigger).to.deep.equal({ ...MINIMAL_SCHEDULE_TRIGGER, @@ -275,6 +328,16 @@ describe("Pubsub Functions", () => { .pubsub.schedule("every 5 minutes") .retryConfig(retryConfig) .onRun(() => null); + expect(result.__trigger.schedule).to.deep.equal({ + schedule: 'every 5 minutes', + retryConfig, + }); + expect(result.__trigger.labels).to.deep.equal({ + 'deployment-scheduled': 'true', + }); + expect(result.__trigger.regions).to.deep.equal(['us-east1']); + expect(result.__trigger.availableMemoryMb).to.deep.equal(256); + expect(result.__trigger.timeout).to.deep.equal('90s'); expect(result.__endpoint.scheduleTrigger).to.deep.equal({ ...MINIMAL_SCHEDULE_TRIGGER, @@ -305,6 +368,17 @@ describe("Pubsub Functions", () => { .timeZone("America/New_York") .retryConfig(retryConfig) .onRun(() => null); + expect(result.__trigger.schedule).to.deep.equal({ + schedule: 'every 5 minutes', + timeZone: 'America/New_York', + retryConfig, + }); + expect(result.__trigger.labels).to.deep.equal({ + 'deployment-scheduled': 'true', + }); + expect(result.__trigger.regions).to.deep.equal(['us-east1']); + expect(result.__trigger.availableMemoryMb).to.deep.equal(256); + expect(result.__trigger.timeout).to.deep.equal('90s'); expect(result.__endpoint.scheduleTrigger).to.deep.equal({ ...MINIMAL_SCHEDULE_TRIGGER, @@ -319,13 +393,108 @@ describe("Pubsub Functions", () => { }); }); - describe("process.env.GCLOUD_PROJECT not set", () => { - it("should not throw if __endpoint is not accessed", () => { - expect(() => pubsub.topic("toppy").onPublish(() => null)).to.not.throw(Error); + describe('handler namespace', () => { + describe('#onPublish', () => { + describe('#topic', () => { + it('should return an empty trigger', () => { + const result = functions.handler.pubsub.topic.onPublish(() => null); + expect(result.__trigger).to.deep.equal({}); + expect(result.__endpoint).to.be.undefined; + }); + + it('should properly handle a new-style event', () => { + const raw = new Buffer('{"hello":"world"}', 'utf8').toString( + 'base64' + ); + const event = { + data: { + data: raw, + attributes: { + foo: 'bar', + }, + }, + context: { + eventId: '70172329041928', + timestamp: '2018-04-09T07:56:12.975Z', + eventType: 'google.pubsub.topic.publish', + resource: { + service: 'pubsub.googleapis.com', + name: 'projects/project1/topics/toppy', + }, + }, + }; + + const result = functions.handler.pubsub.topic.onPublish((data) => { + return { + raw: data.data, + json: data.json, + attributes: data.attributes, + }; + }); + + return expect( + result(event.data, event.context) + ).to.eventually.deep.equal({ + raw, + json: { hello: 'world' }, + attributes: { foo: 'bar' }, + }); + }); + }); + describe('#schedule', () => { + it('should return an empty trigger', () => { + const result = functions.handler.pubsub.schedule.onRun(() => null); + expect(result.__trigger).to.deep.equal({}); + }); + it('should return a handler with a proper event context', () => { + const raw = new Buffer('{"hello":"world"}', 'utf8').toString( + 'base64' + ); + const event = { + data: { + data: raw, + attributes: { + foo: 'bar', + }, + }, + context: { + eventId: '70172329041928', + timestamp: '2018-04-09T07:56:12.975Z', + eventType: 'google.pubsub.topic.publish', + resource: { + service: 'pubsub.googleapis.com', + name: 'projects/project1/topics/toppy', + }, + }, + }; + const result = functions.handler.pubsub.schedule.onRun( + (context) => context.eventId + ); + return expect(result(event.data, event.context)).to.eventually.equal( + '70172329041928' + ); + }); + }); + }); + }); + + describe('process.env.GCLOUD_PROJECT not set', () => { + it('should not throw if __trigger is not accessed', () => { + expect(() => pubsub.topic('toppy').onPublish(() => null)).to.not.throw( + Error + ); + }); + + it('should throw when trigger is accessed', () => { + expect( + () => pubsub.topic('toppy').onPublish(() => null).__trigger + ).to.throw(Error); }); - it("should throw when endpoint is accessed", () => { - expect(() => pubsub.topic("toppy").onPublish(() => null).__endpoint).to.throw(Error); + it('should throw when endpoint is accessed', () => { + expect( + () => pubsub.topic('toppy').onPublish(() => null).__endpoint + ).to.throw(Error); }); it("should not throw when #run is called", () => { diff --git a/spec/v1/providers/remoteConfig.spec.ts b/spec/v1/providers/remoteConfig.spec.ts index 45d13bb32..b5ff28385 100644 --- a/spec/v1/providers/remoteConfig.spec.ts +++ b/spec/v1/providers/remoteConfig.spec.ts @@ -53,6 +53,14 @@ describe("RemoteConfig Functions", () => { it("should have the correct trigger", () => { const cloudFunction = remoteConfig.onUpdate(() => null); + expect(cloudFunction.__trigger).to.deep.equal({ + eventTrigger: { + resource: 'projects/project1', + eventType: 'google.firebase.remoteconfig.update', + service: 'firebaseremoteconfig.googleapis.com', + }, + }); + expect(cloudFunction.__endpoint).to.deep.equal({ ...MINIMAL_V1_ENDPOINT, platform: "gcfv1", @@ -76,7 +84,11 @@ describe("RemoteConfig Functions", () => { }) .remoteConfig.onUpdate(() => null); - expect(cloudFunction.__endpoint.region).to.deep.equal(["us-east1"]); + expect(cloudFunction.__trigger.regions).to.deep.equal(['us-east1']); + expect(cloudFunction.__trigger.availableMemoryMb).to.deep.equal(256); + expect(cloudFunction.__trigger.timeout).to.deep.equal('90s'); + + expect(cloudFunction.__endpoint.region).to.deep.equal(['us-east1']); expect(cloudFunction.__endpoint.availableMemoryMb).to.deep.equal(256); expect(cloudFunction.__endpoint.timeoutSeconds).to.deep.equal(90); }); @@ -112,10 +124,52 @@ describe("RemoteConfig Functions", () => { 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()); - }), + cloudFunctionUpdate(event.data, event.context).then( + (data: any, context: any) => { + expect(data).to.deep.equal(constructVersion()); + } + ), ]); }); }); + + describe('handler namespace', () => { + describe('#onUpdate', () => { + it('should have an empty trigger', () => { + const cloudFunction = functions.handler.remoteConfig.onUpdate( + () => null + ); + + expect(cloudFunction.__trigger).to.deep.equal({}); + expect(cloudFunction.__endpoint).to.be.undefined; + }); + + it('should correctly unwrap the event', () => { + const cloudFunctionUpdate = functions.handler.remoteConfig.onUpdate( + (version: remoteConfig.TemplateVersion, context: EventContext) => + version + ); + const event: Event = { + data: constructVersion(), + context: { + eventId: '70172329041928', + timestamp: '2018-04-09T07:56:12.975Z', + eventType: 'google.firebase.remoteconfig.update', + resource: { + service: 'firebaseremoteconfig.googleapis.com', + name: 'projects/project1', + }, + }, + }; + + return Promise.all([ + cloudFunctionUpdate(event.data, event.context).then( + (data: any, context: any) => { + expect(data).to.deep.equal(constructVersion()); + } + ), + ]); + }); + }); + }); }); diff --git a/spec/v1/providers/storage.spec.ts b/spec/v1/providers/storage.spec.ts index 0055e8769..0b3617afb 100644 --- a/spec/v1/providers/storage.spec.ts +++ b/spec/v1/providers/storage.spec.ts @@ -27,8 +27,18 @@ import * as functions from "../../../src/v1"; import * as storage from "../../../src/v1/providers/storage"; import { MINIMAL_V1_ENDPOINT } from "../../fixtures"; -describe("Storage Functions", () => { - describe("ObjectBuilder", () => { +describe('Storage Functions', () => { + describe('ObjectBuilder', () => { + function expectedTrigger(bucket: string, eventType: string) { + return { + eventTrigger: { + resource: `projects/_/buckets/${bucket}`, + eventType: `google.storage.object.${eventType}`, + service: 'storage.googleapis.com', + }, + }; + } + function expectedEndpoint(bucket: string, eventType: string) { return { ...MINIMAL_V1_ENDPOINT, @@ -66,7 +76,11 @@ describe("Storage Functions", () => { .storage.object() .onArchive(() => null); - expect(fn.__endpoint.region).to.deep.equal(["us-east1"]); + expect(fn.__trigger.regions).to.deep.equal(['us-east1']); + expect(fn.__trigger.availableMemoryMb).to.deep.equal(256); + expect(fn.__trigger.timeout).to.deep.equal('90s'); + + expect(fn.__endpoint.region).to.deep.equal(['us-east1']); expect(fn.__endpoint.availableMemoryMb).to.deep.equal(256); expect(fn.__endpoint.timeoutSeconds).to.deep.equal(90); }); @@ -78,23 +92,49 @@ describe("Storage Functions", () => { .object() .onArchive(() => null); - expect(cloudFunction.__endpoint).to.deep.equal(expectedEndpoint("bucky", "archive")); + expect(cloudFunction.__trigger).to.deep.equal( + expectedTrigger('bucky', 'archive') + ); + + expect(cloudFunction.__endpoint).to.deep.equal( + expectedEndpoint('bucky', 'archive') + ); }); it("should use the default bucket when none is provided", () => { const cloudFunction = storage.object().onArchive(() => null); - expect(cloudFunction.__endpoint).to.deep.equal(expectedEndpoint(defaultBucket, "archive")); + expect(cloudFunction.__trigger).to.deep.equal( + expectedTrigger(defaultBucket, 'archive') + ); + + expect(cloudFunction.__endpoint).to.deep.equal( + expectedEndpoint(defaultBucket, 'archive') + ); }); it("should allow fully qualified bucket names", () => { const subjectQualified = new storage.ObjectBuilder(() => "projects/_/buckets/bucky", {}); const result = subjectQualified.onArchive(() => null); - expect(result.__endpoint).to.deep.equal(expectedEndpoint("bucky", "archive")); + expect(result.__trigger).to.deep.equal( + expectedTrigger('bucky', 'archive') + ); + + expect(result.__endpoint).to.deep.equal( + expectedEndpoint('bucky', 'archive') + ); }); - it("should throw with improperly formatted buckets", () => { + it('should throw with improperly formatted buckets', () => { + expect( + () => + storage + .bucket('bad/bucket/format') + .object() + .onArchive(() => null).__trigger + ).to.throw(Error); + expect( () => storage @@ -139,20 +179,38 @@ describe("Storage Functions", () => { .object() .onDelete(() => null); - expect(cloudFunction.__endpoint).to.deep.equal(expectedEndpoint("bucky", "delete")); + expect(cloudFunction.__trigger).to.deep.equal( + expectedTrigger('bucky', 'delete') + ); + + expect(cloudFunction.__endpoint).to.deep.equal( + expectedEndpoint('bucky', 'delete') + ); }); it("should use the default bucket when none is provided", () => { const cloudFunction = storage.object().onDelete(() => null); - expect(cloudFunction.__endpoint).to.deep.equal(expectedEndpoint(defaultBucket, "delete")); + expect(cloudFunction.__trigger).to.deep.equal( + expectedTrigger(defaultBucket, 'delete') + ); + + expect(cloudFunction.__endpoint).to.deep.equal( + expectedEndpoint(defaultBucket, 'delete') + ); }); it("should allow fully qualified bucket names", () => { const subjectQualified = new storage.ObjectBuilder(() => "projects/_/buckets/bucky", {}); const result = subjectQualified.onDelete(() => null); - expect(result.__endpoint).to.deep.equal(expectedEndpoint("bucky", "delete")); + expect(result.__trigger).to.deep.equal( + expectedTrigger('bucky', 'delete') + ); + + expect(result.__endpoint).to.deep.equal( + expectedEndpoint('bucky', 'delete') + ); }); it("should throw with improperly formatted buckets", () => { @@ -161,6 +219,8 @@ describe("Storage Functions", () => { .object() .onDelete(() => null); + expect(() => fn.__trigger).to.throw(Error); + expect(() => fn.__endpoint).to.throw(Error); }); @@ -199,20 +259,38 @@ describe("Storage Functions", () => { .object() .onFinalize(() => null); - expect(cloudFunction.__endpoint).to.deep.equal(expectedEndpoint("bucky", "finalize")); + expect(cloudFunction.__trigger).to.deep.equal( + expectedTrigger('bucky', 'finalize') + ); + + expect(cloudFunction.__endpoint).to.deep.equal( + expectedEndpoint('bucky', 'finalize') + ); }); it("should use the default bucket when none is provided", () => { const cloudFunction = storage.object().onFinalize(() => null); - expect(cloudFunction.__endpoint).to.deep.equal(expectedEndpoint(defaultBucket, "finalize")); + expect(cloudFunction.__trigger).to.deep.equal( + expectedTrigger(defaultBucket, 'finalize') + ); + + expect(cloudFunction.__endpoint).to.deep.equal( + expectedEndpoint(defaultBucket, 'finalize') + ); }); it("should allow fully qualified bucket names", () => { const subjectQualified = new storage.ObjectBuilder(() => "projects/_/buckets/bucky", {}); const result = subjectQualified.onFinalize(() => null); - expect(result.__endpoint).to.deep.equal(expectedEndpoint("bucky", "finalize")); + expect(result.__trigger).to.deep.equal( + expectedTrigger('bucky', 'finalize') + ); + + expect(result.__endpoint).to.deep.equal( + expectedEndpoint('bucky', 'finalize') + ); }); it("should throw with improperly formatted buckets", () => { @@ -221,6 +299,8 @@ describe("Storage Functions", () => { .object() .onFinalize(() => null); + expect(() => fn.__trigger).to.throw(Error); + expect(() => fn.__endpoint).to.throw(Error); }); @@ -259,12 +339,22 @@ describe("Storage Functions", () => { .object() .onMetadataUpdate(() => null); - expect(cloudFunction.__endpoint).to.deep.equal(expectedEndpoint("bucky", "metadataUpdate")); + expect(cloudFunction.__trigger).to.deep.equal( + expectedTrigger('bucky', 'metadataUpdate') + ); + + expect(cloudFunction.__endpoint).to.deep.equal( + expectedEndpoint('bucky', 'metadataUpdate') + ); }); it("should use the default bucket when none is provided", () => { const cloudFunction = storage.object().onMetadataUpdate(() => null); + expect(cloudFunction.__trigger).to.deep.equal( + expectedTrigger(defaultBucket, 'metadataUpdate') + ); + expect(cloudFunction.__endpoint).to.deep.equal( expectedEndpoint(defaultBucket, "metadataUpdate") ); @@ -274,7 +364,13 @@ describe("Storage Functions", () => { const subjectQualified = new storage.ObjectBuilder(() => "projects/_/buckets/bucky", {}); const result = subjectQualified.onMetadataUpdate(() => null); - expect(result.__endpoint).to.deep.equal(expectedEndpoint("bucky", "metadataUpdate")); + expect(result.__trigger).to.deep.equal( + expectedTrigger('bucky', 'metadataUpdate') + ); + + expect(result.__endpoint).to.deep.equal( + expectedEndpoint('bucky', 'metadataUpdate') + ); }); it("should throw with improperly formatted buckets", () => { @@ -283,6 +379,7 @@ describe("Storage Functions", () => { .object() .onMetadataUpdate(() => null); + expect(() => fn.__trigger).to.throw(Error); expect(() => fn.__endpoint).to.throw(Error); }); @@ -306,27 +403,212 @@ describe("Storage Functions", () => { }, }, }; - return cloudFunction(goodMediaLinkEvent.data, goodMediaLinkEvent.context).then( - (result: any) => { - expect(result).equals(goodMediaLinkEvent.data.mediaLink); + return cloudFunction( + goodMediaLinkEvent.data, + goodMediaLinkEvent.context + ).then((result: any, context: EventContext) => { + expect(result).equals(goodMediaLinkEvent.data.mediaLink); + }); + }); + }); + }); + + describe('namespace handler', () => { + before(() => { + process.env.FIREBASE_CONFIG = JSON.stringify({ + storageBucket: 'bucket', + }); + }); + + after(() => { + delete process.env.FIREBASE_CONFIG; + }); + + describe('#onArchive', () => { + it('should return an empty trigger', () => { + const cloudFunction = functions.handler.storage.bucket.onArchive( + () => null + ); + + expect(cloudFunction.__trigger).to.deep.equal({}); + expect(cloudFunction.__endpoint).to.be.undefined; + }); + + it('should not mess with media links using non-literal slashes', () => { + const cloudFunction = functions.handler.storage.object.onArchive( + (data) => { + return data.mediaLink; + } + ); + const goodMediaLinkEvent = { + data: { + mediaLink: + 'https://www.googleapis.com/storage/v1/b/mybucket.appspot.com' + + '/o/nestedfolder%2Fanotherfolder%2Fmyobject.file?generation=12345&alt=media', + }, + context: { + eventId: '70172329041928', + timestamp: '2018-04-09T07:56:12.975Z', + eventType: 'google.storage.object.archive', + resource: { + service: 'storage.googleapis.com', + name: 'projects/_/buckets/bucky', + }, + }, + }; + return cloudFunction( + goodMediaLinkEvent.data, + goodMediaLinkEvent.context + ).then((result: any, context: EventContext) => { + expect(result).equals(goodMediaLinkEvent.data.mediaLink); + }); + }); + }); + + describe('#onDelete', () => { + it('should return an empty trigger', () => { + const cloudFunction = functions.handler.storage.bucket.onDelete( + () => null + ); + + expect(cloudFunction.__trigger).to.deep.equal({}); + expect(cloudFunction.__endpoint).to.be.undefined; + }); + + it('should not mess with media links using non-literal slashes', () => { + const cloudFunction = functions.handler.storage.object.onDelete( + (data) => { + return data.mediaLink; + } + ); + const goodMediaLinkEvent = { + data: { + mediaLink: + 'https://www.googleapis.com/storage/v1/b/mybucket.appspot.com' + + '/o/nestedfolder%2Fanotherfolder%2Fmyobject.file?generation=12345&alt=media', + }, + context: { + eventId: '70172329041928', + timestamp: '2018-04-09T07:56:12.975Z', + eventType: 'google.storage.object.delete', + resource: { + service: 'storage.googleapis.com', + name: 'projects/_/buckets/bucky', + }, + }, + }; + return cloudFunction( + goodMediaLinkEvent.data, + goodMediaLinkEvent.context + ).then((result: any, context: EventContext) => { + expect(result).equals(goodMediaLinkEvent.data.mediaLink); + }); + }); + }); + + describe('#onFinalize', () => { + it('should return an empty trigger', () => { + const cloudFunction = functions.handler.storage.bucket.onFinalize( + () => null + ); + + expect(cloudFunction.__trigger).to.deep.equal({}); + expect(cloudFunction.__endpoint).to.be.undefined; + }); + + it('should not mess with media links using non-literal slashes', () => { + const cloudFunction = functions.handler.storage.object.onFinalize( + (data) => { + return data.mediaLink; } ); + const goodMediaLinkEvent = { + data: { + mediaLink: + 'https://www.googleapis.com/storage/v1/b/mybucket.appspot.com' + + '/o/nestedfolder%2Fanotherfolder%2Fmyobject.file?generation=12345&alt=media', + }, + context: { + eventId: '70172329041928', + timestamp: '2018-04-09T07:56:12.975Z', + eventType: 'google.storage.object.finalize', + resource: { + service: 'storage.googleapis.com', + name: 'projects/_/buckets/bucky', + }, + }, + }; + return cloudFunction( + goodMediaLinkEvent.data, + goodMediaLinkEvent.context + ).then((result: any, context: EventContext) => { + expect(result).equals(goodMediaLinkEvent.data.mediaLink); + }); + }); + }); + + describe('#onMetadataUpdate', () => { + it('should return an empty trigger', () => { + const cloudFunction = functions.handler.storage.bucket.onMetadataUpdate( + () => null + ); + + expect(cloudFunction.__trigger).to.deep.equal({}); + expect(cloudFunction.__endpoint).to.be.undefined; + }); + + it('should not mess with media links using non-literal slashes', () => { + const cloudFunction = functions.handler.storage.object.onMetadataUpdate( + (data) => { + return data.mediaLink; + } + ); + const goodMediaLinkEvent = { + data: { + mediaLink: + 'https://www.googleapis.com/storage/v1/b/mybucket.appspot.com' + + '/o/nestedfolder%2Fanotherfolder%2Fmyobject.file?generation=12345&alt=media', + }, + context: { + eventId: '70172329041928', + timestamp: '2018-04-09T07:56:12.975Z', + eventType: 'google.storage.object.metadataUpdate', + resource: { + service: 'storage.googleapis.com', + name: 'projects/_/buckets/bucky', + }, + }, + }; + return cloudFunction( + goodMediaLinkEvent.data, + goodMediaLinkEvent.context + ).then((result: any, context: EventContext) => { + expect(result).equals(goodMediaLinkEvent.data.mediaLink); + }); }); }); }); - describe("process.env.FIREBASE_CONFIG not set", () => { + describe('process.env.FIREBASE_CONFIG not set', () => { beforeEach(() => { (config as any).firebaseConfigCache = null; delete process.env.FIREBASE_CONFIG; }); - it("should not throw if __endpoint is not accessed", () => { + it('should not throw if __trigger is not accessed', () => { expect(() => storage.object().onArchive(() => null)).to.not.throw(Error); }); - it("should throw when endpoint is accessed", () => { - expect(() => storage.object().onArchive(() => null).__endpoint).to.throw(Error); + it('should throw when trigger is accessed', () => { + expect(() => storage.object().onArchive(() => null).__trigger).to.throw( + Error + ); + }); + + it('should throw when endpoint is accessed', () => { + expect(() => storage.object().onArchive(() => null).__endpoint).to.throw( + Error + ); }); it("should not throw when #run is called", () => { diff --git a/spec/v1/providers/tasks.spec.ts b/spec/v1/providers/tasks.spec.ts index ec9e6d318..4591ecfaf 100644 --- a/spec/v1/providers/tasks.spec.ts +++ b/spec/v1/providers/tasks.spec.ts @@ -43,8 +43,25 @@ describe("#onDispatch", () => { maxDoublings: 3, minBackoffSeconds: 5, }, - invoker: "private", - }).onDispatch(() => undefined); + invoker: 'private', + }).onDispatch(() => {}); + + expect(result.__trigger).to.deep.equal({ + taskQueueTrigger: { + rateLimits: { + maxConcurrentDispatches: 30, + maxDispatchesPerSecond: 40, + }, + retryConfig: { + maxAttempts: 5, + maxRetrySeconds: 10, + maxBackoffSeconds: 20, + maxDoublings: 3, + minBackoffSeconds: 5, + }, + invoker: ['private'], + }, + }); expect(result.__endpoint).to.deep.equal({ ...MINIMAL_V1_ENDPOINT, @@ -76,6 +93,17 @@ describe("#onDispatch", () => { .tasks.taskQueue({ retryConfig: { maxAttempts: 5 } }) .onDispatch(() => null); + expect(fn.__trigger).to.deep.equal({ + regions: ['us-east1'], + availableMemoryMb: 256, + timeout: '90s', + taskQueueTrigger: { + retryConfig: { + maxAttempts: 5, + }, + }, + }); + expect(fn.__endpoint).to.deep.equal({ ...MINIMAL_V1_ENDPOINT, platform: "gcfv1", @@ -130,6 +158,14 @@ describe("#onDispatch", () => { const response = await runHandler(func, req as any); expect(response.status).to.equal(204); - expect(gotData).to.deep.equal({ foo: "bar" }); + expect(gotData).to.deep.equal({ foo: 'bar' }); + }); +}); + +describe('handler namespace', () => { + it('should return an empty trigger', () => { + const result = functions.handler.tasks.taskQueue.onDispatch(() => null); + expect(result.__trigger).to.deep.equal({}); + expect(result.__endpoint).to.be.undefined; }); }); diff --git a/spec/v1/providers/testLab.spec.ts b/spec/v1/providers/testLab.spec.ts index 6aaccad2e..ac19f45da 100644 --- a/spec/v1/providers/testLab.spec.ts +++ b/spec/v1/providers/testLab.spec.ts @@ -39,6 +39,14 @@ describe("Test Lab Functions", () => { it("should return a trigger/endpoint with appropriate values", () => { const func = testLab.testMatrix().onComplete(() => null); + expect(func.__trigger).to.deep.equal({ + eventTrigger: { + service: 'testing.googleapis.com', + eventType: 'google.testing.testMatrix.complete', + resource: 'projects/project1/testMatrices/{matrix}', + }, + }); + expect(func.__endpoint).to.deep.equal({ ...MINIMAL_V1_ENDPOINT, platform: "gcfv1", @@ -146,13 +154,23 @@ describe("Test Lab Functions", () => { }); }); - describe("process.env.GCLOUD_PROJECT not set", () => { - it("should not throw if __endpoint is not accessed", () => { - expect(() => testLab.testMatrix().onComplete(() => null)).to.not.throw(Error); + describe('process.env.GCLOUD_PROJECT not set', () => { + it('should not throw if trigger is not accessed', () => { + expect(() => testLab.testMatrix().onComplete(() => null)).to.not.throw( + Error + ); + }); + + it('should throw when trigger is accessed', () => { + expect( + () => testLab.testMatrix().onComplete(() => null).__trigger + ).to.throw(Error); }); - it("should throw when endpoint is accessed", () => { - expect(() => testLab.testMatrix().onComplete(() => null).__endpoint).to.throw(Error); + it('should throw when endpoint is accessed', () => { + expect( + () => testLab.testMatrix().onComplete(() => null).__endpoint + ).to.throw(Error); }); }); }); diff --git a/spec/v2/providers/fixtures.ts b/spec/v2/providers/fixtures.ts index f2c5b60e6..91af94b75 100644 --- a/spec/v2/providers/fixtures.ts +++ b/spec/v2/providers/fixtures.ts @@ -1,4 +1,5 @@ -import * as options from "../../../src/v2/options"; +import { TriggerAnnotation } from '../../../src/v2/core'; +import * as options from '../../../src/v2/options'; export const FULL_OPTIONS: options.GlobalOptions = { region: "us-west1", @@ -13,7 +14,25 @@ export const FULL_OPTIONS: options.GlobalOptions = { ingressSettings: "ALLOW_ALL", cpu: "gcf_gen1", labels: { - hello: "world", + hello: 'world', }, - secrets: ["MY_SECRET"], + secrets: ['MY_SECRET'], +}; + +export const FULL_TRIGGER: TriggerAnnotation = { + platform: 'gcfv2', + regions: ['us-west1'], + availableMemoryMb: 512, + timeout: '60s', + minInstances: 1, + maxInstances: 3, + concurrency: 20, + vpcConnector: 'aConnector', + vpcConnectorEgressSettings: 'ALL_TRAFFIC', + serviceAccountEmail: 'root@aProject.iam.gserviceaccount.com', + ingressSettings: 'ALLOW_ALL', + labels: { + hello: 'world', + }, + secrets: ['MY_SECRET'], }; diff --git a/spec/v2/providers/https.spec.ts b/spec/v2/providers/https.spec.ts index eabf471fb..394723020 100644 --- a/spec/v2/providers/https.spec.ts +++ b/spec/v2/providers/https.spec.ts @@ -23,13 +23,15 @@ import { expect } from "chai"; import * as sinon from "sinon"; -import * as debug from "../../../src/common/debug"; -import * as options from "../../../src/v2/options"; -import * as https from "../../../src/v2/providers/https"; -import { expectedResponseHeaders, MockRequest } from "../../fixtures/mockrequest"; -import { runHandler } from "../../helper"; -import { FULL_OPTIONS } from "./fixtures"; -import { FULL_ENDPOINT, MINIMAL_V2_ENDPOINT } from "../../fixtures"; +import * as debug from '../../../src/common/debug'; +import * as options from '../../../src/v2/options'; +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'; describe("onRequest", () => { beforeEach(() => { @@ -46,6 +48,14 @@ describe("onRequest", () => { res.send(200); }); + expect(result.__trigger).to.deep.equal({ + platform: 'gcfv2', + httpsTrigger: { + allowInsecure: false, + }, + labels: {}, + }); + expect(result.__endpoint).to.deep.equal({ ...MINIMAL_V2_ENDPOINT, platform: "gcfv2", @@ -66,6 +76,15 @@ describe("onRequest", () => { } ); + expect(result.__trigger).to.deep.equal({ + ...FULL_TRIGGER, + httpsTrigger: { + allowInsecure: false, + invoker: ['service-account1@', 'service-account2@'], + }, + regions: ['us-west1', 'us-central1'], + }); + expect(result.__endpoint).to.deep.equal({ ...FULL_ENDPOINT, platform: "gcfv2", @@ -95,6 +114,18 @@ describe("onRequest", () => { } ); + expect(result.__trigger).to.deep.equal({ + platform: 'gcfv2', + httpsTrigger: { + allowInsecure: false, + invoker: ['private'], + }, + concurrency: 20, + minInstances: 3, + regions: ['us-west1', 'us-central1'], + labels: {}, + }); + expect(result.__endpoint).to.deep.equal({ ...MINIMAL_V2_ENDPOINT, platform: "gcfv2", @@ -199,8 +230,18 @@ describe("onCall", () => { delete process.env.GCLOUD_PROJECT; }); - it("should return a minimal trigger/endpoint with appropriate values", () => { - const result = https.onCall(() => 42); + it('should return a minimal trigger/endpoint with appropriate values', () => { + const result = https.onCall((request) => 42); + + expect(result.__trigger).to.deep.equal({ + platform: 'gcfv2', + httpsTrigger: { + allowInsecure: false, + }, + labels: { + 'deployment-callable': 'true', + }, + }); expect(result.__endpoint).to.deep.equal({ ...MINIMAL_V2_ENDPOINT, @@ -210,8 +251,19 @@ describe("onCall", () => { }); }); - it("should create a complex trigger/endpoint with appropriate values", () => { - const result = https.onCall(FULL_OPTIONS, () => 42); + it('should create a complex trigger/endpoint with appropriate values', () => { + const result = https.onCall(FULL_OPTIONS, (request) => 42); + + expect(result.__trigger).to.deep.equal({ + ...FULL_TRIGGER, + httpsTrigger: { + allowInsecure: false, + }, + labels: { + ...FULL_TRIGGER.labels, + 'deployment-callable': 'true', + }, + }); expect(result.__endpoint).to.deep.equal({ ...FULL_ENDPOINT, @@ -235,6 +287,19 @@ describe("onCall", () => { () => 42 ); + expect(result.__trigger).to.deep.equal({ + platform: 'gcfv2', + httpsTrigger: { + allowInsecure: false, + }, + concurrency: 20, + minInstances: 3, + regions: ['us-west1', 'us-central1'], + labels: { + 'deployment-callable': 'true', + }, + }); + expect(result.__endpoint).to.deep.equal({ ...MINIMAL_V2_ENDPOINT, platform: "gcfv2", diff --git a/spec/v2/providers/pubsub.spec.ts b/spec/v2/providers/pubsub.spec.ts index 396dbd406..ebd85acf6 100644 --- a/spec/v2/providers/pubsub.spec.ts +++ b/spec/v2/providers/pubsub.spec.ts @@ -1,10 +1,9 @@ import { expect } from "chai"; -import { CloudEvent } from "../../../src/v2/core"; -import { FULL_OPTIONS } from "./fixtures"; -import { FULL_ENDPOINT, MINIMAL_V2_ENDPOINT } from "../../fixtures"; -import * as options from "../../../src/v2/options"; -import * as pubsub from "../../../src/v2/providers/pubsub"; +import { CloudEvent } from '../../../src/v2/core'; +import * as options from '../../../src/v2/options'; +import * as pubsub from '../../../src/v2/providers/pubsub'; +import { FULL_ENDPOINT, MINIMAL_V2_ENDPOINT, FULL_OPTIONS, FULL_TRIGGER } from './fixtures'; const EVENT_TRIGGER = { eventType: "google.cloud.pubsub.topic.v1.messagePublished", @@ -29,8 +28,14 @@ describe("onMessagePublished", () => { delete process.env.GCLOUD_PROJECT; }); - it("should return a minimal trigger/endpoint with appropriate values", () => { - const result = pubsub.onMessagePublished("topic", () => 42); + it('should return a minimal trigger/endpoint with appropriate values', () => { + const result = pubsub.onMessagePublished('topic', () => 42); + + expect(result.__trigger).to.deep.equal({ + platform: 'gcfv2', + eventTrigger: EVENT_TRIGGER, + labels: {}, + }); expect(result.__endpoint).to.deep.equal({ ...MINIMAL_V2_ENDPOINT, @@ -40,8 +45,16 @@ describe("onMessagePublished", () => { }); }); - it("should create a complex trigger/endpoint with appropriate values", () => { - const result = pubsub.onMessagePublished({ ...FULL_OPTIONS, topic: "topic" }, () => 42); + it('should create a complex trigger/endpoint with appropriate values', () => { + const result = pubsub.onMessagePublished( + { ...FULL_OPTIONS, topic: 'topic' }, + () => 42 + ); + + expect(result.__trigger).to.deep.equal({ + ...FULL_TRIGGER, + eventTrigger: EVENT_TRIGGER, + }); expect(result.__endpoint).to.deep.equal({ ...FULL_ENDPOINT, @@ -66,6 +79,15 @@ describe("onMessagePublished", () => { () => 42 ); + expect(result.__trigger).to.deep.equal({ + platform: 'gcfv2', + concurrency: 20, + minInstances: 3, + regions: ['us-west1'], + labels: {}, + eventTrigger: EVENT_TRIGGER, + }); + expect(result.__endpoint).to.deep.equal({ ...MINIMAL_V2_ENDPOINT, platform: "gcfv2", @@ -88,6 +110,15 @@ describe("onMessagePublished", () => { () => 42 ); + expect(result.__trigger).to.deep.equal({ + platform: 'gcfv2', + minInstances: 3, + regions: ['us-west1'], + labels: {}, + eventTrigger: EVENT_TRIGGER, + failurePolicy: { retry: true }, + }); + expect(result.__endpoint).to.deep.equal({ ...MINIMAL_V2_ENDPOINT, platform: "gcfv2", diff --git a/spec/v2/providers/storage.spec.ts b/spec/v2/providers/storage.spec.ts index 5a820ea7d..9a8e27dab 100644 --- a/spec/v2/providers/storage.spec.ts +++ b/spec/v2/providers/storage.spec.ts @@ -1,9 +1,13 @@ -import { expect } from "chai"; -import * as config from "../../../src/common/config"; -import * as options from "../../../src/v2/options"; -import * as storage from "../../../src/v2/providers/storage"; -import { FULL_OPTIONS } from "./fixtures"; -import { FULL_ENDPOINT, MINIMAL_V2_ENDPOINT } from "../../fixtures"; +import { expect } from 'chai'; +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'; + +const EVENT_TRIGGER = { + eventType: 'event-type', + resource: 'some-bucket', +}; const ENDPOINT_EVENT_TRIGGER = { eventType: "event-type", @@ -72,8 +76,14 @@ describe("v2/storage", () => { delete process.env.GCLOUD_PROJECT; }); - it("should create a minimal trigger/endpoint with bucket", () => { - const result = storage.onOperation("event-type", "some-bucket", () => 42); + it('should create a minimal trigger/endpoint with bucket', () => { + const result = storage.onOperation('event-type', 'some-bucket', () => 42); + + expect(result.__trigger).to.deep.equal({ + platform: 'gcfv2', + labels: {}, + eventTrigger: EVENT_TRIGGER, + }); expect(result.__endpoint).to.deep.equal({ ...MINIMAL_V2_ENDPOINT, @@ -86,7 +96,21 @@ describe("v2/storage", () => { it("should create a minimal trigger/endpoint with opts", () => { config.resetCache({ storageBucket: "default-bucket" }); - const result = storage.onOperation("event-type", { region: "us-west1" }, () => 42); + const result = storage.onOperation( + 'event-type', + { region: 'us-west1' }, + () => 42 + ); + + expect(result.__trigger).to.deep.equal({ + platform: 'gcfv2', + labels: {}, + eventTrigger: { + ...EVENT_TRIGGER, + resource: 'default-bucket', + }, + regions: ['us-west1'], + }); expect(result.__endpoint).to.deep.equal({ ...MINIMAL_V2_ENDPOINT, @@ -100,8 +124,18 @@ describe("v2/storage", () => { }); }); - it("should create a minimal trigger with bucket with opts and bucket", () => { - const result = storage.onOperation("event-type", { bucket: "some-bucket" }, () => 42); + it('should create a minimal trigger with bucket with opts and bucket', () => { + const result = storage.onOperation( + 'event-type', + { bucket: 'some-bucket' }, + () => 42 + ); + + expect(result.__trigger).to.deep.equal({ + platform: 'gcfv2', + labels: {}, + eventTrigger: EVENT_TRIGGER, + }); expect(result.__endpoint).to.deep.equal({ ...MINIMAL_V2_ENDPOINT, @@ -121,6 +155,11 @@ describe("v2/storage", () => { () => 42 ); + expect(result.__trigger).to.deep.equal({ + ...FULL_TRIGGER, + eventTrigger: EVENT_TRIGGER, + }); + expect(result.__endpoint).to.deep.equal({ ...FULL_ENDPOINT, platform: "gcfv2", @@ -145,6 +184,15 @@ describe("v2/storage", () => { () => 42 ); + expect(result.__trigger).to.deep.equal({ + platform: 'gcfv2', + concurrency: 20, + minInstances: 3, + regions: ['us-west1'], + labels: {}, + eventTrigger: EVENT_TRIGGER, + }); + expect(result.__endpoint).to.deep.equal({ ...MINIMAL_V2_ENDPOINT, platform: "gcfv2", @@ -157,7 +205,11 @@ describe("v2/storage", () => { }); }); - describe("onObjectArchived", () => { + describe('onObjectArchived', () => { + const ARCHIVED_TRIGGER = { + ...EVENT_TRIGGER, + eventType: storage.archivedEvent, + }; const ENDPOINT_ARCHIVED_TRIGGER = { ...ENDPOINT_EVENT_TRIGGER, eventType: storage.archivedEvent, @@ -172,6 +224,15 @@ describe("v2/storage", () => { const result = storage.onObjectArchived(() => 42); + expect(result.__trigger).to.deep.equal({ + platform: 'gcfv2', + labels: {}, + eventTrigger: { + ...ARCHIVED_TRIGGER, + resource: 'default-bucket', + }, + }); + expect(result.__endpoint).to.deep.equal({ ...MINIMAL_V2_ENDPOINT, platform: "gcfv2", @@ -183,8 +244,17 @@ describe("v2/storage", () => { }); }); - it("should accept bucket and handler", () => { - const result = storage.onObjectArchived("my-bucket", () => 42); + it('should accept bucket and handler', () => { + const result = storage.onObjectArchived('my-bucket', () => 42); + + expect(result.__trigger).to.deep.equal({ + platform: 'gcfv2', + labels: {}, + eventTrigger: { + ...ARCHIVED_TRIGGER, + resource: 'my-bucket', + }, + }); expect(result.__endpoint).to.deep.equal({ ...MINIMAL_V2_ENDPOINT, @@ -203,6 +273,16 @@ describe("v2/storage", () => { () => 42 ); + expect(result.__trigger).to.deep.equal({ + platform: 'gcfv2', + labels: {}, + eventTrigger: { + ...ARCHIVED_TRIGGER, + resource: 'my-bucket', + }, + regions: ['us-west1'], + }); + expect(result.__endpoint).to.deep.equal({ ...MINIMAL_V2_ENDPOINT, platform: "gcfv2", @@ -218,7 +298,15 @@ describe("v2/storage", () => { it("should accept opts and handler, default bucket", () => { config.resetCache({ storageBucket: "default-bucket" }); - const result = storage.onObjectArchived({ region: "us-west1" }, () => 42); + expect(result.__trigger).to.deep.equal({ + platform: 'gcfv2', + labels: {}, + eventTrigger: { + ...ARCHIVED_TRIGGER, + resource: 'default-bucket', + }, + regions: ['us-west1'], + }); expect(result.__endpoint).to.deep.equal({ ...MINIMAL_V2_ENDPOINT, @@ -233,7 +321,11 @@ describe("v2/storage", () => { }); }); - describe("onObjectFinalized", () => { + describe('onObjectFinalized', () => { + const FINALIZED_TRIGGER = { + ...EVENT_TRIGGER, + eventType: storage.finalizedEvent, + }; const ENDPOINT_FINALIZED_TRIGGER = { ...ENDPOINT_EVENT_TRIGGER, eventType: storage.finalizedEvent, @@ -248,6 +340,15 @@ describe("v2/storage", () => { const result = storage.onObjectFinalized(() => 42); + expect(result.__trigger).to.deep.equal({ + platform: 'gcfv2', + labels: {}, + eventTrigger: { + ...FINALIZED_TRIGGER, + resource: 'default-bucket', + }, + }); + expect(result.__endpoint).to.deep.equal({ ...MINIMAL_V2_ENDPOINT, platform: "gcfv2", @@ -259,8 +360,17 @@ describe("v2/storage", () => { }); }); - it("should accept bucket and handler", () => { - const result = storage.onObjectFinalized("my-bucket", () => 42); + it('should accept bucket and handler', () => { + const result = storage.onObjectFinalized('my-bucket', () => 42); + + expect(result.__trigger).to.deep.equal({ + platform: 'gcfv2', + labels: {}, + eventTrigger: { + ...FINALIZED_TRIGGER, + resource: 'my-bucket', + }, + }); expect(result.__endpoint).to.deep.equal({ ...MINIMAL_V2_ENDPOINT, @@ -279,6 +389,16 @@ describe("v2/storage", () => { () => 42 ); + expect(result.__trigger).to.deep.equal({ + platform: 'gcfv2', + labels: {}, + eventTrigger: { + ...FINALIZED_TRIGGER, + resource: 'my-bucket', + }, + regions: ['us-west1'], + }); + expect(result.__endpoint).to.deep.equal({ ...MINIMAL_V2_ENDPOINT, platform: "gcfv2", @@ -294,7 +414,15 @@ describe("v2/storage", () => { it("should accept opts and handler, default bucket", () => { config.resetCache({ storageBucket: "default-bucket" }); - const result = storage.onObjectFinalized({ region: "us-west1" }, () => 42); + expect(result.__trigger).to.deep.equal({ + platform: 'gcfv2', + labels: {}, + eventTrigger: { + ...FINALIZED_TRIGGER, + resource: 'default-bucket', + }, + regions: ['us-west1'], + }); expect(result.__endpoint).to.deep.equal({ ...MINIMAL_V2_ENDPOINT, @@ -309,7 +437,11 @@ describe("v2/storage", () => { }); }); - describe("onObjectDeleted", () => { + describe('onObjectDeleted', () => { + const DELETED_TRIGGER = { + ...EVENT_TRIGGER, + eventType: storage.deletedEvent, + }; const ENDPOINT_DELETED_TRIGGER = { ...ENDPOINT_EVENT_TRIGGER, eventType: storage.deletedEvent, @@ -324,6 +456,15 @@ describe("v2/storage", () => { const result = storage.onObjectDeleted(() => 42); + expect(result.__trigger).to.deep.equal({ + platform: 'gcfv2', + labels: {}, + eventTrigger: { + ...DELETED_TRIGGER, + resource: 'default-bucket', + }, + }); + expect(result.__endpoint).to.deep.equal({ ...MINIMAL_V2_ENDPOINT, platform: "gcfv2", @@ -335,8 +476,17 @@ describe("v2/storage", () => { }); }); - it("should accept bucket and handler", () => { - const result = storage.onObjectDeleted("my-bucket", () => 42); + it('should accept bucket and handler', () => { + const result = storage.onObjectDeleted('my-bucket', () => 42); + + expect(result.__trigger).to.deep.equal({ + platform: 'gcfv2', + labels: {}, + eventTrigger: { + ...DELETED_TRIGGER, + resource: 'my-bucket', + }, + }); expect(result.__endpoint).to.deep.equal({ ...MINIMAL_V2_ENDPOINT, @@ -349,8 +499,21 @@ describe("v2/storage", () => { }); }); - it("should accept opts and handler", () => { - const result = storage.onObjectDeleted({ bucket: "my-bucket", region: "us-west1" }, () => 42); + it('should accept opts and handler', () => { + const result = storage.onObjectDeleted( + { bucket: 'my-bucket', region: 'us-west1' }, + () => 42 + ); + + expect(result.__trigger).to.deep.equal({ + platform: 'gcfv2', + labels: {}, + eventTrigger: { + ...DELETED_TRIGGER, + resource: 'my-bucket', + }, + regions: ['us-west1'], + }); expect(result.__endpoint).to.deep.equal({ ...MINIMAL_V2_ENDPOINT, @@ -367,7 +530,15 @@ describe("v2/storage", () => { it("should accept opts and handler, default bucket", () => { config.resetCache({ storageBucket: "default-bucket" }); - const result = storage.onObjectDeleted({ region: "us-west1" }, () => 42); + expect(result.__trigger).to.deep.equal({ + platform: 'gcfv2', + labels: {}, + eventTrigger: { + ...DELETED_TRIGGER, + resource: 'default-bucket', + }, + regions: ['us-west1'], + }); expect(result.__endpoint).to.deep.equal({ ...MINIMAL_V2_ENDPOINT, @@ -382,7 +553,11 @@ describe("v2/storage", () => { }); }); - describe("onObjectMetadataUpdated", () => { + describe('onObjectMetadataUpdated', () => { + const METADATA_TRIGGER = { + ...EVENT_TRIGGER, + eventType: storage.metadataUpdatedEvent, + }; const ENDPOINT_METADATA_TRIGGER = { ...ENDPOINT_EVENT_TRIGGER, eventType: storage.metadataUpdatedEvent, @@ -397,6 +572,15 @@ describe("v2/storage", () => { const result = storage.onObjectMetadataUpdated(() => 42); + expect(result.__trigger).to.deep.equal({ + platform: 'gcfv2', + labels: {}, + eventTrigger: { + ...METADATA_TRIGGER, + resource: 'default-bucket', + }, + }); + expect(result.__endpoint).to.deep.equal({ ...MINIMAL_V2_ENDPOINT, platform: "gcfv2", @@ -408,8 +592,17 @@ describe("v2/storage", () => { }); }); - it("should accept bucket and handler", () => { - const result = storage.onObjectMetadataUpdated("my-bucket", () => 42); + it('should accept bucket and handler', () => { + const result = storage.onObjectMetadataUpdated('my-bucket', () => 42); + + expect(result.__trigger).to.deep.equal({ + platform: 'gcfv2', + labels: {}, + eventTrigger: { + ...METADATA_TRIGGER, + resource: 'my-bucket', + }, + }); expect(result.__endpoint).to.deep.equal({ ...MINIMAL_V2_ENDPOINT, @@ -428,6 +621,16 @@ describe("v2/storage", () => { () => 42 ); + expect(result.__trigger).to.deep.equal({ + platform: 'gcfv2', + labels: {}, + eventTrigger: { + ...METADATA_TRIGGER, + resource: 'my-bucket', + }, + regions: ['us-west1'], + }); + expect(result.__endpoint).to.deep.equal({ ...MINIMAL_V2_ENDPOINT, platform: "gcfv2", @@ -443,7 +646,15 @@ describe("v2/storage", () => { it("should accept opts and handler, default bucket", () => { config.resetCache({ storageBucket: "default-bucket" }); - const result = storage.onObjectMetadataUpdated({ region: "us-west1" }, () => 42); + expect(result.__trigger).to.deep.equal({ + platform: 'gcfv2', + labels: {}, + eventTrigger: { + ...METADATA_TRIGGER, + resource: 'default-bucket', + }, + regions: ['us-west1'], + }); expect(result.__endpoint).to.deep.equal({ ...MINIMAL_V2_ENDPOINT, diff --git a/spec/v2/providers/tasks.spec.ts b/spec/v2/providers/tasks.spec.ts index 8131a3725..b29831a1b 100644 --- a/spec/v2/providers/tasks.spec.ts +++ b/spec/v2/providers/tasks.spec.ts @@ -23,12 +23,11 @@ import { expect } from "chai"; import { ManifestEndpoint } from "../../../src/runtime/manifest"; -import { onTaskDispatched, Request } from "../../../src/v2/providers/tasks"; -import { MockRequest } from "../../fixtures/mockrequest"; -import { runHandler } from "../../helper"; -import { FULL_OPTIONS } from "./fixtures"; -import { FULL_ENDPOINT, MINIMAL_V2_ENDPOINT } from "../../fixtures"; -import * as options from "../../../src/v2/options"; +import * as options from '../../../src/v2/options'; +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'; const MINIMIAL_TASK_QUEUE_TRIGGER: ManifestEndpoint["taskQueueTrigger"] = { rateLimits: { @@ -54,8 +53,14 @@ describe("onTaskDispatched", () => { delete process.env.GCLOUD_PROJECT; }); - it("should return a minimal trigger/endpoint with appropriate values", () => { - const result = onTaskDispatched(() => undefined); + it('should return a minimal trigger/endpoint with appropriate values', () => { + const result = onTaskDispatched(() => {}); + + expect(result.__trigger).to.deep.equal({ + platform: 'gcfv2', + taskQueueTrigger: {}, + labels: {}, + }); expect(result.__endpoint).to.deep.equal({ ...MINIMAL_V2_ENDPOINT, @@ -80,11 +85,29 @@ describe("onTaskDispatched", () => { maxConcurrentDispatches: 5, maxDispatchesPerSecond: 10, }, - invoker: "private", + invoker: 'private', }, - () => undefined + () => {} ); + expect(result.__trigger).to.deep.equal({ + ...FULL_TRIGGER, + taskQueueTrigger: { + retryConfig: { + maxAttempts: 4, + maxRetrySeconds: 10, + maxDoublings: 3, + minBackoffSeconds: 1, + maxBackoffSeconds: 2, + }, + rateLimits: { + maxConcurrentDispatches: 5, + maxDispatchesPerSecond: 10, + }, + invoker: ['private'], + }, + }); + expect(result.__endpoint).to.deep.equal({ ...FULL_ENDPOINT, platform: "gcfv2", @@ -117,9 +140,18 @@ describe("onTaskDispatched", () => { region: "us-west1", minInstances: 3, }, - () => undefined + (request) => {} ); + expect(result.__trigger).to.deep.equal({ + platform: 'gcfv2', + taskQueueTrigger: {}, + concurrency: 20, + minInstances: 3, + regions: ['us-west1'], + labels: {}, + }); + expect(result.__endpoint).to.deep.equal({ ...MINIMAL_V2_ENDPOINT, platform: "gcfv2", diff --git a/src/v1/cloud-functions.ts b/src/v1/cloud-functions.ts index 1b2f397d2..596b10027 100644 --- a/src/v1/cloud-functions.ts +++ b/src/v1/cloud-functions.ts @@ -20,9 +20,16 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -import { Request, Response } from "express"; -import { warn } from "../logger"; -import { DeploymentOptions, RESET_VALUE } from "./function-configuration"; +import { Request, Response } from 'express'; +import * as _ from 'lodash'; +import { warn } from '../logger'; +import { + DEFAULT_FAILURE_POLICY, + DeploymentOptions, + RESET_VALUE, + FailurePolicy, + Schedule, +} from './function-configuration'; export { Request, Response }; import { convertIfPresent, copyIfPresent } from "../common/encoding"; import { @@ -195,6 +202,95 @@ export interface EventContext> { timestamp: string; } +/** + * The Functions interface for events that change state, such as + * Realtime Database or Cloud Firestore `onWrite` and `onUpdate`. + * + * For more information about the format used to construct `Change` objects, see + * [`cloud-functions.ChangeJson`](/docs/reference/functions/cloud_functions_.changejson). + * + */ +export class Change { + constructor(public before: T, public after: T) {} +} + +/** + * `ChangeJson` is the JSON format used to construct a Change object. + */ +export interface ChangeJson { + /** + * Key-value pairs representing state of data after the change. + */ + after?: any; + /** + * Key-value pairs representing state of data before the change. If + * `fieldMask` is set, then only fields that changed are present in `before`. + */ + before?: any; + /** + * @hidden + * Comma-separated string that represents names of fields that changed. + */ + fieldMask?: string; +} + +export namespace Change { + /** @hidden */ + function reinterpretCast(x: any) { + return x as T; + } + + /** + * @hidden + * Factory method for creating a Change from a `before` object and an `after` + * object. + */ + export function fromObjects(before: T, after: T) { + return new Change(before, after); + } + + /** + * @hidden + * Factory method for creating a Change from a JSON and an optional customizer + * function to be applied to both the `before` and the `after` fields. + */ + export function fromJSON( + json: ChangeJson, + customizer: (x: any) => T = reinterpretCast + ): Change { + let before = { ...json.before }; + if (json.fieldMask) { + before = applyFieldMask(before, json.after, json.fieldMask); + } + + return Change.fromObjects( + customizer(before || {}), + customizer(json.after || {}) + ); + } + + /** @hidden */ + export function applyFieldMask( + sparseBefore: any, + after: any, + fieldMask: string + ) { + const before = { ...after }; + const masks = fieldMask.split(','); + + masks.forEach((mask) => { + const val = _.get(sparseBefore, mask); + if (typeof val === 'undefined') { + _.unset(before, mask); + } else { + _.set(before, mask, val); + } + }); + + return before; + } +} + /** * Resource is a standard format for defining a resource * (google.rpc.context.AttributeContext.Resource). In Cloud Functions, it is the @@ -217,6 +313,36 @@ export interface Resource { labels?: { [tag: string]: string }; } +/** + * TriggerAnnotion is used internally by the firebase CLI to understand what + * type of Cloud Function to deploy. + */ +interface TriggerAnnotation { + availableMemoryMb?: number; + blockingTrigger?: { + eventType: string; + options?: Record; + }; + eventTrigger?: { + eventType: string; + resource: string; + service: string; + }; + failurePolicy?: FailurePolicy; + httpsTrigger?: { + invoker?: string[]; + }; + labels?: { [key: string]: string }; + regions?: string[]; + schedule?: Schedule; + timeout?: Duration; + vpcConnector?: string; + vpcConnectorEgressSettings?: string; + serviceAccountEmail?: string; + ingressSettings?: string; + secrets?: string[]; +} + /** * A Runnable has a `run` method which directly invokes the user-defined * function - useful for unit testing. @@ -239,6 +365,9 @@ export interface Runnable { export interface HttpsFunction { (req: Request, resp: Response): void | Promise; + /** @alpha */ + __trigger: TriggerAnnotation; + /** @alpha */ __endpoint: ManifestEndpoint; @@ -259,6 +388,9 @@ export interface BlockingFunction { /** @public */ (req: Request, resp: Response): void | Promise; + /** @alpha */ + __trigger: TriggerAnnotation; + /** @alpha */ __endpoint: ManifestEndpoint; @@ -276,6 +408,9 @@ export interface BlockingFunction { export interface CloudFunction extends Runnable { (input: any, context?: any): PromiseLike | any; + /** @alpha */ + __trigger: TriggerAnnotation; + /** @alpha */ __endpoint: ManifestEndpoint; @@ -367,7 +502,27 @@ export function makeCloudFunction({ return Promise.resolve(promise); }; - Object.defineProperty(cloudFunction, "__endpoint", { + Object.defineProperty(cloudFunction, '__trigger', { + get: () => { + if (triggerResource() == null) { + return {}; + } + + const trigger: any = _.assign(optionsToTrigger(options), { + eventTrigger: { + resource: triggerResource(), + eventType: legacyEventType || provider + '.' + eventType, + service, + }, + }); + if (!_.isEmpty(labels)) { + trigger.labels = { ...trigger.labels, ...labels }; + } + return trigger; + }, + }); + + Object.defineProperty(cloudFunction, '__endpoint', { get: () => { if (triggerResource() == null) { return undefined; @@ -472,8 +627,70 @@ function _detectAuthType(event: Event) { return "UNAUTHENTICATED"; } -/** @internal */ -export function optionsToEndpoint(options: DeploymentOptions): ManifestEndpoint { +/** @hidden */ +export function optionsToTrigger(options: DeploymentOptions) { + const trigger: any = {}; + copyIfPresent( + trigger, + options, + 'regions', + 'schedule', + 'minInstances', + 'maxInstances', + 'ingressSettings', + 'vpcConnectorEgressSettings', + 'vpcConnector', + 'labels', + 'secrets' + ); + convertIfPresent( + trigger, + options, + 'failurePolicy', + 'failurePolicy', + (policy) => { + if (policy === false) { + return undefined; + } else if (policy === true) { + return DEFAULT_FAILURE_POLICY; + } else { + return policy; + } + } + ); + convertIfPresent( + trigger, + options, + 'timeout', + 'timeoutSeconds', + durationFromSeconds + ); + convertIfPresent(trigger, options, 'availableMemoryMb', 'memory', (mem) => { + const memoryLookup = { + '128MB': 128, + '256MB': 256, + '512MB': 512, + '1GB': 1024, + '2GB': 2048, + '4GB': 4096, + '8GB': 8192, + }; + return memoryLookup[mem]; + }); + convertIfPresent( + trigger, + options, + 'serviceAccountEmail', + 'serviceAccount', + serviceAccountFromShorthand + ); + + return trigger; +} + +export function optionsToEndpoint( + options: DeploymentOptions +): ManifestEndpoint { const endpoint: ManifestEndpoint = {}; copyIfPresent( endpoint, diff --git a/src/v1/handler-builder.ts b/src/v1/handler-builder.ts index e69de29bb..7b2831e7c 100644 --- a/src/v1/handler-builder.ts +++ b/src/v1/handler-builder.ts @@ -0,0 +1,379 @@ +// The MIT License (MIT) +// +// Copyright (c) 2017 Firebase +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +import * as express from 'express'; + +import { apps } from '../apps'; +import { CloudFunction, EventContext, HttpsFunction } from './cloud-functions'; +import * as analytics from './providers/analytics'; +import * as auth from './providers/auth'; +import * as database from './providers/database'; +import * as firestore from './providers/firestore'; +import * as https from './providers/https'; +import * as pubsub from './providers/pubsub'; +import * as remoteConfig from './providers/remoteConfig'; +import * as storage from './providers/storage'; +import * as tasks from './providers/tasks'; +import * as testLab from './providers/testLab'; + +/** + * The `HandlerBuilder` class facilitates the writing of functions by developers + * building Firebase Extensions as well as developers who want to use the gcloud CLI or + * Google Cloud Console to deploy their functions. + * + * **Do not use `HandlerBuilder` when writing normal functions for deployment via + * the Firebase CLI.** For normal purposes, use + * [`FunctionBuilder`](/docs/reference/functions/function_builder_.functionbuilder). + */ +export class HandlerBuilder { + constructor() {} + + /** + * Create a handler for HTTPS events. + + * `onRequest` handles an HTTPS request and has the same signature as an Express app. + * + * @example + * ```javascript + * exports.myFunction = functions.handler.https.onRequest((req, res) => { ... }) + * ``` + * + * `onCall` declares a callable function for clients to call using a Firebase SDK. + * + * @example + * ```javascript + * exports.myFunction = functions.handler.https.onCall((data, context) => { ... }) + * ``` + */ + get https() { + return { + onRequest: ( + handler: (req: express.Request, resp: express.Response) => void + ): HttpsFunction => { + const func = https._onRequestWithOptions(handler, {}); + func.__trigger = {}; + func.__endpoint = undefined; + func.__requiredAPIs = undefined; + return func; + }, + onCall: ( + handler: ( + data: any, + context: https.CallableContext + ) => any | Promise + ): HttpsFunction => { + const func = https._onCallWithOptions(handler, {}); + func.__trigger = {}; + func.__endpoint = undefined; + func.__requiredAPIs = undefined; + return func; + }, + }; + } + + /** + * Create a handler for tasks functions. + * + * @example + * ```javascript + * exports.myFunction = functions.handler.tasks.onDispatch((data, context) => { ... }) + * ``` + */ + /** @hidden */ + get tasks() { + return { + get taskQueue() { + return { + onDispatch: ( + handler: ( + data: any, + context: tasks.TaskContext + ) => void | Promise + ): HttpsFunction => { + const builder = new tasks.TaskQueueBuilder(); + const func = builder.onDispatch(handler); + func.__trigger = {}; + func.__endpoint = undefined; + func.__requiredAPIs = undefined; + return func; + }, + }; + }, + }; + } + + /** + * Create a handler for Firebase Realtime Database events. + * + * `ref.onCreate` handles the creation of new data. + * + * @example + * ```javascript + * exports.myFunction = functions.handler.database.ref.onCreate((snap, context) => { ... }) + * ``` + * + * `ref.onUpdate` handles updates to existing data. + * + * @example + * ```javascript + * exports.myFunction = functions.handler.database.ref.onUpdate((change, context) => { ... }) + * ``` + + * `ref.onDelete` handles the deletion of existing data. + * + * @example + * ```javascript + * exports.myFunction = functions.handler.database.ref.onDelete((snap, context) => { ... }) + * ``` + + * `ref.onWrite` handles the creation, update, or deletion of data. + * + * @example + * ```javascript + * exports.myFunction = functions.handler.database.ref.onWrite((change, context) => { ... }) + * ``` + */ + get database() { + return { + /** @hidden */ + get instance() { + return { + get ref() { + return new database.RefBuilder(apps(), () => null, {}); + }, + }; + }, + get ref() { + return new database.RefBuilder(apps(), () => null, {}); + }, + }; + } + + /** + * Create a handler for Cloud Firestore events. + * + * `document.onCreate` handles the creation of new documents. + * + * @example + * ```javascript + * exports.myFunction = functions.handler.firestore.document.onCreate((snap, context) => { ... }) + * ``` + + * `document.onUpdate` handles updates to existing documents. + * + * @example + * ```javascript + * exports.myFunction = functions.handler.firestore.document.onUpdate((change, context) => { ... }) + * ``` + + * `document.onDelete` handles the deletion of existing documents. + * + * @example + * ```javascript + * exports.myFunction = functions.handler.firestore.document.onDelete((snap, context) => + * { ... }) + * ``` + + * `document.onWrite` handles the creation, update, or deletion of documents. + * + * @example + * ```javascript + * exports.myFunction = functions.handler.firestore.document.onWrite((change, context) => + * { ... }) + * ``` + */ + get firestore() { + return { + get document() { + return new firestore.DocumentBuilder(() => null, {}); + }, + /** @hidden */ + get namespace() { + return new firestore.DocumentBuilder(() => null, {}); + }, + /** @hidden */ + get database() { + return new firestore.DocumentBuilder(() => null, {}); + }, + }; + } + + /** + * Create a handler for Firebase Remote Config events. + + * `remoteConfig.onUpdate` handles events that update a Remote Config template. + + * @example + * ```javascript + * exports.myFunction = functions.handler.remoteConfig.onUpdate() => { ... }) + * ``` + */ + get remoteConfig() { + return { + onUpdate: ( + handler: ( + version: remoteConfig.TemplateVersion, + context: EventContext + ) => PromiseLike | any + ): CloudFunction => { + return new remoteConfig.UpdateBuilder(() => null, {}).onUpdate(handler); + }, + }; + } + + /** + * Create a handler for Google Analytics events. + + * `event.onLog` handles the logging of Analytics conversion events. + + * @example + * ```javascript + * exports.myFunction = functions.handler.analytics.event.onLog((event) => { ... }) + * ``` + */ + get analytics() { + return { + get event() { + return new analytics.AnalyticsEventBuilder(() => null, {}); + }, + }; + } + + /** + * Create a handler for Cloud Storage for Firebase events. + * + * `object.onArchive` handles the archiving of Storage objects. + * + * @example + * ```javascript + * exports.myFunction = functions.handler.storage.object.onArchive((object) => { ... }) + * ``` + + * `object.onDelete` handles the deletion of Storage objects. + * + * @example + * ```javascript + * exports.myFunction = functions.handler.storage.object.onDelete((object) => { ... }) + * ``` + + * `object.onFinalize` handles the creation of Storage objects. + * + * @example + * ```javascript + * exports.myFunction = functions.handler.storage.object.onFinalize((object) => + * { ... }) + * ``` + + * `object.onMetadataUpdate` handles changes to the metadata of existing Storage objects. + * + * @example + * ```javascript + * exports.myFunction = functions.handler.storage.object.onMetadataUpdate((object) => + * { ... }) + * ``` + */ + get storage() { + return { + get bucket() { + return new storage.BucketBuilder(() => null, {}).object(); + }, + + get object() { + return new storage.ObjectBuilder(() => null, {}); + }, + }; + } + + /** + * Create a handler for Cloud Pub/Sub events. + * + * `topic.onPublish` handles messages published to a Pub/Sub topic from SDKs, Cloud Console, or gcloud CLI. + * + * @example + * ```javascript + * exports.myFunction = functions.handler.pubsub.topic.onPublish((message) => { ... }) + * ``` + + * `schedule.onPublish` handles messages published to a Pub/Sub topic on a schedule. + * + * @example + * ```javascript + * exports.myFunction = functions.handler.pubsub.schedule.onPublish((message) => { ... }) + * ``` + */ + get pubsub() { + return { + get topic() { + return new pubsub.TopicBuilder(() => null, {}); + }, + get schedule() { + return new pubsub.ScheduleBuilder(() => null, {}); + }, + }; + } + + /** + * Create a handler for Firebase Authentication events. + * + * `user.onCreate` handles the creation of users. + * + * @example + * ```javascript + * exports.myFunction = functions.handler.auth.user.onCreate((user) => { ... }) + * ``` + + * `user.onDelete` handles the deletion of users. + * + * @example + * ```javascript + * exports.myFunction = functions.handler.auth.user.onDelete((user => { ... }) + * ``` + + */ + get auth() { + return { + get user() { + return new auth.UserBuilder(() => null, {}, {}); + }, + }; + } + + /** + * Create a handler for Firebase Test Lab events. + + * `testMatrix.onComplete` handles the completion of a test matrix. + + * @example + * ```javascript + * exports.myFunction = functions.handler.testLab.testMatrix.onComplete((testMatrix) => { ... }) + * ``` + */ + get testLab() { + return { + get testMatrix() { + return new testLab.TestMatrixBuilder(() => null, {}); + }, + }; + } +} + +export let handler = new HandlerBuilder(); diff --git a/src/v1/providers/auth.ts b/src/v1/providers/auth.ts index 8ec455a8b..b70ae70c1 100644 --- a/src/v1/providers/auth.ts +++ b/src/v1/providers/auth.ts @@ -40,8 +40,9 @@ import { EventContext, makeCloudFunction, optionsToEndpoint, -} from "../cloud-functions"; -import { DeploymentOptions } from "../function-configuration"; + optionsToTrigger, +} from '../cloud-functions'; +import { DeploymentOptions } from '../function-configuration'; import { initV1Endpoint } from "../../runtime/manifest"; // TODO: yank in next breaking change release @@ -213,6 +214,19 @@ export class UserBuilder { const legacyEventType = `providers/cloud.auth/eventTypes/user.${eventType}`; + func.__trigger = { + labels: {}, + ...optionsToTrigger(this.options), + blockingTrigger: { + eventType: legacyEventType, + options: { + accessToken, + idToken, + refreshToken, + }, + }, + }; + func.__endpoint = { platform: "gcfv1", labels: {}, diff --git a/src/v1/providers/https.ts b/src/v1/providers/https.ts index d9d1a9b29..0cb5fa7d6 100644 --- a/src/v1/providers/https.ts +++ b/src/v1/providers/https.ts @@ -29,9 +29,14 @@ import { HttpsError, onCallHandler, Request, -} from "../../common/providers/https"; -import { HttpsFunction, optionsToEndpoint, Runnable } from "../cloud-functions"; -import { DeploymentOptions } from "../function-configuration"; +} from '../../common/providers/https'; +import { + HttpsFunction, + optionsToEndpoint, + optionsToTrigger, + Runnable, +} from '../cloud-functions'; +import { DeploymentOptions } from '../function-configuration'; import { initV1Endpoint } from "../../runtime/manifest"; export { Request, CallableContext, FunctionsErrorCode, HttpsError }; @@ -101,6 +106,13 @@ export function _onCallWithOptions( fixedLen ); + func.__trigger = { + labels: {}, + ...optionsToTrigger(options), + httpsTrigger: {}, + }; + func.__trigger.labels['deployment-callable'] = 'true'; + func.__endpoint = { platform: "gcfv1", labels: {}, diff --git a/src/v1/providers/tasks.ts b/src/v1/providers/tasks.ts index 20a516e68..5bee96997 100644 --- a/src/v1/providers/tasks.ts +++ b/src/v1/providers/tasks.ts @@ -36,7 +36,7 @@ import { ManifestEndpoint, ManifestRequiredAPI, } from "../../runtime/manifest"; -import { optionsToEndpoint } from "../cloud-functions"; +import { optionsToEndpoint, optionsToTrigger } from '../cloud-functions'; import { DeploymentOptions } from "../function-configuration"; export { RetryConfig, RateLimits, TaskContext }; @@ -65,6 +65,9 @@ export interface TaskQueueOptions { export interface TaskQueueFunction { (req: Request, res: express.Response): Promise; + /** @alpha */ + __trigger: unknown; + /** @alpha */ __endpoint: ManifestEndpoint; @@ -106,6 +109,20 @@ export class TaskQueueBuilder { const fixedLen = (data: any, context: TaskContext) => handler(data, context); const func: any = onDispatchHandler(fixedLen); + func.__trigger = { + ...optionsToTrigger(this.depOpts || {}), + taskQueueTrigger: {}, + }; + copyIfPresent(func.__trigger.taskQueueTrigger, this.tqOpts, 'retryConfig'); + copyIfPresent(func.__trigger.taskQueueTrigger, this.tqOpts, 'rateLimits'); + convertIfPresent( + func.__trigger.taskQueueTrigger, + this.tqOpts, + 'invoker', + 'invoker', + convertInvoker + ); + func.__endpoint = { platform: "gcfv1", ...initV1Endpoint(this.depOpts), diff --git a/src/v2/core.ts b/src/v2/core.ts index 7b79654e1..afdc7c5eb 100644 --- a/src/v2/core.ts +++ b/src/v2/core.ts @@ -30,7 +30,36 @@ import { ManifestEndpoint } from "../runtime/manifest"; export { Change }; -export { ParamsOf } from "../common/params"; +/** @internal */ +export interface TriggerAnnotation { + platform?: string; + concurrency?: number; + minInstances?: number; + maxInstances?: number; + availableMemoryMb?: number; + eventTrigger?: { + eventType: string; + resource: string; + service: string; + }; + failurePolicy?: { retry: boolean }; + httpsTrigger?: { + invoker?: string[]; + }; + labels?: { [key: string]: string }; + regions?: string[]; + timeout?: string; + vpcConnector?: string; + vpcConnectorEgressSettings?: string; + serviceAccountEmail?: string; + ingressSettings?: string; + secrets?: string[]; + blockingTrigger?: { + eventType: string; + options?: Record; + }; + // TODO: schedule +} /** * A CloudEventBase is the base of a cross-platform format for encoding a serverless event. @@ -70,6 +99,8 @@ export interface CloudEvent { export interface CloudFunction> { (raw: CloudEvent): any | Promise; + /** @alpha */ + __trigger?: unknown; /** @alpha */ __endpoint: ManifestEndpoint; diff --git a/src/v2/options.ts b/src/v2/options.ts index 91b9b8de6..a94982d71 100644 --- a/src/v2/options.ts +++ b/src/v2/options.ts @@ -25,13 +25,19 @@ * @packageDocumentation */ -import { convertIfPresent, copyIfPresent } from "../common/encoding"; +import { + convertIfPresent, + copyIfPresent, + durationFromSeconds, + serviceAccountFromShorthand, +} from "../common/encoding"; import { RESET_VALUE, ResetValue } from "../common/options"; -import { ManifestEndpoint } from "../runtime/manifest"; +import { ManifestEndpoint } from '../runtime/manifest'; +import { TriggerAnnotation } from './core'; import { declaredParams, Expression } from "../params"; import { ParamSpec, SecretParam } from "../params/types"; -import { HttpsOptions } from "./providers/https"; -import * as logger from "../logger"; +import { HttpsOptions } from './providers/https'; +import * as logger from '../logger'; export { RESET_VALUE } from "../common/options"; @@ -250,6 +256,68 @@ export interface EventHandlerOptions extends Omit { + return MemoryOptionToMB[mem]; + } + ); + convertIfPresent(annotation, opts, 'regions', 'region', (region) => { + if (typeof region === 'string') { + return [region]; + } + return region; + }); + convertIfPresent( + annotation, + opts, + 'serviceAccountEmail', + 'serviceAccount', + serviceAccountFromShorthand + ); + convertIfPresent( + annotation, + opts, + 'timeout', + 'timeoutSeconds', + durationFromSeconds + ); + convertIfPresent( + annotation, + (opts as any) as EventHandlerOptions, + 'failurePolicy', + 'retry', + (retry: boolean) => { + return retry ? { retry: true } : null; + } + ); + + return annotation; +} + /** * Apply GlobalOptions to endpoint manifest. * @internal diff --git a/src/v2/providers/https.ts b/src/v2/providers/https.ts index 0bbc43d18..8f00a3aa5 100644 --- a/src/v2/providers/https.ts +++ b/src/v2/providers/https.ts @@ -235,7 +235,38 @@ export function onRequest( }; } - handler = wrapTraceContext(handler); + Object.defineProperty(handler, '__trigger', { + get: () => { + const baseOpts = options.optionsToTriggerAnnotations( + options.getGlobalOptions() + ); + // global options calls region a scalar and https allows it to be an array, + // but optionsToTriggerAnnotations handles both cases. + const specificOpts = options.optionsToTriggerAnnotations( + opts as options.GlobalOptions + ); + const trigger: any = { + platform: 'gcfv2', + ...baseOpts, + ...specificOpts, + labels: { + ...baseOpts?.labels, + ...specificOpts?.labels, + }, + httpsTrigger: { + allowInsecure: false, + }, + }; + convertIfPresent( + trigger.httpsTrigger, + opts, + 'invoker', + 'invoker', + convertInvoker + ); + return trigger; + }, + }); const baseOpts = options.optionsToEndpoint(options.getGlobalOptions()); // global options calls region a scalar and https allows it to be an array, diff --git a/src/v2/providers/tasks.ts b/src/v2/providers/tasks.ts index f6e63b210..4b31ace79 100644 --- a/src/v2/providers/tasks.ts +++ b/src/v2/providers/tasks.ts @@ -207,8 +207,39 @@ export function onTaskDispatched( const fixedLen = (req: Request) => handler(req); const func: any = wrapTraceContext(onDispatchHandler(fixedLen)); - const globalOpts = options.getGlobalOptions(); - const baseOpts = options.optionsToEndpoint(globalOpts); + Object.defineProperty(func, '__trigger', { + get: () => { + const baseOpts = options.optionsToTriggerAnnotations( + options.getGlobalOptions() + ); + // global options calls region a scalar and https allows it to be an array, + // but optionsToTriggerAnnotations handles both cases. + const specificOpts = options.optionsToTriggerAnnotations( + opts as options.GlobalOptions + ); + const taskQueueTrigger: Record = {}; + copyIfPresent(taskQueueTrigger, opts, 'retryConfig', 'rateLimits'); + convertIfPresent( + taskQueueTrigger, + opts, + 'invoker', + 'invoker', + convertInvoker + ); + return { + platform: 'gcfv2', + ...baseOpts, + ...specificOpts, + labels: { + ...baseOpts?.labels, + ...specificOpts?.labels, + }, + taskQueueTrigger, + }; + }, + }); + + const baseOpts = options.optionsToEndpoint(options.getGlobalOptions()); // global options calls region a scalar and https allows it to be an array, // but optionsToManifestEndpoint handles both cases. const specificOpts = options.optionsToEndpoint(opts as options.GlobalOptions); From 75bd9df397640f79a4c7fae8dd016dca41e8487b Mon Sep 17 00:00:00 2001 From: Tyler Stark Date: Sun, 23 Oct 2022 14:22:38 -0500 Subject: [PATCH 2/8] Resolve stale conflicts from #1153 This commit resolves faulty merge-conflicted code that did not take into account the changes from #1153. --- src/v1/cloud-functions.ts | 90 --------------------------------------- 1 file changed, 90 deletions(-) diff --git a/src/v1/cloud-functions.ts b/src/v1/cloud-functions.ts index 596b10027..53c01e7ce 100644 --- a/src/v1/cloud-functions.ts +++ b/src/v1/cloud-functions.ts @@ -21,7 +21,6 @@ // SOFTWARE. import { Request, Response } from 'express'; -import * as _ from 'lodash'; import { warn } from '../logger'; import { DEFAULT_FAILURE_POLICY, @@ -202,95 +201,6 @@ export interface EventContext> { timestamp: string; } -/** - * The Functions interface for events that change state, such as - * Realtime Database or Cloud Firestore `onWrite` and `onUpdate`. - * - * For more information about the format used to construct `Change` objects, see - * [`cloud-functions.ChangeJson`](/docs/reference/functions/cloud_functions_.changejson). - * - */ -export class Change { - constructor(public before: T, public after: T) {} -} - -/** - * `ChangeJson` is the JSON format used to construct a Change object. - */ -export interface ChangeJson { - /** - * Key-value pairs representing state of data after the change. - */ - after?: any; - /** - * Key-value pairs representing state of data before the change. If - * `fieldMask` is set, then only fields that changed are present in `before`. - */ - before?: any; - /** - * @hidden - * Comma-separated string that represents names of fields that changed. - */ - fieldMask?: string; -} - -export namespace Change { - /** @hidden */ - function reinterpretCast(x: any) { - return x as T; - } - - /** - * @hidden - * Factory method for creating a Change from a `before` object and an `after` - * object. - */ - export function fromObjects(before: T, after: T) { - return new Change(before, after); - } - - /** - * @hidden - * Factory method for creating a Change from a JSON and an optional customizer - * function to be applied to both the `before` and the `after` fields. - */ - export function fromJSON( - json: ChangeJson, - customizer: (x: any) => T = reinterpretCast - ): Change { - let before = { ...json.before }; - if (json.fieldMask) { - before = applyFieldMask(before, json.after, json.fieldMask); - } - - return Change.fromObjects( - customizer(before || {}), - customizer(json.after || {}) - ); - } - - /** @hidden */ - export function applyFieldMask( - sparseBefore: any, - after: any, - fieldMask: string - ) { - const before = { ...after }; - const masks = fieldMask.split(','); - - masks.forEach((mask) => { - const val = _.get(sparseBefore, mask); - if (typeof val === 'undefined') { - _.unset(before, mask); - } else { - _.set(before, mask, val); - } - }); - - return before; - } -} - /** * Resource is a standard format for defining a resource * (google.rpc.context.AttributeContext.Resource). In Cloud Functions, it is the From 0c3aac300a0051fc380d90d4d1b74fec39e9f819 Mon Sep 17 00:00:00 2001 From: Tyler Stark Date: Sun, 23 Oct 2022 14:30:10 -0500 Subject: [PATCH 3/8] Adding back DEFAULT_FAILURE_POLICY (was removed in v4 pr) --- src/v1/function-configuration.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/v1/function-configuration.ts b/src/v1/function-configuration.ts index 848d103ad..1f0d9c4b5 100644 --- a/src/v1/function-configuration.ts +++ b/src/v1/function-configuration.ts @@ -158,6 +158,10 @@ export interface FailurePolicy { retry: Record; } +export const DEFAULT_FAILURE_POLICY: FailurePolicy = { + retry: {}, +}; + export const MAX_NUMBER_USER_LABELS = 58; /** From d5e580406f1cf5a2c2104fd13c41f1be9e0048ff Mon Sep 17 00:00:00 2001 From: Tyler Stark Date: Sun, 23 Oct 2022 14:33:27 -0500 Subject: [PATCH 4/8] npm run format:ts --- spec/v1/cloud-functions.spec.ts | 88 ++-- spec/v1/function-builder.spec.ts | 46 +- spec/v1/providers/analytics.spec.ts | 78 ++- spec/v1/providers/auth.spec.ts | 94 ++-- spec/v1/providers/database.spec.ts | 676 ++++++++++++------------- spec/v1/providers/firestore.spec.ts | 187 +++---- spec/v1/providers/https.spec.ts | 34 +- spec/v1/providers/pubsub.spec.ts | 158 +++--- spec/v1/providers/remoteConfig.spec.ts | 53 +- spec/v1/providers/storage.spec.ts | 309 +++++------ spec/v1/providers/tasks.spec.ts | 14 +- spec/v1/providers/testLab.spec.ts | 26 +- spec/v2/providers/fixtures.ts | 26 +- spec/v2/providers/https.spec.ts | 43 +- spec/v2/providers/pubsub.spec.ts | 29 +- spec/v2/providers/storage.spec.ts | 157 +++--- spec/v2/providers/tasks.spec.ts | 22 +- src/v1/cloud-functions.ts | 84 ++- src/v1/handler-builder.ts | 40 +- src/v1/providers/auth.ts | 4 +- src/v1/providers/https.ts | 13 +- src/v1/providers/tasks.ts | 10 +- src/v2/options.ts | 58 +-- src/v2/providers/https.ts | 20 +- src/v2/providers/tasks.ts | 22 +- 25 files changed, 999 insertions(+), 1292 deletions(-) diff --git a/spec/v1/cloud-functions.spec.ts b/spec/v1/cloud-functions.spec.ts index 8033ebaad..73c19e7b9 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 put a __trigger/__endpoint on the returned CloudFunction', () => { + it("should put a __trigger/__endpoint on the returned CloudFunction", () => { const cf = makeCloudFunction({ provider: "mock.provider", eventType: "mock.event", @@ -53,9 +53,9 @@ describe("makeCloudFunction", () => { expect(cf.__trigger).to.deep.equal({ eventTrigger: { - eventType: 'mock.provider.mock.event', - resource: 'resource', - service: 'service', + eventType: "mock.provider.mock.event", + resource: "resource", + service: "service", }, }); @@ -73,14 +73,14 @@ describe("makeCloudFunction", () => { }); }); - it('should have legacy event type in __trigger/__endpoint if provided', () => { + it("should have legacy event type in __trigger/__endpoint if provided", () => { const cf = makeCloudFunction(cloudFunctionArgs); expect(cf.__trigger).to.deep.equal({ eventTrigger: { - eventType: 'providers/provider/eventTypes/event', - resource: 'resource', - service: 'service', + eventType: "providers/provider/eventTypes/event", + resource: "resource", + service: "service", }, }); @@ -356,18 +356,18 @@ describe("makeAuth and makeAuthType", () => { auth: { uid: "user", token: { - sub: 'user', + sub: "user", }, }, - authType: 'USER', + authType: "USER", }); }); }); -describe('Change', () => { - describe('applyFieldMask', () => { +describe("Change", () => { + describe("applyFieldMask", () => { const after = { - foo: 'bar', + foo: "bar", num: 2, obj: { a: 1, @@ -375,47 +375,41 @@ describe('Change', () => { }, }; - it('should handle deleted values', () => { - const sparseBefore = { baz: 'qux' }; - const fieldMask = 'baz'; - expect( - Change.applyFieldMask(sparseBefore, after, fieldMask) - ).to.deep.equal({ - foo: 'bar', + it("should handle deleted values", () => { + const sparseBefore = { baz: "qux" }; + const fieldMask = "baz"; + expect(Change.applyFieldMask(sparseBefore, after, fieldMask)).to.deep.equal({ + foo: "bar", num: 2, obj: { a: 1, b: 2, }, - baz: 'qux', + baz: "qux", }); }); - it('should handle created values', () => { + it("should handle created values", () => { const sparseBefore = {}; - const fieldMask = 'num,obj.a'; - expect( - Change.applyFieldMask(sparseBefore, after, fieldMask) - ).to.deep.equal({ - foo: 'bar', + const fieldMask = "num,obj.a"; + expect(Change.applyFieldMask(sparseBefore, after, fieldMask)).to.deep.equal({ + foo: "bar", obj: { b: 2, }, }); }); - it('should handle mutated values', () => { + it("should handle mutated values", () => { const sparseBefore = { num: 3, obj: { a: 3, }, }; - const fieldMask = 'num,obj.a'; - expect( - Change.applyFieldMask(sparseBefore, after, fieldMask) - ).to.deep.equal({ - foo: 'bar', + const fieldMask = "num,obj.a"; + expect(Change.applyFieldMask(sparseBefore, after, fieldMask)).to.deep.equal({ + foo: "bar", num: 3, obj: { a: 3, @@ -425,36 +419,36 @@ describe('Change', () => { }); }); - describe('fromJSON', () => { - it('should create a Change object with a `before` and `after`', () => { + describe("fromJSON", () => { + it("should create a Change object with a `before` and `after`", () => { const created = Change.fromJSON({ - before: { foo: 'bar' }, - after: { foo: 'faz' }, + before: { foo: "bar" }, + after: { foo: "faz" }, }); expect(created instanceof Change).to.equal(true); - expect(created.before).to.deep.equal({ foo: 'bar' }); - expect(created.after).to.deep.equal({ foo: 'faz' }); + expect(created.before).to.deep.equal({ foo: "bar" }); + expect(created.after).to.deep.equal({ foo: "faz" }); }); - it('should apply the customizer function to `before` and `after`', () => { + it("should apply the customizer function to `before` and `after`", () => { function customizer(input: any) { - _.set(input, 'another', 'value'); + _.set(input, "another", "value"); return input as T; } const created = Change.fromJSON( { - before: { foo: 'bar' }, - after: { foo: 'faz' }, + before: { foo: "bar" }, + after: { foo: "faz" }, }, customizer ); expect(created.before).to.deep.equal({ - foo: 'bar', - another: 'value', + foo: "bar", + another: "value", }); expect(created.after).to.deep.equal({ - foo: 'faz', - another: 'value', + foo: "faz", + another: "value", }); }); }); diff --git a/spec/v1/function-builder.spec.ts b/spec/v1/function-builder.spec.ts index 4ea9bb760..447686ec9 100644 --- a/spec/v1/function-builder.spec.ts +++ b/spec/v1/function-builder.spec.ts @@ -41,7 +41,7 @@ describe("FunctionBuilder", () => { .auth.user() .onCreate((user) => user); - expect(fn.__trigger.regions).to.deep.equal(['us-east1']); + expect(fn.__trigger.regions).to.deep.equal(["us-east1"]); }); it("should allow multiple supported regions to be set", () => { @@ -50,7 +50,7 @@ describe("FunctionBuilder", () => { .auth.user() .onCreate((user) => user); - expect(fn.__trigger.regions).to.deep.equal(['us-east1', 'us-central1']); + expect(fn.__trigger.regions).to.deep.equal(["us-east1", "us-central1"]); }); it("should allow all supported regions to be set", () => { @@ -69,14 +69,14 @@ describe("FunctionBuilder", () => { .onCreate((user) => user); expect(fn.__trigger.regions).to.deep.equal([ - 'us-central1', - 'us-east1', - 'us-east4', - 'europe-west1', - 'europe-west2', - 'europe-west3', - 'asia-east2', - 'asia-northeast1', + "us-central1", + "us-east1", + "us-east4", + "europe-west1", + "europe-west2", + "europe-west3", + "asia-east2", + "asia-northeast1", ]); }); @@ -105,7 +105,7 @@ describe("FunctionBuilder", () => { .onCreate((user) => user); expect(fn.__trigger.availableMemoryMb).to.deep.equal(256); - expect(fn.__trigger.timeout).to.deep.equal('90s'); + expect(fn.__trigger.timeout).to.deep.equal("90s"); expect(fn.__trigger.failurePolicy).to.deep.equal({ retry: {} }); }); @@ -132,9 +132,9 @@ describe("FunctionBuilder", () => { .auth.user() .onCreate((user) => user); - expect(fn.__trigger.regions).to.deep.equal(['europe-west2']); + expect(fn.__trigger.regions).to.deep.equal(["europe-west2"]); expect(fn.__trigger.availableMemoryMb).to.deep.equal(256); - expect(fn.__trigger.timeout).to.deep.equal('90s'); + expect(fn.__trigger.timeout).to.deep.equal("90s"); }); it("should allow both valid runtime options and supported region to be set in reverse order", () => { @@ -147,9 +147,9 @@ describe("FunctionBuilder", () => { .auth.user() .onCreate((user) => user); - expect(fn.__trigger.regions).to.deep.equal(['europe-west1']); + expect(fn.__trigger.regions).to.deep.equal(["europe-west1"]); expect(fn.__trigger.availableMemoryMb).to.deep.equal(256); - expect(fn.__trigger.timeout).to.deep.equal('90s'); + expect(fn.__trigger.timeout).to.deep.equal("90s"); }); it("should fail if supported region but invalid runtime options are set (reverse order)", () => { @@ -219,7 +219,7 @@ describe("FunctionBuilder", () => { .runWith({ ingressSettings: "ALLOW_INTERNAL_ONLY" }) .https.onRequest(() => undefined); - expect(fn.__trigger.ingressSettings).to.equal('ALLOW_INTERNAL_ONLY'); + expect(fn.__trigger.ingressSettings).to.equal("ALLOW_INTERNAL_ONLY"); }); it("should throw an error if user chooses an invalid ingressSettings", () => { @@ -241,7 +241,7 @@ describe("FunctionBuilder", () => { .auth.user() .onCreate((user) => user); - expect(fn.__trigger.vpcConnector).to.equal('test-connector'); + expect(fn.__trigger.vpcConnector).to.equal("test-connector"); }); it("should allow a vpcConnectorEgressSettings to be set", () => { @@ -253,9 +253,7 @@ describe("FunctionBuilder", () => { .auth.user() .onCreate((user) => user); - expect(fn.__trigger.vpcConnectorEgressSettings).to.equal( - 'PRIVATE_RANGES_ONLY' - ); + expect(fn.__trigger.vpcConnectorEgressSettings).to.equal("PRIVATE_RANGES_ONLY"); }); it("should throw an error if user chooses an invalid vpcConnectorEgressSettings", () => { @@ -284,8 +282,8 @@ describe("FunctionBuilder", () => { expect(fn.__endpoint.serviceAccountEmail).to.equal(serviceAccount); }); - it('should allow a serviceAccount to be set with generated service account email', () => { - const serviceAccount = 'test-service-account@'; + it("should allow a serviceAccount to be set with generated service account email", () => { + const serviceAccount = "test-service-account@"; const projectId = process.env.GCLOUD_PROJECT; const fn = functions .runWith({ @@ -343,7 +341,7 @@ describe("FunctionBuilder", () => { .onCreate((user) => user); expect(fn.__trigger.labels).to.deep.equal({ - 'valid-key': 'valid-value', + "valid-key": "valid-value", }); }); @@ -510,7 +508,7 @@ describe("FunctionBuilder", () => { ).to.throw(); }); - it('should throw error given invalid secret config', () => { + it("should throw error given invalid secret config", () => { expect(() => functions.runWith({ secrets: [sp], diff --git a/spec/v1/providers/analytics.spec.ts b/spec/v1/providers/analytics.spec.ts index 2fb04b7f4..22308b825 100644 --- a/spec/v1/providers/analytics.spec.ts +++ b/spec/v1/providers/analytics.spec.ts @@ -23,9 +23,9 @@ import { expect } from "chai"; import * as functions from "../../../src/v1"; -import { Event, EventContext } from '../../../src/v1/cloud-functions'; +import { Event, EventContext } from "../../../src/v1/cloud-functions"; import * as analytics from "../../../src/v1/providers/analytics"; -import * as analytics_spec_input from './analytics.spec.input'; +import * as analytics_spec_input from "./analytics.spec.input"; import { MINIMAL_V1_ENDPOINT } from "../../fixtures"; describe("Analytics Functions", () => { @@ -48,25 +48,24 @@ describe("Analytics Functions", () => { .analytics.event("event") .onLog((event) => event); - expect(fn.__trigger.regions).to.deep.equal(['us-east1']); + expect(fn.__trigger.regions).to.deep.equal(["us-east1"]); expect(fn.__trigger.availableMemoryMb).to.deep.equal(256); - expect(fn.__trigger.timeout).to.deep.equal('90s'); + expect(fn.__trigger.timeout).to.deep.equal("90s"); - expect(fn.__endpoint.region).to.deep.equal(['us-east1']); + expect(fn.__endpoint.region).to.deep.equal(["us-east1"]); expect(fn.__endpoint.availableMemoryMb).to.deep.equal(256); expect(fn.__endpoint.timeoutSeconds).to.deep.equal(90); }); - describe('#onLog', () => { - it('should return a trigger/endpoint with appropriate values', () => { - const cloudFunction = analytics.event('first_open').onLog(() => null); + describe("#onLog", () => { + it("should return a trigger/endpoint with appropriate values", () => { + const cloudFunction = analytics.event("first_open").onLog(() => null); expect(cloudFunction.__trigger).to.deep.equal({ eventTrigger: { - eventType: - 'providers/google.firebase.analytics/eventTypes/event.log', - resource: 'projects/project1/events/first_open', - service: 'app-measurement.com', + eventType: "providers/google.firebase.analytics/eventTypes/event.log", + resource: "projects/project1/events/first_open", + service: "app-measurement.com", }, }); @@ -298,24 +297,22 @@ describe("Analytics Functions", () => { const payloadData = analytics_spec_input.fullPayload.data; const payloadContext = analytics_spec_input.fullPayload.context; - return expect( - cloudFunction(payloadData, payloadContext) - ).to.eventually.deep.equal(analytics_spec_input.data); + return expect(cloudFunction(payloadData, payloadContext)).to.eventually.deep.equal( + analytics_spec_input.data + ); }); }); }); - describe('handler namespace', () => { - describe('#onLog', () => { - it('should return an empty trigger/endpoint', () => { - const cloudFunction = functions.handler.analytics.event.onLog( - () => null - ); + describe("handler namespace", () => { + describe("#onLog", () => { + it("should return an empty trigger/endpoint", () => { + const cloudFunction = functions.handler.analytics.event.onLog(() => null); expect(cloudFunction.__trigger).to.deep.equal({}); expect(cloudFunction.__endpoint).to.be.undefined; }); - it('should handle an event with the appropriate fields', () => { + it("should handle an event with the appropriate fields", () => { const cloudFunction = functions.handler.analytics.event.onLog( (data: analytics.AnalyticsEvent, context: EventContext) => data ); @@ -325,27 +322,24 @@ describe("Analytics Functions", () => { const event: Event = { data: { userDim: { - userId: 'hi!', + userId: "hi!", }, }, context: { - eventId: '70172329041928', - timestamp: '2018-04-09T07:56:12.975Z', - eventType: - 'providers/google.firebase.analytics/eventTypes/event.log', + eventId: "70172329041928", + timestamp: "2018-04-09T07:56:12.975Z", + eventType: "providers/google.firebase.analytics/eventTypes/event.log", resource: { - service: 'app-measurement.com', - name: 'projects/project1/events/first_open', + service: "app-measurement.com", + name: "projects/project1/events/first_open", }, }, }; - return expect( - cloudFunction(event.data, event.context) - ).to.eventually.deep.equal({ + return expect(cloudFunction(event.data, event.context)).to.eventually.deep.equal({ params: {}, user: { - userId: 'hi!', + userId: "hi!", userProperties: {}, }, }); @@ -353,21 +347,17 @@ describe("Analytics Functions", () => { }); }); - describe('process.env.GCLOUD_PROJECT not set', () => { - it('should not throw if __trigger is not accessed', () => { - expect(() => analytics.event('event').onLog(() => null)).to.not.throw( - Error - ); + describe("process.env.GCLOUD_PROJECT not set", () => { + it("should not throw if __trigger is not accessed", () => { + expect(() => analytics.event("event").onLog(() => null)).to.not.throw(Error); }); - it('should throw when trigger is accessed', () => { - expect( - () => analytics.event('event').onLog(() => null).__trigger - ).to.throw(Error); + it("should throw when trigger is accessed", () => { + expect(() => analytics.event("event").onLog(() => null).__trigger).to.throw(Error); }); - it('should not throw when #run is called', () => { - const cf = analytics.event('event').onLog(() => null); + it("should not throw when #run is called", () => { + const cf = analytics.event("event").onLog(() => null); expect(cf.run).to.not.throw(Error); }); diff --git a/spec/v1/providers/auth.spec.ts b/spec/v1/providers/auth.spec.ts index 7e8d10502..7eae81f33 100644 --- a/spec/v1/providers/auth.spec.ts +++ b/spec/v1/providers/auth.spec.ts @@ -46,13 +46,13 @@ describe("Auth Functions", () => { }, }; - describe('AuthBuilder', () => { + describe("AuthBuilder", () => { function expectedTrigger(project: string, eventType: string) { return { eventTrigger: { resource: `projects/${project}`, eventType: `providers/firebase.auth/eventTypes/${eventType}`, - service: 'firebaseauth.googleapis.com', + service: "firebaseauth.googleapis.com", }, }; } @@ -96,9 +96,9 @@ describe("Auth Functions", () => { .auth.user() .onCreate(() => null); - expect(fn.__trigger.regions).to.deep.equal(['us-east1']); + expect(fn.__trigger.regions).to.deep.equal(["us-east1"]); expect(fn.__trigger.availableMemoryMb).to.deep.equal(256); - expect(fn.__trigger.timeout).to.deep.equal('90s'); + expect(fn.__trigger.timeout).to.deep.equal("90s"); expect(fn.__endpoint.region).to.deep.equal(["us-east1"]); expect(fn.__endpoint.availableMemoryMb).to.deep.equal(256); @@ -109,13 +109,9 @@ describe("Auth Functions", () => { it("should return a trigger/endpoint with appropriate values", () => { const cloudFunction = auth.user().onCreate(() => null); - expect(cloudFunction.__trigger).to.deep.equal( - expectedTrigger(project, 'user.create') - ); + expect(cloudFunction.__trigger).to.deep.equal(expectedTrigger(project, "user.create")); - expect(cloudFunction.__endpoint).to.deep.equal( - expectedEndpoint(project, 'user.create') - ); + expect(cloudFunction.__endpoint).to.deep.equal(expectedEndpoint(project, "user.create")); }); }); @@ -123,13 +119,9 @@ describe("Auth Functions", () => { it("should return a trigger/endpoint with appropriate values", () => { const cloudFunction = auth.user().onDelete(handler); - expect(cloudFunction.__trigger).to.deep.equal( - expectedTrigger(project, 'user.delete') - ); + expect(cloudFunction.__trigger).to.deep.equal(expectedTrigger(project, "user.delete")); - expect(cloudFunction.__endpoint).to.deep.equal( - expectedEndpoint(project, 'user.delete') - ); + expect(cloudFunction.__endpoint).to.deep.equal(expectedEndpoint(project, "user.delete")); }); }); @@ -140,7 +132,7 @@ describe("Auth Functions", () => { expect(fn.__trigger).to.deep.equal({ labels: {}, blockingTrigger: { - eventType: 'providers/cloud.auth/eventTypes/user.beforeCreate', + eventType: "providers/cloud.auth/eventTypes/user.beforeCreate", options: { accessToken: false, idToken: false, @@ -186,11 +178,11 @@ describe("Auth Functions", () => { expect(fn.__trigger).to.deep.equal({ labels: {}, - regions: ['us-east1'], + regions: ["us-east1"], availableMemoryMb: 256, - timeout: '90s', + timeout: "90s", blockingTrigger: { - eventType: 'providers/cloud.auth/eventTypes/user.beforeCreate', + eventType: "providers/cloud.auth/eventTypes/user.beforeCreate", options: { accessToken: true, idToken: false, @@ -230,7 +222,7 @@ describe("Auth Functions", () => { expect(fn.__trigger).to.deep.equal({ labels: {}, blockingTrigger: { - eventType: 'providers/cloud.auth/eventTypes/user.beforeSignIn', + eventType: "providers/cloud.auth/eventTypes/user.beforeSignIn", options: { accessToken: false, idToken: false, @@ -276,11 +268,11 @@ describe("Auth Functions", () => { expect(fn.__trigger).to.deep.equal({ labels: {}, - regions: ['us-east1'], + regions: ["us-east1"], availableMemoryMb: 256, - timeout: '90s', + timeout: "90s", blockingTrigger: { - eventType: 'providers/cloud.auth/eventTypes/user.beforeSignIn', + eventType: "providers/cloud.auth/eventTypes/user.beforeSignIn", options: { accessToken: true, idToken: false, @@ -320,74 +312,62 @@ describe("Auth Functions", () => { cloudFunctionDelete = auth.user().onDelete((data: UserRecord) => data); }); - it('should handle wire format as of v5.0.0 of firebase-admin', () => { - return cloudFunctionDelete(event.data, event.context).then( - (data: any) => { - expect(data.metadata.creationTime).to.equal( - '2016-12-15T19:37:37.059Z' - ); - expect(data.metadata.lastSignInTime).to.equal( - '2017-01-01T00:00:00.000Z' - ); - } - ); + it("should handle wire format as of v5.0.0 of firebase-admin", () => { + return cloudFunctionDelete(event.data, event.context).then((data: any) => { + expect(data.metadata.creationTime).to.equal("2016-12-15T19:37:37.059Z"); + expect(data.metadata.lastSignInTime).to.equal("2017-01-01T00:00:00.000Z"); + }); }); }); }); - describe('handler namespace', () => { - describe('#onCreate', () => { - it('should return an empty trigger', () => { + describe("handler namespace", () => { + describe("#onCreate", () => { + it("should return an empty trigger", () => { const cloudFunction = functions.handler.auth.user.onCreate(() => null); expect(cloudFunction.__trigger).to.deep.equal({}); }); - it('should return an empty endpoint', () => { + it("should return an empty endpoint", () => { const cloudFunction = functions.handler.auth.user.onCreate(() => null); expect(cloudFunction.__endpoint).to.be.undefined; }); }); - describe('#onDelete', () => { + describe("#onDelete", () => { const cloudFunctionDelete: CloudFunction = functions.handler.auth.user.onDelete( (data: UserRecord) => data ); - it('should return an empty trigger', () => { + it("should return an empty trigger", () => { const cloudFunction = functions.handler.auth.user.onDelete(() => null); expect(cloudFunction.__trigger).to.deep.equal({}); }); - it('should return an empty endpoint', () => { + it("should return an empty endpoint", () => { const cloudFunction = functions.handler.auth.user.onDelete(() => null); expect(cloudFunction.__endpoint).to.be.undefined; }); - it('should handle wire format as of v5.0.0 of firebase-admin', () => { - return cloudFunctionDelete(event.data, event.context).then( - (data: any) => { - expect(data.metadata.creationTime).to.equal( - '2016-12-15T19:37:37.059Z' - ); - expect(data.metadata.lastSignInTime).to.equal( - '2017-01-01T00:00:00.000Z' - ); - } - ); + it("should handle wire format as of v5.0.0 of firebase-admin", () => { + return cloudFunctionDelete(event.data, event.context).then((data: any) => { + expect(data.metadata.creationTime).to.equal("2016-12-15T19:37:37.059Z"); + expect(data.metadata.lastSignInTime).to.equal("2017-01-01T00:00:00.000Z"); + }); }); }); }); - describe('process.env.GCLOUD_PROJECT not set', () => { - it('should not throw if __trigger is not accessed', () => { + describe("process.env.GCLOUD_PROJECT not set", () => { + it("should not throw if __trigger is not accessed", () => { expect(() => auth.user().onCreate(() => null)).to.not.throw(Error); }); - it('should throw when trigger is accessed', () => { + it("should throw when trigger is accessed", () => { expect(() => auth.user().onCreate(() => null).__trigger).to.throw(Error); }); - it('should throw when endpoint is accessed', () => { + it("should throw when endpoint is accessed", () => { expect(() => auth.user().onCreate(() => null).__endpoint).to.throw(Error); }); diff --git a/spec/v1/providers/database.spec.ts b/spec/v1/providers/database.spec.ts index 3129f4243..f0317510a 100644 --- a/spec/v1/providers/database.spec.ts +++ b/spec/v1/providers/database.spec.ts @@ -38,7 +38,7 @@ describe("Database Functions", () => { eventTrigger: { resource, eventType: `providers/google.firebase.database/eventTypes/${eventType}`, - service: 'firebaseio.com', + service: "firebaseio.com", }, }; } @@ -80,440 +80,402 @@ describe("Database Functions", () => { .database.ref("/") .onCreate((snap) => snap); - expect(fn.__trigger.regions).to.deep.equal(['us-east1']); + expect(fn.__trigger.regions).to.deep.equal(["us-east1"]); expect(fn.__trigger.availableMemoryMb).to.deep.equal(256); - expect(fn.__trigger.timeout).to.deep.equal('90s'); + expect(fn.__trigger.timeout).to.deep.equal("90s"); - expect(fn.__endpoint.region).to.deep.equal(['us-east1']); + expect(fn.__endpoint.region).to.deep.equal(["us-east1"]); expect(fn.__endpoint.availableMemoryMb).to.deep.equal(256); expect(fn.__endpoint.timeoutSeconds).to.deep.equal(90); - describe('#onWrite()', () => { - it('should return a trigger/endpoint with appropriate values', () => { - const func = database.ref('foo').onWrite(() => null); + describe("#onWrite()", () => { + it("should return a trigger/endpoint with appropriate values", () => { + const func = database.ref("foo").onWrite(() => null); - expect(func.__trigger).to.deep.equal( - expectedTrigger( - 'projects/_/instances/subdomain/refs/foo', - 'ref.write' - ) - ); - - expect(func.__endpoint).to.deep.equal( - expectedEndpoint("projects/_/instances/subdomain/refs/foo", "ref.write") - ); - }); + expect(func.__trigger).to.deep.equal( + expectedTrigger("projects/_/instances/subdomain/refs/foo", "ref.write") + ); - it("should let developers choose a database instance", () => { - const func = database - .instance("custom") - .ref("foo") - .onWrite(() => null); + expect(func.__endpoint).to.deep.equal( + expectedEndpoint("projects/_/instances/subdomain/refs/foo", "ref.write") + ); + }); - expect(func.__trigger).to.deep.equal( - expectedTrigger('projects/_/instances/custom/refs/foo', 'ref.write') - ); + it("should let developers choose a database instance", () => { + const func = database + .instance("custom") + .ref("foo") + .onWrite(() => null); - expect(func.__endpoint).to.deep.equal( - expectedEndpoint("projects/_/instances/custom/refs/foo", "ref.write") - ); - }); + expect(func.__trigger).to.deep.equal( + expectedTrigger("projects/_/instances/custom/refs/foo", "ref.write") + ); - it("should return a handler that emits events with a proper DataSnapshot", () => { - const event = { - data: { - data: null, - delta: { foo: "bar" }, - }, - context: { - eventId: "70172329041928", - eventType: "providers/google.firebase.database/eventTypes/ref.write", - timestamp: "2018-04-09T07:56:12.975Z", - resource: "projects/_/instances/subdomains/refs/users", - }, - }; - const handler = database.ref("/users/{id}").onWrite((change) => { - expect(change.after.val()).to.deep.equal({ foo: "bar" }); + expect(func.__endpoint).to.deep.equal( + expectedEndpoint("projects/_/instances/custom/refs/foo", "ref.write") + ); }); - return handler(event.data, event.context); - }); + it("should return a handler that emits events with a proper DataSnapshot", () => { + const event = { + data: { + data: null, + delta: { foo: "bar" }, + }, + context: { + eventId: "70172329041928", + eventType: "providers/google.firebase.database/eventTypes/ref.write", + timestamp: "2018-04-09T07:56:12.975Z", + resource: "projects/_/instances/subdomains/refs/users", + }, + }; + const handler = database.ref("/users/{id}").onWrite((change) => { + expect(change.after.val()).to.deep.equal({ foo: "bar" }); + }); - it("Should have params of the correct type", () => { - database.ref("foo").onWrite((event, context) => { - expectType>(context.params); + return handler(event.data, event.context); }); - database.ref("foo/{bar}").onWrite((event, context) => { - expectType<{ bar: string }>(context.params); - }); - database.ref("foo/{bar}/{baz}").onWrite((event, context) => { - expectType<{ bar: string; baz: string }>(context.params); + + it("Should have params of the correct type", () => { + database.ref("foo").onWrite((event, context) => { + expectType>(context.params); + }); + database.ref("foo/{bar}").onWrite((event, context) => { + expectType<{ bar: string }>(context.params); + }); + database.ref("foo/{bar}/{baz}").onWrite((event, context) => { + expectType<{ bar: string; baz: string }>(context.params); + }); }); }); - }); - - describe('#onCreate()', () => { - it('should return a trigger/endpoint with appropriate values', () => { - const func = database.ref('foo').onCreate(() => null); - expect(func.__trigger).to.deep.equal( - expectedTrigger( - 'projects/_/instances/subdomain/refs/foo', - 'ref.create' - ) - ); - - expect(func.__endpoint).to.deep.equal( - expectedEndpoint("projects/_/instances/subdomain/refs/foo", "ref.create") - ); - }); + describe("#onCreate()", () => { + it("should return a trigger/endpoint with appropriate values", () => { + const func = database.ref("foo").onCreate(() => null); - it("should let developers choose a database instance", () => { - const func = database - .instance("custom") - .ref("foo") - .onCreate(() => null); + expect(func.__trigger).to.deep.equal( + expectedTrigger("projects/_/instances/subdomain/refs/foo", "ref.create") + ); - expect(func.__trigger).to.deep.equal( - expectedTrigger('projects/_/instances/custom/refs/foo', 'ref.create') - ); + expect(func.__endpoint).to.deep.equal( + expectedEndpoint("projects/_/instances/subdomain/refs/foo", "ref.create") + ); + }); - expect(func.__endpoint).to.deep.equal( - expectedEndpoint("projects/_/instances/custom/refs/foo", "ref.create") - ); - }); + it("should let developers choose a database instance", () => { + const func = database + .instance("custom") + .ref("foo") + .onCreate(() => null); - it("should return a handler that emits events with a proper DataSnapshot", () => { - const event = { - data: { - data: null, - delta: { foo: "bar" }, - }, - context: { - eventId: "70172329041928", - eventType: "providers/google.firebase.database/eventTypes/ref.create", - timestamp: "2018-04-09T07:56:12.975Z", - resource: "projects/_/instances/subdomains/refs/users", - }, - }; + expect(func.__trigger).to.deep.equal( + expectedTrigger("projects/_/instances/custom/refs/foo", "ref.create") + ); - const handler = database.ref("/users/{id}").onCreate((data) => { - expect(data.val()).to.deep.equal({ foo: "bar" }); + expect(func.__endpoint).to.deep.equal( + expectedEndpoint("projects/_/instances/custom/refs/foo", "ref.create") + ); }); - return handler(event.data, event.context); - }); + it("should return a handler that emits events with a proper DataSnapshot", () => { + const event = { + data: { + data: null, + delta: { foo: "bar" }, + }, + context: { + eventId: "70172329041928", + eventType: "providers/google.firebase.database/eventTypes/ref.create", + timestamp: "2018-04-09T07:56:12.975Z", + resource: "projects/_/instances/subdomains/refs/users", + }, + }; + + const handler = database.ref("/users/{id}").onCreate((data) => { + expect(data.val()).to.deep.equal({ foo: "bar" }); + }); - it("Should have params of the correct type", () => { - database.ref("foo").onCreate((event, context) => { - expectType>(context.params); - }); - database.ref("foo/{bar}").onCreate((event, context) => { - expectType<{ bar: string }>(context.params); + return handler(event.data, event.context); }); - database.ref("foo/{bar}/{baz}").onCreate((event, context) => { - expectType<{ bar: string; baz: string }>(context.params); + + it("Should have params of the correct type", () => { + database.ref("foo").onCreate((event, context) => { + expectType>(context.params); + }); + database.ref("foo/{bar}").onCreate((event, context) => { + expectType<{ bar: string }>(context.params); + }); + database.ref("foo/{bar}/{baz}").onCreate((event, context) => { + expectType<{ bar: string; baz: string }>(context.params); + }); }); }); - }); - describe('#onUpdate()', () => { - it('should return a trigger/endpoint with appropriate values', () => { - const func = database.ref('foo').onUpdate(() => null); + describe("#onUpdate()", () => { + it("should return a trigger/endpoint with appropriate values", () => { + const func = database.ref("foo").onUpdate(() => null); - expect(func.__trigger).to.deep.equal( - expectedTrigger( - 'projects/_/instances/subdomain/refs/foo', - 'ref.update' - ) - ); + expect(func.__trigger).to.deep.equal( + expectedTrigger("projects/_/instances/subdomain/refs/foo", "ref.update") + ); - expect(func.__endpoint).to.deep.equal( - expectedEndpoint("projects/_/instances/subdomain/refs/foo", "ref.update") - ); - }); - - it("should let developers choose a database instance", () => { - const func = database - .instance("custom") - .ref("foo") - .onUpdate(() => null); - - expect(func.__trigger).to.deep.equal( - expectedTrigger('projects/_/instances/custom/refs/foo', 'ref.update') - ); + expect(func.__endpoint).to.deep.equal( + expectedEndpoint("projects/_/instances/subdomain/refs/foo", "ref.update") + ); + }); - expect(func.__endpoint).to.deep.equal( - expectedEndpoint("projects/_/instances/custom/refs/foo", "ref.update") - ); - }); + it("should let developers choose a database instance", () => { + const func = database + .instance("custom") + .ref("foo") + .onUpdate(() => null); - it("should return a handler that emits events with a proper DataSnapshot", () => { - const event = { - data: { - data: null, - delta: { foo: "bar" }, - }, - context: { - eventId: "70172329041928", - eventType: "providers/google.firebase.database/eventTypes/ref.update", - timestamp: "2018-04-09T07:56:12.975Z", - resource: "projects/_/instances/subdomains/refs/users", - }, - }; + expect(func.__trigger).to.deep.equal( + expectedTrigger("projects/_/instances/custom/refs/foo", "ref.update") + ); - const handler = database.ref("/users/{id}").onUpdate((change) => { - expect(change.after.val()).to.deep.equal({ foo: "bar" }); + expect(func.__endpoint).to.deep.equal( + expectedEndpoint("projects/_/instances/custom/refs/foo", "ref.update") + ); }); - return handler(event.data, event.context); - }); + it("should return a handler that emits events with a proper DataSnapshot", () => { + const event = { + data: { + data: null, + delta: { foo: "bar" }, + }, + context: { + eventId: "70172329041928", + eventType: "providers/google.firebase.database/eventTypes/ref.update", + timestamp: "2018-04-09T07:56:12.975Z", + resource: "projects/_/instances/subdomains/refs/users", + }, + }; + + const handler = database.ref("/users/{id}").onUpdate((change) => { + expect(change.after.val()).to.deep.equal({ foo: "bar" }); + }); - it("Should have params of the correct type", () => { - database.ref("foo").onUpdate((event, context) => { - expectType>(context.params); - }); - database.ref("foo/{bar}").onUpdate((event, context) => { - expectType<{ bar: string }>(context.params); + return handler(event.data, event.context); }); - database.ref("foo/{bar}/{baz}").onUpdate((event, context) => { - expectType<{ bar: string; baz: string }>(context.params); + + it("Should have params of the correct type", () => { + database.ref("foo").onUpdate((event, context) => { + expectType>(context.params); + }); + database.ref("foo/{bar}").onUpdate((event, context) => { + expectType<{ bar: string }>(context.params); + }); + database.ref("foo/{bar}/{baz}").onUpdate((event, context) => { + expectType<{ bar: string; baz: string }>(context.params); + }); }); }); - }); - describe('#onDelete()', () => { - it('should return a trigger/endpoint with appropriate values', () => { - const func = database.ref('foo').onDelete(() => null); + describe("#onDelete()", () => { + it("should return a trigger/endpoint with appropriate values", () => { + const func = database.ref("foo").onDelete(() => null); - expect(func.__trigger).to.deep.equal( - expectedTrigger( - 'projects/_/instances/subdomain/refs/foo', - 'ref.delete' - ) - ); - - expect(func.__endpoint).to.deep.equal( - expectedEndpoint("projects/_/instances/subdomain/refs/foo", "ref.delete") - ); - }); + expect(func.__trigger).to.deep.equal( + expectedTrigger("projects/_/instances/subdomain/refs/foo", "ref.delete") + ); - it("should let developers choose a database instance", () => { - const func = database - .instance("custom") - .ref("foo") - .onDelete(() => null); + expect(func.__endpoint).to.deep.equal( + expectedEndpoint("projects/_/instances/subdomain/refs/foo", "ref.delete") + ); + }); - expect(func.__trigger).to.deep.equal( - expectedTrigger('projects/_/instances/custom/refs/foo', 'ref.delete') - ); + it("should let developers choose a database instance", () => { + const func = database + .instance("custom") + .ref("foo") + .onDelete(() => null); - expect(func.__endpoint).to.deep.equal( - expectedEndpoint("projects/_/instances/custom/refs/foo", "ref.delete") - ); - }); + expect(func.__trigger).to.deep.equal( + expectedTrigger("projects/_/instances/custom/refs/foo", "ref.delete") + ); - it('should return a handler that emits events with a proper DataSnapshot', () => { - const event = { - data: { - data: { foo: 'bar' }, - delta: null, - }, - context: { - eventId: '70172329041928', - eventType: - 'providers/google.firebase.database/eventTypes/ref.delete', - timestamp: '2018-04-09T07:56:12.975Z', - resource: 'projects/_/instances/subdomains/refs/users', - }, - }; + expect(func.__endpoint).to.deep.equal( + expectedEndpoint("projects/_/instances/custom/refs/foo", "ref.delete") + ); + }); - const handler = database - .ref('/users/{id}') - .onDelete((data, context) => { - expect(data.val()).to.deep.equal({ foo: 'bar' }); + it("should return a handler that emits events with a proper DataSnapshot", () => { + const event = { + data: { + data: { foo: "bar" }, + delta: null, + }, + context: { + eventId: "70172329041928", + eventType: "providers/google.firebase.database/eventTypes/ref.delete", + timestamp: "2018-04-09T07:56:12.975Z", + resource: "projects/_/instances/subdomains/refs/users", + }, + }; + + const handler = database.ref("/users/{id}").onDelete((data, context) => { + expect(data.val()).to.deep.equal({ foo: "bar" }); }); - return handler(event.data, event.context); + return handler(event.data, event.context); + }); }); }); - }); - - describe('handler namespace', () => { - describe('#onWrite()', () => { - it('correctly sets trigger to {}', () => { - const cf = functions.handler.database.ref.onWrite(() => null); - expect(cf.__trigger).to.deep.equal({}); - expect(cf.__endpoint).to.be.undefined; - }); - it('should be able to use the instance entry point', () => { - const func = functions.handler.database.instance.ref.onWrite( - () => null - ); - expect(func.__trigger).to.deep.equal({}); - expect(func.__endpoint).to.be.undefined; - }); + describe("handler namespace", () => { + describe("#onWrite()", () => { + it("correctly sets trigger to {}", () => { + const cf = functions.handler.database.ref.onWrite(() => null); + expect(cf.__trigger).to.deep.equal({}); + expect(cf.__endpoint).to.be.undefined; + }); - it('should return a handler that emits events with a proper DataSnapshot', () => { - const event = { - data: { - data: null, - delta: { foo: 'bar' }, - }, - context: { - eventId: '70172329041928', - eventType: - 'providers/google.firebase.database/eventTypes/ref.write', - timestamp: '2018-04-09T07:56:12.975Z', - resource: 'projects/_/instances/subdomains/refs/users', - }, - }; + it("should be able to use the instance entry point", () => { + const func = functions.handler.database.instance.ref.onWrite(() => null); + expect(func.__trigger).to.deep.equal({}); + expect(func.__endpoint).to.be.undefined; + }); - const handler = functions.handler.database.ref.onWrite( - (change, context) => { - return expect(change.after.val()).to.deep.equal({ foo: 'bar' }); - } - ); + it("should return a handler that emits events with a proper DataSnapshot", () => { + const event = { + data: { + data: null, + delta: { foo: "bar" }, + }, + context: { + eventId: "70172329041928", + eventType: "providers/google.firebase.database/eventTypes/ref.write", + timestamp: "2018-04-09T07:56:12.975Z", + resource: "projects/_/instances/subdomains/refs/users", + }, + }; + + const handler = functions.handler.database.ref.onWrite((change, context) => { + return expect(change.after.val()).to.deep.equal({ foo: "bar" }); + }); - return handler(event.data, event.context); + return handler(event.data, event.context); + }); }); - }); - describe('#onCreate()', () => { - it('correctly sets trigger to {}', () => { - const cf = functions.handler.database.ref.onCreate(() => null); - expect(cf.__trigger).to.deep.equal({}); - expect(cf.__endpoint).to.be.undefined; - }); + describe("#onCreate()", () => { + it("correctly sets trigger to {}", () => { + const cf = functions.handler.database.ref.onCreate(() => null); + expect(cf.__trigger).to.deep.equal({}); + expect(cf.__endpoint).to.be.undefined; + }); - it('should be able to use the instance entry point', () => { - const func = functions.handler.database.instance.ref.onCreate( - () => null - ); - expect(func.__trigger).to.deep.equal({}); - expect(func.__endpoint).to.be.undefined; - }); + it("should be able to use the instance entry point", () => { + const func = functions.handler.database.instance.ref.onCreate(() => null); + expect(func.__trigger).to.deep.equal({}); + expect(func.__endpoint).to.be.undefined; + }); - it('should return a handler that emits events with a proper DataSnapshot', () => { - const event = { - data: { - data: null, - delta: { foo: 'bar' }, - }, - context: { - eventId: '70172329041928', - eventType: - 'providers/google.firebase.database/eventTypes/ref.create', - timestamp: '2018-04-09T07:56:12.975Z', - resource: 'projects/_/instances/subdomains/refs/users', - }, - }; - const handler = functions.handler.database.ref.onCreate( - (data, context) => { - return expect(data.val()).to.deep.equal({ foo: 'bar' }); - } - ); + it("should return a handler that emits events with a proper DataSnapshot", () => { + const event = { + data: { + data: null, + delta: { foo: "bar" }, + }, + context: { + eventId: "70172329041928", + eventType: "providers/google.firebase.database/eventTypes/ref.create", + timestamp: "2018-04-09T07:56:12.975Z", + resource: "projects/_/instances/subdomains/refs/users", + }, + }; + const handler = functions.handler.database.ref.onCreate((data, context) => { + return expect(data.val()).to.deep.equal({ foo: "bar" }); + }); - return handler(event.data, event.context); + return handler(event.data, event.context); + }); }); - }); - describe('#onUpdate()', () => { - it('correctly sets trigger to {}', () => { - const cf = functions.handler.database.ref.onUpdate(() => null); - expect(cf.__trigger).to.deep.equal({}); - expect(cf.__endpoint).to.be.undefined; - }); + describe("#onUpdate()", () => { + it("correctly sets trigger to {}", () => { + const cf = functions.handler.database.ref.onUpdate(() => null); + expect(cf.__trigger).to.deep.equal({}); + expect(cf.__endpoint).to.be.undefined; + }); - it('should be able to use the instance entry point', () => { - const func = functions.handler.database.instance.ref.onUpdate( - () => null - ); - expect(func.__trigger).to.deep.equal({}); - expect(func.__endpoint).to.be.undefined; - }); + it("should be able to use the instance entry point", () => { + const func = functions.handler.database.instance.ref.onUpdate(() => null); + expect(func.__trigger).to.deep.equal({}); + expect(func.__endpoint).to.be.undefined; + }); - it('should return a handler that emits events with a proper DataSnapshot', () => { - const event = { - data: { - data: null, - delta: { foo: 'bar' }, - }, - context: { - eventId: '70172329041928', - eventType: - 'providers/google.firebase.database/eventTypes/ref.update', - timestamp: '2018-04-09T07:56:12.975Z', - resource: 'projects/_/instances/subdomains/refs/users', - }, - }; - const handler = functions.handler.database.ref.onUpdate( - (change, context) => { - return expect(change.after.val()).to.deep.equal({ foo: 'bar' }); - } - ); + it("should return a handler that emits events with a proper DataSnapshot", () => { + const event = { + data: { + data: null, + delta: { foo: "bar" }, + }, + context: { + eventId: "70172329041928", + eventType: "providers/google.firebase.database/eventTypes/ref.update", + timestamp: "2018-04-09T07:56:12.975Z", + resource: "projects/_/instances/subdomains/refs/users", + }, + }; + const handler = functions.handler.database.ref.onUpdate((change, context) => { + return expect(change.after.val()).to.deep.equal({ foo: "bar" }); + }); - return handler(event.data, event.context); + return handler(event.data, event.context); + }); }); - }); - describe('#onDelete()', () => { - it('correctly sets trigger to {}', () => { - const cf = functions.handler.database.ref.onDelete(() => null); - expect(cf.__trigger).to.deep.equal({}); - expect(cf.__endpoint).to.be.undefined; - }); + describe("#onDelete()", () => { + it("correctly sets trigger to {}", () => { + const cf = functions.handler.database.ref.onDelete(() => null); + expect(cf.__trigger).to.deep.equal({}); + expect(cf.__endpoint).to.be.undefined; + }); - it('should be able to use the instance entry point', () => { - const func = functions.handler.database.instance.ref.onDelete( - () => null - ); - expect(func.__trigger).to.deep.equal({}); - expect(func.__endpoint).to.be.undefined; - }); + it("should be able to use the instance entry point", () => { + const func = functions.handler.database.instance.ref.onDelete(() => null); + expect(func.__trigger).to.deep.equal({}); + expect(func.__endpoint).to.be.undefined; + }); - it('should return a handler that emits events with a proper DataSnapshot', () => { - const event = { - data: { - data: { foo: 'bar' }, - delta: null, - }, - context: { - eventId: "70172329041928", - eventType: "providers/google.firebase.database/eventTypes/ref.delete", - timestamp: "2018-04-09T07:56:12.975Z", - resource: "projects/_/instances/subdomains/refs/users", - }, - }; + it("should return a handler that emits events with a proper DataSnapshot", () => { + const event = { + data: { + data: { foo: "bar" }, + delta: null, + }, + context: { + eventId: "70172329041928", + eventType: "providers/google.firebase.database/eventTypes/ref.delete", + timestamp: "2018-04-09T07:56:12.975Z", + resource: "projects/_/instances/subdomains/refs/users", + }, + }; + + const handler = database.ref("/users/{id}").onDelete((data) => { + expect(data.val()).to.deep.equal({ foo: "bar" }); + }); - const handler = database.ref("/users/{id}").onDelete((data) => { - expect(data.val()).to.deep.equal({ foo: "bar" }); + return handler(event.data, event.context); }); - - return handler(event.data, event.context); }); }); - }); - describe('process.env.FIREBASE_CONFIG not set', () => { - it('should not throw if __trigger is not accessed', () => { - expect(() => database.ref('/path').onWrite(() => null)).to.not.throw( - Error - ); + describe("process.env.FIREBASE_CONFIG not set", () => { + it("should not throw if __trigger is not accessed", () => { + expect(() => database.ref("/path").onWrite(() => null)).to.not.throw(Error); + }); }); - }); - it('should throw when trigger is accessed', () => { - expect( - () => database.ref('/path').onWrite(() => null).__trigger - ).to.throw(Error); + it("should throw when trigger is accessed", () => { + expect(() => database.ref("/path").onWrite(() => null).__trigger).to.throw(Error); }); - it('should throw when endpoint is accessed', () => { - expect( - () => database.ref('/path').onWrite(() => null).__endpoint - ).to.throw(Error); + it("should throw when endpoint is accessed", () => { + expect(() => database.ref("/path").onWrite(() => null).__endpoint).to.throw(Error); }); it("should not throw when #run is called", () => { diff --git a/spec/v1/providers/firestore.spec.ts b/spec/v1/providers/firestore.spec.ts index 7cd268fa8..5ee3b8cae 100644 --- a/spec/v1/providers/firestore.spec.ts +++ b/spec/v1/providers/firestore.spec.ts @@ -91,13 +91,13 @@ describe("Firestore Functions", () => { }); } - describe('document builders and event types', () => { + describe("document builders and event types", () => { function expectedTrigger(resource: string, eventType: string) { return { eventTrigger: { resource, eventType: `providers/cloud.firestore/eventTypes/${eventType}`, - service: 'firestore.googleapis.com', + service: "firestore.googleapis.com", }, }; } @@ -136,67 +136,48 @@ describe("Firestore Functions", () => { it("should allow custom namespaces", () => { const resource = "projects/project1/databases/(default)/documents@v2/users/{uid}"; - const cloudFunction = firestore - .document('users/{uid}') - .onWrite(() => null); + const cloudFunction = firestore.document("users/{uid}").onWrite(() => null); - expect(cloudFunction.__trigger).to.deep.equal( - expectedTrigger(resource, 'document.write') - ); + expect(cloudFunction.__trigger).to.deep.equal(expectedTrigger(resource, "document.write")); - expect(cloudFunction.__endpoint).to.deep.equal( - expectedEndpoint(resource, 'document.write') - ); + expect(cloudFunction.__endpoint).to.deep.equal(expectedEndpoint(resource, "document.write")); }); - it('should allow custom namespaces', () => { - const resource = - 'projects/project1/databases/(default)/documents@v2/users/{uid}'; + it("should allow custom namespaces", () => { + const resource = "projects/project1/databases/(default)/documents@v2/users/{uid}"; const cloudFunction = firestore - .namespace('v2') - .document('users/{uid}') + .namespace("v2") + .document("users/{uid}") .onWrite(() => null); - expect(cloudFunction.__trigger).to.deep.equal( - expectedTrigger(resource, 'document.write') - ); + expect(cloudFunction.__trigger).to.deep.equal(expectedTrigger(resource, "document.write")); - expect(cloudFunction.__endpoint).to.deep.equal( - expectedEndpoint(resource, 'document.write') - ); + expect(cloudFunction.__endpoint).to.deep.equal(expectedEndpoint(resource, "document.write")); }); - it('should allow custom databases', () => { - const resource = 'projects/project1/databases/myDB/documents/users/{uid}'; + it("should allow custom databases", () => { + const resource = "projects/project1/databases/myDB/documents/users/{uid}"; const cloudFunction = firestore .database("myDB") .document("users/{uid}") .onWrite(() => null); - expect(cloudFunction.__trigger).to.deep.equal( - expectedTrigger(resource, 'document.write') - ); + expect(cloudFunction.__trigger).to.deep.equal(expectedTrigger(resource, "document.write")); - expect(cloudFunction.__endpoint).to.deep.equal( - expectedEndpoint(resource, 'document.write') - ); + expect(cloudFunction.__endpoint).to.deep.equal(expectedEndpoint(resource, "document.write")); }); it("should allow both custom database and namespace", () => { const resource = "projects/project1/databases/myDB/documents@v2/users/{uid}"; const cloudFunction = firestore - .database('myDB') - .namespace('v2') - .document('users/{uid}') + .database("myDB") + .namespace("v2") + .document("users/{uid}") .onWrite(() => null); - expect(cloudFunction.__trigger).to.deep.equal( - expectedTrigger(resource, 'document.write') - ); + expect(cloudFunction.__trigger).to.deep.equal(expectedTrigger(resource, "document.write")); - expect(cloudFunction.__endpoint).to.deep.equal( - expectedEndpoint(resource, 'document.write') - ); + expect(cloudFunction.__endpoint).to.deep.equal(expectedEndpoint(resource, "document.write")); }); it("should allow both region and runtime options to be set", () => { @@ -211,33 +192,27 @@ describe("Firestore Functions", () => { expectType>(context.params); }); - expect(fn.__trigger.regions).to.deep.equal(['us-east1']); + expect(fn.__trigger.regions).to.deep.equal(["us-east1"]); expect(fn.__trigger.availableMemoryMb).to.deep.equal(256); - expect(fn.__trigger.timeout).to.deep.equal('90s'); + expect(fn.__trigger.timeout).to.deep.equal("90s"); - expect(fn.__endpoint.region).to.deep.equal(['us-east1']); + expect(fn.__endpoint.region).to.deep.equal(["us-east1"]); expect(fn.__endpoint.availableMemoryMb).to.deep.equal(256); expect(fn.__endpoint.timeoutSeconds).to.deep.equal(90); }); }); - describe('process.env.GCLOUD_PROJECT not set', () => { - it('should not throw if __trigger is not accessed', () => { - expect(() => - firestore.document('input').onCreate(() => null) - ).to.not.throw(Error); + describe("process.env.GCLOUD_PROJECT not set", () => { + it("should not throw if __trigger is not accessed", () => { + expect(() => firestore.document("input").onCreate(() => null)).to.not.throw(Error); }); - it('should throw when trigger is accessed', () => { - expect( - () => firestore.document('input').onCreate(() => null).__trigger - ).to.throw(Error); + it("should throw when trigger is accessed", () => { + expect(() => firestore.document("input").onCreate(() => null).__trigger).to.throw(Error); }); - it('should throw when endpoint is accessed', () => { - expect( - () => firestore.document('input').onCreate(() => null).__endpoint - ).to.throw(Error); + it("should throw when endpoint is accessed", () => { + expect(() => firestore.document("input").onCreate(() => null).__endpoint).to.throw(Error); }); it("should not throw when #run is called", () => { @@ -296,21 +271,19 @@ describe("Firestore Functions", () => { }).timeout(5000); it('constructs appropriate fields and getters for event.data on "document.delete" events', () => { - const testFunction = firestore - .document('path') - .onDelete((data, context) => { - 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(), {}, 'document.delete'); + const testFunction = firestore.document("path").onDelete((data, context) => { + 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(), {}, "document.delete"); return testFunction(event.data, event.context); }).timeout(5000); }); - describe('handler namespace', () => { + describe("handler namespace", () => { before(() => { - process.env.GCLOUD_PROJECT = 'project1'; + process.env.GCLOUD_PROJECT = "project1"; }); after(() => { @@ -318,71 +291,55 @@ describe("Firestore Functions", () => { }); it('constructs correct data type and sets trigger to {} on "document.write" events', () => { - const testFunction = functions.handler.firestore.document.onWrite( - (change, context) => { - expect(change.before.data()).to.deep.equal({ - key1: false, - key2: 111, - }); - expect(change.before.get('key1')).to.equal(false); - expect(change.after.data()).to.deep.equal({ key1: true, key2: 123 }); - expect(change.after.get('key1')).to.equal(true); - return true; // otherwise will get warning about returning undefined - } - ); + const testFunction = functions.handler.firestore.document.onWrite((change, context) => { + expect(change.before.data()).to.deep.equal({ + key1: false, + key2: 111, + }); + expect(change.before.get("key1")).to.equal(false); + expect(change.after.data()).to.deep.equal({ key1: true, key2: 123 }); + expect(change.after.get("key1")).to.equal(true); + return true; // otherwise will get warning about returning undefined + }); expect(testFunction.__trigger).to.deep.equal({}); - const event = constructEvent( - createOldValue(), - createValue(), - 'document.write' - ); + const event = constructEvent(createOldValue(), createValue(), "document.write"); return testFunction(event.data, event.context); }).timeout(5000); it('constructs correct data type and sets trigger to {} on "document.create" events', () => { - const testFunction = functions.handler.firestore.document.onCreate( - (data, context) => { - 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 testFunction = functions.handler.firestore.document.onCreate((data, context) => { + 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 + }); expect(testFunction.__trigger).to.deep.equal({}); - const event = constructEvent({}, createValue(), 'document.create'); + const event = constructEvent({}, createValue(), "document.create"); return testFunction(event.data, event.context); }).timeout(5000); it('constructs correct data type and sets trigger to {} on "document.update" events', () => { - const testFunction = functions.handler.firestore.document.onUpdate( - (change) => { - expect(change.before.data()).to.deep.equal({ - key1: false, - key2: 111, - }); - expect(change.before.get('key1')).to.equal(false); - expect(change.after.data()).to.deep.equal({ key1: true, key2: 123 }); - expect(change.after.get('key1')).to.equal(true); - return true; // otherwise will get warning about returning undefined - } - ); + const testFunction = functions.handler.firestore.document.onUpdate((change) => { + expect(change.before.data()).to.deep.equal({ + key1: false, + key2: 111, + }); + expect(change.before.get("key1")).to.equal(false); + expect(change.after.data()).to.deep.equal({ key1: true, key2: 123 }); + expect(change.after.get("key1")).to.equal(true); + return true; // otherwise will get warning about returning undefined + }); expect(testFunction.__trigger).to.deep.equal({}); - const event = constructEvent( - createOldValue(), - createValue(), - 'document.update' - ); + const event = constructEvent(createOldValue(), createValue(), "document.update"); return testFunction(event.data, event.context); }).timeout(5000); it('constructs correct data type and sets trigger to {} on "document.delete" events', () => { - const testFunction = functions.handler.firestore.document.onDelete( - (data, context) => { - 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(), {}, 'document.delete'); + const testFunction = functions.handler.firestore.document.onDelete((data, context) => { + 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(), {}, "document.delete"); expect(testFunction.__trigger).to.deep.equal({}); 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 571968768..9ca03bf31 100644 --- a/spec/v1/providers/https.spec.ts +++ b/spec/v1/providers/https.spec.ts @@ -52,22 +52,22 @@ describe("CloudHttpsBuilder", () => { }) .https.onRequest(() => null); - expect(fn.__trigger.regions).to.deep.equal(['us-east1']); + expect(fn.__trigger.regions).to.deep.equal(["us-east1"]); expect(fn.__trigger.availableMemoryMb).to.deep.equal(256); - expect(fn.__trigger.timeout).to.deep.equal('90s'); - expect(fn.__trigger.httpsTrigger.invoker).to.deep.equal(['private']); + expect(fn.__trigger.timeout).to.deep.equal("90s"); + expect(fn.__trigger.httpsTrigger.invoker).to.deep.equal(["private"]); - expect(fn.__endpoint.region).to.deep.equal(['us-east1']); + expect(fn.__endpoint.region).to.deep.equal(["us-east1"]); expect(fn.__endpoint.availableMemoryMb).to.deep.equal(256); expect(fn.__endpoint.timeoutSeconds).to.deep.equal(90); - expect(fn.__endpoint.httpsTrigger.invoker).to.deep.equal(['private']); + expect(fn.__endpoint.httpsTrigger.invoker).to.deep.equal(["private"]); }); }); }); -describe('handler namespace', () => { - describe('#onRequest', () => { - it('should return an empty trigger', () => { +describe("handler namespace", () => { + describe("#onRequest", () => { + it("should return an empty trigger", () => { const result = functions.handler.https.onRequest((req, res) => { res.send(200); }); @@ -76,8 +76,8 @@ describe('handler namespace', () => { }); }); - describe('#onCall', () => { - it('should return an empty trigger', () => { + describe("#onCall", () => { + it("should return an empty trigger", () => { const result = functions.handler.https.onCall(() => null); expect(result.__trigger).to.deep.equal({}); expect(result.__endpoint).to.be.undefined; @@ -85,15 +85,15 @@ describe('handler namespace', () => { }); }); -describe('#onCall', () => { - it('should return a trigger/endpoint with appropriate values', () => { +describe("#onCall", () => { + it("should return a trigger/endpoint with appropriate values", () => { const result = https.onCall((data) => { - return 'response'; + return "response"; }); expect(result.__trigger).to.deep.equal({ httpsTrigger: {}, - labels: { 'deployment-callable': 'true' }, + labels: { "deployment-callable": "true" }, }); expect(result.__endpoint).to.deep.equal({ @@ -113,11 +113,11 @@ describe('#onCall', () => { }) .https.onCall(() => null); - expect(fn.__trigger.regions).to.deep.equal(['us-east1']); + expect(fn.__trigger.regions).to.deep.equal(["us-east1"]); expect(fn.__trigger.availableMemoryMb).to.deep.equal(256); - expect(fn.__trigger.timeout).to.deep.equal('90s'); + expect(fn.__trigger.timeout).to.deep.equal("90s"); - expect(fn.__endpoint.region).to.deep.equal(['us-east1']); + expect(fn.__endpoint.region).to.deep.equal(["us-east1"]); expect(fn.__endpoint.availableMemoryMb).to.deep.equal(256); expect(fn.__endpoint.timeoutSeconds).to.deep.equal(90); }); diff --git a/spec/v1/providers/pubsub.spec.ts b/spec/v1/providers/pubsub.spec.ts index f599d2fc0..4257cdb53 100644 --- a/spec/v1/providers/pubsub.spec.ts +++ b/spec/v1/providers/pubsub.spec.ts @@ -83,11 +83,11 @@ describe("Pubsub Functions", () => { .pubsub.topic("toppy") .onPublish(() => null); - expect(fn.__trigger.regions).to.deep.equal(['us-east1']); + expect(fn.__trigger.regions).to.deep.equal(["us-east1"]); expect(fn.__trigger.availableMemoryMb).to.deep.equal(256); - expect(fn.__trigger.timeout).to.deep.equal('90s'); + expect(fn.__trigger.timeout).to.deep.equal("90s"); - expect(fn.__endpoint.region).to.deep.equal(['us-east1']); + expect(fn.__endpoint.region).to.deep.equal(["us-east1"]); expect(fn.__endpoint.availableMemoryMb).to.deep.equal(256); expect(fn.__endpoint.timeoutSeconds).to.deep.equal(90); }); @@ -95,13 +95,13 @@ describe("Pubsub Functions", () => { describe("#onPublish", () => { it("should return a trigger/endpoint with appropriate values", () => { // Pick up project from process.env.GCLOUD_PROJECT - const result = pubsub.topic('toppy').onPublish(() => null); + const result = pubsub.topic("toppy").onPublish(() => null); expect(result.__trigger).to.deep.equal({ eventTrigger: { - eventType: 'google.pubsub.topic.publish', - resource: 'projects/project1/topics/toppy', - service: 'pubsub.googleapis.com', + eventType: "google.pubsub.topic.publish", + resource: "projects/project1/topics/toppy", + service: "pubsub.googleapis.com", }, }); @@ -159,14 +159,12 @@ describe("Pubsub Functions", () => { }); }); - describe('#schedule', () => { - it('should return a trigger/endpoint with schedule', () => { - const result = pubsub - .schedule('every 5 minutes') - .onRun((context) => null); + describe("#schedule", () => { + it("should return a trigger/endpoint with schedule", () => { + const result = pubsub.schedule("every 5 minutes").onRun((context) => null); expect(result.__trigger.schedule).to.deep.equal({ - schedule: 'every 5 minutes', + schedule: "every 5 minutes", }); expect(result.__endpoint.scheduleTrigger).to.deep.equal({ @@ -177,13 +175,13 @@ describe("Pubsub Functions", () => { it("should return a trigger/endpoint with schedule and timeZone when one is chosen", () => { const result = pubsub - .schedule('every 5 minutes') - .timeZone('America/New_York') + .schedule("every 5 minutes") + .timeZone("America/New_York") .onRun((context) => null); expect(result.__trigger.schedule).to.deep.equal({ - schedule: 'every 5 minutes', - timeZone: 'America/New_York', + schedule: "every 5 minutes", + timeZone: "America/New_York", }); expect(result.__endpoint.scheduleTrigger).to.deep.equal({ @@ -207,11 +205,11 @@ describe("Pubsub Functions", () => { .onRun(() => null); expect(result.__trigger.schedule).to.deep.equal({ - schedule: 'every 5 minutes', + schedule: "every 5 minutes", retryConfig, }); expect(result.__trigger.labels).to.deep.equal({ - 'deployment-scheduled': 'true', + "deployment-scheduled": "true", }); expect(result.__endpoint.scheduleTrigger).to.deep.equal({ @@ -240,12 +238,12 @@ describe("Pubsub Functions", () => { .onRun(() => null); expect(result.__trigger.schedule).to.deep.equal({ - schedule: 'every 5 minutes', + schedule: "every 5 minutes", retryConfig, - timeZone: 'America/New_York', + timeZone: "America/New_York", }); expect(result.__trigger.labels).to.deep.equal({ - 'deployment-scheduled': 'true', + "deployment-scheduled": "true", }); expect(result.__endpoint.scheduleTrigger).to.deep.equal({ @@ -268,11 +266,11 @@ describe("Pubsub Functions", () => { .pubsub.schedule("every 5 minutes") .onRun(() => null); expect(result.__trigger.schedule).to.deep.equal({ - schedule: 'every 5 minutes', + schedule: "every 5 minutes", }); - expect(result.__trigger.regions).to.deep.equal(['us-east1']); + expect(result.__trigger.regions).to.deep.equal(["us-east1"]); expect(result.__trigger.availableMemoryMb).to.deep.equal(256); - expect(result.__trigger.timeout).to.deep.equal('90s'); + expect(result.__trigger.timeout).to.deep.equal("90s"); expect(result.__endpoint.scheduleTrigger).to.deep.equal({ ...MINIMAL_SCHEDULE_TRIGGER, @@ -294,12 +292,12 @@ describe("Pubsub Functions", () => { .timeZone("America/New_York") .onRun(() => null); expect(result.__trigger.schedule).to.deep.equal({ - schedule: 'every 5 minutes', - timeZone: 'America/New_York', + schedule: "every 5 minutes", + timeZone: "America/New_York", }); - expect(result.__trigger.regions).to.deep.equal(['us-east1']); + expect(result.__trigger.regions).to.deep.equal(["us-east1"]); expect(result.__trigger.availableMemoryMb).to.deep.equal(256); - expect(result.__trigger.timeout).to.deep.equal('90s'); + expect(result.__trigger.timeout).to.deep.equal("90s"); expect(result.__endpoint.scheduleTrigger).to.deep.equal({ ...MINIMAL_SCHEDULE_TRIGGER, @@ -329,15 +327,15 @@ describe("Pubsub Functions", () => { .retryConfig(retryConfig) .onRun(() => null); expect(result.__trigger.schedule).to.deep.equal({ - schedule: 'every 5 minutes', + schedule: "every 5 minutes", retryConfig, }); expect(result.__trigger.labels).to.deep.equal({ - 'deployment-scheduled': 'true', + "deployment-scheduled": "true", }); - expect(result.__trigger.regions).to.deep.equal(['us-east1']); + expect(result.__trigger.regions).to.deep.equal(["us-east1"]); expect(result.__trigger.availableMemoryMb).to.deep.equal(256); - expect(result.__trigger.timeout).to.deep.equal('90s'); + expect(result.__trigger.timeout).to.deep.equal("90s"); expect(result.__endpoint.scheduleTrigger).to.deep.equal({ ...MINIMAL_SCHEDULE_TRIGGER, @@ -369,16 +367,16 @@ describe("Pubsub Functions", () => { .retryConfig(retryConfig) .onRun(() => null); expect(result.__trigger.schedule).to.deep.equal({ - schedule: 'every 5 minutes', - timeZone: 'America/New_York', + schedule: "every 5 minutes", + timeZone: "America/New_York", retryConfig, }); expect(result.__trigger.labels).to.deep.equal({ - 'deployment-scheduled': 'true', + "deployment-scheduled": "true", }); - expect(result.__trigger.regions).to.deep.equal(['us-east1']); + expect(result.__trigger.regions).to.deep.equal(["us-east1"]); expect(result.__trigger.availableMemoryMb).to.deep.equal(256); - expect(result.__trigger.timeout).to.deep.equal('90s'); + expect(result.__trigger.timeout).to.deep.equal("90s"); expect(result.__endpoint.scheduleTrigger).to.deep.equal({ ...MINIMAL_SCHEDULE_TRIGGER, @@ -393,33 +391,31 @@ describe("Pubsub Functions", () => { }); }); - describe('handler namespace', () => { - describe('#onPublish', () => { - describe('#topic', () => { - it('should return an empty trigger', () => { + describe("handler namespace", () => { + describe("#onPublish", () => { + describe("#topic", () => { + it("should return an empty trigger", () => { const result = functions.handler.pubsub.topic.onPublish(() => null); expect(result.__trigger).to.deep.equal({}); expect(result.__endpoint).to.be.undefined; }); - it('should properly handle a new-style event', () => { - const raw = new Buffer('{"hello":"world"}', 'utf8').toString( - 'base64' - ); + it("should properly handle a new-style event", () => { + const raw = new Buffer('{"hello":"world"}', "utf8").toString("base64"); const event = { data: { data: raw, attributes: { - foo: 'bar', + foo: "bar", }, }, context: { - eventId: '70172329041928', - timestamp: '2018-04-09T07:56:12.975Z', - eventType: 'google.pubsub.topic.publish', + eventId: "70172329041928", + timestamp: "2018-04-09T07:56:12.975Z", + eventType: "google.pubsub.topic.publish", resource: { - service: 'pubsub.googleapis.com', - name: 'projects/project1/topics/toppy', + service: "pubsub.googleapis.com", + name: "projects/project1/topics/toppy", }, }, }; @@ -432,69 +428,55 @@ describe("Pubsub Functions", () => { }; }); - return 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' }, + json: { hello: "world" }, + attributes: { foo: "bar" }, }); }); }); - describe('#schedule', () => { - it('should return an empty trigger', () => { + describe("#schedule", () => { + it("should return an empty trigger", () => { const result = functions.handler.pubsub.schedule.onRun(() => null); expect(result.__trigger).to.deep.equal({}); }); - it('should return a handler with a proper event context', () => { - const raw = new Buffer('{"hello":"world"}', 'utf8').toString( - 'base64' - ); + it("should return a handler with a proper event context", () => { + const raw = new Buffer('{"hello":"world"}', "utf8").toString("base64"); const event = { data: { data: raw, attributes: { - foo: 'bar', + foo: "bar", }, }, context: { - eventId: '70172329041928', - timestamp: '2018-04-09T07:56:12.975Z', - eventType: 'google.pubsub.topic.publish', + eventId: "70172329041928", + timestamp: "2018-04-09T07:56:12.975Z", + eventType: "google.pubsub.topic.publish", resource: { - service: 'pubsub.googleapis.com', - name: 'projects/project1/topics/toppy', + service: "pubsub.googleapis.com", + name: "projects/project1/topics/toppy", }, }, }; - const result = functions.handler.pubsub.schedule.onRun( - (context) => context.eventId - ); - return expect(result(event.data, event.context)).to.eventually.equal( - '70172329041928' - ); + const result = functions.handler.pubsub.schedule.onRun((context) => context.eventId); + return expect(result(event.data, event.context)).to.eventually.equal("70172329041928"); }); }); }); }); - describe('process.env.GCLOUD_PROJECT not set', () => { - it('should not throw if __trigger is not accessed', () => { - expect(() => pubsub.topic('toppy').onPublish(() => null)).to.not.throw( - Error - ); + describe("process.env.GCLOUD_PROJECT not set", () => { + it("should not throw if __trigger is not accessed", () => { + expect(() => pubsub.topic("toppy").onPublish(() => null)).to.not.throw(Error); }); - it('should throw when trigger is accessed', () => { - expect( - () => pubsub.topic('toppy').onPublish(() => null).__trigger - ).to.throw(Error); + it("should throw when trigger is accessed", () => { + expect(() => pubsub.topic("toppy").onPublish(() => null).__trigger).to.throw(Error); }); - it('should throw when endpoint is accessed', () => { - expect( - () => pubsub.topic('toppy').onPublish(() => null).__endpoint - ).to.throw(Error); + it("should throw when endpoint is accessed", () => { + expect(() => pubsub.topic("toppy").onPublish(() => null).__endpoint).to.throw(Error); }); it("should not throw when #run is called", () => { diff --git a/spec/v1/providers/remoteConfig.spec.ts b/spec/v1/providers/remoteConfig.spec.ts index b5ff28385..45be85b8c 100644 --- a/spec/v1/providers/remoteConfig.spec.ts +++ b/spec/v1/providers/remoteConfig.spec.ts @@ -55,9 +55,9 @@ describe("RemoteConfig Functions", () => { expect(cloudFunction.__trigger).to.deep.equal({ eventTrigger: { - resource: 'projects/project1', - eventType: 'google.firebase.remoteconfig.update', - service: 'firebaseremoteconfig.googleapis.com', + resource: "projects/project1", + eventType: "google.firebase.remoteconfig.update", + service: "firebaseremoteconfig.googleapis.com", }, }); @@ -84,11 +84,11 @@ describe("RemoteConfig Functions", () => { }) .remoteConfig.onUpdate(() => null); - expect(cloudFunction.__trigger.regions).to.deep.equal(['us-east1']); + expect(cloudFunction.__trigger.regions).to.deep.equal(["us-east1"]); expect(cloudFunction.__trigger.availableMemoryMb).to.deep.equal(256); - expect(cloudFunction.__trigger.timeout).to.deep.equal('90s'); + expect(cloudFunction.__trigger.timeout).to.deep.equal("90s"); - expect(cloudFunction.__endpoint.region).to.deep.equal(['us-east1']); + expect(cloudFunction.__endpoint.region).to.deep.equal(["us-east1"]); expect(cloudFunction.__endpoint.availableMemoryMb).to.deep.equal(256); expect(cloudFunction.__endpoint.timeoutSeconds).to.deep.equal(90); }); @@ -124,50 +124,43 @@ describe("RemoteConfig Functions", () => { it("should unwrap the version in the event", () => { return Promise.all([ - cloudFunctionUpdate(event.data, event.context).then( - (data: any, context: any) => { - expect(data).to.deep.equal(constructVersion()); - } - ), + cloudFunctionUpdate(event.data, event.context).then((data: any, context: any) => { + expect(data).to.deep.equal(constructVersion()); + }), ]); }); }); - describe('handler namespace', () => { - describe('#onUpdate', () => { - it('should have an empty trigger', () => { - const cloudFunction = functions.handler.remoteConfig.onUpdate( - () => null - ); + describe("handler namespace", () => { + describe("#onUpdate", () => { + it("should have an empty trigger", () => { + const cloudFunction = functions.handler.remoteConfig.onUpdate(() => null); expect(cloudFunction.__trigger).to.deep.equal({}); expect(cloudFunction.__endpoint).to.be.undefined; }); - it('should correctly unwrap the event', () => { + it("should correctly unwrap the event", () => { const cloudFunctionUpdate = functions.handler.remoteConfig.onUpdate( - (version: remoteConfig.TemplateVersion, context: EventContext) => - version + (version: remoteConfig.TemplateVersion, context: EventContext) => version ); const event: Event = { data: constructVersion(), context: { - eventId: '70172329041928', - timestamp: '2018-04-09T07:56:12.975Z', - eventType: 'google.firebase.remoteconfig.update', + eventId: "70172329041928", + timestamp: "2018-04-09T07:56:12.975Z", + eventType: "google.firebase.remoteconfig.update", resource: { - service: 'firebaseremoteconfig.googleapis.com', - name: 'projects/project1', + service: "firebaseremoteconfig.googleapis.com", + name: "projects/project1", }, }, }; return Promise.all([ - cloudFunctionUpdate(event.data, event.context).then( - (data: any, context: any) => { - expect(data).to.deep.equal(constructVersion()); - } - ), + cloudFunctionUpdate(event.data, event.context).then((data: any, context: any) => { + expect(data).to.deep.equal(constructVersion()); + }), ]); }); }); diff --git a/spec/v1/providers/storage.spec.ts b/spec/v1/providers/storage.spec.ts index 0b3617afb..7aac9bb58 100644 --- a/spec/v1/providers/storage.spec.ts +++ b/spec/v1/providers/storage.spec.ts @@ -27,14 +27,14 @@ import * as functions from "../../../src/v1"; import * as storage from "../../../src/v1/providers/storage"; import { MINIMAL_V1_ENDPOINT } from "../../fixtures"; -describe('Storage Functions', () => { - describe('ObjectBuilder', () => { +describe("Storage Functions", () => { + describe("ObjectBuilder", () => { function expectedTrigger(bucket: string, eventType: string) { return { eventTrigger: { resource: `projects/_/buckets/${bucket}`, eventType: `google.storage.object.${eventType}`, - service: 'storage.googleapis.com', + service: "storage.googleapis.com", }, }; } @@ -76,11 +76,11 @@ describe('Storage Functions', () => { .storage.object() .onArchive(() => null); - expect(fn.__trigger.regions).to.deep.equal(['us-east1']); + expect(fn.__trigger.regions).to.deep.equal(["us-east1"]); expect(fn.__trigger.availableMemoryMb).to.deep.equal(256); - expect(fn.__trigger.timeout).to.deep.equal('90s'); + expect(fn.__trigger.timeout).to.deep.equal("90s"); - expect(fn.__endpoint.region).to.deep.equal(['us-east1']); + expect(fn.__endpoint.region).to.deep.equal(["us-east1"]); expect(fn.__endpoint.availableMemoryMb).to.deep.equal(256); expect(fn.__endpoint.timeoutSeconds).to.deep.equal(90); }); @@ -92,45 +92,33 @@ describe('Storage Functions', () => { .object() .onArchive(() => null); - expect(cloudFunction.__trigger).to.deep.equal( - expectedTrigger('bucky', 'archive') - ); + expect(cloudFunction.__trigger).to.deep.equal(expectedTrigger("bucky", "archive")); - expect(cloudFunction.__endpoint).to.deep.equal( - expectedEndpoint('bucky', 'archive') - ); + expect(cloudFunction.__endpoint).to.deep.equal(expectedEndpoint("bucky", "archive")); }); it("should use the default bucket when none is provided", () => { const cloudFunction = storage.object().onArchive(() => null); - expect(cloudFunction.__trigger).to.deep.equal( - expectedTrigger(defaultBucket, 'archive') - ); + expect(cloudFunction.__trigger).to.deep.equal(expectedTrigger(defaultBucket, "archive")); - expect(cloudFunction.__endpoint).to.deep.equal( - expectedEndpoint(defaultBucket, 'archive') - ); + expect(cloudFunction.__endpoint).to.deep.equal(expectedEndpoint(defaultBucket, "archive")); }); it("should allow fully qualified bucket names", () => { const subjectQualified = new storage.ObjectBuilder(() => "projects/_/buckets/bucky", {}); const result = subjectQualified.onArchive(() => null); - expect(result.__trigger).to.deep.equal( - expectedTrigger('bucky', 'archive') - ); + expect(result.__trigger).to.deep.equal(expectedTrigger("bucky", "archive")); - expect(result.__endpoint).to.deep.equal( - expectedEndpoint('bucky', 'archive') - ); + expect(result.__endpoint).to.deep.equal(expectedEndpoint("bucky", "archive")); }); - it('should throw with improperly formatted buckets', () => { + it("should throw with improperly formatted buckets", () => { expect( () => storage - .bucket('bad/bucket/format') + .bucket("bad/bucket/format") .object() .onArchive(() => null).__trigger ).to.throw(Error); @@ -179,38 +167,26 @@ describe('Storage Functions', () => { .object() .onDelete(() => null); - expect(cloudFunction.__trigger).to.deep.equal( - expectedTrigger('bucky', 'delete') - ); + expect(cloudFunction.__trigger).to.deep.equal(expectedTrigger("bucky", "delete")); - expect(cloudFunction.__endpoint).to.deep.equal( - expectedEndpoint('bucky', 'delete') - ); + expect(cloudFunction.__endpoint).to.deep.equal(expectedEndpoint("bucky", "delete")); }); it("should use the default bucket when none is provided", () => { const cloudFunction = storage.object().onDelete(() => null); - expect(cloudFunction.__trigger).to.deep.equal( - expectedTrigger(defaultBucket, 'delete') - ); + expect(cloudFunction.__trigger).to.deep.equal(expectedTrigger(defaultBucket, "delete")); - expect(cloudFunction.__endpoint).to.deep.equal( - expectedEndpoint(defaultBucket, 'delete') - ); + expect(cloudFunction.__endpoint).to.deep.equal(expectedEndpoint(defaultBucket, "delete")); }); it("should allow fully qualified bucket names", () => { const subjectQualified = new storage.ObjectBuilder(() => "projects/_/buckets/bucky", {}); const result = subjectQualified.onDelete(() => null); - expect(result.__trigger).to.deep.equal( - expectedTrigger('bucky', 'delete') - ); + expect(result.__trigger).to.deep.equal(expectedTrigger("bucky", "delete")); - expect(result.__endpoint).to.deep.equal( - expectedEndpoint('bucky', 'delete') - ); + expect(result.__endpoint).to.deep.equal(expectedEndpoint("bucky", "delete")); }); it("should throw with improperly formatted buckets", () => { @@ -259,38 +235,26 @@ describe('Storage Functions', () => { .object() .onFinalize(() => null); - expect(cloudFunction.__trigger).to.deep.equal( - expectedTrigger('bucky', 'finalize') - ); + expect(cloudFunction.__trigger).to.deep.equal(expectedTrigger("bucky", "finalize")); - expect(cloudFunction.__endpoint).to.deep.equal( - expectedEndpoint('bucky', 'finalize') - ); + expect(cloudFunction.__endpoint).to.deep.equal(expectedEndpoint("bucky", "finalize")); }); it("should use the default bucket when none is provided", () => { const cloudFunction = storage.object().onFinalize(() => null); - expect(cloudFunction.__trigger).to.deep.equal( - expectedTrigger(defaultBucket, 'finalize') - ); + expect(cloudFunction.__trigger).to.deep.equal(expectedTrigger(defaultBucket, "finalize")); - expect(cloudFunction.__endpoint).to.deep.equal( - expectedEndpoint(defaultBucket, 'finalize') - ); + expect(cloudFunction.__endpoint).to.deep.equal(expectedEndpoint(defaultBucket, "finalize")); }); it("should allow fully qualified bucket names", () => { const subjectQualified = new storage.ObjectBuilder(() => "projects/_/buckets/bucky", {}); const result = subjectQualified.onFinalize(() => null); - expect(result.__trigger).to.deep.equal( - expectedTrigger('bucky', 'finalize') - ); + expect(result.__trigger).to.deep.equal(expectedTrigger("bucky", "finalize")); - expect(result.__endpoint).to.deep.equal( - expectedEndpoint('bucky', 'finalize') - ); + expect(result.__endpoint).to.deep.equal(expectedEndpoint("bucky", "finalize")); }); it("should throw with improperly formatted buckets", () => { @@ -339,20 +303,16 @@ describe('Storage Functions', () => { .object() .onMetadataUpdate(() => null); - expect(cloudFunction.__trigger).to.deep.equal( - expectedTrigger('bucky', 'metadataUpdate') - ); + expect(cloudFunction.__trigger).to.deep.equal(expectedTrigger("bucky", "metadataUpdate")); - expect(cloudFunction.__endpoint).to.deep.equal( - expectedEndpoint('bucky', 'metadataUpdate') - ); + expect(cloudFunction.__endpoint).to.deep.equal(expectedEndpoint("bucky", "metadataUpdate")); }); it("should use the default bucket when none is provided", () => { const cloudFunction = storage.object().onMetadataUpdate(() => null); expect(cloudFunction.__trigger).to.deep.equal( - expectedTrigger(defaultBucket, 'metadataUpdate') + expectedTrigger(defaultBucket, "metadataUpdate") ); expect(cloudFunction.__endpoint).to.deep.equal( @@ -364,13 +324,9 @@ describe('Storage Functions', () => { const subjectQualified = new storage.ObjectBuilder(() => "projects/_/buckets/bucky", {}); const result = subjectQualified.onMetadataUpdate(() => null); - expect(result.__trigger).to.deep.equal( - expectedTrigger('bucky', 'metadataUpdate') - ); + expect(result.__trigger).to.deep.equal(expectedTrigger("bucky", "metadataUpdate")); - expect(result.__endpoint).to.deep.equal( - expectedEndpoint('bucky', 'metadataUpdate') - ); + expect(result.__endpoint).to.deep.equal(expectedEndpoint("bucky", "metadataUpdate")); }); it("should throw with improperly formatted buckets", () => { @@ -403,20 +359,19 @@ describe('Storage Functions', () => { }, }, }; - return cloudFunction( - goodMediaLinkEvent.data, - goodMediaLinkEvent.context - ).then((result: any, context: EventContext) => { - expect(result).equals(goodMediaLinkEvent.data.mediaLink); - }); + return cloudFunction(goodMediaLinkEvent.data, goodMediaLinkEvent.context).then( + (result: any, context: EventContext) => { + expect(result).equals(goodMediaLinkEvent.data.mediaLink); + } + ); }); }); }); - describe('namespace handler', () => { + describe("namespace handler", () => { before(() => { process.env.FIREBASE_CONFIG = JSON.stringify({ - storageBucket: 'bucket', + storageBucket: "bucket", }); }); @@ -424,191 +379,167 @@ describe('Storage Functions', () => { delete process.env.FIREBASE_CONFIG; }); - describe('#onArchive', () => { - it('should return an empty trigger', () => { - const cloudFunction = functions.handler.storage.bucket.onArchive( - () => null - ); + describe("#onArchive", () => { + it("should return an empty trigger", () => { + const cloudFunction = functions.handler.storage.bucket.onArchive(() => null); expect(cloudFunction.__trigger).to.deep.equal({}); expect(cloudFunction.__endpoint).to.be.undefined; }); - it('should not mess with media links using non-literal slashes', () => { - const cloudFunction = functions.handler.storage.object.onArchive( - (data) => { - return data.mediaLink; - } - ); + it("should not mess with media links using non-literal slashes", () => { + const cloudFunction = functions.handler.storage.object.onArchive((data) => { + return data.mediaLink; + }); const goodMediaLinkEvent = { data: { mediaLink: - 'https://www.googleapis.com/storage/v1/b/mybucket.appspot.com' + - '/o/nestedfolder%2Fanotherfolder%2Fmyobject.file?generation=12345&alt=media', + "https://www.googleapis.com/storage/v1/b/mybucket.appspot.com" + + "/o/nestedfolder%2Fanotherfolder%2Fmyobject.file?generation=12345&alt=media", }, context: { - eventId: '70172329041928', - timestamp: '2018-04-09T07:56:12.975Z', - eventType: 'google.storage.object.archive', + eventId: "70172329041928", + timestamp: "2018-04-09T07:56:12.975Z", + eventType: "google.storage.object.archive", resource: { - service: 'storage.googleapis.com', - name: 'projects/_/buckets/bucky', + service: "storage.googleapis.com", + name: "projects/_/buckets/bucky", }, }, }; - return cloudFunction( - goodMediaLinkEvent.data, - goodMediaLinkEvent.context - ).then((result: any, context: EventContext) => { - expect(result).equals(goodMediaLinkEvent.data.mediaLink); - }); + return cloudFunction(goodMediaLinkEvent.data, goodMediaLinkEvent.context).then( + (result: any, context: EventContext) => { + expect(result).equals(goodMediaLinkEvent.data.mediaLink); + } + ); }); }); - describe('#onDelete', () => { - it('should return an empty trigger', () => { - const cloudFunction = functions.handler.storage.bucket.onDelete( - () => null - ); + describe("#onDelete", () => { + it("should return an empty trigger", () => { + const cloudFunction = functions.handler.storage.bucket.onDelete(() => null); expect(cloudFunction.__trigger).to.deep.equal({}); expect(cloudFunction.__endpoint).to.be.undefined; }); - it('should not mess with media links using non-literal slashes', () => { - const cloudFunction = functions.handler.storage.object.onDelete( - (data) => { - return data.mediaLink; - } - ); + it("should not mess with media links using non-literal slashes", () => { + const cloudFunction = functions.handler.storage.object.onDelete((data) => { + return data.mediaLink; + }); const goodMediaLinkEvent = { data: { mediaLink: - 'https://www.googleapis.com/storage/v1/b/mybucket.appspot.com' + - '/o/nestedfolder%2Fanotherfolder%2Fmyobject.file?generation=12345&alt=media', + "https://www.googleapis.com/storage/v1/b/mybucket.appspot.com" + + "/o/nestedfolder%2Fanotherfolder%2Fmyobject.file?generation=12345&alt=media", }, context: { - eventId: '70172329041928', - timestamp: '2018-04-09T07:56:12.975Z', - eventType: 'google.storage.object.delete', + eventId: "70172329041928", + timestamp: "2018-04-09T07:56:12.975Z", + eventType: "google.storage.object.delete", resource: { - service: 'storage.googleapis.com', - name: 'projects/_/buckets/bucky', + service: "storage.googleapis.com", + name: "projects/_/buckets/bucky", }, }, }; - return cloudFunction( - goodMediaLinkEvent.data, - goodMediaLinkEvent.context - ).then((result: any, context: EventContext) => { - expect(result).equals(goodMediaLinkEvent.data.mediaLink); - }); + return cloudFunction(goodMediaLinkEvent.data, goodMediaLinkEvent.context).then( + (result: any, context: EventContext) => { + expect(result).equals(goodMediaLinkEvent.data.mediaLink); + } + ); }); }); - describe('#onFinalize', () => { - it('should return an empty trigger', () => { - const cloudFunction = functions.handler.storage.bucket.onFinalize( - () => null - ); + describe("#onFinalize", () => { + it("should return an empty trigger", () => { + const cloudFunction = functions.handler.storage.bucket.onFinalize(() => null); expect(cloudFunction.__trigger).to.deep.equal({}); expect(cloudFunction.__endpoint).to.be.undefined; }); - it('should not mess with media links using non-literal slashes', () => { - const cloudFunction = functions.handler.storage.object.onFinalize( - (data) => { - return data.mediaLink; - } - ); + it("should not mess with media links using non-literal slashes", () => { + const cloudFunction = functions.handler.storage.object.onFinalize((data) => { + return data.mediaLink; + }); const goodMediaLinkEvent = { data: { mediaLink: - 'https://www.googleapis.com/storage/v1/b/mybucket.appspot.com' + - '/o/nestedfolder%2Fanotherfolder%2Fmyobject.file?generation=12345&alt=media', + "https://www.googleapis.com/storage/v1/b/mybucket.appspot.com" + + "/o/nestedfolder%2Fanotherfolder%2Fmyobject.file?generation=12345&alt=media", }, context: { - eventId: '70172329041928', - timestamp: '2018-04-09T07:56:12.975Z', - eventType: 'google.storage.object.finalize', + eventId: "70172329041928", + timestamp: "2018-04-09T07:56:12.975Z", + eventType: "google.storage.object.finalize", resource: { - service: 'storage.googleapis.com', - name: 'projects/_/buckets/bucky', + service: "storage.googleapis.com", + name: "projects/_/buckets/bucky", }, }, }; - return cloudFunction( - goodMediaLinkEvent.data, - goodMediaLinkEvent.context - ).then((result: any, context: EventContext) => { - expect(result).equals(goodMediaLinkEvent.data.mediaLink); - }); + return cloudFunction(goodMediaLinkEvent.data, goodMediaLinkEvent.context).then( + (result: any, context: EventContext) => { + expect(result).equals(goodMediaLinkEvent.data.mediaLink); + } + ); }); }); - describe('#onMetadataUpdate', () => { - it('should return an empty trigger', () => { - const cloudFunction = functions.handler.storage.bucket.onMetadataUpdate( - () => null - ); + describe("#onMetadataUpdate", () => { + it("should return an empty trigger", () => { + const cloudFunction = functions.handler.storage.bucket.onMetadataUpdate(() => null); expect(cloudFunction.__trigger).to.deep.equal({}); expect(cloudFunction.__endpoint).to.be.undefined; }); - it('should not mess with media links using non-literal slashes', () => { - const cloudFunction = functions.handler.storage.object.onMetadataUpdate( - (data) => { - return data.mediaLink; - } - ); + it("should not mess with media links using non-literal slashes", () => { + const cloudFunction = functions.handler.storage.object.onMetadataUpdate((data) => { + return data.mediaLink; + }); const goodMediaLinkEvent = { data: { mediaLink: - 'https://www.googleapis.com/storage/v1/b/mybucket.appspot.com' + - '/o/nestedfolder%2Fanotherfolder%2Fmyobject.file?generation=12345&alt=media', + "https://www.googleapis.com/storage/v1/b/mybucket.appspot.com" + + "/o/nestedfolder%2Fanotherfolder%2Fmyobject.file?generation=12345&alt=media", }, context: { - eventId: '70172329041928', - timestamp: '2018-04-09T07:56:12.975Z', - eventType: 'google.storage.object.metadataUpdate', + eventId: "70172329041928", + timestamp: "2018-04-09T07:56:12.975Z", + eventType: "google.storage.object.metadataUpdate", resource: { - service: 'storage.googleapis.com', - name: 'projects/_/buckets/bucky', + service: "storage.googleapis.com", + name: "projects/_/buckets/bucky", }, }, }; - return cloudFunction( - goodMediaLinkEvent.data, - goodMediaLinkEvent.context - ).then((result: any, context: EventContext) => { - expect(result).equals(goodMediaLinkEvent.data.mediaLink); - }); + return cloudFunction(goodMediaLinkEvent.data, goodMediaLinkEvent.context).then( + (result: any, context: EventContext) => { + expect(result).equals(goodMediaLinkEvent.data.mediaLink); + } + ); }); }); }); - describe('process.env.FIREBASE_CONFIG not set', () => { + describe("process.env.FIREBASE_CONFIG not set", () => { beforeEach(() => { (config as any).firebaseConfigCache = null; delete process.env.FIREBASE_CONFIG; }); - it('should not throw if __trigger is not accessed', () => { + it("should not throw if __trigger is not accessed", () => { expect(() => storage.object().onArchive(() => null)).to.not.throw(Error); }); - it('should throw when trigger is accessed', () => { - expect(() => storage.object().onArchive(() => null).__trigger).to.throw( - Error - ); + it("should throw when trigger is accessed", () => { + expect(() => storage.object().onArchive(() => null).__trigger).to.throw(Error); }); - it('should throw when endpoint is accessed', () => { - expect(() => storage.object().onArchive(() => null).__endpoint).to.throw( - Error - ); + it("should throw when endpoint is accessed", () => { + expect(() => storage.object().onArchive(() => null).__endpoint).to.throw(Error); }); it("should not throw when #run is called", () => { diff --git a/spec/v1/providers/tasks.spec.ts b/spec/v1/providers/tasks.spec.ts index 4591ecfaf..d8f514490 100644 --- a/spec/v1/providers/tasks.spec.ts +++ b/spec/v1/providers/tasks.spec.ts @@ -43,7 +43,7 @@ describe("#onDispatch", () => { maxDoublings: 3, minBackoffSeconds: 5, }, - invoker: 'private', + invoker: "private", }).onDispatch(() => {}); expect(result.__trigger).to.deep.equal({ @@ -59,7 +59,7 @@ describe("#onDispatch", () => { maxDoublings: 3, minBackoffSeconds: 5, }, - invoker: ['private'], + invoker: ["private"], }, }); @@ -94,9 +94,9 @@ describe("#onDispatch", () => { .onDispatch(() => null); expect(fn.__trigger).to.deep.equal({ - regions: ['us-east1'], + regions: ["us-east1"], availableMemoryMb: 256, - timeout: '90s', + timeout: "90s", taskQueueTrigger: { retryConfig: { maxAttempts: 5, @@ -158,12 +158,12 @@ describe("#onDispatch", () => { const response = await runHandler(func, req as any); expect(response.status).to.equal(204); - expect(gotData).to.deep.equal({ foo: 'bar' }); + expect(gotData).to.deep.equal({ foo: "bar" }); }); }); -describe('handler namespace', () => { - it('should return an empty trigger', () => { +describe("handler namespace", () => { + it("should return an empty trigger", () => { const result = functions.handler.tasks.taskQueue.onDispatch(() => null); expect(result.__trigger).to.deep.equal({}); expect(result.__endpoint).to.be.undefined; diff --git a/spec/v1/providers/testLab.spec.ts b/spec/v1/providers/testLab.spec.ts index ac19f45da..ba8bfc27a 100644 --- a/spec/v1/providers/testLab.spec.ts +++ b/spec/v1/providers/testLab.spec.ts @@ -41,9 +41,9 @@ describe("Test Lab Functions", () => { expect(func.__trigger).to.deep.equal({ eventTrigger: { - service: 'testing.googleapis.com', - eventType: 'google.testing.testMatrix.complete', - resource: 'projects/project1/testMatrices/{matrix}', + service: "testing.googleapis.com", + eventType: "google.testing.testMatrix.complete", + resource: "projects/project1/testMatrices/{matrix}", }, }); @@ -154,23 +154,17 @@ describe("Test Lab Functions", () => { }); }); - describe('process.env.GCLOUD_PROJECT not set', () => { - it('should not throw if trigger is not accessed', () => { - expect(() => testLab.testMatrix().onComplete(() => null)).to.not.throw( - Error - ); + describe("process.env.GCLOUD_PROJECT not set", () => { + it("should not throw if trigger is not accessed", () => { + expect(() => testLab.testMatrix().onComplete(() => null)).to.not.throw(Error); }); - it('should throw when trigger is accessed', () => { - expect( - () => testLab.testMatrix().onComplete(() => null).__trigger - ).to.throw(Error); + it("should throw when trigger is accessed", () => { + expect(() => testLab.testMatrix().onComplete(() => null).__trigger).to.throw(Error); }); - it('should throw when endpoint is accessed', () => { - expect( - () => testLab.testMatrix().onComplete(() => null).__endpoint - ).to.throw(Error); + it("should throw when endpoint is accessed", () => { + expect(() => testLab.testMatrix().onComplete(() => null).__endpoint).to.throw(Error); }); }); }); diff --git a/spec/v2/providers/fixtures.ts b/spec/v2/providers/fixtures.ts index 91af94b75..7c8531aa4 100644 --- a/spec/v2/providers/fixtures.ts +++ b/spec/v2/providers/fixtures.ts @@ -1,5 +1,5 @@ -import { TriggerAnnotation } from '../../../src/v2/core'; -import * as options from '../../../src/v2/options'; +import { TriggerAnnotation } from "../../../src/v2/core"; +import * as options from "../../../src/v2/options"; export const FULL_OPTIONS: options.GlobalOptions = { region: "us-west1", @@ -14,25 +14,25 @@ export const FULL_OPTIONS: options.GlobalOptions = { ingressSettings: "ALLOW_ALL", cpu: "gcf_gen1", labels: { - hello: 'world', + hello: "world", }, - secrets: ['MY_SECRET'], + secrets: ["MY_SECRET"], }; export const FULL_TRIGGER: TriggerAnnotation = { - platform: 'gcfv2', - regions: ['us-west1'], + platform: "gcfv2", + regions: ["us-west1"], availableMemoryMb: 512, - timeout: '60s', + timeout: "60s", minInstances: 1, maxInstances: 3, concurrency: 20, - vpcConnector: 'aConnector', - vpcConnectorEgressSettings: 'ALL_TRAFFIC', - serviceAccountEmail: 'root@aProject.iam.gserviceaccount.com', - ingressSettings: 'ALLOW_ALL', + vpcConnector: "aConnector", + vpcConnectorEgressSettings: "ALL_TRAFFIC", + serviceAccountEmail: "root@aProject.iam.gserviceaccount.com", + ingressSettings: "ALLOW_ALL", labels: { - hello: 'world', + hello: "world", }, - secrets: ['MY_SECRET'], + secrets: ["MY_SECRET"], }; diff --git a/spec/v2/providers/https.spec.ts b/spec/v2/providers/https.spec.ts index 394723020..4432e3015 100644 --- a/spec/v2/providers/https.spec.ts +++ b/spec/v2/providers/https.spec.ts @@ -23,15 +23,12 @@ import { expect } from "chai"; import * as sinon from "sinon"; -import * as debug from '../../../src/common/debug'; -import * as options from '../../../src/v2/options'; -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 * as debug from "../../../src/common/debug"; +import * as options from "../../../src/v2/options"; +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"; describe("onRequest", () => { beforeEach(() => { @@ -49,7 +46,7 @@ describe("onRequest", () => { }); expect(result.__trigger).to.deep.equal({ - platform: 'gcfv2', + platform: "gcfv2", httpsTrigger: { allowInsecure: false, }, @@ -80,9 +77,9 @@ describe("onRequest", () => { ...FULL_TRIGGER, httpsTrigger: { allowInsecure: false, - invoker: ['service-account1@', 'service-account2@'], + invoker: ["service-account1@", "service-account2@"], }, - regions: ['us-west1', 'us-central1'], + regions: ["us-west1", "us-central1"], }); expect(result.__endpoint).to.deep.equal({ @@ -115,14 +112,14 @@ describe("onRequest", () => { ); expect(result.__trigger).to.deep.equal({ - platform: 'gcfv2', + platform: "gcfv2", httpsTrigger: { allowInsecure: false, - invoker: ['private'], + invoker: ["private"], }, concurrency: 20, minInstances: 3, - regions: ['us-west1', 'us-central1'], + regions: ["us-west1", "us-central1"], labels: {}, }); @@ -230,16 +227,16 @@ describe("onCall", () => { delete process.env.GCLOUD_PROJECT; }); - it('should return a minimal trigger/endpoint with appropriate values', () => { + it("should return a minimal trigger/endpoint with appropriate values", () => { const result = https.onCall((request) => 42); expect(result.__trigger).to.deep.equal({ - platform: 'gcfv2', + platform: "gcfv2", httpsTrigger: { allowInsecure: false, }, labels: { - 'deployment-callable': 'true', + "deployment-callable": "true", }, }); @@ -251,7 +248,7 @@ describe("onCall", () => { }); }); - it('should create a complex trigger/endpoint with appropriate values', () => { + it("should create a complex trigger/endpoint with appropriate values", () => { const result = https.onCall(FULL_OPTIONS, (request) => 42); expect(result.__trigger).to.deep.equal({ @@ -261,7 +258,7 @@ describe("onCall", () => { }, labels: { ...FULL_TRIGGER.labels, - 'deployment-callable': 'true', + "deployment-callable": "true", }, }); @@ -288,15 +285,15 @@ describe("onCall", () => { ); expect(result.__trigger).to.deep.equal({ - platform: 'gcfv2', + platform: "gcfv2", httpsTrigger: { allowInsecure: false, }, concurrency: 20, minInstances: 3, - regions: ['us-west1', 'us-central1'], + regions: ["us-west1", "us-central1"], labels: { - 'deployment-callable': 'true', + "deployment-callable": "true", }, }); diff --git a/spec/v2/providers/pubsub.spec.ts b/spec/v2/providers/pubsub.spec.ts index ebd85acf6..3b712044e 100644 --- a/spec/v2/providers/pubsub.spec.ts +++ b/spec/v2/providers/pubsub.spec.ts @@ -1,9 +1,9 @@ import { expect } from "chai"; -import { CloudEvent } from '../../../src/v2/core'; -import * as options from '../../../src/v2/options'; -import * as pubsub from '../../../src/v2/providers/pubsub'; -import { FULL_ENDPOINT, MINIMAL_V2_ENDPOINT, FULL_OPTIONS, FULL_TRIGGER } from './fixtures'; +import { CloudEvent } from "../../../src/v2/core"; +import * as options from "../../../src/v2/options"; +import * as pubsub from "../../../src/v2/providers/pubsub"; +import { FULL_ENDPOINT, MINIMAL_V2_ENDPOINT, FULL_OPTIONS, FULL_TRIGGER } from "./fixtures"; const EVENT_TRIGGER = { eventType: "google.cloud.pubsub.topic.v1.messagePublished", @@ -28,11 +28,11 @@ describe("onMessagePublished", () => { delete process.env.GCLOUD_PROJECT; }); - it('should return a minimal trigger/endpoint with appropriate values', () => { - const result = pubsub.onMessagePublished('topic', () => 42); + it("should return a minimal trigger/endpoint with appropriate values", () => { + const result = pubsub.onMessagePublished("topic", () => 42); expect(result.__trigger).to.deep.equal({ - platform: 'gcfv2', + platform: "gcfv2", eventTrigger: EVENT_TRIGGER, labels: {}, }); @@ -45,11 +45,8 @@ describe("onMessagePublished", () => { }); }); - it('should create a complex trigger/endpoint with appropriate values', () => { - const result = pubsub.onMessagePublished( - { ...FULL_OPTIONS, topic: 'topic' }, - () => 42 - ); + it("should create a complex trigger/endpoint with appropriate values", () => { + const result = pubsub.onMessagePublished({ ...FULL_OPTIONS, topic: "topic" }, () => 42); expect(result.__trigger).to.deep.equal({ ...FULL_TRIGGER, @@ -80,10 +77,10 @@ describe("onMessagePublished", () => { ); expect(result.__trigger).to.deep.equal({ - platform: 'gcfv2', + platform: "gcfv2", concurrency: 20, minInstances: 3, - regions: ['us-west1'], + regions: ["us-west1"], labels: {}, eventTrigger: EVENT_TRIGGER, }); @@ -111,9 +108,9 @@ describe("onMessagePublished", () => { ); expect(result.__trigger).to.deep.equal({ - platform: 'gcfv2', + platform: "gcfv2", minInstances: 3, - regions: ['us-west1'], + regions: ["us-west1"], labels: {}, eventTrigger: EVENT_TRIGGER, failurePolicy: { retry: true }, diff --git a/spec/v2/providers/storage.spec.ts b/spec/v2/providers/storage.spec.ts index 9a8e27dab..ac68dd494 100644 --- a/spec/v2/providers/storage.spec.ts +++ b/spec/v2/providers/storage.spec.ts @@ -1,12 +1,12 @@ -import { expect } from 'chai'; -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 { expect } from "chai"; +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"; const EVENT_TRIGGER = { - eventType: 'event-type', - resource: 'some-bucket', + eventType: "event-type", + resource: "some-bucket", }; const ENDPOINT_EVENT_TRIGGER = { @@ -76,11 +76,11 @@ describe("v2/storage", () => { delete process.env.GCLOUD_PROJECT; }); - it('should create a minimal trigger/endpoint with bucket', () => { - const result = storage.onOperation('event-type', 'some-bucket', () => 42); + it("should create a minimal trigger/endpoint with bucket", () => { + const result = storage.onOperation("event-type", "some-bucket", () => 42); expect(result.__trigger).to.deep.equal({ - platform: 'gcfv2', + platform: "gcfv2", labels: {}, eventTrigger: EVENT_TRIGGER, }); @@ -96,20 +96,16 @@ describe("v2/storage", () => { it("should create a minimal trigger/endpoint with opts", () => { config.resetCache({ storageBucket: "default-bucket" }); - const result = storage.onOperation( - 'event-type', - { region: 'us-west1' }, - () => 42 - ); + const result = storage.onOperation("event-type", { region: "us-west1" }, () => 42); expect(result.__trigger).to.deep.equal({ - platform: 'gcfv2', + platform: "gcfv2", labels: {}, eventTrigger: { ...EVENT_TRIGGER, - resource: 'default-bucket', + resource: "default-bucket", }, - regions: ['us-west1'], + regions: ["us-west1"], }); expect(result.__endpoint).to.deep.equal({ @@ -124,15 +120,11 @@ describe("v2/storage", () => { }); }); - it('should create a minimal trigger with bucket with opts and bucket', () => { - const result = storage.onOperation( - 'event-type', - { bucket: 'some-bucket' }, - () => 42 - ); + it("should create a minimal trigger with bucket with opts and bucket", () => { + const result = storage.onOperation("event-type", { bucket: "some-bucket" }, () => 42); expect(result.__trigger).to.deep.equal({ - platform: 'gcfv2', + platform: "gcfv2", labels: {}, eventTrigger: EVENT_TRIGGER, }); @@ -185,10 +177,10 @@ describe("v2/storage", () => { ); expect(result.__trigger).to.deep.equal({ - platform: 'gcfv2', + platform: "gcfv2", concurrency: 20, minInstances: 3, - regions: ['us-west1'], + regions: ["us-west1"], labels: {}, eventTrigger: EVENT_TRIGGER, }); @@ -205,7 +197,7 @@ describe("v2/storage", () => { }); }); - describe('onObjectArchived', () => { + describe("onObjectArchived", () => { const ARCHIVED_TRIGGER = { ...EVENT_TRIGGER, eventType: storage.archivedEvent, @@ -225,11 +217,11 @@ describe("v2/storage", () => { const result = storage.onObjectArchived(() => 42); expect(result.__trigger).to.deep.equal({ - platform: 'gcfv2', + platform: "gcfv2", labels: {}, eventTrigger: { ...ARCHIVED_TRIGGER, - resource: 'default-bucket', + resource: "default-bucket", }, }); @@ -244,15 +236,15 @@ describe("v2/storage", () => { }); }); - it('should accept bucket and handler', () => { - const result = storage.onObjectArchived('my-bucket', () => 42); + it("should accept bucket and handler", () => { + const result = storage.onObjectArchived("my-bucket", () => 42); expect(result.__trigger).to.deep.equal({ - platform: 'gcfv2', + platform: "gcfv2", labels: {}, eventTrigger: { ...ARCHIVED_TRIGGER, - resource: 'my-bucket', + resource: "my-bucket", }, }); @@ -274,13 +266,13 @@ describe("v2/storage", () => { ); expect(result.__trigger).to.deep.equal({ - platform: 'gcfv2', + platform: "gcfv2", labels: {}, eventTrigger: { ...ARCHIVED_TRIGGER, - resource: 'my-bucket', + resource: "my-bucket", }, - regions: ['us-west1'], + regions: ["us-west1"], }); expect(result.__endpoint).to.deep.equal({ @@ -299,13 +291,13 @@ describe("v2/storage", () => { config.resetCache({ storageBucket: "default-bucket" }); expect(result.__trigger).to.deep.equal({ - platform: 'gcfv2', + platform: "gcfv2", labels: {}, eventTrigger: { ...ARCHIVED_TRIGGER, - resource: 'default-bucket', + resource: "default-bucket", }, - regions: ['us-west1'], + regions: ["us-west1"], }); expect(result.__endpoint).to.deep.equal({ @@ -321,7 +313,7 @@ describe("v2/storage", () => { }); }); - describe('onObjectFinalized', () => { + describe("onObjectFinalized", () => { const FINALIZED_TRIGGER = { ...EVENT_TRIGGER, eventType: storage.finalizedEvent, @@ -341,11 +333,11 @@ describe("v2/storage", () => { const result = storage.onObjectFinalized(() => 42); expect(result.__trigger).to.deep.equal({ - platform: 'gcfv2', + platform: "gcfv2", labels: {}, eventTrigger: { ...FINALIZED_TRIGGER, - resource: 'default-bucket', + resource: "default-bucket", }, }); @@ -360,15 +352,15 @@ describe("v2/storage", () => { }); }); - it('should accept bucket and handler', () => { - const result = storage.onObjectFinalized('my-bucket', () => 42); + it("should accept bucket and handler", () => { + const result = storage.onObjectFinalized("my-bucket", () => 42); expect(result.__trigger).to.deep.equal({ - platform: 'gcfv2', + platform: "gcfv2", labels: {}, eventTrigger: { ...FINALIZED_TRIGGER, - resource: 'my-bucket', + resource: "my-bucket", }, }); @@ -390,13 +382,13 @@ describe("v2/storage", () => { ); expect(result.__trigger).to.deep.equal({ - platform: 'gcfv2', + platform: "gcfv2", labels: {}, eventTrigger: { ...FINALIZED_TRIGGER, - resource: 'my-bucket', + resource: "my-bucket", }, - regions: ['us-west1'], + regions: ["us-west1"], }); expect(result.__endpoint).to.deep.equal({ @@ -415,13 +407,13 @@ describe("v2/storage", () => { config.resetCache({ storageBucket: "default-bucket" }); expect(result.__trigger).to.deep.equal({ - platform: 'gcfv2', + platform: "gcfv2", labels: {}, eventTrigger: { ...FINALIZED_TRIGGER, - resource: 'default-bucket', + resource: "default-bucket", }, - regions: ['us-west1'], + regions: ["us-west1"], }); expect(result.__endpoint).to.deep.equal({ @@ -437,7 +429,7 @@ describe("v2/storage", () => { }); }); - describe('onObjectDeleted', () => { + describe("onObjectDeleted", () => { const DELETED_TRIGGER = { ...EVENT_TRIGGER, eventType: storage.deletedEvent, @@ -457,11 +449,11 @@ describe("v2/storage", () => { const result = storage.onObjectDeleted(() => 42); expect(result.__trigger).to.deep.equal({ - platform: 'gcfv2', + platform: "gcfv2", labels: {}, eventTrigger: { ...DELETED_TRIGGER, - resource: 'default-bucket', + resource: "default-bucket", }, }); @@ -476,15 +468,15 @@ describe("v2/storage", () => { }); }); - it('should accept bucket and handler', () => { - const result = storage.onObjectDeleted('my-bucket', () => 42); + it("should accept bucket and handler", () => { + const result = storage.onObjectDeleted("my-bucket", () => 42); expect(result.__trigger).to.deep.equal({ - platform: 'gcfv2', + platform: "gcfv2", labels: {}, eventTrigger: { ...DELETED_TRIGGER, - resource: 'my-bucket', + resource: "my-bucket", }, }); @@ -499,20 +491,17 @@ describe("v2/storage", () => { }); }); - it('should accept opts and handler', () => { - const result = storage.onObjectDeleted( - { bucket: 'my-bucket', region: 'us-west1' }, - () => 42 - ); + it("should accept opts and handler", () => { + const result = storage.onObjectDeleted({ bucket: "my-bucket", region: "us-west1" }, () => 42); expect(result.__trigger).to.deep.equal({ - platform: 'gcfv2', + platform: "gcfv2", labels: {}, eventTrigger: { ...DELETED_TRIGGER, - resource: 'my-bucket', + resource: "my-bucket", }, - regions: ['us-west1'], + regions: ["us-west1"], }); expect(result.__endpoint).to.deep.equal({ @@ -531,13 +520,13 @@ describe("v2/storage", () => { config.resetCache({ storageBucket: "default-bucket" }); expect(result.__trigger).to.deep.equal({ - platform: 'gcfv2', + platform: "gcfv2", labels: {}, eventTrigger: { ...DELETED_TRIGGER, - resource: 'default-bucket', + resource: "default-bucket", }, - regions: ['us-west1'], + regions: ["us-west1"], }); expect(result.__endpoint).to.deep.equal({ @@ -553,7 +542,7 @@ describe("v2/storage", () => { }); }); - describe('onObjectMetadataUpdated', () => { + describe("onObjectMetadataUpdated", () => { const METADATA_TRIGGER = { ...EVENT_TRIGGER, eventType: storage.metadataUpdatedEvent, @@ -573,11 +562,11 @@ describe("v2/storage", () => { const result = storage.onObjectMetadataUpdated(() => 42); expect(result.__trigger).to.deep.equal({ - platform: 'gcfv2', + platform: "gcfv2", labels: {}, eventTrigger: { ...METADATA_TRIGGER, - resource: 'default-bucket', + resource: "default-bucket", }, }); @@ -592,15 +581,15 @@ describe("v2/storage", () => { }); }); - it('should accept bucket and handler', () => { - const result = storage.onObjectMetadataUpdated('my-bucket', () => 42); + it("should accept bucket and handler", () => { + const result = storage.onObjectMetadataUpdated("my-bucket", () => 42); expect(result.__trigger).to.deep.equal({ - platform: 'gcfv2', + platform: "gcfv2", labels: {}, eventTrigger: { ...METADATA_TRIGGER, - resource: 'my-bucket', + resource: "my-bucket", }, }); @@ -622,13 +611,13 @@ describe("v2/storage", () => { ); expect(result.__trigger).to.deep.equal({ - platform: 'gcfv2', + platform: "gcfv2", labels: {}, eventTrigger: { ...METADATA_TRIGGER, - resource: 'my-bucket', + resource: "my-bucket", }, - regions: ['us-west1'], + regions: ["us-west1"], }); expect(result.__endpoint).to.deep.equal({ @@ -647,13 +636,13 @@ describe("v2/storage", () => { config.resetCache({ storageBucket: "default-bucket" }); expect(result.__trigger).to.deep.equal({ - platform: 'gcfv2', + platform: "gcfv2", labels: {}, eventTrigger: { ...METADATA_TRIGGER, - resource: 'default-bucket', + resource: "default-bucket", }, - regions: ['us-west1'], + regions: ["us-west1"], }); expect(result.__endpoint).to.deep.equal({ diff --git a/spec/v2/providers/tasks.spec.ts b/spec/v2/providers/tasks.spec.ts index b29831a1b..0c444e3af 100644 --- a/spec/v2/providers/tasks.spec.ts +++ b/spec/v2/providers/tasks.spec.ts @@ -23,11 +23,11 @@ import { expect } from "chai"; import { ManifestEndpoint } from "../../../src/runtime/manifest"; -import * as options from '../../../src/v2/options'; -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 * as options from "../../../src/v2/options"; +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"; const MINIMIAL_TASK_QUEUE_TRIGGER: ManifestEndpoint["taskQueueTrigger"] = { rateLimits: { @@ -53,11 +53,11 @@ describe("onTaskDispatched", () => { delete process.env.GCLOUD_PROJECT; }); - it('should return a minimal trigger/endpoint with appropriate values', () => { + it("should return a minimal trigger/endpoint with appropriate values", () => { const result = onTaskDispatched(() => {}); expect(result.__trigger).to.deep.equal({ - platform: 'gcfv2', + platform: "gcfv2", taskQueueTrigger: {}, labels: {}, }); @@ -85,7 +85,7 @@ describe("onTaskDispatched", () => { maxConcurrentDispatches: 5, maxDispatchesPerSecond: 10, }, - invoker: 'private', + invoker: "private", }, () => {} ); @@ -104,7 +104,7 @@ describe("onTaskDispatched", () => { maxConcurrentDispatches: 5, maxDispatchesPerSecond: 10, }, - invoker: ['private'], + invoker: ["private"], }, }); @@ -144,11 +144,11 @@ describe("onTaskDispatched", () => { ); expect(result.__trigger).to.deep.equal({ - platform: 'gcfv2', + platform: "gcfv2", taskQueueTrigger: {}, concurrency: 20, minInstances: 3, - regions: ['us-west1'], + regions: ["us-west1"], labels: {}, }); diff --git a/src/v1/cloud-functions.ts b/src/v1/cloud-functions.ts index 53c01e7ce..3967b3f34 100644 --- a/src/v1/cloud-functions.ts +++ b/src/v1/cloud-functions.ts @@ -20,15 +20,15 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -import { Request, Response } from 'express'; -import { warn } from '../logger'; +import { Request, Response } from "express"; +import { warn } from "../logger"; import { DEFAULT_FAILURE_POLICY, DeploymentOptions, RESET_VALUE, FailurePolicy, Schedule, -} from './function-configuration'; +} from "./function-configuration"; export { Request, Response }; import { convertIfPresent, copyIfPresent } from "../common/encoding"; import { @@ -412,7 +412,7 @@ export function makeCloudFunction({ return Promise.resolve(promise); }; - Object.defineProperty(cloudFunction, '__trigger', { + Object.defineProperty(cloudFunction, "__trigger", { get: () => { if (triggerResource() == null) { return {}; @@ -421,7 +421,7 @@ export function makeCloudFunction({ const trigger: any = _.assign(optionsToTrigger(options), { eventTrigger: { resource: triggerResource(), - eventType: legacyEventType || provider + '.' + eventType, + eventType: legacyEventType || provider + "." + eventType, service, }, }); @@ -432,7 +432,7 @@ export function makeCloudFunction({ }, }); - Object.defineProperty(cloudFunction, '__endpoint', { + Object.defineProperty(cloudFunction, "__endpoint", { get: () => { if (triggerResource() == null) { return undefined; @@ -543,64 +543,50 @@ export function optionsToTrigger(options: DeploymentOptions) { copyIfPresent( trigger, options, - 'regions', - 'schedule', - 'minInstances', - 'maxInstances', - 'ingressSettings', - 'vpcConnectorEgressSettings', - 'vpcConnector', - 'labels', - 'secrets' + "regions", + "schedule", + "minInstances", + "maxInstances", + "ingressSettings", + "vpcConnectorEgressSettings", + "vpcConnector", + "labels", + "secrets" ); - convertIfPresent( - trigger, - options, - 'failurePolicy', - 'failurePolicy', - (policy) => { - if (policy === false) { - return undefined; - } else if (policy === true) { - return DEFAULT_FAILURE_POLICY; - } else { - return policy; - } + convertIfPresent(trigger, options, "failurePolicy", "failurePolicy", (policy) => { + if (policy === false) { + return undefined; + } else if (policy === true) { + return DEFAULT_FAILURE_POLICY; + } else { + return policy; } - ); - convertIfPresent( - trigger, - options, - 'timeout', - 'timeoutSeconds', - durationFromSeconds - ); - convertIfPresent(trigger, options, 'availableMemoryMb', 'memory', (mem) => { + }); + convertIfPresent(trigger, options, "timeout", "timeoutSeconds", durationFromSeconds); + convertIfPresent(trigger, options, "availableMemoryMb", "memory", (mem) => { const memoryLookup = { - '128MB': 128, - '256MB': 256, - '512MB': 512, - '1GB': 1024, - '2GB': 2048, - '4GB': 4096, - '8GB': 8192, + "128MB": 128, + "256MB": 256, + "512MB": 512, + "1GB": 1024, + "2GB": 2048, + "4GB": 4096, + "8GB": 8192, }; return memoryLookup[mem]; }); convertIfPresent( trigger, options, - 'serviceAccountEmail', - 'serviceAccount', + "serviceAccountEmail", + "serviceAccount", serviceAccountFromShorthand ); return trigger; } -export function optionsToEndpoint( - options: DeploymentOptions -): ManifestEndpoint { +export function optionsToEndpoint(options: DeploymentOptions): ManifestEndpoint { const endpoint: ManifestEndpoint = {}; copyIfPresent( endpoint, diff --git a/src/v1/handler-builder.ts b/src/v1/handler-builder.ts index 7b2831e7c..be710ae63 100644 --- a/src/v1/handler-builder.ts +++ b/src/v1/handler-builder.ts @@ -20,20 +20,20 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -import * as express from 'express'; - -import { apps } from '../apps'; -import { CloudFunction, EventContext, HttpsFunction } from './cloud-functions'; -import * as analytics from './providers/analytics'; -import * as auth from './providers/auth'; -import * as database from './providers/database'; -import * as firestore from './providers/firestore'; -import * as https from './providers/https'; -import * as pubsub from './providers/pubsub'; -import * as remoteConfig from './providers/remoteConfig'; -import * as storage from './providers/storage'; -import * as tasks from './providers/tasks'; -import * as testLab from './providers/testLab'; +import * as express from "express"; + +import { apps } from "../apps"; +import { CloudFunction, EventContext, HttpsFunction } from "./cloud-functions"; +import * as analytics from "./providers/analytics"; +import * as auth from "./providers/auth"; +import * as database from "./providers/database"; +import * as firestore from "./providers/firestore"; +import * as https from "./providers/https"; +import * as pubsub from "./providers/pubsub"; +import * as remoteConfig from "./providers/remoteConfig"; +import * as storage from "./providers/storage"; +import * as tasks from "./providers/tasks"; +import * as testLab from "./providers/testLab"; /** * The `HandlerBuilder` class facilitates the writing of functions by developers @@ -76,10 +76,7 @@ export class HandlerBuilder { return func; }, onCall: ( - handler: ( - data: any, - context: https.CallableContext - ) => any | Promise + handler: (data: any, context: https.CallableContext) => any | Promise ): HttpsFunction => { const func = https._onCallWithOptions(handler, {}); func.__trigger = {}; @@ -104,10 +101,7 @@ export class HandlerBuilder { get taskQueue() { return { onDispatch: ( - handler: ( - data: any, - context: tasks.TaskContext - ) => void | Promise + handler: (data: any, context: tasks.TaskContext) => void | Promise ): HttpsFunction => { const builder = new tasks.TaskQueueBuilder(); const func = builder.onDispatch(handler); @@ -376,4 +370,4 @@ export class HandlerBuilder { } } -export let handler = new HandlerBuilder(); +export const handler = new HandlerBuilder(); diff --git a/src/v1/providers/auth.ts b/src/v1/providers/auth.ts index b70ae70c1..edef7b0bb 100644 --- a/src/v1/providers/auth.ts +++ b/src/v1/providers/auth.ts @@ -41,8 +41,8 @@ import { makeCloudFunction, optionsToEndpoint, optionsToTrigger, -} from '../cloud-functions'; -import { DeploymentOptions } from '../function-configuration'; +} from "../cloud-functions"; +import { DeploymentOptions } from "../function-configuration"; import { initV1Endpoint } from "../../runtime/manifest"; // TODO: yank in next breaking change release diff --git a/src/v1/providers/https.ts b/src/v1/providers/https.ts index 0cb5fa7d6..337d98c2b 100644 --- a/src/v1/providers/https.ts +++ b/src/v1/providers/https.ts @@ -29,14 +29,9 @@ import { HttpsError, onCallHandler, Request, -} from '../../common/providers/https'; -import { - HttpsFunction, - optionsToEndpoint, - optionsToTrigger, - Runnable, -} from '../cloud-functions'; -import { DeploymentOptions } from '../function-configuration'; +} from "../../common/providers/https"; +import { HttpsFunction, optionsToEndpoint, optionsToTrigger, Runnable } from "../cloud-functions"; +import { DeploymentOptions } from "../function-configuration"; import { initV1Endpoint } from "../../runtime/manifest"; export { Request, CallableContext, FunctionsErrorCode, HttpsError }; @@ -111,7 +106,7 @@ export function _onCallWithOptions( ...optionsToTrigger(options), httpsTrigger: {}, }; - func.__trigger.labels['deployment-callable'] = 'true'; + func.__trigger.labels["deployment-callable"] = "true"; func.__endpoint = { platform: "gcfv1", diff --git a/src/v1/providers/tasks.ts b/src/v1/providers/tasks.ts index 5bee96997..9126588a5 100644 --- a/src/v1/providers/tasks.ts +++ b/src/v1/providers/tasks.ts @@ -36,7 +36,7 @@ import { ManifestEndpoint, ManifestRequiredAPI, } from "../../runtime/manifest"; -import { optionsToEndpoint, optionsToTrigger } from '../cloud-functions'; +import { optionsToEndpoint, optionsToTrigger } from "../cloud-functions"; import { DeploymentOptions } from "../function-configuration"; export { RetryConfig, RateLimits, TaskContext }; @@ -113,13 +113,13 @@ export class TaskQueueBuilder { ...optionsToTrigger(this.depOpts || {}), taskQueueTrigger: {}, }; - copyIfPresent(func.__trigger.taskQueueTrigger, this.tqOpts, 'retryConfig'); - copyIfPresent(func.__trigger.taskQueueTrigger, this.tqOpts, 'rateLimits'); + copyIfPresent(func.__trigger.taskQueueTrigger, this.tqOpts, "retryConfig"); + copyIfPresent(func.__trigger.taskQueueTrigger, this.tqOpts, "rateLimits"); convertIfPresent( func.__trigger.taskQueueTrigger, this.tqOpts, - 'invoker', - 'invoker', + "invoker", + "invoker", convertInvoker ); diff --git a/src/v2/options.ts b/src/v2/options.ts index a94982d71..cfe951917 100644 --- a/src/v2/options.ts +++ b/src/v2/options.ts @@ -32,12 +32,12 @@ import { serviceAccountFromShorthand, } from "../common/encoding"; import { RESET_VALUE, ResetValue } from "../common/options"; -import { ManifestEndpoint } from '../runtime/manifest'; -import { TriggerAnnotation } from './core'; +import { ManifestEndpoint } from "../runtime/manifest"; +import { TriggerAnnotation } from "./core"; import { declaredParams, Expression } from "../params"; import { ParamSpec, SecretParam } from "../params/types"; -import { HttpsOptions } from './providers/https'; -import * as logger from '../logger'; +import { HttpsOptions } from "./providers/https"; +import * as logger from "../logger"; export { RESET_VALUE } from "../common/options"; @@ -267,26 +267,20 @@ export function optionsToTriggerAnnotations( copyIfPresent( annotation, opts, - 'concurrency', - 'minInstances', - 'maxInstances', - 'ingressSettings', - 'labels', - 'vpcConnector', - 'vpcConnectorEgressSettings', - 'secrets' - ); - convertIfPresent( - annotation, - opts, - 'availableMemoryMb', - 'memory', - (mem: MemoryOption) => { - return MemoryOptionToMB[mem]; - } + "concurrency", + "minInstances", + "maxInstances", + "ingressSettings", + "labels", + "vpcConnector", + "vpcConnectorEgressSettings", + "secrets" ); - convertIfPresent(annotation, opts, 'regions', 'region', (region) => { - if (typeof region === 'string') { + convertIfPresent(annotation, opts, "availableMemoryMb", "memory", (mem: MemoryOption) => { + return MemoryOptionToMB[mem]; + }); + convertIfPresent(annotation, opts, "regions", "region", (region) => { + if (typeof region === "string") { return [region]; } return region; @@ -294,22 +288,16 @@ export function optionsToTriggerAnnotations( convertIfPresent( annotation, opts, - 'serviceAccountEmail', - 'serviceAccount', + "serviceAccountEmail", + "serviceAccount", serviceAccountFromShorthand ); + convertIfPresent(annotation, opts, "timeout", "timeoutSeconds", durationFromSeconds); convertIfPresent( annotation, - opts, - 'timeout', - 'timeoutSeconds', - durationFromSeconds - ); - convertIfPresent( - annotation, - (opts as any) as EventHandlerOptions, - 'failurePolicy', - 'retry', + opts as any as EventHandlerOptions, + "failurePolicy", + "retry", (retry: boolean) => { return retry ? { retry: true } : null; } diff --git a/src/v2/providers/https.ts b/src/v2/providers/https.ts index 8f00a3aa5..d1c9216af 100644 --- a/src/v2/providers/https.ts +++ b/src/v2/providers/https.ts @@ -235,18 +235,14 @@ export function onRequest( }; } - Object.defineProperty(handler, '__trigger', { + Object.defineProperty(handler, "__trigger", { get: () => { - const baseOpts = options.optionsToTriggerAnnotations( - options.getGlobalOptions() - ); + const baseOpts = options.optionsToTriggerAnnotations(options.getGlobalOptions()); // global options calls region a scalar and https allows it to be an array, // but optionsToTriggerAnnotations handles both cases. - const specificOpts = options.optionsToTriggerAnnotations( - opts as options.GlobalOptions - ); + const specificOpts = options.optionsToTriggerAnnotations(opts as options.GlobalOptions); const trigger: any = { - platform: 'gcfv2', + platform: "gcfv2", ...baseOpts, ...specificOpts, labels: { @@ -257,13 +253,7 @@ export function onRequest( allowInsecure: false, }, }; - convertIfPresent( - trigger.httpsTrigger, - opts, - 'invoker', - 'invoker', - convertInvoker - ); + convertIfPresent(trigger.httpsTrigger, opts, "invoker", "invoker", convertInvoker); return trigger; }, }); diff --git a/src/v2/providers/tasks.ts b/src/v2/providers/tasks.ts index 4b31ace79..d5974b722 100644 --- a/src/v2/providers/tasks.ts +++ b/src/v2/providers/tasks.ts @@ -207,27 +207,17 @@ export function onTaskDispatched( const fixedLen = (req: Request) => handler(req); const func: any = wrapTraceContext(onDispatchHandler(fixedLen)); - Object.defineProperty(func, '__trigger', { + Object.defineProperty(func, "__trigger", { get: () => { - const baseOpts = options.optionsToTriggerAnnotations( - options.getGlobalOptions() - ); + const baseOpts = options.optionsToTriggerAnnotations(options.getGlobalOptions()); // global options calls region a scalar and https allows it to be an array, // but optionsToTriggerAnnotations handles both cases. - const specificOpts = options.optionsToTriggerAnnotations( - opts as options.GlobalOptions - ); + const specificOpts = options.optionsToTriggerAnnotations(opts as options.GlobalOptions); const taskQueueTrigger: Record = {}; - copyIfPresent(taskQueueTrigger, opts, 'retryConfig', 'rateLimits'); - convertIfPresent( - taskQueueTrigger, - opts, - 'invoker', - 'invoker', - convertInvoker - ); + copyIfPresent(taskQueueTrigger, opts, "retryConfig", "rateLimits"); + convertIfPresent(taskQueueTrigger, opts, "invoker", "invoker", convertInvoker); return { - platform: 'gcfv2', + platform: "gcfv2", ...baseOpts, ...specificOpts, labels: { From 3cd81db1897a10c56aab50eff07f45406fca6952 Mon Sep 17 00:00:00 2001 From: Tyler Stark Date: Mon, 24 Oct 2022 00:43:12 -0500 Subject: [PATCH 5/8] Resolving merge conflicts --- spec/v1/cloud-functions.spec.ts | 91 -- spec/v1/function-builder.spec.ts | 20 +- spec/v1/providers/analytics.spec.ts | 53 +- spec/v1/providers/auth.spec.ts | 41 +- spec/v1/providers/database.spec.ts | 1212 +++++++++++------------- spec/v1/providers/firestore.spec.ts | 74 +- spec/v1/providers/https.spec.ts | 22 +- spec/v1/providers/pubsub.spec.ts | 79 +- spec/v1/providers/remoteConfig.spec.ts | 37 +- spec/v1/providers/storage.spec.ts | 158 +-- spec/v1/providers/tasks.spec.ts | 10 +- spec/v2/providers/fixtures.ts | 44 + spec/v2/providers/https.spec.ts | 4 +- spec/v2/providers/storage.spec.ts | 17 + spec/v2/providers/tasks.spec.ts | 6 +- src/common/config.ts | 9 + src/common/encoding.ts | 32 + src/v1/cloud-functions.ts | 11 +- src/v1/handler-builder.ts | 373 -------- src/v1/providers/https.ts | 11 + src/v2/core.ts | 2 + src/v2/providers/https.ts | 28 + src/v2/providers/pubsub.ts | 23 + src/v2/providers/storage.ts | 23 + 24 files changed, 768 insertions(+), 1612 deletions(-) delete mode 100644 src/v1/handler-builder.ts diff --git a/spec/v1/cloud-functions.spec.ts b/spec/v1/cloud-functions.spec.ts index 73c19e7b9..3a9c99b67 100644 --- a/spec/v1/cloud-functions.spec.ts +++ b/spec/v1/cloud-functions.spec.ts @@ -23,7 +23,6 @@ import { expect } from "chai"; import { - Change, Event, EventContext, makeCloudFunction, @@ -363,93 +362,3 @@ describe("makeAuth and makeAuthType", () => { }); }); }); - -describe("Change", () => { - describe("applyFieldMask", () => { - const after = { - foo: "bar", - num: 2, - obj: { - a: 1, - b: 2, - }, - }; - - it("should handle deleted values", () => { - const sparseBefore = { baz: "qux" }; - const fieldMask = "baz"; - expect(Change.applyFieldMask(sparseBefore, after, fieldMask)).to.deep.equal({ - foo: "bar", - num: 2, - obj: { - a: 1, - b: 2, - }, - baz: "qux", - }); - }); - - it("should handle created values", () => { - const sparseBefore = {}; - const fieldMask = "num,obj.a"; - expect(Change.applyFieldMask(sparseBefore, after, fieldMask)).to.deep.equal({ - foo: "bar", - obj: { - b: 2, - }, - }); - }); - - it("should handle mutated values", () => { - const sparseBefore = { - num: 3, - obj: { - a: 3, - }, - }; - const fieldMask = "num,obj.a"; - expect(Change.applyFieldMask(sparseBefore, after, fieldMask)).to.deep.equal({ - foo: "bar", - num: 3, - obj: { - a: 3, - b: 2, - }, - }); - }); - }); - - describe("fromJSON", () => { - it("should create a Change object with a `before` and `after`", () => { - const created = Change.fromJSON({ - before: { foo: "bar" }, - after: { foo: "faz" }, - }); - expect(created instanceof Change).to.equal(true); - expect(created.before).to.deep.equal({ foo: "bar" }); - expect(created.after).to.deep.equal({ foo: "faz" }); - }); - - it("should apply the customizer function to `before` and `after`", () => { - function customizer(input: any) { - _.set(input, "another", "value"); - return input as T; - } - const created = Change.fromJSON( - { - before: { foo: "bar" }, - after: { foo: "faz" }, - }, - customizer - ); - expect(created.before).to.deep.equal({ - foo: "bar", - another: "value", - }); - expect(created.after).to.deep.equal({ - foo: "faz", - another: "value", - }); - }); - }); -}); diff --git a/spec/v1/function-builder.spec.ts b/spec/v1/function-builder.spec.ts index 447686ec9..5343b186f 100644 --- a/spec/v1/function-builder.spec.ts +++ b/spec/v1/function-builder.spec.ts @@ -24,7 +24,6 @@ import { expect } from "chai"; import { clearParams, defineSecret } from "../../src/params"; import * as functions from "../../src/v1"; -import { ResetValue } from "../../src/common/options"; describe("FunctionBuilder", () => { before(() => { @@ -104,9 +103,19 @@ describe("FunctionBuilder", () => { .auth.user() .onCreate((user) => user); - expect(fn.__trigger.availableMemoryMb).to.deep.equal(256); - expect(fn.__trigger.timeout).to.deep.equal("90s"); - expect(fn.__trigger.failurePolicy).to.deep.equal({ retry: {} }); + + expect(fn.__trigger.secrets).to.deep.equal([ + { + name: "API_KEY", + }, + ]); + expect(fn.__endpoint.secretEnvironmentVariables).to.deep.equal([ + { + key: "API_KEY", + }, + ]); + + clearParams(); }); it("should apply a default failure policy if it's aliased with `true`", () => { @@ -499,8 +508,6 @@ describe("FunctionBuilder", () => { }); it("should throw error given secrets expressed with full resource name", () => { - const sp = defineSecret("projects/my-project/secrets/API_KEY"); - expect(() => functions.runWith({ secrets: ["projects/my-project/secrets/API_KEY"], @@ -509,6 +516,7 @@ describe("FunctionBuilder", () => { }); it("should throw error given invalid secret config", () => { + const sp = defineSecret("projects/my-project/secrets/API_KEY"); expect(() => functions.runWith({ secrets: [sp], diff --git a/spec/v1/providers/analytics.spec.ts b/spec/v1/providers/analytics.spec.ts index 22308b825..511751ceb 100644 --- a/spec/v1/providers/analytics.spec.ts +++ b/spec/v1/providers/analytics.spec.ts @@ -23,9 +23,9 @@ import { expect } from "chai"; import * as functions from "../../../src/v1"; -import { Event, EventContext } from "../../../src/v1/cloud-functions"; +import { Event } from "../../../src/v1/cloud-functions"; import * as analytics from "../../../src/v1/providers/analytics"; -import * as analytics_spec_input from "./analytics.spec.input"; +import * as analyticsSpecInput from "./analytics.spec.input"; import { MINIMAL_V1_ENDPOINT } from "../../fixtures"; describe("Analytics Functions", () => { @@ -294,59 +294,16 @@ describe("Analytics Functions", () => { .event("first_open") .onLog((data: analytics.AnalyticsEvent) => data); // The payload in analytics_spec_input contains all possible fields at least once. - const payloadData = analytics_spec_input.fullPayload.data; - const payloadContext = analytics_spec_input.fullPayload.context; + const payloadData = analyticsSpecInput.fullPayload.data; + const payloadContext = analyticsSpecInput.fullPayload.context; return expect(cloudFunction(payloadData, payloadContext)).to.eventually.deep.equal( - analytics_spec_input.data + analyticsSpecInput.data ); }); }); }); - describe("handler namespace", () => { - describe("#onLog", () => { - it("should return an empty trigger/endpoint", () => { - const cloudFunction = functions.handler.analytics.event.onLog(() => null); - expect(cloudFunction.__trigger).to.deep.equal({}); - expect(cloudFunction.__endpoint).to.be.undefined; - }); - - it("should handle an event with the appropriate fields", () => { - const cloudFunction = functions.handler.analytics.event.onLog( - (data: analytics.AnalyticsEvent, context: EventContext) => data - ); - - // The event data delivered over the wire will be the JSON for an AnalyticsEvent: - // https://firebase.google.com/docs/auth/admin/manage-users#retrieve_user_data - const event: Event = { - data: { - userDim: { - userId: "hi!", - }, - }, - context: { - eventId: "70172329041928", - timestamp: "2018-04-09T07:56:12.975Z", - eventType: "providers/google.firebase.analytics/eventTypes/event.log", - resource: { - service: "app-measurement.com", - name: "projects/project1/events/first_open", - }, - }, - }; - - return expect(cloudFunction(event.data, event.context)).to.eventually.deep.equal({ - params: {}, - user: { - userId: "hi!", - userProperties: {}, - }, - }); - }); - }); - }); - describe("process.env.GCLOUD_PROJECT not set", () => { it("should not throw if __trigger is not accessed", () => { expect(() => analytics.event("event").onLog(() => null)).to.not.throw(Error); diff --git a/spec/v1/providers/auth.spec.ts b/spec/v1/providers/auth.spec.ts index 7eae81f33..f5f6a806d 100644 --- a/spec/v1/providers/auth.spec.ts +++ b/spec/v1/providers/auth.spec.ts @@ -174,7 +174,7 @@ describe("Auth Functions", () => { refreshToken: false, }, }) - .beforeCreate((u, c) => Promise.resolve()); + .beforeCreate(() => Promise.resolve()); expect(fn.__trigger).to.deep.equal({ labels: {}, @@ -264,7 +264,7 @@ describe("Auth Functions", () => { refreshToken: false, }, }) - .beforeSignIn((u, c) => Promise.resolve()); + .beforeSignIn(() => Promise.resolve()); expect(fn.__trigger).to.deep.equal({ labels: {}, @@ -321,43 +321,6 @@ describe("Auth Functions", () => { }); }); - describe("handler namespace", () => { - describe("#onCreate", () => { - it("should return an empty trigger", () => { - const cloudFunction = functions.handler.auth.user.onCreate(() => null); - expect(cloudFunction.__trigger).to.deep.equal({}); - }); - - it("should return an empty endpoint", () => { - const cloudFunction = functions.handler.auth.user.onCreate(() => null); - expect(cloudFunction.__endpoint).to.be.undefined; - }); - }); - - describe("#onDelete", () => { - const cloudFunctionDelete: CloudFunction = functions.handler.auth.user.onDelete( - (data: UserRecord) => data - ); - - it("should return an empty trigger", () => { - const cloudFunction = functions.handler.auth.user.onDelete(() => null); - expect(cloudFunction.__trigger).to.deep.equal({}); - }); - - it("should return an empty endpoint", () => { - const cloudFunction = functions.handler.auth.user.onDelete(() => null); - expect(cloudFunction.__endpoint).to.be.undefined; - }); - - it("should handle wire format as of v5.0.0 of firebase-admin", () => { - return cloudFunctionDelete(event.data, event.context).then((data: any) => { - expect(data.metadata.creationTime).to.equal("2016-12-15T19:37:37.059Z"); - expect(data.metadata.lastSignInTime).to.equal("2017-01-01T00:00:00.000Z"); - }); - }); - }); - }); - describe("process.env.GCLOUD_PROJECT not set", () => { it("should not throw if __trigger is not accessed", () => { expect(() => auth.user().onCreate(() => null)).to.not.throw(Error); diff --git a/spec/v1/providers/database.spec.ts b/spec/v1/providers/database.spec.ts index f0317510a..aca904c69 100644 --- a/spec/v1/providers/database.spec.ts +++ b/spec/v1/providers/database.spec.ts @@ -20,14 +20,14 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -import { expect } from "chai"; -import { getApp, setApp } from "../../../src/common/app"; +import {expect} from "chai"; +import {getApp, setApp} from "../../../src/common/app"; import * as config from "../../../src/common/config"; -import { applyChange } from "../../../src/common/utilities/utils"; +import {applyChange} from "../../../src/common/utilities/utils"; import * as functions from "../../../src/v1"; import * as database from "../../../src/v1/providers/database"; -import { expectType } from "../../common/metaprogramming"; -import { MINIMAL_V1_ENDPOINT } from "../../fixtures"; +import {expectType} from "../../common/metaprogramming"; +import {MINIMAL_V1_ENDPOINT} from "../../fixtures"; describe("Database Functions", () => { describe("DatabaseBuilder", () => { @@ -62,7 +62,6 @@ describe("Database Functions", () => { config.resetCache({ databaseURL: "https://subdomain.apse.firebasedatabase.app", }); - appsNamespace.init(); }); after(() => { @@ -87,817 +86,676 @@ describe("Database Functions", () => { expect(fn.__endpoint.region).to.deep.equal(["us-east1"]); expect(fn.__endpoint.availableMemoryMb).to.deep.equal(256); expect(fn.__endpoint.timeoutSeconds).to.deep.equal(90); + }); - describe("#onWrite()", () => { - it("should return a trigger/endpoint with appropriate values", () => { - const func = database.ref("foo").onWrite(() => null); + describe("#onWrite()", () => { + it("should return a trigger/endpoint with appropriate values", () => { + const func = database.ref("foo").onWrite(() => null); - expect(func.__trigger).to.deep.equal( - expectedTrigger("projects/_/instances/subdomain/refs/foo", "ref.write") - ); + expect(func.__trigger).to.deep.equal( + expectedTrigger("projects/_/instances/subdomain/refs/foo", "ref.write") + ); - expect(func.__endpoint).to.deep.equal( - expectedEndpoint("projects/_/instances/subdomain/refs/foo", "ref.write") - ); - }); + expect(func.__endpoint).to.deep.equal( + expectedEndpoint("projects/_/instances/subdomain/refs/foo", "ref.write") + ); + }); - it("should let developers choose a database instance", () => { - const func = database - .instance("custom") - .ref("foo") - .onWrite(() => null); + it("should let developers choose a database instance", () => { + const func = database + .instance("custom") + .ref("foo") + .onWrite(() => null); - expect(func.__trigger).to.deep.equal( - expectedTrigger("projects/_/instances/custom/refs/foo", "ref.write") - ); + expect(func.__trigger).to.deep.equal( + expectedTrigger("projects/_/instances/custom/refs/foo", "ref.write") + ); - expect(func.__endpoint).to.deep.equal( - expectedEndpoint("projects/_/instances/custom/refs/foo", "ref.write") - ); - }); + expect(func.__endpoint).to.deep.equal( + expectedEndpoint("projects/_/instances/custom/refs/foo", "ref.write") + ); + }); - it("should return a handler that emits events with a proper DataSnapshot", () => { - const event = { - data: { - data: null, - delta: { foo: "bar" }, - }, - context: { - eventId: "70172329041928", - eventType: "providers/google.firebase.database/eventTypes/ref.write", - timestamp: "2018-04-09T07:56:12.975Z", - resource: "projects/_/instances/subdomains/refs/users", - }, - }; - const handler = database.ref("/users/{id}").onWrite((change) => { - expect(change.after.val()).to.deep.equal({ foo: "bar" }); - }); - - return handler(event.data, event.context); + it("should return a handler that emits events with a proper DataSnapshot", () => { + const event = { + data: { + data: null, + delta: {foo: "bar"}, + }, + context: { + eventId: "70172329041928", + eventType: "providers/google.firebase.database/eventTypes/ref.write", + timestamp: "2018-04-09T07:56:12.975Z", + resource: "projects/_/instances/subdomains/refs/users", + }, + }; + const handler = database.ref("/users/{id}").onWrite((change) => { + expect(change.after.val()).to.deep.equal({foo: "bar"}); }); - it("Should have params of the correct type", () => { - database.ref("foo").onWrite((event, context) => { - expectType>(context.params); - }); - database.ref("foo/{bar}").onWrite((event, context) => { - expectType<{ bar: string }>(context.params); - }); - database.ref("foo/{bar}/{baz}").onWrite((event, context) => { - expectType<{ bar: string; baz: string }>(context.params); - }); - }); + return handler(event.data, event.context); }); - describe("#onCreate()", () => { - it("should return a trigger/endpoint with appropriate values", () => { - const func = database.ref("foo").onCreate(() => null); - - expect(func.__trigger).to.deep.equal( - expectedTrigger("projects/_/instances/subdomain/refs/foo", "ref.create") - ); - - expect(func.__endpoint).to.deep.equal( - expectedEndpoint("projects/_/instances/subdomain/refs/foo", "ref.create") - ); + it("Should have params of the correct type", () => { + database.ref("foo").onWrite((event, context) => { + expectType>(context.params); + }); + database.ref("foo/{bar}").onWrite((event, context) => { + expectType<{ bar: string }>(context.params); }); + database.ref("foo/{bar}/{baz}").onWrite((event, context) => { + expectType<{ bar: string; baz: string }>(context.params); + }); + }); + }); - it("should let developers choose a database instance", () => { - const func = database - .instance("custom") - .ref("foo") - .onCreate(() => null); + describe("#onCreate()", () => { + it("should return a trigger/endpoint with appropriate values", () => { + const func = database.ref("foo").onCreate(() => null); - expect(func.__trigger).to.deep.equal( - expectedTrigger("projects/_/instances/custom/refs/foo", "ref.create") - ); + expect(func.__trigger).to.deep.equal( + expectedTrigger("projects/_/instances/subdomain/refs/foo", "ref.create") + ); - expect(func.__endpoint).to.deep.equal( - expectedEndpoint("projects/_/instances/custom/refs/foo", "ref.create") - ); - }); + expect(func.__endpoint).to.deep.equal( + expectedEndpoint("projects/_/instances/subdomain/refs/foo", "ref.create") + ); + }); - it("should return a handler that emits events with a proper DataSnapshot", () => { - const event = { - data: { - data: null, - delta: { foo: "bar" }, - }, - context: { - eventId: "70172329041928", - eventType: "providers/google.firebase.database/eventTypes/ref.create", - timestamp: "2018-04-09T07:56:12.975Z", - resource: "projects/_/instances/subdomains/refs/users", - }, - }; - - const handler = database.ref("/users/{id}").onCreate((data) => { - expect(data.val()).to.deep.equal({ foo: "bar" }); - }); - - return handler(event.data, event.context); - }); + it("should let developers choose a database instance", () => { + const func = database + .instance("custom") + .ref("foo") + .onCreate(() => null); - it("Should have params of the correct type", () => { - database.ref("foo").onCreate((event, context) => { - expectType>(context.params); - }); - database.ref("foo/{bar}").onCreate((event, context) => { - expectType<{ bar: string }>(context.params); - }); - database.ref("foo/{bar}/{baz}").onCreate((event, context) => { - expectType<{ bar: string; baz: string }>(context.params); - }); - }); - }); + expect(func.__trigger).to.deep.equal( + expectedTrigger("projects/_/instances/custom/refs/foo", "ref.create") + ); - describe("#onUpdate()", () => { - it("should return a trigger/endpoint with appropriate values", () => { - const func = database.ref("foo").onUpdate(() => null); + expect(func.__endpoint).to.deep.equal( + expectedEndpoint("projects/_/instances/custom/refs/foo", "ref.create") + ); + }); - expect(func.__trigger).to.deep.equal( - expectedTrigger("projects/_/instances/subdomain/refs/foo", "ref.update") - ); + it("should return a handler that emits events with a proper DataSnapshot", () => { + const event = { + data: { + data: null, + delta: {foo: "bar"}, + }, + context: { + eventId: "70172329041928", + eventType: "providers/google.firebase.database/eventTypes/ref.create", + timestamp: "2018-04-09T07:56:12.975Z", + resource: "projects/_/instances/subdomains/refs/users", + }, + }; - expect(func.__endpoint).to.deep.equal( - expectedEndpoint("projects/_/instances/subdomain/refs/foo", "ref.update") - ); + const handler = database.ref("/users/{id}").onCreate((data) => { + expect(data.val()).to.deep.equal({foo: "bar"}); }); - it("should let developers choose a database instance", () => { - const func = database - .instance("custom") - .ref("foo") - .onUpdate(() => null); - - expect(func.__trigger).to.deep.equal( - expectedTrigger("projects/_/instances/custom/refs/foo", "ref.update") - ); + return handler(event.data, event.context); + }); - expect(func.__endpoint).to.deep.equal( - expectedEndpoint("projects/_/instances/custom/refs/foo", "ref.update") - ); + it("Should have params of the correct type", () => { + database.ref("foo").onCreate((event, context) => { + expectType>(context.params); }); - - it("should return a handler that emits events with a proper DataSnapshot", () => { - const event = { - data: { - data: null, - delta: { foo: "bar" }, - }, - context: { - eventId: "70172329041928", - eventType: "providers/google.firebase.database/eventTypes/ref.update", - timestamp: "2018-04-09T07:56:12.975Z", - resource: "projects/_/instances/subdomains/refs/users", - }, - }; - - const handler = database.ref("/users/{id}").onUpdate((change) => { - expect(change.after.val()).to.deep.equal({ foo: "bar" }); - }); - - return handler(event.data, event.context); + database.ref("foo/{bar}").onCreate((event, context) => { + expectType<{ bar: string }>(context.params); }); - - it("Should have params of the correct type", () => { - database.ref("foo").onUpdate((event, context) => { - expectType>(context.params); - }); - database.ref("foo/{bar}").onUpdate((event, context) => { - expectType<{ bar: string }>(context.params); - }); - database.ref("foo/{bar}/{baz}").onUpdate((event, context) => { - expectType<{ bar: string; baz: string }>(context.params); - }); + database.ref("foo/{bar}/{baz}").onCreate((event, context) => { + expectType<{ bar: string; baz: string }>(context.params); }); }); + }); - describe("#onDelete()", () => { - it("should return a trigger/endpoint with appropriate values", () => { - const func = database.ref("foo").onDelete(() => null); - - expect(func.__trigger).to.deep.equal( - expectedTrigger("projects/_/instances/subdomain/refs/foo", "ref.delete") - ); + describe("#onUpdate()", () => { + it("should return a trigger/endpoint with appropriate values", () => { + const func = database.ref("foo").onUpdate(() => null); - expect(func.__endpoint).to.deep.equal( - expectedEndpoint("projects/_/instances/subdomain/refs/foo", "ref.delete") - ); - }); + expect(func.__trigger).to.deep.equal( + expectedTrigger("projects/_/instances/subdomain/refs/foo", "ref.update") + ); - it("should let developers choose a database instance", () => { - const func = database - .instance("custom") - .ref("foo") - .onDelete(() => null); + expect(func.__endpoint).to.deep.equal( + expectedEndpoint("projects/_/instances/subdomain/refs/foo", "ref.update") + ); + }); - expect(func.__trigger).to.deep.equal( - expectedTrigger("projects/_/instances/custom/refs/foo", "ref.delete") - ); + it("should let developers choose a database instance", () => { + const func = database + .instance("custom") + .ref("foo") + .onUpdate(() => null); - expect(func.__endpoint).to.deep.equal( - expectedEndpoint("projects/_/instances/custom/refs/foo", "ref.delete") - ); - }); + expect(func.__trigger).to.deep.equal( + expectedTrigger("projects/_/instances/custom/refs/foo", "ref.update") + ); - it("should return a handler that emits events with a proper DataSnapshot", () => { - const event = { - data: { - data: { foo: "bar" }, - delta: null, - }, - context: { - eventId: "70172329041928", - eventType: "providers/google.firebase.database/eventTypes/ref.delete", - timestamp: "2018-04-09T07:56:12.975Z", - resource: "projects/_/instances/subdomains/refs/users", - }, - }; - - const handler = database.ref("/users/{id}").onDelete((data, context) => { - expect(data.val()).to.deep.equal({ foo: "bar" }); - }); - - return handler(event.data, event.context); - }); + expect(func.__endpoint).to.deep.equal( + expectedEndpoint("projects/_/instances/custom/refs/foo", "ref.update") + ); }); - }); - describe("handler namespace", () => { - describe("#onWrite()", () => { - it("correctly sets trigger to {}", () => { - const cf = functions.handler.database.ref.onWrite(() => null); - expect(cf.__trigger).to.deep.equal({}); - expect(cf.__endpoint).to.be.undefined; - }); + it("should return a handler that emits events with a proper DataSnapshot", () => { + const event = { + data: { + data: null, + delta: {foo: "bar"}, + }, + context: { + eventId: "70172329041928", + eventType: "providers/google.firebase.database/eventTypes/ref.update", + timestamp: "2018-04-09T07:56:12.975Z", + resource: "projects/_/instances/subdomains/refs/users", + }, + }; - it("should be able to use the instance entry point", () => { - const func = functions.handler.database.instance.ref.onWrite(() => null); - expect(func.__trigger).to.deep.equal({}); - expect(func.__endpoint).to.be.undefined; + const handler = database.ref("/users/{id}").onUpdate((change) => { + expect(change.after.val()).to.deep.equal({foo: "bar"}); }); - it("should return a handler that emits events with a proper DataSnapshot", () => { - const event = { - data: { - data: null, - delta: { foo: "bar" }, - }, - context: { - eventId: "70172329041928", - eventType: "providers/google.firebase.database/eventTypes/ref.write", - timestamp: "2018-04-09T07:56:12.975Z", - resource: "projects/_/instances/subdomains/refs/users", - }, - }; - - const handler = functions.handler.database.ref.onWrite((change, context) => { - return expect(change.after.val()).to.deep.equal({ foo: "bar" }); - }); - - return handler(event.data, event.context); - }); + return handler(event.data, event.context); }); - describe("#onCreate()", () => { - it("correctly sets trigger to {}", () => { - const cf = functions.handler.database.ref.onCreate(() => null); - expect(cf.__trigger).to.deep.equal({}); - expect(cf.__endpoint).to.be.undefined; + it("Should have params of the correct type", () => { + database.ref("foo").onUpdate((event, context) => { + expectType>(context.params); }); - - it("should be able to use the instance entry point", () => { - const func = functions.handler.database.instance.ref.onCreate(() => null); - expect(func.__trigger).to.deep.equal({}); - expect(func.__endpoint).to.be.undefined; + database.ref("foo/{bar}").onUpdate((event, context) => { + expectType<{ bar: string }>(context.params); }); - - it("should return a handler that emits events with a proper DataSnapshot", () => { - const event = { - data: { - data: null, - delta: { foo: "bar" }, - }, - context: { - eventId: "70172329041928", - eventType: "providers/google.firebase.database/eventTypes/ref.create", - timestamp: "2018-04-09T07:56:12.975Z", - resource: "projects/_/instances/subdomains/refs/users", - }, - }; - const handler = functions.handler.database.ref.onCreate((data, context) => { - return expect(data.val()).to.deep.equal({ foo: "bar" }); - }); - - return handler(event.data, event.context); + database.ref("foo/{bar}/{baz}").onUpdate((event, context) => { + expectType<{ bar: string; baz: string }>(context.params); }); }); + }); - describe("#onUpdate()", () => { - it("correctly sets trigger to {}", () => { - const cf = functions.handler.database.ref.onUpdate(() => null); - expect(cf.__trigger).to.deep.equal({}); - expect(cf.__endpoint).to.be.undefined; - }); + describe("#onDelete()", () => { + it("should return a trigger/endpoint with appropriate values", () => { + const func = database.ref("foo").onDelete(() => null); - it("should be able to use the instance entry point", () => { - const func = functions.handler.database.instance.ref.onUpdate(() => null); - expect(func.__trigger).to.deep.equal({}); - expect(func.__endpoint).to.be.undefined; - }); + expect(func.__trigger).to.deep.equal( + expectedTrigger("projects/_/instances/subdomain/refs/foo", "ref.delete") + ); - it("should return a handler that emits events with a proper DataSnapshot", () => { - const event = { - data: { - data: null, - delta: { foo: "bar" }, - }, - context: { - eventId: "70172329041928", - eventType: "providers/google.firebase.database/eventTypes/ref.update", - timestamp: "2018-04-09T07:56:12.975Z", - resource: "projects/_/instances/subdomains/refs/users", - }, - }; - const handler = functions.handler.database.ref.onUpdate((change, context) => { - return expect(change.after.val()).to.deep.equal({ foo: "bar" }); - }); - - return handler(event.data, event.context); - }); + expect(func.__endpoint).to.deep.equal( + expectedEndpoint("projects/_/instances/subdomain/refs/foo", "ref.delete") + ); }); - describe("#onDelete()", () => { - it("correctly sets trigger to {}", () => { - const cf = functions.handler.database.ref.onDelete(() => null); - expect(cf.__trigger).to.deep.equal({}); - expect(cf.__endpoint).to.be.undefined; - }); + it("should let developers choose a database instance", () => { + const func = database + .instance("custom") + .ref("foo") + .onDelete(() => null); - it("should be able to use the instance entry point", () => { - const func = functions.handler.database.instance.ref.onDelete(() => null); - expect(func.__trigger).to.deep.equal({}); - expect(func.__endpoint).to.be.undefined; - }); + expect(func.__trigger).to.deep.equal( + expectedTrigger("projects/_/instances/custom/refs/foo", "ref.delete") + ); - it("should return a handler that emits events with a proper DataSnapshot", () => { - const event = { - data: { - data: { foo: "bar" }, - delta: null, - }, - context: { - eventId: "70172329041928", - eventType: "providers/google.firebase.database/eventTypes/ref.delete", - timestamp: "2018-04-09T07:56:12.975Z", - resource: "projects/_/instances/subdomains/refs/users", - }, - }; - - const handler = database.ref("/users/{id}").onDelete((data) => { - expect(data.val()).to.deep.equal({ foo: "bar" }); - }); - - return handler(event.data, event.context); - }); + expect(func.__endpoint).to.deep.equal( + expectedEndpoint("projects/_/instances/custom/refs/foo", "ref.delete") + ); }); - }); - describe("process.env.FIREBASE_CONFIG not set", () => { - it("should not throw if __trigger is not accessed", () => { - expect(() => database.ref("/path").onWrite(() => null)).to.not.throw(Error); - }); - }); + it("should return a handler that emits events with a proper DataSnapshot", () => { + const event = { + data: { + data: {foo: "bar"}, + delta: null, + }, + context: { + eventId: "70172329041928", + eventType: "providers/google.firebase.database/eventTypes/ref.delete", + timestamp: "2018-04-09T07:56:12.975Z", + resource: "projects/_/instances/subdomains/refs/users", + }, + }; - it("should throw when trigger is accessed", () => { - expect(() => database.ref("/path").onWrite(() => null).__trigger).to.throw(Error); - }); + const handler = database.ref("/users/{id}").onDelete((data) => { + expect(data.val()).to.deep.equal({foo: "bar"}); + }); - it("should throw when endpoint is accessed", () => { - expect(() => database.ref("/path").onWrite(() => null).__endpoint).to.throw(Error); + return handler(event.data, event.context); + }); }); + }); - it("should not throw when #run is called", () => { - const cf = database.ref("/path").onWrite(() => null); - expect(cf.run).to.not.throw(Error); + describe("process.env.FIREBASE_CONFIG not set", () => { + it("should not throw if __trigger is not accessed", () => { + expect(() => database.ref("/path").onWrite(() => null)).to.not.throw(Error); }); }); - describe("extractInstanceAndPath", () => { - it("should return correct us-central prod instance and path strings if domain is missing", () => { - const [instance, path] = database.extractInstanceAndPath( - "projects/_/instances/foo/refs/bar", - undefined - ); - expect(instance).to.equal("https://foo.firebaseio.com"); - expect(path).to.equal("/bar"); - }); + it("should throw when trigger is accessed", () => { + expect(() => database.ref("/path").onWrite(() => null).__trigger).to.throw(Error); + }); - it("should return the correct staging instance and path strings if domain is present", () => { - const [instance, path] = database.extractInstanceAndPath( - "projects/_/instances/foo/refs/bar", - "firebaseio-staging.com" - ); - expect(instance).to.equal("https://foo.firebaseio-staging.com"); - expect(path).to.equal("/bar"); - }); + it("should throw when endpoint is accessed", () => { + expect(() => database.ref("/path").onWrite(() => null).__endpoint).to.throw(Error); + }); - it("should return the correct instance and path strings if root path is /refs", () => { - const [instance, path] = database.extractInstanceAndPath( - "projects/_/instances/foo/refs/refs" - ); - expect(instance).to.equal("https://foo.firebaseio.com"); - expect(path).to.equal("/refs"); - }); + it("should not throw when #run is called", () => { + const cf = database.ref("/path").onWrite(() => null); + expect(cf.run).to.not.throw(Error); + }); +}); - it("should return the correct instance and path strings if a child path contain /refs", () => { - const [instance, path] = database.extractInstanceAndPath( - "projects/_/instances/foo/refs/root/refs" - ); - expect(instance).to.equal("https://foo.firebaseio.com"); - expect(path).to.equal("/root/refs"); - }); +describe("extractInstanceAndPath", () => { + it("should return correct us-central prod instance and path strings if domain is missing", () => { + const [instance, path] = database.extractInstanceAndPath( + "projects/_/instances/foo/refs/bar", + undefined + ); + expect(instance).to.equal("https://foo.firebaseio.com"); + expect(path).to.equal("/bar"); + }); - it("should return the correct multi-region instance and path strings if domain is present", () => { - const [instance, path] = database.extractInstanceAndPath( - "projects/_/instances/foo/refs/bar", - "euw1.firebasedatabase.app" - ); - expect(instance).to.equal("https://foo.euw1.firebasedatabase.app"); - expect(path).to.equal("/bar"); - }); + it("should return the correct staging instance and path strings if domain is present", () => { + const [instance, path] = database.extractInstanceAndPath( + "projects/_/instances/foo/refs/bar", + "firebaseio-staging.com" + ); + expect(instance).to.equal("https://foo.firebaseio-staging.com"); + expect(path).to.equal("/bar"); + }); - it("should throw an error if the given instance name contains anything except alphanumerics and dashes", () => { - expect(() => { - return database.extractInstanceAndPath( - "projects/_/instances/a.bad.name/refs/bar", - undefined - ); - }).to.throw(Error); - expect(() => { - return database.extractInstanceAndPath( - "projects/_/instances/a_different_bad_name/refs/bar", - undefined - ); - }).to.throw(Error); - expect(() => { - return database.extractInstanceAndPath("projects/_/instances/BAD!!!!/refs/bar", undefined); - }).to.throw(Error); - }); + it("should return the correct instance and path strings if root path is /refs", () => { + const [instance, path] = database.extractInstanceAndPath( + "projects/_/instances/foo/refs/refs" + ); + expect(instance).to.equal("https://foo.firebaseio.com"); + expect(path).to.equal("/refs"); + }); - it("should use the emulator host when present", () => { - process.env.FIREBASE_DATABASE_EMULATOR_HOST = "localhost:1234"; - const [instance, path] = database.extractInstanceAndPath( - "projects/_/instances/foo/refs/bar", - "firebaseio-staging.com" - ); - expect(instance).to.equal("http://localhost:1234/?ns=foo"); - expect(path).to.equal("/bar"); - delete process.env.FIREBASE_DATABASE_EMULATOR_HOST; - }); + it("should return the correct instance and path strings if a child path contain /refs", () => { + const [instance, path] = database.extractInstanceAndPath( + "projects/_/instances/foo/refs/root/refs" + ); + expect(instance).to.equal("https://foo.firebaseio.com"); + expect(path).to.equal("/root/refs"); }); - describe("DataSnapshot", () => { - let subject: any; - const apps = new appsNamespace.Apps(); + it("should return the correct multi-region instance and path strings if domain is present", () => { + const [instance, path] = database.extractInstanceAndPath( + "projects/_/instances/foo/refs/bar", + "euw1.firebasedatabase.app" + ); + expect(instance).to.equal("https://foo.euw1.firebasedatabase.app"); + expect(path).to.equal("/bar"); + }); - const populate = (data: any) => { - const [instance, path] = database.extractInstanceAndPath( - "projects/_/instances/other-subdomain/refs/foo", - "firebaseio-staging.com" + it("should throw an error if the given instance name contains anything except alphanumerics and dashes", () => { + expect(() => { + return database.extractInstanceAndPath( + "projects/_/instances/a.bad.name/refs/bar", + undefined ); - subject = new database.DataSnapshot(data, path, getApp(), instance); - }; + }).to.throw(Error); + expect(() => { + return database.extractInstanceAndPath( + "projects/_/instances/a_different_bad_name/refs/bar", + undefined + ); + }).to.throw(Error); + expect(() => { + return database.extractInstanceAndPath("projects/_/instances/BAD!!!!/refs/bar", undefined); + }).to.throw(Error); + }); - describe("#ref: firebase.database.Reference", () => { - it("should return a ref for correct instance, not the default instance", () => { - populate({}); - expect(subject.ref.toJSON()).to.equal("https://other-subdomain.firebaseio-staging.com/foo"); - }); + it("should use the emulator host when present", () => { + process.env.FIREBASE_DATABASE_EMULATOR_HOST = "localhost:1234"; + const [instance, path] = database.extractInstanceAndPath( + "projects/_/instances/foo/refs/bar", + "firebaseio-staging.com" + ); + expect(instance).to.equal("http://localhost:1234/?ns=foo"); + expect(path).to.equal("/bar"); + delete process.env.FIREBASE_DATABASE_EMULATOR_HOST; + }); +}); + +describe("DataSnapshot", () => { + let subject: any; + + const populate = (data: any) => { + const [instance, path] = database.extractInstanceAndPath( + "projects/_/instances/other-subdomain/refs/foo", + "firebaseio-staging.com" + ); + subject = new database.DataSnapshot(data, path, getApp(), instance); + }; + + describe("#ref: firebase.database.Reference", () => { + it("should return a ref for correct instance, not the default instance", () => { + populate({}); + expect(subject.ref.toJSON()).to.equal("https://other-subdomain.firebaseio-staging.com/foo"); }); + }); - describe("#val(): any", () => { - it("should return child values based on the child path", () => { - populate(applyChange({ a: { b: "c" } }, { a: { d: "e" } })); - expect(subject.child("a").val()).to.deep.equal({ b: "c", d: "e" }); - }); + describe("#val(): any", () => { + it("should return child values based on the child path", () => { + populate(applyChange({a: {b: "c"}}, {a: {d: "e"}})); + expect(subject.child("a").val()).to.deep.equal({b: "c", d: "e"}); + }); - it("should return null for children past a leaf", () => { - populate(applyChange({ a: 23 }, { b: 33 })); - expect(subject.child("a/b").val()).to.be.null; - expect(subject.child("b/c").val()).to.be.null; - }); + it("should return null for children past a leaf", () => { + populate(applyChange({a: 23}, {b: 33})); + expect(subject.child("a/b").val()).to.be.null; + expect(subject.child("b/c").val()).to.be.null; + }); - it("should return a leaf value", () => { - populate(23); - expect(subject.val()).to.eq(23); - populate({ b: 23, a: null }); - expect(subject.child("b").val()).to.eq(23); - }); + it("should return a leaf value", () => { + populate(23); + expect(subject.val()).to.eq(23); + populate({b: 23, a: null}); + expect(subject.child("b").val()).to.eq(23); + }); - it("should coerce object into array if all keys are integers", () => { - populate({ 0: "a", 1: "b", 2: { c: "d" } }); - expect(subject.val()).to.deep.equal(["a", "b", { c: "d" }]); - populate({ 0: "a", 2: "b", 3: { c: "d" } }); - expect(subject.val()).to.deep.equal(["a", undefined, "b", { c: "d" }]); - populate({ foo: { 0: "a", 1: "b" } }); - expect(subject.val()).to.deep.equal({ foo: ["a", "b"] }); - }); + it("should coerce object into array if all keys are integers", () => { + populate({0: "a", 1: "b", 2: {c: "d"}}); + expect(subject.val()).to.deep.equal(["a", "b", {c: "d"}]); + populate({0: "a", 2: "b", 3: {c: "d"}}); + expect(subject.val()).to.deep.equal(["a", undefined, "b", {c: "d"}]); + populate({foo: {0: "a", 1: "b"}}); + expect(subject.val()).to.deep.equal({foo: ["a", "b"]}); + }); - // Regression test: zero-values (including children) were accidentally forwarded as 'null'. - it("should deal with zero-values appropriately", () => { - populate(0); - expect(subject.val()).to.equal(0); - populate({ myKey: 0 }); - expect(subject.val()).to.deep.equal({ myKey: 0 }); - }); + // Regression test: zero-values (including children) were accidentally forwarded as 'null'. + it("should deal with zero-values appropriately", () => { + populate(0); + expect(subject.val()).to.equal(0); + populate({myKey: 0}); + expect(subject.val()).to.deep.equal({myKey: 0}); + }); - // Regression test: .val() was returning array of nulls when there's a property called length (BUG#37683995) - it('should return correct values when data has "length" property', () => { - populate({ length: 3, foo: "bar" }); - expect(subject.val()).to.deep.equal({ length: 3, foo: "bar" }); - }); + // Regression test: .val() was returning array of nulls when there's a property called length (BUG#37683995) + it('should return correct values when data has "length" property', () => { + populate({length: 3, foo: "bar"}); + expect(subject.val()).to.deep.equal({length: 3, foo: "bar"}); + }); - it("should deal with null-values appropriately", () => { - populate(null); - expect(subject.val()).to.be.null; + it("should deal with null-values appropriately", () => { + populate(null); + expect(subject.val()).to.be.null; - populate({ myKey: null }); - expect(subject.val()).to.be.null; - }); + populate({myKey: null}); + expect(subject.val()).to.be.null; + }); - it("should deal with empty object values appropriately", () => { - populate({}); - expect(subject.val()).to.be.null; + it("should deal with empty object values appropriately", () => { + populate({}); + expect(subject.val()).to.be.null; - populate({ myKey: {} }); - expect(subject.val()).to.be.null; + populate({myKey: {}}); + expect(subject.val()).to.be.null; - populate({ myKey: { child: null } }); - expect(subject.val()).to.be.null; - }); + populate({myKey: {child: null}}); + expect(subject.val()).to.be.null; + }); - it("should deal with empty array values appropriately", () => { - populate([]); - expect(subject.val()).to.be.null; + it("should deal with empty array values appropriately", () => { + populate([]); + expect(subject.val()).to.be.null; - populate({ myKey: [] }); - expect(subject.val()).to.be.null; + populate({myKey: []}); + expect(subject.val()).to.be.null; - populate({ myKey: [null] }); - expect(subject.val()).to.be.null; + populate({myKey: [null]}); + expect(subject.val()).to.be.null; - populate({ myKey: [{}] }); - expect(subject.val()).to.be.null; + populate({myKey: [{}]}); + expect(subject.val()).to.be.null; - populate({ myKey: [{ myKey: null }] }); - expect(subject.val()).to.be.null; + populate({myKey: [{myKey: null}]}); + expect(subject.val()).to.be.null; - populate({ myKey: [{ myKey: {} }] }); - expect(subject.val()).to.be.null; - }); + populate({myKey: [{myKey: {}}]}); + expect(subject.val()).to.be.null; }); + }); - describe("#child(): DataSnapshot", () => { - it("should work with multiple calls", () => { - populate({ a: { b: { c: "d" } } }); - expect(subject.child("a").child("b/c").val()).to.equal("d"); - }); + describe("#child(): DataSnapshot", () => { + it("should work with multiple calls", () => { + populate({a: {b: {c: "d"}}}); + expect(subject.child("a").child("b/c").val()).to.equal("d"); }); + }); - describe("#exists(): boolean", () => { - it("should be true for an object value", () => { - populate({ a: { b: "c" } }); - expect(subject.child("a").exists()).to.be.true; - }); + describe("#exists(): boolean", () => { + it("should be true for an object value", () => { + populate({a: {b: "c"}}); + expect(subject.child("a").exists()).to.be.true; + }); - it("should be true for a leaf value", () => { - populate({ a: { b: "c" } }); - expect(subject.child("a/b").exists()).to.be.true; - }); + it("should be true for a leaf value", () => { + populate({a: {b: "c"}}); + expect(subject.child("a/b").exists()).to.be.true; + }); - it("should be false for a non-existent value", () => { - populate({ a: { b: "c", nullChild: null } }); - expect(subject.child("d").exists()).to.be.false; - expect(subject.child("nullChild").exists()).to.be.false; - }); + it("should be false for a non-existent value", () => { + populate({a: {b: "c", nullChild: null}}); + expect(subject.child("d").exists()).to.be.false; + expect(subject.child("nullChild").exists()).to.be.false; + }); - it("should be false for a value pathed beyond a leaf", () => { - populate({ a: { b: "c" } }); - expect(subject.child("a/b/c").exists()).to.be.false; - }); + it("should be false for a value pathed beyond a leaf", () => { + populate({a: {b: "c"}}); + expect(subject.child("a/b/c").exists()).to.be.false; + }); - it("should be false for an empty object value", () => { - populate({ a: {} }); - expect(subject.child("a").exists()).to.be.false; + it("should be false for an empty object value", () => { + populate({a: {}}); + expect(subject.child("a").exists()).to.be.false; - populate({ a: { child: null } }); - expect(subject.child("a").exists()).to.be.false; + populate({a: {child: null}}); + expect(subject.child("a").exists()).to.be.false; - populate({ a: { child: {} } }); - expect(subject.child("a").exists()).to.be.false; - }); + populate({a: {child: {}}}); + expect(subject.child("a").exists()).to.be.false; + }); - it("should be false for an empty array value", () => { - populate({ a: [] }); - expect(subject.child("a").exists()).to.be.false; + it("should be false for an empty array value", () => { + populate({a: []}); + expect(subject.child("a").exists()).to.be.false; - populate({ a: [null] }); - expect(subject.child("a").exists()).to.be.false; + populate({a: [null]}); + expect(subject.child("a").exists()).to.be.false; - populate({ a: [{}] }); - expect(subject.child("a").exists()).to.be.false; - }); + populate({a: [{}]}); + expect(subject.child("a").exists()).to.be.false; }); + }); - describe("#forEach(action: (a: DataSnapshot) => boolean): boolean", () => { - it("should iterate through child snapshots", () => { - populate({ a: "b", c: "d" }); - let out = ""; - subject.forEach((snap: any) => { - out += snap.val(); - }); - expect(out).to.equal("bd"); + describe("#forEach(action: (a: DataSnapshot) => boolean): boolean", () => { + it("should iterate through child snapshots", () => { + populate({a: "b", c: "d"}); + let out = ""; + subject.forEach((snap: any) => { + out += snap.val(); }); + expect(out).to.equal("bd"); + }); - it("should have correct key values for child snapshots", () => { - populate({ a: "b", c: "d" }); - let out = ""; - subject.forEach((snap: any) => { - out += snap.key; - }); - expect(out).to.equal("ac"); + it("should have correct key values for child snapshots", () => { + populate({a: "b", c: "d"}); + let out = ""; + subject.forEach((snap: any) => { + out += snap.key; }); + expect(out).to.equal("ac"); + }); - it("should not execute for leaf or null nodes", () => { - populate(23); - let count = 0; - const counter = () => count++; - - expect(subject.forEach(counter)).to.equal(false); - expect(count).to.eq(0); + it("should not execute for leaf or null nodes", () => { + populate(23); + let count = 0; + const counter = () => count++; - populate({ - a: "foo", - nullChild: null, - emptyObjectChild: {}, - emptyArrayChild: [], - }); - count = 0; + expect(subject.forEach(counter)).to.equal(false); + expect(count).to.eq(0); - expect(subject.forEach(counter)).to.equal(false); - expect(count).to.eq(1); + populate({ + a: "foo", + nullChild: null, + emptyObjectChild: {}, + emptyArrayChild: [], }); + count = 0; - it("should cancel further enumeration if callback returns true", () => { - populate({ a: "b", c: "d", e: "f", g: "h" }); - let out = ""; - const ret = subject.forEach((snap: any) => { - if (snap.val() === "f") { - return true; - } - out += snap.val(); - }); - expect(out).to.equal("bd"); - expect(ret).to.equal(true); - }); + expect(subject.forEach(counter)).to.equal(false); + expect(count).to.eq(1); + }); - it("should not cancel further enumeration if callback returns a truthy value", () => { - populate({ a: "b", c: "d", e: "f", g: "h" }); - let out = ""; - const ret = subject.forEach((snap: any) => { - out += snap.val(); - return 1; - }); - expect(out).to.equal("bdfh"); - expect(ret).to.equal(false); + it("should cancel further enumeration if callback returns true", () => { + populate({a: "b", c: "d", e: "f", g: "h"}); + let out = ""; + const ret = subject.forEach((snap: any) => { + if (snap.val() === "f") { + return true; + } + out += snap.val(); + }); + expect(out).to.equal("bd"); + expect(ret).to.equal(true); + }); + + it("should not cancel further enumeration if callback returns a truthy value", () => { + populate({a: "b", c: "d", e: "f", g: "h"}); + let out = ""; + const ret = subject.forEach((snap: any) => { + out += snap.val(); + return 1; }); + expect(out).to.equal("bdfh"); + expect(ret).to.equal(false); + }); - it("should not cancel further enumeration if callback does not return", () => { - populate({ a: "b", c: "d", e: "f", g: "h" }); - let out = ""; - const ret = subject.forEach((snap: any) => { - out += snap.val(); - }); - expect(out).to.equal("bdfh"); - expect(ret).to.equal(false); + it("should not cancel further enumeration if callback does not return", () => { + populate({a: "b", c: "d", e: "f", g: "h"}); + let out = ""; + const ret = subject.forEach((snap: any) => { + out += snap.val(); }); + expect(out).to.equal("bdfh"); + expect(ret).to.equal(false); }); + }); - describe("#numChildren()", () => { - it("should be key count for objects", () => { - populate({ - a: "b", - c: "d", - nullChild: null, - emptyObjectChild: {}, - emptyArrayChild: [], - }); - expect(subject.numChildren()).to.eq(2); + describe("#numChildren()", () => { + it("should be key count for objects", () => { + populate({ + a: "b", + c: "d", + nullChild: null, + emptyObjectChild: {}, + emptyArrayChild: [], }); + expect(subject.numChildren()).to.eq(2); + }); - it("should be 0 for non-objects", () => { - populate(23); - expect(subject.numChildren()).to.eq(0); + it("should be 0 for non-objects", () => { + populate(23); + expect(subject.numChildren()).to.eq(0); - populate({ - nullChild: null, - emptyObjectChild: {}, - emptyArrayChild: [], - }); - expect(subject.numChildren()).to.eq(0); + populate({ + nullChild: null, + emptyObjectChild: {}, + emptyArrayChild: [], }); + expect(subject.numChildren()).to.eq(0); }); + }); - describe("#hasChildren()", () => { - it("should true for objects", () => { - populate({ - a: "b", - c: "d", - nullChild: null, - emptyObjectChild: {}, - emptyArrayChild: [], - }); - expect(subject.hasChildren()).to.be.true; + describe("#hasChildren()", () => { + it("should true for objects", () => { + populate({ + a: "b", + c: "d", + nullChild: null, + emptyObjectChild: {}, + emptyArrayChild: [], }); + expect(subject.hasChildren()).to.be.true; + }); - it("should be false for non-objects", () => { - populate(23); - expect(subject.hasChildren()).to.be.false; + it("should be false for non-objects", () => { + populate(23); + expect(subject.hasChildren()).to.be.false; - populate({ - nullChild: null, - emptyObjectChild: {}, - emptyArrayChild: [], - }); - expect(subject.hasChildren()).to.be.false; + populate({ + nullChild: null, + emptyObjectChild: {}, + emptyArrayChild: [], }); + expect(subject.hasChildren()).to.be.false; }); + }); - describe("#hasChild(childPath): boolean", () => { - it("should return true for a child or deep child", () => { - populate({ a: { b: "c" }, d: 23 }); - expect(subject.hasChild("a/b")).to.be.true; - expect(subject.hasChild("d")).to.be.true; - }); + describe("#hasChild(childPath): boolean", () => { + it("should return true for a child or deep child", () => { + populate({a: {b: "c"}, d: 23}); + expect(subject.hasChild("a/b")).to.be.true; + expect(subject.hasChild("d")).to.be.true; + }); - it("should return false if a child is missing", () => { - populate({ - a: "b", - nullChild: null, - emptyObjectChild: {}, - emptyArrayChild: [], - }); - expect(subject.hasChild("c")).to.be.false; - expect(subject.hasChild("a/b")).to.be.false; - expect(subject.hasChild("nullChild")).to.be.false; - expect(subject.hasChild("emptyObjectChild")).to.be.false; - expect(subject.hasChild("emptyArrayChild")).to.be.false; - expect(subject.hasChild("c")).to.be.false; - expect(subject.hasChild("a/b")).to.be.false; - }); + it("should return false if a child is missing", () => { + populate({ + a: "b", + nullChild: null, + emptyObjectChild: {}, + emptyArrayChild: [], + }); + expect(subject.hasChild("c")).to.be.false; + expect(subject.hasChild("a/b")).to.be.false; + expect(subject.hasChild("nullChild")).to.be.false; + expect(subject.hasChild("emptyObjectChild")).to.be.false; + expect(subject.hasChild("emptyArrayChild")).to.be.false; + expect(subject.hasChild("c")).to.be.false; + expect(subject.hasChild("a/b")).to.be.false; }); + }); - describe("#key: string", () => { - it("should return the key name", () => { - expect(subject.key).to.equal("foo"); - }); + describe("#key: string", () => { + it("should return the key name", () => { + expect(subject.key).to.equal("foo"); + }); - it("should return null for the root", () => { - const [instance, path] = database.extractInstanceAndPath( - "projects/_/instances/foo/refs/", - undefined - ); - const snapshot = new database.DataSnapshot(null, path, getApp(), instance); - expect(snapshot.key).to.be.null; - }); + it("should return null for the root", () => { + const [instance, path] = database.extractInstanceAndPath( + "projects/_/instances/foo/refs/", + undefined + ); + const snapshot = new database.DataSnapshot(null, path, getApp(), instance); + expect(snapshot.key).to.be.null; + }); - it("should work for child paths", () => { - expect(subject.child("foo/bar").key).to.equal("bar"); - }); + it("should work for child paths", () => { + expect(subject.child("foo/bar").key).to.equal("bar"); }); + }); - describe("#toJSON(): Object", () => { - it("should return the current value", () => { - populate({ - a: "b", - nullChild: null, - emptyObjectChild: {}, - emptyArrayChild: [], - }); - expect(subject.toJSON()).to.deep.equal(subject.val()); + describe("#toJSON(): Object", () => { + it("should return the current value", () => { + populate({ + a: "b", + nullChild: null, + emptyObjectChild: {}, + emptyArrayChild: [], }); + expect(subject.toJSON()).to.deep.equal(subject.val()); + }); - it("should be stringifyable", () => { - populate({ - a: "b", - nullChild: null, - emptyObjectChild: {}, - emptyArrayChild: [], - }); - expect(JSON.stringify(subject)).to.deep.equal('{"a":"b"}'); + it("should be stringifyable", () => { + populate({ + a: "b", + nullChild: null, + emptyObjectChild: {}, + emptyArrayChild: [], }); + expect(JSON.stringify(subject)).to.deep.equal('{"a":"b"}'); }); }); }); diff --git a/spec/v1/providers/firestore.spec.ts b/spec/v1/providers/firestore.spec.ts index 5ee3b8cae..c9fbf0334 100644 --- a/spec/v1/providers/firestore.spec.ts +++ b/spec/v1/providers/firestore.spec.ts @@ -136,10 +136,12 @@ describe("Firestore Functions", () => { it("should allow custom namespaces", () => { const resource = "projects/project1/databases/(default)/documents@v2/users/{uid}"; - const cloudFunction = firestore.document("users/{uid}").onWrite(() => null); + const cloudFunction = firestore + .namespace('v2') + .document("users/{uid}") + .onWrite(() => null); expect(cloudFunction.__trigger).to.deep.equal(expectedTrigger(resource, "document.write")); - expect(cloudFunction.__endpoint).to.deep.equal(expectedEndpoint(resource, "document.write")); }); @@ -271,76 +273,12 @@ describe("Firestore Functions", () => { }).timeout(5000); it('constructs appropriate fields and getters for event.data on "document.delete" events', () => { - const testFunction = firestore.document("path").onDelete((data, context) => { - 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(), {}, "document.delete"); - return testFunction(event.data, event.context); - }).timeout(5000); - }); - - describe("handler namespace", () => { - before(() => { - process.env.GCLOUD_PROJECT = "project1"; - }); - - after(() => { - delete process.env.GCLOUD_PROJECT; - }); - - it('constructs correct data type and sets trigger to {} on "document.write" events', () => { - const testFunction = functions.handler.firestore.document.onWrite((change, context) => { - expect(change.before.data()).to.deep.equal({ - key1: false, - key2: 111, - }); - expect(change.before.get("key1")).to.equal(false); - expect(change.after.data()).to.deep.equal({ key1: true, key2: 123 }); - expect(change.after.get("key1")).to.equal(true); - return true; // otherwise will get warning about returning undefined - }); - expect(testFunction.__trigger).to.deep.equal({}); - const event = constructEvent(createOldValue(), createValue(), "document.write"); - return testFunction(event.data, event.context); - }).timeout(5000); - - it('constructs correct data type and sets trigger to {} on "document.create" events', () => { - const testFunction = functions.handler.firestore.document.onCreate((data, context) => { - 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 - }); - expect(testFunction.__trigger).to.deep.equal({}); - const event = constructEvent({}, createValue(), "document.create"); - return testFunction(event.data, event.context); - }).timeout(5000); - - it('constructs correct data type and sets trigger to {} on "document.update" events', () => { - const testFunction = functions.handler.firestore.document.onUpdate((change) => { - expect(change.before.data()).to.deep.equal({ - key1: false, - key2: 111, - }); - expect(change.before.get("key1")).to.equal(false); - expect(change.after.data()).to.deep.equal({ key1: true, key2: 123 }); - expect(change.after.get("key1")).to.equal(true); - return true; // otherwise will get warning about returning undefined - }); - expect(testFunction.__trigger).to.deep.equal({}); - const event = constructEvent(createOldValue(), createValue(), "document.update"); - return testFunction(event.data, event.context); - }).timeout(5000); - - it('constructs correct data type and sets trigger to {} on "document.delete" events', () => { - const testFunction = functions.handler.firestore.document.onDelete((data, context) => { + 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(), {}, "document.delete"); - expect(testFunction.__trigger).to.deep.equal({}); + const event = constructEvent(createOldValue(), {}); 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 9ca03bf31..f829aa6d3 100644 --- a/spec/v1/providers/https.spec.ts +++ b/spec/v1/providers/https.spec.ts @@ -65,29 +65,9 @@ describe("CloudHttpsBuilder", () => { }); }); -describe("handler namespace", () => { - describe("#onRequest", () => { - it("should return an empty trigger", () => { - const result = functions.handler.https.onRequest((req, res) => { - res.send(200); - }); - expect(result.__trigger).to.deep.equal({}); - expect(result.__endpoint).to.be.undefined; - }); - }); - - describe("#onCall", () => { - it("should return an empty trigger", () => { - const result = functions.handler.https.onCall(() => null); - expect(result.__trigger).to.deep.equal({}); - expect(result.__endpoint).to.be.undefined; - }); - }); -}); - describe("#onCall", () => { it("should return a trigger/endpoint with appropriate values", () => { - const result = https.onCall((data) => { + const result = https.onCall(() => { return "response"; }); diff --git a/spec/v1/providers/pubsub.spec.ts b/spec/v1/providers/pubsub.spec.ts index 4257cdb53..77b6fe24a 100644 --- a/spec/v1/providers/pubsub.spec.ts +++ b/spec/v1/providers/pubsub.spec.ts @@ -161,7 +161,7 @@ describe("Pubsub Functions", () => { describe("#schedule", () => { it("should return a trigger/endpoint with schedule", () => { - const result = pubsub.schedule("every 5 minutes").onRun((context) => null); + const result = pubsub.schedule("every 5 minutes").onRun(() => null); expect(result.__trigger.schedule).to.deep.equal({ schedule: "every 5 minutes", @@ -177,7 +177,7 @@ describe("Pubsub Functions", () => { const result = pubsub .schedule("every 5 minutes") .timeZone("America/New_York") - .onRun((context) => null); + .onRun(() => null); expect(result.__trigger.schedule).to.deep.equal({ schedule: "every 5 minutes", @@ -391,81 +391,6 @@ describe("Pubsub Functions", () => { }); }); - describe("handler namespace", () => { - describe("#onPublish", () => { - describe("#topic", () => { - it("should return an empty trigger", () => { - const result = functions.handler.pubsub.topic.onPublish(() => null); - expect(result.__trigger).to.deep.equal({}); - expect(result.__endpoint).to.be.undefined; - }); - - it("should properly handle a new-style event", () => { - const raw = new Buffer('{"hello":"world"}', "utf8").toString("base64"); - const event = { - data: { - data: raw, - attributes: { - foo: "bar", - }, - }, - context: { - eventId: "70172329041928", - timestamp: "2018-04-09T07:56:12.975Z", - eventType: "google.pubsub.topic.publish", - resource: { - service: "pubsub.googleapis.com", - name: "projects/project1/topics/toppy", - }, - }, - }; - - const result = functions.handler.pubsub.topic.onPublish((data) => { - return { - raw: data.data, - json: data.json, - attributes: data.attributes, - }; - }); - - return expect(result(event.data, event.context)).to.eventually.deep.equal({ - raw, - json: { hello: "world" }, - attributes: { foo: "bar" }, - }); - }); - }); - describe("#schedule", () => { - it("should return an empty trigger", () => { - const result = functions.handler.pubsub.schedule.onRun(() => null); - expect(result.__trigger).to.deep.equal({}); - }); - it("should return a handler with a proper event context", () => { - const raw = new Buffer('{"hello":"world"}', "utf8").toString("base64"); - const event = { - data: { - data: raw, - attributes: { - foo: "bar", - }, - }, - context: { - eventId: "70172329041928", - timestamp: "2018-04-09T07:56:12.975Z", - eventType: "google.pubsub.topic.publish", - resource: { - service: "pubsub.googleapis.com", - name: "projects/project1/topics/toppy", - }, - }, - }; - const result = functions.handler.pubsub.schedule.onRun((context) => context.eventId); - return expect(result(event.data, event.context)).to.eventually.equal("70172329041928"); - }); - }); - }); - }); - describe("process.env.GCLOUD_PROJECT not set", () => { it("should not throw if __trigger is not accessed", () => { expect(() => pubsub.topic("toppy").onPublish(() => null)).to.not.throw(Error); diff --git a/spec/v1/providers/remoteConfig.spec.ts b/spec/v1/providers/remoteConfig.spec.ts index 45be85b8c..f5fb427e6 100644 --- a/spec/v1/providers/remoteConfig.spec.ts +++ b/spec/v1/providers/remoteConfig.spec.ts @@ -124,45 +124,10 @@ describe("RemoteConfig Functions", () => { it("should unwrap the version in the event", () => { return Promise.all([ - cloudFunctionUpdate(event.data, event.context).then((data: any, context: any) => { + cloudFunctionUpdate(event.data, event.context).then((data: any) => { expect(data).to.deep.equal(constructVersion()); }), ]); }); }); - - describe("handler namespace", () => { - describe("#onUpdate", () => { - it("should have an empty trigger", () => { - const cloudFunction = functions.handler.remoteConfig.onUpdate(() => null); - - expect(cloudFunction.__trigger).to.deep.equal({}); - expect(cloudFunction.__endpoint).to.be.undefined; - }); - - it("should correctly unwrap the event", () => { - const cloudFunctionUpdate = functions.handler.remoteConfig.onUpdate( - (version: remoteConfig.TemplateVersion, context: EventContext) => version - ); - const event: Event = { - data: constructVersion(), - context: { - eventId: "70172329041928", - timestamp: "2018-04-09T07:56:12.975Z", - eventType: "google.firebase.remoteconfig.update", - resource: { - service: "firebaseremoteconfig.googleapis.com", - name: "projects/project1", - }, - }, - }; - - return Promise.all([ - cloudFunctionUpdate(event.data, event.context).then((data: any, context: any) => { - expect(data).to.deep.equal(constructVersion()); - }), - ]); - }); - }); - }); }); diff --git a/spec/v1/providers/storage.spec.ts b/spec/v1/providers/storage.spec.ts index 7aac9bb58..77f8610fc 100644 --- a/spec/v1/providers/storage.spec.ts +++ b/spec/v1/providers/storage.spec.ts @@ -360,163 +360,7 @@ describe("Storage Functions", () => { }, }; return cloudFunction(goodMediaLinkEvent.data, goodMediaLinkEvent.context).then( - (result: any, context: EventContext) => { - expect(result).equals(goodMediaLinkEvent.data.mediaLink); - } - ); - }); - }); - }); - - describe("namespace handler", () => { - before(() => { - process.env.FIREBASE_CONFIG = JSON.stringify({ - storageBucket: "bucket", - }); - }); - - after(() => { - delete process.env.FIREBASE_CONFIG; - }); - - describe("#onArchive", () => { - it("should return an empty trigger", () => { - const cloudFunction = functions.handler.storage.bucket.onArchive(() => null); - - expect(cloudFunction.__trigger).to.deep.equal({}); - expect(cloudFunction.__endpoint).to.be.undefined; - }); - - it("should not mess with media links using non-literal slashes", () => { - const cloudFunction = functions.handler.storage.object.onArchive((data) => { - return data.mediaLink; - }); - const goodMediaLinkEvent = { - data: { - mediaLink: - "https://www.googleapis.com/storage/v1/b/mybucket.appspot.com" + - "/o/nestedfolder%2Fanotherfolder%2Fmyobject.file?generation=12345&alt=media", - }, - context: { - eventId: "70172329041928", - timestamp: "2018-04-09T07:56:12.975Z", - eventType: "google.storage.object.archive", - resource: { - service: "storage.googleapis.com", - name: "projects/_/buckets/bucky", - }, - }, - }; - return cloudFunction(goodMediaLinkEvent.data, goodMediaLinkEvent.context).then( - (result: any, context: EventContext) => { - expect(result).equals(goodMediaLinkEvent.data.mediaLink); - } - ); - }); - }); - - describe("#onDelete", () => { - it("should return an empty trigger", () => { - const cloudFunction = functions.handler.storage.bucket.onDelete(() => null); - - expect(cloudFunction.__trigger).to.deep.equal({}); - expect(cloudFunction.__endpoint).to.be.undefined; - }); - - it("should not mess with media links using non-literal slashes", () => { - const cloudFunction = functions.handler.storage.object.onDelete((data) => { - return data.mediaLink; - }); - const goodMediaLinkEvent = { - data: { - mediaLink: - "https://www.googleapis.com/storage/v1/b/mybucket.appspot.com" + - "/o/nestedfolder%2Fanotherfolder%2Fmyobject.file?generation=12345&alt=media", - }, - context: { - eventId: "70172329041928", - timestamp: "2018-04-09T07:56:12.975Z", - eventType: "google.storage.object.delete", - resource: { - service: "storage.googleapis.com", - name: "projects/_/buckets/bucky", - }, - }, - }; - return cloudFunction(goodMediaLinkEvent.data, goodMediaLinkEvent.context).then( - (result: any, context: EventContext) => { - expect(result).equals(goodMediaLinkEvent.data.mediaLink); - } - ); - }); - }); - - describe("#onFinalize", () => { - it("should return an empty trigger", () => { - const cloudFunction = functions.handler.storage.bucket.onFinalize(() => null); - - expect(cloudFunction.__trigger).to.deep.equal({}); - expect(cloudFunction.__endpoint).to.be.undefined; - }); - - it("should not mess with media links using non-literal slashes", () => { - const cloudFunction = functions.handler.storage.object.onFinalize((data) => { - return data.mediaLink; - }); - const goodMediaLinkEvent = { - data: { - mediaLink: - "https://www.googleapis.com/storage/v1/b/mybucket.appspot.com" + - "/o/nestedfolder%2Fanotherfolder%2Fmyobject.file?generation=12345&alt=media", - }, - context: { - eventId: "70172329041928", - timestamp: "2018-04-09T07:56:12.975Z", - eventType: "google.storage.object.finalize", - resource: { - service: "storage.googleapis.com", - name: "projects/_/buckets/bucky", - }, - }, - }; - return cloudFunction(goodMediaLinkEvent.data, goodMediaLinkEvent.context).then( - (result: any, context: EventContext) => { - expect(result).equals(goodMediaLinkEvent.data.mediaLink); - } - ); - }); - }); - - describe("#onMetadataUpdate", () => { - it("should return an empty trigger", () => { - const cloudFunction = functions.handler.storage.bucket.onMetadataUpdate(() => null); - - expect(cloudFunction.__trigger).to.deep.equal({}); - expect(cloudFunction.__endpoint).to.be.undefined; - }); - - it("should not mess with media links using non-literal slashes", () => { - const cloudFunction = functions.handler.storage.object.onMetadataUpdate((data) => { - return data.mediaLink; - }); - const goodMediaLinkEvent = { - data: { - mediaLink: - "https://www.googleapis.com/storage/v1/b/mybucket.appspot.com" + - "/o/nestedfolder%2Fanotherfolder%2Fmyobject.file?generation=12345&alt=media", - }, - context: { - eventId: "70172329041928", - timestamp: "2018-04-09T07:56:12.975Z", - eventType: "google.storage.object.metadataUpdate", - resource: { - service: "storage.googleapis.com", - name: "projects/_/buckets/bucky", - }, - }, - }; - return cloudFunction(goodMediaLinkEvent.data, goodMediaLinkEvent.context).then( - (result: any, context: EventContext) => { + (result: any) => { expect(result).equals(goodMediaLinkEvent.data.mediaLink); } ); diff --git a/spec/v1/providers/tasks.spec.ts b/spec/v1/providers/tasks.spec.ts index d8f514490..dc0b5e0c6 100644 --- a/spec/v1/providers/tasks.spec.ts +++ b/spec/v1/providers/tasks.spec.ts @@ -44,7 +44,7 @@ describe("#onDispatch", () => { minBackoffSeconds: 5, }, invoker: "private", - }).onDispatch(() => {}); + }).onDispatch(() => undefined); expect(result.__trigger).to.deep.equal({ taskQueueTrigger: { @@ -161,11 +161,3 @@ describe("#onDispatch", () => { expect(gotData).to.deep.equal({ foo: "bar" }); }); }); - -describe("handler namespace", () => { - it("should return an empty trigger", () => { - const result = functions.handler.tasks.taskQueue.onDispatch(() => null); - expect(result.__trigger).to.deep.equal({}); - expect(result.__endpoint).to.be.undefined; - }); -}); diff --git a/spec/v2/providers/fixtures.ts b/spec/v2/providers/fixtures.ts index 7c8531aa4..dc4ef1117 100644 --- a/spec/v2/providers/fixtures.ts +++ b/spec/v2/providers/fixtures.ts @@ -1,5 +1,28 @@ +import { ManifestEndpoint } from '../../../src/runtime/manifest'; import { TriggerAnnotation } from "../../../src/v2/core"; import * as options from "../../../src/v2/options"; +import { RESET_VALUE } from "../../../src/common/options"; + +export const MINIMAL_V2_ENDPOINT: ManifestEndpoint = { + availableMemoryMb: RESET_VALUE, + concurrency: RESET_VALUE, + ingressSettings: RESET_VALUE, + maxInstances: RESET_VALUE, + minInstances: RESET_VALUE, + serviceAccountEmail: RESET_VALUE, + timeoutSeconds: RESET_VALUE, + vpc: RESET_VALUE, +}; + +export const MINIMAL_V1_ENDPOINT: ManifestEndpoint = { + availableMemoryMb: RESET_VALUE, + ingressSettings: RESET_VALUE, + maxInstances: RESET_VALUE, + minInstances: RESET_VALUE, + serviceAccountEmail: RESET_VALUE, + timeoutSeconds: RESET_VALUE, + vpc: RESET_VALUE, +}; export const FULL_OPTIONS: options.GlobalOptions = { region: "us-west1", @@ -36,3 +59,24 @@ export const FULL_TRIGGER: TriggerAnnotation = { }, secrets: ["MY_SECRET"], }; + +export const FULL_ENDPOINT: ManifestEndpoint = { + platform: 'gcfv2', + region: ['us-west1'], + availableMemoryMb: 512, + timeoutSeconds: 60, + minInstances: 1, + maxInstances: 3, + concurrency: 20, + vpc: { + connector: 'aConnector', + egressSettings: 'ALL_TRAFFIC', + }, + serviceAccountEmail: 'root@', + ingressSettings: 'ALLOW_ALL', + cpu: 'gcf_gen1', + labels: { + hello: 'world', + }, + secretEnvironmentVariables: [{ key: 'MY_SECRET' }], +}; diff --git a/spec/v2/providers/https.spec.ts b/spec/v2/providers/https.spec.ts index 4432e3015..f41dd2f90 100644 --- a/spec/v2/providers/https.spec.ts +++ b/spec/v2/providers/https.spec.ts @@ -228,7 +228,7 @@ describe("onCall", () => { }); it("should return a minimal trigger/endpoint with appropriate values", () => { - const result = https.onCall((request) => 42); + const result = https.onCall(() => 42); expect(result.__trigger).to.deep.equal({ platform: "gcfv2", @@ -249,7 +249,7 @@ describe("onCall", () => { }); it("should create a complex trigger/endpoint with appropriate values", () => { - const result = https.onCall(FULL_OPTIONS, (request) => 42); + const result = https.onCall(FULL_OPTIONS, () => 42); expect(result.__trigger).to.deep.equal({ ...FULL_TRIGGER, diff --git a/spec/v2/providers/storage.spec.ts b/spec/v2/providers/storage.spec.ts index ac68dd494..5ad5117e0 100644 --- a/spec/v2/providers/storage.spec.ts +++ b/spec/v2/providers/storage.spec.ts @@ -289,6 +289,10 @@ describe("v2/storage", () => { it("should accept opts and handler, default bucket", () => { config.resetCache({ storageBucket: "default-bucket" }); + const result = storage.onObjectArchived( + { region: "us-west1" }, + () => 42 + ); expect(result.__trigger).to.deep.equal({ platform: "gcfv2", @@ -405,6 +409,10 @@ describe("v2/storage", () => { it("should accept opts and handler, default bucket", () => { config.resetCache({ storageBucket: "default-bucket" }); + const result = storage.onObjectFinalized( + { region: "us-west1" }, + () => 42 + ); expect(result.__trigger).to.deep.equal({ platform: "gcfv2", @@ -518,6 +526,10 @@ describe("v2/storage", () => { it("should accept opts and handler, default bucket", () => { config.resetCache({ storageBucket: "default-bucket" }); + const result = storage.onObjectDeleted( + { region: "us-west1" }, + () => 42 + ); expect(result.__trigger).to.deep.equal({ platform: "gcfv2", @@ -635,6 +647,11 @@ describe("v2/storage", () => { it("should accept opts and handler, default bucket", () => { config.resetCache({ storageBucket: "default-bucket" }); + const result = storage.onObjectMetadataUpdated( + { region: "us-west1" }, + () => 42 + ); + expect(result.__trigger).to.deep.equal({ platform: "gcfv2", labels: {}, diff --git a/spec/v2/providers/tasks.spec.ts b/spec/v2/providers/tasks.spec.ts index 0c444e3af..4962b342e 100644 --- a/spec/v2/providers/tasks.spec.ts +++ b/spec/v2/providers/tasks.spec.ts @@ -54,7 +54,7 @@ describe("onTaskDispatched", () => { }); it("should return a minimal trigger/endpoint with appropriate values", () => { - const result = onTaskDispatched(() => {}); + const result = onTaskDispatched(() => undefined); expect(result.__trigger).to.deep.equal({ platform: "gcfv2", @@ -87,7 +87,7 @@ describe("onTaskDispatched", () => { }, invoker: "private", }, - () => {} + () => undefined ); expect(result.__trigger).to.deep.equal({ @@ -140,7 +140,7 @@ describe("onTaskDispatched", () => { region: "us-west1", minInstances: 3, }, - (request) => {} + () => undefined ); expect(result.__trigger).to.deep.equal({ diff --git a/src/common/config.ts b/src/common/config.ts index ac634f2e7..e0811b387 100644 --- a/src/common/config.ts +++ b/src/common/config.ts @@ -35,6 +35,15 @@ export function firebaseConfig(): AppOptions | null { cache = JSON.parse(env); return cache; } + // Could have Runtime Config with Firebase in it as an ENV value. + try { + const config = JSON.parse(process.env.CLOUD_RUNTIME_CONFIG); + if (config.firebase) { + return config.firebase; + } + } catch (e) { + // do nothing + } if (process.env.GCLOUD_PROJECT) { logger.warn( diff --git a/src/common/encoding.ts b/src/common/encoding.ts index 019182864..7fb60cf0b 100644 --- a/src/common/encoding.ts +++ b/src/common/encoding.ts @@ -22,6 +22,17 @@ // Copied from firebase-tools/src/gcp/proto +/** + * A type alias used to annotate interfaces as using a google.protobuf.Duration. + * This type is parsed/encoded as a string of seconds + the "s" prefix. + */ +export type Duration = string; + +/** Get a google.protobuf.Duration for a number of seconds. */ +export function durationFromSeconds(s: number): Duration { + return `${s}s`; +} + /** * Utility function to help copy fields from type A to B. * As a safety net, catches typos or fields that aren't named the same @@ -61,6 +72,27 @@ export function convertIfPresent( dest[destField] = converter(src[srcField]); } +export function serviceAccountFromShorthand( + serviceAccount: string +): string | null { + if (serviceAccount === 'default') { + return null; + } else if (serviceAccount.endsWith('@')) { + if (!process.env.GCLOUD_PROJECT) { + throw new Error( + `Unable to determine email for service account '${serviceAccount}' because process.env.GCLOUD_PROJECT is not set.` + ); + } + return `${serviceAccount}${process.env.GCLOUD_PROJECT}.iam.gserviceaccount.com`; + } else if (serviceAccount.includes('@')) { + return serviceAccount; + } else { + throw new Error( + `Invalid option for serviceAccount: '${serviceAccount}'. Valid options are 'default', a service account email, or '{serviceAccountName}@'` + ); + } +} + export function convertInvoker(invoker: string | string[]): string[] { if (typeof invoker === "string") { invoker = [invoker]; diff --git a/src/v1/cloud-functions.ts b/src/v1/cloud-functions.ts index 3967b3f34..809feda1e 100644 --- a/src/v1/cloud-functions.ts +++ b/src/v1/cloud-functions.ts @@ -30,7 +30,7 @@ import { Schedule, } from "./function-configuration"; export { Request, Response }; -import { convertIfPresent, copyIfPresent } from "../common/encoding"; +import { convertIfPresent, copyIfPresent, serviceAccountFromShorthand, durationFromSeconds } from "../common/encoding"; import { initV1Endpoint, initV1ScheduleTrigger, @@ -245,7 +245,7 @@ interface TriggerAnnotation { labels?: { [key: string]: string }; regions?: string[]; schedule?: Schedule; - timeout?: Duration; + timeout?: string; vpcConnector?: string; vpcConnectorEgressSettings?: string; serviceAccountEmail?: string; @@ -418,14 +418,15 @@ export function makeCloudFunction({ return {}; } - const trigger: any = _.assign(optionsToTrigger(options), { + const trigger: any = { + ...optionsToTrigger(options), eventTrigger: { resource: triggerResource(), eventType: legacyEventType || provider + "." + eventType, service, }, - }); - if (!_.isEmpty(labels)) { + }; + if (!!labels && Object.keys(labels).length) { trigger.labels = { ...trigger.labels, ...labels }; } return trigger; diff --git a/src/v1/handler-builder.ts b/src/v1/handler-builder.ts deleted file mode 100644 index be710ae63..000000000 --- a/src/v1/handler-builder.ts +++ /dev/null @@ -1,373 +0,0 @@ -// The MIT License (MIT) -// -// Copyright (c) 2017 Firebase -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -import * as express from "express"; - -import { apps } from "../apps"; -import { CloudFunction, EventContext, HttpsFunction } from "./cloud-functions"; -import * as analytics from "./providers/analytics"; -import * as auth from "./providers/auth"; -import * as database from "./providers/database"; -import * as firestore from "./providers/firestore"; -import * as https from "./providers/https"; -import * as pubsub from "./providers/pubsub"; -import * as remoteConfig from "./providers/remoteConfig"; -import * as storage from "./providers/storage"; -import * as tasks from "./providers/tasks"; -import * as testLab from "./providers/testLab"; - -/** - * The `HandlerBuilder` class facilitates the writing of functions by developers - * building Firebase Extensions as well as developers who want to use the gcloud CLI or - * Google Cloud Console to deploy their functions. - * - * **Do not use `HandlerBuilder` when writing normal functions for deployment via - * the Firebase CLI.** For normal purposes, use - * [`FunctionBuilder`](/docs/reference/functions/function_builder_.functionbuilder). - */ -export class HandlerBuilder { - constructor() {} - - /** - * Create a handler for HTTPS events. - - * `onRequest` handles an HTTPS request and has the same signature as an Express app. - * - * @example - * ```javascript - * exports.myFunction = functions.handler.https.onRequest((req, res) => { ... }) - * ``` - * - * `onCall` declares a callable function for clients to call using a Firebase SDK. - * - * @example - * ```javascript - * exports.myFunction = functions.handler.https.onCall((data, context) => { ... }) - * ``` - */ - get https() { - return { - onRequest: ( - handler: (req: express.Request, resp: express.Response) => void - ): HttpsFunction => { - const func = https._onRequestWithOptions(handler, {}); - func.__trigger = {}; - func.__endpoint = undefined; - func.__requiredAPIs = undefined; - return func; - }, - onCall: ( - handler: (data: any, context: https.CallableContext) => any | Promise - ): HttpsFunction => { - const func = https._onCallWithOptions(handler, {}); - func.__trigger = {}; - func.__endpoint = undefined; - func.__requiredAPIs = undefined; - return func; - }, - }; - } - - /** - * Create a handler for tasks functions. - * - * @example - * ```javascript - * exports.myFunction = functions.handler.tasks.onDispatch((data, context) => { ... }) - * ``` - */ - /** @hidden */ - get tasks() { - return { - get taskQueue() { - return { - onDispatch: ( - handler: (data: any, context: tasks.TaskContext) => void | Promise - ): HttpsFunction => { - const builder = new tasks.TaskQueueBuilder(); - const func = builder.onDispatch(handler); - func.__trigger = {}; - func.__endpoint = undefined; - func.__requiredAPIs = undefined; - return func; - }, - }; - }, - }; - } - - /** - * Create a handler for Firebase Realtime Database events. - * - * `ref.onCreate` handles the creation of new data. - * - * @example - * ```javascript - * exports.myFunction = functions.handler.database.ref.onCreate((snap, context) => { ... }) - * ``` - * - * `ref.onUpdate` handles updates to existing data. - * - * @example - * ```javascript - * exports.myFunction = functions.handler.database.ref.onUpdate((change, context) => { ... }) - * ``` - - * `ref.onDelete` handles the deletion of existing data. - * - * @example - * ```javascript - * exports.myFunction = functions.handler.database.ref.onDelete((snap, context) => { ... }) - * ``` - - * `ref.onWrite` handles the creation, update, or deletion of data. - * - * @example - * ```javascript - * exports.myFunction = functions.handler.database.ref.onWrite((change, context) => { ... }) - * ``` - */ - get database() { - return { - /** @hidden */ - get instance() { - return { - get ref() { - return new database.RefBuilder(apps(), () => null, {}); - }, - }; - }, - get ref() { - return new database.RefBuilder(apps(), () => null, {}); - }, - }; - } - - /** - * Create a handler for Cloud Firestore events. - * - * `document.onCreate` handles the creation of new documents. - * - * @example - * ```javascript - * exports.myFunction = functions.handler.firestore.document.onCreate((snap, context) => { ... }) - * ``` - - * `document.onUpdate` handles updates to existing documents. - * - * @example - * ```javascript - * exports.myFunction = functions.handler.firestore.document.onUpdate((change, context) => { ... }) - * ``` - - * `document.onDelete` handles the deletion of existing documents. - * - * @example - * ```javascript - * exports.myFunction = functions.handler.firestore.document.onDelete((snap, context) => - * { ... }) - * ``` - - * `document.onWrite` handles the creation, update, or deletion of documents. - * - * @example - * ```javascript - * exports.myFunction = functions.handler.firestore.document.onWrite((change, context) => - * { ... }) - * ``` - */ - get firestore() { - return { - get document() { - return new firestore.DocumentBuilder(() => null, {}); - }, - /** @hidden */ - get namespace() { - return new firestore.DocumentBuilder(() => null, {}); - }, - /** @hidden */ - get database() { - return new firestore.DocumentBuilder(() => null, {}); - }, - }; - } - - /** - * Create a handler for Firebase Remote Config events. - - * `remoteConfig.onUpdate` handles events that update a Remote Config template. - - * @example - * ```javascript - * exports.myFunction = functions.handler.remoteConfig.onUpdate() => { ... }) - * ``` - */ - get remoteConfig() { - return { - onUpdate: ( - handler: ( - version: remoteConfig.TemplateVersion, - context: EventContext - ) => PromiseLike | any - ): CloudFunction => { - return new remoteConfig.UpdateBuilder(() => null, {}).onUpdate(handler); - }, - }; - } - - /** - * Create a handler for Google Analytics events. - - * `event.onLog` handles the logging of Analytics conversion events. - - * @example - * ```javascript - * exports.myFunction = functions.handler.analytics.event.onLog((event) => { ... }) - * ``` - */ - get analytics() { - return { - get event() { - return new analytics.AnalyticsEventBuilder(() => null, {}); - }, - }; - } - - /** - * Create a handler for Cloud Storage for Firebase events. - * - * `object.onArchive` handles the archiving of Storage objects. - * - * @example - * ```javascript - * exports.myFunction = functions.handler.storage.object.onArchive((object) => { ... }) - * ``` - - * `object.onDelete` handles the deletion of Storage objects. - * - * @example - * ```javascript - * exports.myFunction = functions.handler.storage.object.onDelete((object) => { ... }) - * ``` - - * `object.onFinalize` handles the creation of Storage objects. - * - * @example - * ```javascript - * exports.myFunction = functions.handler.storage.object.onFinalize((object) => - * { ... }) - * ``` - - * `object.onMetadataUpdate` handles changes to the metadata of existing Storage objects. - * - * @example - * ```javascript - * exports.myFunction = functions.handler.storage.object.onMetadataUpdate((object) => - * { ... }) - * ``` - */ - get storage() { - return { - get bucket() { - return new storage.BucketBuilder(() => null, {}).object(); - }, - - get object() { - return new storage.ObjectBuilder(() => null, {}); - }, - }; - } - - /** - * Create a handler for Cloud Pub/Sub events. - * - * `topic.onPublish` handles messages published to a Pub/Sub topic from SDKs, Cloud Console, or gcloud CLI. - * - * @example - * ```javascript - * exports.myFunction = functions.handler.pubsub.topic.onPublish((message) => { ... }) - * ``` - - * `schedule.onPublish` handles messages published to a Pub/Sub topic on a schedule. - * - * @example - * ```javascript - * exports.myFunction = functions.handler.pubsub.schedule.onPublish((message) => { ... }) - * ``` - */ - get pubsub() { - return { - get topic() { - return new pubsub.TopicBuilder(() => null, {}); - }, - get schedule() { - return new pubsub.ScheduleBuilder(() => null, {}); - }, - }; - } - - /** - * Create a handler for Firebase Authentication events. - * - * `user.onCreate` handles the creation of users. - * - * @example - * ```javascript - * exports.myFunction = functions.handler.auth.user.onCreate((user) => { ... }) - * ``` - - * `user.onDelete` handles the deletion of users. - * - * @example - * ```javascript - * exports.myFunction = functions.handler.auth.user.onDelete((user => { ... }) - * ``` - - */ - get auth() { - return { - get user() { - return new auth.UserBuilder(() => null, {}, {}); - }, - }; - } - - /** - * Create a handler for Firebase Test Lab events. - - * `testMatrix.onComplete` handles the completion of a test matrix. - - * @example - * ```javascript - * exports.myFunction = functions.handler.testLab.testMatrix.onComplete((testMatrix) => { ... }) - * ``` - */ - get testLab() { - return { - get testMatrix() { - return new testLab.TestMatrixBuilder(() => null, {}); - }, - }; - } -} - -export const handler = new HandlerBuilder(); diff --git a/src/v1/providers/https.ts b/src/v1/providers/https.ts index 337d98c2b..162023802 100644 --- a/src/v1/providers/https.ts +++ b/src/v1/providers/https.ts @@ -66,6 +66,17 @@ export function _onRequestWithOptions( const cloudFunction: any = (req: Request, res: express.Response) => { return handler(req, res); }; + cloudFunction.__trigger = { + ...optionsToTrigger(options), + httpsTrigger: {}, + }; + convertIfPresent( + cloudFunction.__trigger.httpsTrigger, + options, + 'invoker', + 'invoker', + convertInvoker + ); // TODO parse the options cloudFunction.__endpoint = { diff --git a/src/v2/core.ts b/src/v2/core.ts index afdc7c5eb..72c7e8e7d 100644 --- a/src/v2/core.ts +++ b/src/v2/core.ts @@ -30,6 +30,8 @@ import { ManifestEndpoint } from "../runtime/manifest"; export { Change }; +export { ParamsOf } from "../common/params"; + /** @internal */ export interface TriggerAnnotation { platform?: string; diff --git a/src/v2/providers/https.ts b/src/v2/providers/https.ts index d1c9216af..d8bc886cb 100644 --- a/src/v2/providers/https.ts +++ b/src/v2/providers/https.ts @@ -173,6 +173,8 @@ export type HttpsFunction = (( /** An Express response object, for this function to respond to callers. */ res: express.Response ) => void | Promise) & { + /** @alpha */ + __trigger?: unknown; /** @alpha */ __endpoint: ManifestEndpoint; }; @@ -235,6 +237,8 @@ export function onRequest( }; } + handler = wrapTraceContext(handler); + Object.defineProperty(handler, "__trigger", { get: () => { const baseOpts = options.optionsToTriggerAnnotations(options.getGlobalOptions()); @@ -322,6 +326,30 @@ export function onCall>( fixedLen ); + Object.defineProperty(func, '__trigger', { + get: () => { + const baseOpts = options.optionsToTriggerAnnotations( + options.getGlobalOptions() + ); + // global options calls region a scalar and https allows it to be an array, + // but optionsToTriggerAnnotations handles both cases. + const specificOpts = options.optionsToTriggerAnnotations(opts); + return { + platform: 'gcfv2', + ...baseOpts, + ...specificOpts, + labels: { + ...baseOpts?.labels, + ...specificOpts?.labels, + 'deployment-callable': 'true', + }, + httpsTrigger: { + allowInsecure: false, + }, + }; + }, + }); + const baseOpts = options.optionsToEndpoint(options.getGlobalOptions()); // global options calls region a scalar and https allows it to be an array, // but optionsToEndpoint handles both cases. diff --git a/src/v2/providers/pubsub.ts b/src/v2/providers/pubsub.ts index 0be727adc..4ad6f13fe 100644 --- a/src/v2/providers/pubsub.ts +++ b/src/v2/providers/pubsub.ts @@ -303,6 +303,29 @@ export function onMessagePublished( func.run = handler; + Object.defineProperty(func, '__trigger', { + get: () => { + const baseOpts = options.optionsToTriggerAnnotations( + options.getGlobalOptions() + ); + const specificOpts = options.optionsToTriggerAnnotations(opts); + + return { + platform: 'gcfv2', + ...baseOpts, + ...specificOpts, + labels: { + ...baseOpts?.labels, + ...specificOpts?.labels, + }, + eventTrigger: { + eventType: 'google.cloud.pubsub.topic.v1.messagePublished', + resource: `projects/${process.env.GCLOUD_PROJECT}/topics/${topic}`, + }, + }; + }, + }); + const baseOpts = options.optionsToEndpoint(options.getGlobalOptions()); const specificOpts = options.optionsToEndpoint(opts); diff --git a/src/v2/providers/storage.ts b/src/v2/providers/storage.ts index 3428576bf..01ecdd6d3 100644 --- a/src/v2/providers/storage.ts +++ b/src/v2/providers/storage.ts @@ -553,6 +553,29 @@ export function onOperation( func.run = handler; + Object.defineProperty(func, '__trigger', { + get: () => { + const baseOpts = options.optionsToTriggerAnnotations( + options.getGlobalOptions() + ); + const specificOpts = options.optionsToTriggerAnnotations(opts); + + return { + platform: 'gcfv2', + ...baseOpts, + ...specificOpts, + labels: { + ...baseOpts?.labels, + ...specificOpts?.labels, + }, + eventTrigger: { + eventType, + resource: bucket, // TODO(colerogers): replace with 'bucket,' eventually + }, + }; + }, + }); + // TypeScript doesn't recognize defineProperty as adding a property and complains // that __endpoint doesn't exist. We can either cast to any and lose all type safety // or we can just assign a meaningless value before calling defineProperty. From 637de820d0c4a21229cf6e61dad4bee57c224f79 Mon Sep 17 00:00:00 2001 From: Tyler Stark Date: Mon, 24 Oct 2022 00:43:55 -0500 Subject: [PATCH 6/8] npm run format:ts --- spec/v1/function-builder.spec.ts | 1 - spec/v1/providers/database.spec.ts | 113 +++++++++++++--------------- spec/v1/providers/firestore.spec.ts | 2 +- spec/v2/providers/fixtures.ts | 20 ++--- spec/v2/providers/storage.spec.ts | 20 +---- src/common/encoding.ts | 10 +-- src/v1/cloud-functions.ts | 7 +- src/v1/providers/https.ts | 4 +- src/v2/providers/https.ts | 10 +-- src/v2/providers/pubsub.ts | 10 +-- src/v2/providers/storage.ts | 8 +- 11 files changed, 92 insertions(+), 113 deletions(-) diff --git a/spec/v1/function-builder.spec.ts b/spec/v1/function-builder.spec.ts index 5343b186f..34dfa4b00 100644 --- a/spec/v1/function-builder.spec.ts +++ b/spec/v1/function-builder.spec.ts @@ -103,7 +103,6 @@ describe("FunctionBuilder", () => { .auth.user() .onCreate((user) => user); - expect(fn.__trigger.secrets).to.deep.equal([ { name: "API_KEY", diff --git a/spec/v1/providers/database.spec.ts b/spec/v1/providers/database.spec.ts index aca904c69..feb9343e5 100644 --- a/spec/v1/providers/database.spec.ts +++ b/spec/v1/providers/database.spec.ts @@ -20,14 +20,14 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -import {expect} from "chai"; -import {getApp, setApp} from "../../../src/common/app"; +import { expect } from "chai"; +import { getApp, setApp } from "../../../src/common/app"; import * as config from "../../../src/common/config"; -import {applyChange} from "../../../src/common/utilities/utils"; +import { applyChange } from "../../../src/common/utilities/utils"; import * as functions from "../../../src/v1"; import * as database from "../../../src/v1/providers/database"; -import {expectType} from "../../common/metaprogramming"; -import {MINIMAL_V1_ENDPOINT} from "../../fixtures"; +import { expectType } from "../../common/metaprogramming"; +import { MINIMAL_V1_ENDPOINT } from "../../fixtures"; describe("Database Functions", () => { describe("DatabaseBuilder", () => { @@ -120,7 +120,7 @@ describe("Database Functions", () => { const event = { data: { data: null, - delta: {foo: "bar"}, + delta: { foo: "bar" }, }, context: { eventId: "70172329041928", @@ -130,7 +130,7 @@ describe("Database Functions", () => { }, }; const handler = database.ref("/users/{id}").onWrite((change) => { - expect(change.after.val()).to.deep.equal({foo: "bar"}); + expect(change.after.val()).to.deep.equal({ foo: "bar" }); }); return handler(event.data, event.context); @@ -181,7 +181,7 @@ describe("Database Functions", () => { const event = { data: { data: null, - delta: {foo: "bar"}, + delta: { foo: "bar" }, }, context: { eventId: "70172329041928", @@ -192,7 +192,7 @@ describe("Database Functions", () => { }; const handler = database.ref("/users/{id}").onCreate((data) => { - expect(data.val()).to.deep.equal({foo: "bar"}); + expect(data.val()).to.deep.equal({ foo: "bar" }); }); return handler(event.data, event.context); @@ -243,7 +243,7 @@ describe("Database Functions", () => { const event = { data: { data: null, - delta: {foo: "bar"}, + delta: { foo: "bar" }, }, context: { eventId: "70172329041928", @@ -254,7 +254,7 @@ describe("Database Functions", () => { }; const handler = database.ref("/users/{id}").onUpdate((change) => { - expect(change.after.val()).to.deep.equal({foo: "bar"}); + expect(change.after.val()).to.deep.equal({ foo: "bar" }); }); return handler(event.data, event.context); @@ -304,7 +304,7 @@ describe("Database Functions", () => { it("should return a handler that emits events with a proper DataSnapshot", () => { const event = { data: { - data: {foo: "bar"}, + data: { foo: "bar" }, delta: null, }, context: { @@ -316,7 +316,7 @@ describe("Database Functions", () => { }; const handler = database.ref("/users/{id}").onDelete((data) => { - expect(data.val()).to.deep.equal({foo: "bar"}); + expect(data.val()).to.deep.equal({ foo: "bar" }); }); return handler(event.data, event.context); @@ -364,9 +364,7 @@ describe("extractInstanceAndPath", () => { }); it("should return the correct instance and path strings if root path is /refs", () => { - const [instance, path] = database.extractInstanceAndPath( - "projects/_/instances/foo/refs/refs" - ); + const [instance, path] = database.extractInstanceAndPath("projects/_/instances/foo/refs/refs"); expect(instance).to.equal("https://foo.firebaseio.com"); expect(path).to.equal("/refs"); }); @@ -390,10 +388,7 @@ describe("extractInstanceAndPath", () => { it("should throw an error if the given instance name contains anything except alphanumerics and dashes", () => { expect(() => { - return database.extractInstanceAndPath( - "projects/_/instances/a.bad.name/refs/bar", - undefined - ); + return database.extractInstanceAndPath("projects/_/instances/a.bad.name/refs/bar", undefined); }).to.throw(Error); expect(() => { return database.extractInstanceAndPath( @@ -438,12 +433,12 @@ describe("DataSnapshot", () => { describe("#val(): any", () => { it("should return child values based on the child path", () => { - populate(applyChange({a: {b: "c"}}, {a: {d: "e"}})); - expect(subject.child("a").val()).to.deep.equal({b: "c", d: "e"}); + populate(applyChange({ a: { b: "c" } }, { a: { d: "e" } })); + expect(subject.child("a").val()).to.deep.equal({ b: "c", d: "e" }); }); it("should return null for children past a leaf", () => { - populate(applyChange({a: 23}, {b: 33})); + populate(applyChange({ a: 23 }, { b: 33 })); expect(subject.child("a/b").val()).to.be.null; expect(subject.child("b/c").val()).to.be.null; }); @@ -451,38 +446,38 @@ describe("DataSnapshot", () => { it("should return a leaf value", () => { populate(23); expect(subject.val()).to.eq(23); - populate({b: 23, a: null}); + populate({ b: 23, a: null }); expect(subject.child("b").val()).to.eq(23); }); it("should coerce object into array if all keys are integers", () => { - populate({0: "a", 1: "b", 2: {c: "d"}}); - expect(subject.val()).to.deep.equal(["a", "b", {c: "d"}]); - populate({0: "a", 2: "b", 3: {c: "d"}}); - expect(subject.val()).to.deep.equal(["a", undefined, "b", {c: "d"}]); - populate({foo: {0: "a", 1: "b"}}); - expect(subject.val()).to.deep.equal({foo: ["a", "b"]}); + populate({ 0: "a", 1: "b", 2: { c: "d" } }); + expect(subject.val()).to.deep.equal(["a", "b", { c: "d" }]); + populate({ 0: "a", 2: "b", 3: { c: "d" } }); + expect(subject.val()).to.deep.equal(["a", undefined, "b", { c: "d" }]); + populate({ foo: { 0: "a", 1: "b" } }); + expect(subject.val()).to.deep.equal({ foo: ["a", "b"] }); }); // Regression test: zero-values (including children) were accidentally forwarded as 'null'. it("should deal with zero-values appropriately", () => { populate(0); expect(subject.val()).to.equal(0); - populate({myKey: 0}); - expect(subject.val()).to.deep.equal({myKey: 0}); + populate({ myKey: 0 }); + expect(subject.val()).to.deep.equal({ myKey: 0 }); }); // Regression test: .val() was returning array of nulls when there's a property called length (BUG#37683995) it('should return correct values when data has "length" property', () => { - populate({length: 3, foo: "bar"}); - expect(subject.val()).to.deep.equal({length: 3, foo: "bar"}); + populate({ length: 3, foo: "bar" }); + expect(subject.val()).to.deep.equal({ length: 3, foo: "bar" }); }); it("should deal with null-values appropriately", () => { populate(null); expect(subject.val()).to.be.null; - populate({myKey: null}); + populate({ myKey: null }); expect(subject.val()).to.be.null; }); @@ -490,10 +485,10 @@ describe("DataSnapshot", () => { populate({}); expect(subject.val()).to.be.null; - populate({myKey: {}}); + populate({ myKey: {} }); expect(subject.val()).to.be.null; - populate({myKey: {child: null}}); + populate({ myKey: { child: null } }); expect(subject.val()).to.be.null; }); @@ -501,78 +496,78 @@ describe("DataSnapshot", () => { populate([]); expect(subject.val()).to.be.null; - populate({myKey: []}); + populate({ myKey: [] }); expect(subject.val()).to.be.null; - populate({myKey: [null]}); + populate({ myKey: [null] }); expect(subject.val()).to.be.null; - populate({myKey: [{}]}); + populate({ myKey: [{}] }); expect(subject.val()).to.be.null; - populate({myKey: [{myKey: null}]}); + populate({ myKey: [{ myKey: null }] }); expect(subject.val()).to.be.null; - populate({myKey: [{myKey: {}}]}); + populate({ myKey: [{ myKey: {} }] }); expect(subject.val()).to.be.null; }); }); describe("#child(): DataSnapshot", () => { it("should work with multiple calls", () => { - populate({a: {b: {c: "d"}}}); + populate({ a: { b: { c: "d" } } }); expect(subject.child("a").child("b/c").val()).to.equal("d"); }); }); describe("#exists(): boolean", () => { it("should be true for an object value", () => { - populate({a: {b: "c"}}); + populate({ a: { b: "c" } }); expect(subject.child("a").exists()).to.be.true; }); it("should be true for a leaf value", () => { - populate({a: {b: "c"}}); + populate({ a: { b: "c" } }); expect(subject.child("a/b").exists()).to.be.true; }); it("should be false for a non-existent value", () => { - populate({a: {b: "c", nullChild: null}}); + populate({ a: { b: "c", nullChild: null } }); expect(subject.child("d").exists()).to.be.false; expect(subject.child("nullChild").exists()).to.be.false; }); it("should be false for a value pathed beyond a leaf", () => { - populate({a: {b: "c"}}); + populate({ a: { b: "c" } }); expect(subject.child("a/b/c").exists()).to.be.false; }); it("should be false for an empty object value", () => { - populate({a: {}}); + populate({ a: {} }); expect(subject.child("a").exists()).to.be.false; - populate({a: {child: null}}); + populate({ a: { child: null } }); expect(subject.child("a").exists()).to.be.false; - populate({a: {child: {}}}); + populate({ a: { child: {} } }); expect(subject.child("a").exists()).to.be.false; }); it("should be false for an empty array value", () => { - populate({a: []}); + populate({ a: [] }); expect(subject.child("a").exists()).to.be.false; - populate({a: [null]}); + populate({ a: [null] }); expect(subject.child("a").exists()).to.be.false; - populate({a: [{}]}); + populate({ a: [{}] }); expect(subject.child("a").exists()).to.be.false; }); }); describe("#forEach(action: (a: DataSnapshot) => boolean): boolean", () => { it("should iterate through child snapshots", () => { - populate({a: "b", c: "d"}); + populate({ a: "b", c: "d" }); let out = ""; subject.forEach((snap: any) => { out += snap.val(); @@ -581,7 +576,7 @@ describe("DataSnapshot", () => { }); it("should have correct key values for child snapshots", () => { - populate({a: "b", c: "d"}); + populate({ a: "b", c: "d" }); let out = ""; subject.forEach((snap: any) => { out += snap.key; @@ -610,7 +605,7 @@ describe("DataSnapshot", () => { }); it("should cancel further enumeration if callback returns true", () => { - populate({a: "b", c: "d", e: "f", g: "h"}); + populate({ a: "b", c: "d", e: "f", g: "h" }); let out = ""; const ret = subject.forEach((snap: any) => { if (snap.val() === "f") { @@ -623,7 +618,7 @@ describe("DataSnapshot", () => { }); it("should not cancel further enumeration if callback returns a truthy value", () => { - populate({a: "b", c: "d", e: "f", g: "h"}); + populate({ a: "b", c: "d", e: "f", g: "h" }); let out = ""; const ret = subject.forEach((snap: any) => { out += snap.val(); @@ -634,7 +629,7 @@ describe("DataSnapshot", () => { }); it("should not cancel further enumeration if callback does not return", () => { - populate({a: "b", c: "d", e: "f", g: "h"}); + populate({ a: "b", c: "d", e: "f", g: "h" }); let out = ""; const ret = subject.forEach((snap: any) => { out += snap.val(); @@ -696,7 +691,7 @@ describe("DataSnapshot", () => { describe("#hasChild(childPath): boolean", () => { it("should return true for a child or deep child", () => { - populate({a: {b: "c"}, d: 23}); + populate({ a: { b: "c" }, d: 23 }); expect(subject.hasChild("a/b")).to.be.true; expect(subject.hasChild("d")).to.be.true; }); diff --git a/spec/v1/providers/firestore.spec.ts b/spec/v1/providers/firestore.spec.ts index c9fbf0334..759a26a4a 100644 --- a/spec/v1/providers/firestore.spec.ts +++ b/spec/v1/providers/firestore.spec.ts @@ -137,7 +137,7 @@ describe("Firestore Functions", () => { it("should allow custom namespaces", () => { const resource = "projects/project1/databases/(default)/documents@v2/users/{uid}"; const cloudFunction = firestore - .namespace('v2') + .namespace("v2") .document("users/{uid}") .onWrite(() => null); diff --git a/spec/v2/providers/fixtures.ts b/spec/v2/providers/fixtures.ts index dc4ef1117..18cc81f85 100644 --- a/spec/v2/providers/fixtures.ts +++ b/spec/v2/providers/fixtures.ts @@ -1,4 +1,4 @@ -import { ManifestEndpoint } from '../../../src/runtime/manifest'; +import { ManifestEndpoint } from "../../../src/runtime/manifest"; import { TriggerAnnotation } from "../../../src/v2/core"; import * as options from "../../../src/v2/options"; import { RESET_VALUE } from "../../../src/common/options"; @@ -61,22 +61,22 @@ export const FULL_TRIGGER: TriggerAnnotation = { }; export const FULL_ENDPOINT: ManifestEndpoint = { - platform: 'gcfv2', - region: ['us-west1'], + platform: "gcfv2", + region: ["us-west1"], availableMemoryMb: 512, timeoutSeconds: 60, minInstances: 1, maxInstances: 3, concurrency: 20, vpc: { - connector: 'aConnector', - egressSettings: 'ALL_TRAFFIC', + connector: "aConnector", + egressSettings: "ALL_TRAFFIC", }, - serviceAccountEmail: 'root@', - ingressSettings: 'ALLOW_ALL', - cpu: 'gcf_gen1', + serviceAccountEmail: "root@", + ingressSettings: "ALLOW_ALL", + cpu: "gcf_gen1", labels: { - hello: 'world', + hello: "world", }, - secretEnvironmentVariables: [{ key: 'MY_SECRET' }], + secretEnvironmentVariables: [{ key: "MY_SECRET" }], }; diff --git a/spec/v2/providers/storage.spec.ts b/spec/v2/providers/storage.spec.ts index 5ad5117e0..d5a699d70 100644 --- a/spec/v2/providers/storage.spec.ts +++ b/spec/v2/providers/storage.spec.ts @@ -289,10 +289,7 @@ describe("v2/storage", () => { it("should accept opts and handler, default bucket", () => { config.resetCache({ storageBucket: "default-bucket" }); - const result = storage.onObjectArchived( - { region: "us-west1" }, - () => 42 - ); + const result = storage.onObjectArchived({ region: "us-west1" }, () => 42); expect(result.__trigger).to.deep.equal({ platform: "gcfv2", @@ -409,10 +406,7 @@ describe("v2/storage", () => { it("should accept opts and handler, default bucket", () => { config.resetCache({ storageBucket: "default-bucket" }); - const result = storage.onObjectFinalized( - { region: "us-west1" }, - () => 42 - ); + const result = storage.onObjectFinalized({ region: "us-west1" }, () => 42); expect(result.__trigger).to.deep.equal({ platform: "gcfv2", @@ -526,10 +520,7 @@ describe("v2/storage", () => { it("should accept opts and handler, default bucket", () => { config.resetCache({ storageBucket: "default-bucket" }); - const result = storage.onObjectDeleted( - { region: "us-west1" }, - () => 42 - ); + const result = storage.onObjectDeleted({ region: "us-west1" }, () => 42); expect(result.__trigger).to.deep.equal({ platform: "gcfv2", @@ -647,10 +638,7 @@ describe("v2/storage", () => { it("should accept opts and handler, default bucket", () => { config.resetCache({ storageBucket: "default-bucket" }); - const result = storage.onObjectMetadataUpdated( - { region: "us-west1" }, - () => 42 - ); + const result = storage.onObjectMetadataUpdated({ region: "us-west1" }, () => 42); expect(result.__trigger).to.deep.equal({ platform: "gcfv2", diff --git a/src/common/encoding.ts b/src/common/encoding.ts index 7fb60cf0b..88b40ef3f 100644 --- a/src/common/encoding.ts +++ b/src/common/encoding.ts @@ -72,19 +72,17 @@ export function convertIfPresent( dest[destField] = converter(src[srcField]); } -export function serviceAccountFromShorthand( - serviceAccount: string -): string | null { - if (serviceAccount === 'default') { +export function serviceAccountFromShorthand(serviceAccount: string): string | null { + if (serviceAccount === "default") { return null; - } else if (serviceAccount.endsWith('@')) { + } else if (serviceAccount.endsWith("@")) { if (!process.env.GCLOUD_PROJECT) { throw new Error( `Unable to determine email for service account '${serviceAccount}' because process.env.GCLOUD_PROJECT is not set.` ); } return `${serviceAccount}${process.env.GCLOUD_PROJECT}.iam.gserviceaccount.com`; - } else if (serviceAccount.includes('@')) { + } else if (serviceAccount.includes("@")) { return serviceAccount; } else { throw new Error( diff --git a/src/v1/cloud-functions.ts b/src/v1/cloud-functions.ts index 809feda1e..d2551e516 100644 --- a/src/v1/cloud-functions.ts +++ b/src/v1/cloud-functions.ts @@ -30,7 +30,12 @@ import { Schedule, } from "./function-configuration"; export { Request, Response }; -import { convertIfPresent, copyIfPresent, serviceAccountFromShorthand, durationFromSeconds } from "../common/encoding"; +import { + convertIfPresent, + copyIfPresent, + serviceAccountFromShorthand, + durationFromSeconds, +} from "../common/encoding"; import { initV1Endpoint, initV1ScheduleTrigger, diff --git a/src/v1/providers/https.ts b/src/v1/providers/https.ts index 162023802..0cdcc52c1 100644 --- a/src/v1/providers/https.ts +++ b/src/v1/providers/https.ts @@ -73,8 +73,8 @@ export function _onRequestWithOptions( convertIfPresent( cloudFunction.__trigger.httpsTrigger, options, - 'invoker', - 'invoker', + "invoker", + "invoker", convertInvoker ); // TODO parse the options diff --git a/src/v2/providers/https.ts b/src/v2/providers/https.ts index d8bc886cb..010a4a355 100644 --- a/src/v2/providers/https.ts +++ b/src/v2/providers/https.ts @@ -326,22 +326,20 @@ export function onCall>( fixedLen ); - Object.defineProperty(func, '__trigger', { + Object.defineProperty(func, "__trigger", { get: () => { - const baseOpts = options.optionsToTriggerAnnotations( - options.getGlobalOptions() - ); + const baseOpts = options.optionsToTriggerAnnotations(options.getGlobalOptions()); // global options calls region a scalar and https allows it to be an array, // but optionsToTriggerAnnotations handles both cases. const specificOpts = options.optionsToTriggerAnnotations(opts); return { - platform: 'gcfv2', + platform: "gcfv2", ...baseOpts, ...specificOpts, labels: { ...baseOpts?.labels, ...specificOpts?.labels, - 'deployment-callable': 'true', + "deployment-callable": "true", }, httpsTrigger: { allowInsecure: false, diff --git a/src/v2/providers/pubsub.ts b/src/v2/providers/pubsub.ts index 4ad6f13fe..2f215d52b 100644 --- a/src/v2/providers/pubsub.ts +++ b/src/v2/providers/pubsub.ts @@ -303,15 +303,13 @@ export function onMessagePublished( func.run = handler; - Object.defineProperty(func, '__trigger', { + Object.defineProperty(func, "__trigger", { get: () => { - const baseOpts = options.optionsToTriggerAnnotations( - options.getGlobalOptions() - ); + const baseOpts = options.optionsToTriggerAnnotations(options.getGlobalOptions()); const specificOpts = options.optionsToTriggerAnnotations(opts); return { - platform: 'gcfv2', + platform: "gcfv2", ...baseOpts, ...specificOpts, labels: { @@ -319,7 +317,7 @@ export function onMessagePublished( ...specificOpts?.labels, }, eventTrigger: { - eventType: 'google.cloud.pubsub.topic.v1.messagePublished', + eventType: "google.cloud.pubsub.topic.v1.messagePublished", resource: `projects/${process.env.GCLOUD_PROJECT}/topics/${topic}`, }, }; diff --git a/src/v2/providers/storage.ts b/src/v2/providers/storage.ts index 01ecdd6d3..cf88cdc51 100644 --- a/src/v2/providers/storage.ts +++ b/src/v2/providers/storage.ts @@ -553,15 +553,13 @@ export function onOperation( func.run = handler; - Object.defineProperty(func, '__trigger', { + Object.defineProperty(func, "__trigger", { get: () => { - const baseOpts = options.optionsToTriggerAnnotations( - options.getGlobalOptions() - ); + const baseOpts = options.optionsToTriggerAnnotations(options.getGlobalOptions()); const specificOpts = options.optionsToTriggerAnnotations(opts); return { - platform: 'gcfv2', + platform: "gcfv2", ...baseOpts, ...specificOpts, labels: { From f12e183d924fcbbc04fd7a9a1006268667741805 Mon Sep 17 00:00:00 2001 From: Tyler Stark Date: Tue, 25 Oct 2022 12:20:20 -0500 Subject: [PATCH 7/8] Updates per PR comments --- spec/v1/providers/analytics.spec.ts | 4 ++++ spec/v1/providers/firestore.spec.ts | 4 +++- spec/v2/providers/fixtures.ts | 22 +--------------------- src/common/config.ts | 9 --------- 4 files changed, 8 insertions(+), 31 deletions(-) diff --git a/spec/v1/providers/analytics.spec.ts b/spec/v1/providers/analytics.spec.ts index 511751ceb..90a617686 100644 --- a/spec/v1/providers/analytics.spec.ts +++ b/spec/v1/providers/analytics.spec.ts @@ -309,6 +309,10 @@ describe("Analytics Functions", () => { expect(() => analytics.event("event").onLog(() => null)).to.not.throw(Error); }); + it("should throw when __endpoint is accessed", () => { + expect(() => analytics.event("event").onLog(() => null).__endpoint).to.throw(Error); + }); + it("should throw when trigger is accessed", () => { expect(() => analytics.event("event").onLog(() => null).__trigger).to.throw(Error); }); diff --git a/spec/v1/providers/firestore.spec.ts b/spec/v1/providers/firestore.spec.ts index 759a26a4a..f8f4288db 100644 --- a/spec/v1/providers/firestore.spec.ts +++ b/spec/v1/providers/firestore.spec.ts @@ -175,7 +175,9 @@ describe("Firestore Functions", () => { .database("myDB") .namespace("v2") .document("users/{uid}") - .onWrite(() => null); + .onWrite((snap, context) => { + expectType<{ uid: string }>(context.params); + }); expect(cloudFunction.__trigger).to.deep.equal(expectedTrigger(resource, "document.write")); diff --git a/spec/v2/providers/fixtures.ts b/spec/v2/providers/fixtures.ts index 18cc81f85..1766a3dfb 100644 --- a/spec/v2/providers/fixtures.ts +++ b/spec/v2/providers/fixtures.ts @@ -1,28 +1,8 @@ import { ManifestEndpoint } from "../../../src/runtime/manifest"; import { TriggerAnnotation } from "../../../src/v2/core"; import * as options from "../../../src/v2/options"; -import { RESET_VALUE } from "../../../src/common/options"; -export const MINIMAL_V2_ENDPOINT: ManifestEndpoint = { - availableMemoryMb: RESET_VALUE, - concurrency: RESET_VALUE, - ingressSettings: RESET_VALUE, - maxInstances: RESET_VALUE, - minInstances: RESET_VALUE, - serviceAccountEmail: RESET_VALUE, - timeoutSeconds: RESET_VALUE, - vpc: RESET_VALUE, -}; - -export const MINIMAL_V1_ENDPOINT: ManifestEndpoint = { - availableMemoryMb: RESET_VALUE, - ingressSettings: RESET_VALUE, - maxInstances: RESET_VALUE, - minInstances: RESET_VALUE, - serviceAccountEmail: RESET_VALUE, - timeoutSeconds: RESET_VALUE, - vpc: RESET_VALUE, -}; +export { MINIMAL_V1_ENDPOINT, MINIMAL_V2_ENDPOINT } from "../../fixtures"; export const FULL_OPTIONS: options.GlobalOptions = { region: "us-west1", diff --git a/src/common/config.ts b/src/common/config.ts index e0811b387..ac634f2e7 100644 --- a/src/common/config.ts +++ b/src/common/config.ts @@ -35,15 +35,6 @@ export function firebaseConfig(): AppOptions | null { cache = JSON.parse(env); return cache; } - // Could have Runtime Config with Firebase in it as an ENV value. - try { - const config = JSON.parse(process.env.CLOUD_RUNTIME_CONFIG); - if (config.firebase) { - return config.firebase; - } - } catch (e) { - // do nothing - } if (process.env.GCLOUD_PROJECT) { logger.warn( From 82b040d384f108563966579d71e70b60c89d96ec Mon Sep 17 00:00:00 2001 From: Tyler Stark Date: Wed, 26 Oct 2022 22:13:48 -0500 Subject: [PATCH 8/8] Updates per PR feedback --- spec/v1/function-builder.spec.ts | 35 ++++++++ spec/v1/providers/database.spec.ts | 139 ++++++++++++++++------------- 2 files changed, 113 insertions(+), 61 deletions(-) diff --git a/spec/v1/function-builder.spec.ts b/spec/v1/function-builder.spec.ts index 34dfa4b00..955a0ab38 100644 --- a/spec/v1/function-builder.spec.ts +++ b/spec/v1/function-builder.spec.ts @@ -41,6 +41,7 @@ describe("FunctionBuilder", () => { .onCreate((user) => user); expect(fn.__trigger.regions).to.deep.equal(["us-east1"]); + expect(fn.__endpoint.region).to.deep.equal(["us-east1"]); }); it("should allow multiple supported regions to be set", () => { @@ -50,6 +51,7 @@ describe("FunctionBuilder", () => { .onCreate((user) => user); expect(fn.__trigger.regions).to.deep.equal(["us-east1", "us-central1"]); + expect(fn.__endpoint.region).to.deep.equal(["us-east1", "us-central1"]); }); it("should allow all supported regions to be set", () => { @@ -77,6 +79,17 @@ describe("FunctionBuilder", () => { "asia-east2", "asia-northeast1", ]); + + expect(fn.__endpoint.region).to.deep.equal([ + "us-central1", + "us-east1", + "us-east4", + "europe-west1", + "europe-west2", + "europe-west3", + "asia-east2", + "asia-northeast1", + ]); }); it("should allow valid runtime options to be set", () => { @@ -92,6 +105,8 @@ describe("FunctionBuilder", () => { expect(fn.__endpoint.availableMemoryMb).to.deep.equal(256); expect(fn.__endpoint.timeoutSeconds).to.deep.equal(90); expect(fn.__endpoint.eventTrigger.retry).to.deep.equal(true); + expect(fn.__trigger.availableMemoryMb).to.deep.equal(256); + expect(fn.__trigger.timeout).to.deep.equal("90s"); }); it("should allow SecretParams in the secrets array and convert them", () => { @@ -143,6 +158,9 @@ describe("FunctionBuilder", () => { expect(fn.__trigger.regions).to.deep.equal(["europe-west2"]); expect(fn.__trigger.availableMemoryMb).to.deep.equal(256); expect(fn.__trigger.timeout).to.deep.equal("90s"); + expect(fn.__endpoint.region).to.deep.equal(["europe-west2"]); + expect(fn.__endpoint.availableMemoryMb).to.deep.equal(256); + expect(fn.__endpoint.timeoutSeconds).to.deep.equal(90); }); it("should allow both valid runtime options and supported region to be set in reverse order", () => { @@ -158,6 +176,9 @@ describe("FunctionBuilder", () => { expect(fn.__trigger.regions).to.deep.equal(["europe-west1"]); expect(fn.__trigger.availableMemoryMb).to.deep.equal(256); expect(fn.__trigger.timeout).to.deep.equal("90s"); + expect(fn.__endpoint.region).to.deep.equal(["europe-west1"]); + expect(fn.__endpoint.availableMemoryMb).to.deep.equal(256); + expect(fn.__endpoint.timeoutSeconds).to.deep.equal(90); }); it("should fail if supported region but invalid runtime options are set (reverse order)", () => { @@ -228,6 +249,7 @@ describe("FunctionBuilder", () => { .https.onRequest(() => undefined); expect(fn.__trigger.ingressSettings).to.equal("ALLOW_INTERNAL_ONLY"); + expect(fn.__endpoint.ingressSettings).to.equal("ALLOW_INTERNAL_ONLY"); }); it("should throw an error if user chooses an invalid ingressSettings", () => { @@ -250,6 +272,7 @@ describe("FunctionBuilder", () => { .onCreate((user) => user); expect(fn.__trigger.vpcConnector).to.equal("test-connector"); + expect(fn.__endpoint.vpc).to.deep.equal({ connector: "test-connector" }); }); it("should allow a vpcConnectorEgressSettings to be set", () => { @@ -262,6 +285,10 @@ describe("FunctionBuilder", () => { .onCreate((user) => user); expect(fn.__trigger.vpcConnectorEgressSettings).to.equal("PRIVATE_RANGES_ONLY"); + expect(fn.__endpoint.vpc).to.deep.equal({ + connector: "test-connector", + egressSettings: "PRIVATE_RANGES_ONLY", + }); }); it("should throw an error if user chooses an invalid vpcConnectorEgressSettings", () => { @@ -288,6 +315,7 @@ describe("FunctionBuilder", () => { .onCreate((user) => user); expect(fn.__endpoint.serviceAccountEmail).to.equal(serviceAccount); + expect(fn.__trigger.serviceAccountEmail).to.equal(serviceAccount); }); it("should allow a serviceAccount to be set with generated service account email", () => { @@ -303,6 +331,7 @@ describe("FunctionBuilder", () => { expect(fn.__trigger.serviceAccountEmail).to.equal( `test-service-account@${projectId}.iam.gserviceaccount.com` ); + expect(fn.__endpoint.serviceAccountEmail).to.equal(`test-service-account@`); }); it("should set a null serviceAccountEmail if service account is set to `default`", () => { @@ -315,6 +344,7 @@ describe("FunctionBuilder", () => { .onCreate((user) => user); expect(fn.__trigger.serviceAccountEmail).to.be.null; + expect(fn.__endpoint.serviceAccountEmail).to.equal(serviceAccount); }); it("should throw an error if serviceAccount is set to an invalid value", () => { @@ -336,6 +366,7 @@ describe("FunctionBuilder", () => { .onCreate((user) => user); expect(fn.__endpoint.availableMemoryMb).to.deep.equal(4096); + expect(fn.__trigger.availableMemoryMb).to.deep.equal(4096); }); it("should allow labels to be set", () => { @@ -351,6 +382,9 @@ describe("FunctionBuilder", () => { expect(fn.__trigger.labels).to.deep.equal({ "valid-key": "valid-value", }); + expect(fn.__endpoint.labels).to.deep.equal({ + "valid-key": "valid-value", + }); }); it("should throw an error if more than 58 labels are set", () => { @@ -504,6 +538,7 @@ describe("FunctionBuilder", () => { .onCreate((user) => user); expect(fn.__trigger.secrets).to.deep.equal(secrets); + expect(fn.__endpoint.secretEnvironmentVariables).to.deep.equal([{ key: secrets[0] }]); }); it("should throw error given secrets expressed with full resource name", () => { diff --git a/spec/v1/providers/database.spec.ts b/spec/v1/providers/database.spec.ts index feb9343e5..9651961dc 100644 --- a/spec/v1/providers/database.spec.ts +++ b/spec/v1/providers/database.spec.ts @@ -321,6 +321,18 @@ describe("Database Functions", () => { return handler(event.data, event.context); }); + + it("Should have params of the correct type", () => { + database.ref("foo").onDelete((event, context) => { + expectType>(context.params); + }); + database.ref("foo/{bar}").onDelete((event, context) => { + expectType<{ bar: string }>(context.params); + }); + database.ref("foo/{bar}/{baz}").onDelete((event, context) => { + expectType<{ bar: string; baz: string }>(context.params); + }); + }); }); }); @@ -342,74 +354,79 @@ describe("Database Functions", () => { const cf = database.ref("/path").onWrite(() => null); expect(cf.run).to.not.throw(Error); }); -}); - -describe("extractInstanceAndPath", () => { - it("should return correct us-central prod instance and path strings if domain is missing", () => { - const [instance, path] = database.extractInstanceAndPath( - "projects/_/instances/foo/refs/bar", - undefined - ); - expect(instance).to.equal("https://foo.firebaseio.com"); - expect(path).to.equal("/bar"); - }); - it("should return the correct staging instance and path strings if domain is present", () => { - const [instance, path] = database.extractInstanceAndPath( - "projects/_/instances/foo/refs/bar", - "firebaseio-staging.com" - ); - expect(instance).to.equal("https://foo.firebaseio-staging.com"); - expect(path).to.equal("/bar"); - }); + describe("extractInstanceAndPath", () => { + it("should return correct us-central prod instance and path strings if domain is missing", () => { + const [instance, path] = database.extractInstanceAndPath( + "projects/_/instances/foo/refs/bar", + undefined + ); + expect(instance).to.equal("https://foo.firebaseio.com"); + expect(path).to.equal("/bar"); + }); - it("should return the correct instance and path strings if root path is /refs", () => { - const [instance, path] = database.extractInstanceAndPath("projects/_/instances/foo/refs/refs"); - expect(instance).to.equal("https://foo.firebaseio.com"); - expect(path).to.equal("/refs"); - }); + it("should return the correct staging instance and path strings if domain is present", () => { + const [instance, path] = database.extractInstanceAndPath( + "projects/_/instances/foo/refs/bar", + "firebaseio-staging.com" + ); + expect(instance).to.equal("https://foo.firebaseio-staging.com"); + expect(path).to.equal("/bar"); + }); - it("should return the correct instance and path strings if a child path contain /refs", () => { - const [instance, path] = database.extractInstanceAndPath( - "projects/_/instances/foo/refs/root/refs" - ); - expect(instance).to.equal("https://foo.firebaseio.com"); - expect(path).to.equal("/root/refs"); - }); + it("should return the correct instance and path strings if root path is /refs", () => { + const [instance, path] = database.extractInstanceAndPath( + "projects/_/instances/foo/refs/refs" + ); + expect(instance).to.equal("https://foo.firebaseio.com"); + expect(path).to.equal("/refs"); + }); - it("should return the correct multi-region instance and path strings if domain is present", () => { - const [instance, path] = database.extractInstanceAndPath( - "projects/_/instances/foo/refs/bar", - "euw1.firebasedatabase.app" - ); - expect(instance).to.equal("https://foo.euw1.firebasedatabase.app"); - expect(path).to.equal("/bar"); - }); + it("should return the correct instance and path strings if a child path contain /refs", () => { + const [instance, path] = database.extractInstanceAndPath( + "projects/_/instances/foo/refs/root/refs" + ); + expect(instance).to.equal("https://foo.firebaseio.com"); + expect(path).to.equal("/root/refs"); + }); - it("should throw an error if the given instance name contains anything except alphanumerics and dashes", () => { - expect(() => { - return database.extractInstanceAndPath("projects/_/instances/a.bad.name/refs/bar", undefined); - }).to.throw(Error); - expect(() => { - return database.extractInstanceAndPath( - "projects/_/instances/a_different_bad_name/refs/bar", - undefined + it("should return the correct multi-region instance and path strings if domain is present", () => { + const [instance, path] = database.extractInstanceAndPath( + "projects/_/instances/foo/refs/bar", + "euw1.firebasedatabase.app" ); - }).to.throw(Error); - expect(() => { - return database.extractInstanceAndPath("projects/_/instances/BAD!!!!/refs/bar", undefined); - }).to.throw(Error); - }); + expect(instance).to.equal("https://foo.euw1.firebasedatabase.app"); + expect(path).to.equal("/bar"); + }); - it("should use the emulator host when present", () => { - process.env.FIREBASE_DATABASE_EMULATOR_HOST = "localhost:1234"; - const [instance, path] = database.extractInstanceAndPath( - "projects/_/instances/foo/refs/bar", - "firebaseio-staging.com" - ); - expect(instance).to.equal("http://localhost:1234/?ns=foo"); - expect(path).to.equal("/bar"); - delete process.env.FIREBASE_DATABASE_EMULATOR_HOST; + it("should throw an error if the given instance name contains anything except alphanumerics and dashes", () => { + expect(() => { + return database.extractInstanceAndPath( + "projects/_/instances/a.bad.name/refs/bar", + undefined + ); + }).to.throw(Error); + expect(() => { + return database.extractInstanceAndPath( + "projects/_/instances/a_different_bad_name/refs/bar", + undefined + ); + }).to.throw(Error); + expect(() => { + return database.extractInstanceAndPath("projects/_/instances/BAD!!!!/refs/bar", undefined); + }).to.throw(Error); + }); + + it("should use the emulator host when present", () => { + process.env.FIREBASE_DATABASE_EMULATOR_HOST = "localhost:1234"; + const [instance, path] = database.extractInstanceAndPath( + "projects/_/instances/foo/refs/bar", + "firebaseio-staging.com" + ); + expect(instance).to.equal("http://localhost:1234/?ns=foo"); + expect(path).to.equal("/bar"); + delete process.env.FIREBASE_DATABASE_EMULATOR_HOST; + }); }); });