@@ -111,6 +111,125 @@ describe('pollCdnUntilCaughtUp', () => {
111111 expect ( result ) . toMatchObject ( { ok : true , reason : 'match' , attempts : 3 } ) ;
112112 } ) ;
113113
114+ it ( 're-triggers on the configured cadence while the CDN is behind' , async ( ) => {
115+ const { now, sleep } = fakeClock ( ) ;
116+ let retriggerCalls = 0 ;
117+ const result = await pollCdnUntilCaughtUp ( {
118+ ...base ,
119+ now,
120+ sleep,
121+ fetchImpl : scriptedFetch ( [
122+ ( ) => manifest ( '0.12.0' ) ,
123+ ( ) => manifest ( '0.12.0' ) ,
124+ ( ) => manifest ( '0.12.0' ) ,
125+ ( ) => manifest ( '0.12.0' ) ,
126+ ( ) => manifest ( '0.12.0' ) ,
127+ ( ) => manifest ( '0.12.0' ) ,
128+ ( ) => manifest ( '0.13.0' ) ,
129+ ] ) ,
130+ retrigger : async ( ) => {
131+ retriggerCalls += 1 ;
132+ } ,
133+ retriggerEveryAttempts : 3 ,
134+ } ) ;
135+
136+ expect ( retriggerCalls ) . toBe ( 2 ) ;
137+ expect ( result ) . toMatchObject ( { ok : true , attempts : 7 , retriggers : 2 } ) ;
138+ } ) ;
139+
140+ it ( 'does not re-trigger when the first attempt matches' , async ( ) => {
141+ const { now, sleep } = fakeClock ( ) ;
142+ let retriggerCalls = 0 ;
143+ const result = await pollCdnUntilCaughtUp ( {
144+ ...base ,
145+ now,
146+ sleep,
147+ fetchImpl : scriptedFetch ( [ ( ) => manifest ( '0.13.0' ) ] ) ,
148+ retrigger : async ( ) => {
149+ retriggerCalls += 1 ;
150+ } ,
151+ retriggerEveryAttempts : 1 ,
152+ } ) ;
153+
154+ expect ( retriggerCalls ) . toBe ( 0 ) ;
155+ expect ( result . retriggers ) . toBe ( 0 ) ;
156+ } ) ;
157+
158+ it ( 'does not re-trigger when the CDN is ahead' , async ( ) => {
159+ const { now, sleep } = fakeClock ( ) ;
160+ let retriggerCalls = 0 ;
161+ const result = await pollCdnUntilCaughtUp ( {
162+ ...base ,
163+ now,
164+ sleep,
165+ fetchImpl : scriptedFetch ( [ ( ) => manifest ( '0.14.0' ) ] ) ,
166+ retrigger : async ( ) => {
167+ retriggerCalls += 1 ;
168+ } ,
169+ retriggerEveryAttempts : 1 ,
170+ } ) ;
171+
172+ expect ( retriggerCalls ) . toBe ( 0 ) ;
173+ expect ( result ) . toMatchObject ( { reason : 'ahead' , retriggers : 0 } ) ;
174+ } ) ;
175+
176+ it ( 'keeps polling when a re-trigger throws' , async ( ) => {
177+ const { now, sleep } = fakeClock ( ) ;
178+ let retriggerCalls = 0 ;
179+ const result = await pollCdnUntilCaughtUp ( {
180+ ...base ,
181+ now,
182+ sleep,
183+ fetchImpl : scriptedFetch ( [ ( ) => manifest ( '0.12.0' ) , ( ) => manifest ( '0.13.0' ) ] ) ,
184+ retrigger : async ( ) => {
185+ retriggerCalls += 1 ;
186+ throw new Error ( 'trigger failed' ) ;
187+ } ,
188+ retriggerEveryAttempts : 1 ,
189+ } ) ;
190+
191+ expect ( retriggerCalls ) . toBe ( 1 ) ;
192+ expect ( result ) . toMatchObject ( { ok : true , attempts : 2 , retriggers : 1 } ) ;
193+ } ) ;
194+
195+ it ( 're-triggers while the CDN is unreachable' , async ( ) => {
196+ const { now, sleep } = fakeClock ( ) ;
197+ let retriggerCalls = 0 ;
198+ const result = await pollCdnUntilCaughtUp ( {
199+ ...base ,
200+ now,
201+ sleep,
202+ fetchImpl : scriptedFetch ( [
203+ ( ) => {
204+ throw new Error ( 'ECONNREFUSED' ) ;
205+ } ,
206+ ( ) => manifest ( '0.13.0' ) ,
207+ ] ) ,
208+ retrigger : async ( ) => {
209+ retriggerCalls += 1 ;
210+ } ,
211+ retriggerEveryAttempts : 1 ,
212+ } ) ;
213+
214+ expect ( retriggerCalls ) . toBe ( 1 ) ;
215+ expect ( result ) . toMatchObject ( { ok : true , retriggers : 1 } ) ;
216+ } ) ;
217+
218+ it ( 'reports re-trigger attempts when the budget expires' , async ( ) => {
219+ const { now, sleep } = fakeClock ( ) ;
220+ const result = await pollCdnUntilCaughtUp ( {
221+ ...base ,
222+ budgetMs : 45_000 ,
223+ now,
224+ sleep,
225+ fetchImpl : scriptedFetch ( [ ( ) => manifest ( '0.12.0' ) ] ) ,
226+ retrigger : async ( ) => { } ,
227+ retriggerEveryAttempts : 1 ,
228+ } ) ;
229+
230+ expect ( result ) . toMatchObject ( { reason : 'timeout' , attempts : 3 , retriggers : 2 } ) ;
231+ } ) ;
232+
114233 it ( 'treats an unreachable CDN as lag rather than a failure' , async ( ) => {
115234 const { now, sleep } = fakeClock ( ) ;
116235 const result = await pollCdnUntilCaughtUp ( {
0 commit comments