@@ -72,14 +72,23 @@ describe("gateway usage helpers", () => {
7272 } ) ;
7373
7474 function expectUtcDateRange (
75- range : ReturnType < typeof testApi . parseDateRange > ,
75+ result : ReturnType < typeof testApi . resolveDateRange > ,
7676 startDate : string ,
7777 endDate : string ,
7878 ) {
79+ const range = expectDateRange ( result ) ;
7980 expect ( range . startMs ) . toBe ( testApi . parseDateToMs ( startDate ) ) ;
8081 expect ( range . endMs ) . toBe ( testApi . parseDateToMs ( endDate ) ! + dayMs - 1 ) ;
8182 }
8283
84+ function expectDateRange ( result : ReturnType < typeof testApi . resolveDateRange > ) {
85+ expect ( result . ok ) . toBe ( true ) ;
86+ if ( ! result . ok ) {
87+ throw new Error ( result . error ) ;
88+ }
89+ return result . value ;
90+ }
91+
8392 beforeEach ( ( ) => {
8493 testApi . costUsageCache . clear ( ) ;
8594 vi . useRealTimers ( ) ;
@@ -106,21 +115,18 @@ describe("gateway usage helpers", () => {
106115 expect ( testApi . parseDateToMs ( "2024-02-29" ) ) . toBe ( Date . UTC ( 2024 , 1 , 29 ) ) ;
107116 } ) ;
108117
109- it ( "findInvalidExplicitDate flags provided-but-unparseable dates and ignores absent/valid ones" , ( ) => {
110- // Explicitly provided invalid dates (bad format or impossible calendar date) are reported.
111- expect ( testApi . findInvalidExplicitDate ( { startDate : "2026-02-30" } ) ) . toBe ( "startDate" ) ;
112- expect ( testApi . findInvalidExplicitDate ( { endDate : "2026-2-5" } ) ) . toBe ( "endDate" ) ;
113- expect ( testApi . findInvalidExplicitDate ( { startDate : 0 } ) ) . toBe ( "startDate" ) ;
114- expect ( testApi . findInvalidExplicitDate ( { endDate : [ ] } ) ) . toBe ( "endDate" ) ;
115- expect (
116- testApi . findInvalidExplicitDate ( { startDate : "2026-02-01" , endDate : "2026-13-01" } ) ,
117- ) . toBe ( "endDate" ) ;
118- // Absent or valid dates are not flagged, so they still fall through to the default range.
119- expect ( testApi . findInvalidExplicitDate ( { } ) ) . toBeUndefined ( ) ;
120- expect ( testApi . findInvalidExplicitDate ( { startDate : "" , endDate : null } ) ) . toBeUndefined ( ) ;
121- expect (
122- testApi . findInvalidExplicitDate ( { startDate : "2026-02-01" , endDate : "2026-02-02" } ) ,
123- ) . toBeUndefined ( ) ;
118+ it . each ( [
119+ [ { startDate : "2026-02-30" } , "invalid startDate" ] ,
120+ [ { endDate : "2026-2-5" } , "invalid endDate" ] ,
121+ [ { startDate : 0 } , "invalid startDate" ] ,
122+ [ { endDate : [ ] } , "invalid endDate" ] ,
123+ [ { startDate : "2026-02-01" , endDate : "2026-13-01" } , "invalid endDate" ] ,
124+ [ { startDate : "2026-02-03" , endDate : "2026-02-02" } , "startDate must not be after endDate" ] ,
125+ ] ) ( "resolveDateRange rejects invalid explicit ranges" , ( params , error ) => {
126+ expect ( testApi . resolveDateRange ( params ) ) . toEqual ( {
127+ ok : false ,
128+ error : expect . stringContaining ( error ) ,
129+ } ) ;
124130 } ) ;
125131
126132 it ( "usage.cost rejects an explicitly provided invalid date with INVALID_REQUEST" , async ( ) => {
@@ -139,6 +145,25 @@ describe("gateway usage helpers", () => {
139145 expect ( vi . mocked ( loadCostUsageSummaryFromCache ) ) . not . toHaveBeenCalled ( ) ;
140146 } ) ;
141147
148+ it . each ( [ "usage.cost" , "sessions.usage" ] as const ) (
149+ "%s rejects startDate after endDate with INVALID_REQUEST" ,
150+ async ( method ) => {
151+ const respond = vi . fn ( ) ;
152+ await usageHandlers [ method ] ( {
153+ respond,
154+ params : { startDate : "2026-02-03" , endDate : "2026-02-02" } ,
155+ context : { getRuntimeConfig : vi . fn ( ( ) => ( { } ) ) } ,
156+ } as unknown as Parameters < ( typeof usageHandlers ) [ typeof method ] > [ 0 ] ) ;
157+
158+ expect ( respond ) . toHaveBeenCalledTimes ( 1 ) ;
159+ const [ ok , payload , error ] = respond . mock . calls [ 0 ] ;
160+ expect ( ok ) . toBe ( false ) ;
161+ expect ( payload ) . toBeUndefined ( ) ;
162+ expect ( JSON . stringify ( error ) ) . toContain ( "startDate must not be after endDate" ) ;
163+ expect ( vi . mocked ( loadCostUsageSummaryFromCache ) ) . not . toHaveBeenCalled ( ) ;
164+ } ,
165+ ) ;
166+
142167 it ( "parseUtcOffsetToMinutes supports whole-hour and half-hour offsets" , ( ) => {
143168 expect ( testApi . parseUtcOffsetToMinutes ( "UTC-4" ) ) . toBe ( - 240 ) ;
144169 expect ( testApi . parseUtcOffsetToMinutes ( "UTC+5:30" ) ) . toBe ( 330 ) ;
@@ -160,80 +185,91 @@ describe("gateway usage helpers", () => {
160185 expect ( testApi . parseDays ( "nope" ) ) . toBeUndefined ( ) ;
161186 } ) ;
162187
163- it ( "parseDateRange uses explicit start/end as UTC when mode is missing (backward compatible)" , ( ) => {
164- const range = testApi . parseDateRange ( { startDate : "2026-02-01" , endDate : "2026-02-02" } ) ;
165- expectUtcDateRange ( range , "2026-02-01" , "2026-02-02" ) ;
166- } ) ;
167-
168- it ( "parseDateRange uses explicit UTC mode" , ( ) => {
169- const range = testApi . parseDateRange ( {
188+ it ( "resolveDateRange uses explicit start/end as UTC when mode is missing (backward compatible)" , ( ) => {
189+ const result = testApi . resolveDateRange ( {
170190 startDate : "2026-02-01" ,
171191 endDate : "2026-02-02" ,
172- mode : "utc" ,
173192 } ) ;
174- expectUtcDateRange ( range , "2026-02-01" , "2026-02-02" ) ;
193+ expectUtcDateRange ( result , "2026-02-01" , "2026-02-02" ) ;
175194 } ) ;
176195
177- it ( "parseDateRange uses specific UTC offset for explicit dates " , ( ) => {
178- const range = testApi . parseDateRange ( {
196+ it ( "resolveDateRange uses explicit UTC mode " , ( ) => {
197+ const result = testApi . resolveDateRange ( {
179198 startDate : "2026-02-01" ,
180199 endDate : "2026-02-02" ,
181- mode : "specific" ,
182- utcOffset : "UTC+5:30" ,
200+ mode : "utc" ,
183201 } ) ;
202+ expectUtcDateRange ( result , "2026-02-01" , "2026-02-02" ) ;
203+ } ) ;
204+
205+ it ( "resolveDateRange uses specific UTC offset for explicit dates" , ( ) => {
206+ const range = expectDateRange (
207+ testApi . resolveDateRange ( {
208+ startDate : "2026-02-01" ,
209+ endDate : "2026-02-02" ,
210+ mode : "specific" ,
211+ utcOffset : "UTC+5:30" ,
212+ } ) ,
213+ ) ;
184214 const start = Date . UTC ( 2026 , 1 , 1 ) - 5.5 * 60 * 60 * 1000 ;
185215 const endStart = Date . UTC ( 2026 , 1 , 2 ) - 5.5 * 60 * 60 * 1000 ;
186216 expect ( range . startMs ) . toBe ( start ) ;
187217 expect ( range . endMs ) . toBe ( endStart + dayMs - 1 ) ;
188218 } ) ;
189219
190- it ( "parseDateRange falls back to UTC when specific mode offset is missing or invalid" , ( ) => {
191- const missingOffset = testApi . parseDateRange ( {
192- startDate : "2026-02-01" ,
193- endDate : "2026-02-02" ,
194- mode : "specific" ,
195- } ) ;
196- const invalidOffset = testApi . parseDateRange ( {
197- startDate : "2026-02-01" ,
198- endDate : "2026-02-02" ,
199- mode : "specific" ,
200- utcOffset : "bad-value" ,
201- } ) ;
220+ it ( "resolveDateRange falls back to UTC when specific mode offset is missing or invalid" , ( ) => {
221+ const missingOffset = expectDateRange (
222+ testApi . resolveDateRange ( {
223+ startDate : "2026-02-01" ,
224+ endDate : "2026-02-02" ,
225+ mode : "specific" ,
226+ } ) ,
227+ ) ;
228+ const invalidOffset = expectDateRange (
229+ testApi . resolveDateRange ( {
230+ startDate : "2026-02-01" ,
231+ endDate : "2026-02-02" ,
232+ mode : "specific" ,
233+ utcOffset : "bad-value" ,
234+ } ) ,
235+ ) ;
202236 expect ( missingOffset . startMs ) . toBe ( Date . UTC ( 2026 , 1 , 1 ) ) ;
203237 expect ( missingOffset . endMs ) . toBe ( Date . UTC ( 2026 , 1 , 2 ) + dayMs - 1 ) ;
204238 expect ( invalidOffset . startMs ) . toBe ( Date . UTC ( 2026 , 1 , 1 ) ) ;
205239 expect ( invalidOffset . endMs ) . toBe ( Date . UTC ( 2026 , 1 , 2 ) + dayMs - 1 ) ;
206240 } ) ;
207241
208- it ( "parseDateRange uses specific offset for today/day math after UTC midnight" , ( ) => {
242+ it ( "resolveDateRange uses specific offset for today/day math after UTC midnight" , ( ) => {
209243 vi . useFakeTimers ( ) ;
210244 vi . setSystemTime ( new Date ( "2026-02-17T03:57:00.000Z" ) ) ;
211- const range = testApi . parseDateRange ( {
212- days : 1 ,
213- mode : "specific" ,
214- utcOffset : "UTC-5" ,
215- } ) ;
245+ const range = expectDateRange (
246+ testApi . resolveDateRange ( {
247+ days : 1 ,
248+ mode : "specific" ,
249+ utcOffset : "UTC-5" ,
250+ } ) ,
251+ ) ;
216252 expect ( range . startMs ) . toBe ( Date . UTC ( 2026 , 1 , 16 , 5 , 0 , 0 , 0 ) ) ;
217253 expect ( range . endMs ) . toBe ( Date . UTC ( 2026 , 1 , 17 , 4 , 59 , 59 , 999 ) ) ;
218254 } ) ;
219255
220- it ( "parseDateRange uses gateway local day boundaries in gateway mode" , ( ) => {
256+ it ( "resolveDateRange uses gateway local day boundaries in gateway mode" , ( ) => {
221257 vi . useFakeTimers ( ) ;
222258 vi . setSystemTime ( new Date ( "2026-02-05T12:34:56.000Z" ) ) ;
223- const range = testApi . parseDateRange ( { days : 1 , mode : "gateway" } ) ;
259+ const range = expectDateRange ( testApi . resolveDateRange ( { days : 1 , mode : "gateway" } ) ) ;
224260 const expectedStart = new Date ( 2026 , 1 , 5 ) . getTime ( ) ;
225261 expect ( range . startMs ) . toBe ( expectedStart ) ;
226262 expect ( range . endMs ) . toBe ( expectedStart + dayMs - 1 ) ;
227263 } ) ;
228264
229- it ( "parseDateRange clamps days to at least 1 and defaults to 30 days" , ( ) => {
265+ it ( "resolveDateRange clamps days to at least 1 and defaults to 30 days" , ( ) => {
230266 vi . useFakeTimers ( ) ;
231267 vi . setSystemTime ( new Date ( "2026-02-05T12:34:56.000Z" ) ) ;
232- const oneDay = testApi . parseDateRange ( { days : 0 } ) ;
268+ const oneDay = expectDateRange ( testApi . resolveDateRange ( { days : 0 } ) ) ;
233269 expect ( oneDay . endMs ) . toBe ( Date . UTC ( 2026 , 1 , 5 ) + dayMs - 1 ) ;
234270 expect ( oneDay . startMs ) . toBe ( Date . UTC ( 2026 , 1 , 5 ) ) ;
235271
236- const def = testApi . parseDateRange ( { } ) ;
272+ const def = expectDateRange ( testApi . resolveDateRange ( { } ) ) ;
237273 expect ( def . endMs ) . toBe ( Date . UTC ( 2026 , 1 , 5 ) + dayMs - 1 ) ;
238274 expect ( def . startMs ) . toBe ( Date . UTC ( 2026 , 1 , 5 ) - 29 * dayMs ) ;
239275 } ) ;
0 commit comments