@@ -87,6 +87,11 @@ describe("CoderApi", () => {
8787 mockConfig . set ( "coder.httpClientLogLevel" , "BASIC" ) ;
8888 } ) ;
8989
90+ afterEach ( ( ) => {
91+ // Dispose any api created during the test to clean up config watchers
92+ api ?. dispose ( ) ;
93+ } ) ;
94+
9095 describe ( "HTTP Interceptors" , ( ) => {
9196 it ( "adds custom headers and HTTP agent to requests" , async ( ) => {
9297 const mockAgent = new ProxyAgent ( ) ;
@@ -429,24 +434,24 @@ describe("CoderApi", () => {
429434 } ) ;
430435 } ) ;
431436
432- describe ( "Reconnection on Host/Token Changes" , ( ) => {
433- const setupAutoOpeningWebSocket = ( ) => {
434- const sockets : Array < Partial < Ws > > = [ ] ;
435- vi . mocked ( Ws ) . mockImplementation ( ( url : string | URL ) => {
436- const mockWs = createMockWebSocket ( String ( url ) , {
437- on : vi . fn ( ( event , handler ) => {
438- if ( event === "open" ) {
439- setImmediate ( ( ) => handler ( ) ) ;
440- }
441- return mockWs as Ws ;
442- } ) ,
443- } ) ;
444- sockets . push ( mockWs ) ;
445- return mockWs as Ws ;
437+ const setupAutoOpeningWebSocket = ( ) => {
438+ const sockets : Array < Partial < Ws > > = [ ] ;
439+ vi . mocked ( Ws ) . mockImplementation ( ( url : string | URL ) => {
440+ const mockWs = createMockWebSocket ( String ( url ) , {
441+ on : vi . fn ( ( event , handler ) => {
442+ if ( event === "open" ) {
443+ setImmediate ( ( ) => handler ( ) ) ;
444+ }
445+ return mockWs as Ws ;
446+ } ) ,
446447 } ) ;
447- return sockets ;
448- } ;
448+ sockets . push ( mockWs ) ;
449+ return mockWs as Ws ;
450+ } ) ;
451+ return sockets ;
452+ } ;
449453
454+ describe ( "Reconnection on Host/Token Changes" , ( ) => {
450455 it ( "triggers reconnection when session token changes" , async ( ) => {
451456 const sockets = setupAutoOpeningWebSocket ( ) ;
452457 api = createApi ( CODER_URL , AXIOS_TOKEN ) ;
@@ -593,6 +598,55 @@ describe("CoderApi", () => {
593598 expect ( sockets [ 0 ] . close ) . toHaveBeenCalled ( ) ;
594599 } ) ;
595600 } ) ;
601+
602+ describe ( "Configuration Change Reconnection" , ( ) => {
603+ const tick = ( ) => new Promise ( ( resolve ) => setImmediate ( resolve ) ) ;
604+
605+ it ( "reconnects sockets when watched config value changes" , async ( ) => {
606+ mockConfig . set ( "coder.insecure" , false ) ;
607+ const sockets = setupAutoOpeningWebSocket ( ) ;
608+ api = createApi ( CODER_URL , AXIOS_TOKEN ) ;
609+ await api . watchAgentMetadata ( AGENT_ID ) ;
610+
611+ mockConfig . set ( "coder.insecure" , true ) ;
612+ await tick ( ) ;
613+
614+ expect ( sockets [ 0 ] . close ) . toHaveBeenCalledWith (
615+ 1000 ,
616+ "Replacing connection" ,
617+ ) ;
618+ expect ( sockets ) . toHaveLength ( 2 ) ;
619+ } ) ;
620+
621+ it . each ( [
622+ [ "unchanged value" , "coder.insecure" , false ] ,
623+ [ "unrelated setting" , "unrelated.setting" , "new-value" ] ,
624+ ] ) ( "does not reconnect for %s" , async ( _desc , key , value ) => {
625+ mockConfig . set ( "coder.insecure" , false ) ;
626+ const sockets = setupAutoOpeningWebSocket ( ) ;
627+ api = createApi ( CODER_URL , AXIOS_TOKEN ) ;
628+ await api . watchAgentMetadata ( AGENT_ID ) ;
629+
630+ mockConfig . set ( key , value ) ;
631+ await tick ( ) ;
632+
633+ expect ( sockets [ 0 ] . close ) . not . toHaveBeenCalled ( ) ;
634+ expect ( sockets ) . toHaveLength ( 1 ) ;
635+ } ) ;
636+
637+ it ( "stops watching after dispose" , async ( ) => {
638+ mockConfig . set ( "coder.insecure" , false ) ;
639+ const sockets = setupAutoOpeningWebSocket ( ) ;
640+ api = createApi ( CODER_URL , AXIOS_TOKEN ) ;
641+ await api . watchAgentMetadata ( AGENT_ID ) ;
642+
643+ api . dispose ( ) ;
644+ mockConfig . set ( "coder.insecure" , true ) ;
645+ await tick ( ) ;
646+
647+ expect ( sockets ) . toHaveLength ( 1 ) ;
648+ } ) ;
649+ } ) ;
596650} ) ;
597651
598652const mockAdapterImpl = vi . hoisted ( ( ) => ( config : Record < string , unknown > ) => {
0 commit comments