@@ -75,13 +75,17 @@ describe("CodeIndexConfigManager", () => {
7575 } )
7676
7777 it ( "should detect restart requirement when provider changes" , async ( ) => {
78- // Initial state
78+ // Initial state - properly configured
7979 mockContextProxy . getGlobalState . mockReturnValue ( {
8080 codebaseIndexEnabled : true ,
8181 codebaseIndexQdrantUrl : "http://qdrant.local" ,
8282 codebaseIndexEmbedderProvider : "openai" ,
8383 codebaseIndexEmbedderModelId : "text-embedding-3-large" ,
8484 } )
85+ mockContextProxy . getSecret . mockImplementation ( ( key : string ) => {
86+ if ( key === "codeIndexOpenAiKey" ) return "test-openai-key"
87+ return undefined
88+ } )
8589
8690 await configManager . loadConfiguration ( )
8791
@@ -91,12 +95,260 @@ describe("CodeIndexConfigManager", () => {
9195 codebaseIndexQdrantUrl : "http://qdrant.local" ,
9296 codebaseIndexEmbedderProvider : "ollama" ,
9397 codebaseIndexEmbedderBaseUrl : "http://ollama.local" ,
94- codebaseIndexEmbedderModelId : "llama2 " ,
98+ codebaseIndexEmbedderModelId : "nomic-embed-text " ,
9599 } )
96100
97101 const result = await configManager . loadConfiguration ( )
98102 expect ( result . requiresRestart ) . toBe ( true )
99103 } )
104+
105+ it ( "should detect restart requirement when vector dimensions change" , async ( ) => {
106+ // Initial state with text-embedding-3-small (1536D)
107+ mockContextProxy . getGlobalState . mockReturnValue ( {
108+ codebaseIndexEnabled : true ,
109+ codebaseIndexQdrantUrl : "http://qdrant.local" ,
110+ codebaseIndexEmbedderProvider : "openai" ,
111+ codebaseIndexEmbedderModelId : "text-embedding-3-small" ,
112+ } )
113+ mockContextProxy . getSecret . mockReturnValue ( "test-key" )
114+
115+ await configManager . loadConfiguration ( )
116+
117+ // Change to text-embedding-3-large (3072D)
118+ mockContextProxy . getGlobalState . mockReturnValue ( {
119+ codebaseIndexEnabled : true ,
120+ codebaseIndexQdrantUrl : "http://qdrant.local" ,
121+ codebaseIndexEmbedderProvider : "openai" ,
122+ codebaseIndexEmbedderModelId : "text-embedding-3-large" ,
123+ } )
124+
125+ const result = await configManager . loadConfiguration ( )
126+ expect ( result . requiresRestart ) . toBe ( true )
127+ } )
128+
129+ it ( "should NOT require restart when models have same dimensions" , async ( ) => {
130+ // Initial state with text-embedding-3-small (1536D)
131+ mockContextProxy . getGlobalState . mockReturnValue ( {
132+ codebaseIndexEnabled : true ,
133+ codebaseIndexQdrantUrl : "http://qdrant.local" ,
134+ codebaseIndexEmbedderProvider : "openai" ,
135+ codebaseIndexEmbedderModelId : "text-embedding-3-small" ,
136+ } )
137+ mockContextProxy . getSecret . mockImplementation ( ( key : string ) => {
138+ if ( key === "codeIndexOpenAiKey" ) return "test-key"
139+ return undefined
140+ } )
141+
142+ await configManager . loadConfiguration ( )
143+
144+ // Change to text-embedding-ada-002 (also 1536D)
145+ mockContextProxy . getGlobalState . mockReturnValue ( {
146+ codebaseIndexEnabled : true ,
147+ codebaseIndexQdrantUrl : "http://qdrant.local" ,
148+ codebaseIndexEmbedderProvider : "openai" ,
149+ codebaseIndexEmbedderModelId : "text-embedding-ada-002" ,
150+ } )
151+
152+ const result = await configManager . loadConfiguration ( )
153+ expect ( result . requiresRestart ) . toBe ( false )
154+ } )
155+
156+ it ( "should detect restart requirement when transitioning to enabled+configured" , async ( ) => {
157+ // Initial state - disabled
158+ mockContextProxy . getGlobalState . mockReturnValue ( {
159+ codebaseIndexEnabled : false ,
160+ } )
161+
162+ await configManager . loadConfiguration ( )
163+
164+ // Enable and configure
165+ mockContextProxy . getGlobalState . mockReturnValue ( {
166+ codebaseIndexEnabled : true ,
167+ codebaseIndexQdrantUrl : "http://qdrant.local" ,
168+ codebaseIndexEmbedderProvider : "openai" ,
169+ codebaseIndexEmbedderModelId : "text-embedding-3-small" ,
170+ } )
171+ mockContextProxy . getSecret . mockReturnValue ( "test-key" )
172+
173+ const result = await configManager . loadConfiguration ( )
174+ expect ( result . requiresRestart ) . toBe ( true )
175+ } )
176+
177+ describe ( "simplified restart detection" , ( ) => {
178+ it ( "should detect restart requirement for API key changes" , async ( ) => {
179+ // Initial state
180+ mockContextProxy . getGlobalState . mockReturnValue ( {
181+ codebaseIndexEnabled : true ,
182+ codebaseIndexQdrantUrl : "http://qdrant.local" ,
183+ codebaseIndexEmbedderProvider : "openai" ,
184+ codebaseIndexEmbedderModelId : "text-embedding-3-small" ,
185+ } )
186+ mockContextProxy . getSecret . mockReturnValue ( "old-key" )
187+
188+ await configManager . loadConfiguration ( )
189+
190+ // Change API key
191+ mockContextProxy . getSecret . mockImplementation ( ( key : string ) => {
192+ if ( key === "codeIndexOpenAiKey" ) return "new-key"
193+ return undefined
194+ } )
195+
196+ const result = await configManager . loadConfiguration ( )
197+ expect ( result . requiresRestart ) . toBe ( true )
198+ } )
199+
200+ it ( "should detect restart requirement for Qdrant URL changes" , async ( ) => {
201+ // Initial state
202+ mockContextProxy . getGlobalState . mockReturnValue ( {
203+ codebaseIndexEnabled : true ,
204+ codebaseIndexQdrantUrl : "http://old-qdrant.local" ,
205+ codebaseIndexEmbedderProvider : "openai" ,
206+ codebaseIndexEmbedderModelId : "text-embedding-3-small" ,
207+ } )
208+ mockContextProxy . getSecret . mockReturnValue ( "test-key" )
209+
210+ await configManager . loadConfiguration ( )
211+
212+ // Change Qdrant URL
213+ mockContextProxy . getGlobalState . mockReturnValue ( {
214+ codebaseIndexEnabled : true ,
215+ codebaseIndexQdrantUrl : "http://new-qdrant.local" ,
216+ codebaseIndexEmbedderProvider : "openai" ,
217+ codebaseIndexEmbedderModelId : "text-embedding-3-small" ,
218+ } )
219+
220+ const result = await configManager . loadConfiguration ( )
221+ expect ( result . requiresRestart ) . toBe ( true )
222+ } )
223+
224+ it ( "should handle unknown model dimensions safely" , async ( ) => {
225+ // Initial state with known model
226+ mockContextProxy . getGlobalState . mockReturnValue ( {
227+ codebaseIndexEnabled : true ,
228+ codebaseIndexQdrantUrl : "http://qdrant.local" ,
229+ codebaseIndexEmbedderProvider : "openai" ,
230+ codebaseIndexEmbedderModelId : "text-embedding-3-small" ,
231+ } )
232+ mockContextProxy . getSecret . mockReturnValue ( "test-key" )
233+
234+ await configManager . loadConfiguration ( )
235+
236+ // Change to unknown model
237+ mockContextProxy . getGlobalState . mockReturnValue ( {
238+ codebaseIndexEnabled : true ,
239+ codebaseIndexQdrantUrl : "http://qdrant.local" ,
240+ codebaseIndexEmbedderProvider : "openai" ,
241+ codebaseIndexEmbedderModelId : "unknown-model" ,
242+ } )
243+
244+ const result = await configManager . loadConfiguration ( )
245+ expect ( result . requiresRestart ) . toBe ( true )
246+ } )
247+
248+ it ( "should handle Ollama configuration changes" , async ( ) => {
249+ // Initial state
250+ mockContextProxy . getGlobalState . mockReturnValue ( {
251+ codebaseIndexEnabled : true ,
252+ codebaseIndexQdrantUrl : "http://qdrant.local" ,
253+ codebaseIndexEmbedderProvider : "ollama" ,
254+ codebaseIndexEmbedderBaseUrl : "http://old-ollama.local" ,
255+ codebaseIndexEmbedderModelId : "nomic-embed-text" ,
256+ } )
257+
258+ await configManager . loadConfiguration ( )
259+
260+ // Change Ollama base URL
261+ mockContextProxy . getGlobalState . mockReturnValue ( {
262+ codebaseIndexEnabled : true ,
263+ codebaseIndexQdrantUrl : "http://qdrant.local" ,
264+ codebaseIndexEmbedderProvider : "ollama" ,
265+ codebaseIndexEmbedderBaseUrl : "http://new-ollama.local" ,
266+ codebaseIndexEmbedderModelId : "nomic-embed-text" ,
267+ } )
268+
269+ const result = await configManager . loadConfiguration ( )
270+ expect ( result . requiresRestart ) . toBe ( true )
271+ } )
272+
273+ it ( "should not require restart when disabled remains disabled" , async ( ) => {
274+ // Initial state - disabled but configured
275+ mockContextProxy . getGlobalState . mockReturnValue ( {
276+ codebaseIndexEnabled : false ,
277+ codebaseIndexQdrantUrl : "http://qdrant.local" ,
278+ codebaseIndexEmbedderProvider : "openai" ,
279+ } )
280+ mockContextProxy . getSecret . mockImplementation ( ( key : string ) => {
281+ if ( key === "codeIndexOpenAiKey" ) return "test-key"
282+ return undefined
283+ } )
284+
285+ await configManager . loadConfiguration ( )
286+
287+ // Still disabled but change other settings
288+ mockContextProxy . getGlobalState . mockReturnValue ( {
289+ codebaseIndexEnabled : false ,
290+ codebaseIndexQdrantUrl : "http://different-qdrant.local" ,
291+ codebaseIndexEmbedderProvider : "ollama" ,
292+ codebaseIndexEmbedderBaseUrl : "http://ollama.local" ,
293+ } )
294+
295+ const result = await configManager . loadConfiguration ( )
296+ expect ( result . requiresRestart ) . toBe ( false )
297+ } )
298+
299+ it ( "should not require restart when unconfigured remains unconfigured" , async ( ) => {
300+ // Initial state - enabled but unconfigured (missing API key)
301+ mockContextProxy . getGlobalState . mockReturnValue ( {
302+ codebaseIndexEnabled : true ,
303+ codebaseIndexQdrantUrl : "http://qdrant.local" ,
304+ codebaseIndexEmbedderProvider : "openai" ,
305+ } )
306+ mockContextProxy . getSecret . mockReturnValue ( undefined )
307+
308+ await configManager . loadConfiguration ( )
309+
310+ // Still unconfigured but change model
311+ mockContextProxy . getGlobalState . mockReturnValue ( {
312+ codebaseIndexEnabled : true ,
313+ codebaseIndexQdrantUrl : "http://qdrant.local" ,
314+ codebaseIndexEmbedderProvider : "openai" ,
315+ codebaseIndexEmbedderModelId : "text-embedding-3-large" ,
316+ } )
317+
318+ const result = await configManager . loadConfiguration ( )
319+ expect ( result . requiresRestart ) . toBe ( false )
320+ } )
321+ } )
322+
323+ describe ( "getRestartInfo public method" , ( ) => {
324+ it ( "should provide restart info without loading configuration" , async ( ) => {
325+ // Setup initial state
326+ mockContextProxy . getGlobalState . mockReturnValue ( {
327+ codebaseIndexEnabled : true ,
328+ codebaseIndexQdrantUrl : "http://qdrant.local" ,
329+ codebaseIndexEmbedderProvider : "openai" ,
330+ codebaseIndexEmbedderModelId : "text-embedding-3-small" ,
331+ } )
332+ mockContextProxy . getSecret . mockReturnValue ( "test-key" )
333+
334+ await configManager . loadConfiguration ( )
335+
336+ // Create a mock previous config
337+ const mockPrevConfig = {
338+ enabled : true ,
339+ configured : true ,
340+ embedderProvider : "openai" as const ,
341+ modelId : "text-embedding-3-large" , // Different model with different dimensions
342+ openAiKey : "test-key" ,
343+ ollamaBaseUrl : undefined ,
344+ qdrantUrl : "http://qdrant.local" ,
345+ qdrantApiKey : undefined ,
346+ }
347+
348+ const requiresRestart = configManager . doesConfigChangeRequireRestart ( mockPrevConfig )
349+ expect ( requiresRestart ) . toBe ( true )
350+ } )
351+ } )
100352 } )
101353
102354 describe ( "isConfigured" , ( ) => {
0 commit comments