@@ -9,6 +9,7 @@ vi.mock('@/lib/execution/cancellation', () => ({
99} ) )
1010
1111import { isExecutionCancelled , isRedisCancellationEnabled } from '@/lib/execution/cancellation'
12+ import { EDGE } from '@/executor/constants'
1213import type { DAG , DAGNode } from '@/executor/dag/builder'
1314import type { EdgeManager } from '@/executor/execution/edge-manager'
1415import type { NodeExecutionOrchestrator } from '@/executor/orchestrators/node'
@@ -89,6 +90,9 @@ function createMockEdgeManager(
8990 restoreIncomingEdge : vi . fn ( ) ,
9091 clearDeactivatedEdges : vi . fn ( ) ,
9192 clearDeactivatedEdgesForNodes : vi . fn ( ) ,
93+ getDeactivatedEdges : vi . fn ( ( ) => [ ] ) ,
94+ getNodesWithActivatedEdge : vi . fn ( ( ) => [ ] ) ,
95+ markNodeWithActivatedEdge : vi . fn ( ) ,
9296 } as unknown as MockEdgeManager
9397}
9498
@@ -130,7 +134,9 @@ describe('ExecutionEngine', () => {
130134 endNode . incomingEdges . add ( 'start' )
131135
132136 const dag = createMockDAG ( [ startNode , endNode ] )
133- const context = createMockContext ( )
137+ const context = createMockContext ( {
138+ decisions : { router : new Map ( ) , condition : new Map ( ) } ,
139+ } )
134140 const edgeManager = createMockEdgeManager ( ( node ) => {
135141 if ( node . id === 'start' ) return [ 'end' ]
136142 return [ ]
@@ -147,7 +153,9 @@ describe('ExecutionEngine', () => {
147153 it ( 'should mark execution as successful when completed without cancellation' , async ( ) => {
148154 const startNode = createMockNode ( 'start' , 'starter' )
149155 const dag = createMockDAG ( [ startNode ] )
150- const context = createMockContext ( )
156+ const context = createMockContext ( {
157+ decisions : { router : new Map ( ) , condition : new Map ( ) } ,
158+ } )
151159 const edgeManager = createMockEdgeManager ( )
152160 const nodeOrchestrator = createMockNodeOrchestrator ( )
153161
@@ -173,12 +181,36 @@ describe('ExecutionEngine', () => {
173181 const nodeOrchestrator = createMockNodeOrchestrator ( )
174182
175183 const engine = new ExecutionEngine ( context , dag , edgeManager , nodeOrchestrator )
176- const result = await engine . run ( )
184+ const result = await engine . run ( 'hitl' )
177185
178186 expect ( result . success ) . toBe ( true )
179187 expect ( nodeOrchestrator . executionCount ) . toBe ( 0 )
180188 } )
181189
190+ it ( 'marks resumed pause edge targets as activated before readiness checks' , async ( ) => {
191+ const targetNode = createMockNode ( 'join' , 'function' )
192+ targetNode . incomingEdges . add ( 'pause-block' )
193+ targetNode . incomingEdges . add ( 'condition-block' )
194+ const dag = createMockDAG ( [ targetNode ] )
195+ const context = createMockContext ( {
196+ metadata : {
197+ executionId : 'test-execution' ,
198+ startTime : new Date ( ) . toISOString ( ) ,
199+ pendingBlocks : [ ] ,
200+ remainingEdges : [ { source : 'pause-block' , target : 'join' } ] ,
201+ } as any ,
202+ } )
203+ const edgeManager = createMockEdgeManager ( ( ) => [ ] )
204+ vi . mocked ( edgeManager . isNodeReady ) . mockReturnValue ( false )
205+ const nodeOrchestrator = createMockNodeOrchestrator ( )
206+
207+ const engine = new ExecutionEngine ( context , dag , edgeManager , nodeOrchestrator )
208+ await engine . run ( )
209+
210+ expect ( edgeManager . markNodeWithActivatedEdge ) . toHaveBeenCalledWith ( 'join' )
211+ expect ( nodeOrchestrator . executeNode ) . not . toHaveBeenCalled ( )
212+ } )
213+
182214 it ( 'should execute all nodes in a multi-node workflow' , async ( ) => {
183215 const nodes = [
184216 createMockNode ( 'start' , 'starter' ) ,
@@ -207,6 +239,73 @@ describe('ExecutionEngine', () => {
207239 expect ( result . success ) . toBe ( true )
208240 expect ( nodeOrchestrator . executionCount ) . toBe ( 4 )
209241 } )
242+
243+ it ( 'records paused block completion before returning paused result' , async ( ) => {
244+ const node = createMockNode ( 'hitl' , 'function' )
245+ const dag = createMockDAG ( [ node ] )
246+ const context = createMockContext ( {
247+ decisions : { router : new Map ( ) , condition : new Map ( ) } ,
248+ } )
249+ const edgeManager = createMockEdgeManager ( )
250+ const nodeOrchestrator = createMockNodeOrchestrator ( )
251+ const pauseOutput = {
252+ response : { status : 'paused' } ,
253+ _pauseMetadata : {
254+ contextId : 'pause-1' ,
255+ blockId : 'hitl' ,
256+ response : { status : 'paused' } ,
257+ timestamp : new Date ( ) . toISOString ( ) ,
258+ pauseKind : 'hitl' ,
259+ } ,
260+ }
261+ vi . mocked ( nodeOrchestrator . executeNode ) . mockResolvedValue ( {
262+ nodeId : 'hitl' ,
263+ output : pauseOutput ,
264+ isFinalOutput : false ,
265+ } )
266+
267+ const engine = new ExecutionEngine ( context , dag , edgeManager , nodeOrchestrator )
268+ const result = await engine . run ( 'hitl' )
269+
270+ expect ( result . status ) . toBe ( 'paused' )
271+ expect ( nodeOrchestrator . handleNodeCompletion ) . toHaveBeenCalledWith (
272+ context ,
273+ 'hitl' ,
274+ pauseOutput
275+ )
276+ } )
277+
278+ it ( 'does not stop run-until execution on parallel batch continuation' , async ( ) => {
279+ const parallelEnd = createMockNode ( 'parallel-end' , 'parallel' )
280+ const nextNode = createMockNode ( 'next' , 'function' )
281+ parallelEnd . outgoingEdges . set ( 'continue' , {
282+ target : 'next' ,
283+ sourceHandle : EDGE . PARALLEL_CONTINUE ,
284+ } )
285+ nextNode . incomingEdges . add ( 'parallel-end' )
286+ const dag = createMockDAG ( [ parallelEnd , nextNode ] )
287+ const context = createMockContext ( {
288+ stopAfterBlockId : 'parallel-end' ,
289+ decisions : { router : new Map ( ) , condition : new Map ( ) } ,
290+ } )
291+ const edgeManager = createMockEdgeManager ( ( node ) =>
292+ node . id === 'parallel-end' ? [ 'next' ] : [ ]
293+ )
294+ const nodeOrchestrator = createMockNodeOrchestrator ( )
295+ vi . mocked ( nodeOrchestrator . executeNode ) . mockImplementation ( async ( _ctx , nodeId ) => ( {
296+ nodeId,
297+ output :
298+ nodeId === 'parallel-end'
299+ ? { selectedRoute : EDGE . PARALLEL_CONTINUE }
300+ : { result : 'done' } ,
301+ isFinalOutput : nodeId === 'next' ,
302+ } ) )
303+
304+ const engine = new ExecutionEngine ( context , dag , edgeManager , nodeOrchestrator )
305+ await engine . run ( 'parallel-end' )
306+
307+ expect ( nodeOrchestrator . executeNode ) . toHaveBeenCalledWith ( context , 'next' )
308+ } )
210309 } )
211310
212311 describe ( 'Cancellation via AbortSignal' , ( ) => {
0 commit comments