@@ -15,6 +15,7 @@ const mocks = vi.hoisted(() => ({
1515 isAsyncJobEnqueueError : vi . fn ( ) ,
1616 markFailed : vi . fn ( ) ,
1717 markPushed : vi . fn ( ) ,
18+ queueCancel : vi . fn ( ) ,
1819 queueEnqueue : vi . fn ( ) ,
1920 queueGetJob : vi . fn ( ) ,
2021 requireAttempt : vi . fn ( ) ,
@@ -31,6 +32,8 @@ vi.mock('@/lib/core/async-jobs', () => ({
3132 JOB_STATUS : {
3233 COMPLETED : 'completed' ,
3334 FAILED : 'failed' ,
35+ PENDING : 'pending' ,
36+ PROCESSING : 'processing' ,
3437 } ,
3538} ) )
3639
@@ -70,6 +73,7 @@ describe('newsletter Resend queueing', () => {
7073 vi . clearAllMocks ( )
7174 mocks . getAsyncBackendType . mockReturnValue ( 'trigger-dev' )
7275 mocks . getJobQueue . mockResolvedValue ( {
76+ cancelJob : mocks . queueCancel ,
7377 enqueue : mocks . queueEnqueue ,
7478 getJob : mocks . queueGetJob ,
7579 } )
@@ -142,23 +146,94 @@ describe('newsletter Resend queueing', () => {
142146 )
143147 } )
144148
145- it ( 'moves a newsletter run to failed when its persisted database job failed' , async ( ) => {
146- mocks . getAsyncBackendType . mockReturnValue ( 'database' )
147- mocks . claimAttempt . mockResolvedValue ( {
148- attempt : 2 ,
149- jobId : 'newsletter_resend_run-1_2' ,
150- run,
151- shouldEnqueue : false ,
152- } )
149+ it ( 'starts a new attempt when a persisted Trigger.dev job failed' , async ( ) => {
150+ mocks . claimAttempt
151+ . mockResolvedValueOnce ( {
152+ attempt : 2 ,
153+ jobId : 'trigger-run-failed' ,
154+ run,
155+ shouldEnqueue : false ,
156+ } )
157+ . mockResolvedValueOnce ( {
158+ attempt : 3 ,
159+ jobId : null ,
160+ run : { ...run , resendSyncJobId : null } ,
161+ shouldEnqueue : true ,
162+ } )
153163 mocks . queueGetJob . mockResolvedValue ( {
154- id : 'newsletter_resend_run-1_2 ' ,
164+ id : 'trigger-run-failed ' ,
155165 status : 'failed' ,
156166 error : 'worker stopped' ,
157167 } )
168+ mocks . queueEnqueue . mockResolvedValue ( 'trigger-run-retry' )
169+ mocks . setJob . mockResolvedValue ( { ...run , resendSyncJobId : 'trigger-run-retry' } )
170+
171+ const result = await enqueueNewsletterResendSync ( 'run-1' , 'admin-1' )
158172
159- await expect ( enqueueNewsletterResendSync ( 'run-1' , 'admin-1' ) ) . rejects . toThrow ( 'worker stopped' )
160173 expect ( mocks . markFailed ) . toHaveBeenCalledWith ( 'run-1' , 2 , expect . any ( Error ) )
161- expect ( mocks . queueEnqueue ) . not . toHaveBeenCalled ( )
174+ expect ( mocks . queueEnqueue ) . toHaveBeenCalledWith (
175+ 'newsletter-resend-sync' ,
176+ { runId : 'run-1' , attempt : 3 , requestedById : 'admin-1' } ,
177+ expect . objectContaining ( { jobId : 'newsletter_resend_run-1_3' } )
178+ )
179+ expect ( result . jobId ) . toBe ( 'trigger-run-retry' )
180+ } )
181+
182+ it ( 're-enqueues when a stored Trigger.dev run no longer exists' , async ( ) => {
183+ mocks . claimAttempt
184+ . mockResolvedValueOnce ( {
185+ attempt : 2 ,
186+ jobId : 'trigger-run-missing' ,
187+ run,
188+ shouldEnqueue : false ,
189+ } )
190+ . mockResolvedValueOnce ( {
191+ attempt : 3 ,
192+ jobId : null ,
193+ run,
194+ shouldEnqueue : true ,
195+ } )
196+ mocks . queueGetJob . mockResolvedValue ( null )
197+
198+ await enqueueNewsletterResendSync ( 'run-1' , 'admin-1' )
199+
200+ expect ( mocks . markFailed ) . toHaveBeenCalledWith ( 'run-1' , 2 , expect . any ( Error ) )
201+ expect ( mocks . queueEnqueue ) . toHaveBeenCalledWith (
202+ 'newsletter-resend-sync' ,
203+ { runId : 'run-1' , attempt : 3 , requestedById : 'admin-1' } ,
204+ expect . objectContaining ( { jobId : 'newsletter_resend_run-1_3' } )
205+ )
206+ } )
207+
208+ it ( 'cancels and replaces an active Trigger.dev run when an admin resumes it' , async ( ) => {
209+ mocks . claimAttempt
210+ . mockResolvedValueOnce ( {
211+ attempt : 2 ,
212+ jobId : 'trigger-run-active' ,
213+ run,
214+ shouldEnqueue : false ,
215+ } )
216+ . mockResolvedValueOnce ( {
217+ attempt : 3 ,
218+ jobId : null ,
219+ run,
220+ shouldEnqueue : true ,
221+ } )
222+ mocks . queueGetJob . mockResolvedValue ( {
223+ id : 'trigger-run-active' ,
224+ status : 'processing' ,
225+ } )
226+
227+ const result = await enqueueNewsletterResendSync ( 'run-1' , 'admin-1' )
228+
229+ expect ( mocks . queueCancel ) . toHaveBeenCalledWith ( 'trigger-run-active' )
230+ expect ( mocks . markFailed ) . toHaveBeenCalledWith ( 'run-1' , 2 , expect . any ( Error ) )
231+ expect ( mocks . queueEnqueue ) . toHaveBeenCalledWith (
232+ 'newsletter-resend-sync' ,
233+ { runId : 'run-1' , attempt : 3 , requestedById : 'admin-1' } ,
234+ expect . objectContaining ( { jobId : 'newsletter_resend_run-1_3' } )
235+ )
236+ expect ( result . jobId ) . toBe ( 'trigger-run-123' )
162237 } )
163238
164239 it ( 'resets failed recipients before a task retry' , async ( ) => {
0 commit comments