@@ -736,3 +736,156 @@ func TestExecutePipeline_UnknownPipeline(t *testing.T) {
736736 t .Errorf ("expected error to mention pipeline name, got: %v" , err )
737737 }
738738}
739+
740+ // TestTriggerWorkflow_PreservesStepOutputs verifies that injecting a
741+ // PipelineContextHolder into the context before calling TriggerWorkflow causes
742+ // StepOutputs to be populated after the pipeline runs.
743+ func TestTriggerWorkflow_PreservesStepOutputs (t * testing.T ) {
744+ engine , _ := setupPipelineEngine (t )
745+
746+ pipelineCfg := map [string ]any {
747+ "step_output_pipeline" : map [string ]any {
748+ "steps" : []any {
749+ map [string ]any {
750+ "name" : "set_value" ,
751+ "type" : "step.set" ,
752+ "config" : map [string ]any {
753+ "values" : map [string ]any {
754+ "color" : "blue" ,
755+ },
756+ },
757+ },
758+ },
759+ },
760+ }
761+ if err := engine .configurePipelines (pipelineCfg ); err != nil {
762+ t .Fatalf ("configurePipelines failed: %v" , err )
763+ }
764+
765+ holder := & module.PipelineContextHolder {}
766+ ctx := context .WithValue (context .Background (), module .PipelineContextKey , holder )
767+
768+ if err := engine .TriggerWorkflow (ctx , "pipeline:step_output_pipeline" , "" , map [string ]any {}); err != nil {
769+ t .Fatalf ("TriggerWorkflow failed: %v" , err )
770+ }
771+
772+ pc := holder .Get ()
773+ if pc == nil {
774+ t .Fatal ("expected PipelineContextHolder to be populated after TriggerWorkflow" )
775+ }
776+ if pc .StepOutputs == nil {
777+ t .Fatal ("expected StepOutputs to be non-nil" )
778+ }
779+ stepOut , ok := pc .StepOutputs ["set_value" ]
780+ if ! ok {
781+ t .Fatalf ("expected StepOutputs to contain 'set_value', got keys: %v" , stepKeys (pc .StepOutputs ))
782+ }
783+ if stepOut ["color" ] != "blue" {
784+ t .Errorf ("expected step output color=blue, got %v" , stepOut ["color" ])
785+ }
786+ }
787+
788+ // TestExecutePipeline_UsesUnifiedPath verifies that ExecutePipeline and
789+ // TriggerWorkflow with a PipelineContextHolder produce identical results.
790+ func TestExecutePipeline_UsesUnifiedPath (t * testing.T ) {
791+ engine , _ := setupPipelineEngine (t )
792+
793+ pipelineCfg := map [string ]any {
794+ "unified_path_pipeline" : map [string ]any {
795+ "steps" : []any {
796+ map [string ]any {
797+ "name" : "set_msg" ,
798+ "type" : "step.set" ,
799+ "config" : map [string ]any {
800+ "values" : map [string ]any {
801+ "msg" : "hello" ,
802+ },
803+ },
804+ },
805+ },
806+ },
807+ }
808+ if err := engine .configurePipelines (pipelineCfg ); err != nil {
809+ t .Fatalf ("configurePipelines failed: %v" , err )
810+ }
811+
812+ // Path 1: ExecutePipeline
813+ result1 , err := engine .ExecutePipeline (context .Background (), "unified_path_pipeline" , map [string ]any {})
814+ if err != nil {
815+ t .Fatalf ("ExecutePipeline failed: %v" , err )
816+ }
817+
818+ // Path 2: TriggerWorkflow with PipelineContextHolder
819+ holder := & module.PipelineContextHolder {}
820+ ctx := context .WithValue (context .Background (), module .PipelineContextKey , holder )
821+ if err := engine .TriggerWorkflow (ctx , "pipeline:unified_path_pipeline" , "" , map [string ]any {}); err != nil {
822+ t .Fatalf ("TriggerWorkflow failed: %v" , err )
823+ }
824+ pc := holder .Get ()
825+ if pc == nil {
826+ t .Fatal ("holder should be populated" )
827+ }
828+
829+ if result1 ["msg" ] != pc .Current ["msg" ] {
830+ t .Errorf ("ExecutePipeline and TriggerWorkflow produced different results: %v vs %v" , result1 ["msg" ], pc .Current ["msg" ])
831+ }
832+ if pc .StepOutputs ["set_msg" ]["msg" ] != "hello" {
833+ t .Errorf ("expected StepOutputs[set_msg][msg]=hello, got %v" , pc .StepOutputs ["set_msg" ]["msg" ])
834+ }
835+ }
836+
837+ // TestExecutePipelineContext_PreservesStepOutputs verifies that ExecutePipelineContext
838+ // returns a PipelineContext with StepOutputs populated for all executed steps.
839+ func TestExecutePipelineContext_PreservesStepOutputs (t * testing.T ) {
840+ engine , _ := setupPipelineEngine (t )
841+
842+ pipelineCfg := map [string ]any {
843+ "ctx_step_output_pipeline" : map [string ]any {
844+ "steps" : []any {
845+ map [string ]any {
846+ "name" : "step_a" ,
847+ "type" : "step.set" ,
848+ "config" : map [string ]any {
849+ "values" : map [string ]any {"a" : "1" },
850+ },
851+ },
852+ map [string ]any {
853+ "name" : "step_b" ,
854+ "type" : "step.set" ,
855+ "config" : map [string ]any {
856+ "values" : map [string ]any {"b" : "2" },
857+ },
858+ },
859+ },
860+ },
861+ }
862+ if err := engine .configurePipelines (pipelineCfg ); err != nil {
863+ t .Fatalf ("configurePipelines failed: %v" , err )
864+ }
865+
866+ pc , err := engine .ExecutePipelineContext (context .Background (), "ctx_step_output_pipeline" , map [string ]any {})
867+ if err != nil {
868+ t .Fatalf ("ExecutePipelineContext failed: %v" , err )
869+ }
870+ if pc == nil {
871+ t .Fatal ("expected non-nil PipelineContext" )
872+ }
873+ if len (pc .StepOutputs ) != 2 {
874+ t .Errorf ("expected 2 step outputs, got %d: %v" , len (pc .StepOutputs ), stepKeys (pc .StepOutputs ))
875+ }
876+ if pc .StepOutputs ["step_a" ]["a" ] != "1" {
877+ t .Errorf ("expected step_a output a=1, got %v" , pc .StepOutputs ["step_a" ]["a" ])
878+ }
879+ if pc .StepOutputs ["step_b" ]["b" ] != "2" {
880+ t .Errorf ("expected step_b output b=2, got %v" , pc .StepOutputs ["step_b" ]["b" ])
881+ }
882+ }
883+
884+ // stepKeys returns the keys of a step output map for test failure messages.
885+ func stepKeys (m map [string ]map [string ]any ) []string {
886+ keys := make ([]string , 0 , len (m ))
887+ for k := range m {
888+ keys = append (keys , k )
889+ }
890+ return keys
891+ }
0 commit comments