@@ -104,13 +104,22 @@ describe("api", () => {
104104 false ,
105105 ] ,
106106 [
107- "should handle null/undefined config values" ,
107+ "should handle null config values" ,
108108 { "coder.tlsCertFile" : null , "coder.tlsKeyFile" : null } ,
109109 true ,
110110 ] ,
111+ [
112+ "should handle undefined config values" ,
113+ { "coder.tlsCertFile" : undefined , "coder.tlsKeyFile" : undefined } ,
114+ true ,
115+ ] ,
116+ [ "should handle missing config entries" , { } , true ] ,
111117 ] ) ( "%s" , ( _ , configValues : Record < string , unknown > , expected ) => {
112118 mockConfiguration . get . mockImplementation ( ( key : string ) => {
113- return configValues [ key ] ?? "" ;
119+ if ( key in configValues ) {
120+ return configValues [ key ] ;
121+ }
122+ return undefined ;
114123 } ) ;
115124
116125 // Mock expandPath to return the path as-is
@@ -173,12 +182,32 @@ describe("api", () => {
173182 rejectUnauthorized : true ,
174183 } ,
175184 ] ,
185+ [
186+ "undefined configuration values" ,
187+ {
188+ "coder.tlsCertFile" : undefined ,
189+ "coder.tlsKeyFile" : undefined ,
190+ "coder.tlsCaFile" : undefined ,
191+ "coder.tlsAltHost" : undefined ,
192+ "coder.insecure" : undefined ,
193+ } ,
194+ {
195+ cert : undefined ,
196+ key : undefined ,
197+ ca : undefined ,
198+ servername : undefined ,
199+ rejectUnauthorized : true ,
200+ } ,
201+ ] ,
176202 ] ) (
177203 "should create ProxyAgent with %s" ,
178204 async ( _ , configValues : Record < string , unknown > , expectedAgentConfig ) => {
179- mockConfiguration . get . mockImplementation (
180- ( key : string ) => configValues [ key ] ?? "" ,
181- ) ;
205+ mockConfiguration . get . mockImplementation ( ( key : string ) => {
206+ if ( key in configValues ) {
207+ return configValues [ key ] ;
208+ }
209+ return undefined ;
210+ } ) ;
182211
183212 if ( configValues [ "coder.tlsCertFile" ] ) {
184213 vi . mocked ( fs . readFile )
@@ -374,6 +403,171 @@ describe("api", () => {
374403 expect . objectContaining ( { url : "https://example.com/api" } ) ,
375404 ) ;
376405 } ) ;
406+
407+ it ( "should handle stream data events" , async ( ) => {
408+ let dataHandler : ( chunk : Buffer ) => void ;
409+ const mockData = {
410+ on : vi . fn ( ( event : string , handler : ( chunk : Buffer ) => void ) => {
411+ if ( event === "data" ) {
412+ dataHandler = handler ;
413+ }
414+ } ) ,
415+ destroy : vi . fn ( ) ,
416+ } ;
417+
418+ const mockAxiosInstance = {
419+ request : vi
420+ . fn ( )
421+ . mockResolvedValue ( createMockAxiosResponse ( { data : mockData } ) ) ,
422+ } ;
423+
424+ const adapter = createStreamingFetchAdapter ( mockAxiosInstance as never ) ;
425+
426+ let enqueuedData : Buffer | undefined ;
427+ global . ReadableStream = vi . fn ( ) . mockImplementation ( ( options ) => {
428+ const controller = {
429+ enqueue : vi . fn ( ( chunk : Buffer ) => {
430+ enqueuedData = chunk ;
431+ } ) ,
432+ close : vi . fn ( ) ,
433+ error : vi . fn ( ) ,
434+ } ;
435+ if ( options . start ) {
436+ options . start ( controller ) ;
437+ }
438+ return { getReader : vi . fn ( ( ) => ( { read : vi . fn ( ) } ) ) } ;
439+ } ) as never ;
440+
441+ await adapter ( "https://example.com/api" ) ;
442+
443+ // Simulate data event
444+ const testData = Buffer . from ( "test data" ) ;
445+ dataHandler ! ( testData ) ;
446+
447+ expect ( enqueuedData ) . toEqual ( testData ) ;
448+ expect ( mockData . on ) . toHaveBeenCalledWith ( "data" , expect . any ( Function ) ) ;
449+ } ) ;
450+
451+ it ( "should handle stream end event" , async ( ) => {
452+ let endHandler : ( ) => void ;
453+ const mockData = {
454+ on : vi . fn ( ( event : string , handler : ( ) => void ) => {
455+ if ( event === "end" ) {
456+ endHandler = handler ;
457+ }
458+ } ) ,
459+ destroy : vi . fn ( ) ,
460+ } ;
461+
462+ const mockAxiosInstance = {
463+ request : vi
464+ . fn ( )
465+ . mockResolvedValue ( createMockAxiosResponse ( { data : mockData } ) ) ,
466+ } ;
467+
468+ const adapter = createStreamingFetchAdapter ( mockAxiosInstance as never ) ;
469+
470+ let streamClosed = false ;
471+ global . ReadableStream = vi . fn ( ) . mockImplementation ( ( options ) => {
472+ const controller = {
473+ enqueue : vi . fn ( ) ,
474+ close : vi . fn ( ( ) => {
475+ streamClosed = true ;
476+ } ) ,
477+ error : vi . fn ( ) ,
478+ } ;
479+ if ( options . start ) {
480+ options . start ( controller ) ;
481+ }
482+ return { getReader : vi . fn ( ( ) => ( { read : vi . fn ( ) } ) ) } ;
483+ } ) as never ;
484+
485+ await adapter ( "https://example.com/api" ) ;
486+
487+ // Simulate end event
488+ endHandler ! ( ) ;
489+
490+ expect ( streamClosed ) . toBe ( true ) ;
491+ expect ( mockData . on ) . toHaveBeenCalledWith ( "end" , expect . any ( Function ) ) ;
492+ } ) ;
493+
494+ it ( "should handle stream error event" , async ( ) => {
495+ let errorHandler : ( err : Error ) => void ;
496+ const mockData = {
497+ on : vi . fn ( ( event : string , handler : ( err : Error ) => void ) => {
498+ if ( event === "error" ) {
499+ errorHandler = handler ;
500+ }
501+ } ) ,
502+ destroy : vi . fn ( ) ,
503+ } ;
504+
505+ const mockAxiosInstance = {
506+ request : vi
507+ . fn ( )
508+ . mockResolvedValue ( createMockAxiosResponse ( { data : mockData } ) ) ,
509+ } ;
510+
511+ const adapter = createStreamingFetchAdapter ( mockAxiosInstance as never ) ;
512+
513+ let streamError : Error | undefined ;
514+ global . ReadableStream = vi . fn ( ) . mockImplementation ( ( options ) => {
515+ const controller = {
516+ enqueue : vi . fn ( ) ,
517+ close : vi . fn ( ) ,
518+ error : vi . fn ( ( err : Error ) => {
519+ streamError = err ;
520+ } ) ,
521+ } ;
522+ if ( options . start ) {
523+ options . start ( controller ) ;
524+ }
525+ return { getReader : vi . fn ( ( ) => ( { read : vi . fn ( ) } ) ) } ;
526+ } ) as never ;
527+
528+ await adapter ( "https://example.com/api" ) ;
529+
530+ // Simulate error event
531+ const testError = new Error ( "Stream error" ) ;
532+ errorHandler ! ( testError ) ;
533+
534+ expect ( streamError ) . toBe ( testError ) ;
535+ expect ( mockData . on ) . toHaveBeenCalledWith ( "error" , expect . any ( Function ) ) ;
536+ } ) ;
537+
538+ it ( "should handle stream cancel" , async ( ) => {
539+ const mockData = {
540+ on : vi . fn ( ) ,
541+ destroy : vi . fn ( ) ,
542+ } ;
543+
544+ const mockAxiosInstance = {
545+ request : vi
546+ . fn ( )
547+ . mockResolvedValue ( createMockAxiosResponse ( { data : mockData } ) ) ,
548+ } ;
549+
550+ const adapter = createStreamingFetchAdapter ( mockAxiosInstance as never ) ;
551+
552+ let cancelFunction : ( ( ) => Promise < void > ) | undefined ;
553+ global . ReadableStream = vi . fn ( ) . mockImplementation ( ( options ) => {
554+ if ( options . cancel ) {
555+ cancelFunction = options . cancel ;
556+ }
557+ if ( options . start ) {
558+ options . start ( { enqueue : vi . fn ( ) , close : vi . fn ( ) , error : vi . fn ( ) } ) ;
559+ }
560+ return { getReader : vi . fn ( ( ) => ( { read : vi . fn ( ) } ) ) } ;
561+ } ) as never ;
562+
563+ await adapter ( "https://example.com/api" ) ;
564+
565+ // Call cancel
566+ expect ( cancelFunction ) . toBeDefined ( ) ;
567+ await cancelFunction ! ( ) ;
568+
569+ expect ( mockData . destroy ) . toHaveBeenCalled ( ) ;
570+ } ) ;
377571 } ) ;
378572
379573 describe ( "startWorkspaceIfStoppedOrFailed" , ( ) => {
0 commit comments