From 6e3a0261fb83be8931dbf63d5d4ebace3602a6eb Mon Sep 17 00:00:00 2001 From: Daniel Young Lee Date: Tue, 23 Nov 2021 21:47:34 -0800 Subject: [PATCH 1/9] Endpoint annotation on task queue function. --- spec/v1/providers/https.spec.ts | 40 +++++++++++++++++++++++++++++---- src/handler-builder.ts | 2 -- src/providers/https.ts | 30 ++++++++++++++++++++++++- 3 files changed, 65 insertions(+), 7 deletions(-) diff --git a/spec/v1/providers/https.spec.ts b/spec/v1/providers/https.spec.ts index ddc9c670d..d44871c5a 100644 --- a/spec/v1/providers/https.spec.ts +++ b/spec/v1/providers/https.spec.ts @@ -94,7 +94,7 @@ function runHandler( describe('CloudHttpsBuilder', () => { describe('#onRequest', () => { - it('should return a trigger/endpoint with appropriate values', () => { + it('should return a trigger with appropriate values', () => { const result = https.onRequest((req, resp) => { resp.send(200); }); @@ -130,7 +130,7 @@ describe('CloudHttpsBuilder', () => { describe('handler namespace', () => { describe('#onRequest', () => { - it('should return an empty trigger and endpoint', () => { + it('should return an empty trigger', () => { const result = functions.handler.https.onRequest((req, res) => { res.send(200); }); @@ -140,7 +140,7 @@ describe('handler namespace', () => { }); describe('#onCall', () => { - it('should return an empty trigger and endpoint', () => { + it('should return an empty trigger', () => { const result = functions.handler.https.onCall(() => null); expect(result.__trigger).to.deep.equal({}); expect(result.__endpoint).to.deep.equal({}); @@ -151,6 +151,7 @@ describe('handler namespace', () => { it('should return an empty trigger', () => { const result = functions.handler.https.taskQueue.onEnqueue(() => null); expect(result.__trigger).to.deep.equal({}); + expect(result.__endpoint).to.deep.equal({}); }); }); }); @@ -231,7 +232,7 @@ describe('#onCall', () => { }); describe('#onEnqueue', () => { - it('should return a Trigger with appropriate values', () => { + it('should return a trigger/endpoint with appropriate values', () => { const result = https .taskQueue({ rateLimits: { @@ -248,6 +249,7 @@ describe('#onEnqueue', () => { invoker: 'private', }) .onDispatch(() => {}); + expect(result.__trigger).to.deep.equal({ taskQueueTrigger: { rateLimits: { @@ -264,6 +266,24 @@ describe('#onEnqueue', () => { invoker: ['private'], }, }); + + expect(result.__endpoint).to.deep.equal({ + platform: 'gcfv1', + taskQueueTrigger: { + rateLimits: { + maxBurstSize: 20, + maxConcurrentDispatches: 30, + maxDispatchesPerSecond: 40, + }, + retryConfig: { + maxAttempts: 5, + maxBackoffSeconds: 20, + maxDoublings: 3, + minBackoffSeconds: 5, + }, + invoker: ['private'], + }, + }); }); it('should allow both region and runtime options to be set', () => { @@ -286,6 +306,18 @@ describe('#onEnqueue', () => { }, }, }); + + expect(fn.__endpoint).to.deep.equal({ + platform: 'gcfv1', + region: ['us-east1'], + availableMemoryMb: 256, + timeoutSeconds: 90, + taskQueueTrigger: { + retryConfig: { + maxAttempts: 5, + }, + }, + }); }); it('has a .run method', async () => { diff --git a/src/handler-builder.ts b/src/handler-builder.ts index d1286f275..7ebf78f15 100644 --- a/src/handler-builder.ts +++ b/src/handler-builder.ts @@ -70,7 +70,6 @@ export class HandlerBuilder { ): HttpsFunction => { const func = https._onRequestWithOptions(handler, {}); func.__trigger = {}; - func.__endpoint = {}; return func; }, onCall: ( @@ -81,7 +80,6 @@ export class HandlerBuilder { ): HttpsFunction => { const func = https._onCallWithOptions(handler, {}); func.__trigger = {}; - func.__endpoint = {}; return func; }, /** @hidden */ diff --git a/src/providers/https.ts b/src/providers/https.ts index 1b5cfaa4d..40dc6af8e 100644 --- a/src/providers/https.ts +++ b/src/providers/https.ts @@ -28,7 +28,12 @@ import { optionsToTrigger, Runnable, } from '../cloud-functions'; -import {convertIfPresent, convertInvoker, copyIfPresent} from '../common/encoding'; +import { + convertIfPresent, + convertInvoker, + copyIfPresent, +} from '../common/encoding'; +import { ManifestEndpoint } from '../common/manifest'; import { CallableContext, FunctionsErrorCode, @@ -97,6 +102,7 @@ export interface TaskQueueOptions { export interface TaskQueueFunction { (req: Request, res: express.Response): Promise; __trigger: unknown; + __endpoint: ManifestEndpoint; run(data: any, context: TaskContext): void | Promise; } @@ -132,6 +138,28 @@ export class TaskQueueBuilder { convertInvoker ); + func.__endpoint = { + platform: 'gcfv1', + ...optionsToEndpoint(this.depOpts), + taskQueueTrigger: {}, + }; + copyIfPresent(func.__endpoint.taskQueueTrigger, this.tqOpts, 'retryConfig'); + copyIfPresent(func.__endpoint.taskQueueTrigger, this.tqOpts, 'rateLimits'); + convertIfPresent( + func.__endpoint.taskQueueTrigger, + this.tqOpts, + 'invoker', + 'invoker', + convertInvoker + ); + + func.__requiredAPIs = [ + { + api: 'cloudtasks.googleapis.com', + reason: 'Needed for v1 task queue functions', + }, + ]; + func.run = handler; return func; From 58f05dc6c5ff05afda4ef14651a8b39917ea1804 Mon Sep 17 00:00:00 2001 From: Daniel Young Lee Date: Tue, 23 Nov 2021 21:50:10 -0800 Subject: [PATCH 2/9] Loose ends on endpoint annotation on event triggers. --- src/cloud-functions.ts | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/cloud-functions.ts b/src/cloud-functions.ts index 5f7823c2f..838cca9ba 100644 --- a/src/cloud-functions.ts +++ b/src/cloud-functions.ts @@ -446,7 +446,7 @@ export function makeCloudFunction({ Object.defineProperty(cloudFunction, '__endpoint', { get: () => { if (triggerResource() == null) { - return undefined; + return {}; } const endpoint: ManifestEndpoint = { @@ -474,6 +474,19 @@ export function makeCloudFunction({ }, }); + if (options.schedule) { + cloudFunction.__requiredAPIs = [ + { + api: 'pubsub.googleapis.com', + reason: 'Needed for v1 scheduled functions.', + }, + { + api: 'cloudscheduler.googleapis.com', + reason: 'Needed for v1 scheduled functions.', + }, + ]; + } + cloudFunction.run = handler || contextOnlyHandler; return cloudFunction; } @@ -609,15 +622,14 @@ export function optionsToEndpoint( 'serviceAccount', (sa) => sa ); - if (options.vpcConnector) { - const vpc: ManifestEndpoint['vpc'] = { connector: options.vpcConnector }; + if (options?.vpcConnector) { + endpoint.vpc = { connector: options.vpcConnector }; convertIfPresent( - vpc, + endpoint.vpc, options, 'egressSettings', 'vpcConnectorEgressSettings' ); - endpoint.vpc = vpc; } convertIfPresent(endpoint, options, 'availableMemoryMb', 'memory', (mem) => { const memoryLookup = { From 24e1d42006426650c3352237ed025c06b6a9bac8 Mon Sep 17 00:00:00 2001 From: Daniel Young Lee Date: Tue, 23 Nov 2021 21:50:35 -0800 Subject: [PATCH 3/9] Reformat + endpoint annotations for handlerr namespace. --- src/cloud-functions.ts | 10 +++---- src/handler-builder.ts | 65 ++++++++++++++++++++++-------------------- 2 files changed, 39 insertions(+), 36 deletions(-) diff --git a/src/cloud-functions.ts b/src/cloud-functions.ts index 838cca9ba..a3d74a749 100644 --- a/src/cloud-functions.ts +++ b/src/cloud-functions.ts @@ -477,14 +477,14 @@ export function makeCloudFunction({ if (options.schedule) { cloudFunction.__requiredAPIs = [ { - api: 'pubsub.googleapis.com', - reason: 'Needed for v1 scheduled functions.', + api: "pubsub.googleapis.com", + reason: "Needed for v1 scheduled functions." }, { - api: 'cloudscheduler.googleapis.com', - reason: 'Needed for v1 scheduled functions.', + api: "cloudscheduler.googleapis.com", + reason: "Needed for v1 scheduled functions." }, - ]; + ] } cloudFunction.run = handler || contextOnlyHandler; diff --git a/src/handler-builder.ts b/src/handler-builder.ts index 7ebf78f15..bc834ea25 100644 --- a/src/handler-builder.ts +++ b/src/handler-builder.ts @@ -48,16 +48,16 @@ export class HandlerBuilder { /** * 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) => { ... }) @@ -70,6 +70,7 @@ export class HandlerBuilder { ): HttpsFunction => { const func = https._onRequestWithOptions(handler, {}); func.__trigger = {}; + func.__endpoint = {}; return func; }, onCall: ( @@ -80,6 +81,7 @@ export class HandlerBuilder { ): HttpsFunction => { const func = https._onCallWithOptions(handler, {}); func.__trigger = {}; + func.__endpoint = {}; return func; }, /** @hidden */ @@ -94,6 +96,7 @@ export class HandlerBuilder { const builder = new https.TaskQueueBuilder(); const func = builder.onDispatch(handler); func.__trigger = {}; + func.__endpoint = {}; return func; }, }; @@ -103,21 +106,21 @@ export class HandlerBuilder { /** * 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 @@ -150,21 +153,21 @@ export class HandlerBuilder { /** * 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 @@ -172,7 +175,7 @@ export class HandlerBuilder { * exports.myFunction = functions.handler.firestore.document.onDelete((snap, context) => * { ... }) * ``` - + * `document.onWrite` handles the creation, update, or deletion of documents. * * @example @@ -201,7 +204,7 @@ export class HandlerBuilder { * 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() => { ... }) @@ -222,9 +225,9 @@ export class HandlerBuilder { /** * 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) => { ... }) @@ -240,21 +243,21 @@ export class HandlerBuilder { /** * 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 @@ -262,7 +265,7 @@ export class HandlerBuilder { * exports.myFunction = functions.handler.storage.object.onFinalize((object) => * { ... }) * ``` - + * `object.onMetadataUpdate` handles changes to the metadata of existing Storage objects. * * @example @@ -285,16 +288,16 @@ export class HandlerBuilder { /** * 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. - * + * + * `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) => { ... }) @@ -313,21 +316,21 @@ export class HandlerBuilder { /** * 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 { @@ -341,7 +344,7 @@ export class HandlerBuilder { * 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) => { ... }) From 27c5fca99fe9c738bc1158f6049931ed825e322a Mon Sep 17 00:00:00 2001 From: Daniel Young Lee Date: Wed, 24 Nov 2021 11:27:15 -0800 Subject: [PATCH 4/9] Add more unit tests. --- spec/v1/providers/analytics.spec.ts | 21 ++- spec/v1/providers/auth.spec.ts | 40 ++++- spec/v1/providers/database.spec.ts | 228 +++++++++++++++++++++------- spec/v1/providers/firestore.spec.ts | 71 +++++---- spec/v1/providers/pubsub.spec.ts | 4 + 5 files changed, 274 insertions(+), 90 deletions(-) diff --git a/spec/v1/providers/analytics.spec.ts b/spec/v1/providers/analytics.spec.ts index d92244439..43cedf3f1 100644 --- a/spec/v1/providers/analytics.spec.ts +++ b/spec/v1/providers/analytics.spec.ts @@ -50,10 +50,14 @@ describe('Analytics Functions', () => { 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 TriggerDefinition with appropriate values', () => { + it('should return a trigger/endpoint with appropriate values', () => { const cloudFunction = analytics.event('first_open').onLog(() => null); expect(cloudFunction.__trigger).to.deep.equal({ @@ -64,6 +68,18 @@ describe('Analytics Functions', () => { service: 'app-measurement.com', }, }); + + expect(cloudFunction.__endpoint).to.deep.equal({ + platform: 'gcfv1', + eventTrigger: { + eventFilters: { + resource: 'projects/project1/events/first_open', + }, + eventType: + 'providers/google.firebase.analytics/eventTypes/event.log', + retry: false, + }, + }); }); }); @@ -305,11 +321,12 @@ describe('Analytics Functions', () => { describe('handler namespace', () => { describe('#onLog', () => { - it('should return an empty trigger', () => { + 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.deep.equal({}); }); it('should handle an event with the appropriate fields', () => { diff --git a/spec/v1/providers/auth.spec.ts b/spec/v1/providers/auth.spec.ts index e22052573..5ac4254a8 100644 --- a/spec/v1/providers/auth.spec.ts +++ b/spec/v1/providers/auth.spec.ts @@ -76,11 +76,16 @@ describe('Auth Functions', () => { 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('#onCreate', () => { - it('should return a TriggerDefinition with appropriate values', () => { + it('should return a trigger/endpoint with appropriate values', () => { const cloudFunction = auth.user().onCreate(() => null); + expect(cloudFunction.__trigger).to.deep.equal({ eventTrigger: { eventType: 'providers/firebase.auth/eventTypes/user.create', @@ -88,12 +93,24 @@ describe('Auth Functions', () => { service: 'firebaseauth.googleapis.com', }, }); + + expect(cloudFunction.__endpoint).to.deep.equal({ + platform: 'gcfv1', + eventTrigger: { + eventFilters: { + resource: 'projects/project1', + }, + eventType: 'providers/firebase.auth/eventTypes/user.create', + retry: false, + }, + }); }); }); describe('#onDelete', () => { - it('should return a TriggerDefinition with appropriate values', () => { + it('should return a trigger/endpoint with appropriate values', () => { const cloudFunction = auth.user().onDelete(handler); + expect(cloudFunction.__trigger).to.deep.equal({ eventTrigger: { eventType: 'providers/firebase.auth/eventTypes/user.delete', @@ -101,6 +118,17 @@ describe('Auth Functions', () => { service: 'firebaseauth.googleapis.com', }, }); + + expect(cloudFunction.__endpoint).to.deep.equal({ + platform: 'gcfv1', + eventTrigger: { + eventFilters: { + resource: 'projects/project1', + }, + eventType: 'providers/firebase.auth/eventTypes/user.delete', + retry: false, + }, + }); }); }); @@ -198,6 +226,11 @@ describe('Auth Functions', () => { 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.deep.equal({}); + }); }); describe('#onDelete', () => { @@ -205,12 +238,13 @@ describe('Auth Functions', () => { (data: firebase.auth.UserRecord) => data ); - it('should return an empty trigger', () => { + it('should return an empty trigger/endpoint', () => { const handler = (user: firebase.auth.UserRecord) => { return Promise.resolve(); }; const cloudFunction = functions.handler.auth.user.onDelete(handler); expect(cloudFunction.__trigger).to.deep.equal({}); + expect(cloudFunction.__endpoint).to.deep.equal({}); }); it('should handle wire format as of v5.0.0 of firebase-admin', () => { diff --git a/spec/v1/providers/database.spec.ts b/spec/v1/providers/database.spec.ts index d49c27854..790f97f94 100644 --- a/spec/v1/providers/database.spec.ts +++ b/spec/v1/providers/database.spec.ts @@ -56,21 +56,34 @@ describe('Database Functions', () => { 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('#onWrite()', () => { - it('should return "ref.write" as the event type', () => { - const eventType = database.ref('foo').onWrite(() => null).__trigger - .eventTrigger.eventType; - expect(eventType).to.eq( - 'providers/google.firebase.database/eventTypes/ref.write' - ); - }); + it('should return a trigger/endpoint with appropriate values', () => { + const func = database.ref('foo').onWrite(() => null); + + expect(func.__trigger).to.deep.equal({ + eventTrigger: { + eventType: 'providers/google.firebase.database/eventTypes/ref.write', + resource: 'projects/_/instances/subdomain/refs/foo', + service: 'firebaseio.com', + }, + }); - it('should construct a proper resource path', () => { - const resource = database.ref('foo').onWrite(() => null).__trigger - .eventTrigger.resource; - expect(resource).to.eq('projects/_/instances/subdomain/refs/foo'); + expect(func.__endpoint).to.deep.equal({ + platform: "gcfv1", + eventTrigger: { + eventFilters: { + resource: 'projects/_/instances/subdomain/refs/foo', + }, + eventType: 'providers/google.firebase.database/eventTypes/ref.write', + retry: false, + }, + }); }); it('should let developers choose a database instance', () => { @@ -78,8 +91,25 @@ describe('Database Functions', () => { .instance('custom') .ref('foo') .onWrite(() => null); - const resource = func.__trigger.eventTrigger.resource; - expect(resource).to.eq('projects/_/instances/custom/refs/foo'); + + expect(func.__trigger).to.deep.equal({ + eventTrigger: { + eventType: 'providers/google.firebase.database/eventTypes/ref.write', + resource: 'projects/_/instances/custom/refs/foo', + service: 'firebaseio.com', + }, + }); + + expect(func.__endpoint).to.deep.equal({ + platform: "gcfv1", + eventTrigger: { + eventFilters: { + resource: 'projects/_/instances/custom/refs/foo', + }, + eventType: 'providers/google.firebase.database/eventTypes/ref.write', + retry: false, + }, + }); }); it('should return a handler that emits events with a proper DataSnapshot', () => { @@ -107,18 +137,27 @@ describe('Database Functions', () => { }); describe('#onCreate()', () => { - it('should return "ref.create" as the event type', () => { - const eventType = database.ref('foo').onCreate(() => null).__trigger - .eventTrigger.eventType; - expect(eventType).to.eq( - 'providers/google.firebase.database/eventTypes/ref.create' - ); - }); + it('should return a trigger/endpoint with appropriate values', () => { + const func = database.ref('foo').onCreate(() => null); + + expect(func.__trigger).to.deep.equal({ + eventTrigger: { + eventType: 'providers/google.firebase.database/eventTypes/ref.create', + resource: 'projects/_/instances/subdomain/refs/foo', + service: 'firebaseio.com', + }, + }); - it('should construct a proper resource path', () => { - const resource = database.ref('foo').onCreate(() => null).__trigger - .eventTrigger.resource; - expect(resource).to.eq('projects/_/instances/subdomain/refs/foo'); + expect(func.__endpoint).to.deep.equal({ + platform: "gcfv1", + eventTrigger: { + eventFilters: { + resource: 'projects/_/instances/subdomain/refs/foo', + }, + eventType: 'providers/google.firebase.database/eventTypes/ref.create', + retry: false, + }, + }); }); it('should let developers choose a database instance', () => { @@ -126,8 +165,25 @@ describe('Database Functions', () => { .instance('custom') .ref('foo') .onCreate(() => null); - const resource = func.__trigger.eventTrigger.resource; - expect(resource).to.eq('projects/_/instances/custom/refs/foo'); + + expect(func.__trigger).to.deep.equal({ + eventTrigger: { + eventType: 'providers/google.firebase.database/eventTypes/ref.create', + resource: 'projects/_/instances/custom/refs/foo', + service: 'firebaseio.com', + }, + }); + + expect(func.__endpoint).to.deep.equal({ + platform: "gcfv1", + eventTrigger: { + eventFilters: { + resource: 'projects/_/instances/custom/refs/foo', + }, + eventType: 'providers/google.firebase.database/eventTypes/ref.create', + retry: false, + }, + }); }); it('should return a handler that emits events with a proper DataSnapshot', () => { @@ -156,18 +212,27 @@ describe('Database Functions', () => { }); describe('#onUpdate()', () => { - it('should return "ref.update" as the event type', () => { - const eventType = database.ref('foo').onUpdate(() => null).__trigger - .eventTrigger.eventType; - expect(eventType).to.eq( - 'providers/google.firebase.database/eventTypes/ref.update' - ); - }); + it('should return a trigger/endpoint with appropriate values', () => { + const func = database.ref('foo').onUpdate(() => null); + + expect(func.__trigger).to.deep.equal({ + eventTrigger: { + eventType: 'providers/google.firebase.database/eventTypes/ref.update', + resource: 'projects/_/instances/subdomain/refs/foo', + service: 'firebaseio.com', + }, + }); - it('should construct a proper resource path', () => { - const resource = database.ref('foo').onUpdate(() => null).__trigger - .eventTrigger.resource; - expect(resource).to.eq('projects/_/instances/subdomain/refs/foo'); + expect(func.__endpoint).to.deep.equal({ + platform: "gcfv1", + eventTrigger: { + eventFilters: { + resource: 'projects/_/instances/subdomain/refs/foo', + }, + eventType: 'providers/google.firebase.database/eventTypes/ref.update', + retry: false, + }, + }); }); it('should let developers choose a database instance', () => { @@ -175,8 +240,25 @@ describe('Database Functions', () => { .instance('custom') .ref('foo') .onUpdate(() => null); - const resource = func.__trigger.eventTrigger.resource; - expect(resource).to.eq('projects/_/instances/custom/refs/foo'); + + expect(func.__trigger).to.deep.equal({ + eventTrigger: { + eventType: 'providers/google.firebase.database/eventTypes/ref.update', + resource: 'projects/_/instances/custom/refs/foo', + service: 'firebaseio.com', + }, + }); + + expect(func.__endpoint).to.deep.equal({ + platform: "gcfv1", + eventTrigger: { + eventFilters: { + resource: 'projects/_/instances/custom/refs/foo', + }, + eventType: 'providers/google.firebase.database/eventTypes/ref.update', + retry: false, + }, + }); }); it('should return a handler that emits events with a proper DataSnapshot', () => { @@ -205,27 +287,53 @@ describe('Database Functions', () => { }); describe('#onDelete()', () => { - it('should return "ref.delete" as the event type', () => { - const eventType = database.ref('foo').onDelete(() => null).__trigger - .eventTrigger.eventType; - expect(eventType).to.eq( - 'providers/google.firebase.database/eventTypes/ref.delete' - ); - }); + it('should return a trigger/endpoint with appropriate values', () => { + const func = database.ref('foo').onDelete(() => null); + + expect(func.__trigger).to.deep.equal({ + eventTrigger: { + eventType: 'providers/google.firebase.database/eventTypes/ref.delete', + resource: 'projects/_/instances/subdomain/refs/foo', + service: 'firebaseio.com', + }, + }); - it('should construct a proper resource path', () => { - const resource = database.ref('foo').onDelete(() => null).__trigger - .eventTrigger.resource; - expect(resource).to.eq('projects/_/instances/subdomain/refs/foo'); - }); + expect(func.__endpoint).to.deep.equal({ + platform: "gcfv1", + eventTrigger: { + eventFilters: { + resource: 'projects/_/instances/subdomain/refs/foo', + }, + eventType: 'providers/google.firebase.database/eventTypes/ref.delete', + retry: false, + }, + }); + }) it('should let developers choose a database instance', () => { const func = database .instance('custom') .ref('foo') .onDelete(() => null); - const resource = func.__trigger.eventTrigger.resource; - expect(resource).to.eq('projects/_/instances/custom/refs/foo'); + + expect(func.__trigger).to.deep.equal({ + eventTrigger: { + eventType: 'providers/google.firebase.database/eventTypes/ref.delete', + resource: 'projects/_/instances/custom/refs/foo', + service: 'firebaseio.com', + }, + }); + + expect(func.__endpoint).to.deep.equal({ + platform: "gcfv1", + eventTrigger: { + eventFilters: { + resource: 'projects/_/instances/custom/refs/foo', + }, + eventType: 'providers/google.firebase.database/eventTypes/ref.delete', + retry: false, + }, + }); }); it('should return a handler that emits events with a proper DataSnapshot', () => { @@ -259,6 +367,7 @@ describe('Database Functions', () => { it('correctly sets trigger to {}', () => { const cf = functions.handler.database.ref.onWrite(() => null); expect(cf.__trigger).to.deep.equal({}); + expect(cf.__endpoint).to.deep.equal({}); }); it('should be able to use the instance entry point', () => { @@ -266,6 +375,7 @@ describe('Database Functions', () => { () => null ); expect(func.__trigger).to.deep.equal({}); + expect(func.__endpoint).to.deep.equal({}); }); it('should return a handler that emits events with a proper DataSnapshot', () => { @@ -296,7 +406,8 @@ describe('Database Functions', () => { describe('#onCreate()', () => { it('correctly sets trigger to {}', () => { const cf = functions.handler.database.ref.onCreate(() => null); - return expect(cf.__trigger).to.deep.equal({}); + expect(cf.__trigger).to.deep.equal({}); + expect(cf.__endpoint).to.deep.equal({}); }); it('should be able to use the instance entry point', () => { @@ -304,6 +415,7 @@ describe('Database Functions', () => { () => null ); expect(func.__trigger).to.deep.equal({}); + expect(func.__endpoint).to.deep.equal({}); }); it('should return a handler that emits events with a proper DataSnapshot', () => { @@ -333,7 +445,8 @@ describe('Database Functions', () => { describe('#onUpdate()', () => { it('correctly sets trigger to {}', () => { const cf = functions.handler.database.ref.onUpdate(() => null); - return expect(cf.__trigger).to.deep.equal({}); + expect(cf.__trigger).to.deep.equal({}); + expect(cf.__endpoint).to.deep.equal({}); }); it('should be able to use the instance entry point', () => { @@ -341,6 +454,7 @@ describe('Database Functions', () => { () => null ); expect(func.__trigger).to.deep.equal({}); + expect(func.__endpoint).to.deep.equal({}); }); it('should return a handler that emits events with a proper DataSnapshot', () => { @@ -370,7 +484,8 @@ describe('Database Functions', () => { describe('#onDelete()', () => { it('correctly sets trigger to {}', () => { const cf = functions.handler.database.ref.onDelete(() => null); - return expect(cf.__trigger).to.deep.equal({}); + expect(cf.__trigger).to.deep.equal({}); + expect(cf.__endpoint).to.deep.equal({}); }); it('should be able to use the instance entry point', () => { @@ -378,6 +493,7 @@ describe('Database Functions', () => { () => null ); expect(func.__trigger).to.deep.equal({}); + expect(func.__trigger).to.deep.equal({}); }); it('should return a handler that emits events with a proper DataSnapshot', () => { diff --git a/spec/v1/providers/firestore.spec.ts b/spec/v1/providers/firestore.spec.ts index 25cffaf6c..a09b8d19f 100644 --- a/spec/v1/providers/firestore.spec.ts +++ b/spec/v1/providers/firestore.spec.ts @@ -103,6 +103,19 @@ describe('Firestore Functions', () => { }; } + function expectedEndpoint(resource: string, eventType: string) { + return { + platform: "gcfv1", + eventTrigger: { + eventFilters: { + resource, + }, + eventType: `providers/cloud.firestore/eventTypes/${eventType}`, + retry: false, + }, + }; + } + before(() => { process.env.GCLOUD_PROJECT = 'project1'; }); @@ -117,9 +130,14 @@ describe('Firestore Functions', () => { const cloudFunction = firestore .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 namespaces', () => { @@ -129,9 +147,14 @@ describe('Firestore Functions', () => { .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', () => { @@ -140,9 +163,14 @@ describe('Firestore Functions', () => { .database('myDB') .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 both custom database and namespace', () => { @@ -153,9 +181,14 @@ describe('Firestore Functions', () => { .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 both region and runtime options to be set', () => { @@ -171,36 +204,10 @@ describe('Firestore Functions', () => { 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'); - }); - - it('onCreate should have the "document.create" eventType', () => { - const resource = - 'projects/project1/databases/(default)/documents/users/{uid}'; - const eventType = firestore.document('users/{uid}').onCreate(() => null) - .__trigger.eventTrigger.eventType; - expect(eventType).to.eq( - expectedTrigger(resource, 'document.create').eventTrigger.eventType - ); - }); - it('onUpdate should have the "document.update" eventType', () => { - const resource = - 'projects/project1/databases/(default)/documents/users/{uid}'; - const eventType = firestore.document('users/{uid}').onUpdate(() => null) - .__trigger.eventTrigger.eventType; - expect(eventType).to.eq( - expectedTrigger(resource, 'document.update').eventTrigger.eventType - ); - }); - - it('onDelete should have the "document.delete" eventType', () => { - const resource = - 'projects/project1/databases/(default)/documents/users/{uid}'; - const eventType = firestore.document('users/{uid}').onDelete(() => null) - .__trigger.eventTrigger.eventType; - expect(eventType).to.eq( - expectedTrigger(resource, 'document.delete').eventTrigger.eventType - ); + 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); }); }); @@ -217,6 +224,12 @@ describe('Firestore Functions', () => { ).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', () => { const cf = firestore.document('input').onCreate(() => null); expect(cf.run).to.not.throw(Error); diff --git a/spec/v1/providers/pubsub.spec.ts b/spec/v1/providers/pubsub.spec.ts index 1ab8c7ba4..6901c0e64 100644 --- a/spec/v1/providers/pubsub.spec.ts +++ b/spec/v1/providers/pubsub.spec.ts @@ -85,6 +85,10 @@ describe('Pubsub Functions', () => { 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('#onPublish', () => { From 2439ad196d5d111d441b5161e751ac3c068b3f57 Mon Sep 17 00:00:00 2001 From: Daniel Young Lee Date: Wed, 24 Nov 2021 11:55:45 -0800 Subject: [PATCH 5/9] Refactor to simplify test code. --- spec/v1/providers/auth.spec.ts | 81 +++++----- spec/v1/providers/database.spec.ts | 241 ++++++++++++----------------- 2 files changed, 147 insertions(+), 175 deletions(-) diff --git a/spec/v1/providers/auth.spec.ts b/spec/v1/providers/auth.spec.ts index 5ac4254a8..2337d5632 100644 --- a/spec/v1/providers/auth.spec.ts +++ b/spec/v1/providers/auth.spec.ts @@ -51,12 +51,37 @@ describe('Auth Functions', () => { }; 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 { + platform: 'gcfv1', + eventTrigger: { + eventFilters: { + resource: `projects/${project}`, + }, + eventType: `providers/firebase.auth/eventTypes/${eventType}`, + retry: false, + }, + }; + } + const handler = (user: firebase.auth.UserRecord) => { return Promise.resolve(); }; + const project = 'project1'; + before(() => { - process.env.GCLOUD_PROJECT = 'project1'; + process.env.GCLOUD_PROJECT = project; }); after(() => { @@ -86,24 +111,13 @@ describe('Auth Functions', () => { it('should return a trigger/endpoint with appropriate values', () => { const cloudFunction = auth.user().onCreate(() => null); - expect(cloudFunction.__trigger).to.deep.equal({ - eventTrigger: { - eventType: 'providers/firebase.auth/eventTypes/user.create', - resource: 'projects/project1', - service: 'firebaseauth.googleapis.com', - }, - }); - - expect(cloudFunction.__endpoint).to.deep.equal({ - platform: 'gcfv1', - eventTrigger: { - eventFilters: { - resource: 'projects/project1', - }, - eventType: 'providers/firebase.auth/eventTypes/user.create', - retry: false, - }, - }); + expect(cloudFunction.__trigger).to.deep.equal( + expectedTrigger(project, 'user.create') + ); + + expect(cloudFunction.__endpoint).to.deep.equal( + expectedEndpoint(project, 'user.create') + ); }); }); @@ -111,24 +125,13 @@ describe('Auth Functions', () => { it('should return a trigger/endpoint with appropriate values', () => { const cloudFunction = auth.user().onDelete(handler); - expect(cloudFunction.__trigger).to.deep.equal({ - eventTrigger: { - eventType: 'providers/firebase.auth/eventTypes/user.delete', - resource: 'projects/project1', - service: 'firebaseauth.googleapis.com', - }, - }); - - expect(cloudFunction.__endpoint).to.deep.equal({ - platform: 'gcfv1', - eventTrigger: { - eventFilters: { - resource: 'projects/project1', - }, - eventType: 'providers/firebase.auth/eventTypes/user.delete', - retry: false, - }, - }); + expect(cloudFunction.__trigger).to.deep.equal( + expectedTrigger(project, 'user.delete') + ); + + expect(cloudFunction.__endpoint).to.deep.equal( + expectedEndpoint(project, 'user.delete') + ); }); }); @@ -271,6 +274,10 @@ describe('Auth Functions', () => { 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); + }); + it('should not throw when #run is called', () => { const cf = auth.user().onCreate(() => null); expect(cf.run).to.not.throw(Error); diff --git a/spec/v1/providers/database.spec.ts b/spec/v1/providers/database.spec.ts index 790f97f94..069ee57ba 100644 --- a/spec/v1/providers/database.spec.ts +++ b/spec/v1/providers/database.spec.ts @@ -31,6 +31,29 @@ 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 { + platform: 'gcfv1', + eventTrigger: { + eventFilters: { + resource, + }, + eventType: `providers/google.firebase.database/eventTypes/${eventType}`, + retry: false, + }, + }; + } + before(() => { (config as any).firebaseConfigCache = { databaseURL: 'https://subdomain.apse.firebasedatabase.app', @@ -66,24 +89,19 @@ describe('Database Functions', () => { it('should return a trigger/endpoint with appropriate values', () => { const func = database.ref('foo').onWrite(() => null); - expect(func.__trigger).to.deep.equal({ - eventTrigger: { - eventType: 'providers/google.firebase.database/eventTypes/ref.write', - resource: 'projects/_/instances/subdomain/refs/foo', - service: 'firebaseio.com', - }, - }); + expect(func.__trigger).to.deep.equal( + expectedTrigger( + 'projects/_/instances/subdomain/refs/foo', + 'ref.write' + ) + ); - expect(func.__endpoint).to.deep.equal({ - platform: "gcfv1", - eventTrigger: { - eventFilters: { - resource: 'projects/_/instances/subdomain/refs/foo', - }, - eventType: 'providers/google.firebase.database/eventTypes/ref.write', - retry: false, - }, - }); + expect(func.__endpoint).to.deep.equal( + expectedEndpoint( + 'projects/_/instances/subdomain/refs/foo', + 'ref.write' + ) + ); }); it('should let developers choose a database instance', () => { @@ -92,24 +110,13 @@ describe('Database Functions', () => { .ref('foo') .onWrite(() => null); - expect(func.__trigger).to.deep.equal({ - eventTrigger: { - eventType: 'providers/google.firebase.database/eventTypes/ref.write', - resource: 'projects/_/instances/custom/refs/foo', - service: 'firebaseio.com', - }, - }); + expect(func.__trigger).to.deep.equal( + expectedTrigger('projects/_/instances/custom/refs/foo', 'ref.write') + ); - expect(func.__endpoint).to.deep.equal({ - platform: "gcfv1", - eventTrigger: { - eventFilters: { - resource: 'projects/_/instances/custom/refs/foo', - }, - eventType: 'providers/google.firebase.database/eventTypes/ref.write', - retry: false, - }, - }); + 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', () => { @@ -140,24 +147,19 @@ describe('Database Functions', () => { it('should return a trigger/endpoint with appropriate values', () => { const func = database.ref('foo').onCreate(() => null); - expect(func.__trigger).to.deep.equal({ - eventTrigger: { - eventType: 'providers/google.firebase.database/eventTypes/ref.create', - resource: 'projects/_/instances/subdomain/refs/foo', - service: 'firebaseio.com', - }, - }); + expect(func.__trigger).to.deep.equal( + expectedTrigger( + 'projects/_/instances/subdomain/refs/foo', + 'ref.create' + ) + ); - expect(func.__endpoint).to.deep.equal({ - platform: "gcfv1", - eventTrigger: { - eventFilters: { - resource: 'projects/_/instances/subdomain/refs/foo', - }, - eventType: 'providers/google.firebase.database/eventTypes/ref.create', - retry: false, - }, - }); + expect(func.__endpoint).to.deep.equal( + expectedEndpoint( + 'projects/_/instances/subdomain/refs/foo', + 'ref.create' + ) + ); }); it('should let developers choose a database instance', () => { @@ -166,24 +168,13 @@ describe('Database Functions', () => { .ref('foo') .onCreate(() => null); - expect(func.__trigger).to.deep.equal({ - eventTrigger: { - eventType: 'providers/google.firebase.database/eventTypes/ref.create', - resource: 'projects/_/instances/custom/refs/foo', - service: 'firebaseio.com', - }, - }); + expect(func.__trigger).to.deep.equal( + expectedTrigger('projects/_/instances/custom/refs/foo', 'ref.create') + ); - expect(func.__endpoint).to.deep.equal({ - platform: "gcfv1", - eventTrigger: { - eventFilters: { - resource: 'projects/_/instances/custom/refs/foo', - }, - eventType: 'providers/google.firebase.database/eventTypes/ref.create', - retry: false, - }, - }); + expect(func.__endpoint).to.deep.equal( + expectedEndpoint('projects/_/instances/custom/refs/foo', 'ref.create') + ); }); it('should return a handler that emits events with a proper DataSnapshot', () => { @@ -215,24 +206,19 @@ describe('Database Functions', () => { it('should return a trigger/endpoint with appropriate values', () => { const func = database.ref('foo').onUpdate(() => null); - expect(func.__trigger).to.deep.equal({ - eventTrigger: { - eventType: 'providers/google.firebase.database/eventTypes/ref.update', - resource: 'projects/_/instances/subdomain/refs/foo', - service: 'firebaseio.com', - }, - }); + expect(func.__trigger).to.deep.equal( + expectedTrigger( + 'projects/_/instances/subdomain/refs/foo', + 'ref.update' + ) + ); - expect(func.__endpoint).to.deep.equal({ - platform: "gcfv1", - eventTrigger: { - eventFilters: { - resource: 'projects/_/instances/subdomain/refs/foo', - }, - eventType: 'providers/google.firebase.database/eventTypes/ref.update', - retry: false, - }, - }); + expect(func.__endpoint).to.deep.equal( + expectedEndpoint( + 'projects/_/instances/subdomain/refs/foo', + 'ref.update' + ) + ); }); it('should let developers choose a database instance', () => { @@ -241,24 +227,13 @@ describe('Database Functions', () => { .ref('foo') .onUpdate(() => null); - expect(func.__trigger).to.deep.equal({ - eventTrigger: { - eventType: 'providers/google.firebase.database/eventTypes/ref.update', - resource: 'projects/_/instances/custom/refs/foo', - service: 'firebaseio.com', - }, - }); + expect(func.__trigger).to.deep.equal( + expectedTrigger('projects/_/instances/custom/refs/foo', 'ref.update') + ); - expect(func.__endpoint).to.deep.equal({ - platform: "gcfv1", - eventTrigger: { - eventFilters: { - resource: 'projects/_/instances/custom/refs/foo', - }, - eventType: 'providers/google.firebase.database/eventTypes/ref.update', - retry: false, - }, - }); + expect(func.__endpoint).to.deep.equal( + expectedEndpoint('projects/_/instances/custom/refs/foo', 'ref.update') + ); }); it('should return a handler that emits events with a proper DataSnapshot', () => { @@ -290,25 +265,20 @@ describe('Database Functions', () => { it('should return a trigger/endpoint with appropriate values', () => { const func = database.ref('foo').onDelete(() => null); - expect(func.__trigger).to.deep.equal({ - eventTrigger: { - eventType: 'providers/google.firebase.database/eventTypes/ref.delete', - resource: 'projects/_/instances/subdomain/refs/foo', - service: 'firebaseio.com', - }, - }); + expect(func.__trigger).to.deep.equal( + expectedTrigger( + 'projects/_/instances/subdomain/refs/foo', + 'ref.delete' + ) + ); - expect(func.__endpoint).to.deep.equal({ - platform: "gcfv1", - eventTrigger: { - eventFilters: { - resource: 'projects/_/instances/subdomain/refs/foo', - }, - eventType: 'providers/google.firebase.database/eventTypes/ref.delete', - retry: false, - }, - }); - }) + expect(func.__endpoint).to.deep.equal( + expectedEndpoint( + 'projects/_/instances/subdomain/refs/foo', + 'ref.delete' + ) + ); + }); it('should let developers choose a database instance', () => { const func = database @@ -316,24 +286,13 @@ describe('Database Functions', () => { .ref('foo') .onDelete(() => null); - expect(func.__trigger).to.deep.equal({ - eventTrigger: { - eventType: 'providers/google.firebase.database/eventTypes/ref.delete', - resource: 'projects/_/instances/custom/refs/foo', - service: 'firebaseio.com', - }, - }); + expect(func.__trigger).to.deep.equal( + expectedTrigger('projects/_/instances/custom/refs/foo', 'ref.delete') + ); - expect(func.__endpoint).to.deep.equal({ - platform: "gcfv1", - eventTrigger: { - eventFilters: { - resource: 'projects/_/instances/custom/refs/foo', - }, - eventType: 'providers/google.firebase.database/eventTypes/ref.delete', - retry: false, - }, - }); + 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', () => { @@ -493,7 +452,7 @@ describe('Database Functions', () => { () => null ); expect(func.__trigger).to.deep.equal({}); - expect(func.__trigger).to.deep.equal({}); + expect(func.__endpoint).to.deep.equal({}); }); it('should return a handler that emits events with a proper DataSnapshot', () => { @@ -535,6 +494,12 @@ describe('Database Functions', () => { ).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', () => { const cf = database.ref('/path').onWrite(() => null); expect(cf.run).to.not.throw(Error); From 654dd47905b78761b0ee805596d394a7cbe9964b Mon Sep 17 00:00:00 2001 From: Daniel Young Lee Date: Wed, 24 Nov 2021 15:33:02 -0800 Subject: [PATCH 6/9] Rest of specs. --- spec/v1/providers/firestore.spec.ts | 2 +- spec/v1/providers/pubsub.spec.ts | 99 ++++++++- spec/v1/providers/remoteConfig.spec.ts | 42 ++-- spec/v1/providers/storage.spec.ts | 277 +++++++++++++++---------- spec/v1/providers/testLab.spec.ts | 20 +- 5 files changed, 306 insertions(+), 134 deletions(-) diff --git a/spec/v1/providers/firestore.spec.ts b/spec/v1/providers/firestore.spec.ts index a09b8d19f..1fddda575 100644 --- a/spec/v1/providers/firestore.spec.ts +++ b/spec/v1/providers/firestore.spec.ts @@ -105,7 +105,7 @@ describe('Firestore Functions', () => { function expectedEndpoint(resource: string, eventType: string) { return { - platform: "gcfv1", + platform: 'gcfv1', eventTrigger: { eventFilters: { resource, diff --git a/spec/v1/providers/pubsub.spec.ts b/spec/v1/providers/pubsub.spec.ts index 6901c0e64..ddd65fc60 100644 --- a/spec/v1/providers/pubsub.spec.ts +++ b/spec/v1/providers/pubsub.spec.ts @@ -92,9 +92,10 @@ describe('Pubsub Functions', () => { }); describe('#onPublish', () => { - it('should return a TriggerDefinition with appropriate values', () => { + it('should return a trigger/endpoint with appropriate values', () => { // Pick up project from process.env.GCLOUD_PROJECT const result = pubsub.topic('toppy').onPublish(() => null); + expect(result.__trigger).to.deep.equal({ eventTrigger: { eventType: 'google.pubsub.topic.publish', @@ -102,6 +103,17 @@ describe('Pubsub Functions', () => { service: 'pubsub.googleapis.com', }, }); + + expect(result.__endpoint).to.deep.equal({ + platform: 'gcfv1', + eventTrigger: { + eventType: 'google.pubsub.topic.publish', + eventFilters: { + resource: 'projects/project1/topics/toppy', + }, + retry: false, + }, + }); }); it('should throw with improperly formatted topics', () => { @@ -147,27 +159,38 @@ describe('Pubsub Functions', () => { }); describe('#schedule', () => { - it('should return a trigger with 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({ + schedule: 'every 5 minutes', + }); }); - it('should return a trigger with schedule and timeZone when one is chosen', () => { + 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((context) => null); + expect(result.__trigger.schedule).to.deep.equal({ schedule: 'every 5 minutes', timeZone: 'America/New_York', }); + + expect(result.__endpoint.scheduleTrigger).to.deep.equal({ + schedule: 'every 5 minutes', + timeZone: 'America/New_York', + }); }); - it('should return a trigger with schedule and retry config when called with retryConfig', () => { + it('should return a trigger/endpoint with schedule and retry config when called with retryConfig', () => { const retryConfig = { retryCount: 3, maxRetryDuration: '10 minutes', @@ -179,6 +202,7 @@ describe('Pubsub Functions', () => { .schedule('every 5 minutes') .retryConfig(retryConfig) .onRun(() => null); + expect(result.__trigger.schedule).to.deep.equal({ schedule: 'every 5 minutes', retryConfig, @@ -186,10 +210,18 @@ describe('Pubsub Functions', () => { expect(result.__trigger.labels).to.deep.equal({ 'deployment-scheduled': 'true', }); + + expect(result.__endpoint.scheduleTrigger).to.deep.equal({ + schedule: 'every 5 minutes', + retryConfig, + }); + expect(result.__endpoint.labels).to.deep.equal({ + 'deployment-scheduled': 'true', + }); }); it( - 'should return a trigger with schedule, timeZone and retry config' + + 'should return a trigger/endpoint with schedule, timeZone and retry config' + 'when called with retryConfig and timeout', () => { const retryConfig = { @@ -204,6 +236,7 @@ describe('Pubsub Functions', () => { .timeZone('America/New_York') .retryConfig(retryConfig) .onRun(() => null); + expect(result.__trigger.schedule).to.deep.equal({ schedule: 'every 5 minutes', retryConfig, @@ -212,10 +245,19 @@ describe('Pubsub Functions', () => { expect(result.__trigger.labels).to.deep.equal({ 'deployment-scheduled': 'true', }); + + expect(result.__endpoint.scheduleTrigger).to.deep.equal({ + schedule: 'every 5 minutes', + retryConfig, + timeZone: 'America/New_York', + }); + expect(result.__endpoint.labels).to.deep.equal({ + 'deployment-scheduled': 'true', + }); } ); - it('should return an appropriate trigger when called with region and options', () => { + it('should return an appropriate trigger/endpoint when called with region and options', () => { const result = functions .region('us-east1') .runWith({ @@ -230,9 +272,16 @@ describe('Pubsub Functions', () => { 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({ + schedule: 'every 5 minutes', + }); + expect(result.__endpoint.region).to.deep.equal(['us-east1']); + expect(result.__endpoint.availableMemoryMb).to.deep.equal(256); + expect(result.__endpoint.timeoutSeconds).to.deep.equal(90); }); - it('should return an appropriate trigger when called with region, timeZone, and options', () => { + it('should return an appropriate trigger/endpoint when called with region, timeZone, and options', () => { const result = functions .region('us-east1') .runWith({ @@ -249,9 +298,17 @@ describe('Pubsub Functions', () => { 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({ + schedule: 'every 5 minutes', + timeZone: 'America/New_York', + }); + expect(result.__endpoint.region).to.deep.equal(['us-east1']); + expect(result.__endpoint.availableMemoryMb).to.deep.equal(256); + expect(result.__endpoint.timeoutSeconds).to.deep.equal(90); }); - it('should return an appropriate trigger when called with region, options and retryConfig', () => { + it('should return an appropriate trigger/endpoint when called with region, options and retryConfig', () => { const retryConfig = { retryCount: 3, maxRetryDuration: '10 minutes', @@ -278,9 +335,17 @@ describe('Pubsub Functions', () => { 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({ + schedule: 'every 5 minutes', + retryConfig, + }); + expect(result.__endpoint.region).to.deep.equal(['us-east1']); + expect(result.__endpoint.availableMemoryMb).to.deep.equal(256); + expect(result.__endpoint.timeoutSeconds).to.deep.equal(90); }); - it('should return an appropriate trigger when called with region, options, retryConfig, and timeZone', () => { + it('should return an appropriate trigger/endpoint when called with region, options, retryConfig, and timeZone', () => { const retryConfig = { retryCount: 3, maxRetryDuration: '10 minutes', @@ -309,6 +374,15 @@ describe('Pubsub Functions', () => { 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({ + schedule: 'every 5 minutes', + timeZone: 'America/New_York', + retryConfig, + }); + expect(result.__endpoint.region).to.deep.equal(['us-east1']); + expect(result.__endpoint.availableMemoryMb).to.deep.equal(256); + expect(result.__endpoint.timeoutSeconds).to.deep.equal(90); }); }); }); @@ -319,6 +393,7 @@ describe('Pubsub Functions', () => { it('should return an empty trigger', () => { const result = functions.handler.pubsub.topic.onPublish(() => null); expect(result.__trigger).to.deep.equal({}); + expect(result.__endpoint).to.deep.equal({}); }); it('should properly handle a new-style event', () => { @@ -410,6 +485,12 @@ describe('Pubsub Functions', () => { ).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', () => { const cf = pubsub.topic('toppy').onPublish(() => null); expect(cf.run).to.not.throw(Error); diff --git a/spec/v1/providers/remoteConfig.spec.ts b/spec/v1/providers/remoteConfig.spec.ts index f7221cf99..9497ca01a 100644 --- a/spec/v1/providers/remoteConfig.spec.ts +++ b/spec/v1/providers/remoteConfig.spec.ts @@ -20,13 +20,11 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. import { expect } from 'chai'; -import * as _ from 'lodash'; import { CloudFunction, Event, EventContext, - TriggerAnnotated, } from '../../../src/cloud-functions'; import * as functions from '../../../src/index'; import * as remoteConfig from '../../../src/providers/remoteConfig'; @@ -47,18 +45,6 @@ describe('RemoteConfig Functions', () => { } describe('#onUpdate', () => { - function expectedTrigger(): TriggerAnnotated { - return { - __trigger: { - eventTrigger: { - resource: 'projects/project1', - eventType: 'google.firebase.remoteconfig.update', - service: 'firebaseremoteconfig.googleapis.com', - }, - }, - }; - } - before(() => { process.env.GCLOUD_PROJECT = 'project1'; }); @@ -69,9 +55,25 @@ describe('RemoteConfig Functions', () => { it('should have the correct trigger', () => { const cloudFunction = remoteConfig.onUpdate(() => null); - expect(cloudFunction.__trigger).to.deep.equal( - expectedTrigger().__trigger - ); + + 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({ + platform: 'gcfv1', + eventTrigger: { + eventType: 'google.firebase.remoteconfig.update', + eventFilters: { + resource: 'projects/project1', + }, + retry: false, + }, + }); }); it('should allow both region and runtime options to be set', () => { @@ -86,6 +88,10 @@ describe('RemoteConfig Functions', () => { 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); }); }); @@ -135,7 +141,9 @@ describe('RemoteConfig Functions', () => { const cloudFunction = functions.handler.remoteConfig.onUpdate( () => null ); + expect(cloudFunction.__trigger).to.deep.equal({}); + expect(cloudFunction.__endpoint).to.deep.equal({}); }); it('should correctly unwrap the event', () => { diff --git a/spec/v1/providers/storage.spec.ts b/spec/v1/providers/storage.spec.ts index 02762630d..a5961d94c 100644 --- a/spec/v1/providers/storage.spec.ts +++ b/spec/v1/providers/storage.spec.ts @@ -28,9 +28,34 @@ import * as storage from '../../../src/providers/storage'; 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 { + platform: 'gcfv1', + eventTrigger: { + eventFilters: { + resource: `projects/_/buckets/${bucket}`, + }, + eventType: `google.storage.object.${eventType}`, + retry: false, + }, + }; + } + + const defaultBucket = 'bucket'; + before(() => { (config as any).firebaseConfigCache = { - storageBucket: 'bucket', + storageBucket: defaultBucket, }; }); @@ -51,6 +76,10 @@ describe('Storage Functions', () => { 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('#onArchive', () => { @@ -59,24 +88,26 @@ describe('Storage Functions', () => { .bucket('bucky') .object() .onArchive(() => null); - expect(cloudFunction.__trigger).to.deep.equal({ - eventTrigger: { - eventType: 'google.storage.object.archive', - resource: 'projects/_/buckets/bucky', - service: 'storage.googleapis.com', - }, - }); + + 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.__trigger).to.deep.equal({ - eventTrigger: { - eventType: 'google.storage.object.archive', - resource: 'projects/_/buckets/bucket', - service: 'storage.googleapis.com', - }, - }); + + 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', () => { @@ -85,13 +116,14 @@ describe('Storage Functions', () => { {} ); const result = subjectQualified.onArchive(() => null); - expect(result.__trigger).to.deep.equal({ - eventTrigger: { - eventType: 'google.storage.object.archive', - resource: 'projects/_/buckets/bucky', - service: 'storage.googleapis.com', - }, - }); + + 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', () => { @@ -102,6 +134,14 @@ describe('Storage Functions', () => { .object() .onArchive(() => null).__trigger ).to.throw(Error); + + expect( + () => + storage + .bucket('bad/bucket/format') + .object() + .onArchive(() => null).__endpoint + ).to.throw(Error); }); it('should not mess with media links using non-literal slashes', () => { @@ -139,24 +179,26 @@ describe('Storage Functions', () => { .bucket('bucky') .object() .onDelete(() => null); - expect(cloudFunction.__trigger).to.deep.equal({ - eventTrigger: { - eventType: 'google.storage.object.delete', - resource: 'projects/_/buckets/bucky', - service: 'storage.googleapis.com', - }, - }); + + 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.__trigger).to.deep.equal({ - eventTrigger: { - eventType: 'google.storage.object.delete', - resource: 'projects/_/buckets/bucket', - service: 'storage.googleapis.com', - }, - }); + + 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', () => { @@ -165,23 +207,25 @@ describe('Storage Functions', () => { {} ); const result = subjectQualified.onDelete(() => null); - expect(result.__trigger).to.deep.equal({ - eventTrigger: { - eventType: 'google.storage.object.delete', - resource: 'projects/_/buckets/bucky', - service: 'storage.googleapis.com', - }, - }); + + 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', () => { - expect( - () => - storage - .bucket('bad/bucket/format') - .object() - .onDelete(() => null).__trigger - ).to.throw(Error); + const fn = storage + .bucket('bad/bucket/format') + .object() + .onDelete(() => null); + + expect(() => fn.__trigger).to.throw(Error); + + expect(() => fn.__endpoint).to.throw(Error); }); it('should not mess with media links using non-literal slashes', () => { @@ -219,24 +263,26 @@ describe('Storage Functions', () => { .bucket('bucky') .object() .onFinalize(() => null); - expect(cloudFunction.__trigger).to.deep.equal({ - eventTrigger: { - eventType: 'google.storage.object.finalize', - resource: 'projects/_/buckets/bucky', - service: 'storage.googleapis.com', - }, - }); + + 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.__trigger).to.deep.equal({ - eventTrigger: { - eventType: 'google.storage.object.finalize', - resource: 'projects/_/buckets/bucket', - service: 'storage.googleapis.com', - }, - }); + + 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', () => { @@ -245,23 +291,25 @@ describe('Storage Functions', () => { {} ); const result = subjectQualified.onFinalize(() => null); - expect(result.__trigger).to.deep.equal({ - eventTrigger: { - eventType: 'google.storage.object.finalize', - resource: 'projects/_/buckets/bucky', - service: 'storage.googleapis.com', - }, - }); + + 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', () => { - expect( - () => - storage - .bucket('bad/bucket/format') - .object() - .onFinalize(() => null).__trigger - ).to.throw(Error); + const fn = storage + .bucket('bad/bucket/format') + .object() + .onFinalize(() => null); + + expect(() => fn.__trigger).to.throw(Error); + + expect(() => fn.__endpoint).to.throw(Error); }); it('should not mess with media links using non-literal slashes', () => { @@ -299,24 +347,26 @@ describe('Storage Functions', () => { .bucket('bucky') .object() .onMetadataUpdate(() => null); - expect(cloudFunction.__trigger).to.deep.equal({ - eventTrigger: { - eventType: 'google.storage.object.metadataUpdate', - resource: 'projects/_/buckets/bucky', - service: 'storage.googleapis.com', - }, - }); + + 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({ - eventTrigger: { - eventType: 'google.storage.object.metadataUpdate', - resource: 'projects/_/buckets/bucket', - service: 'storage.googleapis.com', - }, - }); + + expect(cloudFunction.__trigger).to.deep.equal( + expectedTrigger(defaultBucket, 'metadataUpdate') + ); + + expect(cloudFunction.__endpoint).to.deep.equal( + expectedEndpoint(defaultBucket, 'metadataUpdate') + ); }); it('should allow fully qualified bucket names', () => { @@ -325,23 +375,24 @@ describe('Storage Functions', () => { {} ); const result = subjectQualified.onMetadataUpdate(() => null); - expect(result.__trigger).to.deep.equal({ - eventTrigger: { - eventType: 'google.storage.object.metadataUpdate', - resource: 'projects/_/buckets/bucky', - service: 'storage.googleapis.com', - }, - }); + + 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', () => { - expect( - () => - storage - .bucket('bad/bucket/format') - .object() - .onMetadataUpdate(() => null).__trigger - ).to.throw(Error); + const fn = storage + .bucket('bad/bucket/format') + .object() + .onMetadataUpdate(() => null); + + expect(() => fn.__trigger).to.throw(Error); + expect(() => fn.__endpoint).to.throw(Error); }); it('should not mess with media links using non-literal slashes', () => { @@ -390,7 +441,9 @@ describe('Storage Functions', () => { const cloudFunction = functions.handler.storage.bucket.onArchive( () => null ); + expect(cloudFunction.__trigger).to.deep.equal({}); + expect(cloudFunction.__endpoint).to.deep.equal({}); }); it('should not mess with media links using non-literal slashes', () => { @@ -429,7 +482,9 @@ describe('Storage Functions', () => { const cloudFunction = functions.handler.storage.bucket.onDelete( () => null ); + expect(cloudFunction.__trigger).to.deep.equal({}); + expect(cloudFunction.__endpoint).to.deep.equal({}); }); it('should not mess with media links using non-literal slashes', () => { @@ -468,7 +523,9 @@ describe('Storage Functions', () => { const cloudFunction = functions.handler.storage.bucket.onFinalize( () => null ); + expect(cloudFunction.__trigger).to.deep.equal({}); + expect(cloudFunction.__endpoint).to.deep.equal({}); }); it('should not mess with media links using non-literal slashes', () => { @@ -507,7 +564,9 @@ describe('Storage Functions', () => { const cloudFunction = functions.handler.storage.bucket.onMetadataUpdate( () => null ); + expect(cloudFunction.__trigger).to.deep.equal({}); + expect(cloudFunction.__endpoint).to.deep.equal({}); }); it('should not mess with media links using non-literal slashes', () => { @@ -558,6 +617,12 @@ describe('Storage Functions', () => { ); }); + it('should throw when endpoint is accessed', () => { + expect(() => storage.object().onArchive(() => null).__endpoint).to.throw( + Error + ); + }); + it('should not throw when #run is called', () => { const cf = storage.object().onArchive(() => null); expect(cf.run).to.not.throw(Error); diff --git a/spec/v1/providers/testLab.spec.ts b/spec/v1/providers/testLab.spec.ts index 3dbe8c0f5..138f1e8bf 100644 --- a/spec/v1/providers/testLab.spec.ts +++ b/spec/v1/providers/testLab.spec.ts @@ -35,8 +35,9 @@ describe('Test Lab Functions', () => { delete process.env.GCLOUD_PROJECT; }); - it('should return a TriggerDefinition with appropriate values', () => { + 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', @@ -44,6 +45,17 @@ describe('Test Lab Functions', () => { resource: 'projects/project1/testMatrices/{matrix}', }, }); + + expect(func.__endpoint).to.deep.equal({ + platform: 'gcfv1', + eventTrigger: { + eventType: 'google.testing.testMatrix.complete', + eventFilters: { + resource: 'projects/project1/testMatrices/{matrix}', + }, + retry: false, + }, + }); }); it('should parse TestMatrix in "INVALID" state', () => { @@ -155,6 +167,12 @@ describe('Test Lab Functions', () => { () => testLab.testMatrix().onComplete(() => null).__trigger ).to.throw(Error); }); + + it('should throw when endpoint is accessed', () => { + expect( + () => testLab.testMatrix().onComplete(() => null).__endpoint + ).to.throw(Error); + }); }); }); From 8a322303b1a6d1afb2b32581057acf8747d65474 Mon Sep 17 00:00:00 2001 From: Daniel Young Lee Date: Wed, 24 Nov 2021 15:33:09 -0800 Subject: [PATCH 7/9] Prettier. --- src/cloud-functions.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/cloud-functions.ts b/src/cloud-functions.ts index a3d74a749..838cca9ba 100644 --- a/src/cloud-functions.ts +++ b/src/cloud-functions.ts @@ -477,14 +477,14 @@ export function makeCloudFunction({ if (options.schedule) { cloudFunction.__requiredAPIs = [ { - api: "pubsub.googleapis.com", - reason: "Needed for v1 scheduled functions." + api: 'pubsub.googleapis.com', + reason: 'Needed for v1 scheduled functions.', }, { - api: "cloudscheduler.googleapis.com", - reason: "Needed for v1 scheduled functions." + api: 'cloudscheduler.googleapis.com', + reason: 'Needed for v1 scheduled functions.', }, - ] + ]; } cloudFunction.run = handler || contextOnlyHandler; From 554a33efaaa666ca69bedd1b11a7f0c87cff357f Mon Sep 17 00:00:00 2001 From: Daniel Young Lee Date: Thu, 25 Nov 2021 10:28:03 -0800 Subject: [PATCH 8/9] More nits for unnecessary silence trick. --- src/v2/providers/pubsub.ts | 5 ----- src/v2/providers/storage.ts | 11 +++++------ 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/src/v2/providers/pubsub.ts b/src/v2/providers/pubsub.ts index 81c4bca68..a751f0cd2 100644 --- a/src/v2/providers/pubsub.ts +++ b/src/v2/providers/pubsub.ts @@ -135,11 +135,6 @@ export function onMessagePublished( func.run = handler; - // TypeScript doesn't recognize defineProperty as adding a property and complains - // that __trigger 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. - func.__trigger = 'silence the transpiler'; - Object.defineProperty(func, '__trigger', { get: () => { const baseOpts = options.optionsToTriggerAnnotations( diff --git a/src/v2/providers/storage.ts b/src/v2/providers/storage.ts index c092d25a2..e03115df0 100644 --- a/src/v2/providers/storage.ts +++ b/src/v2/providers/storage.ts @@ -315,12 +315,6 @@ export function onOperation( func.run = handler; - // 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. - func.__trigger = 'silence the transpiler'; - func.__endpoint = {} as ManifestEndpoint; - Object.defineProperty(func, '__trigger', { get: () => { const baseOpts = options.optionsToTriggerAnnotations( @@ -344,6 +338,11 @@ export function onOperation( }, }); + // 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. + func.__endpoint = {} as ManifestEndpoint; + // SDK may attempt to read FIREBASE_CONFIG env var to fetch the default bucket name. // To prevent runtime errors when FIREBASE_CONFIG env var is missing, we use getters. Object.defineProperty(func, '__endpoint', { From b64a328f20da327d678c7a2d7d342d1da3518e38 Mon Sep 17 00:00:00 2001 From: Daniel Young Lee Date: Thu, 9 Dec 2021 21:10:46 -0800 Subject: [PATCH 9/9] Always set labels (to empty), handler namespace sets to undefined instead of empty. --- spec/v1/cloud-functions.spec.ts | 5 +++++ spec/v1/providers/analytics.spec.ts | 3 ++- spec/v1/providers/auth.spec.ts | 16 +++++++++------- spec/v1/providers/database.spec.ts | 17 +++++++++-------- spec/v1/providers/firestore.spec.ts | 1 + spec/v1/providers/https.spec.ts | 6 +++--- spec/v1/providers/pubsub.spec.ts | 11 ++++------- spec/v1/providers/remoteConfig.spec.ts | 3 ++- spec/v1/providers/storage.spec.ts | 9 +++++---- spec/v1/providers/testLab.spec.ts | 1 + spec/v2/providers/https.spec.ts | 5 +---- src/cloud-functions.ts | 18 ++++++++---------- src/common/manifest.ts | 10 ++++++++-- src/handler-builder.ts | 9 ++++++--- src/providers/https.ts | 3 ++- 15 files changed, 66 insertions(+), 51 deletions(-) diff --git a/spec/v1/cloud-functions.spec.ts b/spec/v1/cloud-functions.spec.ts index 5c861f3d6..c894bfa23 100644 --- a/spec/v1/cloud-functions.spec.ts +++ b/spec/v1/cloud-functions.spec.ts @@ -67,6 +67,7 @@ describe('makeCloudFunction', () => { }, retry: false, }, + labels: {}, }); }); @@ -90,6 +91,7 @@ describe('makeCloudFunction', () => { }, retry: false, }, + labels: {}, }); }); @@ -121,6 +123,7 @@ describe('makeCloudFunction', () => { }, retry: false, }, + labels: {}, }); }); @@ -143,6 +146,7 @@ describe('makeCloudFunction', () => { }, retry: true, }, + labels: {}, }); }); @@ -165,6 +169,7 @@ describe('makeCloudFunction', () => { expect(cf.__endpoint).to.deep.equal({ platform: 'gcfv1', scheduleTrigger: schedule, + labels: {}, }); }); diff --git a/spec/v1/providers/analytics.spec.ts b/spec/v1/providers/analytics.spec.ts index 43cedf3f1..24eb0a008 100644 --- a/spec/v1/providers/analytics.spec.ts +++ b/spec/v1/providers/analytics.spec.ts @@ -79,6 +79,7 @@ describe('Analytics Functions', () => { 'providers/google.firebase.analytics/eventTypes/event.log', retry: false, }, + labels: {}, }); }); }); @@ -326,7 +327,7 @@ describe('Analytics Functions', () => { () => null ); expect(cloudFunction.__trigger).to.deep.equal({}); - expect(cloudFunction.__endpoint).to.deep.equal({}); + expect(cloudFunction.__endpoint).to.undefined; }); it('should handle an event with the appropriate fields', () => { diff --git a/spec/v1/providers/auth.spec.ts b/spec/v1/providers/auth.spec.ts index 2337d5632..6168cf767 100644 --- a/spec/v1/providers/auth.spec.ts +++ b/spec/v1/providers/auth.spec.ts @@ -71,6 +71,7 @@ describe('Auth Functions', () => { eventType: `providers/firebase.auth/eventTypes/${eventType}`, retry: false, }, + labels: {}, }; } @@ -232,7 +233,7 @@ describe('Auth Functions', () => { it('should return an empty endpoint', () => { const cloudFunction = functions.handler.auth.user.onCreate(() => null); - expect(cloudFunction.__endpoint).to.deep.equal({}); + expect(cloudFunction.__endpoint).to.be.undefined; }); }); @@ -241,13 +242,14 @@ describe('Auth Functions', () => { (data: firebase.auth.UserRecord) => data ); - it('should return an empty trigger/endpoint', () => { - const handler = (user: firebase.auth.UserRecord) => { - return Promise.resolve(); - }; - const cloudFunction = functions.handler.auth.user.onDelete(handler); + it('should return an empty trigger', () => { + const cloudFunction = functions.handler.auth.user.onDelete(() => null); expect(cloudFunction.__trigger).to.deep.equal({}); - expect(cloudFunction.__endpoint).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', () => { diff --git a/spec/v1/providers/database.spec.ts b/spec/v1/providers/database.spec.ts index 069ee57ba..f932dd871 100644 --- a/spec/v1/providers/database.spec.ts +++ b/spec/v1/providers/database.spec.ts @@ -51,6 +51,7 @@ describe('Database Functions', () => { eventType: `providers/google.firebase.database/eventTypes/${eventType}`, retry: false, }, + labels: {}, }; } @@ -326,7 +327,7 @@ describe('Database Functions', () => { it('correctly sets trigger to {}', () => { const cf = functions.handler.database.ref.onWrite(() => null); expect(cf.__trigger).to.deep.equal({}); - expect(cf.__endpoint).to.deep.equal({}); + expect(cf.__endpoint).to.be.undefined; }); it('should be able to use the instance entry point', () => { @@ -334,7 +335,7 @@ describe('Database Functions', () => { () => null ); expect(func.__trigger).to.deep.equal({}); - expect(func.__endpoint).to.deep.equal({}); + expect(func.__endpoint).to.be.undefined; }); it('should return a handler that emits events with a proper DataSnapshot', () => { @@ -366,7 +367,7 @@ describe('Database Functions', () => { it('correctly sets trigger to {}', () => { const cf = functions.handler.database.ref.onCreate(() => null); expect(cf.__trigger).to.deep.equal({}); - expect(cf.__endpoint).to.deep.equal({}); + expect(cf.__endpoint).to.be.undefined; }); it('should be able to use the instance entry point', () => { @@ -374,7 +375,7 @@ describe('Database Functions', () => { () => null ); expect(func.__trigger).to.deep.equal({}); - expect(func.__endpoint).to.deep.equal({}); + expect(func.__endpoint).to.be.undefined; }); it('should return a handler that emits events with a proper DataSnapshot', () => { @@ -405,7 +406,7 @@ describe('Database Functions', () => { it('correctly sets trigger to {}', () => { const cf = functions.handler.database.ref.onUpdate(() => null); expect(cf.__trigger).to.deep.equal({}); - expect(cf.__endpoint).to.deep.equal({}); + expect(cf.__endpoint).to.be.undefined; }); it('should be able to use the instance entry point', () => { @@ -413,7 +414,7 @@ describe('Database Functions', () => { () => null ); expect(func.__trigger).to.deep.equal({}); - expect(func.__endpoint).to.deep.equal({}); + expect(func.__endpoint).to.be.undefined; }); it('should return a handler that emits events with a proper DataSnapshot', () => { @@ -444,7 +445,7 @@ describe('Database Functions', () => { it('correctly sets trigger to {}', () => { const cf = functions.handler.database.ref.onDelete(() => null); expect(cf.__trigger).to.deep.equal({}); - expect(cf.__endpoint).to.deep.equal({}); + expect(cf.__endpoint).to.be.undefined; }); it('should be able to use the instance entry point', () => { @@ -452,7 +453,7 @@ describe('Database Functions', () => { () => null ); expect(func.__trigger).to.deep.equal({}); - expect(func.__endpoint).to.deep.equal({}); + expect(func.__endpoint).to.be.undefined; }); it('should return a handler that emits events with a proper DataSnapshot', () => { diff --git a/spec/v1/providers/firestore.spec.ts b/spec/v1/providers/firestore.spec.ts index 1fddda575..3f9f07fbe 100644 --- a/spec/v1/providers/firestore.spec.ts +++ b/spec/v1/providers/firestore.spec.ts @@ -113,6 +113,7 @@ describe('Firestore Functions', () => { eventType: `providers/cloud.firestore/eventTypes/${eventType}`, retry: false, }, + labels: {}, }; } diff --git a/spec/v1/providers/https.spec.ts b/spec/v1/providers/https.spec.ts index d44871c5a..22e6d52fd 100644 --- a/spec/v1/providers/https.spec.ts +++ b/spec/v1/providers/https.spec.ts @@ -135,7 +135,7 @@ describe('handler namespace', () => { res.send(200); }); expect(result.__trigger).to.deep.equal({}); - expect(result.__endpoint).to.deep.equal({}); + expect(result.__endpoint).to.be.undefined; }); }); @@ -143,7 +143,7 @@ describe('handler namespace', () => { it('should return an empty trigger', () => { const result = functions.handler.https.onCall(() => null); expect(result.__trigger).to.deep.equal({}); - expect(result.__endpoint).to.deep.equal({}); + expect(result.__endpoint).to.be.undefined; }); }); @@ -151,7 +151,7 @@ describe('handler namespace', () => { it('should return an empty trigger', () => { const result = functions.handler.https.taskQueue.onEnqueue(() => null); expect(result.__trigger).to.deep.equal({}); - expect(result.__endpoint).to.deep.equal({}); + expect(result.__endpoint).to.be.undefined; }); }); }); diff --git a/spec/v1/providers/pubsub.spec.ts b/spec/v1/providers/pubsub.spec.ts index ddd65fc60..5547515ab 100644 --- a/spec/v1/providers/pubsub.spec.ts +++ b/spec/v1/providers/pubsub.spec.ts @@ -113,6 +113,7 @@ describe('Pubsub Functions', () => { }, retry: false, }, + labels: {}, }); }); @@ -215,9 +216,7 @@ describe('Pubsub Functions', () => { schedule: 'every 5 minutes', retryConfig, }); - expect(result.__endpoint.labels).to.deep.equal({ - 'deployment-scheduled': 'true', - }); + expect(result.__endpoint.labels).to.be.empty; }); it( @@ -251,9 +250,7 @@ describe('Pubsub Functions', () => { retryConfig, timeZone: 'America/New_York', }); - expect(result.__endpoint.labels).to.deep.equal({ - 'deployment-scheduled': 'true', - }); + expect(result.__endpoint.labels).to.be.empty; } ); @@ -393,7 +390,7 @@ describe('Pubsub Functions', () => { it('should return an empty trigger', () => { const result = functions.handler.pubsub.topic.onPublish(() => null); expect(result.__trigger).to.deep.equal({}); - expect(result.__endpoint).to.deep.equal({}); + expect(result.__endpoint).to.be.undefined; }); it('should properly handle a new-style event', () => { diff --git a/spec/v1/providers/remoteConfig.spec.ts b/spec/v1/providers/remoteConfig.spec.ts index 9497ca01a..f3fde2043 100644 --- a/spec/v1/providers/remoteConfig.spec.ts +++ b/spec/v1/providers/remoteConfig.spec.ts @@ -73,6 +73,7 @@ describe('RemoteConfig Functions', () => { }, retry: false, }, + labels: {}, }); }); @@ -143,7 +144,7 @@ describe('RemoteConfig Functions', () => { ); expect(cloudFunction.__trigger).to.deep.equal({}); - expect(cloudFunction.__endpoint).to.deep.equal({}); + expect(cloudFunction.__endpoint).to.be.undefined; }); it('should correctly unwrap the event', () => { diff --git a/spec/v1/providers/storage.spec.ts b/spec/v1/providers/storage.spec.ts index a5961d94c..d96b131b9 100644 --- a/spec/v1/providers/storage.spec.ts +++ b/spec/v1/providers/storage.spec.ts @@ -48,6 +48,7 @@ describe('Storage Functions', () => { eventType: `google.storage.object.${eventType}`, retry: false, }, + labels: {}, }; } @@ -443,7 +444,7 @@ describe('Storage Functions', () => { ); expect(cloudFunction.__trigger).to.deep.equal({}); - expect(cloudFunction.__endpoint).to.deep.equal({}); + expect(cloudFunction.__endpoint).to.be.undefined; }); it('should not mess with media links using non-literal slashes', () => { @@ -484,7 +485,7 @@ describe('Storage Functions', () => { ); expect(cloudFunction.__trigger).to.deep.equal({}); - expect(cloudFunction.__endpoint).to.deep.equal({}); + expect(cloudFunction.__endpoint).to.be.undefined; }); it('should not mess with media links using non-literal slashes', () => { @@ -525,7 +526,7 @@ describe('Storage Functions', () => { ); expect(cloudFunction.__trigger).to.deep.equal({}); - expect(cloudFunction.__endpoint).to.deep.equal({}); + expect(cloudFunction.__endpoint).to.be.undefined; }); it('should not mess with media links using non-literal slashes', () => { @@ -566,7 +567,7 @@ describe('Storage Functions', () => { ); expect(cloudFunction.__trigger).to.deep.equal({}); - expect(cloudFunction.__endpoint).to.deep.equal({}); + expect(cloudFunction.__endpoint).to.be.undefined; }); it('should not mess with media links using non-literal slashes', () => { diff --git a/spec/v1/providers/testLab.spec.ts b/spec/v1/providers/testLab.spec.ts index 138f1e8bf..b3ba22d7f 100644 --- a/spec/v1/providers/testLab.spec.ts +++ b/spec/v1/providers/testLab.spec.ts @@ -55,6 +55,7 @@ describe('Test Lab Functions', () => { }, retry: false, }, + labels: {}, }); }); diff --git a/spec/v2/providers/https.spec.ts b/spec/v2/providers/https.spec.ts index 30ad53088..5f0f7eb74 100644 --- a/spec/v2/providers/https.spec.ts +++ b/spec/v2/providers/https.spec.ts @@ -252,8 +252,8 @@ describe('onCall', () => { expect(result.__endpoint).to.deep.equal({ platform: 'gcfv2', - labels: {}, callableTrigger: {}, + labels: {}, }); }); @@ -274,9 +274,6 @@ describe('onCall', () => { expect(result.__endpoint).to.deep.equal({ ...FULL_ENDPOINT, callableTrigger: {}, - labels: { - ...FULL_ENDPOINT.labels, - }, }); }); diff --git a/src/cloud-functions.ts b/src/cloud-functions.ts index 838cca9ba..d2de0504c 100644 --- a/src/cloud-functions.ts +++ b/src/cloud-functions.ts @@ -37,7 +37,7 @@ import { durationFromSeconds, serviceAccountFromShorthand, } from './common/encoding'; -import { ManifestEndpoint } from './common/manifest'; +import { ManifestEndpoint, ManifestRequiredAPI } from './common/manifest'; /** @hidden */ const WILDCARD_REGEX = new RegExp('{[^/{}]*}', 'g'); @@ -290,6 +290,7 @@ export interface TriggerAnnotated { */ export interface EndpointAnnotated { __endpoint: ManifestEndpoint; + __requiredAPIs?: ManifestRequiredAPI[]; } /** @@ -446,7 +447,7 @@ export function makeCloudFunction({ Object.defineProperty(cloudFunction, '__endpoint', { get: () => { if (triggerResource() == null) { - return {}; + return undefined; } const endpoint: ManifestEndpoint = { @@ -466,9 +467,10 @@ export function makeCloudFunction({ }; } - if (Object.keys(labels).length > 0) { - endpoint.labels = { ...endpoint.labels, ...labels }; - } + // Note: We intentionally don't make use of labels args here. + // labels is used to pass SDK-defined labels to the trigger, which isn't + // something we will do in the container contract world. + endpoint.labels = { ...endpoint.labels }; return endpoint; }, @@ -476,13 +478,9 @@ export function makeCloudFunction({ if (options.schedule) { cloudFunction.__requiredAPIs = [ - { - api: 'pubsub.googleapis.com', - reason: 'Needed for v1 scheduled functions.', - }, { api: 'cloudscheduler.googleapis.com', - reason: 'Needed for v1 scheduled functions.', + reason: 'Needed for scheduled functions.', }, ]; } diff --git a/src/common/manifest.ts b/src/common/manifest.ts index 02784b68c..379e61a4f 100644 --- a/src/common/manifest.ts +++ b/src/common/manifest.ts @@ -68,12 +68,18 @@ export interface ManifestEndpoint { }; } +/* @internal */ +export interface ManifestRequiredAPI { + api: string; + reason: string; +} + /** * @internal * An definition of a function deployment as appears in the Manifest. **/ -export interface ManifestBackend { +export interface ManifestStack { specVersion: 'v1alpha1'; - requiredAPIs: Record; + requiredAPIs: ManifestRequiredAPI[]; endpoints: Record; } diff --git a/src/handler-builder.ts b/src/handler-builder.ts index bc834ea25..c89f3c936 100644 --- a/src/handler-builder.ts +++ b/src/handler-builder.ts @@ -70,7 +70,8 @@ export class HandlerBuilder { ): HttpsFunction => { const func = https._onRequestWithOptions(handler, {}); func.__trigger = {}; - func.__endpoint = {}; + func.__endpoint = undefined; + func.__requiredAPIs = undefined; return func; }, onCall: ( @@ -81,7 +82,8 @@ export class HandlerBuilder { ): HttpsFunction => { const func = https._onCallWithOptions(handler, {}); func.__trigger = {}; - func.__endpoint = {}; + func.__endpoint = undefined; + func.__requiredAPIs = undefined; return func; }, /** @hidden */ @@ -96,7 +98,8 @@ export class HandlerBuilder { const builder = new https.TaskQueueBuilder(); const func = builder.onDispatch(handler); func.__trigger = {}; - func.__endpoint = {}; + func.__endpoint = undefined; + func.__requiredAPIs = undefined; return func; }, }; diff --git a/src/providers/https.ts b/src/providers/https.ts index 40dc6af8e..8ab072e78 100644 --- a/src/providers/https.ts +++ b/src/providers/https.ts @@ -33,7 +33,7 @@ import { convertInvoker, copyIfPresent, } from '../common/encoding'; -import { ManifestEndpoint } from '../common/manifest'; +import { ManifestEndpoint, ManifestRequiredAPI } from '../common/manifest'; import { CallableContext, FunctionsErrorCode, @@ -103,6 +103,7 @@ export interface TaskQueueFunction { (req: Request, res: express.Response): Promise; __trigger: unknown; __endpoint: ManifestEndpoint; + __requiredAPIs?: ManifestRequiredAPI[]; run(data: any, context: TaskContext): void | Promise; }