@@ -668,4 +668,238 @@ describe('Executor', () => {
668668 expect ( createContextSpy ) . toHaveBeenCalled ( )
669669 } )
670670 } )
671+
672+ /**
673+ * Dependency checking logic tests
674+ */
675+ describe ( 'dependency checking' , ( ) => {
676+ test ( 'should handle multi-input blocks with inactive sources correctly' , ( ) => {
677+ // Create workflow with router -> multiple APIs -> single agent
678+ const routerWorkflow = {
679+ blocks : [
680+ {
681+ id : 'start' ,
682+ metadata : { id : 'starter' , name : 'Start' } ,
683+ config : { params : { } } ,
684+ enabled : true ,
685+ } ,
686+ {
687+ id : 'router' ,
688+ metadata : { id : 'router' , name : 'Router' } ,
689+ config : { params : { prompt : 'test' , model : 'gpt-4' } } ,
690+ enabled : true ,
691+ } ,
692+ {
693+ id : 'api1' ,
694+ metadata : { id : 'api' , name : 'API 1' } ,
695+ config : { params : { url : 'http://api1.com' , method : 'GET' } } ,
696+ enabled : true ,
697+ } ,
698+ {
699+ id : 'api2' ,
700+ metadata : { id : 'api' , name : 'API 2' } ,
701+ config : { params : { url : 'http://api2.com' , method : 'GET' } } ,
702+ enabled : true ,
703+ } ,
704+ {
705+ id : 'agent' ,
706+ metadata : { id : 'agent' , name : 'Agent' } ,
707+ config : { params : { model : 'gpt-4' , userPrompt : 'test' } } ,
708+ enabled : true ,
709+ } ,
710+ ] ,
711+ connections : [
712+ { source : 'start' , target : 'router' } ,
713+ { source : 'router' , target : 'api1' } ,
714+ { source : 'router' , target : 'api2' } ,
715+ { source : 'api1' , target : 'agent' } ,
716+ { source : 'api2' , target : 'agent' } ,
717+ ] ,
718+ loops : { } ,
719+ parallels : { } ,
720+ }
721+
722+ const executor = new Executor ( routerWorkflow )
723+ const checkDependencies = ( executor as any ) . checkDependencies . bind ( executor )
724+
725+ // Mock context simulating: router selected api1, api1 executed, api2 not in active path
726+ const mockContext = {
727+ blockStates : new Map ( ) ,
728+ decisions : {
729+ router : new Map ( [ [ 'router' , 'api1' ] ] ) ,
730+ condition : new Map ( ) ,
731+ } ,
732+ activeExecutionPath : new Set ( [ 'start' , 'router' , 'api1' , 'agent' ] ) ,
733+ workflow : routerWorkflow ,
734+ } as any
735+
736+ const executedBlocks = new Set ( [ 'start' , 'router' , 'api1' ] )
737+
738+ // Test agent's dependencies
739+ const agentConnections = [
740+ { source : 'api1' , target : 'agent' , sourceHandle : 'source' } ,
741+ { source : 'api2' , target : 'agent' , sourceHandle : 'source' } ,
742+ ]
743+
744+ const dependenciesMet = checkDependencies ( agentConnections , executedBlocks , mockContext )
745+
746+ // Both dependencies should be met:
747+ // - api1: in active path AND executed = met
748+ // - api2: NOT in active path = automatically met
749+ expect ( dependenciesMet ) . toBe ( true )
750+ } )
751+
752+ test ( 'should prioritize special connection types over active path check' , ( ) => {
753+ const workflow = createMinimalWorkflow ( )
754+ const executor = new Executor ( workflow )
755+ const checkDependencies = ( executor as any ) . checkDependencies . bind ( executor )
756+
757+ const mockContext = {
758+ blockStates : new Map ( ) ,
759+ decisions : { router : new Map ( ) , condition : new Map ( ) } ,
760+ activeExecutionPath : new Set ( [ 'block1' ] ) , // block2 not in active path
761+ completedLoops : new Set ( ) ,
762+ workflow : workflow ,
763+ } as any
764+
765+ const executedBlocks = new Set ( [ 'block1' ] )
766+
767+ // Test error connection (should be handled before active path check)
768+ const errorConnections = [ { source : 'block2' , target : 'block3' , sourceHandle : 'error' } ]
769+
770+ // Mock block2 with error state
771+ mockContext . blockStates . set ( 'block2' , {
772+ output : { error : 'test error' } ,
773+ } )
774+
775+ // Even though block2 is not in active path, error connection should be handled specially
776+ const errorDepsResult = checkDependencies ( errorConnections , new Set ( [ 'block2' ] ) , mockContext )
777+ expect ( errorDepsResult ) . toBe ( true ) // source executed + has error = dependency met
778+
779+ // Test loop connection
780+ const loopConnections = [
781+ { source : 'block2' , target : 'block3' , sourceHandle : 'loop-end-source' } ,
782+ ]
783+
784+ mockContext . completedLoops . add ( 'block2' )
785+ const loopDepsResult = checkDependencies ( loopConnections , new Set ( [ 'block2' ] ) , mockContext )
786+ expect ( loopDepsResult ) . toBe ( true ) // loop completed = dependency met
787+ } )
788+
789+ test ( 'should handle router decisions correctly in dependency checking' , ( ) => {
790+ const workflow = createMinimalWorkflow ( )
791+ const executor = new Executor ( workflow )
792+ const checkDependencies = ( executor as any ) . checkDependencies . bind ( executor )
793+
794+ // Add router block to workflow
795+ workflow . blocks . push ( {
796+ id : 'router1' ,
797+ metadata : { id : 'router' , name : 'Router' } ,
798+ config : { params : { } } ,
799+ enabled : true ,
800+ } )
801+
802+ const mockContext = {
803+ blockStates : new Map ( ) ,
804+ decisions : {
805+ router : new Map ( [ [ 'router1' , 'target1' ] ] ) , // router selected target1
806+ condition : new Map ( ) ,
807+ } ,
808+ activeExecutionPath : new Set ( [ 'router1' , 'target1' , 'target2' ] ) ,
809+ workflow : workflow ,
810+ } as any
811+
812+ const executedBlocks = new Set ( [ 'router1' ] )
813+
814+ // Test selected target
815+ const selectedConnections = [ { source : 'router1' , target : 'target1' , sourceHandle : 'source' } ]
816+ const selectedResult = checkDependencies ( selectedConnections , executedBlocks , mockContext )
817+ expect ( selectedResult ) . toBe ( true ) // router executed + target selected = dependency met
818+
819+ // Test non-selected target
820+ const nonSelectedConnections = [
821+ { source : 'router1' , target : 'target2' , sourceHandle : 'source' } ,
822+ ]
823+ const nonSelectedResult = checkDependencies (
824+ nonSelectedConnections ,
825+ executedBlocks ,
826+ mockContext
827+ )
828+ expect ( nonSelectedResult ) . toBe ( true ) // router executed + target NOT selected = dependency auto-met
829+ } )
830+
831+ test ( 'should handle condition decisions correctly in dependency checking' , ( ) => {
832+ const conditionWorkflow = createWorkflowWithCondition ( )
833+ const executor = new Executor ( conditionWorkflow )
834+ const checkDependencies = ( executor as any ) . checkDependencies . bind ( executor )
835+
836+ const mockContext = {
837+ blockStates : new Map ( ) ,
838+ decisions : {
839+ router : new Map ( ) ,
840+ condition : new Map ( [ [ 'condition1' , 'true' ] ] ) , // condition selected true path
841+ } ,
842+ activeExecutionPath : new Set ( [ 'condition1' , 'trueTarget' ] ) ,
843+ workflow : conditionWorkflow ,
844+ } as any
845+
846+ const executedBlocks = new Set ( [ 'condition1' ] )
847+
848+ // Test selected condition path
849+ const trueConnections = [
850+ { source : 'condition1' , target : 'trueTarget' , sourceHandle : 'condition-true' } ,
851+ ]
852+ const trueResult = checkDependencies ( trueConnections , executedBlocks , mockContext )
853+ expect ( trueResult ) . toBe ( true )
854+
855+ // Test non-selected condition path
856+ const falseConnections = [
857+ { source : 'condition1' , target : 'falseTarget' , sourceHandle : 'condition-false' } ,
858+ ]
859+ const falseResult = checkDependencies ( falseConnections , executedBlocks , mockContext )
860+ expect ( falseResult ) . toBe ( true ) // condition executed + path NOT selected = dependency auto-met
861+ } )
862+
863+ test ( 'should handle regular sequential dependencies correctly' , ( ) => {
864+ const workflow = createMinimalWorkflow ( )
865+ const executor = new Executor ( workflow )
866+ const checkDependencies = ( executor as any ) . checkDependencies . bind ( executor )
867+
868+ const mockContext = {
869+ blockStates : new Map ( ) ,
870+ decisions : { router : new Map ( ) , condition : new Map ( ) } ,
871+ activeExecutionPath : new Set ( [ 'block1' , 'block2' ] ) ,
872+ workflow : workflow ,
873+ } as any
874+
875+ const executedBlocks = new Set ( [ 'block1' ] )
876+
877+ // Test normal sequential dependency
878+ const normalConnections = [ { source : 'block1' , target : 'block2' , sourceHandle : 'source' } ]
879+
880+ // Without error
881+ const normalResult = checkDependencies ( normalConnections , executedBlocks , mockContext )
882+ expect ( normalResult ) . toBe ( true ) // source executed + no error = dependency met
883+
884+ // With error should fail regular connection
885+ mockContext . blockStates . set ( 'block1' , {
886+ output : { error : 'test error' } ,
887+ } )
888+ const errorResult = checkDependencies ( normalConnections , executedBlocks , mockContext )
889+ expect ( errorResult ) . toBe ( false ) // source executed + has error = regular dependency not met
890+ } )
891+
892+ test ( 'should handle empty dependency list' , ( ) => {
893+ const workflow = createMinimalWorkflow ( )
894+ const executor = new Executor ( workflow )
895+ const checkDependencies = ( executor as any ) . checkDependencies . bind ( executor )
896+
897+ const mockContext = createMockContext ( )
898+ const executedBlocks = new Set < string > ( )
899+
900+ // Empty connections should return true
901+ const result = checkDependencies ( [ ] , executedBlocks , mockContext )
902+ expect ( result ) . toBe ( true )
903+ } )
904+ } )
671905} )
0 commit comments