Skip to content

Commit 1e3ac7c

Browse files
authored
Allow passing context.app to wrapped callable functions. (#123)
Change allows users to pass `app` property in the mocked callable context, e.g. ```js export myFunc = functions.https.onCall((data, context) => { if (context.app == undefined) { throw new functions.https.HttpsError( 'failed-precondition', 'The function must be called from an App Check verified app.') } }); ``` ### Before ```js wrap(myFunc)('data', { app: { appId: 'my-app-123' }}); // => Options object {"auth":{"uid":""},"app":{}} has invalid key "app" ``` ### After ```js wrap(myFunc)('data', { app: { appId: 'my-app-123' }}); // no error ``` The diff on `main.spec.ts` looks terrible - I'm just adding a few test cases for callable functions, and indenting the ones that existed before. Fixes #122
1 parent 34f612a commit 1e3ac7c

File tree

2 files changed

+125
-73
lines changed

2 files changed

+125
-73
lines changed

spec/main.spec.ts

Lines changed: 119 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -28,89 +28,136 @@ import { mockConfig, makeChange, _makeResourceName, wrap } from '../src/main';
2828

2929
describe('main', () => {
3030
describe('#wrap', () => {
31-
const constructCF = (eventType?: string) => {
32-
const cloudFunction = (input) => input;
33-
set(cloudFunction, 'run', (data, context) => {
34-
return { data, context };
31+
describe('background functions', () => {
32+
const constructBackgroundCF = (eventType?: string) => {
33+
const cloudFunction = (input) => input;
34+
set(cloudFunction, 'run', (data, context) => {
35+
return { data, context };
36+
});
37+
set(cloudFunction, '__trigger', {
38+
eventTrigger: {
39+
resource: 'ref/{wildcard}/nested/{anotherWildcard}',
40+
eventType: eventType || 'event',
41+
service: 'service',
42+
},
43+
});
44+
return cloudFunction as functions.CloudFunction<any>;
45+
};
46+
47+
it('should invoke the function with the supplied data', () => {
48+
const wrapped = wrap(constructBackgroundCF());
49+
expect(wrapped('data').data).to.equal('data');
3550
});
36-
set(cloudFunction, '__trigger', {
37-
eventTrigger: {
38-
resource: 'ref/{wildcard}/nested/{anotherWildcard}',
39-
eventType: eventType || 'event',
40-
service: 'service',
41-
},
51+
52+
it('should generate the appropriate context if no fields specified', () => {
53+
const context = wrap(constructBackgroundCF())('data').context;
54+
expect(typeof context.eventId).to.equal('string');
55+
expect(context.resource.service).to.equal('service');
56+
expect(
57+
/ref\/wildcard[1-9]\/nested\/anotherWildcard[1-9]/.test(
58+
context.resource.name
59+
)
60+
).to.be.true;
61+
expect(context.eventType).to.equal('event');
62+
expect(Date.parse(context.timestamp)).to.be.greaterThan(0);
63+
expect(context.params).to.deep.equal({});
64+
expect(context.auth).to.be.undefined;
65+
expect(context.authType).to.be.undefined;
4266
});
43-
return cloudFunction as functions.CloudFunction<any>;
44-
};
4567

46-
it('should invoke the function with the supplied data', () => {
47-
const wrapped = wrap(constructCF());
48-
expect(wrapped('data').data).to.equal('data');
49-
});
68+
it('should allow specification of context fields', () => {
69+
const wrapped = wrap(constructBackgroundCF());
70+
const context = wrapped('data', {
71+
eventId: '111',
72+
timestamp: '2018-03-28T18:58:50.370Z',
73+
}).context;
74+
expect(context.eventId).to.equal('111');
75+
expect(context.timestamp).to.equal('2018-03-28T18:58:50.370Z');
76+
});
5077

51-
it('should generate the appropriate context if no fields specified', () => {
52-
const context = wrap(constructCF())('data').context;
53-
expect(typeof context.eventId).to.equal('string');
54-
expect(context.resource.service).to.equal('service');
55-
expect(
56-
/ref\/wildcard[1-9]\/nested\/anotherWildcard[1-9]/.test(
57-
context.resource.name
58-
)
59-
).to.be.true;
60-
expect(context.eventType).to.equal('event');
61-
expect(Date.parse(context.timestamp)).to.be.greaterThan(0);
62-
expect(context.params).to.deep.equal({});
63-
expect(context.auth).to.be.undefined;
64-
expect(context.authType).to.be.undefined;
65-
});
78+
it('should generate auth and authType for database functions', () => {
79+
const context = wrap(constructBackgroundCF('google.firebase.database.ref.write'))(
80+
'data'
81+
).context;
82+
expect(context.auth).to.equal(null);
83+
expect(context.authType).to.equal('UNAUTHENTICATED');
84+
});
6685

67-
it('should allow specification of context fields', () => {
68-
const wrapped = wrap(constructCF());
69-
const context = wrapped('data', {
70-
eventId: '111',
71-
timestamp: '2018-03-28T18:58:50.370Z',
72-
}).context;
73-
expect(context.eventId).to.equal('111');
74-
expect(context.timestamp).to.equal('2018-03-28T18:58:50.370Z');
75-
});
86+
it('should allow auth and authType to be specified for database functions', () => {
87+
const wrapped = wrap(constructBackgroundCF('google.firebase.database.ref.write'));
88+
const context = wrapped('data', {
89+
auth: { uid: 'abc' },
90+
authType: 'USER',
91+
}).context;
92+
expect(context.auth).to.deep.equal({ uid: 'abc' });
93+
expect(context.authType).to.equal('USER');
94+
});
7695

77-
it('should generate auth and authType for database functions', () => {
78-
const context = wrap(constructCF('google.firebase.database.ref.write'))(
79-
'data'
80-
).context;
81-
expect(context.auth).to.equal(null);
82-
expect(context.authType).to.equal('UNAUTHENTICATED');
83-
});
96+
it('should throw when passed invalid options', () => {
97+
const wrapped = wrap(constructBackgroundCF());
98+
expect(() =>
99+
wrapped('data', {
100+
auth: { uid: 'abc' },
101+
isInvalid: true,
102+
} as any)
103+
).to.throw();
104+
});
84105

85-
it('should allow auth and authType to be specified for database functions', () => {
86-
const wrapped = wrap(constructCF('google.firebase.database.ref.write'));
87-
const context = wrapped('data', {
88-
auth: { uid: 'abc' },
89-
authType: 'USER',
90-
}).context;
91-
expect(context.auth).to.deep.equal({ uid: 'abc' });
92-
expect(context.authType).to.equal('USER');
106+
it('should generate the appropriate resource based on params', () => {
107+
const params = {
108+
wildcard: 'a',
109+
anotherWildcard: 'b',
110+
};
111+
const wrapped = wrap(constructBackgroundCF());
112+
const context = wrapped('data', { params }).context;
113+
expect(context.params).to.deep.equal(params);
114+
expect(context.resource.name).to.equal('ref/a/nested/b');
115+
});
93116
});
94117

95-
it('should throw when passed invalid options', () => {
96-
const wrapped = wrap(constructCF());
97-
expect(() =>
98-
wrapped('data', {
118+
describe('callable functions', () => {
119+
let wrappedCF;
120+
121+
before(() => {
122+
const cloudFunction = (input) => input;
123+
set(cloudFunction, 'run', (data, context) => {
124+
return { data, context };
125+
});
126+
set(cloudFunction, '__trigger', {
127+
labels: {
128+
'deployment-callable': 'true',
129+
},
130+
httpsTrigger: {},
131+
});
132+
wrappedCF = wrap(cloudFunction as functions.CloudFunction<any>);
133+
});
134+
135+
it('should invoke the function with the supplied data', () => {
136+
expect(wrappedCF('data').data).to.equal('data');
137+
});
138+
139+
it('should allow specification of context fields', () => {
140+
const context = wrappedCF('data', {
99141
auth: { uid: 'abc' },
100-
isInvalid: true,
101-
} as any)
102-
).to.throw();
103-
});
142+
app: { appId: 'efg' },
143+
instanceIdToken: '123',
144+
rawRequest: { body: 'hello' }
145+
}).context;
146+
expect(context.auth).to.deep.equal({ uid: 'abc' });
147+
expect(context.app).to.deep.equal({ appId: 'efg' });
148+
expect(context.instanceIdToken).to.equal('123');
149+
expect(context.rawRequest).to.deep.equal({ body: 'hello'});
150+
});
151+
152+
it('should throw when passed invalid options', () => {
153+
expect(() =>
154+
wrappedCF('data', {
155+
auth: { uid: 'abc' },
156+
isInvalid: true,
157+
} as any)
158+
).to.throw();
159+
});
104160

105-
it('should generate the appropriate resource based on params', () => {
106-
const params = {
107-
wildcard: 'a',
108-
anotherWildcard: 'b',
109-
};
110-
const wrapped = wrap(constructCF());
111-
const context = wrapped('data', { params }).context;
112-
expect(context.params).to.deep.equal(params);
113-
expect(context.resource.name).to.equal('ref/a/nested/b');
114161
});
115162
});
116163

src/main.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,11 @@ export type EventContextOptions = {
6464

6565
/** Fields of the callable context that can be overridden/customized. */
6666
export type CallableContextOptions = {
67+
/**
68+
* The result of decoding and verifying a Firebase AppCheck token.
69+
*/
70+
app?: any;
71+
6772
/**
6873
* The result of decoding and verifying a Firebase Auth ID token.
6974
*/
@@ -149,7 +154,7 @@ export function wrap<T>(
149154
let context;
150155

151156
if (isCallableFunction) {
152-
_checkOptionValidity(['auth', 'instanceIdToken', 'rawRequest'], options);
157+
_checkOptionValidity(['app', 'auth', 'instanceIdToken', 'rawRequest'], options);
153158
let callableContextOptions = options as CallableContextOptions;
154159
context = {
155160
...callableContextOptions,

0 commit comments

Comments
 (0)