github.com/kubevela/workflow@v0.6.0/pkg/tasks/custom/task_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 custom
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"fmt"
    23  	"testing"
    24  
    25  	"github.com/crossplane/crossplane-runtime/pkg/test"
    26  	monitorContext "github.com/kubevela/pkg/monitor/context"
    27  	"github.com/pkg/errors"
    28  	"github.com/stretchr/testify/require"
    29  	corev1 "k8s.io/api/core/v1"
    30  	"k8s.io/apimachinery/pkg/runtime"
    31  	"sigs.k8s.io/controller-runtime/pkg/client"
    32  	"sigs.k8s.io/yaml"
    33  
    34  	"github.com/kubevela/workflow/api/v1alpha1"
    35  	wfContext "github.com/kubevela/workflow/pkg/context"
    36  	"github.com/kubevela/workflow/pkg/cue/model/value"
    37  	"github.com/kubevela/workflow/pkg/cue/process"
    38  	"github.com/kubevela/workflow/pkg/providers"
    39  	"github.com/kubevela/workflow/pkg/types"
    40  )
    41  
    42  func TestTaskLoader(t *testing.T) {
    43  	wfCtx := newWorkflowContextForTest(t)
    44  	r := require.New(t)
    45  	discover := providers.NewProviders()
    46  	discover.Register("test", map[string]types.Handler{
    47  		"output": func(mCtx monitorContext.Context, ctx wfContext.Context, v *value.Value, act types.Action) error {
    48  			ip, _ := v.MakeValue(`
    49  myIP: value: "1.1.1.1"            
    50  `)
    51  			return v.FillObject(ip)
    52  		},
    53  		"input": func(mCtx monitorContext.Context, ctx wfContext.Context, v *value.Value, act types.Action) error {
    54  			val, err := v.LookupValue("set", "prefixIP")
    55  			r.NoError(err)
    56  			str, err := val.CueValue().String()
    57  			r.NoError(err)
    58  			r.Equal(str, "1.1.1.1")
    59  			return nil
    60  		},
    61  		"wait": func(mCtx monitorContext.Context, ctx wfContext.Context, v *value.Value, act types.Action) error {
    62  			act.Wait("I am waiting")
    63  			return nil
    64  		},
    65  		"terminate": func(mCtx monitorContext.Context, ctx wfContext.Context, v *value.Value, act types.Action) error {
    66  			act.Terminate("I am terminated")
    67  			return nil
    68  		},
    69  		"suspend": func(mCtx monitorContext.Context, ctx wfContext.Context, v *value.Value, act types.Action) error {
    70  			act.Terminate("I am suspended")
    71  			return nil
    72  		},
    73  		"resume": func(mCtx monitorContext.Context, ctx wfContext.Context, v *value.Value, act types.Action) error {
    74  			act.Terminate("I am resumed")
    75  			return nil
    76  		},
    77  		"executeFailed": func(mCtx monitorContext.Context, ctx wfContext.Context, v *value.Value, act types.Action) error {
    78  			return errors.New("execute error")
    79  		},
    80  		"ok": func(mCtx monitorContext.Context, ctx wfContext.Context, v *value.Value, act types.Action) error {
    81  			return nil
    82  		},
    83  	})
    84  
    85  	pCtx := process.NewContext(process.ContextData{
    86  		Name:      "app",
    87  		Namespace: "default",
    88  	})
    89  	tasksLoader := NewTaskLoader(mockLoadTemplate, nil, discover, 0, pCtx)
    90  
    91  	steps := []v1alpha1.WorkflowStep{
    92  		{
    93  			WorkflowStepBase: v1alpha1.WorkflowStepBase{
    94  				Name: "output",
    95  				Type: "output",
    96  				Outputs: v1alpha1.StepOutputs{{
    97  					ValueFrom: "myIP.value",
    98  					Name:      "podIP",
    99  				}},
   100  			},
   101  		},
   102  		{
   103  			WorkflowStepBase: v1alpha1.WorkflowStepBase{
   104  				Name: "input",
   105  				Type: "input",
   106  				Inputs: v1alpha1.StepInputs{{
   107  					From:         "podIP",
   108  					ParameterKey: "set.prefixIP",
   109  				}},
   110  			},
   111  		},
   112  		{
   113  			WorkflowStepBase: v1alpha1.WorkflowStepBase{
   114  				Name: "wait",
   115  				Type: "wait",
   116  			},
   117  		},
   118  		{
   119  			WorkflowStepBase: v1alpha1.WorkflowStepBase{
   120  				Name: "terminate",
   121  				Type: "terminate",
   122  			},
   123  		},
   124  		{
   125  			WorkflowStepBase: v1alpha1.WorkflowStepBase{
   126  				Name: "template",
   127  				Type: "templateError",
   128  			},
   129  		},
   130  		{
   131  			WorkflowStepBase: v1alpha1.WorkflowStepBase{
   132  				Name: "execute",
   133  				Type: "executeFailed",
   134  			},
   135  		},
   136  		{
   137  			WorkflowStepBase: v1alpha1.WorkflowStepBase{
   138  				Name: "steps",
   139  				Type: "steps",
   140  			},
   141  		},
   142  	}
   143  
   144  	for _, step := range steps {
   145  		gen, err := tasksLoader.GetTaskGenerator(context.Background(), step.Type)
   146  		r.NoError(err)
   147  		run, err := gen(step, &types.TaskGeneratorOptions{})
   148  		r.NoError(err)
   149  		status, action, err := run.Run(wfCtx, &types.TaskRunOptions{})
   150  		r.NoError(err)
   151  		if step.Name == "wait" {
   152  			r.Equal(status.Phase, v1alpha1.WorkflowStepPhaseRunning)
   153  			r.Equal(status.Reason, types.StatusReasonWait)
   154  			r.Equal(status.Message, "I am waiting")
   155  			continue
   156  		}
   157  		if step.Name == "terminate" {
   158  			r.Equal(action.Terminated, true)
   159  			r.Equal(status.Reason, types.StatusReasonTerminate)
   160  			r.Equal(status.Message, "I am terminated")
   161  			continue
   162  		}
   163  		if step.Name == "template" {
   164  			r.Equal(status.Phase, v1alpha1.WorkflowStepPhaseFailed)
   165  			r.Equal(status.Reason, types.StatusReasonExecute)
   166  			continue
   167  		}
   168  		if step.Name == "execute" {
   169  			r.Equal(status.Phase, v1alpha1.WorkflowStepPhaseFailed)
   170  			r.Equal(status.Reason, types.StatusReasonExecute)
   171  			continue
   172  		}
   173  		r.Equal(status.Phase, v1alpha1.WorkflowStepPhaseSucceeded)
   174  	}
   175  
   176  }
   177  
   178  func TestErrCases(t *testing.T) {
   179  	wfCtx := newWorkflowContextForTest(t)
   180  	r := require.New(t)
   181  	closeVar, err := value.NewValue(`
   182  close({
   183     x: 100
   184  })
   185  `, nil, "", value.TagFieldOrder)
   186  	r.NoError(err)
   187  	err = wfCtx.SetVar(closeVar, "score")
   188  	r.NoError(err)
   189  	discover := providers.NewProviders()
   190  	discover.Register("test", map[string]types.Handler{
   191  		"input": func(mCtx monitorContext.Context, ctx wfContext.Context, v *value.Value, act types.Action) error {
   192  			val, err := v.LookupValue("prefixIP")
   193  			r.NoError(err)
   194  			str, err := val.CueValue().String()
   195  			r.NoError(err)
   196  			r.Equal(str, "1.1.1.1")
   197  			return nil
   198  		},
   199  		"ok": func(mCtx monitorContext.Context, ctx wfContext.Context, v *value.Value, act types.Action) error {
   200  			return nil
   201  		},
   202  		"error": func(mCtx monitorContext.Context, ctx wfContext.Context, v *value.Value, act types.Action) error {
   203  			return errors.New("mock error")
   204  		},
   205  	})
   206  	pCtx := process.NewContext(process.ContextData{
   207  		Name:      "app",
   208  		Namespace: "default",
   209  	})
   210  	tasksLoader := NewTaskLoader(mockLoadTemplate, nil, discover, 0, pCtx)
   211  
   212  	steps := []v1alpha1.WorkflowStep{
   213  		{
   214  			WorkflowStepBase: v1alpha1.WorkflowStepBase{
   215  				Name: "input-replace",
   216  				Type: "ok",
   217  				Properties: &runtime.RawExtension{Raw: []byte(`
   218  {"score": {"x": 101}}
   219  		`)},
   220  				Inputs: v1alpha1.StepInputs{{
   221  					From:         "score",
   222  					ParameterKey: "score",
   223  				}},
   224  			},
   225  		},
   226  		{
   227  			WorkflowStepBase: v1alpha1.WorkflowStepBase{
   228  				Name: "input",
   229  				Type: "input",
   230  				Inputs: v1alpha1.StepInputs{{
   231  					From:         "podIP",
   232  					ParameterKey: "prefixIP",
   233  				}},
   234  			},
   235  		},
   236  		{
   237  			WorkflowStepBase: v1alpha1.WorkflowStepBase{
   238  				Name: "output-var-conflict",
   239  				Type: "ok",
   240  				Outputs: v1alpha1.StepOutputs{{
   241  					Name:      "score",
   242  					ValueFrom: "name",
   243  				}},
   244  			},
   245  		},
   246  		{
   247  			WorkflowStepBase: v1alpha1.WorkflowStepBase{
   248  				Name: "wait",
   249  				Type: "wait",
   250  			},
   251  		},
   252  		{
   253  			WorkflowStepBase: v1alpha1.WorkflowStepBase{
   254  				Name: "err",
   255  				Type: "error",
   256  			},
   257  		},
   258  		{
   259  			WorkflowStepBase: v1alpha1.WorkflowStepBase{
   260  				Name: "failed-after-retries",
   261  				Type: "error",
   262  			},
   263  		},
   264  	}
   265  	for _, step := range steps {
   266  		gen, err := tasksLoader.GetTaskGenerator(context.Background(), step.Type)
   267  		r.NoError(err)
   268  		run, err := gen(step, &types.TaskGeneratorOptions{})
   269  		r.NoError(err)
   270  		status, operation, _ := run.Run(wfCtx, &types.TaskRunOptions{})
   271  		switch step.Name {
   272  		case "input-replace":
   273  			r.Equal(status.Message, "")
   274  			r.Equal(operation.Waiting, false)
   275  			r.Equal(status.Phase, v1alpha1.WorkflowStepPhaseSucceeded)
   276  			r.Equal(status.Reason, "")
   277  		case "input":
   278  			r.Equal(status.Message, "get input from [podIP]: failed to lookup value: var(path=podIP) not exist")
   279  			r.Equal(operation.Waiting, false)
   280  			r.Equal(status.Phase, v1alpha1.WorkflowStepPhaseFailed)
   281  			r.Equal(status.Reason, types.StatusReasonInput)
   282  		case "output-var-conflict":
   283  			r.Contains(status.Message, "conflict")
   284  			r.Equal(operation.Waiting, false)
   285  			r.Equal(status.Phase, v1alpha1.WorkflowStepPhaseSucceeded)
   286  		case "failed-after-retries":
   287  			wfContext.CleanupMemoryStore("app-v1", "default")
   288  			newCtx := newWorkflowContextForTest(t)
   289  			for i := 0; i < types.MaxWorkflowStepErrorRetryTimes; i++ {
   290  				status, operation, err = run.Run(newCtx, &types.TaskRunOptions{})
   291  				r.NoError(err)
   292  				r.Equal(operation.Waiting, true)
   293  				r.Equal(operation.FailedAfterRetries, false)
   294  				r.Equal(status.Phase, v1alpha1.WorkflowStepPhaseFailed)
   295  			}
   296  			status, operation, err = run.Run(newCtx, &types.TaskRunOptions{})
   297  			r.NoError(err)
   298  			r.Equal(operation.Waiting, false)
   299  			r.Equal(operation.FailedAfterRetries, true)
   300  			r.Equal(status.Phase, v1alpha1.WorkflowStepPhaseFailed)
   301  			r.Equal(status.Reason, types.StatusReasonFailedAfterRetries)
   302  		default:
   303  			r.Equal(operation.Waiting, true)
   304  			r.Equal(status.Phase, v1alpha1.WorkflowStepPhaseFailed)
   305  		}
   306  	}
   307  }
   308  
   309  func TestSteps(t *testing.T) {
   310  
   311  	var (
   312  		echo    string
   313  		mockErr = errors.New("mock error")
   314  	)
   315  
   316  	wfCtx := newWorkflowContextForTest(t)
   317  	r := require.New(t)
   318  	discover := providers.NewProviders()
   319  	discover.Register("test", map[string]types.Handler{
   320  		"ok": func(mCtx monitorContext.Context, ctx wfContext.Context, v *value.Value, act types.Action) error {
   321  			echo = echo + "ok"
   322  			return nil
   323  		},
   324  		"error": func(mCtx monitorContext.Context, ctx wfContext.Context, v *value.Value, act types.Action) error {
   325  			return mockErr
   326  		},
   327  	})
   328  	exec := &executor{
   329  		handlers: discover,
   330  	}
   331  
   332  	testCases := []struct {
   333  		base     string
   334  		expected string
   335  		hasErr   bool
   336  	}{
   337  		{
   338  			base: `
   339  process: {
   340  	#provider: "test"
   341  	#do: "ok"
   342  }
   343  
   344  #up: [process]
   345  `,
   346  			expected: "okok",
   347  		},
   348  		{
   349  			base: `
   350  process: {
   351  	#provider: "test"
   352  	#do: "ok"
   353  }
   354  
   355  #up: [process,{
   356    #do: "steps"
   357    p1: process
   358    #up: [process]
   359  }]
   360  `,
   361  			expected: "okokokok",
   362  		},
   363  		{
   364  			base: `
   365  process: {
   366  	#provider: "test"
   367  	#do: "ok"
   368  }
   369  
   370  #up: [process,{
   371    p1: process
   372    #up: [process]
   373  }]
   374  `,
   375  			expected: "okok",
   376  		},
   377  		{
   378  			base: `
   379  process: {
   380  	#provider: "test"
   381  	#do: "ok"
   382  }
   383  
   384  #up: [process,{
   385    #do: "steps"
   386    err: {
   387      #provider: "test"
   388  	#do: "error"
   389    } @step(1)
   390    #up: [{},process] @step(2)
   391  }]
   392  `,
   393  			expected: "okok",
   394  			hasErr:   true,
   395  		},
   396  
   397  		{
   398  			base: `
   399  	#provider: "test"
   400  	#do: "ok"
   401  `,
   402  			expected: "ok",
   403  		},
   404  		{
   405  			base: `
   406  process: {
   407  	#provider: "test"
   408  	#do: "ok"
   409      err: true
   410  }
   411  
   412  if process.err {
   413    err: {
   414      #provider: "test"
   415  	  #do: "error"
   416    }
   417  }
   418  
   419  apply: {
   420  	#provider: "test"
   421  	#do: "ok"
   422  }
   423  
   424  #up: [process,{}]
   425  `,
   426  			expected: "ok",
   427  			hasErr:   true,
   428  		},
   429  	}
   430  
   431  	for i, tc := range testCases {
   432  		echo = ""
   433  		v, err := value.NewValue(tc.base, nil, "", value.TagFieldOrder)
   434  		r.NoError(err)
   435  		err = exec.doSteps(nil, wfCtx, v)
   436  		r.Equal(err != nil, tc.hasErr)
   437  		r.Equal(tc.expected, echo, i)
   438  	}
   439  
   440  }
   441  
   442  func TestPendingInputCheck(t *testing.T) {
   443  	wfCtx := newWorkflowContextForTest(t)
   444  	r := require.New(t)
   445  	discover := providers.NewProviders()
   446  	discover.Register("test", map[string]types.Handler{
   447  		"ok": func(mCtx monitorContext.Context, ctx wfContext.Context, v *value.Value, act types.Action) error {
   448  			return nil
   449  		},
   450  	})
   451  	step := v1alpha1.WorkflowStep{
   452  		WorkflowStepBase: v1alpha1.WorkflowStepBase{
   453  			Name: "pending",
   454  			Type: "ok",
   455  			Inputs: v1alpha1.StepInputs{{
   456  				From:         "score",
   457  				ParameterKey: "score",
   458  			}},
   459  		},
   460  	}
   461  	pCtx := process.NewContext(process.ContextData{
   462  		Name:      "app",
   463  		Namespace: "default",
   464  	})
   465  	tasksLoader := NewTaskLoader(mockLoadTemplate, nil, discover, 0, pCtx)
   466  	gen, err := tasksLoader.GetTaskGenerator(context.Background(), step.Type)
   467  	r.NoError(err)
   468  	run, err := gen(step, &types.TaskGeneratorOptions{})
   469  	r.NoError(err)
   470  	logCtx := monitorContext.NewTraceContext(context.Background(), "test-app")
   471  	p, _ := run.Pending(logCtx, wfCtx, nil)
   472  	r.Equal(p, true)
   473  	score, err := value.NewValue(`
   474  100
   475  `, nil, "")
   476  	r.NoError(err)
   477  	err = wfCtx.SetVar(score, "score")
   478  	r.NoError(err)
   479  	p, _ = run.Pending(logCtx, wfCtx, nil)
   480  	r.Equal(p, false)
   481  }
   482  
   483  func TestPendingDependsOnCheck(t *testing.T) {
   484  	wfCtx := newWorkflowContextForTest(t)
   485  	r := require.New(t)
   486  	discover := providers.NewProviders()
   487  	discover.Register("test", map[string]types.Handler{
   488  		"ok": func(mCtx monitorContext.Context, ctx wfContext.Context, v *value.Value, act types.Action) error {
   489  			return nil
   490  		},
   491  	})
   492  	step := v1alpha1.WorkflowStep{
   493  		WorkflowStepBase: v1alpha1.WorkflowStepBase{
   494  			Name:      "pending",
   495  			Type:      "ok",
   496  			DependsOn: []string{"depend"},
   497  		},
   498  	}
   499  	pCtx := process.NewContext(process.ContextData{
   500  		Name:      "app",
   501  		Namespace: "default",
   502  	})
   503  	tasksLoader := NewTaskLoader(mockLoadTemplate, nil, discover, 0, pCtx)
   504  	gen, err := tasksLoader.GetTaskGenerator(context.Background(), step.Type)
   505  	r.NoError(err)
   506  	run, err := gen(step, &types.TaskGeneratorOptions{})
   507  	r.NoError(err)
   508  	logCtx := monitorContext.NewTraceContext(context.Background(), "test-app")
   509  	p, _ := run.Pending(logCtx, wfCtx, nil)
   510  	r.Equal(p, true)
   511  	ss := map[string]v1alpha1.StepStatus{
   512  		"depend": {
   513  			Phase: v1alpha1.WorkflowStepPhaseSucceeded,
   514  		},
   515  	}
   516  	p, _ = run.Pending(logCtx, wfCtx, ss)
   517  	r.Equal(p, false)
   518  }
   519  
   520  func TestSkip(t *testing.T) {
   521  	r := require.New(t)
   522  	discover := providers.NewProviders()
   523  	discover.Register("test", map[string]types.Handler{
   524  		"ok": func(mCtx monitorContext.Context, ctx wfContext.Context, v *value.Value, act types.Action) error {
   525  			return nil
   526  		},
   527  	})
   528  	step := v1alpha1.WorkflowStep{
   529  		WorkflowStepBase: v1alpha1.WorkflowStepBase{
   530  			Name: "skip",
   531  			Type: "ok",
   532  		},
   533  	}
   534  	pCtx := process.NewContext(process.ContextData{
   535  		Name:      "app",
   536  		Namespace: "default",
   537  	})
   538  	tasksLoader := NewTaskLoader(mockLoadTemplate, nil, discover, 0, pCtx)
   539  	gen, err := tasksLoader.GetTaskGenerator(context.Background(), step.Type)
   540  	r.NoError(err)
   541  	runner, err := gen(step, &types.TaskGeneratorOptions{})
   542  	r.NoError(err)
   543  	wfCtx := newWorkflowContextForTest(t)
   544  	status, operations, err := runner.Run(wfCtx, &types.TaskRunOptions{
   545  		PreCheckHooks: []types.TaskPreCheckHook{
   546  			func(step v1alpha1.WorkflowStep, options *types.PreCheckOptions) (*types.PreCheckResult, error) {
   547  				return &types.PreCheckResult{Skip: true}, nil
   548  			},
   549  		},
   550  	})
   551  	r.NoError(err)
   552  	r.Equal(status.Phase, v1alpha1.WorkflowStepPhaseSkipped)
   553  	r.Equal(status.Reason, types.StatusReasonSkip)
   554  	r.Equal(operations.Skip, true)
   555  }
   556  
   557  func TestTimeout(t *testing.T) {
   558  	r := require.New(t)
   559  	discover := providers.NewProviders()
   560  	discover.Register("test", map[string]types.Handler{
   561  		"ok": func(mCtx monitorContext.Context, ctx wfContext.Context, v *value.Value, act types.Action) error {
   562  			return nil
   563  		},
   564  	})
   565  	step := v1alpha1.WorkflowStep{
   566  		WorkflowStepBase: v1alpha1.WorkflowStepBase{
   567  			Name: "timeout",
   568  			Type: "ok",
   569  		},
   570  	}
   571  	pCtx := process.NewContext(process.ContextData{
   572  		Name:      "app",
   573  		Namespace: "default",
   574  	})
   575  	tasksLoader := NewTaskLoader(mockLoadTemplate, nil, discover, 0, pCtx)
   576  	gen, err := tasksLoader.GetTaskGenerator(context.Background(), step.Type)
   577  	r.NoError(err)
   578  	runner, err := gen(step, &types.TaskGeneratorOptions{})
   579  	r.NoError(err)
   580  	ctx := newWorkflowContextForTest(t)
   581  	status, _, err := runner.Run(ctx, &types.TaskRunOptions{
   582  		PreCheckHooks: []types.TaskPreCheckHook{
   583  			func(step v1alpha1.WorkflowStep, options *types.PreCheckOptions) (*types.PreCheckResult, error) {
   584  				return &types.PreCheckResult{Timeout: true}, nil
   585  			},
   586  		},
   587  	})
   588  	r.NoError(err)
   589  	r.Equal(status.Phase, v1alpha1.WorkflowStepPhaseFailed)
   590  	r.Equal(status.Reason, types.StatusReasonTimeout)
   591  }
   592  
   593  func TestValidateIfValue(t *testing.T) {
   594  	ctx := newWorkflowContextForTest(t)
   595  	pCtx := process.NewContext(process.ContextData{
   596  		Name:      "app",
   597  		Namespace: "default",
   598  		Data:      map[string]interface{}{"arr": []string{"a", "b"}},
   599  	})
   600  	basicVal, basicTemplate, err := MakeBasicValue(ctx, `key: "value"`, pCtx)
   601  	r := require.New(t)
   602  	r.NoError(err)
   603  
   604  	testCases := []struct {
   605  		name        string
   606  		step        v1alpha1.WorkflowStep
   607  		status      map[string]v1alpha1.StepStatus
   608  		expected    bool
   609  		expectedErr string
   610  	}{
   611  		{
   612  			name: "timeout true",
   613  			step: v1alpha1.WorkflowStep{
   614  				WorkflowStepBase: v1alpha1.WorkflowStepBase{
   615  					If: "status.step1.timeout",
   616  				},
   617  			},
   618  			status: map[string]v1alpha1.StepStatus{
   619  				"step1": {
   620  					Reason: "Timeout",
   621  				},
   622  			},
   623  			expected: true,
   624  		},
   625  		{
   626  			name: "context true",
   627  			step: v1alpha1.WorkflowStep{
   628  				WorkflowStepBase: v1alpha1.WorkflowStepBase{
   629  					If: `context.name == "app"`,
   630  				},
   631  			},
   632  			expected: true,
   633  		},
   634  		{
   635  			name: "context arr true",
   636  			step: v1alpha1.WorkflowStep{
   637  				WorkflowStepBase: v1alpha1.WorkflowStepBase{
   638  					If: `context.arr[0] == "a"`,
   639  				},
   640  			},
   641  			expected: true,
   642  		},
   643  		{
   644  			name: "parameter true",
   645  			step: v1alpha1.WorkflowStep{
   646  				WorkflowStepBase: v1alpha1.WorkflowStepBase{
   647  					If: `parameter.key == "value"`,
   648  				},
   649  			},
   650  			expected: true,
   651  		},
   652  		{
   653  			name: "failed true",
   654  			step: v1alpha1.WorkflowStep{
   655  				WorkflowStepBase: v1alpha1.WorkflowStepBase{
   656  					If: `status.step1.phase != "failed"`,
   657  				},
   658  			},
   659  			status: map[string]v1alpha1.StepStatus{
   660  				"step1": {
   661  					Phase: v1alpha1.WorkflowStepPhaseSucceeded,
   662  				},
   663  			},
   664  			expected: true,
   665  		},
   666  		{
   667  			name: "input true",
   668  			step: v1alpha1.WorkflowStep{
   669  				WorkflowStepBase: v1alpha1.WorkflowStepBase{
   670  					If: `inputs.test == "yes"`,
   671  					Inputs: v1alpha1.StepInputs{
   672  						{
   673  							From: "test",
   674  						},
   675  					},
   676  				},
   677  			},
   678  			expected: true,
   679  		},
   680  		{
   681  			name: "input with arr in context",
   682  			step: v1alpha1.WorkflowStep{
   683  				WorkflowStepBase: v1alpha1.WorkflowStepBase{
   684  					If: `inputs["context.arr[0]"] == "a"`,
   685  					Inputs: v1alpha1.StepInputs{
   686  						{
   687  							From: "context.arr[0]",
   688  						},
   689  					},
   690  				},
   691  			},
   692  			expected: true,
   693  		},
   694  		{
   695  			name: "input false with dash",
   696  			step: v1alpha1.WorkflowStep{
   697  				WorkflowStepBase: v1alpha1.WorkflowStepBase{
   698  					If: `inputs["test-input"] == "yes"`,
   699  					Inputs: v1alpha1.StepInputs{
   700  						{
   701  							From: "test-input",
   702  						},
   703  					},
   704  				},
   705  			},
   706  			expectedErr: "not found",
   707  			expected:    false,
   708  		},
   709  		{
   710  			name: "input value is struct",
   711  			step: v1alpha1.WorkflowStep{
   712  				WorkflowStepBase: v1alpha1.WorkflowStepBase{
   713  					If: `inputs["test-struct"].hello == "world"`,
   714  					Inputs: v1alpha1.StepInputs{
   715  						{
   716  							From: "test-struct",
   717  						},
   718  					},
   719  				},
   720  			},
   721  			expected: true,
   722  		},
   723  		{
   724  			name: "dash in if",
   725  			step: v1alpha1.WorkflowStep{
   726  				WorkflowStepBase: v1alpha1.WorkflowStepBase{
   727  					If: "status.step1-test.timeout",
   728  				},
   729  			},
   730  			expectedErr: "invalid if value",
   731  			expected:    false,
   732  		},
   733  		{
   734  			name: "dash in status",
   735  			step: v1alpha1.WorkflowStep{
   736  				WorkflowStepBase: v1alpha1.WorkflowStepBase{
   737  					If: `status["step1-test"].timeout`,
   738  				},
   739  			},
   740  			status: map[string]v1alpha1.StepStatus{
   741  				"step1-test": {
   742  					Reason: "Timeout",
   743  				},
   744  			},
   745  			expected: true,
   746  		},
   747  		{
   748  			name: "error if",
   749  			step: v1alpha1.WorkflowStep{
   750  				WorkflowStepBase: v1alpha1.WorkflowStepBase{
   751  					If: `test == true`,
   752  				},
   753  			},
   754  			expectedErr: "invalid if value",
   755  			expected:    false,
   756  		},
   757  	}
   758  
   759  	for _, tc := range testCases {
   760  		t.Run(tc.name, func(t *testing.T) {
   761  			r := require.New(t)
   762  			v, err := ValidateIfValue(ctx, tc.step, tc.status, &types.PreCheckOptions{
   763  				BasicTemplate: basicTemplate,
   764  				BasicValue:    basicVal,
   765  			})
   766  			if tc.expectedErr != "" {
   767  				r.Contains(err.Error(), tc.expectedErr)
   768  				r.Equal(v, false)
   769  				return
   770  			}
   771  			r.NoError(err)
   772  			r.Equal(v, tc.expected)
   773  		})
   774  	}
   775  }
   776  
   777  func newWorkflowContextForTest(t *testing.T) wfContext.Context {
   778  	r := require.New(t)
   779  	cm := corev1.ConfigMap{}
   780  	testCaseJson, err := yaml.YAMLToJSON([]byte(testCaseYaml))
   781  	r.NoError(err)
   782  	err = json.Unmarshal(testCaseJson, &cm)
   783  	r.NoError(err)
   784  
   785  	cli := &test.MockClient{
   786  		MockGet: func(ctx context.Context, key client.ObjectKey, obj client.Object) error {
   787  			o, ok := obj.(*corev1.ConfigMap)
   788  			if ok {
   789  				*o = cm
   790  			}
   791  			return nil
   792  		},
   793  		MockPatch: func(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption) error {
   794  			return nil
   795  		},
   796  	}
   797  	wfCtx, err := wfContext.NewContext(context.Background(), cli, "default", "app-v1", nil)
   798  	r.NoError(err)
   799  	v, err := value.NewValue(`"yes"`, nil, "")
   800  	r.NoError(err)
   801  	r.NoError(wfCtx.SetVar(v, "test"))
   802  	v, err = value.NewValue(`{hello: "world"}`, nil, "")
   803  	r.NoError(err)
   804  	r.NoError(wfCtx.SetVar(v, "test-struct"))
   805  	return wfCtx
   806  }
   807  
   808  func mockLoadTemplate(_ context.Context, name string) (string, error) {
   809  	templ := `
   810  parameter: {}
   811  process: {
   812  	#provider: "test"
   813  	#do: "%s"
   814  	parameter
   815  }
   816  // check injected context.
   817  name: context.name
   818  `
   819  	switch name {
   820  	case "output":
   821  		return fmt.Sprintf(templ+`myIP: process.myIP`, "output"), nil
   822  	case "input":
   823  		return fmt.Sprintf(templ, "input"), nil
   824  	case "wait":
   825  		return fmt.Sprintf(templ, "wait"), nil
   826  	case "terminate":
   827  		return fmt.Sprintf(templ, "terminate"), nil
   828  	case "templateError":
   829  		return `
   830  output: xx
   831  `, nil
   832  	case "executeFailed":
   833  		return fmt.Sprintf(templ, "executeFailed"), nil
   834  	case "ok":
   835  		return fmt.Sprintf(templ, "ok"), nil
   836  	case "error":
   837  		return fmt.Sprintf(templ, "error"), nil
   838  	case "steps":
   839  		return `
   840  #do: "steps"
   841  ok: {
   842  	#provider: "test"
   843  	#do: "ok"
   844  }
   845  `, nil
   846  	}
   847  
   848  	return "", nil
   849  }
   850  
   851  var (
   852  	testCaseYaml = `apiVersion: v1
   853  data:
   854    test: ""
   855  kind: ConfigMap
   856  metadata:
   857    name: app-v1
   858  `
   859  )