Skip to content

Commit 85ea517

Browse files
committed
test: Add unit tests for ServiceFactory embedder and vector store creation
1 parent 8a4d475 commit 85ea517

File tree

1 file changed

+287
-0
lines changed

1 file changed

+287
-0
lines changed
Lines changed: 287 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,287 @@
1+
import { CodeIndexServiceFactory } from "../service-factory"
2+
import { CodeIndexConfigManager } from "../config-manager"
3+
import { CacheManager } from "../cache-manager"
4+
import { OpenAiEmbedder } from "../embedders/openai"
5+
import { CodeIndexOllamaEmbedder } from "../embedders/ollama"
6+
import { QdrantVectorStore } from "../vector-store/qdrant-client"
7+
8+
// Mock the embedders and vector store
9+
jest.mock("../embedders/openai")
10+
jest.mock("../embedders/ollama")
11+
jest.mock("../vector-store/qdrant-client")
12+
13+
// Mock the embedding models module
14+
jest.mock("../../../shared/embeddingModels", () => ({
15+
getDefaultModelId: jest.fn(),
16+
getModelDimension: jest.fn(),
17+
}))
18+
19+
const MockedOpenAiEmbedder = OpenAiEmbedder as jest.MockedClass<typeof OpenAiEmbedder>
20+
const MockedCodeIndexOllamaEmbedder = CodeIndexOllamaEmbedder as jest.MockedClass<typeof CodeIndexOllamaEmbedder>
21+
const MockedQdrantVectorStore = QdrantVectorStore as jest.MockedClass<typeof QdrantVectorStore>
22+
23+
// Import the mocked functions
24+
import { getDefaultModelId, getModelDimension } from "../../../shared/embeddingModels"
25+
const mockGetDefaultModelId = getDefaultModelId as jest.MockedFunction<typeof getDefaultModelId>
26+
const mockGetModelDimension = getModelDimension as jest.MockedFunction<typeof getModelDimension>
27+
28+
describe("CodeIndexServiceFactory", () => {
29+
let factory: CodeIndexServiceFactory
30+
let mockConfigManager: jest.Mocked<CodeIndexConfigManager>
31+
let mockCacheManager: jest.Mocked<CacheManager>
32+
33+
beforeEach(() => {
34+
jest.clearAllMocks()
35+
36+
mockConfigManager = {
37+
getConfig: jest.fn(),
38+
} as any
39+
40+
mockCacheManager = {} as any
41+
42+
factory = new CodeIndexServiceFactory(mockConfigManager, "/test/workspace", mockCacheManager)
43+
})
44+
45+
describe("createEmbedder", () => {
46+
it("should pass model ID to OpenAI embedder when using OpenAI provider", () => {
47+
// Arrange
48+
const testModelId = "text-embedding-3-large"
49+
const testConfig = {
50+
embedderProvider: "openai",
51+
modelId: testModelId,
52+
openAiOptions: {
53+
openAiNativeApiKey: "test-api-key",
54+
},
55+
}
56+
mockConfigManager.getConfig.mockReturnValue(testConfig as any)
57+
58+
// Act
59+
factory.createEmbedder()
60+
61+
// Assert
62+
expect(MockedOpenAiEmbedder).toHaveBeenCalledWith({
63+
openAiNativeApiKey: "test-api-key",
64+
openAiEmbeddingModelId: testModelId,
65+
})
66+
})
67+
68+
it("should pass model ID to Ollama embedder when using Ollama provider", () => {
69+
// Arrange
70+
const testModelId = "nomic-embed-text:latest"
71+
const testConfig = {
72+
embedderProvider: "ollama",
73+
modelId: testModelId,
74+
ollamaOptions: {
75+
ollamaBaseUrl: "http://localhost:11434",
76+
},
77+
}
78+
mockConfigManager.getConfig.mockReturnValue(testConfig as any)
79+
80+
// Act
81+
factory.createEmbedder()
82+
83+
// Assert
84+
expect(MockedCodeIndexOllamaEmbedder).toHaveBeenCalledWith({
85+
ollamaBaseUrl: "http://localhost:11434",
86+
ollamaModelId: testModelId,
87+
})
88+
})
89+
90+
it("should handle undefined model ID for OpenAI embedder", () => {
91+
// Arrange
92+
const testConfig = {
93+
embedderProvider: "openai",
94+
modelId: undefined,
95+
openAiOptions: {
96+
openAiNativeApiKey: "test-api-key",
97+
},
98+
}
99+
mockConfigManager.getConfig.mockReturnValue(testConfig as any)
100+
101+
// Act
102+
factory.createEmbedder()
103+
104+
// Assert
105+
expect(MockedOpenAiEmbedder).toHaveBeenCalledWith({
106+
openAiNativeApiKey: "test-api-key",
107+
openAiEmbeddingModelId: undefined,
108+
})
109+
})
110+
111+
it("should handle undefined model ID for Ollama embedder", () => {
112+
// Arrange
113+
const testConfig = {
114+
embedderProvider: "ollama",
115+
modelId: undefined,
116+
ollamaOptions: {
117+
ollamaBaseUrl: "http://localhost:11434",
118+
},
119+
}
120+
mockConfigManager.getConfig.mockReturnValue(testConfig as any)
121+
122+
// Act
123+
factory.createEmbedder()
124+
125+
// Assert
126+
expect(MockedCodeIndexOllamaEmbedder).toHaveBeenCalledWith({
127+
ollamaBaseUrl: "http://localhost:11434",
128+
ollamaModelId: undefined,
129+
})
130+
})
131+
132+
it("should throw error when OpenAI API key is missing", () => {
133+
// Arrange
134+
const testConfig = {
135+
embedderProvider: "openai",
136+
modelId: "text-embedding-3-large",
137+
openAiOptions: {
138+
openAiNativeApiKey: undefined,
139+
},
140+
}
141+
mockConfigManager.getConfig.mockReturnValue(testConfig as any)
142+
143+
// Act & Assert
144+
expect(() => factory.createEmbedder()).toThrow("OpenAI configuration missing for embedder creation")
145+
})
146+
147+
it("should throw error when Ollama base URL is missing", () => {
148+
// Arrange
149+
const testConfig = {
150+
embedderProvider: "ollama",
151+
modelId: "nomic-embed-text:latest",
152+
ollamaOptions: {
153+
ollamaBaseUrl: undefined,
154+
},
155+
}
156+
mockConfigManager.getConfig.mockReturnValue(testConfig as any)
157+
158+
// Act & Assert
159+
expect(() => factory.createEmbedder()).toThrow("Ollama configuration missing for embedder creation")
160+
})
161+
162+
it("should throw error for invalid embedder provider", () => {
163+
// Arrange
164+
const testConfig = {
165+
embedderProvider: "invalid-provider",
166+
modelId: "some-model",
167+
}
168+
mockConfigManager.getConfig.mockReturnValue(testConfig as any)
169+
170+
// Act & Assert
171+
expect(() => factory.createEmbedder()).toThrow("Invalid embedder type configured: invalid-provider")
172+
})
173+
})
174+
175+
describe("createVectorStore", () => {
176+
beforeEach(() => {
177+
jest.clearAllMocks()
178+
mockGetDefaultModelId.mockReturnValue("default-model")
179+
})
180+
181+
it("should use config.modelId for OpenAI provider", () => {
182+
// Arrange
183+
const testModelId = "text-embedding-3-large"
184+
const testConfig = {
185+
embedderProvider: "openai",
186+
modelId: testModelId,
187+
qdrantUrl: "http://localhost:6333",
188+
qdrantApiKey: "test-key",
189+
}
190+
mockConfigManager.getConfig.mockReturnValue(testConfig as any)
191+
mockGetModelDimension.mockReturnValue(3072)
192+
193+
// Act
194+
factory.createVectorStore()
195+
196+
// Assert
197+
expect(mockGetModelDimension).toHaveBeenCalledWith("openai", testModelId)
198+
expect(MockedQdrantVectorStore).toHaveBeenCalledWith(
199+
"/test/workspace",
200+
"http://localhost:6333",
201+
3072,
202+
"test-key",
203+
)
204+
})
205+
206+
it("should use config.modelId for Ollama provider", () => {
207+
// Arrange
208+
const testModelId = "nomic-embed-text:latest"
209+
const testConfig = {
210+
embedderProvider: "ollama",
211+
modelId: testModelId,
212+
qdrantUrl: "http://localhost:6333",
213+
qdrantApiKey: "test-key",
214+
}
215+
mockConfigManager.getConfig.mockReturnValue(testConfig as any)
216+
mockGetModelDimension.mockReturnValue(768)
217+
218+
// Act
219+
factory.createVectorStore()
220+
221+
// Assert
222+
expect(mockGetModelDimension).toHaveBeenCalledWith("ollama", testModelId)
223+
expect(MockedQdrantVectorStore).toHaveBeenCalledWith(
224+
"/test/workspace",
225+
"http://localhost:6333",
226+
768,
227+
"test-key",
228+
)
229+
})
230+
231+
it("should use default model when config.modelId is undefined", () => {
232+
// Arrange
233+
const testConfig = {
234+
embedderProvider: "openai",
235+
modelId: undefined,
236+
qdrantUrl: "http://localhost:6333",
237+
qdrantApiKey: "test-key",
238+
}
239+
mockConfigManager.getConfig.mockReturnValue(testConfig as any)
240+
mockGetModelDimension.mockReturnValue(1536)
241+
242+
// Act
243+
factory.createVectorStore()
244+
245+
// Assert
246+
expect(mockGetModelDimension).toHaveBeenCalledWith("openai", "default-model")
247+
expect(MockedQdrantVectorStore).toHaveBeenCalledWith(
248+
"/test/workspace",
249+
"http://localhost:6333",
250+
1536,
251+
"test-key",
252+
)
253+
})
254+
255+
it("should throw error when vector dimension cannot be determined", () => {
256+
// Arrange
257+
const testConfig = {
258+
embedderProvider: "openai",
259+
modelId: "unknown-model",
260+
qdrantUrl: "http://localhost:6333",
261+
qdrantApiKey: "test-key",
262+
}
263+
mockConfigManager.getConfig.mockReturnValue(testConfig as any)
264+
mockGetModelDimension.mockReturnValue(undefined)
265+
266+
// Act & Assert
267+
expect(() => factory.createVectorStore()).toThrow(
268+
"Could not determine vector dimension for model 'unknown-model'. Check model profiles or config.",
269+
)
270+
})
271+
272+
it("should throw error when Qdrant URL is missing", () => {
273+
// Arrange
274+
const testConfig = {
275+
embedderProvider: "openai",
276+
modelId: "text-embedding-3-small",
277+
qdrantUrl: undefined,
278+
qdrantApiKey: "test-key",
279+
}
280+
mockConfigManager.getConfig.mockReturnValue(testConfig as any)
281+
mockGetModelDimension.mockReturnValue(1536)
282+
283+
// Act & Assert
284+
expect(() => factory.createVectorStore()).toThrow("Qdrant URL missing for vector store creation")
285+
})
286+
})
287+
})

0 commit comments

Comments
 (0)