@@ -426,24 +426,29 @@ describe('Configuration Update Handling', () => {
426426 let tempUserFile : string ;
427427 let oldEnv : NodeJS . ProcessEnv ;
428428
429+ const waitForMockCall = async ( mock : MockInstance , callCount = 1 ) => {
430+ for ( let i = 0 ; i < 20 ; i += 1 ) {
431+ if ( mock . mock . calls . length >= callCount ) {
432+ return ;
433+ }
434+ await new Promise ( ( resolve ) => setTimeout ( resolve , 10 ) ) ;
435+ }
436+ } ;
437+
429438 beforeEach ( ( ) => {
430439 oldEnv = { ...process . env } ;
431440 tempDir = fs . mkdtempSync ( 'gitproxy-test' ) ;
432441 tempUserFile = path . join ( tempDir , 'test-settings.json' ) ;
442+ process . env . CONFIG_FILE = tempUserFile ;
433443 configFile . setConfigFile ( tempUserFile ) ;
434444 } ) ;
435445
436446 it ( 'should test ConfigLoader initialization' , async ( ) => {
437447 const configWithSources = {
448+ ...defaultSettings ,
438449 configurationSources : {
439450 enabled : true ,
440- sources : [
441- {
442- type : 'file' ,
443- enabled : true ,
444- path : tempUserFile ,
445- } ,
446- ] ,
451+ sources : [ ] ,
447452 } ,
448453 } ;
449454
@@ -481,15 +486,98 @@ describe('Configuration Update Handling', () => {
481486 consoleErrorSpy . mockRestore ( ) ;
482487 } ) ;
483488
484- afterEach ( ( ) => {
485- if ( fs . existsSync ( tempUserFile ) ) {
486- fs . rmSync ( tempUserFile , { force : true } ) ;
489+ it ( 'should apply valid configuration updates from external sources' , async ( ) => {
490+ const updatedConfigFile = path . join ( tempDir , 'updated-settings.json' ) ;
491+ const proxyStop = vi . fn ( ) . mockResolvedValue ( undefined ) ;
492+ const proxyStart = vi . fn ( ) . mockResolvedValue ( undefined ) ;
493+ vi . doMock ( '../src/proxy' , ( ) => ( {
494+ stop : proxyStop ,
495+ start : proxyStart ,
496+ } ) ) ;
497+
498+ const configWithSources = {
499+ configurationSources : {
500+ enabled : true ,
501+ sources : [
502+ {
503+ type : 'file' ,
504+ enabled : true ,
505+ path : updatedConfigFile ,
506+ } ,
507+ ] ,
508+ } ,
509+ } ;
510+ const updatedConfig = {
511+ ...defaultSettings ,
512+ configurationSources : configWithSources . configurationSources ,
513+ sessionMaxAgeHours : 8 ,
514+ } ;
515+
516+ fs . writeFileSync ( tempUserFile , JSON . stringify ( configWithSources ) ) ;
517+ fs . writeFileSync ( updatedConfigFile , JSON . stringify ( updatedConfig ) ) ;
518+
519+ const config = await import ( '../src/config' ) ;
520+
521+ await waitForMockCall ( proxyStart ) ;
522+
523+ expect ( proxyStop ) . toHaveBeenCalledTimes ( 1 ) ;
524+ expect ( proxyStart ) . toHaveBeenCalledTimes ( 1 ) ;
525+ expect ( config . getSessionMaxAgeHours ( ) ) . toBe ( updatedConfig . sessionMaxAgeHours ) ;
526+ } ) ;
527+
528+ it ( 'should restart the proxy with the previous config when updates fail' , async ( ) => {
529+ const updatedConfigFile = path . join ( tempDir , 'updated-settings.json' ) ;
530+ const proxyStop = vi . fn ( ) . mockRejectedValue ( new Error ( 'stop failed' ) ) ;
531+ const proxyStart = vi . fn ( ) . mockResolvedValue ( undefined ) ;
532+ vi . doMock ( '../src/proxy' , ( ) => ( {
533+ stop : proxyStop ,
534+ start : proxyStart ,
535+ } ) ) ;
536+ const consoleErrorSpy = vi . spyOn ( console , 'error' ) . mockImplementation ( ( ) => { } ) ;
537+
538+ const configWithSources = {
539+ configurationSources : {
540+ enabled : true ,
541+ sources : [
542+ {
543+ type : 'file' ,
544+ enabled : true ,
545+ path : updatedConfigFile ,
546+ } ,
547+ ] ,
548+ } ,
549+ } ;
550+ const updatedConfig = {
551+ ...defaultSettings ,
552+ configurationSources : configWithSources . configurationSources ,
553+ sessionMaxAgeHours : 8 ,
554+ } ;
555+
556+ fs . writeFileSync ( tempUserFile , JSON . stringify ( configWithSources ) ) ;
557+ fs . writeFileSync ( updatedConfigFile , JSON . stringify ( updatedConfig ) ) ;
558+
559+ try {
560+ await import ( '../src/config' ) ;
561+
562+ await waitForMockCall ( proxyStart ) ;
563+
564+ expect ( proxyStop ) . toHaveBeenCalledTimes ( 1 ) ;
565+ expect ( proxyStart ) . toHaveBeenCalledTimes ( 1 ) ;
566+ expect ( consoleErrorSpy ) . toHaveBeenCalledWith (
567+ 'Failed to apply new configuration: stop failed' ,
568+ ) ;
569+ } finally {
570+ consoleErrorSpy . mockRestore ( ) ;
487571 }
572+ } ) ;
573+
574+ afterEach ( ( ) => {
488575 if ( fs . existsSync ( tempDir ) ) {
489- fs . rmdirSync ( tempDir ) ;
576+ fs . rmSync ( tempDir , { recursive : true , force : true } ) ;
490577 }
491578 process . env = oldEnv ;
492579
580+ vi . doUnmock ( '../src/proxy' ) ;
493581 vi . resetModules ( ) ;
494582 } ) ;
495583} ) ;
@@ -591,5 +679,14 @@ describe('loadFullConfiguration', () => {
591679 'Invalid regular expression for commitConfig.author.email.local.block: [invalid(regex' ,
592680 ) ;
593681 } ) ;
682+
683+ it ( 'should throw error when merged defaults miss required top-level values' , async ( ) => {
684+ const config = await import ( '../src/config' ) ;
685+ const settingsWithoutSink = { ...defaultSettings , sink : undefined } ;
686+
687+ expect ( ( ) => config . assertHasRequiredTopLevelConfig ( settingsWithoutSink ) ) . toThrow (
688+ 'Missing required top-level configuration values: sink' ,
689+ ) ;
690+ } ) ;
594691 } ) ;
595692} ) ;
0 commit comments