1- import { describe , expect , test } from "vitest"
1+ import { describe , expect , test , vi } from "vitest"
22
33import type { ApiHandlerCreateMessageMetadata } from "../../index"
44import { RequestConfigBuilder } from "../config-builder/request-config-builder"
@@ -53,7 +53,7 @@ describe("RequestConfigBuilder", () => {
5353 const builder = new RequestConfigBuilder ( { initial : "value" } )
5454 builder . addAbortSignal ( undefined )
5555
56- const config = builder . build ( ) as Record < string , any >
56+ const config = builder . build ( ) as { signal ?: AbortSignal }
5757 expect ( config . signal ) . toBeUndefined ( )
5858 } )
5959
@@ -65,7 +65,7 @@ describe("RequestConfigBuilder", () => {
6565 const builder = new RequestConfigBuilder ( { initial : "value" } )
6666 builder . addAbortSignal ( metadata )
6767
68- const config = builder . build ( ) as Record < string , any >
68+ const config = builder . build ( ) as { signal ?: AbortSignal }
6969 expect ( config . signal ) . toBeUndefined ( )
7070 } )
7171
@@ -115,7 +115,7 @@ describe("RequestConfigBuilder", () => {
115115 const result = builder . addHeaders ( )
116116
117117 expect ( result ) . toBe ( builder ) // chainable
118- const config = builder . build ( ) as Record < string , any >
118+ const config = builder . build ( ) as { headers ?: Record < string , string > }
119119 expect ( config . headers ) . toBeUndefined ( )
120120 } )
121121
@@ -124,7 +124,7 @@ describe("RequestConfigBuilder", () => {
124124 const result = builder . addHeaders ( { } )
125125
126126 expect ( result ) . toBe ( builder ) // chainable
127- const config = builder . build ( ) as Record < string , any >
127+ const config = builder . build ( ) as { headers ?: Record < string , string > }
128128 expect ( config . headers ) . toBeUndefined ( )
129129 } )
130130
@@ -173,9 +173,9 @@ describe("RequestConfigBuilder", () => {
173173
174174 test ( "should do nothing when value is undefined" , ( ) => {
175175 const builder = new RequestConfigBuilder ( { initial : "value" } )
176- builder . setOption ( "initial" , undefined as any )
176+ builder . setOption ( "initial" , undefined as unknown as string )
177177
178- const config = builder . build ( ) as Record < string , any >
178+ const config = builder . build ( ) as { initial ?: string }
179179 // When setOption receives undefined, it should NOT modify the existing value
180180 expect ( config . initial ) . toBe ( "value" )
181181 } )
@@ -196,7 +196,12 @@ describe("RequestConfigBuilder", () => {
196196 builder . setOption ( "booleanKey" , true )
197197 builder . setOption ( "objectKey" , { nested : true } )
198198
199- const config = builder . build ( ) as Record < string , any >
199+ const config = builder . build ( ) as {
200+ stringKey ?: string
201+ numberKey ?: number
202+ booleanKey ?: boolean
203+ objectKey ?: { nested : boolean }
204+ }
200205 expect ( config . stringKey ) . toBe ( "stringValue" )
201206 expect ( config . numberKey ) . toBe ( 42 )
202207 expect ( config . booleanKey ) . toBe ( true )
@@ -208,7 +213,7 @@ describe("RequestConfigBuilder", () => {
208213 const result = builder . setOption ( "key1" , "value1" ) . setOption ( "key2" , "value2" )
209214
210215 expect ( result ) . toBe ( builder )
211- const config = builder . build ( ) as Record < string , any >
216+ const config = builder . build ( ) as { key1 ?: string ; key2 ?: string }
212217 expect ( config . key1 ) . toBe ( "value1" )
213218 expect ( config . key2 ) . toBe ( "value2" )
214219 } )
@@ -222,7 +227,7 @@ describe("RequestConfigBuilder", () => {
222227
223228 test ( "should return undefined for non-existent key" , ( ) => {
224229 const builder = new RequestConfigBuilder ( )
225- expect ( builder . getOption ( "nonExistent" as any ) ) . toBeUndefined ( )
230+ expect ( builder . getOption ( "nonExistent" ) ) . toBeUndefined ( )
226231 } )
227232 } )
228233
@@ -243,7 +248,7 @@ describe("RequestConfigBuilder", () => {
243248
244249 test ( "modifying build result should not affect internal state" , ( ) => {
245250 const builder = new RequestConfigBuilder ( { key : "value" } )
246- const result = builder . build ( ) as Record < string , any >
251+ const result = builder . build ( ) as { key : string }
247252
248253 result . key = "modified"
249254 expect ( builder . getOption ( "key" ) ) . toBe ( "value" )
@@ -259,7 +264,11 @@ describe("RequestConfigBuilder", () => {
259264 const builder = new RequestConfigBuilder ( )
260265 builder . addAbortSignal ( metadata ) . addHeaders ( { "X-Custom" : "value" } ) . setOption ( "modelId" , "test-model" )
261266
262- const config = builder . build ( ) as Record < string , any >
267+ const config = builder . build ( ) as {
268+ signal ?: AbortSignal
269+ headers ?: Record < string , string >
270+ modelId ?: string
271+ }
263272 expect ( config . signal ) . toBe ( controller . signal )
264273 expect ( config . headers ) . toEqual ( { "X-Custom" : "value" } )
265274 expect ( config . modelId ) . toBe ( "test-model" )
@@ -279,7 +288,7 @@ describe("RequestConfigBuilder", () => {
279288 abortSignal : controller . signal ,
280289 }
281290
282- const result = RequestConfigBuilder . fromMetadata ( metadata ) as Record < string , any >
291+ const result = RequestConfigBuilder . fromMetadata ( metadata ) as { signal ?: AbortSignal }
283292 expect ( result . signal ) . toBe ( controller . signal )
284293 } )
285294
@@ -291,23 +300,30 @@ describe("RequestConfigBuilder", () => {
291300 }
292301 const extraOptions = { modelId : "test-model" , customKey : "customValue" }
293302
294- const result = RequestConfigBuilder . fromMetadata ( metadata , extraOptions ) as Record < string , any >
303+ const result = RequestConfigBuilder . fromMetadata ( metadata , extraOptions ) as {
304+ signal ?: AbortSignal
305+ modelId ?: string
306+ customKey ?: string
307+ }
295308 expect ( result . signal ) . toBe ( controller . signal )
296309 expect ( result . modelId ) . toBe ( "test-model" )
297310 expect ( result . customKey ) . toBe ( "customValue" )
298311 } )
299312
300313 test ( "should return only extraOptions when metadata is undefined" , ( ) => {
301314 const extraOptions = { modelId : "test-model" }
302- const result = RequestConfigBuilder . fromMetadata ( undefined , extraOptions ) as Record < string , any >
315+ const result = RequestConfigBuilder . fromMetadata ( undefined , extraOptions ) as { modelId ?: string }
303316 expect ( result . modelId ) . toBe ( "test-model" )
304317 } )
305318
306319 test ( "should not set signal when metadata.abortSignal is undefined" , ( ) => {
307320 const metadata : ApiHandlerCreateMessageMetadata = { taskId : "test-task" }
308321 const extraOptions = { modelId : "test-model" }
309322
310- const result = RequestConfigBuilder . fromMetadata ( metadata , extraOptions ) as Record < string , any >
323+ const result = RequestConfigBuilder . fromMetadata ( metadata , extraOptions ) as {
324+ signal ?: AbortSignal
325+ modelId ?: string
326+ }
311327 expect ( result . signal ) . toBeUndefined ( )
312328 expect ( result . modelId ) . toBe ( "test-model" )
313329 } )
@@ -345,23 +361,61 @@ describe("RequestConfigBuilder", () => {
345361 expect ( config . signal ?. aborted ) . toBe ( true )
346362 } )
347363
348- test ( "should merge internal controller signal with timeout" , async ( ) => {
364+ test ( "should clear timeout when cleanup runs before timeout fires " , async ( ) => {
349365 vi . useFakeTimers ( )
350- const internalController = new AbortController ( )
351- const builder = new RequestConfigBuilder ( )
366+ try {
367+ const internalController = new AbortController ( )
368+ const builder = new RequestConfigBuilder ( )
352369
353- builder . addMergedSignal ( internalController , undefined , 100 )
370+ builder . addMergedSignal ( internalController , undefined , 100 )
354371
355- const config = builder . build ( ) as { signal ?: AbortSignal ; _cleanup ?: ( ) => void }
356- expect ( config . signal ) . not . toBe ( internalController . signal )
357- expect ( config ) . not . toHaveProperty ( "_cleanup" )
358- expect ( config . signal ?. aborted ) . toBe ( false )
372+ const config = builder . build ( ) as { signal ?: AbortSignal ; _cleanup ?: ( ) => void }
373+ expect ( config . signal ) . not . toBe ( internalController . signal )
374+ expect ( config ) . not . toHaveProperty ( "_cleanup" )
375+ expect ( config . signal ?. aborted ) . toBe ( false )
376+ expect ( vi . getTimerCount ( ) ) . toBe ( 1 )
359377
360- await vi . advanceTimersByTimeAsync ( 100 )
378+ builder . getCleanup ( ) ( )
361379
380+ expect ( vi . getTimerCount ( ) ) . toBe ( 0 )
381+ await vi . advanceTimersByTimeAsync ( 100 )
382+ expect ( config . signal ?. aborted ) . toBe ( false )
383+ } finally {
384+ vi . useRealTimers ( )
385+ }
386+ } )
387+
388+ test ( "should cleanup timeouts from repeated addMergedSignal calls" , ( ) => {
389+ vi . useFakeTimers ( )
390+ try {
391+ const internalController = new AbortController ( )
392+ const builder = new RequestConfigBuilder ( )
393+
394+ builder . addMergedSignal ( internalController , undefined , 100 )
395+ builder . addMergedSignal ( internalController , undefined , 200 )
396+
397+ expect ( vi . getTimerCount ( ) ) . toBe ( 2 )
398+ builder . getCleanup ( ) ( )
399+ expect ( vi . getTimerCount ( ) ) . toBe ( 0 )
400+ } finally {
401+ vi . useRealTimers ( )
402+ }
403+ } )
404+
405+ test ( "should immediately abort when metadata signal is already aborted" , ( ) => {
406+ const internalController = new AbortController ( )
407+ const externalController = new AbortController ( )
408+ externalController . abort ( )
409+ const builder = new RequestConfigBuilder ( )
410+
411+ builder . addMergedSignal ( internalController , {
412+ taskId : "test-task" ,
413+ abortSignal : externalController . signal ,
414+ } )
415+
416+ const config = builder . build ( ) as { signal ?: AbortSignal }
362417 expect ( config . signal ?. aborted ) . toBe ( true )
363- builder . getCleanup ( ) ( )
364- vi . useRealTimers ( )
418+ expect ( ( ) => builder . getCleanup ( ) ( ) ) . not . toThrow ( )
365419 } )
366420 } )
367421
@@ -393,7 +447,7 @@ describe("RequestConfigBuilder", () => {
393447 expect ( result . aborted ) . toBe ( false )
394448 } )
395449
396- test ( "should abort merged signal when primarySignal is aborted" , async ( ) => {
450+ test ( "should abort merged signal when primarySignal is aborted" , ( ) => {
397451 const primaryController = new AbortController ( )
398452 const secondaryController = new AbortController ( )
399453
@@ -413,12 +467,10 @@ describe("RequestConfigBuilder", () => {
413467
414468 primaryController . abort ( )
415469
416- // Wait for event to propagate
417- await new Promise ( ( resolve ) => setTimeout ( resolve , 10 ) )
418470 expect ( aborted ) . toBe ( true )
419471 } )
420472
421- test ( "should abort merged signal when secondarySignal is aborted" , async ( ) => {
473+ test ( "should abort merged signal when secondarySignal is aborted" , ( ) => {
422474 const primaryController = new AbortController ( )
423475 const secondaryController = new AbortController ( )
424476
@@ -438,12 +490,10 @@ describe("RequestConfigBuilder", () => {
438490
439491 secondaryController . abort ( )
440492
441- // Wait for event to propagate
442- await new Promise ( ( resolve ) => setTimeout ( resolve , 10 ) )
443493 expect ( aborted ) . toBe ( true )
444494 } )
445495
446- test ( "should not abort merged signal when neither signal is aborted" , async ( ) => {
496+ test ( "should not abort merged signal when neither signal is aborted" , ( ) => {
447497 const primaryController = new AbortController ( )
448498 const secondaryController = new AbortController ( )
449499
@@ -461,7 +511,6 @@ describe("RequestConfigBuilder", () => {
461511 { once : true } ,
462512 )
463513
464- await new Promise ( ( resolve ) => setTimeout ( resolve , 10 ) )
465514 expect ( aborted ) . toBe ( false )
466515 } )
467516
@@ -509,7 +558,7 @@ describe("RequestConfigBuilder", () => {
509558 test ( "should handle empty builder through full lifecycle" , ( ) => {
510559 const builder = new RequestConfigBuilder ( )
511560 expect ( builder . build ( ) ) . toBeUndefined ( )
512- expect ( builder . getOption ( "anyKey" as any ) ) . toBeUndefined ( )
561+ expect ( builder . getOption ( "anyKey" ) ) . toBeUndefined ( )
513562 } )
514563
515564 test ( "should work with custom default options type" , ( ) => {
0 commit comments