@@ -154,6 +154,9 @@ describe('chunk', () => {
154154} )
155155
156156describe ( 'hardPurgeSurrogateKeys' , ( ) => {
157+ // Skips the between-pass delay so tests don't wait 20 real seconds.
158+ const noSleep = async ( ) => { }
159+
157160 // A minimal stand-in for a fetch Response, with a case-insensitive headers.get.
158161 function fakeResponse (
159162 status : number ,
@@ -170,14 +173,16 @@ describe('hardPurgeSurrogateKeys', () => {
170173 }
171174 }
172175
173- test ( 'sends one hard batch purge with a surrogate_keys body (no soft header)' , async ( ) => {
176+ test ( 'sends one hard batch purge per pass with a surrogate_keys body (no soft header)' , async ( ) => {
174177 fetchWithRetry . mockResolvedValue ( { ok : true } )
175178 await hardPurgeSurrogateKeys (
176179 [ 'language:en,path:a.md' , 'language:en,path:b.md' ] ,
177180 'token-123' ,
178181 'svc-1' ,
182+ undefined ,
183+ noSleep ,
179184 )
180- expect ( fetchWithRetry ) . toHaveBeenCalledTimes ( 1 )
185+ expect ( fetchWithRetry ) . toHaveBeenCalledTimes ( 2 )
181186 const [ url , init ] = fetchWithRetry . mock . calls [ 0 ]
182187 expect ( url ) . toBe ( 'https://api.fastly.com/service/svc-1/purge' )
183188 expect ( init . method ) . toBe ( 'POST' )
@@ -186,46 +191,83 @@ describe('hardPurgeSurrogateKeys', () => {
186191 expect ( JSON . parse ( init . body ) ) . toEqual ( {
187192 surrogate_keys : [ 'language:en,path:a.md' , 'language:en,path:b.md' ] ,
188193 } )
194+ // The second pass repeats the identical batch.
195+ expect ( fetchWithRetry . mock . calls [ 1 ] [ 1 ] . body ) . toBe ( init . body )
196+ } )
197+
198+ test ( 'waits between the two passes to let the shield re-populate first' , async ( ) => {
199+ fetchWithRetry . mockResolvedValue ( { ok : true } )
200+ const waits : number [ ] = [ ]
201+ await hardPurgeSurrogateKeys (
202+ [ 'language:en,path:a.md' ] ,
203+ 'tok' ,
204+ 'svc' ,
205+ undefined ,
206+ async ( ms : number ) => {
207+ waits . push ( ms )
208+ } ,
209+ )
210+ expect ( waits ) . toEqual ( [ 20_000 ] )
189211 } )
190212
191- test ( 'splits more than 256 keys into multiple batches' , async ( ) => {
213+ test ( 'splits more than 256 keys into multiple batches, per pass ' , async ( ) => {
192214 fetchWithRetry . mockResolvedValue ( { ok : true } )
193215 const keys = Array . from ( { length : 257 } , ( _unused , i ) => `language:en,path:p${ i } .md` )
194- await hardPurgeSurrogateKeys ( keys , 'tok' , 'svc' )
195- expect ( fetchWithRetry ) . toHaveBeenCalledTimes ( 2 )
216+ await hardPurgeSurrogateKeys ( keys , 'tok' , 'svc' , undefined , noSleep )
217+ // 2 batches x 2 passes.
218+ expect ( fetchWithRetry ) . toHaveBeenCalledTimes ( 4 )
196219 expect ( JSON . parse ( fetchWithRetry . mock . calls [ 0 ] [ 1 ] . body ) . surrogate_keys ) . toHaveLength ( 256 )
197220 expect ( JSON . parse ( fetchWithRetry . mock . calls [ 1 ] [ 1 ] . body ) . surrogate_keys ) . toHaveLength ( 1 )
221+ expect ( JSON . parse ( fetchWithRetry . mock . calls [ 2 ] [ 1 ] . body ) . surrogate_keys ) . toHaveLength ( 256 )
222+ expect ( JSON . parse ( fetchWithRetry . mock . calls [ 3 ] [ 1 ] . body ) . surrogate_keys ) . toHaveLength ( 1 )
198223 } )
199224
200- test ( 'throws if any batch fails, after attempting all of them' , async ( ) => {
225+ test ( 'throws if any batch fails, after attempting all of them in both passes ' , async ( ) => {
201226 fetchWithRetry . mockResolvedValueOnce ( { ok : true } ) . mockResolvedValueOnce ( {
202227 ok : false ,
203228 status : 500 ,
204229 statusText : 'err' ,
205230 text : async ( ) => 'boom' ,
206231 } )
232+ fetchWithRetry . mockResolvedValue ( { ok : true } )
207233 const keys = Array . from ( { length : 300 } , ( _unused , i ) => `language:en,path:p${ i } .md` )
208- await expect ( hardPurgeSurrogateKeys ( keys , 'tok' , 'svc' ) ) . rejects . toThrow (
209- / 1 o f 2 b a t c h p u r g e \( s \) f a i l e d / ,
234+ await expect ( hardPurgeSurrogateKeys ( keys , 'tok' , 'svc' , undefined , noSleep ) ) . rejects . toThrow (
235+ / 1 o f 4 b a t c h p u r g e \( s \) f a i l e d / ,
210236 )
237+ expect ( fetchWithRetry ) . toHaveBeenCalledTimes ( 4 )
238+ } )
239+
240+ test ( 'still runs the second pass when the first one fails outright' , async ( ) => {
241+ fetchWithRetry
242+ . mockResolvedValueOnce ( {
243+ ok : false ,
244+ status : 500 ,
245+ statusText : 'err' ,
246+ text : async ( ) => 'boom' ,
247+ } )
248+ . mockResolvedValue ( { ok : true } )
249+ await expect (
250+ hardPurgeSurrogateKeys ( [ 'language:en,path:a.md' ] , 'tok' , 'svc' , undefined , noSleep ) ,
251+ ) . rejects . toThrow ( / 1 o f 2 b a t c h p u r g e \( s \) f a i l e d / )
211252 expect ( fetchWithRetry ) . toHaveBeenCalledTimes ( 2 )
212253 } )
213254
214255 test ( 'retries a 429, honoring the hint, then succeeds' , async ( ) => {
215256 fetchWithRetry
216257 . mockResolvedValueOnce ( fakeResponse ( 429 , { headers : { 'retry-after' : '0' } } ) )
217- . mockResolvedValueOnce ( fakeResponse ( 200 , { ok : true } ) )
218- await hardPurgeSurrogateKeys ( [ 'language:en,path:a.md' ] , 'tok' , 'svc' , ( ) => 0 )
219- expect ( fetchWithRetry ) . toHaveBeenCalledTimes ( 2 )
258+ . mockResolvedValue ( fakeResponse ( 200 , { ok : true } ) )
259+ await hardPurgeSurrogateKeys ( [ 'language:en,path:a.md' ] , 'tok' , 'svc' , ( ) => 0 , noSleep )
260+ // 429 + retry on the first pass, then one call for the second pass.
261+ expect ( fetchWithRetry ) . toHaveBeenCalledTimes ( 3 )
220262 } )
221263
222264 test ( 'gives up after the retry budget and reports the batch as failed' , async ( ) => {
223265 fetchWithRetry . mockResolvedValue ( fakeResponse ( 429 , { headers : { 'retry-after' : '0' } } ) )
224266 await expect (
225- hardPurgeSurrogateKeys ( [ 'language:en,path:a.md' ] , 'tok' , 'svc' , ( ) => 0 ) ,
226- ) . rejects . toThrow ( / 1 o f 1 b a t c h p u r g e \( s \) f a i l e d / )
227- // Initial attempt + 5 retries.
228- expect ( fetchWithRetry ) . toHaveBeenCalledTimes ( 6 )
267+ hardPurgeSurrogateKeys ( [ 'language:en,path:a.md' ] , 'tok' , 'svc' , ( ) => 0 , noSleep ) ,
268+ ) . rejects . toThrow ( / 2 o f 2 b a t c h p u r g e \( s \) f a i l e d / )
269+ // ( Initial attempt + 5 retries) x 2 passes .
270+ expect ( fetchWithRetry ) . toHaveBeenCalledTimes ( 12 )
229271 } )
230272} )
231273
0 commit comments