@@ -638,6 +638,154 @@ func TestCascadeDelete_KillTriggerRunError_LogsAndContinues(t *testing.T) {
638638 require .ElementsMatch (t , []string {"tr-bad" , "tr-good" }, trStub .killedNames )
639639}
640640
641+ func TestCascadeDelete_ActivePipelineRuns (t * testing.T ) {
642+ now := metav1 .Now ()
643+ pipeline := & v2pb.Pipeline {
644+ ObjectMeta : metav1.ObjectMeta {
645+ Name : "test-pipeline" ,
646+ Namespace : "test-namespace" ,
647+ Finalizers : []string {api .PipelineFinalizer },
648+ DeletionTimestamp : & now ,
649+ },
650+ Spec : v2pb.PipelineSpec {
651+ Commit : & v2pb.CommitInfo {GitRef : "abc123" , Branch : "main" },
652+ },
653+ }
654+ runningPR := & v2pb.PipelineRun {
655+ ObjectMeta : metav1.ObjectMeta {Name : "pr-running" , Namespace : "test-namespace" },
656+ Spec : v2pb.PipelineRunSpec {
657+ Pipeline : & apipb.ResourceIdentifier {Name : "test-pipeline" , Namespace : "test-namespace" },
658+ },
659+ Status : v2pb.PipelineRunStatus {State : v2pb .PIPELINE_RUN_STATE_RUNNING },
660+ }
661+
662+ reconciler := setUpReconciler (t , []client.Object {pipeline , runningPR }, env.Context {})
663+ result , err := reconciler .Reconcile (context .Background (), ctrl.Request {
664+ NamespacedName : types.NamespacedName {Name : "test-pipeline" , Namespace : "test-namespace" },
665+ })
666+ require .NoError (t , err )
667+ require .Equal (t , ctrl.Result {RequeueAfter : reconcileInterval }, result )
668+
669+ updatedPR := & v2pb.PipelineRun {}
670+ require .NoError (t , reconciler .Get (context .Background (), "test-namespace" , "pr-running" , & metav1.GetOptions {}, updatedPR ))
671+ require .True (t , updatedPR .Spec .Kill )
672+ }
673+
674+ // stubPipelineRunManager implements pipelinerun.Manager with configurable
675+ // behavior to cover error branches in handleDeletion's PipelineRun path.
676+ type stubPipelineRunManager struct {
677+ listAll []* v2pb.PipelineRun
678+ listAllErr error
679+ listActive []* v2pb.PipelineRun
680+ listActiveErr error
681+ killErrByName map [string ]error
682+ killedNames []string
683+ }
684+
685+ func (s * stubPipelineRunManager ) ListPipelineRunsForPipeline (ctx context.Context , namespace , pipelineName string ) ([]* v2pb.PipelineRun , error ) {
686+ return s .listAll , s .listAllErr
687+ }
688+
689+ func (s * stubPipelineRunManager ) ListActivePipelineRunsForPipeline (ctx context.Context , namespace , pipelineName string ) ([]* v2pb.PipelineRun , error ) {
690+ return s .listActive , s .listActiveErr
691+ }
692+
693+ func (s * stubPipelineRunManager ) KillPipelineRun (ctx context.Context , pr * v2pb.PipelineRun ) error {
694+ s .killedNames = append (s .killedNames , pr .Name )
695+ if err , ok := s .killErrByName [pr .Name ]; ok {
696+ return err
697+ }
698+ return nil
699+ }
700+
701+ func (s * stubPipelineRunManager ) DeleteAllPipelineRuns (ctx context.Context , namespace , pipelineName string ) error {
702+ return nil
703+ }
704+
705+ func setUpReconcilerWithStubManagers (t * testing.T , initialObjects []client.Object , trMgr triggerrun.Manager , prMgr pipelinerun.Manager ) * Reconciler {
706+ scheme := runtime .NewScheme ()
707+ require .NoError (t , v2pb .AddToScheme (scheme ))
708+ k8sClient := fake .NewClientBuilder ().WithScheme (scheme ).WithObjects (initialObjects ... ).WithStatusSubresource (initialObjects ... ).Build ()
709+ logger := zaptest .NewLogger (t )
710+ handler := apiHandler .NewFakeAPIHandler (k8sClient )
711+ if trMgr == nil {
712+ trMgr = triggerrun .NewManager (handler , logger )
713+ }
714+ if prMgr == nil {
715+ prMgr = pipelinerun .NewManager (handler , logger )
716+ }
717+ return & Reconciler {
718+ Handler : handler ,
719+ logger : logger ,
720+ triggerRunManager : trMgr ,
721+ pipelineRunManager : prMgr ,
722+ }
723+ }
724+
725+ func TestCascadeDelete_ListActivePipelineRunsError (t * testing.T ) {
726+ now := metav1 .Now ()
727+ pipeline := & v2pb.Pipeline {
728+ ObjectMeta : metav1.ObjectMeta {
729+ Name : "test-pipeline" ,
730+ Namespace : "test-namespace" ,
731+ Finalizers : []string {api .PipelineFinalizer },
732+ DeletionTimestamp : & now ,
733+ },
734+ Spec : v2pb.PipelineSpec {
735+ Commit : & v2pb.CommitInfo {GitRef : "abc123" , Branch : "main" },
736+ },
737+ }
738+ activeErr := errors .New ("list active pr boom" )
739+ prStub := & stubPipelineRunManager {
740+ // First List (children check) returns one PR so we proceed past the empty-children branch.
741+ listAll : []* v2pb.PipelineRun {
742+ {ObjectMeta : metav1.ObjectMeta {Name : "pr-1" , Namespace : "test-namespace" }},
743+ },
744+ listActiveErr : activeErr ,
745+ }
746+ reconciler := setUpReconcilerWithStubManagers (t , []client.Object {pipeline }, nil , prStub )
747+
748+ _ , err := reconciler .Reconcile (context .Background (), ctrl.Request {
749+ NamespacedName : types.NamespacedName {Name : "test-pipeline" , Namespace : "test-namespace" },
750+ })
751+ require .Error (t , err )
752+ require .ErrorIs (t , err , activeErr )
753+ require .Contains (t , err .Error (), "list active pipeline runs for pipeline test-namespace/test-pipeline" )
754+ }
755+
756+ func TestCascadeDelete_KillPipelineRunError_LogsAndContinues (t * testing.T ) {
757+ now := metav1 .Now ()
758+ pipeline := & v2pb.Pipeline {
759+ ObjectMeta : metav1.ObjectMeta {
760+ Name : "test-pipeline" ,
761+ Namespace : "test-namespace" ,
762+ Finalizers : []string {api .PipelineFinalizer },
763+ DeletionTimestamp : & now ,
764+ },
765+ Spec : v2pb.PipelineSpec {
766+ Commit : & v2pb.CommitInfo {GitRef : "abc123" , Branch : "main" },
767+ },
768+ }
769+ active := []* v2pb.PipelineRun {
770+ {ObjectMeta : metav1.ObjectMeta {Name : "pr-bad" , Namespace : "test-namespace" }},
771+ {ObjectMeta : metav1.ObjectMeta {Name : "pr-good" , Namespace : "test-namespace" }},
772+ }
773+ prStub := & stubPipelineRunManager {
774+ listAll : active ,
775+ listActive : active ,
776+ killErrByName : map [string ]error {"pr-bad" : errors .New ("kill boom" )},
777+ }
778+ reconciler := setUpReconcilerWithStubManagers (t , []client.Object {pipeline }, nil , prStub )
779+
780+ result , err := reconciler .Reconcile (context .Background (), ctrl.Request {
781+ NamespacedName : types.NamespacedName {Name : "test-pipeline" , Namespace : "test-namespace" },
782+ })
783+ require .NoError (t , err )
784+ require .Equal (t , ctrl.Result {RequeueAfter : reconcileInterval }, result )
785+
786+ require .ElementsMatch (t , []string {"pr-bad" , "pr-good" }, prStub .killedNames )
787+ }
788+
641789func setUpReconciler (t * testing.T , initialObjects []client.Object , env env.Context ) * Reconciler {
642790 scheme := runtime .NewScheme ()
643791 err := v2pb .AddToScheme (scheme )
0 commit comments