1+ import type { MockedClass , MockedFunction } from "vitest"
12import { describe , it , expect , beforeEach , vi } from "vitest"
3+ import { OpenAI } from "openai"
24import { OpenRouterEmbedder } from "../openrouter"
35import { getModelDimension , getDefaultModelId } from "../../../../shared/embeddingModels"
46
5- // Mock global fetch
6- const mockFetch = vi . fn ( )
7- global . fetch = mockFetch
7+ // Mock the OpenAI SDK
8+ vi . mock ( "openai" )
9+
10+ // Mock TelemetryService
11+ vi . mock ( "@roo-code/telemetry" , ( ) => ( {
12+ TelemetryService : {
13+ instance : {
14+ captureEvent : vi . fn ( ) ,
15+ } ,
16+ } ,
17+ TelemetryEventName : { } ,
18+ } ) )
19+
20+ // Mock i18n
21+ vi . mock ( "../../../../i18n" , ( ) => ( {
22+ t : ( key : string , params ?: Record < string , any > ) => {
23+ const translations : Record < string , string > = {
24+ "embeddings:validation.apiKeyRequired" : "validation.apiKeyRequired" ,
25+ "embeddings:authenticationFailed" :
26+ "Failed to create embeddings: Authentication failed. Please check your OpenRouter API key." ,
27+ "embeddings:failedWithStatus" : `Failed to create embeddings after ${ params ?. attempts } attempts: HTTP ${ params ?. statusCode } - ${ params ?. errorMessage } ` ,
28+ "embeddings:failedWithError" : `Failed to create embeddings after ${ params ?. attempts } attempts: ${ params ?. errorMessage } ` ,
29+ "embeddings:failedMaxAttempts" : `Failed to create embeddings after ${ params ?. attempts } attempts` ,
30+ "embeddings:textExceedsTokenLimit" : `Text at index ${ params ?. index } exceeds maximum token limit (${ params ?. itemTokens } > ${ params ?. maxTokens } ). Skipping.` ,
31+ "embeddings:rateLimitRetry" : `Rate limit hit, retrying in ${ params ?. delayMs } ms (attempt ${ params ?. attempt } /${ params ?. maxRetries } )` ,
32+ }
33+ return translations [ key ] || key
34+ } ,
35+ } ) )
36+
37+ const MockedOpenAI = OpenAI as MockedClass < typeof OpenAI >
838
939describe ( "OpenRouterEmbedder" , ( ) => {
1040 const mockApiKey = "test-api-key"
41+ let mockEmbeddingsCreate : MockedFunction < any >
42+ let mockOpenAIInstance : any
43+
44+ beforeEach ( ( ) => {
45+ vi . clearAllMocks ( )
46+ vi . spyOn ( console , "warn" ) . mockImplementation ( ( ) => { } )
47+ vi . spyOn ( console , "error" ) . mockImplementation ( ( ) => { } )
48+
49+ // Setup mock OpenAI instance
50+ mockEmbeddingsCreate = vi . fn ( )
51+ mockOpenAIInstance = {
52+ embeddings : {
53+ create : mockEmbeddingsCreate ,
54+ } ,
55+ }
56+
57+ MockedOpenAI . mockImplementation ( ( ) => mockOpenAIInstance )
58+ } )
59+
60+ afterEach ( ( ) => {
61+ vi . restoreAllMocks ( )
62+ } )
1163
1264 describe ( "constructor" , ( ) => {
1365 it ( "should create an instance with valid API key" , ( ) => {
@@ -16,7 +68,7 @@ describe("OpenRouterEmbedder", () => {
1668 } )
1769
1870 it ( "should throw error with empty API key" , ( ) => {
19- expect ( ( ) => new OpenRouterEmbedder ( "" ) ) . toThrow ( "API key is required " )
71+ expect ( ( ) => new OpenRouterEmbedder ( "" ) ) . toThrow ( "validation.apiKeyRequired " )
2072 } )
2173
2274 it ( "should use default model when none specified" , ( ) => {
@@ -46,93 +98,99 @@ describe("OpenRouterEmbedder", () => {
4698
4799 beforeEach ( ( ) => {
48100 embedder = new OpenRouterEmbedder ( mockApiKey )
49- mockFetch . mockClear ( )
50101 } )
51102
52103 it ( "should create embeddings successfully" , async ( ) => {
104+ // Create base64 encoded embedding with values that can be exactly represented in Float32
105+ const testEmbedding = new Float32Array ( [ 0.25 , 0.5 , 0.75 ] )
106+ const base64String = Buffer . from ( testEmbedding . buffer ) . toString ( "base64" )
107+
53108 const mockResponse = {
54- ok : true ,
55- json : vi . fn ( ) . mockResolvedValue ( {
56- data : [
57- {
58- embedding : Buffer . from ( new Float32Array ( [ 0.1 , 0.2 , 0.3 ] ) . buffer ) . toString ( "base64" ) ,
59- } ,
60- ] ,
61- usage : {
62- prompt_tokens : 5 ,
63- total_tokens : 5 ,
109+ data : [
110+ {
111+ embedding : base64String ,
64112 } ,
65- } ) ,
113+ ] ,
114+ usage : {
115+ prompt_tokens : 5 ,
116+ total_tokens : 5 ,
117+ } ,
66118 }
67119
68- mockFetch . mockResolvedValue ( mockResponse )
120+ mockEmbeddingsCreate . mockResolvedValue ( mockResponse )
69121
70122 const result = await embedder . createEmbeddings ( [ "test text" ] )
71123
124+ expect ( mockEmbeddingsCreate ) . toHaveBeenCalledWith ( {
125+ input : [ "test text" ] ,
126+ model : "openai/text-embedding-3-large" ,
127+ encoding_format : "base64" ,
128+ } )
72129 expect ( result . embeddings ) . toHaveLength ( 1 )
73- expect ( result . embeddings [ 0 ] ) . toEqual ( [ 0.1 , 0.2 , 0.3 ] )
130+ expect ( result . embeddings [ 0 ] ) . toEqual ( [ 0.25 , 0.5 , 0.75 ] )
74131 expect ( result . usage ?. promptTokens ) . toBe ( 5 )
75132 expect ( result . usage ?. totalTokens ) . toBe ( 5 )
76133 } )
77134
78135 it ( "should handle multiple texts" , async ( ) => {
136+ const embedding1 = new Float32Array ( [ 0.25 , 0.5 ] )
137+ const embedding2 = new Float32Array ( [ 0.75 , 1.0 ] )
138+ const base64String1 = Buffer . from ( embedding1 . buffer ) . toString ( "base64" )
139+ const base64String2 = Buffer . from ( embedding2 . buffer ) . toString ( "base64" )
140+
79141 const mockResponse = {
80- ok : true ,
81- json : vi . fn ( ) . mockResolvedValue ( {
82- data : [
83- {
84- embedding : Buffer . from ( new Float32Array ( [ 0.1 , 0.2 ] ) . buffer ) . toString ( "base64" ) ,
85- } ,
86- {
87- embedding : Buffer . from ( new Float32Array ( [ 0.3 , 0.4 ] ) . buffer ) . toString ( "base64" ) ,
88- } ,
89- ] ,
90- usage : {
91- prompt_tokens : 10 ,
92- total_tokens : 10 ,
142+ data : [
143+ {
144+ embedding : base64String1 ,
145+ } ,
146+ {
147+ embedding : base64String2 ,
93148 } ,
94- } ) ,
149+ ] ,
150+ usage : {
151+ prompt_tokens : 10 ,
152+ total_tokens : 10 ,
153+ } ,
95154 }
96155
97- mockFetch . mockResolvedValue ( mockResponse )
156+ mockEmbeddingsCreate . mockResolvedValue ( mockResponse )
98157
99158 const result = await embedder . createEmbeddings ( [ "text1" , "text2" ] )
100159
101160 expect ( result . embeddings ) . toHaveLength ( 2 )
102- expect ( result . embeddings [ 0 ] ) . toEqual ( [ 0.1 , 0.2 ] )
103- expect ( result . embeddings [ 1 ] ) . toEqual ( [ 0.3 , 0.4 ] )
161+ expect ( result . embeddings [ 0 ] ) . toEqual ( [ 0.25 , 0.5 ] )
162+ expect ( result . embeddings [ 1 ] ) . toEqual ( [ 0.75 , 1.0 ] )
104163 } )
105164
106165 it ( "should use custom model when provided" , async ( ) => {
107166 const customModel = "mistralai/mistral-embed-2312"
108167 const embedderWithCustomModel = new OpenRouterEmbedder ( mockApiKey , customModel )
109168
169+ const testEmbedding = new Float32Array ( [ 0.25 , 0.5 ] )
170+ const base64String = Buffer . from ( testEmbedding . buffer ) . toString ( "base64" )
171+
110172 const mockResponse = {
111- ok : true ,
112- json : vi . fn ( ) . mockResolvedValue ( {
113- data : [
114- {
115- embedding : Buffer . from ( new Float32Array ( [ 0.1 , 0.2 ] ) . buffer ) . toString ( "base64" ) ,
116- } ,
117- ] ,
118- usage : {
119- prompt_tokens : 5 ,
120- total_tokens : 5 ,
173+ data : [
174+ {
175+ embedding : base64String ,
121176 } ,
122- } ) ,
177+ ] ,
178+ usage : {
179+ prompt_tokens : 5 ,
180+ total_tokens : 5 ,
181+ } ,
123182 }
124183
125- mockFetch . mockResolvedValue ( mockResponse )
184+ mockEmbeddingsCreate . mockResolvedValue ( mockResponse )
126185
127186 await embedderWithCustomModel . createEmbeddings ( [ "test" ] )
128187
129- // Verify the fetch was called with the custom model
130- expect ( mockFetch ) . toHaveBeenCalledWith (
131- expect . stringContaining ( "openrouter.ai/api/v1/embeddings" ) ,
132- expect . objectContaining ( {
133- body : expect . stringContaining ( `"model":"${ customModel } "` ) ,
134- } ) ,
135- )
188+ // Verify the embeddings.create was called with the custom model
189+ expect ( mockEmbeddingsCreate ) . toHaveBeenCalledWith ( {
190+ input : [ "test" ] ,
191+ model : customModel ,
192+ encoding_format : "base64" ,
193+ } )
136194 } )
137195 } )
138196
@@ -141,42 +199,47 @@ describe("OpenRouterEmbedder", () => {
141199
142200 beforeEach ( ( ) => {
143201 embedder = new OpenRouterEmbedder ( mockApiKey )
144- mockFetch . mockClear ( )
145202 } )
146203
147204 it ( "should validate configuration successfully" , async ( ) => {
205+ const testEmbedding = new Float32Array ( [ 0.25 , 0.5 ] )
206+ const base64String = Buffer . from ( testEmbedding . buffer ) . toString ( "base64" )
207+
148208 const mockResponse = {
149- ok : true ,
150- json : vi . fn ( ) . mockResolvedValue ( {
151- data : [
152- {
153- embedding : Buffer . from ( new Float32Array ( [ 0.1 , 0.2 ] ) . buffer ) . toString ( "base64" ) ,
154- } ,
155- ] ,
156- } ) ,
209+ data : [
210+ {
211+ embedding : base64String ,
212+ } ,
213+ ] ,
214+ usage : {
215+ prompt_tokens : 1 ,
216+ total_tokens : 1 ,
217+ } ,
157218 }
158219
159- mockFetch . mockResolvedValue ( mockResponse )
220+ mockEmbeddingsCreate . mockResolvedValue ( mockResponse )
160221
161222 const result = await embedder . validateConfiguration ( )
162223
163224 expect ( result . valid ) . toBe ( true )
164225 expect ( result . error ) . toBeUndefined ( )
226+ expect ( mockEmbeddingsCreate ) . toHaveBeenCalledWith ( {
227+ input : [ "test" ] ,
228+ model : "openai/text-embedding-3-large" ,
229+ encoding_format : "base64" ,
230+ } )
165231 } )
166232
167233 it ( "should handle validation failure" , async ( ) => {
168- const mockResponse = {
169- ok : false ,
170- status : 401 ,
171- text : vi . fn ( ) . mockResolvedValue ( "Unauthorized" ) ,
172- }
234+ const authError = new Error ( "Invalid API key" )
235+ ; ( authError as any ) . status = 401
173236
174- mockFetch . mockResolvedValue ( mockResponse )
237+ mockEmbeddingsCreate . mockRejectedValue ( authError )
175238
176239 const result = await embedder . validateConfiguration ( )
177240
178241 expect ( result . valid ) . toBe ( false )
179- expect ( result . error ) . toBeDefined ( )
242+ expect ( result . error ) . toBe ( "embeddings:validation.authenticationFailed" )
180243 } )
181244 } )
182245
0 commit comments