22
22
23
23
import { has , merge , random , get } from 'lodash' ;
24
24
25
- import { CloudFunction , EventContext , Change } from 'firebase-functions' ;
25
+ import { CloudFunction , EventContext , Change , https } from 'firebase-functions' ;
26
26
27
27
/** Fields of the event context that can be overridden/customized. */
28
28
export type EventContextOptions = {
@@ -33,7 +33,7 @@ export type EventContextOptions = {
33
33
/** The values for the wildcards in the reference path that a database or Firestore function is listening to.
34
34
* If omitted, random values will be generated.
35
35
*/
36
- params ?: { [ option : string ] : any } ;
36
+ params ?: { [ option : string ] : any } ;
37
37
/** (Only for database functions and https.onCall.) Firebase auth variable representing the user that triggered
38
38
* the function. Defaults to null.
39
39
*/
@@ -55,6 +55,11 @@ export type CallableContextOptions = {
55
55
* An unverified token for a Firebase Instance ID.
56
56
*/
57
57
instanceIdToken ?: string ;
58
+
59
+ /**
60
+ * The raw HTTP request object.
61
+ */
62
+ rawRequest ?: https . Request ;
58
63
} ;
59
64
60
65
/* Fields for both Event and Callable contexts, checked at runtime */
@@ -63,73 +68,90 @@ export type ContextOptions = EventContextOptions | CallableContextOptions;
63
68
/** A function that can be called with test data and optional override values for the event context.
64
69
* It will subsequently invoke the cloud function it wraps with the provided test data and a generated event context.
65
70
*/
66
- export type WrappedFunction = ( data : any , options ?: ContextOptions ) => any | Promise < any > ;
71
+ export type WrappedFunction = (
72
+ data : any ,
73
+ options ?: ContextOptions ,
74
+ ) => any | Promise < any > ;
67
75
68
76
/** Takes a cloud function to be tested, and returns a WrappedFunction which can be called in test code. */
69
77
export function wrap < T > ( cloudFunction : CloudFunction < T > ) : WrappedFunction {
70
78
if ( ! has ( cloudFunction , '__trigger' ) ) {
71
- throw new Error ( 'Wrap can only be called on functions written with the firebase-functions SDK.' ) ;
79
+ throw new Error (
80
+ 'Wrap can only be called on functions written with the firebase-functions SDK.' ,
81
+ ) ;
72
82
}
73
83
74
- if ( has ( cloudFunction , '__trigger.httpsTrigger' ) &&
75
- ( get ( cloudFunction , '__trigger.labels.deployment-callable' ) !== 'true' ) ) {
76
- throw new Error ( 'Wrap function is only available for `onCall` HTTP functions, not `onRequest`.' ) ;
84
+ if (
85
+ has ( cloudFunction , '__trigger.httpsTrigger' ) &&
86
+ get ( cloudFunction , '__trigger.labels.deployment-callable' ) !== 'true'
87
+ ) {
88
+ throw new Error (
89
+ 'Wrap function is only available for `onCall` HTTP functions, not `onRequest`.' ,
90
+ ) ;
77
91
}
78
92
79
93
if ( ! has ( cloudFunction , 'run' ) ) {
80
- throw new Error ( 'This library can only be used with functions written with firebase-functions v1.0.0 and above' ) ;
94
+ throw new Error (
95
+ 'This library can only be used with functions written with firebase-functions v1.0.0 and above' ,
96
+ ) ;
81
97
}
82
98
83
- const isCallableFunction = get ( cloudFunction , '__trigger.labels.deployment-callable' ) === 'true' ;
99
+ const isCallableFunction =
100
+ get ( cloudFunction , '__trigger.labels.deployment-callable' ) === 'true' ;
84
101
85
102
let wrapped : WrappedFunction = ( data : T , options : ContextOptions ) => {
86
103
// Although in Typescript we require `options` some of our JS samples do not pass it.
87
104
options = options || { } ;
88
105
let context ;
89
106
90
107
if ( isCallableFunction ) {
91
- _checkOptionValidity ( [ 'auth' , 'instanceIdToken' ] , options ) ;
108
+ _checkOptionValidity ( [ 'auth' , 'instanceIdToken' , 'rawRequest' ] , options ) ;
92
109
let callableContextOptions = options as CallableContextOptions ;
93
110
context = {
94
- ...callableContextOptions ,
95
- rawRequest : 'rawRequest is not supported in firebase-functions-test' ,
111
+ ...callableContextOptions ,
96
112
} ;
97
113
} else {
98
- _checkOptionValidity ( [ 'eventId' , 'timestamp' , 'params' , 'auth' , 'authType' ] , options ) ;
114
+ _checkOptionValidity (
115
+ [ 'eventId' , 'timestamp' , 'params' , 'auth' , 'authType' ] ,
116
+ options ,
117
+ ) ;
99
118
let eventContextOptions = options as EventContextOptions ;
100
119
const defaultContext : EventContext = {
101
- eventId : _makeEventId ( ) ,
102
- resource : cloudFunction . __trigger . eventTrigger && {
103
- service : cloudFunction . __trigger . eventTrigger . service ,
104
- name : _makeResourceName (
105
- cloudFunction . __trigger . eventTrigger . resource ,
106
- has ( eventContextOptions , 'params' ) && eventContextOptions . params ,
107
- ) ,
108
- } ,
109
- eventType : get ( cloudFunction , '__trigger.eventTrigger.eventType' ) ,
110
- timestamp : ( new Date ( ) ) . toISOString ( ) ,
111
- params : { } ,
120
+ eventId : _makeEventId ( ) ,
121
+ resource : cloudFunction . __trigger . eventTrigger && {
122
+ service : cloudFunction . __trigger . eventTrigger . service ,
123
+ name : _makeResourceName (
124
+ cloudFunction . __trigger . eventTrigger . resource ,
125
+ has ( eventContextOptions , 'params' ) && eventContextOptions . params ,
126
+ ) ,
127
+ } ,
128
+ eventType : get ( cloudFunction , '__trigger.eventTrigger.eventType' ) ,
129
+ timestamp : new Date ( ) . toISOString ( ) ,
130
+ params : { } ,
112
131
} ;
113
132
114
- if ( has ( defaultContext , 'eventType' ) && defaultContext . eventType !== undefined &&
115
- defaultContext . eventType . match ( / f i r e b a s e .d a t a b a s e / ) ) {
133
+ if (
134
+ has ( defaultContext , 'eventType' ) &&
135
+ defaultContext . eventType !== undefined &&
136
+ defaultContext . eventType . match ( / f i r e b a s e .d a t a b a s e / )
137
+ ) {
116
138
defaultContext . authType = 'UNAUTHENTICATED' ;
117
139
defaultContext . auth = null ;
118
140
}
119
141
context = merge ( { } , defaultContext , eventContextOptions ) ;
120
142
}
121
143
122
- return cloudFunction . run (
123
- data ,
124
- context ,
125
- ) ;
144
+ return cloudFunction . run ( data , context ) ;
126
145
} ;
127
146
128
147
return wrapped ;
129
148
}
130
149
131
150
/** @internal */
132
- export function _makeResourceName ( triggerResource : string , params = { } ) : string {
151
+ export function _makeResourceName (
152
+ triggerResource : string ,
153
+ params = { } ,
154
+ ) : string {
133
155
const wildcardRegex = new RegExp ( '{[^/{}]*}' , 'g' ) ;
134
156
let resourceName = triggerResource . replace ( wildcardRegex , ( wildcard ) => {
135
157
let wildcardNoBraces = wildcard . slice ( 1 , - 1 ) ; // .slice removes '{' and '}' from wildcard
@@ -140,15 +162,27 @@ export function _makeResourceName(triggerResource: string, params = {}): string
140
162
}
141
163
142
164
function _makeEventId ( ) : string {
143
- return Math . random ( ) . toString ( 36 ) . substring ( 2 , 15 ) + Math . random ( ) . toString ( 36 ) . substring ( 2 , 15 ) ;
165
+ return (
166
+ Math . random ( )
167
+ . toString ( 36 )
168
+ . substring ( 2 , 15 ) +
169
+ Math . random ( )
170
+ . toString ( 36 )
171
+ . substring ( 2 , 15 )
172
+ ) ;
144
173
}
145
174
146
- function _checkOptionValidity ( validFields : string [ ] , options : { [ s : string ] : any } ) {
147
- Object . keys ( options ) . forEach ( ( key ) => {
148
- if ( validFields . indexOf ( key ) === - 1 ) {
149
- throw new Error ( `Options object ${ JSON . stringify ( options ) } has invalid key "${ key } "` ) ;
150
- }
151
- } ) ;
175
+ function _checkOptionValidity (
176
+ validFields : string [ ] ,
177
+ options : { [ s : string ] : any } ,
178
+ ) {
179
+ Object . keys ( options ) . forEach ( ( key ) => {
180
+ if ( validFields . indexOf ( key ) === - 1 ) {
181
+ throw new Error (
182
+ `Options object ${ JSON . stringify ( options ) } has invalid key "${ key } "` ,
183
+ ) ;
184
+ }
185
+ } ) ;
152
186
}
153
187
154
188
/** Make a Change object to be used as test data for Firestore and real time database onWrite and onUpdate functions. */
0 commit comments