@@ -110,4 +110,81 @@ describe("cached assets", () => {
110110
111111 expect ( result ) . toEqual ( { url : null } ) ;
112112 } ) ;
113+
114+ it ( "schedules blob deletion with grace period beyond Redis TTL" , async ( ) => {
115+ const indexKey = ns ( "test" , "grace-test" ) ;
116+ const lockKey = ns ( "lock" , "grace-test" ) ;
117+ const purgeQueue = "test-queue" ;
118+ const ttl = 60 ; // 1 minute
119+ const gracePeriod = 300 ; // 5 minutes
120+
121+ const beforeMs = Date . now ( ) ;
122+
123+ await getOrCreateCachedAsset ( {
124+ indexKey,
125+ lockKey,
126+ ttlSeconds : ttl ,
127+ purgeQueue,
128+ blobGracePeriodSeconds : gracePeriod ,
129+ produceAndUpload : async ( ) => ( {
130+ url : "https://cdn/grace.webp" ,
131+ key : "grace-key" ,
132+ } ) ,
133+ } ) ;
134+
135+ const afterMs = Date . now ( ) ;
136+
137+ const { redis } = await import ( "@/lib/redis" ) ;
138+ const purgeKey = ns ( "purge" , purgeQueue ) ;
139+ const members = ( await redis . zrange ( purgeKey , 0 , - 1 ) ) as string [ ] ;
140+
141+ expect ( members ) . toHaveLength ( 1 ) ;
142+ expect ( members [ 0 ] ) . toBe ( "https://cdn/grace.webp" ) ;
143+
144+ const scheduledDeleteMs = await redis . zscore ( purgeKey , members [ 0 ] ) ;
145+ expect ( scheduledDeleteMs ) . not . toBeNull ( ) ;
146+
147+ const expectedMinMs = beforeMs + ( ttl + gracePeriod ) * 1000 ;
148+ const expectedMaxMs = afterMs + ( ttl + gracePeriod ) * 1000 ;
149+
150+ expect ( scheduledDeleteMs ) . toBeGreaterThanOrEqual ( expectedMinMs ) ;
151+ expect ( scheduledDeleteMs ) . toBeLessThanOrEqual ( expectedMaxMs ) ;
152+ } ) ;
153+
154+ it ( "uses 24-hour default grace period when not specified" , async ( ) => {
155+ const indexKey = ns ( "test" , "default-grace" ) ;
156+ const lockKey = ns ( "lock" , "default-grace" ) ;
157+ const purgeQueue = "default-queue" ;
158+ const ttl = 3600 ; // 1 hour
159+
160+ const beforeMs = Date . now ( ) ;
161+
162+ await getOrCreateCachedAsset ( {
163+ indexKey,
164+ lockKey,
165+ ttlSeconds : ttl ,
166+ purgeQueue,
167+ // No blobGracePeriodSeconds specified - should default to 86400 (24 hours)
168+ produceAndUpload : async ( ) => ( {
169+ url : "https://cdn/default.webp" ,
170+ } ) ,
171+ } ) ;
172+
173+ const afterMs = Date . now ( ) ;
174+
175+ const { redis } = await import ( "@/lib/redis" ) ;
176+ const purgeKey = ns ( "purge" , purgeQueue ) ;
177+ const members = ( await redis . zrange ( purgeKey , 0 , - 1 ) ) as string [ ] ;
178+
179+ expect ( members ) . toHaveLength ( 1 ) ;
180+ const scheduledDeleteMs = await redis . zscore ( purgeKey , members [ 0 ] ) ;
181+ expect ( scheduledDeleteMs ) . not . toBeNull ( ) ;
182+
183+ const defaultGracePeriod = 86400 ; // 24 hours
184+ const expectedMinMs = beforeMs + ( ttl + defaultGracePeriod ) * 1000 ;
185+ const expectedMaxMs = afterMs + ( ttl + defaultGracePeriod ) * 1000 ;
186+
187+ expect ( scheduledDeleteMs ) . toBeGreaterThanOrEqual ( expectedMinMs ) ;
188+ expect ( scheduledDeleteMs ) . toBeLessThanOrEqual ( expectedMaxMs ) ;
189+ } ) ;
113190} ) ;
0 commit comments