Skip to content

Commit 27d7a06

Browse files
committed
test: Add comprehensive restart detection tests for configuration changes
1 parent 85ea517 commit 27d7a06

File tree

1 file changed

+254
-2
lines changed

1 file changed

+254
-2
lines changed

src/services/code-index/__tests__/config-manager.test.ts

Lines changed: 254 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)