Skip to content

Commit 4e48f03

Browse files
authored
Container contract support for Firebase Functions SDK (#1014)
* Annotate function triggers with __endpoint property (#999) In addition to annotating function triggers with `__trigger` property, we add `__endpoint` annotation. This property will be used by the to-be-developed functions runtime to generate/declare deployment manifest that the CLI will use to deploy the function. There are lots of code duplication between the utility functions for annotating the `__trigger` and `__endpoint` properties. I didn't try to refactor the common code since I expect that we will favor `__endpoint` property in the future. * Add missing import. * More of annotate endpoint property for functions (#1009) Follows up #999 to annotate each funuctions with `__endpoint` property. Highlight of changes: * Extend unit test coverage for all v1 providers * Add `__endpoint` annotation to v1 task queue functions * Add `__requiredAPIs` annotation to task queue and scheduler functions * Explicitly set `__endpoint` to undefined in the handler namespace * No SDK-level label setting in the __endpoint annotation.
1 parent 9016314 commit 4e48f03

23 files changed

+1564
-303
lines changed

spec/v1/cloud-functions.spec.ts

Lines changed: 106 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,32 +41,136 @@ describe('makeCloudFunction', () => {
4141
legacyEventType: 'providers/provider/eventTypes/event',
4242
};
4343

44-
it('should put a __trigger on the returned CloudFunction', () => {
44+
it('should put a __trigger/__endpoint on the returned CloudFunction', () => {
4545
const cf = makeCloudFunction({
4646
provider: 'mock.provider',
4747
eventType: 'mock.event',
4848
service: 'service',
4949
triggerResource: () => 'resource',
5050
handler: () => null,
5151
});
52+
5253
expect(cf.__trigger).to.deep.equal({
5354
eventTrigger: {
5455
eventType: 'mock.provider.mock.event',
5556
resource: 'resource',
5657
service: 'service',
5758
},
5859
});
60+
61+
expect(cf.__endpoint).to.deep.equal({
62+
platform: 'gcfv1',
63+
eventTrigger: {
64+
eventType: 'mock.provider.mock.event',
65+
eventFilters: {
66+
resource: 'resource',
67+
},
68+
retry: false,
69+
},
70+
labels: {},
71+
});
5972
});
6073

61-
it('should have legacy event type in __trigger if provided', () => {
74+
it('should have legacy event type in __trigger/__endpoint if provided', () => {
6275
const cf = makeCloudFunction(cloudFunctionArgs);
76+
6377
expect(cf.__trigger).to.deep.equal({
6478
eventTrigger: {
6579
eventType: 'providers/provider/eventTypes/event',
6680
resource: 'resource',
6781
service: 'service',
6882
},
6983
});
84+
85+
expect(cf.__endpoint).to.deep.equal({
86+
platform: 'gcfv1',
87+
eventTrigger: {
88+
eventType: 'providers/provider/eventTypes/event',
89+
eventFilters: {
90+
resource: 'resource',
91+
},
92+
retry: false,
93+
},
94+
labels: {},
95+
});
96+
});
97+
98+
it('should include converted options in __endpoint', () => {
99+
const cf = makeCloudFunction({
100+
provider: 'mock.provider',
101+
eventType: 'mock.event',
102+
service: 'service',
103+
triggerResource: () => 'resource',
104+
handler: () => null,
105+
options: {
106+
timeoutSeconds: 10,
107+
regions: ['us-central1'],
108+
memory: '128MB',
109+
serviceAccount: '[email protected]',
110+
},
111+
});
112+
113+
expect(cf.__endpoint).to.deep.equal({
114+
platform: 'gcfv1',
115+
timeoutSeconds: 10,
116+
region: ['us-central1'],
117+
availableMemoryMb: 128,
118+
serviceAccountEmail: '[email protected]',
119+
eventTrigger: {
120+
eventType: 'mock.provider.mock.event',
121+
eventFilters: {
122+
resource: 'resource',
123+
},
124+
retry: false,
125+
},
126+
labels: {},
127+
});
128+
});
129+
130+
it('should set retry given failure policy in __endpoint', () => {
131+
const cf = makeCloudFunction({
132+
provider: 'mock.provider',
133+
eventType: 'mock.event',
134+
service: 'service',
135+
triggerResource: () => 'resource',
136+
handler: () => null,
137+
options: { failurePolicy: { retry: {} } },
138+
});
139+
140+
expect(cf.__endpoint).to.deep.equal({
141+
platform: 'gcfv1',
142+
eventTrigger: {
143+
eventType: 'mock.provider.mock.event',
144+
eventFilters: {
145+
resource: 'resource',
146+
},
147+
retry: true,
148+
},
149+
labels: {},
150+
});
151+
});
152+
153+
it('should setup a scheduleTrigger in __endpoint given a schedule', () => {
154+
const schedule = {
155+
schedule: 'every 5 minutes',
156+
retryConfig: { retryCount: 3 },
157+
timeZone: 'America/New_York',
158+
};
159+
const cf = makeCloudFunction({
160+
provider: 'mock.provider',
161+
eventType: 'mock.event',
162+
service: 'service',
163+
triggerResource: () => 'resource',
164+
handler: () => null,
165+
options: {
166+
schedule,
167+
},
168+
});
169+
expect(cf.__endpoint).to.deep.equal({
170+
platform: 'gcfv1',
171+
scheduleTrigger: schedule,
172+
labels: {},
173+
});
70174
});
71175

72176
it('should construct the right context for event', () => {

spec/v1/providers/analytics.spec.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,14 @@ describe('Analytics Functions', () => {
5050
expect(fn.__trigger.regions).to.deep.equal(['us-east1']);
5151
expect(fn.__trigger.availableMemoryMb).to.deep.equal(256);
5252
expect(fn.__trigger.timeout).to.deep.equal('90s');
53+
54+
expect(fn.__endpoint.region).to.deep.equal(['us-east1']);
55+
expect(fn.__endpoint.availableMemoryMb).to.deep.equal(256);
56+
expect(fn.__endpoint.timeoutSeconds).to.deep.equal(90);
5357
});
5458

5559
describe('#onLog', () => {
56-
it('should return a TriggerDefinition with appropriate values', () => {
60+
it('should return a trigger/endpoint with appropriate values', () => {
5761
const cloudFunction = analytics.event('first_open').onLog(() => null);
5862

5963
expect(cloudFunction.__trigger).to.deep.equal({
@@ -64,6 +68,19 @@ describe('Analytics Functions', () => {
6468
service: 'app-measurement.com',
6569
},
6670
});
71+
72+
expect(cloudFunction.__endpoint).to.deep.equal({
73+
platform: 'gcfv1',
74+
eventTrigger: {
75+
eventFilters: {
76+
resource: 'projects/project1/events/first_open',
77+
},
78+
eventType:
79+
'providers/google.firebase.analytics/eventTypes/event.log',
80+
retry: false,
81+
},
82+
labels: {},
83+
});
6784
});
6885
});
6986

@@ -305,11 +322,12 @@ describe('Analytics Functions', () => {
305322

306323
describe('handler namespace', () => {
307324
describe('#onLog', () => {
308-
it('should return an empty trigger', () => {
325+
it('should return an empty trigger/endpoint', () => {
309326
const cloudFunction = functions.handler.analytics.event.onLog(
310327
() => null
311328
);
312329
expect(cloudFunction.__trigger).to.deep.equal({});
330+
expect(cloudFunction.__endpoint).to.undefined;
313331
});
314332

315333
it('should handle an event with the appropriate fields', () => {

spec/v1/providers/auth.spec.ts

Lines changed: 64 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,38 @@ describe('Auth Functions', () => {
5151
};
5252

5353
describe('AuthBuilder', () => {
54+
function expectedTrigger(project: string, eventType: string) {
55+
return {
56+
eventTrigger: {
57+
resource: `projects/${project}`,
58+
eventType: `providers/firebase.auth/eventTypes/${eventType}`,
59+
service: 'firebaseauth.googleapis.com',
60+
},
61+
};
62+
}
63+
64+
function expectedEndpoint(project: string, eventType: string) {
65+
return {
66+
platform: 'gcfv1',
67+
eventTrigger: {
68+
eventFilters: {
69+
resource: `projects/${project}`,
70+
},
71+
eventType: `providers/firebase.auth/eventTypes/${eventType}`,
72+
retry: false,
73+
},
74+
labels: {},
75+
};
76+
}
77+
5478
const handler = (user: firebase.auth.UserRecord) => {
5579
return Promise.resolve();
5680
};
5781

82+
const project = 'project1';
83+
5884
before(() => {
59-
process.env.GCLOUD_PROJECT = 'project1';
85+
process.env.GCLOUD_PROJECT = project;
6086
});
6187

6288
after(() => {
@@ -76,31 +102,37 @@ describe('Auth Functions', () => {
76102
expect(fn.__trigger.regions).to.deep.equal(['us-east1']);
77103
expect(fn.__trigger.availableMemoryMb).to.deep.equal(256);
78104
expect(fn.__trigger.timeout).to.deep.equal('90s');
105+
106+
expect(fn.__endpoint.region).to.deep.equal(['us-east1']);
107+
expect(fn.__endpoint.availableMemoryMb).to.deep.equal(256);
108+
expect(fn.__endpoint.timeoutSeconds).to.deep.equal(90);
79109
});
80110

81111
describe('#onCreate', () => {
82-
it('should return a TriggerDefinition with appropriate values', () => {
112+
it('should return a trigger/endpoint with appropriate values', () => {
83113
const cloudFunction = auth.user().onCreate(() => null);
84-
expect(cloudFunction.__trigger).to.deep.equal({
85-
eventTrigger: {
86-
eventType: 'providers/firebase.auth/eventTypes/user.create',
87-
resource: 'projects/project1',
88-
service: 'firebaseauth.googleapis.com',
89-
},
90-
});
114+
115+
expect(cloudFunction.__trigger).to.deep.equal(
116+
expectedTrigger(project, 'user.create')
117+
);
118+
119+
expect(cloudFunction.__endpoint).to.deep.equal(
120+
expectedEndpoint(project, 'user.create')
121+
);
91122
});
92123
});
93124

94125
describe('#onDelete', () => {
95-
it('should return a TriggerDefinition with appropriate values', () => {
126+
it('should return a trigger/endpoint with appropriate values', () => {
96127
const cloudFunction = auth.user().onDelete(handler);
97-
expect(cloudFunction.__trigger).to.deep.equal({
98-
eventTrigger: {
99-
eventType: 'providers/firebase.auth/eventTypes/user.delete',
100-
resource: 'projects/project1',
101-
service: 'firebaseauth.googleapis.com',
102-
},
103-
});
128+
129+
expect(cloudFunction.__trigger).to.deep.equal(
130+
expectedTrigger(project, 'user.delete')
131+
);
132+
133+
expect(cloudFunction.__endpoint).to.deep.equal(
134+
expectedEndpoint(project, 'user.delete')
135+
);
104136
});
105137
});
106138

@@ -198,6 +230,11 @@ describe('Auth Functions', () => {
198230
const cloudFunction = functions.handler.auth.user.onCreate(() => null);
199231
expect(cloudFunction.__trigger).to.deep.equal({});
200232
});
233+
234+
it('should return an empty endpoint', () => {
235+
const cloudFunction = functions.handler.auth.user.onCreate(() => null);
236+
expect(cloudFunction.__endpoint).to.be.undefined;
237+
});
201238
});
202239

203240
describe('#onDelete', () => {
@@ -206,13 +243,15 @@ describe('Auth Functions', () => {
206243
);
207244

208245
it('should return an empty trigger', () => {
209-
const handler = (user: firebase.auth.UserRecord) => {
210-
return Promise.resolve();
211-
};
212-
const cloudFunction = functions.handler.auth.user.onDelete(handler);
246+
const cloudFunction = functions.handler.auth.user.onDelete(() => null);
213247
expect(cloudFunction.__trigger).to.deep.equal({});
214248
});
215249

250+
it('should return an empty endpoint', () => {
251+
const cloudFunction = functions.handler.auth.user.onDelete(() => null);
252+
expect(cloudFunction.__endpoint).to.be.undefined;
253+
});
254+
216255
it('should handle wire format as of v5.0.0 of firebase-admin', () => {
217256
return cloudFunctionDelete(event.data, event.context).then(
218257
(data: any) => {
@@ -237,6 +276,10 @@ describe('Auth Functions', () => {
237276
expect(() => auth.user().onCreate(() => null).__trigger).to.throw(Error);
238277
});
239278

279+
it('should throw when endpoint is accessed', () => {
280+
expect(() => auth.user().onCreate(() => null).__endpoint).to.throw(Error);
281+
});
282+
240283
it('should not throw when #run is called', () => {
241284
const cf = auth.user().onCreate(() => null);
242285
expect(cf.run).to.not.throw(Error);

0 commit comments

Comments
 (0)