@@ -92,13 +92,19 @@ describe('Firebase Functions > Call', () => {
92
92
Record < string , any > ,
93
93
{ message : string ; code : number ; long : number }
94
94
> ( functions , 'dataTest' ) ;
95
- const result = await func ( data ) ;
95
+ try {
96
+
97
+ const result = await func ( data ) ;
98
+
99
+ expect ( result . data ) . to . deep . equal ( {
100
+ message : 'stub response' ,
101
+ code : 42 ,
102
+ long : 420
103
+ } ) ;
104
+ } catch ( err ) {
105
+ console . error ( err )
106
+ }
96
107
97
- expect ( result . data ) . to . deep . equal ( {
98
- message : 'stub response' ,
99
- code : 42 ,
100
- long : 420
101
- } ) ;
102
108
} ) ;
103
109
104
110
it ( 'scalars' , async ( ) => {
@@ -226,3 +232,155 @@ describe('Firebase Functions > Call', () => {
226
232
await expectError ( func ( ) , 'deadline-exceeded' , 'deadline-exceeded' ) ;
227
233
} ) ;
228
234
} ) ;
235
+
236
+ describe ( 'Firebase Functions > Stream' , ( ) => {
237
+ let app : FirebaseApp ;
238
+ const region = 'us-central1' ;
239
+
240
+ before ( ( ) => {
241
+ const useEmulator = ! ! process . env . FIREBASE_FUNCTIONS_EMULATOR_ORIGIN ;
242
+ const projectId = useEmulator
243
+ ? 'functions-integration-test'
244
+ : TEST_PROJECT . projectId ;
245
+ const messagingSenderId = 'messaging-sender-id' ;
246
+ app = makeFakeApp ( { projectId, messagingSenderId } ) ;
247
+ } ) ;
248
+
249
+ it ( 'successfully streams data and resolves final result' , async ( ) => {
250
+ const functions = createTestService ( app , region ) ;
251
+ const mockFetch = sinon . stub ( functions , 'fetchImpl' as any ) ;
252
+
253
+ const mockResponse = new ReadableStream ( {
254
+ start ( controller ) {
255
+ controller . enqueue ( new TextEncoder ( ) . encode ( 'data: {"message":"Hello"}\n' ) ) ;
256
+ controller . enqueue ( new TextEncoder ( ) . encode ( 'data: {"message":"World"}\n' ) ) ;
257
+ controller . enqueue ( new TextEncoder ( ) . encode ( 'data: {"result":"Final Result"}\n' ) ) ;
258
+ controller . close ( ) ;
259
+ }
260
+ } ) ;
261
+
262
+ mockFetch . resolves ( {
263
+ body : mockResponse ,
264
+ headers : new Headers ( { 'Content-Type' : 'text/event-stream' } ) ,
265
+ status : 200 ,
266
+ statusText : 'OK' ,
267
+ } as Response ) ;
268
+
269
+ const func = httpsCallable < Record < string , any > , string , string > ( functions , 'streamTest' ) ;
270
+ const streamResult = await func . stream ( { } ) ;
271
+
272
+ const messages : string [ ] = [ ] ;
273
+ for await ( const message of streamResult . stream ) {
274
+ messages . push ( message ) ;
275
+ }
276
+
277
+ expect ( messages ) . to . deep . equal ( [ 'Hello' , 'World' ] ) ;
278
+ expect ( await streamResult . data ) . to . equal ( 'Final Result' ) ;
279
+
280
+ mockFetch . restore ( ) ;
281
+ } ) ;
282
+
283
+ it ( 'handles network errors' , async ( ) => {
284
+ const functions = createTestService ( app , region ) ;
285
+ const mockFetch = sinon . stub ( functions , 'fetchImpl' as any ) ;
286
+
287
+ mockFetch . rejects ( new Error ( 'Network error' ) ) ;
288
+
289
+ const func = httpsCallable < Record < string , any > , string , string > ( functions , 'errTest' ) ;
290
+ const streamResult = await func . stream ( { } ) ;
291
+
292
+ let errorThrown = false ;
293
+ try {
294
+ for await ( const _ of streamResult . stream ) {
295
+ // This should not execute
296
+ }
297
+ } catch ( error : unknown ) {
298
+ errorThrown = true ;
299
+ expect ( ( error as FunctionsError ) . code ) . to . equal ( `${ FUNCTIONS_TYPE } /internal` ) ;
300
+ }
301
+
302
+ expect ( errorThrown ) . to . be . true ;
303
+ expect ( streamResult . data ) . to . be . a ( 'promise' ) ;
304
+
305
+ mockFetch . restore ( ) ;
306
+ } ) ;
307
+
308
+ it ( 'handles server-side errors' , async ( ) => {
309
+ const functions = createTestService ( app , region ) ;
310
+ const mockFetch = sinon . stub ( functions , 'fetchImpl' as any ) ;
311
+
312
+ const mockResponse = new ReadableStream ( {
313
+ start ( controller ) {
314
+ controller . enqueue ( new TextEncoder ( ) . encode ( 'data: {"error":{"status":"INVALID_ARGUMENT","message":"Invalid input"}}\n' ) ) ;
315
+ controller . close ( ) ;
316
+ }
317
+ } ) ;
318
+
319
+ mockFetch . resolves ( {
320
+ body : mockResponse ,
321
+ headers : new Headers ( { 'Content-Type' : 'text/event-stream' } ) ,
322
+ status : 200 ,
323
+ statusText : 'OK' ,
324
+ } as Response ) ;
325
+
326
+ const func = httpsCallable < Record < string , any > , string , string > ( functions , 'errTest' ) ;
327
+ const streamResult = await func . stream ( { } ) ;
328
+
329
+ let errorThrown = false ;
330
+ try {
331
+ for await ( const _ of streamResult . stream ) {
332
+ // This should not execute
333
+ }
334
+ } catch ( error ) {
335
+ errorThrown = true ;
336
+ expect ( ( error as FunctionsError ) . code ) . to . equal ( `${ FUNCTIONS_TYPE } /invalid-argument` ) ;
337
+ expect ( ( error as FunctionsError ) . message ) . to . equal ( 'Invalid input' ) ;
338
+ }
339
+
340
+ expect ( errorThrown ) . to . be . true ;
341
+ expectError ( streamResult . data , "invalid-argument" , "Invalid input" )
342
+
343
+ mockFetch . restore ( ) ;
344
+ } ) ;
345
+
346
+ it ( 'includes authentication and app check tokens in request headers' , async ( ) => {
347
+ const authMock : FirebaseAuthInternal = {
348
+ getToken : async ( ) => ( { accessToken : 'auth-token' } )
349
+ } as unknown as FirebaseAuthInternal ;
350
+ const authProvider = new Provider < FirebaseAuthInternalName > (
351
+ 'auth-internal' ,
352
+ new ComponentContainer ( 'test' )
353
+ ) ;
354
+ authProvider . setComponent (
355
+ new Component ( 'auth-internal' , ( ) => authMock , ComponentType . PRIVATE )
356
+ ) ;
357
+
358
+ const functions = createTestService ( app , region , authProvider ) ;
359
+ const mockFetch = sinon . stub ( functions , 'fetchImpl' as any ) ;
360
+
361
+ const mockResponse = new ReadableStream ( {
362
+ start ( controller ) {
363
+ controller . enqueue ( new TextEncoder ( ) . encode ( 'data: {"result":"Success"}\n' ) ) ;
364
+ controller . close ( ) ;
365
+ }
366
+ } ) ;
367
+
368
+ mockFetch . resolves ( {
369
+ body : mockResponse ,
370
+ headers : new Headers ( { 'Content-Type' : 'text/event-stream' } ) ,
371
+ status : 200 ,
372
+ statusText : 'OK' ,
373
+ } as Response ) ;
374
+
375
+ const func = httpsCallable < Record < string , any > , string , string > ( functions , 'errTest' ) ;
376
+ await func . stream ( { } ) ;
377
+
378
+ expect ( mockFetch . calledOnce ) . to . be . true ;
379
+ const [ _ , options ] = mockFetch . firstCall . args ;
380
+ expect ( options . headers [ 'Authorization' ] ) . to . equal ( 'Bearer auth-token' ) ;
381
+ expect ( options . headers [ 'Content-Type' ] ) . to . equal ( 'application/json' ) ;
382
+ expect ( options . headers [ 'Accept' ] ) . to . equal ( 'text/event-stream' ) ;
383
+
384
+ mockFetch . restore ( ) ;
385
+ } ) ;
386
+ } ) ;
0 commit comments