22 * @vitest -environment node
33 */
44import { asyncJobs , tableJobs , workflowExecutionLogs } from '@sim/db/schema'
5+ import { createLogger } from '@sim/logger'
56import { createMockRequest , dbChainMockFns , queueTableRows , resetDbChainMock } from '@sim/testing'
67import { beforeEach , describe , expect , it , vi } from 'vitest'
78import { MAX_JOB_DURATION_SECONDS , MIN_JOB_DURATION_SECONDS } from '@/lib/core/async-jobs'
@@ -16,6 +17,11 @@ vi.mock('@/lib/uploads/core/storage-service', () => ({ deleteFile: mockDeleteFil
1617
1718import { GET } from '@/app/api/cron/cleanup-stale-executions/route'
1819
20+ const cleanupLogger =
21+ vi . mocked ( createLogger ) . mock . results [
22+ vi . mocked ( createLogger ) . mock . calls . findIndex ( ( [ name ] ) => name === 'CleanupStaleExecutions' )
23+ ] . value
24+
1925interface MockCondition {
2026 type ?: string
2127 conditions ?: unknown [ ]
@@ -43,6 +49,7 @@ describe('stale execution cleanup deadline grace', () => {
4349 beforeEach ( ( ) => {
4450 vi . clearAllMocks ( )
4551 resetDbChainMock ( )
52+ cleanupLogger . info . mockReset ( )
4653 mockVerifyCronAuth . mockReturnValue ( null )
4754 } )
4855
@@ -331,6 +338,34 @@ describe('stale execution cleanup deadline grace', () => {
331338 expect ( dbChainMockFns . update . mock . calls . some ( ( [ table ] ) => table === asyncJobs ) ) . toBe ( true )
332339 } )
333340
341+ it ( 'does not mark a committed workflow batch as failed when later bookkeeping throws' , async ( ) => {
342+ for ( let batch = 0 ; batch < 10 ; batch ++ ) {
343+ const workflowBatch = Array . from ( { length : 100 } , ( _ , index ) => ( {
344+ id : `execution-${ batch } -${ index } ` ,
345+ } ) )
346+ queueTableRows ( workflowExecutionLogs , workflowBatch )
347+ dbChainMockFns . returning . mockResolvedValueOnce ( workflowBatch )
348+ }
349+ cleanupLogger . info . mockImplementation ( ( message : string ) => {
350+ if (
351+ message === 'Deferred remaining stale workflow executions after reaching the per-run cap'
352+ ) {
353+ throw new Error ( 'logger unavailable' )
354+ }
355+ } )
356+
357+ const response = await GET ( createRequest ( ) )
358+
359+ expect ( response . status ) . toBe ( 200 )
360+ await expect ( response . json ( ) ) . resolves . toMatchObject ( {
361+ executions : {
362+ found : 1000 ,
363+ cleaned : 1000 ,
364+ failed : 0 ,
365+ } ,
366+ } )
367+ } )
368+
334369 it ( 'continues draining when an atomic race updates fewer rows than were selected' , async ( ) => {
335370 const firstCandidates = Array . from ( { length : 100 } , ( _ , index ) => ( {
336371 id : `execution-${ index } ` ,
0 commit comments