@@ -4,6 +4,7 @@ import { describe } from "node:test";
4
4
import { expect } from "vitest" ;
5
5
import { z } from "zod" ;
6
6
import { Worker } from "./worker.js" ;
7
+ import Redis from "ioredis" ;
7
8
8
9
describe ( "Worker" , ( ) => {
9
10
redisTest ( "Process items that don't throw" , { timeout : 30_000 } , async ( { redisContainer } ) => {
@@ -191,6 +192,89 @@ describe("Worker", () => {
191
192
}
192
193
) ;
193
194
195
+ redisTest (
196
+ "Redrive an item from DLQ and process it successfully" ,
197
+ { timeout : 30_000 } ,
198
+ async ( { redisContainer } ) => {
199
+ const processedItems : number [ ] = [ ] ;
200
+ const failedItemId = "fail-then-redrive-item" ;
201
+ let attemptCount = 0 ;
202
+
203
+ const worker = new Worker ( {
204
+ name : "test-worker" ,
205
+ redisOptions : {
206
+ host : redisContainer . getHost ( ) ,
207
+ port : redisContainer . getPort ( ) ,
208
+ password : redisContainer . getPassword ( ) ,
209
+ } ,
210
+ catalog : {
211
+ testJob : {
212
+ schema : z . object ( { value : z . number ( ) } ) ,
213
+ visibilityTimeoutMs : 1000 ,
214
+ retry : { maxAttempts : 3 , minTimeoutInMs : 10 , maxTimeoutInMs : 50 } ,
215
+ } ,
216
+ } ,
217
+ jobs : {
218
+ testJob : async ( { id, payload } ) => {
219
+ if ( id === failedItemId && attemptCount < 3 ) {
220
+ attemptCount ++ ;
221
+ throw new Error ( "Temporary failure" ) ;
222
+ }
223
+ processedItems . push ( payload . value ) ;
224
+ } ,
225
+ } ,
226
+ concurrency : {
227
+ workers : 1 ,
228
+ tasksPerWorker : 1 ,
229
+ } ,
230
+ pollIntervalMs : 50 ,
231
+ logger : new Logger ( "test" , "error" ) ,
232
+ } ) ;
233
+
234
+ try {
235
+ // Enqueue the item that will fail 3 times
236
+ await worker . enqueue ( {
237
+ id : failedItemId ,
238
+ job : "testJob" ,
239
+ payload : { value : 999 } ,
240
+ } ) ;
241
+
242
+ worker . start ( ) ;
243
+
244
+ // Wait for the item to be processed and moved to DLQ
245
+ await new Promise ( ( resolve ) => setTimeout ( resolve , 1000 ) ) ;
246
+
247
+ // Check that the item is in the DLQ
248
+ let dlqSize = await worker . queue . sizeOfDeadLetterQueue ( ) ;
249
+ expect ( dlqSize ) . toBe ( 1 ) ;
250
+
251
+ // Create a Redis client to publish the redrive message
252
+ const redisClient = new Redis ( {
253
+ host : redisContainer . getHost ( ) ,
254
+ port : redisContainer . getPort ( ) ,
255
+ password : redisContainer . getPassword ( ) ,
256
+ } ) ;
257
+
258
+ // Publish redrive message
259
+ await redisClient . publish ( "test-worker:redrive" , JSON . stringify ( { id : failedItemId } ) ) ;
260
+
261
+ // Wait for the item to be redrived and processed
262
+ await new Promise ( ( resolve ) => setTimeout ( resolve , 1000 ) ) ;
263
+
264
+ // Check that the item was processed successfully
265
+ expect ( processedItems ) . toEqual ( [ 999 ] ) ;
266
+
267
+ // Check that the DLQ is now empty
268
+ dlqSize = await worker . queue . sizeOfDeadLetterQueue ( ) ;
269
+ expect ( dlqSize ) . toBe ( 0 ) ;
270
+
271
+ await redisClient . quit ( ) ;
272
+ } finally {
273
+ worker . stop ( ) ;
274
+ }
275
+ }
276
+ ) ;
277
+
194
278
//todo test that throwing an error doesn't screw up the other items
195
279
//todo process more items when finished
196
280
0 commit comments