Skip to content

Commit c62f78f

Browse files
committed
Add tests
1 parent 417432f commit c62f78f

File tree

2 files changed

+87
-2
lines changed

2 files changed

+87
-2
lines changed

src/api/providers/__tests__/gemini.test.ts

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import { Anthropic } from "@anthropic-ai/sdk"
44

55
import { GeminiHandler } from "../gemini"
6-
import { geminiDefaultModelId } from "../../../shared/api"
6+
import { geminiDefaultModelId, type ModelInfo } from "../../../shared/api"
77

88
const GEMINI_20_FLASH_THINKING_NAME = "gemini-2.0-flash-thinking-exp-1219"
99

@@ -168,4 +168,89 @@ describe("GeminiHandler", () => {
168168
expect(modelInfo.id).toBe(geminiDefaultModelId) // Default model
169169
})
170170
})
171+
172+
describe("calculateCost", () => {
173+
// Mock ModelInfo based on gemini-1.5-flash-latest pricing (per 1M tokens)
174+
// Removed 'id' and 'name' as they are not part of ModelInfo type directly
175+
const mockInfo: ModelInfo = {
176+
inputPrice: 0.125, // $/1M tokens
177+
outputPrice: 0.375, // $/1M tokens
178+
cacheWritesPrice: 0.125, // Assume same as input for test
179+
cacheReadsPrice: 0.125 * 0.25, // Assume 0.25x input for test
180+
contextWindow: 1_000_000,
181+
maxTokens: 8192,
182+
supportsPromptCache: true, // Enable cache calculations for tests
183+
}
184+
185+
it("should calculate cost correctly based on input and output tokens", () => {
186+
const inputTokens = 10000 // Use larger numbers for per-million pricing
187+
const outputTokens = 20000
188+
// Added non-null assertions (!) as mockInfo guarantees these values
189+
const expectedCost =
190+
(inputTokens / 1_000_000) * mockInfo.inputPrice! + (outputTokens / 1_000_000) * mockInfo.outputPrice!
191+
192+
const cost = handler.calculateCost({ info: mockInfo, inputTokens, outputTokens })
193+
expect(cost).toBeCloseTo(expectedCost)
194+
})
195+
196+
it("should return 0 if token counts are zero", () => {
197+
// Note: The method expects numbers, not undefined. Passing undefined would be a type error.
198+
// The calculateCost method itself returns undefined if prices are missing, but 0 if tokens are 0 and prices exist.
199+
expect(handler.calculateCost({ info: mockInfo, inputTokens: 0, outputTokens: 0 })).toBe(0)
200+
})
201+
202+
it("should handle only input tokens", () => {
203+
const inputTokens = 5000
204+
// Added non-null assertion (!)
205+
const expectedCost = (inputTokens / 1_000_000) * mockInfo.inputPrice!
206+
expect(handler.calculateCost({ info: mockInfo, inputTokens, outputTokens: 0 })).toBeCloseTo(expectedCost)
207+
})
208+
209+
it("should handle only output tokens", () => {
210+
const outputTokens = 15000
211+
// Added non-null assertion (!)
212+
const expectedCost = (outputTokens / 1_000_000) * mockInfo.outputPrice!
213+
expect(handler.calculateCost({ info: mockInfo, inputTokens: 0, outputTokens })).toBeCloseTo(expectedCost)
214+
})
215+
216+
it("should calculate cost with cache write tokens", () => {
217+
const inputTokens = 10000
218+
const outputTokens = 20000
219+
const cacheWriteTokens = 5000
220+
const CACHE_TTL = 5 // Match the constant in gemini.ts
221+
222+
// Added non-null assertions (!)
223+
const expectedInputCost = (inputTokens / 1_000_000) * mockInfo.inputPrice!
224+
const expectedOutputCost = (outputTokens / 1_000_000) * mockInfo.outputPrice!
225+
const expectedCacheWriteCost =
226+
mockInfo.cacheWritesPrice! * (cacheWriteTokens / 1_000_000) * (CACHE_TTL / 60)
227+
const expectedCost = expectedInputCost + expectedOutputCost + expectedCacheWriteCost
228+
229+
const cost = handler.calculateCost({ info: mockInfo, inputTokens, outputTokens, cacheWriteTokens })
230+
expect(cost).toBeCloseTo(expectedCost)
231+
})
232+
233+
it("should calculate cost with cache read tokens", () => {
234+
const inputTokens = 10000 // Total logical input
235+
const outputTokens = 20000
236+
const cacheReadTokens = 8000 // Part of inputTokens read from cache
237+
238+
const uncachedReadTokens = inputTokens - cacheReadTokens
239+
// Added non-null assertions (!)
240+
const expectedInputCost = (uncachedReadTokens / 1_000_000) * mockInfo.inputPrice!
241+
const expectedOutputCost = (outputTokens / 1_000_000) * mockInfo.outputPrice!
242+
const expectedCacheReadCost = mockInfo.cacheReadsPrice! * (cacheReadTokens / 1_000_000)
243+
const expectedCost = expectedInputCost + expectedOutputCost + expectedCacheReadCost
244+
245+
const cost = handler.calculateCost({ info: mockInfo, inputTokens, outputTokens, cacheReadTokens })
246+
expect(cost).toBeCloseTo(expectedCost)
247+
})
248+
249+
it("should return undefined if pricing info is missing", () => {
250+
// Create a copy and explicitly set a price to undefined
251+
const incompleteInfo: ModelInfo = { ...mockInfo, outputPrice: undefined }
252+
const cost = handler.calculateCost({ info: incompleteInfo, inputTokens: 1000, outputTokens: 1000 })
253+
expect(cost).toBeUndefined()
254+
})
255+
})
171256
})

src/api/providers/gemini.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ export class GeminiHandler extends BaseProvider implements SingleCompletionHandl
190190
}
191191
}
192192

193-
private calculateCost({
193+
public calculateCost({
194194
info,
195195
inputTokens,
196196
outputTokens,

0 commit comments

Comments
 (0)