Skip to content

Commit 5b41c0f

Browse files
committed
Support for scheduled functions
1 parent f1dd34d commit 5b41c0f

File tree

3 files changed

+77
-1
lines changed

3 files changed

+77
-1
lines changed

spec/index.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ import './lifecycle.spec';
6363
import './main.spec';
6464
import './app.spec';
6565
import './providers/https.spec';
66+
import './providers/scheduled.spec';
6667
// import './providers/analytics.spec';
6768
// import './providers/auth.spec';
6869
// import './providers/database.spec';

spec/providers/scheduled.spec.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import * as sinon from 'sinon';
2+
import * as functions from 'firebase-functions';
3+
import fft = require('../../src/index');
4+
import { WrappedScheduledFunction } from '../../src/main';
5+
6+
describe('providers/scheduled', () => {
7+
8+
const fakeFn = sinon.fake.resolves();
9+
const scheduledFunc = functions.pubsub.schedule('every 2 hours').onRun(fakeFn);
10+
11+
const emptyObjectMatcher = sinon.match((v) => sinon.match.object.test(v) && Object.keys(v).length === 0);
12+
13+
afterEach(() => {
14+
fakeFn.resetHistory();
15+
});
16+
17+
it('should run the wrapped function with generated context', async () => {
18+
const test = fft();
19+
const fn: WrappedScheduledFunction = test.wrap(scheduledFunc);
20+
await fn();
21+
// Function should only be called with 1 argument
22+
sinon.assert.calledOnce(fakeFn);
23+
sinon.assert.calledWithExactly(fakeFn, sinon.match({
24+
eventType: sinon.match.string,
25+
timestamp: sinon.match.string,
26+
params: emptyObjectMatcher,
27+
}));
28+
});
29+
30+
it('should run the wrapped function with provided context', async () => {
31+
const timestamp = new Date().toISOString();
32+
const test = fft();
33+
const fn: WrappedScheduledFunction = test.wrap(scheduledFunc);
34+
await fn({ timestamp });
35+
sinon.assert.calledOnce(fakeFn);
36+
sinon.assert.calledWithExactly(fakeFn, sinon.match({
37+
eventType: sinon.match.string,
38+
timestamp,
39+
params: emptyObjectMatcher,
40+
}));
41+
});
42+
});

src/main.ts

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,12 +65,45 @@ export type ContextOptions = EventContextOptions | CallableContextOptions;
6565
*/
6666
export type WrappedFunction = (data: any, options?: ContextOptions) => any | Promise<any>;
6767

68+
/** A scheduled function that can be called with optional override values for the event context.
69+
* It will subsequently invoke the cloud function it wraps with a generated event context.
70+
*/
71+
export type WrappedScheduledFunction = (options?: ContextOptions) => any | Promise<any>;
72+
6873
/** Takes a cloud function to be tested, and returns a WrappedFunction which can be called in test code. */
69-
export function wrap<T>(cloudFunction: CloudFunction<T>): WrappedFunction {
74+
export function wrap<T>(cloudFunction: CloudFunction<T>): WrappedScheduledFunction | WrappedFunction {
7075
if (!has(cloudFunction, '__trigger')) {
7176
throw new Error('Wrap can only be called on functions written with the firebase-functions SDK.');
7277
}
7378

79+
if (get(cloudFunction, '__trigger.labels.deployment-scheduled') === 'true') {
80+
const scheduledWrapped: WrappedScheduledFunction = (options: ContextOptions) => {
81+
// Although in Typescript we require `options` some of our JS samples do not pass it.
82+
options = options || {};
83+
84+
_checkOptionValidity(['eventId', 'timestamp'], options);
85+
let eventContextOptions = options as EventContextOptions;
86+
const defaultContext: EventContext = {
87+
eventId: _makeEventId(),
88+
resource: cloudFunction.__trigger.eventTrigger && {
89+
service: cloudFunction.__trigger.eventTrigger.service,
90+
name: _makeResourceName(
91+
cloudFunction.__trigger.eventTrigger.resource,
92+
has(eventContextOptions, 'params') && eventContextOptions.params,
93+
),
94+
},
95+
eventType: get(cloudFunction, '__trigger.eventTrigger.eventType'),
96+
timestamp: (new Date()).toISOString(),
97+
params: {},
98+
};
99+
const context = merge({}, defaultContext, eventContextOptions);
100+
101+
// @ts-ignore
102+
return cloudFunction.run(context);
103+
};
104+
return scheduledWrapped;
105+
}
106+
74107
if (has(cloudFunction, '__trigger.httpsTrigger') &&
75108
(get(cloudFunction, '__trigger.labels.deployment-callable') !== 'true')) {
76109
throw new Error('Wrap function is only available for `onCall` HTTP functions, not `onRequest`.');

0 commit comments

Comments
 (0)