@@ -390,7 +390,7 @@ test('an essential read retries one failed frame without exposing the transient
390390 assert . throws ( ( ) => { f . client . readRetryReason = 'send-failed' ; } , TypeError ) ;
391391} ) ;
392392
393- test ( 'an essential read retries one frame-load timeout within the same call ' , async ( t ) => {
393+ test ( 'an explicit timeout overrides the frame-load budget ' , async ( t ) => {
394394 t . mock . timers . enable ( { apis : [ 'setTimeout' ] } ) ;
395395 const f = fixture ( { autoLoad : false , timeoutMs : 80 } ) ;
396396 t . after ( ( ) => f . client . close ( ) ) ;
@@ -413,20 +413,78 @@ test('an essential read retries one frame-load timeout within the same call', as
413413 assert . equal ( f . failures . length , 0 ) ;
414414} ) ;
415415
416- test ( 'iframe startup does not consume the broker response timeout ' , async ( t ) => {
416+ test ( 'the default 30-second frame-load budget still permits only two attempts ' , async ( t ) => {
417417 t . mock . timers . enable ( { apis : [ 'setTimeout' ] } ) ;
418- const f = fixture ( { autoLoad : false , timeoutMs : 80 } ) ;
418+ const f = fixture ( { autoLoad : false , timeoutMs : undefined } ) ;
419+ t . after ( ( ) => f . client . close ( ) ) ;
420+ const read = f . client . read ( ) ;
421+ const outcome = read . then ( value => ( { value } ) , error => ( { error } ) ) ;
422+ const firstFrame = f . frame ;
423+
424+ t . mock . timers . tick ( 6_500 ) ;
425+ await new Promise ( resolve => setImmediate ( resolve ) ) ;
426+ assert . equal ( f . frame , firstFrame ) ;
427+ assert . equal ( firstFrame . removed , undefined ) ;
428+ assert . equal ( f . client . readRetryReason , null ) ;
429+
430+ t . mock . timers . tick ( 23_500 ) ;
431+ await new Promise ( resolve => setImmediate ( resolve ) ) ;
432+ const retryFrame = f . frame ;
433+ assert . notEqual ( retryFrame , firstFrame ) ;
434+ assert . equal ( firstFrame . removed , true ) ;
435+ assert . equal ( f . client . readRetryReason , 'frame-load-timeout' ) ;
436+
437+ t . mock . timers . tick ( 30_000 ) ;
438+ const result = await outcome ;
439+ assert . equal ( result . value , undefined ) ;
440+ assert . equal ( result . error ?. code , 'frame-load-timeout' ) ;
441+ assert . equal ( retryFrame . removed , true ) ;
442+ assert . equal ( f . frames . length , 2 ) ;
443+ assert . equal ( f . sent . length , 0 ) ;
444+ assert . deepEqual ( f . failures , [ result . error ] ) ;
445+ } ) ;
446+
447+ test ( 'the default broker response budget remains 6.5 seconds after the frame loads' , async ( t ) => {
448+ t . mock . timers . enable ( { apis : [ 'setTimeout' ] } ) ;
449+ const f = fixture ( { timeoutMs : undefined } ) ;
450+ t . after ( ( ) => f . client . close ( ) ) ;
451+ const read = f . client . read ( ) ;
452+ const outcome = read . then ( value => ( { value } ) , error => ( { error } ) ) ;
453+ await new Promise ( resolve => setImmediate ( resolve ) ) ;
454+ const firstFrame = f . frame ;
455+
456+ t . mock . timers . tick ( 6_499 ) ;
457+ await new Promise ( resolve => setImmediate ( resolve ) ) ;
458+ assert . equal ( f . frame , firstFrame ) ;
459+ assert . equal ( firstFrame . removed , undefined ) ;
460+
461+ t . mock . timers . tick ( 1 ) ;
462+ await new Promise ( resolve => setImmediate ( resolve ) ) ;
463+ assert . notEqual ( f . frame , firstFrame ) ;
464+ assert . equal ( firstFrame . removed , true ) ;
465+ assert . equal ( f . client . readRetryReason , 'response-timeout' ) ;
466+ f . reply ( unknown ) ;
467+ assert . deepEqual ( await outcome , { value : unknown } ) ;
468+ assert . equal ( f . failures . length , 0 ) ;
469+ } ) ;
470+
471+ test ( 'a separate frame-load budget lets startup wait without consuming the response timeout' , async ( t ) => {
472+ t . mock . timers . enable ( { apis : [ 'setTimeout' ] } ) ;
473+ const f = fixture ( { autoLoad : false , timeoutMs : 80 , frameLoadTimeoutMs : 240 } ) ;
419474 t . after ( ( ) => f . client . close ( ) ) ;
420475 const pending = f . client . read ( ) ;
421476 const outcome = pending . then ( value => ( { value } ) , error => ( { error } ) ) ;
422477 const requestFrame = f . frame ;
423478
424- t . mock . timers . tick ( 50 ) ;
479+ t . mock . timers . tick ( 160 ) ;
480+ assert . equal ( f . frame , requestFrame ) ;
481+ assert . equal ( requestFrame . removed , undefined ) ;
482+ assert . equal ( f . sent . length , 0 ) ;
425483 requestFrame . fireLoad ( ) ;
426484 await new Promise ( resolve => setImmediate ( resolve ) ) ;
427485 const request = f . sent . at ( - 1 ) . data ;
428486
429- t . mock . timers . tick ( 50 ) ;
487+ t . mock . timers . tick ( 79 ) ;
430488 f . message ( { ...envelope , id : request . id , ok : true , state : unknown } , {
431489 source : requestFrame . contentWindow ,
432490 } ) ;
@@ -435,7 +493,7 @@ test('iframe startup does not consume the broker response timeout', async (t) =>
435493 assert . deepEqual ( result . value , unknown ) ;
436494} ) ;
437495
438- test ( 'an essential read retries one response timeout with a fresh frame ' , async ( t ) => {
496+ test ( 'an explicit timeout overrides the broker response budget ' , async ( t ) => {
439497 t . mock . timers . enable ( { apis : [ 'setTimeout' ] } ) ;
440498 const f = fixture ( { timeoutMs : 80 } ) ;
441499 t . after ( ( ) => f . client . close ( ) ) ;
@@ -658,8 +716,10 @@ test('a retained-refusal read superseded by a newer accepted choice resolves wit
658716 assert . equal ( f . failures . length , 0 ) ;
659717} ) ;
660718
661- test ( 'association timeout bounds frame readiness and prevents a late identifier request' , async ( ) => {
662- const f = fixture ( { timeoutMs : 100 } ) ;
719+ test ( 'association keeps its 750ms total deadline despite a longer frame-load budget' , async ( t ) => {
720+ t . mock . timers . enable ( { apis : [ 'setTimeout' ] } ) ;
721+ const f = fixture ( { timeoutMs : 100 , frameLoadTimeoutMs : 30_000 } ) ;
722+ t . after ( ( ) => f . client . close ( ) ) ;
663723 const initial = await nextRequest ( ( ) => f . client . read ( ) ) ;
664724 f . reply ( accepted ) ;
665725 await initial . promise ;
@@ -668,9 +728,13 @@ test('association timeout bounds frame readiness and prevents a late identifier
668728 f . message ( { ...envelope , type : 'changed' , state : renewed } ) ;
669729
670730 let outcome = 'pending' ;
671- const association = f . client . associate ( renewed . generation , { timeoutMs : 5 } ) ;
731+ const association = f . client . associate ( renewed . generation , { timeoutMs : 750 } ) ;
672732 association . then ( ( ) => { outcome = 'resolved' ; } , ( ) => { outcome = 'rejected' ; } ) ;
673- await new Promise ( resolve => setTimeout ( resolve , 20 ) ) ;
733+ t . mock . timers . tick ( 749 ) ;
734+ await new Promise ( resolve => setImmediate ( resolve ) ) ;
735+ assert . equal ( outcome , 'pending' ) ;
736+ t . mock . timers . tick ( 1 ) ;
737+ await new Promise ( resolve => setImmediate ( resolve ) ) ;
674738 assert . equal ( outcome , 'rejected' ) ;
675739 const lateFrame = f . frame ;
676740 lateFrame . fireLoad ( ) ;
@@ -679,7 +743,6 @@ test('association timeout bounds frame readiness and prevents a late identifier
679743 await assert . rejects ( association , / t r a c k i n g / i) ;
680744 assert . deepEqual ( f . client . state , renewed ) ;
681745 assert . equal ( f . failures . length , 0 ) ;
682- f . client . close ( ) ;
683746} ) ;
684747
685748test ( 'an unavailable association falls back without suspending accepted consent' , async ( ) => {
0 commit comments