@@ -89,6 +89,22 @@ async function put(key: string, body = 'x'): Promise<void> {
8989 await writeFile ( join ( root , key ) , body )
9090}
9191
92+ /** Writes `count` objects a surviving document names, answering `lookups` reference checks. */
93+ async function putKept ( count : number , lookups = 1 ) : Promise < string [ ] > {
94+ const width = String ( count - 1 ) . length
95+ const keys = Array . from (
96+ { length : count } ,
97+ ( _ , index ) => `workspace/wsdoomed1/a-kept/${ String ( index ) . padStart ( width , '0' ) } .bin`
98+ )
99+ for ( const key of keys ) await put ( key )
100+ for ( let lookup = 0 ; lookup < lookups ; lookup ++ )
101+ queueTableRows (
102+ schemaMock . document ,
103+ keys . map ( ( key ) => ( { key } ) )
104+ )
105+ return keys
106+ }
107+
92108async function exists ( key : string ) : Promise < boolean > {
93109 try {
94110 await stat ( join ( root , key ) )
@@ -287,16 +303,8 @@ describe('deleted workspace storage sweep', () => {
287303 } )
288304
289305 it ( 'reaches objects behind more kept pages than one run can walk, spending no attempt' , async ( ) => {
290- const keptKeys = Array . from (
291- { length : 1000 } ,
292- ( _ , index ) => `workspace/wsdoomed1/a-kept/${ String ( index ) . padStart ( 4 , '0' ) } .bin`
293- )
294- for ( const key of [ ...keptKeys , 'workspace/wsdoomed1/z-unkept/0.bin' ] ) await put ( key )
295- for ( let call = 0 ; call < 30 ; call ++ )
296- queueTableRows (
297- schemaMock . document ,
298- keptKeys . map ( ( key ) => ( { key } ) )
299- )
306+ const keptKeys = await putKept ( 1000 , 30 )
307+ await put ( 'workspace/wsdoomed1/z-unkept/0.bin' )
300308
301309 const outcomes = await sweepUntilSettled ( { version : 1 , workspaceId : 'wsdoomed1' } , 2 )
302310
@@ -335,16 +343,19 @@ describe('deleted workspace storage sweep', () => {
335343 for ( const key of DOOMED_KEYS ) expect ( await exists ( key ) ) . toBe ( false )
336344 } )
337345
346+ it ( 'walks the family from the start when its saved position is not a key under the prefix' , async ( ) => {
347+ await expect (
348+ sweep (
349+ { version : 1 , workspaceId : 'wsdoomed1' , pageToken : 'zz-other-provider' } ,
350+ outboxContext ( )
351+ )
352+ ) . resolves . toBe ( undefined )
353+
354+ for ( const key of DOOMED_KEYS ) expect ( await exists ( key ) ) . toBe ( false )
355+ } )
356+
338357 it ( 'spends an attempt when a restart from a rejected position only walks kept objects' , async ( ) => {
339- const keptKeys = Array . from (
340- { length : 500 } ,
341- ( _ , index ) => `workspace/wsdoomed1/a-kept/${ String ( index ) . padStart ( 3 , '0' ) } .bin`
342- )
343- for ( const key of keptKeys ) await put ( key )
344- queueTableRows (
345- schemaMock . document ,
346- keptKeys . map ( ( key ) => ( { key } ) )
347- )
358+ const keptKeys = await putKept ( 500 )
348359 vi . mocked ( listStorageKeyPage ) . mockRejectedValueOnce (
349360 new Error ( 'The continuation token is invalid' )
350361 )
@@ -357,7 +368,29 @@ describe('deleted workspace storage sweep', () => {
357368
358369 expect ( outcome ) . toMatchObject ( { outcome : 'deferred' } )
359370 expect ( outcome ) . not . toHaveProperty ( 'consumeAttempt' )
360- expect ( checkpointPayload ) . toHaveBeenCalledWith ( { family : 0 , pageToken : keptKeys . at ( - 1 ) } )
371+ expect ( checkpointPayload ) . toHaveBeenCalledWith ( {
372+ family : 0 ,
373+ pageToken : keptKeys . at ( - 1 ) ,
374+ resumes : 0 ,
375+ } )
376+ } )
377+
378+ it ( 'spends an attempt on a moved position once its resume budget is gone' , async ( ) => {
379+ const keptKeys = await putKept ( 1000 )
380+ const checkpointPayload = vi . fn ( async ( ) => { } )
381+
382+ const outcome = await sweep (
383+ { version : 1 , workspaceId : 'wsdoomed1' , resumes : 1000 } ,
384+ deadlineAfter ( 1 , { checkpointPayload } )
385+ )
386+
387+ expect ( outcome ) . toMatchObject ( { outcome : 'deferred' } )
388+ expect ( outcome ) . not . toHaveProperty ( 'consumeAttempt' )
389+ expect ( checkpointPayload ) . toHaveBeenCalledWith ( {
390+ family : 0 ,
391+ pageToken : keptKeys [ 499 ] ,
392+ resumes : 1001 ,
393+ } )
361394 } )
362395
363396 it . each ( [
@@ -369,6 +402,8 @@ describe('deleted workspace storage sweep', () => {
369402 { version : 1 , workspaceId : 'wsdoomed1' , pageToken : 42 } ,
370403 { version : 1 , workspaceId : 'wsdoomed1' , pageToken : '' } ,
371404 { version : 1 , workspaceId : 'wsdoomed1' , pageToken : 'x' . repeat ( 4097 ) } ,
405+ { version : 1 , workspaceId : 'wsdoomed1' , resumes : - 1 } ,
406+ { version : 1 , workspaceId : 'wsdoomed1' , resumes : '1' } ,
372407 ] ) ( 'rejects an unbounded or malformed payload %o' , async ( payload ) => {
373408 await expect ( sweep ( payload , outboxContext ( ) ) ) . rejects . toThrow ( )
374409 for ( const key of DOOMED_KEYS ) expect ( await exists ( key ) ) . toBe ( true )
0 commit comments