github.com/kubevela/workflow@v0.6.0/pkg/utils/operation_test.go (about)

     1  /*
     2  Copyright 2022 The KubeVela Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package utils
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"testing"
    23  	"time"
    24  
    25  	"github.com/stretchr/testify/require"
    26  	corev1 "k8s.io/api/core/v1"
    27  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    28  	"sigs.k8s.io/controller-runtime/pkg/client"
    29  
    30  	"github.com/kubevela/workflow/api/v1alpha1"
    31  	wfTypes "github.com/kubevela/workflow/pkg/types"
    32  )
    33  
    34  func TestSuspendWorkflowRun(t *testing.T) {
    35  	ctx := context.Background()
    36  
    37  	testCases := map[string]struct {
    38  		run         *v1alpha1.WorkflowRun
    39  		expected    *v1alpha1.WorkflowRun
    40  		step        string
    41  		expectedErr string
    42  	}{
    43  		"terminated": {
    44  			run: &v1alpha1.WorkflowRun{
    45  				ObjectMeta: metav1.ObjectMeta{
    46  					Name: "terminated",
    47  				},
    48  				Status: v1alpha1.WorkflowRunStatus{
    49  					Terminated: true,
    50  				},
    51  			},
    52  			expectedErr: "can not suspend a terminated workflow",
    53  		},
    54  		"already suspend": {
    55  			run: &v1alpha1.WorkflowRun{
    56  				ObjectMeta: metav1.ObjectMeta{
    57  					Name: "already-suspend",
    58  				},
    59  				Status: v1alpha1.WorkflowRunStatus{
    60  					Suspend: true,
    61  				},
    62  			},
    63  			expected: &v1alpha1.WorkflowRun{
    64  				Status: v1alpha1.WorkflowRunStatus{
    65  					Suspend: true,
    66  				},
    67  			},
    68  		},
    69  		"not suspend": {
    70  			run: &v1alpha1.WorkflowRun{
    71  				ObjectMeta: metav1.ObjectMeta{
    72  					Name: "not-suspend",
    73  				},
    74  				Status: v1alpha1.WorkflowRunStatus{
    75  					Suspend: false,
    76  				},
    77  			},
    78  			expected: &v1alpha1.WorkflowRun{
    79  				Status: v1alpha1.WorkflowRunStatus{
    80  					Suspend: true,
    81  				},
    82  			},
    83  		},
    84  		"step not found": {
    85  			run: &v1alpha1.WorkflowRun{
    86  				ObjectMeta: metav1.ObjectMeta{
    87  					Name: "step-not-found",
    88  				},
    89  				Status: v1alpha1.WorkflowRunStatus{
    90  					Suspend: false,
    91  				},
    92  			},
    93  			step:        "not-found",
    94  			expectedErr: "can not find",
    95  		},
    96  		"suspend all": {
    97  			run: &v1alpha1.WorkflowRun{
    98  				ObjectMeta: metav1.ObjectMeta{
    99  					Name: "suspend-all",
   100  				},
   101  				Status: v1alpha1.WorkflowRunStatus{
   102  					Steps: []v1alpha1.WorkflowStepStatus{
   103  						{
   104  							StepStatus: v1alpha1.StepStatus{
   105  								Name:  "step1",
   106  								Phase: v1alpha1.WorkflowStepPhaseRunning,
   107  							},
   108  							SubStepsStatus: []v1alpha1.StepStatus{
   109  								{
   110  									Name:  "sub1",
   111  									Phase: v1alpha1.WorkflowStepPhaseRunning,
   112  								},
   113  							},
   114  						},
   115  						{
   116  							StepStatus: v1alpha1.StepStatus{
   117  								Name:  "step2",
   118  								Phase: v1alpha1.WorkflowStepPhaseSucceeded,
   119  							},
   120  							SubStepsStatus: []v1alpha1.StepStatus{
   121  								{
   122  									Name:  "sub2",
   123  									Phase: v1alpha1.WorkflowStepPhaseRunning,
   124  								},
   125  							},
   126  						},
   127  					},
   128  				},
   129  			},
   130  			expected: &v1alpha1.WorkflowRun{
   131  				Status: v1alpha1.WorkflowRunStatus{
   132  					Suspend: true,
   133  					Steps: []v1alpha1.WorkflowStepStatus{
   134  						{
   135  							StepStatus: v1alpha1.StepStatus{
   136  								Name:  "step1",
   137  								Phase: v1alpha1.WorkflowStepPhaseSuspending,
   138  							},
   139  							SubStepsStatus: []v1alpha1.StepStatus{
   140  								{
   141  									Name:  "sub1",
   142  									Phase: v1alpha1.WorkflowStepPhaseSuspending,
   143  								},
   144  							},
   145  						},
   146  						{
   147  							StepStatus: v1alpha1.StepStatus{
   148  								Name:  "step2",
   149  								Phase: v1alpha1.WorkflowStepPhaseSucceeded,
   150  							},
   151  							SubStepsStatus: []v1alpha1.StepStatus{
   152  								{
   153  									Name:  "sub2",
   154  									Phase: v1alpha1.WorkflowStepPhaseSuspending,
   155  								},
   156  							},
   157  						},
   158  					},
   159  				},
   160  			},
   161  		},
   162  		"suspend specific step": {
   163  			run: &v1alpha1.WorkflowRun{
   164  				ObjectMeta: metav1.ObjectMeta{
   165  					Name: "suspend-step",
   166  				},
   167  				Status: v1alpha1.WorkflowRunStatus{
   168  					Steps: []v1alpha1.WorkflowStepStatus{
   169  						{
   170  							StepStatus: v1alpha1.StepStatus{
   171  								Name:  "step1",
   172  								Phase: v1alpha1.WorkflowStepPhaseRunning,
   173  							},
   174  							SubStepsStatus: []v1alpha1.StepStatus{
   175  								{
   176  									Name:  "sub1",
   177  									Phase: v1alpha1.WorkflowStepPhaseRunning,
   178  								},
   179  							},
   180  						},
   181  					},
   182  				},
   183  			},
   184  			expected: &v1alpha1.WorkflowRun{
   185  				Status: v1alpha1.WorkflowRunStatus{
   186  					Suspend: true,
   187  					Steps: []v1alpha1.WorkflowStepStatus{
   188  						{
   189  							StepStatus: v1alpha1.StepStatus{
   190  								Name:  "step1",
   191  								Phase: v1alpha1.WorkflowStepPhaseSuspending,
   192  							},
   193  							SubStepsStatus: []v1alpha1.StepStatus{
   194  								{
   195  									Name:  "sub1",
   196  									Phase: v1alpha1.WorkflowStepPhaseSuspending,
   197  								},
   198  							},
   199  						},
   200  					},
   201  				},
   202  			},
   203  			step: "step1",
   204  		},
   205  		"suspend specific sub step": {
   206  			run: &v1alpha1.WorkflowRun{
   207  				ObjectMeta: metav1.ObjectMeta{
   208  					Name: "suspend-sub-step",
   209  				},
   210  				Status: v1alpha1.WorkflowRunStatus{
   211  					Steps: []v1alpha1.WorkflowStepStatus{
   212  						{
   213  							StepStatus: v1alpha1.StepStatus{
   214  								Name:  "step1",
   215  								Phase: v1alpha1.WorkflowStepPhaseRunning,
   216  							},
   217  							SubStepsStatus: []v1alpha1.StepStatus{
   218  								{
   219  									Name:  "sub1",
   220  									Phase: v1alpha1.WorkflowStepPhaseRunning,
   221  								},
   222  							},
   223  						},
   224  					},
   225  				},
   226  			},
   227  			expected: &v1alpha1.WorkflowRun{
   228  				Status: v1alpha1.WorkflowRunStatus{
   229  					Suspend: true,
   230  					Steps: []v1alpha1.WorkflowStepStatus{
   231  						{
   232  							StepStatus: v1alpha1.StepStatus{
   233  								Name:  "step1",
   234  								Phase: v1alpha1.WorkflowStepPhaseRunning,
   235  							},
   236  							SubStepsStatus: []v1alpha1.StepStatus{
   237  								{
   238  									Name:  "sub1",
   239  									Phase: v1alpha1.WorkflowStepPhaseSuspending,
   240  								},
   241  							},
   242  						},
   243  					},
   244  				},
   245  			},
   246  			step: "sub1",
   247  		},
   248  	}
   249  	for name, tc := range testCases {
   250  		t.Run(name, func(t *testing.T) {
   251  			r := require.New(t)
   252  			err := cli.Create(ctx, tc.run)
   253  			r.NoError(err)
   254  			defer func() {
   255  				err = cli.Delete(ctx, tc.run)
   256  				r.NoError(err)
   257  			}()
   258  			if tc.step != "" {
   259  				operator := NewWorkflowRunStepOperator(cli, nil, tc.run)
   260  				err = operator.Suspend(ctx, tc.step)
   261  			} else {
   262  				operator := NewWorkflowRunOperator(cli, nil, tc.run)
   263  				err = operator.Suspend(ctx)
   264  			}
   265  			if tc.expectedErr != "" {
   266  				r.Contains(err.Error(), tc.expectedErr)
   267  				return
   268  			}
   269  			r.NoError(err)
   270  			run := &v1alpha1.WorkflowRun{}
   271  			err = cli.Get(ctx, client.ObjectKey{Name: tc.run.Name}, run)
   272  			r.NoError(err)
   273  			r.Equal(true, run.Status.Suspend)
   274  			r.Equal(tc.expected.Status, run.Status)
   275  		})
   276  	}
   277  }
   278  
   279  func TestTerminateWorkflowRun(t *testing.T) {
   280  	ctx := context.Background()
   281  
   282  	testCases := map[string]struct {
   283  		run      *v1alpha1.WorkflowRun
   284  		expected *v1alpha1.WorkflowRun
   285  	}{
   286  		"suspend": {
   287  			run: &v1alpha1.WorkflowRun{
   288  				ObjectMeta: metav1.ObjectMeta{
   289  					Name: "suspend",
   290  				},
   291  				Status: v1alpha1.WorkflowRunStatus{
   292  					Suspend: true,
   293  				},
   294  			},
   295  			expected: &v1alpha1.WorkflowRun{
   296  				Status: v1alpha1.WorkflowRunStatus{
   297  					Terminated: true,
   298  				},
   299  			},
   300  		},
   301  		"running step": {
   302  			run: &v1alpha1.WorkflowRun{
   303  				ObjectMeta: metav1.ObjectMeta{
   304  					Name: "running-step",
   305  				},
   306  				Status: v1alpha1.WorkflowRunStatus{
   307  					Steps: []v1alpha1.WorkflowStepStatus{
   308  						{
   309  							StepStatus: v1alpha1.StepStatus{
   310  								Phase: v1alpha1.WorkflowStepPhaseRunning,
   311  							},
   312  							SubStepsStatus: []v1alpha1.StepStatus{
   313  								{
   314  									Phase: v1alpha1.WorkflowStepPhaseRunning,
   315  								},
   316  								{
   317  									Phase:  v1alpha1.WorkflowStepPhaseFailed,
   318  									Reason: wfTypes.StatusReasonFailedAfterRetries,
   319  								},
   320  								{
   321  									Phase:  v1alpha1.WorkflowStepPhaseFailed,
   322  									Reason: wfTypes.StatusReasonTimeout,
   323  								},
   324  								{
   325  									Phase:  v1alpha1.WorkflowStepPhaseFailed,
   326  									Reason: wfTypes.StatusReasonExecute,
   327  								},
   328  							},
   329  						},
   330  						{
   331  							StepStatus: v1alpha1.StepStatus{
   332  								Phase:  v1alpha1.WorkflowStepPhaseFailed,
   333  								Reason: wfTypes.StatusReasonFailedAfterRetries,
   334  							},
   335  						},
   336  						{
   337  							StepStatus: v1alpha1.StepStatus{
   338  								Phase:  v1alpha1.WorkflowStepPhaseFailed,
   339  								Reason: wfTypes.StatusReasonTimeout,
   340  							},
   341  						},
   342  						{
   343  							StepStatus: v1alpha1.StepStatus{
   344  								Phase:  v1alpha1.WorkflowStepPhaseFailed,
   345  								Reason: wfTypes.StatusReasonExecute,
   346  							},
   347  						},
   348  					},
   349  				},
   350  			},
   351  			expected: &v1alpha1.WorkflowRun{
   352  				Status: v1alpha1.WorkflowRunStatus{
   353  					Steps: []v1alpha1.WorkflowStepStatus{
   354  						{
   355  							StepStatus: v1alpha1.StepStatus{
   356  								Phase:  v1alpha1.WorkflowStepPhaseFailed,
   357  								Reason: wfTypes.StatusReasonTerminate,
   358  							},
   359  							SubStepsStatus: []v1alpha1.StepStatus{
   360  								{
   361  									Phase:  v1alpha1.WorkflowStepPhaseFailed,
   362  									Reason: wfTypes.StatusReasonTerminate,
   363  								},
   364  								{
   365  									Phase:  v1alpha1.WorkflowStepPhaseFailed,
   366  									Reason: wfTypes.StatusReasonFailedAfterRetries,
   367  								},
   368  								{
   369  									Phase:  v1alpha1.WorkflowStepPhaseFailed,
   370  									Reason: wfTypes.StatusReasonTimeout,
   371  								},
   372  								{
   373  									Phase:  v1alpha1.WorkflowStepPhaseFailed,
   374  									Reason: wfTypes.StatusReasonTerminate,
   375  								},
   376  							},
   377  						},
   378  						{
   379  							StepStatus: v1alpha1.StepStatus{
   380  								Phase:  v1alpha1.WorkflowStepPhaseFailed,
   381  								Reason: wfTypes.StatusReasonFailedAfterRetries,
   382  							},
   383  						},
   384  						{
   385  							StepStatus: v1alpha1.StepStatus{
   386  								Phase:  v1alpha1.WorkflowStepPhaseFailed,
   387  								Reason: wfTypes.StatusReasonTimeout,
   388  							},
   389  						},
   390  						{
   391  							StepStatus: v1alpha1.StepStatus{
   392  								Phase:  v1alpha1.WorkflowStepPhaseFailed,
   393  								Reason: wfTypes.StatusReasonTerminate,
   394  							},
   395  						},
   396  					},
   397  					Terminated: true,
   398  				},
   399  			},
   400  		},
   401  	}
   402  	for name, tc := range testCases {
   403  		t.Run(name, func(t *testing.T) {
   404  			r := require.New(t)
   405  			err := cli.Create(ctx, tc.run)
   406  			r.NoError(err)
   407  			defer func() {
   408  				err = cli.Delete(ctx, tc.run)
   409  				r.NoError(err)
   410  			}()
   411  			operator := NewWorkflowRunOperator(cli, nil, tc.run)
   412  			err = operator.Terminate(ctx)
   413  			r.NoError(err)
   414  			run := &v1alpha1.WorkflowRun{}
   415  			err = cli.Get(ctx, client.ObjectKey{Name: tc.run.Name}, run)
   416  			r.NoError(err)
   417  			r.Equal(false, run.Status.Suspend)
   418  			r.Equal(true, run.Status.Terminated)
   419  			r.Equal(tc.expected.Status, run.Status)
   420  		})
   421  	}
   422  }
   423  
   424  func TestResumeWorkflowRun(t *testing.T) {
   425  	ctx := context.Background()
   426  
   427  	testCases := map[string]struct {
   428  		run         *v1alpha1.WorkflowRun
   429  		step        string
   430  		expected    *v1alpha1.WorkflowRun
   431  		expectedErr string
   432  	}{
   433  		"not suspend": {
   434  			run: &v1alpha1.WorkflowRun{
   435  				ObjectMeta: metav1.ObjectMeta{
   436  					Name: "suspend",
   437  				},
   438  				Status: v1alpha1.WorkflowRunStatus{
   439  					Suspend: false,
   440  				},
   441  			},
   442  			expected: &v1alpha1.WorkflowRun{
   443  				Status: v1alpha1.WorkflowRunStatus{},
   444  			},
   445  		},
   446  		"suspend": {
   447  			run: &v1alpha1.WorkflowRun{
   448  				ObjectMeta: metav1.ObjectMeta{
   449  					Name: "suspend",
   450  				},
   451  				Status: v1alpha1.WorkflowRunStatus{
   452  					Suspend: true,
   453  				},
   454  			},
   455  			expected: &v1alpha1.WorkflowRun{
   456  				Status: v1alpha1.WorkflowRunStatus{
   457  					Suspend: false,
   458  				},
   459  			},
   460  		},
   461  		"step not found": {
   462  			run: &v1alpha1.WorkflowRun{
   463  				ObjectMeta: metav1.ObjectMeta{
   464  					Name: "suspend",
   465  				},
   466  				Status: v1alpha1.WorkflowRunStatus{
   467  					Suspend: true,
   468  				},
   469  			},
   470  			step:        "not-found",
   471  			expectedErr: "can not find step not-found",
   472  		},
   473  		"suspend step": {
   474  			run: &v1alpha1.WorkflowRun{
   475  				ObjectMeta: metav1.ObjectMeta{
   476  					Name: "suspend-step",
   477  				},
   478  				Status: v1alpha1.WorkflowRunStatus{
   479  					Suspend: true,
   480  					Steps: []v1alpha1.WorkflowStepStatus{
   481  						{
   482  							StepStatus: v1alpha1.StepStatus{
   483  								Phase: v1alpha1.WorkflowStepPhaseSuspending,
   484  							},
   485  							SubStepsStatus: []v1alpha1.StepStatus{
   486  								{
   487  									Phase: v1alpha1.WorkflowStepPhaseSuspending,
   488  								},
   489  							},
   490  						},
   491  					},
   492  				},
   493  			},
   494  			expected: &v1alpha1.WorkflowRun{
   495  				Status: v1alpha1.WorkflowRunStatus{
   496  					Steps: []v1alpha1.WorkflowStepStatus{
   497  						{
   498  							StepStatus: v1alpha1.StepStatus{
   499  								Phase: v1alpha1.WorkflowStepPhaseRunning,
   500  							},
   501  							SubStepsStatus: []v1alpha1.StepStatus{
   502  								{
   503  									Phase: v1alpha1.WorkflowStepPhaseRunning,
   504  								},
   505  							},
   506  						},
   507  					},
   508  				},
   509  			},
   510  		},
   511  		"resume the specific step": {
   512  			step: "step1",
   513  			run: &v1alpha1.WorkflowRun{
   514  				ObjectMeta: metav1.ObjectMeta{
   515  					Name: "resume-specific-step",
   516  				},
   517  				Status: v1alpha1.WorkflowRunStatus{
   518  					Suspend: true,
   519  					Steps: []v1alpha1.WorkflowStepStatus{
   520  						{
   521  							StepStatus: v1alpha1.StepStatus{
   522  								Name:  "step1",
   523  								Phase: v1alpha1.WorkflowStepPhaseSuspending,
   524  							},
   525  						},
   526  						{
   527  							StepStatus: v1alpha1.StepStatus{
   528  								Name:  "step2",
   529  								Phase: v1alpha1.WorkflowStepPhaseSuspending,
   530  							},
   531  						},
   532  					},
   533  				},
   534  			},
   535  			expected: &v1alpha1.WorkflowRun{
   536  				Status: v1alpha1.WorkflowRunStatus{
   537  					Steps: []v1alpha1.WorkflowStepStatus{
   538  						{
   539  							StepStatus: v1alpha1.StepStatus{
   540  								Name:  "step1",
   541  								Phase: v1alpha1.WorkflowStepPhaseRunning,
   542  							},
   543  						},
   544  						{
   545  							StepStatus: v1alpha1.StepStatus{
   546  								Name:  "step2",
   547  								Phase: v1alpha1.WorkflowStepPhaseSuspending,
   548  							},
   549  						},
   550  					},
   551  				},
   552  			},
   553  		},
   554  		"resume the specific sub step": {
   555  			step: "sub-step1",
   556  			run: &v1alpha1.WorkflowRun{
   557  				ObjectMeta: metav1.ObjectMeta{
   558  					Name: "resume-specific-sub-step",
   559  				},
   560  				Status: v1alpha1.WorkflowRunStatus{
   561  					Suspend: true,
   562  					Steps: []v1alpha1.WorkflowStepStatus{
   563  						{
   564  							StepStatus: v1alpha1.StepStatus{
   565  								Name:  "step1",
   566  								Phase: v1alpha1.WorkflowStepPhaseSuspending,
   567  							},
   568  							SubStepsStatus: []v1alpha1.StepStatus{
   569  								{
   570  									Name:  "sub-step1",
   571  									Phase: v1alpha1.WorkflowStepPhaseSuspending,
   572  								},
   573  							},
   574  						},
   575  					},
   576  				},
   577  			},
   578  			expected: &v1alpha1.WorkflowRun{
   579  				Status: v1alpha1.WorkflowRunStatus{
   580  					Steps: []v1alpha1.WorkflowStepStatus{
   581  						{
   582  							StepStatus: v1alpha1.StepStatus{
   583  								Name:  "step1",
   584  								Phase: v1alpha1.WorkflowStepPhaseSuspending,
   585  							},
   586  							SubStepsStatus: []v1alpha1.StepStatus{
   587  								{
   588  									Name:  "sub-step1",
   589  									Phase: v1alpha1.WorkflowStepPhaseRunning,
   590  								},
   591  							},
   592  						},
   593  					},
   594  				},
   595  			},
   596  		},
   597  	}
   598  	for name, tc := range testCases {
   599  		t.Run(name, func(t *testing.T) {
   600  			r := require.New(t)
   601  			err := cli.Create(ctx, tc.run)
   602  			r.NoError(err)
   603  			defer func() {
   604  				err = cli.Delete(ctx, tc.run)
   605  				r.NoError(err)
   606  			}()
   607  			if tc.step == "" {
   608  				operator := NewWorkflowRunOperator(cli, nil, tc.run)
   609  				err = operator.Resume(ctx)
   610  				if tc.expectedErr != "" {
   611  					r.Error(err)
   612  					r.Equal(tc.expectedErr, err.Error())
   613  					return
   614  				}
   615  			} else {
   616  				operator := NewWorkflowRunStepOperator(cli, nil, tc.run)
   617  				err = operator.Resume(ctx, tc.step)
   618  				if tc.expectedErr != "" {
   619  					r.Error(err)
   620  					r.Equal(tc.expectedErr, err.Error())
   621  					return
   622  				}
   623  			}
   624  			r.NoError(err)
   625  			run := &v1alpha1.WorkflowRun{}
   626  			err = cli.Get(ctx, client.ObjectKey{Name: tc.run.Name}, run)
   627  			r.NoError(err)
   628  			r.Equal(false, run.Status.Suspend)
   629  			r.Equal(tc.expected.Status, run.Status, name)
   630  		})
   631  	}
   632  }
   633  
   634  func TestRollbackWorkflowRun(t *testing.T) {
   635  	r := require.New(t)
   636  	operator := NewWorkflowRunOperator(cli, nil, nil)
   637  	err := operator.Rollback(context.Background())
   638  	r.Equal("can not rollback a WorkflowRun", err.Error())
   639  }
   640  
   641  func TestRestartRunStep(t *testing.T) {
   642  	ctx := context.Background()
   643  
   644  	testCases := map[string]struct {
   645  		run          *v1alpha1.WorkflowRun
   646  		expected     *v1alpha1.WorkflowRun
   647  		vars         map[string]any
   648  		expectedVars string
   649  		stepName     string
   650  		expectedErr  string
   651  	}{
   652  		"no step name": {
   653  			run: &v1alpha1.WorkflowRun{
   654  				ObjectMeta: metav1.ObjectMeta{
   655  					Name: "no-step-name",
   656  				},
   657  				Status: v1alpha1.WorkflowRunStatus{
   658  					ContextBackend: &corev1.ObjectReference{
   659  						Name:      "workflow-no-step-name-context",
   660  						Namespace: "default",
   661  					},
   662  					Steps: []v1alpha1.WorkflowStepStatus{
   663  						{
   664  							StepStatus: v1alpha1.StepStatus{
   665  								Name: "exist",
   666  							},
   667  						},
   668  					},
   669  				},
   670  			},
   671  			expected: &v1alpha1.WorkflowRun{
   672  				ObjectMeta: metav1.ObjectMeta{
   673  					Name: "no-step-name",
   674  				},
   675  				Status: v1alpha1.WorkflowRunStatus{},
   676  			},
   677  		},
   678  		"not found": {
   679  			run: &v1alpha1.WorkflowRun{
   680  				ObjectMeta: metav1.ObjectMeta{
   681  					Name: "not-found",
   682  				},
   683  				Status: v1alpha1.WorkflowRunStatus{},
   684  			},
   685  			expectedErr: "not found",
   686  			stepName:    "not-found",
   687  		},
   688  		"not found2": {
   689  			run: &v1alpha1.WorkflowRun{
   690  				ObjectMeta: metav1.ObjectMeta{
   691  					Name: "not-found",
   692  				},
   693  				Spec: v1alpha1.WorkflowRunSpec{
   694  					WorkflowSpec: &v1alpha1.WorkflowSpec{
   695  						Steps: []v1alpha1.WorkflowStep{},
   696  					},
   697  				},
   698  				Status: v1alpha1.WorkflowRunStatus{},
   699  			},
   700  			expectedErr: "not found",
   701  			stepName:    "not-found",
   702  		},
   703  		"step not failed": {
   704  			run: &v1alpha1.WorkflowRun{
   705  				ObjectMeta: metav1.ObjectMeta{
   706  					Name: "step-not-failed",
   707  				},
   708  				Spec: v1alpha1.WorkflowRunSpec{
   709  					WorkflowSpec: &v1alpha1.WorkflowSpec{
   710  						Steps: []v1alpha1.WorkflowStep{},
   711  					},
   712  				},
   713  				Status: v1alpha1.WorkflowRunStatus{
   714  					Steps: []v1alpha1.WorkflowStepStatus{
   715  						{
   716  							StepStatus: v1alpha1.StepStatus{
   717  								Name: "step-not-failed",
   718  							},
   719  						},
   720  					},
   721  				},
   722  			},
   723  			stepName:    "step-not-failed",
   724  			expectedErr: "can not restart from a non-failed step",
   725  		},
   726  		"retry step in step-by-step": {
   727  			stepName: "step2",
   728  			vars: map[string]any{
   729  				"step1-output1": "step1-output1",
   730  				"step2-output1": "step2-output1",
   731  				"step2-output2": "step2-output2",
   732  				"step3-output1": "step3-output1",
   733  				"step3-output2": "step3-output2",
   734  			},
   735  			expectedVars: "{\n\t\"step1-output1\": \"step1-output1\"\n}",
   736  			run: &v1alpha1.WorkflowRun{
   737  				ObjectMeta: metav1.ObjectMeta{
   738  					Name: "retry-step",
   739  				},
   740  				Spec: v1alpha1.WorkflowRunSpec{
   741  					WorkflowSpec: &v1alpha1.WorkflowSpec{
   742  						Steps: []v1alpha1.WorkflowStep{
   743  							{
   744  								WorkflowStepBase: v1alpha1.WorkflowStepBase{
   745  									Name: "step1",
   746  									Outputs: v1alpha1.StepOutputs{
   747  										{
   748  											Name: "step1-output1",
   749  										},
   750  									},
   751  								},
   752  							},
   753  							{
   754  								WorkflowStepBase: v1alpha1.WorkflowStepBase{
   755  									Name: "step2",
   756  									Outputs: v1alpha1.StepOutputs{
   757  										{
   758  											Name: "step2-output1",
   759  										},
   760  										{
   761  											Name: "step2-output2",
   762  										},
   763  									},
   764  								},
   765  							},
   766  							{
   767  								WorkflowStepBase: v1alpha1.WorkflowStepBase{
   768  									Name: "step3",
   769  									Outputs: v1alpha1.StepOutputs{
   770  										{
   771  											Name: "step3-output1",
   772  										},
   773  										{
   774  											Name: "step3-output2",
   775  										},
   776  									},
   777  								},
   778  							},
   779  						},
   780  					},
   781  				},
   782  				Status: v1alpha1.WorkflowRunStatus{
   783  					Terminated: true,
   784  					Finished:   true,
   785  					Suspend:    true,
   786  					EndTime:    metav1.Time{Time: time.Now()},
   787  					ContextBackend: &corev1.ObjectReference{
   788  						Name:      "workflow-retry-step-context",
   789  						Namespace: "default",
   790  					},
   791  					Mode: v1alpha1.WorkflowExecuteMode{
   792  						Steps: v1alpha1.WorkflowModeStep,
   793  					},
   794  					Steps: []v1alpha1.WorkflowStepStatus{
   795  						{
   796  							StepStatus: v1alpha1.StepStatus{
   797  								Name:  "step1",
   798  								Phase: v1alpha1.WorkflowStepPhaseSucceeded,
   799  							},
   800  						},
   801  						{
   802  							StepStatus: v1alpha1.StepStatus{
   803  								Name:  "step2",
   804  								Phase: v1alpha1.WorkflowStepPhaseFailed,
   805  							},
   806  							SubStepsStatus: []v1alpha1.StepStatus{
   807  								{
   808  									Phase: v1alpha1.WorkflowStepPhaseRunning,
   809  								},
   810  							},
   811  						},
   812  						{
   813  							StepStatus: v1alpha1.StepStatus{
   814  								Name:  "step3",
   815  								Phase: v1alpha1.WorkflowStepPhaseFailed,
   816  							},
   817  						},
   818  					},
   819  				},
   820  			},
   821  			expected: &v1alpha1.WorkflowRun{
   822  				Status: v1alpha1.WorkflowRunStatus{
   823  					Mode: v1alpha1.WorkflowExecuteMode{
   824  						Steps: v1alpha1.WorkflowModeStep,
   825  					},
   826  					ContextBackend: &corev1.ObjectReference{
   827  						Name:      "workflow-retry-step-context",
   828  						Namespace: "default",
   829  					},
   830  					Steps: []v1alpha1.WorkflowStepStatus{
   831  						{
   832  							StepStatus: v1alpha1.StepStatus{
   833  								Name:  "step1",
   834  								Phase: v1alpha1.WorkflowStepPhaseSucceeded,
   835  							},
   836  						},
   837  					},
   838  				},
   839  			},
   840  		},
   841  		"retry step in dag": {
   842  			stepName: "step2",
   843  			vars: map[string]any{
   844  				"step2-output1": "step2-output1",
   845  				"step2-output2": "step2-output2",
   846  			},
   847  			expectedVars: "{}",
   848  			run: &v1alpha1.WorkflowRun{
   849  				ObjectMeta: metav1.ObjectMeta{
   850  					Name: "retry-step",
   851  				},
   852  				Spec: v1alpha1.WorkflowRunSpec{
   853  					WorkflowSpec: &v1alpha1.WorkflowSpec{
   854  						Steps: []v1alpha1.WorkflowStep{
   855  							{
   856  								WorkflowStepBase: v1alpha1.WorkflowStepBase{
   857  									Name:      "step1",
   858  									DependsOn: []string{"step3"},
   859  								},
   860  							},
   861  							{
   862  								WorkflowStepBase: v1alpha1.WorkflowStepBase{
   863  									Name: "step2",
   864  									Outputs: v1alpha1.StepOutputs{
   865  										{
   866  											Name: "step2-output1",
   867  										},
   868  										{
   869  											Name: "step2-output2",
   870  										},
   871  									},
   872  								},
   873  							},
   874  							{
   875  								WorkflowStepBase: v1alpha1.WorkflowStepBase{
   876  									Name: "step3",
   877  									Inputs: v1alpha1.StepInputs{
   878  										{
   879  											From: "step2-output1",
   880  										},
   881  									},
   882  								},
   883  							},
   884  						},
   885  					},
   886  				},
   887  				Status: v1alpha1.WorkflowRunStatus{
   888  					Terminated: true,
   889  					Finished:   true,
   890  					Suspend:    true,
   891  					EndTime:    metav1.Time{Time: time.Now()},
   892  					ContextBackend: &corev1.ObjectReference{
   893  						Name:      "workflow-retry-step-context",
   894  						Namespace: "default",
   895  					},
   896  					Mode: v1alpha1.WorkflowExecuteMode{
   897  						Steps: v1alpha1.WorkflowModeDAG,
   898  					},
   899  					Steps: []v1alpha1.WorkflowStepStatus{
   900  						{
   901  							StepStatus: v1alpha1.StepStatus{
   902  								Name:  "step1",
   903  								Phase: v1alpha1.WorkflowStepPhaseSucceeded,
   904  							},
   905  						},
   906  						{
   907  							StepStatus: v1alpha1.StepStatus{
   908  								Name:  "step2",
   909  								Phase: v1alpha1.WorkflowStepPhaseFailed,
   910  							},
   911  							SubStepsStatus: []v1alpha1.StepStatus{
   912  								{
   913  									Phase: v1alpha1.WorkflowStepPhaseRunning,
   914  								},
   915  							},
   916  						},
   917  						{
   918  							StepStatus: v1alpha1.StepStatus{
   919  								Name:  "step3",
   920  								Phase: v1alpha1.WorkflowStepPhaseFailed,
   921  							},
   922  						},
   923  					},
   924  				},
   925  			},
   926  			expected: &v1alpha1.WorkflowRun{
   927  				Status: v1alpha1.WorkflowRunStatus{
   928  					Mode: v1alpha1.WorkflowExecuteMode{
   929  						Steps: v1alpha1.WorkflowModeDAG,
   930  					},
   931  					ContextBackend: &corev1.ObjectReference{
   932  						Name:      "workflow-retry-step-context",
   933  						Namespace: "default",
   934  					},
   935  				},
   936  			},
   937  		},
   938  		"retry sub step in step-by-step": {
   939  			stepName: "step2-sub2",
   940  			vars: map[string]any{
   941  				"step2-output1":      "step2-output1",
   942  				"step2-output2":      "step2-output2",
   943  				"step2-sub2-output1": "step2-sub2-output1",
   944  			},
   945  			expectedVars: "{\n\t\"step2-output1\": \"step2-output1\"\n\t\"step2-output2\": \"step2-output2\"\n}",
   946  			run: &v1alpha1.WorkflowRun{
   947  				ObjectMeta: metav1.ObjectMeta{
   948  					Name: "retry-sub-step",
   949  				},
   950  				Spec: v1alpha1.WorkflowRunSpec{
   951  					WorkflowSpec: &v1alpha1.WorkflowSpec{
   952  						Steps: []v1alpha1.WorkflowStep{
   953  							{
   954  								WorkflowStepBase: v1alpha1.WorkflowStepBase{
   955  									Name: "step1",
   956  								},
   957  							},
   958  							{
   959  								WorkflowStepBase: v1alpha1.WorkflowStepBase{
   960  									Name: "step2",
   961  									Outputs: v1alpha1.StepOutputs{
   962  										{
   963  											Name: "step2-output1",
   964  										},
   965  										{
   966  											Name: "step2-output2",
   967  										},
   968  									},
   969  								},
   970  								SubSteps: []v1alpha1.WorkflowStepBase{
   971  									{
   972  										Name: "step2-sub1",
   973  									},
   974  									{
   975  										Name: "step2-sub2",
   976  										Outputs: v1alpha1.StepOutputs{
   977  											{
   978  												Name: "step2-sub2-output1",
   979  											},
   980  										},
   981  									},
   982  									{
   983  										Name: "step2-sub3",
   984  									},
   985  								},
   986  							},
   987  							{
   988  								WorkflowStepBase: v1alpha1.WorkflowStepBase{
   989  									Name: "step3",
   990  								},
   991  							},
   992  						},
   993  					},
   994  				},
   995  				Status: v1alpha1.WorkflowRunStatus{
   996  					Terminated: true,
   997  					Finished:   true,
   998  					Suspend:    true,
   999  					EndTime:    metav1.Time{Time: time.Now()},
  1000  					Mode: v1alpha1.WorkflowExecuteMode{
  1001  						Steps:    v1alpha1.WorkflowModeStep,
  1002  						SubSteps: v1alpha1.WorkflowModeStep,
  1003  					},
  1004  					ContextBackend: &corev1.ObjectReference{
  1005  						Name:      "workflow-retry-sub-step-context",
  1006  						Namespace: "default",
  1007  					},
  1008  					Steps: []v1alpha1.WorkflowStepStatus{
  1009  						{
  1010  							StepStatus: v1alpha1.StepStatus{
  1011  								Name:  "step1",
  1012  								Phase: v1alpha1.WorkflowStepPhaseSucceeded,
  1013  							},
  1014  						},
  1015  						{
  1016  							StepStatus: v1alpha1.StepStatus{
  1017  								Name:  "step2",
  1018  								Phase: v1alpha1.WorkflowStepPhaseFailed,
  1019  							},
  1020  							SubStepsStatus: []v1alpha1.StepStatus{
  1021  								{
  1022  									Name:  "step2-sub1",
  1023  									Phase: v1alpha1.WorkflowStepPhaseFailed,
  1024  								},
  1025  								{
  1026  									Name:  "step2-sub2",
  1027  									Phase: v1alpha1.WorkflowStepPhaseFailed,
  1028  								},
  1029  								{
  1030  									Name:  "step2-sub3",
  1031  									Phase: v1alpha1.WorkflowStepPhaseFailed,
  1032  								},
  1033  							},
  1034  						},
  1035  						{
  1036  							StepStatus: v1alpha1.StepStatus{
  1037  								Name:  "step3",
  1038  								Phase: v1alpha1.WorkflowStepPhaseFailed,
  1039  							},
  1040  						},
  1041  					},
  1042  				},
  1043  			},
  1044  			expected: &v1alpha1.WorkflowRun{
  1045  				Status: v1alpha1.WorkflowRunStatus{
  1046  					Mode: v1alpha1.WorkflowExecuteMode{
  1047  						Steps:    v1alpha1.WorkflowModeStep,
  1048  						SubSteps: v1alpha1.WorkflowModeStep,
  1049  					},
  1050  					ContextBackend: &corev1.ObjectReference{
  1051  						Name:      "workflow-retry-sub-step-context",
  1052  						Namespace: "default",
  1053  					},
  1054  					Steps: []v1alpha1.WorkflowStepStatus{
  1055  						{
  1056  							StepStatus: v1alpha1.StepStatus{
  1057  								Name:  "step1",
  1058  								Phase: v1alpha1.WorkflowStepPhaseSucceeded,
  1059  							},
  1060  						},
  1061  						{
  1062  							StepStatus: v1alpha1.StepStatus{
  1063  								Name:  "step2",
  1064  								Phase: v1alpha1.WorkflowStepPhaseRunning,
  1065  							},
  1066  							SubStepsStatus: []v1alpha1.StepStatus{
  1067  								{
  1068  									Name:  "step2-sub1",
  1069  									Phase: v1alpha1.WorkflowStepPhaseFailed,
  1070  								},
  1071  							},
  1072  						},
  1073  					},
  1074  				},
  1075  			},
  1076  		},
  1077  		"retry sub step in dag": {
  1078  			stepName: "step2-sub2",
  1079  			vars: map[string]any{
  1080  				"step2-sub2-output1": "step2-sub2-output1",
  1081  			},
  1082  			expectedVars: "{}",
  1083  			run: &v1alpha1.WorkflowRun{
  1084  				ObjectMeta: metav1.ObjectMeta{
  1085  					Name: "retry-sub-step",
  1086  				},
  1087  				Spec: v1alpha1.WorkflowRunSpec{
  1088  					WorkflowSpec: &v1alpha1.WorkflowSpec{
  1089  						Steps: []v1alpha1.WorkflowStep{
  1090  							{
  1091  								WorkflowStepBase: v1alpha1.WorkflowStepBase{
  1092  									Name: "step1",
  1093  								},
  1094  							},
  1095  							{
  1096  								WorkflowStepBase: v1alpha1.WorkflowStepBase{
  1097  									Name: "step2",
  1098  								},
  1099  								SubSteps: []v1alpha1.WorkflowStepBase{
  1100  									{
  1101  										Name:      "step2-sub1",
  1102  										DependsOn: []string{"step2-sub2"},
  1103  									},
  1104  									{
  1105  										Name: "step2-sub2",
  1106  										Outputs: v1alpha1.StepOutputs{
  1107  											{
  1108  												Name: "step2-sub2-output1",
  1109  											},
  1110  										},
  1111  									},
  1112  									{
  1113  										Name: "step2-sub3",
  1114  										Inputs: v1alpha1.StepInputs{
  1115  											{
  1116  												From: "step2-sub2-output1",
  1117  											},
  1118  										},
  1119  									},
  1120  								},
  1121  							},
  1122  							{
  1123  								WorkflowStepBase: v1alpha1.WorkflowStepBase{
  1124  									Name:      "step3",
  1125  									DependsOn: []string{"step2"},
  1126  								},
  1127  							},
  1128  						},
  1129  					},
  1130  				},
  1131  				Status: v1alpha1.WorkflowRunStatus{
  1132  					Terminated: true,
  1133  					Finished:   true,
  1134  					Suspend:    true,
  1135  					EndTime:    metav1.Time{Time: time.Now()},
  1136  					ContextBackend: &corev1.ObjectReference{
  1137  						Name:      "workflow-retry-sub-step-context",
  1138  						Namespace: "default",
  1139  					},
  1140  					Mode: v1alpha1.WorkflowExecuteMode{
  1141  						Steps:    v1alpha1.WorkflowModeDAG,
  1142  						SubSteps: v1alpha1.WorkflowModeDAG,
  1143  					},
  1144  					Steps: []v1alpha1.WorkflowStepStatus{
  1145  						{
  1146  							StepStatus: v1alpha1.StepStatus{
  1147  								Name:  "step1",
  1148  								Phase: v1alpha1.WorkflowStepPhaseSucceeded,
  1149  							},
  1150  						},
  1151  						{
  1152  							StepStatus: v1alpha1.StepStatus{
  1153  								Name:  "step2",
  1154  								Phase: v1alpha1.WorkflowStepPhaseFailed,
  1155  							},
  1156  							SubStepsStatus: []v1alpha1.StepStatus{
  1157  								{
  1158  									Name:  "step2-sub1",
  1159  									Phase: v1alpha1.WorkflowStepPhaseFailed,
  1160  								},
  1161  								{
  1162  									Name:  "step2-sub2",
  1163  									Phase: v1alpha1.WorkflowStepPhaseFailed,
  1164  								},
  1165  								{
  1166  									Name:  "step2-sub3",
  1167  									Phase: v1alpha1.WorkflowStepPhaseFailed,
  1168  								},
  1169  							},
  1170  						},
  1171  						{
  1172  							StepStatus: v1alpha1.StepStatus{
  1173  								Name:  "step3",
  1174  								Phase: v1alpha1.WorkflowStepPhaseFailed,
  1175  							},
  1176  						},
  1177  					},
  1178  				},
  1179  			},
  1180  			expected: &v1alpha1.WorkflowRun{
  1181  				Status: v1alpha1.WorkflowRunStatus{
  1182  					Mode: v1alpha1.WorkflowExecuteMode{
  1183  						Steps:    v1alpha1.WorkflowModeDAG,
  1184  						SubSteps: v1alpha1.WorkflowModeDAG,
  1185  					},
  1186  					ContextBackend: &corev1.ObjectReference{
  1187  						Name:      "workflow-retry-sub-step-context",
  1188  						Namespace: "default",
  1189  					},
  1190  					Steps: []v1alpha1.WorkflowStepStatus{
  1191  						{
  1192  							StepStatus: v1alpha1.StepStatus{
  1193  								Name:  "step1",
  1194  								Phase: v1alpha1.WorkflowStepPhaseSucceeded,
  1195  							},
  1196  						},
  1197  						{
  1198  							StepStatus: v1alpha1.StepStatus{
  1199  								Name:  "step2",
  1200  								Phase: v1alpha1.WorkflowStepPhaseRunning,
  1201  							},
  1202  						},
  1203  					},
  1204  				},
  1205  			},
  1206  		},
  1207  	}
  1208  	for name, tc := range testCases {
  1209  		t.Run(name, func(t *testing.T) {
  1210  			r := require.New(t)
  1211  			err := cli.Create(ctx, tc.run)
  1212  			r.NoError(err)
  1213  			defer func() {
  1214  				err = cli.Delete(ctx, tc.run)
  1215  				r.NoError(err)
  1216  			}()
  1217  			if tc.vars != nil {
  1218  				b, err := json.Marshal(tc.vars)
  1219  				r.NoError(err)
  1220  				cm := &corev1.ConfigMap{
  1221  					ObjectMeta: metav1.ObjectMeta{
  1222  						Name:      tc.run.Status.ContextBackend.Name,
  1223  						Namespace: tc.run.Namespace,
  1224  					},
  1225  					Data: map[string]string{
  1226  						"vars": string(b),
  1227  					},
  1228  				}
  1229  				err = cli.Create(ctx, cm)
  1230  				r.NoError(err)
  1231  				defer func() {
  1232  					err = cli.Delete(ctx, cm)
  1233  					r.NoError(err)
  1234  				}()
  1235  			}
  1236  			if tc.stepName == "" {
  1237  				operator := NewWorkflowRunOperator(cli, nil, tc.run)
  1238  				err = operator.Restart(ctx)
  1239  				if tc.expectedErr != "" {
  1240  					r.Contains(err.Error(), tc.expectedErr)
  1241  					return
  1242  				}
  1243  				r.NoError(err)
  1244  			} else {
  1245  				operator := NewWorkflowRunStepOperator(cli, nil, tc.run)
  1246  				err = operator.Restart(ctx, tc.stepName)
  1247  				if tc.expectedErr != "" {
  1248  					r.Contains(err.Error(), tc.expectedErr)
  1249  					return
  1250  				}
  1251  				r.NoError(err)
  1252  			}
  1253  			run := &v1alpha1.WorkflowRun{}
  1254  			err = cli.Get(ctx, client.ObjectKey{Name: tc.run.Name}, run)
  1255  			r.NoError(err)
  1256  			r.Equal(false, run.Status.Suspend)
  1257  			r.Equal(false, run.Status.Terminated)
  1258  			r.Equal(false, run.Status.Finished)
  1259  			r.True(run.Status.EndTime.IsZero())
  1260  			r.Equal(tc.expected.Status, run.Status)
  1261  			if tc.vars != nil {
  1262  				cm := &corev1.ConfigMap{}
  1263  				err = cli.Get(ctx, client.ObjectKey{Name: tc.run.Status.ContextBackend.Name, Namespace: tc.run.Namespace}, cm)
  1264  				r.NoError(err)
  1265  				r.Equal(tc.expectedVars, cm.Data["vars"])
  1266  			}
  1267  		})
  1268  	}
  1269  }