@@ -2,35 +2,53 @@ import { describe, expect, it, vi } from "vitest";
22
33import { DurableObjectQueueHandler } from "./queue" ;
44
5- vi . mock ( ' cloudflare:workers' , ( ) => ( {
5+ vi . mock ( " cloudflare:workers" , ( ) => ( {
66 DurableObject : class {
77 ctx : DurableObjectState ;
88 env : CloudflareEnv ;
99 constructor ( ctx : DurableObjectState , env : CloudflareEnv ) {
1010 this . ctx = ctx ;
1111 this . env = env ;
1212 }
13- }
14- } ) )
15-
16- const createDurableObjectQueue = ( { fetchDuration, statusCode, headers} : { fetchDuration : number , statusCode ?: number , headers ?: Headers } ) => {
13+ } ,
14+ } ) ) ;
15+
16+ const createDurableObjectQueue = ( {
17+ fetchDuration,
18+ statusCode,
19+ headers,
20+ } : {
21+ fetchDuration : number ;
22+ statusCode ?: number ;
23+ headers ?: Headers ;
24+ } ) => {
1725 const mockState = {
1826 waitUntil : vi . fn ( ) ,
19- blockConcurrencyWhile : vi . fn ( ) . mockImplementation ( async ( fn ) => fn ( ) ) ,
27+ blockConcurrencyWhile : vi . fn ( ) . mockImplementation ( async ( fn ) => fn ( ) ) ,
2028 storage : {
2129 setAlarm : vi . fn ( ) ,
2230 getAlarm : vi . fn ( ) ,
23- }
31+ } ,
2432 } ;
2533 // eslint-disable-next-line @typescript-eslint/no-explicit-any
2634 return new DurableObjectQueueHandler ( mockState as any , {
2735 NEXT_CACHE_REVALIDATION_WORKER : {
28- fetch : vi . fn ( ) . mockReturnValue ( new Promise < Response > ( ( res ) => setTimeout ( ( ) => res ( new Response ( null , {
29- status : statusCode ,
30- headers : headers ?? new Headers ( [ [ "x-nextjs-cache" , "REVALIDATED" ] ] )
31- } ) ) , fetchDuration ) ) ) ,
36+ fetch : vi . fn ( ) . mockReturnValue (
37+ new Promise < Response > ( ( res ) =>
38+ setTimeout (
39+ ( ) =>
40+ res (
41+ new Response ( null , {
42+ status : statusCode ,
43+ headers : headers ?? new Headers ( [ [ "x-nextjs-cache" , "REVALIDATED" ] ] ) ,
44+ } )
45+ ) ,
46+ fetchDuration
47+ )
48+ )
49+ ) ,
3250 connect : vi . fn ( ) ,
33- }
51+ } ,
3452 } ) ;
3553} ;
3654
@@ -39,19 +57,19 @@ const createMessage = (dedupId: string) => ({
3957 MessageGroupId : "test.local/test" ,
4058 MessageDeduplicationId : dedupId ,
4159 previewModeId : "test" ,
42- } )
60+ } ) ;
4361
44- describe ( ' DurableObjectQueue' , ( ) => {
45- describe ( ' successful revalidation' , ( ) => {
62+ describe ( " DurableObjectQueue" , ( ) => {
63+ describe ( " successful revalidation" , ( ) => {
4664 it ( "should process a single revalidation" , async ( ) => {
47- const queue = createDurableObjectQueue ( { fetchDuration :10 } ) ;
65+ const queue = createDurableObjectQueue ( { fetchDuration : 10 } ) ;
4866 const firstRequest = await queue . revalidate ( createMessage ( "id" ) ) ;
4967 expect ( firstRequest ) . toBeUndefined ( ) ;
5068 expect ( queue . ongoingRevalidations . size ) . toBe ( 1 ) ;
5169 expect ( queue . ongoingRevalidations . has ( "id" ) ) . toBe ( true ) ;
52-
70+
5371 await queue . ongoingRevalidations . get ( "id" ) ;
54-
72+
5573 expect ( queue . ongoingRevalidations . size ) . toBe ( 0 ) ;
5674 expect ( queue . ongoingRevalidations . has ( "id" ) ) . toBe ( false ) ;
5775 expect ( queue . service . fetch ) . toHaveBeenCalledWith ( "https://test.local/test" , {
@@ -60,59 +78,59 @@ describe('DurableObjectQueue', () => {
6078 "x-prerender-revalidate" : "test" ,
6179 "x-isr" : "1" ,
6280 } ,
63- signal : expect . any ( AbortSignal )
81+ signal : expect . any ( AbortSignal ) ,
6482 } ) ;
65- } )
66-
67- it ( ' should dedupe revalidations' , async ( ) => {
68- const queue = createDurableObjectQueue ( { fetchDuration :10 } ) ;
83+ } ) ;
84+
85+ it ( " should dedupe revalidations" , async ( ) => {
86+ const queue = createDurableObjectQueue ( { fetchDuration : 10 } ) ;
6987 await queue . revalidate ( createMessage ( "id" ) ) ;
7088 await queue . revalidate ( createMessage ( "id" ) ) ;
7189 expect ( queue . ongoingRevalidations . size ) . toBe ( 1 ) ;
7290 expect ( queue . ongoingRevalidations . has ( "id" ) ) . toBe ( true ) ;
7391 } ) ;
74-
75- it ( ' should block concurrency' , async ( ) => {
76- const queue = createDurableObjectQueue ( { fetchDuration : 10 } ) ;
92+
93+ it ( " should block concurrency" , async ( ) => {
94+ const queue = createDurableObjectQueue ( { fetchDuration : 10 } ) ;
7795 await queue . revalidate ( createMessage ( "id" ) ) ;
7896 await queue . revalidate ( createMessage ( "id2" ) ) ;
7997 await queue . revalidate ( createMessage ( "id3" ) ) ;
8098 await queue . revalidate ( createMessage ( "id4" ) ) ;
8199 await queue . revalidate ( createMessage ( "id5" ) ) ;
82100 // the next one should block until one of the previous ones finishes
83101 const blockedReq = queue . revalidate ( createMessage ( "id6" ) ) ;
84-
102+
85103 expect ( queue . ongoingRevalidations . size ) . toBe ( 5 ) ;
86104 expect ( queue . ongoingRevalidations . has ( "id6" ) ) . toBe ( false ) ;
87105 expect ( Array . from ( queue . ongoingRevalidations . keys ( ) ) ) . toEqual ( [ "id" , "id2" , "id3" , "id4" , "id5" ] ) ;
88-
106+
89107 // @ts -expect-error
90108 expect ( queue . ctx . blockConcurrencyWhile ) . toHaveBeenCalledTimes ( 1 ) ;
91-
109+
92110 // Here we await the blocked request to ensure it's resolved
93111 await blockedReq ;
94112 // We then need to await for the actual revalidation to finish
95113 await Promise . all ( Array . from ( queue . ongoingRevalidations . values ( ) ) ) ;
96114 expect ( queue . ongoingRevalidations . size ) . toBe ( 0 ) ;
97115 expect ( queue . service . fetch ) . toHaveBeenCalledTimes ( 6 ) ;
98116 } ) ;
99- } )
117+ } ) ;
100118
101- describe ( ' failed revalidation' , ( ) => {
102- it ( ' should not put it in failed state for an incorrect 200' , async ( ) => {
119+ describe ( " failed revalidation" , ( ) => {
120+ it ( " should not put it in failed state for an incorrect 200" , async ( ) => {
103121 const queue = createDurableObjectQueue ( {
104122 fetchDuration : 10 ,
105123 statusCode : 200 ,
106- headers : new Headers ( [ [ "x-nextjs-cache" , "MISS" ] ] )
124+ headers : new Headers ( [ [ "x-nextjs-cache" , "MISS" ] ] ) ,
107125 } ) ;
108126 await queue . revalidate ( createMessage ( "id" ) ) ;
109127
110128 await queue . ongoingRevalidations . get ( "id" ) ;
111129
112130 expect ( queue . routeInFailedState . size ) . toBe ( 0 ) ;
113- } )
131+ } ) ;
114132
115- it ( ' should not put it in failed state for a failed revalidation with 404' , async ( ) => {
133+ it ( " should not put it in failed state for a failed revalidation with 404" , async ( ) => {
116134 const queue = createDurableObjectQueue ( {
117135 fetchDuration : 10 ,
118136 statusCode : 404 ,
@@ -130,7 +148,7 @@ describe('DurableObjectQueue', () => {
130148 expect ( queue . service . fetch ) . toHaveBeenCalledTimes ( 2 ) ;
131149 } ) ;
132150
133- it ( ' should put it in failed state if revalidation fails with 500' , async ( ) => {
151+ it ( " should put it in failed state if revalidation fails with 500" , async ( ) => {
134152 const queue = createDurableObjectQueue ( {
135153 fetchDuration : 10 ,
136154 statusCode : 500 ,
@@ -149,7 +167,7 @@ describe('DurableObjectQueue', () => {
149167 expect ( queue . service . fetch ) . toHaveBeenCalledTimes ( 1 ) ;
150168 } ) ;
151169
152- it ( ' should put it in failed state if revalidation fetch throw' , async ( ) => {
170+ it ( " should put it in failed state if revalidation fetch throw" , async ( ) => {
153171 const queue = createDurableObjectQueue ( {
154172 fetchDuration : 10 ,
155173 } ) ;
@@ -169,106 +187,128 @@ describe('DurableObjectQueue', () => {
169187 expect ( queue . routeInFailedState . size ) . toBe ( 1 ) ;
170188 expect ( queue . service . fetch ) . toHaveBeenCalledTimes ( 1 ) ;
171189 } ) ;
190+ } ) ;
172191
173- } )
174-
175- describe ( 'addAlarm' , ( ) => {
176- const getStorage = ( queue : DurableObjectQueueHandler ) : DurableObjectStorage => {
192+ describe ( "addAlarm" , ( ) => {
193+ const getStorage = ( queue : DurableObjectQueueHandler ) : DurableObjectStorage => {
177194 // @ts -expect-error - ctx is a protected field
178- return queue . ctx . storage
179- }
195+ return queue . ctx . storage ;
196+ } ;
180197
181- it ( ' should not add an alarm if there are no failed states' , async ( ) => {
182- const queue = createDurableObjectQueue ( { fetchDuration : 10 } ) ;
198+ it ( " should not add an alarm if there are no failed states" , async ( ) => {
199+ const queue = createDurableObjectQueue ( { fetchDuration : 10 } ) ;
183200 await queue . addAlarm ( ) ;
184201 expect ( getStorage ( queue ) . setAlarm ) . not . toHaveBeenCalled ( ) ;
185202 } ) ;
186203
187- it ( ' should add an alarm if there are failed states' , async ( ) => {
188- const queue = createDurableObjectQueue ( { fetchDuration : 10 } ) ;
189- queue . routeInFailedState . set ( "id" , { msg : createMessage ( "id" ) , retryCount : 0 , nextAlarm : 1000 } ) ;
204+ it ( " should add an alarm if there are failed states" , async ( ) => {
205+ const queue = createDurableObjectQueue ( { fetchDuration : 10 } ) ;
206+ queue . routeInFailedState . set ( "id" , { msg : createMessage ( "id" ) , retryCount : 0 , nextAlarm : 1000 } ) ;
190207 await queue . addAlarm ( ) ;
191208 expect ( getStorage ( queue ) . setAlarm ) . toHaveBeenCalledWith ( 1000 ) ;
192209 } ) ;
193210
194- it ( ' should not add an alarm if there is already an alarm set' , async ( ) => {
195- const queue = createDurableObjectQueue ( { fetchDuration : 10 } ) ;
196- queue . routeInFailedState . set ( "id" , { msg : createMessage ( "id" ) , retryCount : 0 , nextAlarm : 1000 } ) ;
211+ it ( " should not add an alarm if there is already an alarm set" , async ( ) => {
212+ const queue = createDurableObjectQueue ( { fetchDuration : 10 } ) ;
213+ queue . routeInFailedState . set ( "id" , { msg : createMessage ( "id" ) , retryCount : 0 , nextAlarm : 1000 } ) ;
197214 // @ts -expect-error
198215 queue . ctx . storage . getAlarm . mockResolvedValueOnce ( 1000 ) ;
199216 await queue . addAlarm ( ) ;
200217 expect ( getStorage ( queue ) . setAlarm ) . not . toHaveBeenCalled ( ) ;
201218 } ) ;
202219
203- it ( ' should set the alarm to the lowest nextAlarm' , async ( ) => {
204- const queue = createDurableObjectQueue ( { fetchDuration : 10 } ) ;
205- queue . routeInFailedState . set ( "id" , { msg : createMessage ( "id" ) , retryCount : 0 , nextAlarm : 1000 } ) ;
206- queue . routeInFailedState . set ( "id2" , { msg : createMessage ( "id2" ) , retryCount : 0 , nextAlarm : 500 } ) ;
220+ it ( " should set the alarm to the lowest nextAlarm" , async ( ) => {
221+ const queue = createDurableObjectQueue ( { fetchDuration : 10 } ) ;
222+ queue . routeInFailedState . set ( "id" , { msg : createMessage ( "id" ) , retryCount : 0 , nextAlarm : 1000 } ) ;
223+ queue . routeInFailedState . set ( "id2" , { msg : createMessage ( "id2" ) , retryCount : 0 , nextAlarm : 500 } ) ;
207224 await queue . addAlarm ( ) ;
208225 expect ( getStorage ( queue ) . setAlarm ) . toHaveBeenCalledWith ( 500 ) ;
209226 } ) ;
210227 } ) ;
211228
212-
213- describe ( 'addToFailedState' , ( ) => {
214- it ( 'should add a failed state' , async ( ) => {
215- const queue = createDurableObjectQueue ( { fetchDuration : 10 } ) ;
229+ describe ( "addToFailedState" , ( ) => {
230+ it ( "should add a failed state" , async ( ) => {
231+ const queue = createDurableObjectQueue ( { fetchDuration : 10 } ) ;
216232 await queue . addToFailedState ( createMessage ( "id" ) ) ;
217233 expect ( queue . routeInFailedState . size ) . toBe ( 1 ) ;
218234 expect ( queue . routeInFailedState . has ( "id" ) ) . toBe ( true ) ;
219235 expect ( queue . routeInFailedState . get ( "id" ) ?. retryCount ) . toBe ( 1 ) ;
220236 } ) ;
221237
222- it ( ' should add a failed state with the correct nextAlarm' , async ( ) => {
223- const queue = createDurableObjectQueue ( { fetchDuration : 10 } ) ;
238+ it ( " should add a failed state with the correct nextAlarm" , async ( ) => {
239+ const queue = createDurableObjectQueue ( { fetchDuration : 10 } ) ;
224240 await queue . addToFailedState ( createMessage ( "id" ) ) ;
225241 expect ( queue . routeInFailedState . get ( "id" ) ?. nextAlarm ) . toBeGreaterThan ( Date . now ( ) ) ;
226- expect ( queue . routeInFailedState . get ( "id" ) ?. retryCount ) . toBe ( 1 )
242+ expect ( queue . routeInFailedState . get ( "id" ) ?. retryCount ) . toBe ( 1 ) ;
227243 } ) ;
228244
229- it ( ' should add a failed state with the correct nextAlarm for a retry' , async ( ) => {
230- const queue = createDurableObjectQueue ( { fetchDuration : 10 } ) ;
245+ it ( " should add a failed state with the correct nextAlarm for a retry" , async ( ) => {
246+ const queue = createDurableObjectQueue ( { fetchDuration : 10 } ) ;
231247 await queue . addToFailedState ( createMessage ( "id" ) ) ;
232248 await queue . addToFailedState ( createMessage ( "id" ) ) ;
233249 expect ( queue . routeInFailedState . get ( "id" ) ?. nextAlarm ) . toBeGreaterThan ( Date . now ( ) ) ;
234- expect ( queue . routeInFailedState . get ( "id" ) ?. retryCount ) . toBe ( 2 )
250+ expect ( queue . routeInFailedState . get ( "id" ) ?. retryCount ) . toBe ( 2 ) ;
235251 } ) ;
236252
237- it ( ' should not add a failed state if it has been retried 6 times' , async ( ) => {
238- const queue = createDurableObjectQueue ( { fetchDuration : 10 } ) ;
239- queue . routeInFailedState . set ( "id" , { msg : createMessage ( "id" ) , retryCount : 6 , nextAlarm : 1000 } ) ;
253+ it ( " should not add a failed state if it has been retried 6 times" , async ( ) => {
254+ const queue = createDurableObjectQueue ( { fetchDuration : 10 } ) ;
255+ queue . routeInFailedState . set ( "id" , { msg : createMessage ( "id" ) , retryCount : 6 , nextAlarm : 1000 } ) ;
240256 await queue . addToFailedState ( createMessage ( "id" ) ) ;
241257 expect ( queue . routeInFailedState . size ) . toBe ( 0 ) ;
242- } )
243- } )
244-
245- describe ( 'alarm' , ( ) => {
246- it ( 'should execute revalidations for expired events' , async ( ) => {
247- const queue = createDurableObjectQueue ( { fetchDuration : 10 } ) ;
248- queue . routeInFailedState . set ( "id" , { msg : createMessage ( "id" ) , retryCount : 0 , nextAlarm : Date . now ( ) - 1000 } ) ;
249- queue . routeInFailedState . set ( "id2" , { msg : createMessage ( "id2" ) , retryCount : 0 , nextAlarm : Date . now ( ) - 1000 } ) ;
258+ } ) ;
259+ } ) ;
260+
261+ describe ( "alarm" , ( ) => {
262+ it ( "should execute revalidations for expired events" , async ( ) => {
263+ const queue = createDurableObjectQueue ( { fetchDuration : 10 } ) ;
264+ queue . routeInFailedState . set ( "id" , {
265+ msg : createMessage ( "id" ) ,
266+ retryCount : 0 ,
267+ nextAlarm : Date . now ( ) - 1000 ,
268+ } ) ;
269+ queue . routeInFailedState . set ( "id2" , {
270+ msg : createMessage ( "id2" ) ,
271+ retryCount : 0 ,
272+ nextAlarm : Date . now ( ) - 1000 ,
273+ } ) ;
250274 await queue . alarm ( ) ;
251275 expect ( queue . routeInFailedState . size ) . toBe ( 0 ) ;
252276 expect ( queue . service . fetch ) . toHaveBeenCalledTimes ( 2 ) ;
253277 } ) ;
254278
255- it ( 'should execute revalidations for the next event to retry' , async ( ) => {
256- const queue = createDurableObjectQueue ( { fetchDuration : 10 } ) ;
257- queue . routeInFailedState . set ( "id" , { msg : createMessage ( "id" ) , retryCount : 0 , nextAlarm : Date . now ( ) + 1000 } ) ;
258- queue . routeInFailedState . set ( "id2" , { msg : createMessage ( "id2" ) , retryCount : 0 , nextAlarm : Date . now ( ) + 500 } ) ;
279+ it ( "should execute revalidations for the next event to retry" , async ( ) => {
280+ const queue = createDurableObjectQueue ( { fetchDuration : 10 } ) ;
281+ queue . routeInFailedState . set ( "id" , {
282+ msg : createMessage ( "id" ) ,
283+ retryCount : 0 ,
284+ nextAlarm : Date . now ( ) + 1000 ,
285+ } ) ;
286+ queue . routeInFailedState . set ( "id2" , {
287+ msg : createMessage ( "id2" ) ,
288+ retryCount : 0 ,
289+ nextAlarm : Date . now ( ) + 500 ,
290+ } ) ;
259291 await queue . alarm ( ) ;
260292 expect ( queue . routeInFailedState . size ) . toBe ( 1 ) ;
261293 expect ( queue . service . fetch ) . toHaveBeenCalledTimes ( 1 ) ;
262294 expect ( queue . routeInFailedState . has ( "id2" ) ) . toBe ( false ) ;
263295 } ) ;
264296
265- it ( 'should execute revalidations for the next event to retry and expired events' , async ( ) => {
266- const queue = createDurableObjectQueue ( { fetchDuration : 10 } ) ;
267- queue . routeInFailedState . set ( "id" , { msg : createMessage ( "id" ) , retryCount : 0 , nextAlarm : Date . now ( ) + 1000 } ) ;
268- queue . routeInFailedState . set ( "id2" , { msg : createMessage ( "id2" ) , retryCount : 0 , nextAlarm : Date . now ( ) - 1000 } ) ;
297+ it ( "should execute revalidations for the next event to retry and expired events" , async ( ) => {
298+ const queue = createDurableObjectQueue ( { fetchDuration : 10 } ) ;
299+ queue . routeInFailedState . set ( "id" , {
300+ msg : createMessage ( "id" ) ,
301+ retryCount : 0 ,
302+ nextAlarm : Date . now ( ) + 1000 ,
303+ } ) ;
304+ queue . routeInFailedState . set ( "id2" , {
305+ msg : createMessage ( "id2" ) ,
306+ retryCount : 0 ,
307+ nextAlarm : Date . now ( ) - 1000 ,
308+ } ) ;
269309 await queue . alarm ( ) ;
270310 expect ( queue . routeInFailedState . size ) . toBe ( 0 ) ;
271311 expect ( queue . service . fetch ) . toHaveBeenCalledTimes ( 2 ) ;
272312 } ) ;
273- } )
274- } )
313+ } ) ;
314+ } ) ;
0 commit comments