11import { describe , it , expect } from "vitest" ;
2- import { sanitizeModelName , getTimestampedFilename } from "./utils.ts" ;
2+ import {
3+ sanitizeModelName ,
4+ getTimestampedFilename ,
5+ calculateTotalCost ,
6+ } from "./utils.ts" ;
7+ import type { ModelPricing } from "./pricing.ts" ;
8+ import type { SingleTestResult } from "./report.ts" ;
39
410describe ( "sanitizeModelName" , ( ) => {
511 it ( "replaces slashes with dashes" , ( ) => {
@@ -34,12 +40,22 @@ describe("getTimestampedFilename", () => {
3440 const fixedDate = new Date ( "2025-12-12T14:30:45Z" ) ;
3541
3642 it ( "generates filename without model name" , ( ) => {
37- const result = getTimestampedFilename ( "result" , "json" , undefined , fixedDate ) ;
43+ const result = getTimestampedFilename (
44+ "result" ,
45+ "json" ,
46+ undefined ,
47+ fixedDate ,
48+ ) ;
3849 expect ( result ) . toBe ( "result-2025-12-12-14-30-45.json" ) ;
3950 } ) ;
4051
4152 it ( "generates filename with simple model name" , ( ) => {
42- const result = getTimestampedFilename ( "result" , "json" , "gpt-4o" , fixedDate ) ;
53+ const result = getTimestampedFilename (
54+ "result" ,
55+ "json" ,
56+ "gpt-4o" ,
57+ fixedDate ,
58+ ) ;
4359 expect ( result ) . toBe ( "result-2025-12-12-14-30-45-gpt-4o.json" ) ;
4460 } ) ;
4561
@@ -50,7 +66,9 @@ describe("getTimestampedFilename", () => {
5066 "anthropic/claude-sonnet-4" ,
5167 fixedDate ,
5268 ) ;
53- expect ( result ) . toBe ( "result-2025-12-12-14-30-45-anthropic-claude-sonnet-4.json" ) ;
69+ expect ( result ) . toBe (
70+ "result-2025-12-12-14-30-45-anthropic-claude-sonnet-4.json" ,
71+ ) ;
5472 } ) ;
5573
5674 it ( "generates filename with model name containing special characters" , ( ) => {
@@ -64,13 +82,111 @@ describe("getTimestampedFilename", () => {
6482 } ) ;
6583
6684 it ( "handles different file extensions" , ( ) => {
67- const result = getTimestampedFilename ( "output" , "txt" , "test-model" , fixedDate ) ;
85+ const result = getTimestampedFilename (
86+ "output" ,
87+ "txt" ,
88+ "test-model" ,
89+ fixedDate ,
90+ ) ;
6891 expect ( result ) . toBe ( "output-2025-12-12-14-30-45-test-model.txt" ) ;
6992 } ) ;
7093
7194 it ( "pads single-digit months and days" , ( ) => {
7295 const earlyDate = new Date ( "2025-01-05T08:09:07Z" ) ;
73- const result = getTimestampedFilename ( "result" , "json" , undefined , earlyDate ) ;
96+ const result = getTimestampedFilename (
97+ "result" ,
98+ "json" ,
99+ undefined ,
100+ earlyDate ,
101+ ) ;
74102 expect ( result ) . toBe ( "result-2025-01-05-08-09-07.json" ) ;
75103 } ) ;
76104} ) ;
105+
106+ describe ( "calculateTotalCost" , ( ) => {
107+ const pricing : ModelPricing = {
108+ inputCostPerToken : 1.0 / 1_000_000 ,
109+ outputCostPerToken : 2.0 / 1_000_000 ,
110+ cacheReadInputTokenCost : 0.1 / 1_000_000 ,
111+ } ;
112+
113+ it ( "calculates zero cost for empty results" , ( ) => {
114+ const tests : SingleTestResult [ ] = [ ] ;
115+ const result = calculateTotalCost ( tests , pricing ) ;
116+
117+ expect ( result ) . toEqual ( {
118+ inputCost : 0 ,
119+ outputCost : 0 ,
120+ cacheReadCost : 0 ,
121+ totalCost : 0 ,
122+ inputTokens : 0 ,
123+ outputTokens : 0 ,
124+ cachedInputTokens : 0 ,
125+ } ) ;
126+ } ) ;
127+
128+ it ( "aggregates usage from multiple steps and tests" , ( ) => {
129+ const tests : SingleTestResult [ ] = [
130+ {
131+ testName : "test1" ,
132+ prompt : "p1" ,
133+ resultWriteContent : null ,
134+ verification : { } as any ,
135+ steps : [
136+ {
137+ usage : {
138+ inputTokens : 100 ,
139+ outputTokens : 50 ,
140+ cachedInputTokens : 10 ,
141+ } ,
142+ } as any ,
143+ {
144+ usage : {
145+ inputTokens : 200 ,
146+ outputTokens : 100 ,
147+ cachedInputTokens : 0 ,
148+ } ,
149+ } as any ,
150+ ] ,
151+ } ,
152+ {
153+ testName : "test2" ,
154+ prompt : "p2" ,
155+ resultWriteContent : null ,
156+ verification : { } as any ,
157+ steps : [
158+ {
159+ usage : {
160+ inputTokens : 300 ,
161+ outputTokens : 150 ,
162+ cachedInputTokens : 20 ,
163+ } ,
164+ } as any ,
165+ ] ,
166+ } ,
167+ ] ;
168+
169+ // Total Input: 100 + 200 + 300 = 600
170+ // Total Output: 50 + 100 + 150 = 300
171+ // Total Cached: 10 + 0 + 20 = 30
172+ // Uncached Input: 600 - 30 = 570
173+
174+ // Costs (per Token):
175+ // Input: 570 * (1.0 / 1e6) = 0.00057
176+ // Output: 300 * (2.0 / 1e6) = 0.0006
177+ // Cache: 30 * (0.1 / 1e6) = 0.000003
178+ // Total: 0.00057 + 0.0006 + 0.000003 = 0.001173
179+
180+ const result = calculateTotalCost ( tests , pricing ) ;
181+
182+ expect ( result ) . toEqual ( {
183+ inputCost : 0.00057 ,
184+ outputCost : 0.0006 ,
185+ cacheReadCost : 0.000003 ,
186+ totalCost : 0.001173 ,
187+ inputTokens : 600 ,
188+ outputTokens : 300 ,
189+ cachedInputTokens : 30 ,
190+ } ) ;
191+ } ) ;
192+ } ) ;
0 commit comments