github.com/oam-dev/kubevela@v1.9.11/references/cli/workflow_test.go (about)

     1  /*
     2  Copyright 2021 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 cli
    18  
    19  import (
    20  	"bytes"
    21  	"context"
    22  	"fmt"
    23  	"os"
    24  	"strings"
    25  	"testing"
    26  	"time"
    27  
    28  	"github.com/stretchr/testify/require"
    29  	corev1 "k8s.io/api/core/v1"
    30  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    31  	"k8s.io/apimachinery/pkg/runtime"
    32  	"k8s.io/apimachinery/pkg/types"
    33  
    34  	workflowv1alpha1 "github.com/kubevela/workflow/api/v1alpha1"
    35  	wfTypes "github.com/kubevela/workflow/pkg/types"
    36  
    37  	"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
    38  	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
    39  	cmdutil "github.com/oam-dev/kubevela/pkg/utils/util"
    40  )
    41  
    42  var workflowSpec = v1beta1.ApplicationSpec{
    43  	Components: []common.ApplicationComponent{{
    44  		Name:       "test-component",
    45  		Type:       "worker",
    46  		Properties: &runtime.RawExtension{Raw: []byte(`{"cmd":["sleep","1000"],"image":"busybox"}`)},
    47  	}},
    48  	Workflow: &v1beta1.Workflow{
    49  		Steps: []workflowv1alpha1.WorkflowStep{{
    50  			WorkflowStepBase: workflowv1alpha1.WorkflowStepBase{
    51  				Name:       "test-wf1",
    52  				Type:       "foowf",
    53  				Properties: &runtime.RawExtension{Raw: []byte(`{"namespace":"default"}`)},
    54  			},
    55  		}},
    56  	},
    57  }
    58  
    59  func TestWorkflowSuspend(t *testing.T) {
    60  	c := initArgs()
    61  	ioStream := cmdutil.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr}
    62  	ctx := context.TODO()
    63  
    64  	testCases := map[string]struct {
    65  		app         *v1beta1.Application
    66  		expected    *v1beta1.Application
    67  		step        string
    68  		expectedErr string
    69  	}{
    70  		"no app name specified": {
    71  			expectedErr: "please specify the name of application/workflow",
    72  		},
    73  		"workflow not running": {
    74  			app: &v1beta1.Application{
    75  				ObjectMeta: metav1.ObjectMeta{
    76  					Name:      "workflow-not-running",
    77  					Namespace: "default",
    78  				},
    79  				Spec:   workflowSpec,
    80  				Status: common.AppStatus{},
    81  			},
    82  			expectedErr: "the workflow in application workflow-not-running is not start",
    83  		},
    84  		"suspend successfully": {
    85  			app: &v1beta1.Application{
    86  				ObjectMeta: metav1.ObjectMeta{
    87  					Name:      "workflow",
    88  					Namespace: "test",
    89  				},
    90  				Spec: workflowSpec,
    91  				Status: common.AppStatus{
    92  					Workflow: &common.WorkflowStatus{
    93  						Suspend: false,
    94  					},
    95  				},
    96  			},
    97  			expected: &v1beta1.Application{
    98  				ObjectMeta: metav1.ObjectMeta{
    99  					Name:      "workflow",
   100  					Namespace: "test",
   101  				},
   102  				Spec: workflowSpec,
   103  				Status: common.AppStatus{
   104  					Workflow: &common.WorkflowStatus{
   105  						Suspend: true,
   106  					},
   107  				},
   108  			},
   109  		},
   110  		"step not found": {
   111  			app: &v1beta1.Application{
   112  				ObjectMeta: metav1.ObjectMeta{
   113  					Name:      "step-not-found",
   114  					Namespace: "default",
   115  				},
   116  				Spec: workflowSpec,
   117  				Status: common.AppStatus{
   118  					Workflow: &common.WorkflowStatus{
   119  						Suspend: false,
   120  					},
   121  				},
   122  			},
   123  			step:        "not-found",
   124  			expectedErr: "can not find",
   125  		},
   126  		"suspend all": {
   127  			app: &v1beta1.Application{
   128  				ObjectMeta: metav1.ObjectMeta{
   129  					Name:      "suspend-all",
   130  					Namespace: "default",
   131  				},
   132  				Spec: workflowSpec,
   133  				Status: common.AppStatus{
   134  					Workflow: &common.WorkflowStatus{
   135  						Steps: []workflowv1alpha1.WorkflowStepStatus{
   136  							{
   137  								StepStatus: workflowv1alpha1.StepStatus{
   138  									Name:  "step1",
   139  									Phase: workflowv1alpha1.WorkflowStepPhaseRunning,
   140  								},
   141  								SubStepsStatus: []workflowv1alpha1.StepStatus{
   142  									{
   143  										Name:  "sub1",
   144  										Phase: workflowv1alpha1.WorkflowStepPhaseRunning,
   145  									},
   146  								},
   147  							},
   148  							{
   149  								StepStatus: workflowv1alpha1.StepStatus{
   150  									Name:  "step2",
   151  									Phase: workflowv1alpha1.WorkflowStepPhaseRunning,
   152  								},
   153  								SubStepsStatus: []workflowv1alpha1.StepStatus{
   154  									{
   155  										Name:  "sub2",
   156  										Phase: workflowv1alpha1.WorkflowStepPhaseSucceeded,
   157  									},
   158  								},
   159  							},
   160  						},
   161  					},
   162  				},
   163  			},
   164  			expected: &v1beta1.Application{
   165  				Status: common.AppStatus{
   166  					Workflow: &common.WorkflowStatus{
   167  						Suspend: true,
   168  						Steps: []workflowv1alpha1.WorkflowStepStatus{
   169  							{
   170  								StepStatus: workflowv1alpha1.StepStatus{
   171  									Name:  "step1",
   172  									Phase: workflowv1alpha1.WorkflowStepPhaseSuspending,
   173  								},
   174  								SubStepsStatus: []workflowv1alpha1.StepStatus{
   175  									{
   176  										Name:  "sub1",
   177  										Phase: workflowv1alpha1.WorkflowStepPhaseSuspending,
   178  									},
   179  								},
   180  							},
   181  							{
   182  								StepStatus: workflowv1alpha1.StepStatus{
   183  									Name:  "step2",
   184  									Phase: workflowv1alpha1.WorkflowStepPhaseSuspending,
   185  								},
   186  								SubStepsStatus: []workflowv1alpha1.StepStatus{
   187  									{
   188  										Name:  "sub2",
   189  										Phase: workflowv1alpha1.WorkflowStepPhaseSucceeded,
   190  									},
   191  								},
   192  							},
   193  						},
   194  					},
   195  				},
   196  			},
   197  		},
   198  		"suspend specific step": {
   199  			app: &v1beta1.Application{
   200  				ObjectMeta: metav1.ObjectMeta{
   201  					Name:      "suspend-step",
   202  					Namespace: "default",
   203  				},
   204  				Spec: workflowSpec,
   205  				Status: common.AppStatus{
   206  					Workflow: &common.WorkflowStatus{
   207  						Steps: []workflowv1alpha1.WorkflowStepStatus{
   208  							{
   209  								StepStatus: workflowv1alpha1.StepStatus{
   210  									Name:  "step1",
   211  									Phase: workflowv1alpha1.WorkflowStepPhaseRunning,
   212  								},
   213  								SubStepsStatus: []workflowv1alpha1.StepStatus{
   214  									{
   215  										Name:  "sub1",
   216  										Phase: workflowv1alpha1.WorkflowStepPhaseRunning,
   217  									},
   218  								},
   219  							},
   220  						},
   221  					},
   222  				},
   223  			},
   224  			expected: &v1beta1.Application{
   225  				Status: common.AppStatus{
   226  					Workflow: &common.WorkflowStatus{
   227  						Suspend: true,
   228  						Steps: []workflowv1alpha1.WorkflowStepStatus{
   229  							{
   230  								StepStatus: workflowv1alpha1.StepStatus{
   231  									Name:  "step1",
   232  									Phase: workflowv1alpha1.WorkflowStepPhaseSuspending,
   233  								},
   234  								SubStepsStatus: []workflowv1alpha1.StepStatus{
   235  									{
   236  										Name:  "sub1",
   237  										Phase: workflowv1alpha1.WorkflowStepPhaseSuspending,
   238  									},
   239  								},
   240  							},
   241  						},
   242  					},
   243  				},
   244  			},
   245  			step: "step1",
   246  		},
   247  		"suspend specific sub step": {
   248  			app: &v1beta1.Application{
   249  				ObjectMeta: metav1.ObjectMeta{
   250  					Name:      "suspend-sub-step",
   251  					Namespace: "default",
   252  				},
   253  				Spec: workflowSpec,
   254  				Status: common.AppStatus{
   255  					Workflow: &common.WorkflowStatus{
   256  						Steps: []workflowv1alpha1.WorkflowStepStatus{
   257  							{
   258  								StepStatus: workflowv1alpha1.StepStatus{
   259  									Name:  "step1",
   260  									Phase: workflowv1alpha1.WorkflowStepPhaseRunning,
   261  								},
   262  								SubStepsStatus: []workflowv1alpha1.StepStatus{
   263  									{
   264  										Name:  "sub1",
   265  										Phase: workflowv1alpha1.WorkflowStepPhaseRunning,
   266  									},
   267  								},
   268  							},
   269  						},
   270  					},
   271  				},
   272  			},
   273  			expected: &v1beta1.Application{
   274  				Status: common.AppStatus{
   275  					Workflow: &common.WorkflowStatus{
   276  						Suspend: true,
   277  						Steps: []workflowv1alpha1.WorkflowStepStatus{
   278  							{
   279  								StepStatus: workflowv1alpha1.StepStatus{
   280  									Name:  "step1",
   281  									Phase: workflowv1alpha1.WorkflowStepPhaseRunning,
   282  								},
   283  								SubStepsStatus: []workflowv1alpha1.StepStatus{
   284  									{
   285  										Name:  "sub1",
   286  										Phase: workflowv1alpha1.WorkflowStepPhaseSuspending,
   287  									},
   288  								},
   289  							},
   290  						},
   291  					},
   292  				},
   293  			},
   294  			step: "sub1",
   295  		},
   296  	}
   297  
   298  	for name, tc := range testCases {
   299  		t.Run(name, func(t *testing.T) {
   300  			r := require.New(t)
   301  			cmd := NewWorkflowSuspendCommand(c, ioStream, &WorkflowArgs{Args: c, Writer: ioStream.Out})
   302  			initCommand(cmd)
   303  			// clean up the arguments before start
   304  			cmd.SetArgs([]string{})
   305  			client, err := c.GetClient()
   306  			r.NoError(err)
   307  			if tc.app != nil {
   308  				err := client.Create(ctx, tc.app)
   309  				r.NoError(err)
   310  				cmdArgs := []string{tc.app.Name}
   311  				if tc.app.Namespace != corev1.NamespaceDefault {
   312  					err := client.Create(ctx, &corev1.Namespace{
   313  						ObjectMeta: metav1.ObjectMeta{
   314  							Name: tc.app.Namespace,
   315  						},
   316  					})
   317  					r.NoError(err)
   318  					cmdArgs = append(cmdArgs, "-n", tc.app.Namespace)
   319  					cmd.SetArgs([]string{tc.app.Name, "-n", tc.app.Namespace})
   320  				}
   321  				if tc.step != "" {
   322  					cmdArgs = append(cmdArgs, "--step", tc.step)
   323  				}
   324  				cmd.SetArgs(cmdArgs)
   325  			}
   326  			err = cmd.Execute()
   327  			if tc.expectedErr != "" {
   328  				r.Contains(err.Error(), tc.expectedErr)
   329  				return
   330  			}
   331  			r.NoError(err)
   332  
   333  			wf := &v1beta1.Application{}
   334  			err = client.Get(ctx, types.NamespacedName{
   335  				Namespace: tc.app.Namespace,
   336  				Name:      tc.app.Name,
   337  			}, wf)
   338  			r.NoError(err)
   339  			r.Equal(true, wf.Status.Workflow.Suspend)
   340  			r.Equal(tc.expected.Status, wf.Status)
   341  		})
   342  	}
   343  }
   344  
   345  func TestWorkflowResume(t *testing.T) {
   346  	c := initArgs()
   347  	ioStream := cmdutil.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr}
   348  	ctx := context.TODO()
   349  
   350  	testCases := map[string]struct {
   351  		app         *v1beta1.Application
   352  		expectedErr error
   353  	}{
   354  		"no app name specified": {
   355  			expectedErr: fmt.Errorf("please specify the name of application/workflow"),
   356  		},
   357  		"workflow not suspended": {
   358  			app: &v1beta1.Application{
   359  				ObjectMeta: metav1.ObjectMeta{
   360  					Name:      "workflow-not-suspended",
   361  					Namespace: "default",
   362  				},
   363  				Spec: workflowSpec,
   364  				Status: common.AppStatus{
   365  					Workflow: &common.WorkflowStatus{
   366  						Suspend: false,
   367  					},
   368  				},
   369  			},
   370  		},
   371  		"workflow not running": {
   372  			app: &v1beta1.Application{
   373  				ObjectMeta: metav1.ObjectMeta{
   374  					Name:      "workflow-not-running",
   375  					Namespace: "default",
   376  				},
   377  				Spec:   workflowSpec,
   378  				Status: common.AppStatus{},
   379  			},
   380  			expectedErr: fmt.Errorf("the workflow in application workflow-not-running is not start"),
   381  		},
   382  		"workflow terminated": {
   383  			app: &v1beta1.Application{
   384  				ObjectMeta: metav1.ObjectMeta{
   385  					Name:      "workflow-terminated",
   386  					Namespace: "default",
   387  				},
   388  				Spec: workflowSpec,
   389  				Status: common.AppStatus{
   390  					Workflow: &common.WorkflowStatus{
   391  						Terminated: true,
   392  					},
   393  				},
   394  			},
   395  			expectedErr: fmt.Errorf("can not resume a terminated workflow"),
   396  		},
   397  		"resume successfully": {
   398  			app: &v1beta1.Application{
   399  				ObjectMeta: metav1.ObjectMeta{
   400  					Name:      "workflow",
   401  					Namespace: "test",
   402  				},
   403  				Spec: workflowSpec,
   404  				Status: common.AppStatus{
   405  					Workflow: &common.WorkflowStatus{
   406  						Suspend: true,
   407  						Steps: []workflowv1alpha1.WorkflowStepStatus{
   408  							{
   409  								StepStatus: workflowv1alpha1.StepStatus{
   410  									Type:  "suspend",
   411  									Phase: "running",
   412  								},
   413  							},
   414  							{
   415  								StepStatus: workflowv1alpha1.StepStatus{
   416  									Type: "step-group",
   417  								},
   418  								SubStepsStatus: []workflowv1alpha1.StepStatus{
   419  									{
   420  										Type:  "suspend",
   421  										Phase: "running",
   422  									},
   423  								},
   424  							},
   425  						},
   426  					},
   427  				},
   428  			},
   429  		},
   430  	}
   431  
   432  	for name, tc := range testCases {
   433  		t.Run(name, func(t *testing.T) {
   434  			r := require.New(t)
   435  			cmd := NewWorkflowResumeCommand(c, ioStream, &WorkflowArgs{Args: c, Writer: ioStream.Out})
   436  			initCommand(cmd)
   437  			// clean up the arguments before start
   438  			cmd.SetArgs([]string{})
   439  			client, err := c.GetClient()
   440  			r.NoError(err)
   441  			if tc.app != nil {
   442  				err := client.Create(ctx, tc.app)
   443  				r.NoError(err)
   444  
   445  				if tc.app.Namespace != corev1.NamespaceDefault {
   446  					err := client.Create(ctx, &corev1.Namespace{
   447  						ObjectMeta: metav1.ObjectMeta{
   448  							Name: tc.app.Namespace,
   449  						},
   450  					})
   451  					r.NoError(err)
   452  					cmd.SetArgs([]string{tc.app.Name, "-n", tc.app.Namespace})
   453  				} else {
   454  					cmd.SetArgs([]string{tc.app.Name})
   455  				}
   456  			}
   457  			err = cmd.Execute()
   458  			if tc.expectedErr != nil {
   459  				r.Equal(tc.expectedErr, err)
   460  				return
   461  			}
   462  			r.NoError(err)
   463  
   464  			wf := &v1beta1.Application{}
   465  			err = client.Get(ctx, types.NamespacedName{
   466  				Namespace: tc.app.Namespace,
   467  				Name:      tc.app.Name,
   468  			}, wf)
   469  			r.NoError(err)
   470  			r.Equal(false, wf.Status.Workflow.Suspend)
   471  			for _, step := range wf.Status.Workflow.Steps {
   472  				if step.Type == "suspend" {
   473  					r.Equal(step.Phase, workflowv1alpha1.WorkflowStepPhaseRunning)
   474  				}
   475  				for _, sub := range step.SubStepsStatus {
   476  					if sub.Type == "suspend" {
   477  						r.Equal(sub.Phase, workflowv1alpha1.WorkflowStepPhaseRunning)
   478  					}
   479  				}
   480  			}
   481  		})
   482  	}
   483  }
   484  
   485  func TestWorkflowTerminate(t *testing.T) {
   486  	c := initArgs()
   487  	ioStream := cmdutil.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr}
   488  	ctx := context.TODO()
   489  
   490  	testCases := map[string]struct {
   491  		app         *v1beta1.Application
   492  		expectedErr error
   493  	}{
   494  		"no app name specified": {
   495  			expectedErr: fmt.Errorf("please specify the name of application/workflow"),
   496  		},
   497  		"workflow not running": {
   498  			app: &v1beta1.Application{
   499  				ObjectMeta: metav1.ObjectMeta{
   500  					Name:      "workflow-not-running",
   501  					Namespace: "default",
   502  				},
   503  				Spec:   workflowSpec,
   504  				Status: common.AppStatus{},
   505  			},
   506  			expectedErr: fmt.Errorf("the workflow in application workflow-not-running is not start"),
   507  		},
   508  		"terminate successfully": {
   509  			app: &v1beta1.Application{
   510  				ObjectMeta: metav1.ObjectMeta{
   511  					Name:      "workflow",
   512  					Namespace: "test",
   513  				},
   514  				Spec: workflowSpec,
   515  				Status: common.AppStatus{
   516  					Workflow: &common.WorkflowStatus{
   517  						Terminated: false,
   518  						Steps: []workflowv1alpha1.WorkflowStepStatus{
   519  							{
   520  								StepStatus: workflowv1alpha1.StepStatus{
   521  									Name:  "1",
   522  									Type:  "suspend",
   523  									Phase: "succeeded",
   524  								},
   525  							},
   526  							{
   527  								StepStatus: workflowv1alpha1.StepStatus{
   528  									Name:  "2",
   529  									Type:  "suspend",
   530  									Phase: "running",
   531  								},
   532  							},
   533  							{
   534  								StepStatus: workflowv1alpha1.StepStatus{
   535  									Name:  "3",
   536  									Type:  "step-group",
   537  									Phase: "running",
   538  								},
   539  								SubStepsStatus: []workflowv1alpha1.StepStatus{
   540  									{
   541  										Type:  "suspend",
   542  										Phase: "running",
   543  									},
   544  									{
   545  										Type:  "suspend",
   546  										Phase: "succeeded",
   547  									},
   548  								},
   549  							},
   550  						},
   551  					},
   552  				},
   553  			},
   554  		},
   555  	}
   556  
   557  	for name, tc := range testCases {
   558  		t.Run(name, func(t *testing.T) {
   559  			r := require.New(t)
   560  			cmd := NewWorkflowTerminateCommand(c, ioStream, &WorkflowArgs{Args: c, Writer: ioStream.Out})
   561  			initCommand(cmd)
   562  			// clean up the arguments before start
   563  			cmd.SetArgs([]string{})
   564  			client, err := c.GetClient()
   565  			r.NoError(err)
   566  			if tc.app != nil {
   567  				err := client.Create(ctx, tc.app)
   568  				r.NoError(err)
   569  
   570  				if tc.app.Namespace != corev1.NamespaceDefault {
   571  					err := client.Create(ctx, &corev1.Namespace{
   572  						ObjectMeta: metav1.ObjectMeta{
   573  							Name: tc.app.Namespace,
   574  						},
   575  					})
   576  					r.NoError(err)
   577  					cmd.SetArgs([]string{tc.app.Name, "-n", tc.app.Namespace})
   578  				} else {
   579  					cmd.SetArgs([]string{tc.app.Name})
   580  				}
   581  			}
   582  			err = cmd.Execute()
   583  			if tc.expectedErr != nil {
   584  				r.Equal(tc.expectedErr, err)
   585  				return
   586  			}
   587  			r.NoError(err)
   588  
   589  			wf := &v1beta1.Application{}
   590  			err = client.Get(ctx, types.NamespacedName{
   591  				Namespace: tc.app.Namespace,
   592  				Name:      tc.app.Name,
   593  			}, wf)
   594  			r.NoError(err)
   595  			r.Equal(true, wf.Status.Workflow.Terminated)
   596  			for _, step := range wf.Status.Workflow.Steps {
   597  				if step.Phase != workflowv1alpha1.WorkflowStepPhaseSucceeded {
   598  					r.Equal(step.Phase, workflowv1alpha1.WorkflowStepPhaseFailed)
   599  					r.Equal(step.Reason, wfTypes.StatusReasonTerminate)
   600  				}
   601  				for _, sub := range step.SubStepsStatus {
   602  					if sub.Phase != workflowv1alpha1.WorkflowStepPhaseSucceeded {
   603  						r.Equal(sub.Phase, workflowv1alpha1.WorkflowStepPhaseFailed)
   604  						r.Equal(sub.Reason, wfTypes.StatusReasonTerminate)
   605  					}
   606  				}
   607  			}
   608  		})
   609  	}
   610  }
   611  
   612  func TestWorkflowRestart(t *testing.T) {
   613  	c := initArgs()
   614  	ioStream := cmdutil.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr}
   615  	ctx := context.TODO()
   616  
   617  	testCases := map[string]struct {
   618  		app         *v1beta1.Application
   619  		expectedErr error
   620  	}{
   621  		"no app name specified": {
   622  			expectedErr: fmt.Errorf("please specify the name of application/workflow"),
   623  		},
   624  		"workflow not running": {
   625  			app: &v1beta1.Application{
   626  				ObjectMeta: metav1.ObjectMeta{
   627  					Name:      "workflow-not-running",
   628  					Namespace: "default",
   629  				},
   630  				Spec:   workflowSpec,
   631  				Status: common.AppStatus{},
   632  			},
   633  			expectedErr: fmt.Errorf("the workflow in application workflow-not-running is not start"),
   634  		},
   635  		"restart successfully": {
   636  			app: &v1beta1.Application{
   637  				ObjectMeta: metav1.ObjectMeta{
   638  					Name:      "workflow",
   639  					Namespace: "test",
   640  				},
   641  				Spec: workflowSpec,
   642  				Status: common.AppStatus{
   643  					Workflow: &common.WorkflowStatus{
   644  						Terminated: true,
   645  					},
   646  				},
   647  			},
   648  		},
   649  	}
   650  
   651  	for name, tc := range testCases {
   652  		t.Run(name, func(t *testing.T) {
   653  			r := require.New(t)
   654  			cmd := NewWorkflowRestartCommand(c, ioStream, &WorkflowArgs{Args: c, Writer: ioStream.Out})
   655  			initCommand(cmd)
   656  			// clean up the arguments before start
   657  			cmd.SetArgs([]string{})
   658  			client, err := c.GetClient()
   659  			r.NoError(err)
   660  			if tc.app != nil {
   661  				err := client.Create(ctx, tc.app)
   662  				r.NoError(err)
   663  
   664  				if tc.app.Namespace != corev1.NamespaceDefault {
   665  					err := client.Create(ctx, &corev1.Namespace{
   666  						ObjectMeta: metav1.ObjectMeta{
   667  							Name: tc.app.Namespace,
   668  						},
   669  					})
   670  					r.NoError(err)
   671  					cmd.SetArgs([]string{tc.app.Name, "-n", tc.app.Namespace})
   672  				} else {
   673  					cmd.SetArgs([]string{tc.app.Name})
   674  				}
   675  			}
   676  			err = cmd.Execute()
   677  			if tc.expectedErr != nil {
   678  				r.Equal(tc.expectedErr, err)
   679  				return
   680  			}
   681  			r.NoError(err)
   682  
   683  			wf := &v1beta1.Application{}
   684  			err = client.Get(ctx, types.NamespacedName{
   685  				Namespace: tc.app.Namespace,
   686  				Name:      tc.app.Name,
   687  			}, wf)
   688  			r.NoError(err)
   689  			var nilStatus *common.WorkflowStatus = nil
   690  			r.Equal(nilStatus, wf.Status.Workflow)
   691  		})
   692  	}
   693  }
   694  
   695  func TestWorkflowRollback(t *testing.T) {
   696  	c := initArgs()
   697  	ioStream := cmdutil.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr}
   698  	ctx := context.TODO()
   699  
   700  	testCases := map[string]struct {
   701  		app         *v1beta1.Application
   702  		revision    *v1beta1.ApplicationRevision
   703  		expectedErr error
   704  	}{
   705  		"no app name specified": {
   706  			expectedErr: fmt.Errorf("please specify the name of application/workflow"),
   707  		},
   708  		"workflow running": {
   709  			app: &v1beta1.Application{
   710  				ObjectMeta: metav1.ObjectMeta{
   711  					Name:      "workflow-running",
   712  					Namespace: "default",
   713  				},
   714  				Spec: workflowSpec,
   715  				Status: common.AppStatus{
   716  					Workflow: &common.WorkflowStatus{
   717  						Suspend:    false,
   718  						Terminated: false,
   719  						Finished:   false,
   720  					},
   721  				},
   722  			},
   723  			expectedErr: fmt.Errorf("can not rollback a running workflow"),
   724  		},
   725  		"invalid revision": {
   726  			app: &v1beta1.Application{
   727  				ObjectMeta: metav1.ObjectMeta{
   728  					Name:      "invalid-revision",
   729  					Namespace: "default",
   730  				},
   731  				Spec: workflowSpec,
   732  				Status: common.AppStatus{
   733  					Workflow: &common.WorkflowStatus{
   734  						Suspend: true,
   735  					},
   736  				},
   737  			},
   738  			expectedErr: fmt.Errorf("the latest revision is not set: invalid-revision"),
   739  		},
   740  		"rollback successfully": {
   741  			app: &v1beta1.Application{
   742  				ObjectMeta: metav1.ObjectMeta{
   743  					Name:      "workflow",
   744  					Namespace: "test",
   745  				},
   746  				Spec: workflowSpec,
   747  				Status: common.AppStatus{
   748  					LatestRevision: &common.Revision{
   749  						Name: "revision-v1",
   750  					},
   751  					Workflow: &common.WorkflowStatus{
   752  						Terminated: true,
   753  					},
   754  				},
   755  			},
   756  			revision: &v1beta1.ApplicationRevision{
   757  				ObjectMeta: metav1.ObjectMeta{
   758  					Name:      "revision-v1",
   759  					Namespace: "test",
   760  				},
   761  				Spec: v1beta1.ApplicationRevisionSpec{
   762  					ApplicationRevisionCompressibleFields: v1beta1.ApplicationRevisionCompressibleFields{
   763  						Application: v1beta1.Application{
   764  							Spec: v1beta1.ApplicationSpec{
   765  								Components: []common.ApplicationComponent{{
   766  									Name:       "revision-component",
   767  									Type:       "worker",
   768  									Properties: &runtime.RawExtension{Raw: []byte(`{"cmd":["sleep","1000"],"image":"busybox"}`)},
   769  								}},
   770  							},
   771  						},
   772  					},
   773  				},
   774  			},
   775  		},
   776  	}
   777  
   778  	for name, tc := range testCases {
   779  		t.Run(name, func(t *testing.T) {
   780  			r := require.New(t)
   781  			cmd := NewWorkflowRollbackCommand(c, ioStream, &WorkflowArgs{Args: c, Writer: ioStream.Out})
   782  			initCommand(cmd)
   783  			// clean up the arguments before start
   784  			cmd.SetArgs([]string{})
   785  			client, err := c.GetClient()
   786  			r.NoError(err)
   787  			if tc.app != nil {
   788  				err := client.Create(ctx, tc.app)
   789  				r.NoError(err)
   790  
   791  				if tc.app.Namespace != corev1.NamespaceDefault {
   792  					err := client.Create(ctx, &corev1.Namespace{
   793  						ObjectMeta: metav1.ObjectMeta{
   794  							Name: tc.app.Namespace,
   795  						},
   796  					})
   797  					r.NoError(err)
   798  					cmd.SetArgs([]string{tc.app.Name, "-n", tc.app.Namespace})
   799  				} else {
   800  					cmd.SetArgs([]string{tc.app.Name})
   801  				}
   802  			}
   803  			if tc.revision != nil {
   804  				err := client.Create(ctx, tc.revision)
   805  				r.NoError(err)
   806  			}
   807  			err = cmd.Execute()
   808  			if tc.expectedErr != nil {
   809  				r.Equal(tc.expectedErr, err)
   810  				return
   811  			}
   812  			r.NoError(err)
   813  
   814  			wf := &v1beta1.Application{}
   815  			err = client.Get(ctx, types.NamespacedName{
   816  				Namespace: tc.app.Namespace,
   817  				Name:      tc.app.Name,
   818  			}, wf)
   819  			r.NoError(err)
   820  			r.Equal(wf.Spec.Components[0].Name, "revision-component")
   821  		})
   822  	}
   823  }
   824  
   825  func TestWorkflowList(t *testing.T) {
   826  	c := initArgs()
   827  	buf := new(bytes.Buffer)
   828  	ioStream := cmdutil.IOStreams{In: os.Stdin, Out: buf, ErrOut: os.Stderr}
   829  	ctx := context.TODO()
   830  	testCases := map[string]struct {
   831  		workflows         []interface{}
   832  		apps              []*v1beta1.Application
   833  		workflowRuns      []*workflowv1alpha1.WorkflowRun
   834  		expectedErr       error
   835  		namespace         string
   836  		expectAppListSize int
   837  	}{
   838  		"specified all namespaces flag": {
   839  			workflows: []interface{}{
   840  				&v1beta1.Application{
   841  					ObjectMeta: metav1.ObjectMeta{
   842  						Name:      "app-workflow",
   843  						Namespace: "some-ns",
   844  					},
   845  					Spec: workflowSpec,
   846  					Status: common.AppStatus{
   847  						LatestRevision: &common.Revision{
   848  							Name: "revision-v1",
   849  						},
   850  						Workflow: &common.WorkflowStatus{
   851  							Terminated: true,
   852  							Phase:      workflowv1alpha1.WorkflowStateInitializing,
   853  							StartTime:  metav1.NewTime(time.Now().Add(-10 * time.Minute)),
   854  							EndTime:    metav1.NewTime(time.Now()),
   855  						},
   856  					},
   857  				},
   858  				&workflowv1alpha1.WorkflowRun{
   859  					ObjectMeta: metav1.ObjectMeta{
   860  						Name:      "workflow-run",
   861  						Namespace: "some-ns",
   862  					},
   863  					Status: workflowv1alpha1.WorkflowRunStatus{
   864  						Phase:     workflowv1alpha1.WorkflowStateExecuting,
   865  						StartTime: metav1.NewTime(time.Now().Add(-10 * time.Minute)),
   866  						EndTime:   metav1.NewTime(time.Now()),
   867  					},
   868  				},
   869  			},
   870  			expectedErr: nil,
   871  		},
   872  		"specified namespace flag": {
   873  			workflows: []interface{}{
   874  				&workflowv1alpha1.WorkflowRun{
   875  					ObjectMeta: metav1.ObjectMeta{
   876  						Name:      "workflow-run-1",
   877  						Namespace: "test",
   878  					},
   879  					Status: workflowv1alpha1.WorkflowRunStatus{
   880  						Phase:     workflowv1alpha1.WorkflowStateExecuting,
   881  						StartTime: metav1.NewTime(time.Now().Add(-10 * time.Minute)),
   882  						EndTime:   metav1.NewTime(time.Now()),
   883  					},
   884  				},
   885  			},
   886  			namespace:   "test",
   887  			expectedErr: nil,
   888  		},
   889  	}
   890  	for name, tc := range testCases {
   891  		t.Run(name, func(t *testing.T) {
   892  			r := require.New(t)
   893  			buf.Reset()
   894  			cmd := NewWorkflowListCommand(c, ioStream, &WorkflowArgs{Args: c, Writer: ioStream.Out})
   895  			initCommand(cmd)
   896  			// clean up the arguments before start
   897  			cmd.SetArgs([]string{})
   898  			client, err := c.GetClient()
   899  			r.NoError(err)
   900  			if tc.workflows != nil && len(tc.workflows) > 0 {
   901  				for _, w := range tc.workflows {
   902  					if workflow, ok := w.(*workflowv1alpha1.WorkflowRun); ok {
   903  						err := client.Create(ctx, workflow)
   904  						r.NoError(err)
   905  					} else if app, ok := w.(*v1beta1.Application); ok {
   906  						err := client.Create(ctx, app)
   907  						r.NoError(err)
   908  					}
   909  				}
   910  			}
   911  			args := []string{}
   912  			if tc.namespace == "" {
   913  				args = append(args, "-A")
   914  			} else {
   915  				args = append(args, "-n", tc.namespace)
   916  			}
   917  
   918  			cmd.SetArgs(args)
   919  			err = cmd.Execute()
   920  
   921  			lines := strings.Split(strings.TrimSpace(buf.String()), "\n")
   922  
   923  			headerFields := strings.Fields(lines[0])
   924  			if tc.namespace == "" {
   925  				r.Equal(headerFields[0], "NAMESPACE")
   926  
   927  			} else {
   928  				r.Equal(headerFields[0], "NAME")
   929  			}
   930  			offset := 0
   931  			for i, line := range lines[1:] {
   932  				fields := strings.Fields(line)
   933  				if workflow, ok := tc.workflows[i].(*workflowv1alpha1.WorkflowRun); ok {
   934  					if tc.namespace == "" {
   935  						r.Equal(fields[0], workflow.Namespace)
   936  						offset = offset + 1
   937  					} else if tc.namespace == workflow.Namespace {
   938  						r.Equal(fields[0+offset], workflow.Name)
   939  						r.Equal(fields[1+offset], "WorkflowRun")
   940  						r.Equal(fields[2+offset], string(workflow.Status.Phase))
   941  						r.Equal(fields[3+offset], workflow.Status.StartTime.Format("2006-01-02"))
   942  						r.Equal(fields[4+offset], workflow.Status.StartTime.Format("15:04:05"))
   943  						//skipping a few fields due to the format including  timezone information
   944  						r.Equal(fields[7+offset], workflow.Status.EndTime.Format("2006-01-02"))
   945  						r.Equal(fields[8+offset], workflow.Status.EndTime.Format("15:04:05"))
   946  					}
   947  				} else if app, ok := tc.workflows[i].(*v1beta1.Application); ok {
   948  					offset = 0
   949  					if tc.namespace == "" {
   950  						r.Equal(fields[0+offset], app.Namespace)
   951  						offset = offset + 1
   952  					} else if tc.namespace == app.Namespace {
   953  						r.Equal(fields[0+offset], app.Name)
   954  						r.Equal(fields[1+offset], "Application")
   955  						r.Equal(fields[2+offset], string(app.Status.Workflow.Phase))
   956  						r.Equal(fields[3+offset], app.Status.Workflow.StartTime.Format("2006-01-02"))
   957  						r.Equal(fields[4+offset], app.Status.Workflow.StartTime.Format("15:04:05"))
   958  						//skipping a few fields due to the format including  timezone information
   959  						r.Equal(fields[7+offset], app.Status.Workflow.EndTime.Format("2006-01-02"))
   960  						r.Equal(fields[8+offset], app.Status.Workflow.EndTime.Format("15:04:05"))
   961  					}
   962  				}
   963  
   964  			}
   965  
   966  			if tc.expectedErr != nil {
   967  				r.Equal(tc.expectedErr, err)
   968  				return
   969  			}
   970  			r.NoError(err)
   971  
   972  		})
   973  	}
   974  }