@@ -74,6 +74,44 @@ describe("CodeIndexConfigManager", () => {
7474 } )
7575 } )
7676
77+ it ( "should load OpenAI Compatible configuration from globalState and secrets" , async ( ) => {
78+ const mockGlobalState = {
79+ codebaseIndexEnabled : true ,
80+ codebaseIndexQdrantUrl : "http://qdrant.local" ,
81+ codebaseIndexEmbedderProvider : "openai-compatible" ,
82+ codebaseIndexEmbedderBaseUrl : "" ,
83+ codebaseIndexEmbedderModelId : "text-embedding-3-large" ,
84+ }
85+ mockContextProxy . getGlobalState . mockImplementation ( ( key : string ) => {
86+ if ( key === "codebaseIndexConfig" ) return mockGlobalState
87+ if ( key === "codebaseIndexOpenAiCompatibleBaseUrl" ) return "https://api.example.com/v1"
88+ return undefined
89+ } )
90+ mockContextProxy . getSecret . mockImplementation ( ( key : string ) => {
91+ if ( key === "codeIndexQdrantApiKey" ) return "test-qdrant-key"
92+ if ( key === "codebaseIndexOpenAiCompatibleApiKey" ) return "test-openai-compatible-key"
93+ return undefined
94+ } )
95+
96+ const result = await configManager . loadConfiguration ( )
97+
98+ expect ( result . currentConfig ) . toEqual ( {
99+ isEnabled : true ,
100+ isConfigured : true ,
101+ embedderProvider : "openai-compatible" ,
102+ modelId : "text-embedding-3-large" ,
103+ openAiOptions : { openAiNativeApiKey : "" } ,
104+ ollamaOptions : { ollamaBaseUrl : "" } ,
105+ openAiCompatibleOptions : {
106+ baseUrl : "https://api.example.com/v1" ,
107+ apiKey : "test-openai-compatible-key" ,
108+ } ,
109+ qdrantUrl : "http://qdrant.local" ,
110+ qdrantApiKey : "test-qdrant-key" ,
111+ searchMinScore : 0.4 ,
112+ } )
113+ } )
114+
77115 it ( "should detect restart requirement when provider changes" , async ( ) => {
78116 // Initial state - properly configured
79117 mockContextProxy . getGlobalState . mockReturnValue ( {
@@ -270,6 +308,76 @@ describe("CodeIndexConfigManager", () => {
270308 expect ( result . requiresRestart ) . toBe ( true )
271309 } )
272310
311+ it ( "should handle OpenAI Compatible configuration changes" , async ( ) => {
312+ // Initial state
313+ mockContextProxy . getGlobalState . mockImplementation ( ( key : string ) => {
314+ if ( key === "codebaseIndexConfig" ) {
315+ return {
316+ codebaseIndexEnabled : true ,
317+ codebaseIndexQdrantUrl : "http://qdrant.local" ,
318+ codebaseIndexEmbedderProvider : "openai-compatible" ,
319+ codebaseIndexEmbedderModelId : "text-embedding-3-small" ,
320+ }
321+ }
322+ if ( key === "codebaseIndexOpenAiCompatibleBaseUrl" ) return "https://old-api.example.com/v1"
323+ return undefined
324+ } )
325+ mockContextProxy . getSecret . mockImplementation ( ( key : string ) => {
326+ if ( key === "codebaseIndexOpenAiCompatibleApiKey" ) return "old-api-key"
327+ return undefined
328+ } )
329+
330+ await configManager . loadConfiguration ( )
331+
332+ // Change OpenAI Compatible base URL
333+ mockContextProxy . getGlobalState . mockImplementation ( ( key : string ) => {
334+ if ( key === "codebaseIndexConfig" ) {
335+ return {
336+ codebaseIndexEnabled : true ,
337+ codebaseIndexQdrantUrl : "http://qdrant.local" ,
338+ codebaseIndexEmbedderProvider : "openai-compatible" ,
339+ codebaseIndexEmbedderModelId : "text-embedding-3-small" ,
340+ }
341+ }
342+ if ( key === "codebaseIndexOpenAiCompatibleBaseUrl" ) return "https://new-api.example.com/v1"
343+ return undefined
344+ } )
345+
346+ const result = await configManager . loadConfiguration ( )
347+ expect ( result . requiresRestart ) . toBe ( true )
348+ } )
349+
350+ it ( "should handle OpenAI Compatible API key changes" , async ( ) => {
351+ // Initial state
352+ mockContextProxy . getGlobalState . mockImplementation ( ( key : string ) => {
353+ if ( key === "codebaseIndexConfig" ) {
354+ return {
355+ codebaseIndexEnabled : true ,
356+ codebaseIndexQdrantUrl : "http://qdrant.local" ,
357+ codebaseIndexEmbedderProvider : "openai-compatible" ,
358+ codebaseIndexEmbedderModelId : "text-embedding-3-small" ,
359+ }
360+ }
361+ if ( key === "codebaseIndexOpenAiCompatibleBaseUrl" ) return "https://api.example.com/v1"
362+ return undefined
363+ } )
364+ mockContextProxy . getSecret . mockImplementation ( ( key : string ) => {
365+ if ( key === "codebaseIndexOpenAiCompatibleApiKey" ) return "old-api-key"
366+ return undefined
367+ } )
368+
369+ await configManager . loadConfiguration ( )
370+
371+ // Change OpenAI Compatible API key
372+ mockContextProxy . getSecret . mockImplementation ( ( key : string ) => {
373+ if ( key === "codebaseIndexOpenAiCompatibleApiKey" ) return "new-api-key"
374+ return undefined
375+ } )
376+
377+ const result = await configManager . loadConfiguration ( )
378+ expect ( result . requiresRestart ) . toBe ( true )
379+ } )
380+
273381 it ( "should not require restart when disabled remains disabled" , async ( ) => {
274382 // Initial state - disabled but configured
275383 mockContextProxy . getGlobalState . mockReturnValue ( {
@@ -448,6 +556,69 @@ describe("CodeIndexConfigManager", () => {
448556 expect ( configManager . isFeatureConfigured ) . toBe ( true )
449557 } )
450558
559+ it ( "should validate OpenAI Compatible configuration correctly" , async ( ) => {
560+ mockContextProxy . getGlobalState . mockImplementation ( ( key : string ) => {
561+ if ( key === "codebaseIndexConfig" ) {
562+ return {
563+ codebaseIndexEnabled : true ,
564+ codebaseIndexQdrantUrl : "http://qdrant.local" ,
565+ codebaseIndexEmbedderProvider : "openai-compatible" ,
566+ }
567+ }
568+ if ( key === "codebaseIndexOpenAiCompatibleBaseUrl" ) return "https://api.example.com/v1"
569+ return undefined
570+ } )
571+ mockContextProxy . getSecret . mockImplementation ( ( key : string ) => {
572+ if ( key === "codebaseIndexOpenAiCompatibleApiKey" ) return "test-api-key"
573+ return undefined
574+ } )
575+
576+ await configManager . loadConfiguration ( )
577+ expect ( configManager . isFeatureConfigured ) . toBe ( true )
578+ } )
579+
580+ it ( "should return false when OpenAI Compatible base URL is missing" , async ( ) => {
581+ mockContextProxy . getGlobalState . mockImplementation ( ( key : string ) => {
582+ if ( key === "codebaseIndexConfig" ) {
583+ return {
584+ codebaseIndexEnabled : true ,
585+ codebaseIndexQdrantUrl : "http://qdrant.local" ,
586+ codebaseIndexEmbedderProvider : "openai-compatible" ,
587+ }
588+ }
589+ if ( key === "codebaseIndexOpenAiCompatibleBaseUrl" ) return ""
590+ return undefined
591+ } )
592+ mockContextProxy . getSecret . mockImplementation ( ( key : string ) => {
593+ if ( key === "codebaseIndexOpenAiCompatibleApiKey" ) return "test-api-key"
594+ return undefined
595+ } )
596+
597+ await configManager . loadConfiguration ( )
598+ expect ( configManager . isFeatureConfigured ) . toBe ( false )
599+ } )
600+
601+ it ( "should return false when OpenAI Compatible API key is missing" , async ( ) => {
602+ mockContextProxy . getGlobalState . mockImplementation ( ( key : string ) => {
603+ if ( key === "codebaseIndexConfig" ) {
604+ return {
605+ codebaseIndexEnabled : true ,
606+ codebaseIndexQdrantUrl : "http://qdrant.local" ,
607+ codebaseIndexEmbedderProvider : "openai-compatible" ,
608+ }
609+ }
610+ if ( key === "codebaseIndexOpenAiCompatibleBaseUrl" ) return "https://api.example.com/v1"
611+ return undefined
612+ } )
613+ mockContextProxy . getSecret . mockImplementation ( ( key : string ) => {
614+ if ( key === "codebaseIndexOpenAiCompatibleApiKey" ) return ""
615+ return undefined
616+ } )
617+
618+ await configManager . loadConfiguration ( )
619+ expect ( configManager . isFeatureConfigured ) . toBe ( false )
620+ } )
621+
451622 it ( "should return false when required values are missing" , async ( ) => {
452623 mockContextProxy . getGlobalState . mockReturnValue ( {
453624 codebaseIndexEnabled : true ,
0 commit comments