github.com/ouraigua/jenkins-library@v0.0.0-20231028010029-fbeaf2f3aa9b/pkg/config/evaluation_test.go (about)

     1  //go:build unit
     2  // +build unit
     3  
     4  package config
     5  
     6  import (
     7  	"errors"
     8  	"fmt"
     9  	"io"
    10  	"os"
    11  	"path/filepath"
    12  	"strings"
    13  	"testing"
    14  
    15  	"github.com/SAP/jenkins-library/pkg/mock"
    16  	"github.com/stretchr/testify/assert"
    17  )
    18  
    19  func evaluateConditionsGlobMock(pattern string) ([]string, error) {
    20  	matches := []string{}
    21  	switch pattern {
    22  	case "**/conf.js":
    23  		matches = append(matches, "conf.js")
    24  	case "**/package.json":
    25  		matches = append(matches, "package.json", "package/node_modules/lib/package.json", "node_modules/package.json", "test/package.json")
    26  
    27  	}
    28  	return matches, nil
    29  }
    30  
    31  func evaluateConditionsOpenFileMock(name string, _ map[string]string) (io.ReadCloser, error) {
    32  	var fileContent io.ReadCloser
    33  	switch name {
    34  	case "package.json":
    35  		fileContent = io.NopCloser(strings.NewReader(`
    36  		{
    37  			"scripts": {
    38  				"npmScript": "echo test",
    39  				"npmScript2": "echo test"
    40  			}
    41  		}
    42  		`))
    43  	case "_package.json":
    44  		fileContent = io.NopCloser(strings.NewReader("wrong json format"))
    45  	case "test/package.json":
    46  		fileContent = io.NopCloser(strings.NewReader("{}"))
    47  	}
    48  	return fileContent, nil
    49  }
    50  
    51  func TestRunConfigV1EvaluateConditionsV1(t *testing.T) {
    52  	config := Config{Stages: map[string]map[string]interface{}{
    53  		"Test Stage 1": {
    54  			"step1":    true,       // explicit activate
    55  			"step5":    true,       // explicit activate
    56  			"step2":    false,      // explicit deactivate
    57  			"testKey":  "testVal",  // some condition 1
    58  			"testKey2": "testVal2", // some condition 2
    59  		},
    60  	}}
    61  	filesMock := mock.FilesMock{}
    62  	envRootPath := ".pipeline"
    63  
    64  	tests := []struct {
    65  		name           string
    66  		pipelineConfig PipelineDefinitionV1
    67  		wantRunSteps   map[string]map[string]bool
    68  		wantRunStages  map[string]bool
    69  	}{
    70  		{
    71  			name: "all steps in stage are inactive",
    72  			pipelineConfig: PipelineDefinitionV1{Spec: Spec{Stages: []Stage{{DisplayName: "Test Stage 1",
    73  				Steps: []Step{{
    74  					Name:                "step1",
    75  					NotActiveConditions: []StepCondition{{ConfigKey: "testKey"}},
    76  				}, {
    77  					Name: "step2",
    78  				}, {
    79  					Name:                "step3",
    80  					NotActiveConditions: []StepCondition{{ConfigKey: "testKey"}},
    81  				}},
    82  			},
    83  			}}},
    84  			wantRunSteps: map[string]map[string]bool{
    85  				"Test Stage 1": {
    86  					"step1": false,
    87  					"step2": false,
    88  					"step3": false,
    89  				}},
    90  			wantRunStages: map[string]bool{"Test Stage 1": false},
    91  		},
    92  		{
    93  			name: "simple stepActive conditions",
    94  			pipelineConfig: PipelineDefinitionV1{Spec: Spec{Stages: []Stage{{DisplayName: "Test Stage 1",
    95  				Steps: []Step{{
    96  					Name:       "step3",
    97  					Conditions: []StepCondition{{ConfigKey: "testKey"}},
    98  				}, {
    99  					Name:       "step4",
   100  					Conditions: []StepCondition{{ConfigKey: "notExistentKey"}},
   101  				}},
   102  			},
   103  			}}},
   104  			wantRunSteps: map[string]map[string]bool{
   105  				"Test Stage 1": {
   106  					"step3": true,
   107  					"step4": false,
   108  				}},
   109  			wantRunStages: map[string]bool{"Test Stage 1": true},
   110  		},
   111  		{
   112  			name: "explicit active/deactivate over stepActiveCondition",
   113  			pipelineConfig: PipelineDefinitionV1{Spec: Spec{Stages: []Stage{{DisplayName: "Test Stage 1",
   114  				Steps: []Step{{
   115  					Name:       "step1",
   116  					Conditions: []StepCondition{{ConfigKey: "notExistentKey"}},
   117  				}, {
   118  					Name:       "step2",
   119  					Conditions: []StepCondition{{ConfigKey: "testKey"}},
   120  				}},
   121  			},
   122  			}}},
   123  			wantRunSteps: map[string]map[string]bool{
   124  				"Test Stage 1": {
   125  					"step1": true,
   126  					"step2": false,
   127  				}},
   128  			wantRunStages: map[string]bool{"Test Stage 1": true},
   129  		},
   130  		{
   131  			name: "stepNotActiveCondition over stepActiveCondition",
   132  			pipelineConfig: PipelineDefinitionV1{Spec: Spec{Stages: []Stage{{DisplayName: "Test Stage 1",
   133  				Steps: []Step{{
   134  					Name:                "step3",
   135  					Conditions:          []StepCondition{{ConfigKey: "testKey"}},
   136  					NotActiveConditions: []StepCondition{{ConfigKey: "testKey2"}},
   137  				}, {
   138  					// false notActive condition
   139  					Name:                "step4",
   140  					Conditions:          []StepCondition{{ConfigKey: "testKey"}},
   141  					NotActiveConditions: []StepCondition{{ConfigKey: "notExistentKey"}},
   142  				}},
   143  			},
   144  			}}},
   145  			wantRunSteps: map[string]map[string]bool{
   146  				"Test Stage 1": {
   147  					"step3": false,
   148  					"step4": true,
   149  				}},
   150  			wantRunStages: map[string]bool{"Test Stage 1": true},
   151  		},
   152  		{
   153  			name: "stepNotActiveCondition over explicitly activated step",
   154  			pipelineConfig: PipelineDefinitionV1{Spec: Spec{Stages: []Stage{{DisplayName: "Test Stage 1",
   155  				Steps: []Step{{
   156  					Name:                "step1",
   157  					NotActiveConditions: []StepCondition{{ConfigKey: "testKey"}},
   158  				}, {
   159  					Name:                "step5",
   160  					NotActiveConditions: []StepCondition{{ConfigKey: "notExistentKey"}},
   161  				}},
   162  			},
   163  			}}},
   164  			wantRunSteps: map[string]map[string]bool{
   165  				"Test Stage 1": {
   166  					"step1": false,
   167  					"step5": true,
   168  				}},
   169  			wantRunStages: map[string]bool{"Test Stage 1": true},
   170  		},
   171  		{
   172  			name: "deactivate if only active step in stage",
   173  			pipelineConfig: PipelineDefinitionV1{Spec: Spec{Stages: []Stage{{DisplayName: "Test Stage 1",
   174  				Steps: []Step{{
   175  					Name:                "step1",
   176  					NotActiveConditions: []StepCondition{{ConfigKey: "testKey"}},
   177  				}, {
   178  					Name: "step2",
   179  				}, {
   180  					Name:                "step3",
   181  					NotActiveConditions: []StepCondition{{OnlyActiveStepInStage: true}},
   182  				}, {
   183  					Name:       "step4",
   184  					Conditions: []StepCondition{{ConfigKey: "keyNotExist"}},
   185  				}},
   186  			},
   187  			}}},
   188  			wantRunSteps: map[string]map[string]bool{
   189  				"Test Stage 1": {
   190  					"step1": false,
   191  					"step2": false,
   192  					"step3": false,
   193  					"step4": false,
   194  				}},
   195  			wantRunStages: map[string]bool{"Test Stage 1": false},
   196  		},
   197  		{
   198  			name: "OnlyActiveStepInStage: one of the next steps is active",
   199  			pipelineConfig: PipelineDefinitionV1{Spec: Spec{Stages: []Stage{{DisplayName: "Test Stage 1",
   200  				Steps: []Step{{
   201  					Name:                "step1",
   202  					NotActiveConditions: []StepCondition{{ConfigKey: "testKey"}},
   203  				}, {
   204  					Name: "step2",
   205  				}, {
   206  					Name:                "step3",
   207  					Conditions:          []StepCondition{{ConfigKey: "testKey"}},
   208  					NotActiveConditions: []StepCondition{{OnlyActiveStepInStage: true}},
   209  				}, {
   210  					Name:       "step4",
   211  					Conditions: []StepCondition{{ConfigKey: "testKey2"}},
   212  				}},
   213  			},
   214  			}}},
   215  			wantRunSteps: map[string]map[string]bool{
   216  				"Test Stage 1": {
   217  					"step1": false,
   218  					"step2": false,
   219  					"step3": true,
   220  					"step4": true,
   221  				}},
   222  			wantRunStages: map[string]bool{"Test Stage 1": true},
   223  		},
   224  	}
   225  	for _, tt := range tests {
   226  		t.Run(tt.name, func(t *testing.T) {
   227  			r := &RunConfigV1{PipelineConfig: tt.pipelineConfig}
   228  			assert.NoError(t, r.evaluateConditionsV1(&config, &filesMock, envRootPath),
   229  				fmt.Sprintf("evaluateConditionsV1() err, pipelineConfig = %v", tt.pipelineConfig),
   230  			)
   231  
   232  			assert.Equal(t, tt.wantRunSteps, r.RunSteps, "RunSteps mismatch")
   233  			assert.Equal(t, tt.wantRunStages, r.RunStages, "RunStages mismatch")
   234  		})
   235  	}
   236  }
   237  
   238  func TestEvaluateV1(t *testing.T) {
   239  	tt := []struct {
   240  		name          string
   241  		config        StepConfig
   242  		stepCondition StepCondition
   243  		runSteps      map[string]bool
   244  		expected      bool
   245  		expectedError error
   246  	}{
   247  		{
   248  			name: "Config condition - true",
   249  			config: StepConfig{Config: map[string]interface{}{
   250  				"deployTool": "helm3",
   251  			}},
   252  			stepCondition: StepCondition{Config: map[string][]interface{}{"deployTool": {"helm", "helm3", "kubectl"}}},
   253  			expected:      true,
   254  		},
   255  		{
   256  			name: "Config condition - false",
   257  			config: StepConfig{Config: map[string]interface{}{
   258  				"deployTool": "notsupported",
   259  			}},
   260  			stepCondition: StepCondition{Config: map[string][]interface{}{"deployTool": {"helm", "helm3", "kubectl"}}},
   261  			expected:      false,
   262  		},
   263  		{
   264  			name: "Config condition - integer - true",
   265  			config: StepConfig{Config: map[string]interface{}{
   266  				"executors": 1,
   267  			}},
   268  			stepCondition: StepCondition{Config: map[string][]interface{}{"executors": {1}}},
   269  			expected:      true,
   270  		},
   271  		{
   272  			name: "Config condition - wrong condition definition",
   273  			config: StepConfig{Config: map[string]interface{}{
   274  				"deployTool": "helm3",
   275  			}},
   276  			stepCondition: StepCondition{Config: map[string][]interface{}{"deployTool": {"helm", "helm3", "kubectl"}, "deployTool2": {"myTool"}}},
   277  			expectedError: fmt.Errorf("only one config key allowed per condition but 2 provided"),
   278  		},
   279  		{
   280  			name: "ConfigKey condition - true",
   281  			config: StepConfig{Config: map[string]interface{}{
   282  				"dockerRegistryUrl": "https://my.docker.registry.url",
   283  			}},
   284  			stepCondition: StepCondition{ConfigKey: "dockerRegistryUrl"},
   285  			expected:      true,
   286  		},
   287  		{
   288  			name:          "ConfigKey condition - false",
   289  			config:        StepConfig{Config: map[string]interface{}{}},
   290  			stepCondition: StepCondition{ConfigKey: "dockerRegistryUrl"},
   291  			expected:      false,
   292  		},
   293  		{
   294  			name: "nested ConfigKey condition - true",
   295  			config: StepConfig{Config: map[string]interface{}{
   296  				"cloudFoundry": map[string]interface{}{"space": "dev"},
   297  			}},
   298  			stepCondition: StepCondition{ConfigKey: "cloudFoundry/space"},
   299  			expected:      true,
   300  		},
   301  		{
   302  			name: "nested ConfigKey condition - false",
   303  			config: StepConfig{Config: map[string]interface{}{
   304  				"cloudFoundry": map[string]interface{}{"noSpace": "dev"},
   305  			}},
   306  			stepCondition: StepCondition{ConfigKey: "cloudFoundry/space"},
   307  			expected:      false,
   308  		},
   309  		{
   310  			name:          "FilePattern condition - true",
   311  			config:        StepConfig{Config: map[string]interface{}{}},
   312  			stepCondition: StepCondition{FilePattern: "**/conf.js"},
   313  			expected:      true,
   314  		},
   315  		{
   316  			name:          "FilePattern condition - false",
   317  			config:        StepConfig{Config: map[string]interface{}{}},
   318  			stepCondition: StepCondition{FilePattern: "**/confx.js"},
   319  			expected:      false,
   320  		},
   321  		{
   322  			name: "FilePatternFromConfig condition - true",
   323  			config: StepConfig{Config: map[string]interface{}{
   324  				"newmanCollection": "**/*.postman_collection.json",
   325  			}},
   326  			stepCondition: StepCondition{FilePatternFromConfig: "newmanCollection"},
   327  			expected:      true,
   328  		},
   329  		{
   330  			name: "FilePatternFromConfig condition - false",
   331  			config: StepConfig{Config: map[string]interface{}{
   332  				"newmanCollection": "**/*.postmanx_collection.json",
   333  			}},
   334  			stepCondition: StepCondition{FilePatternFromConfig: "newmanCollection"},
   335  			expected:      false,
   336  		},
   337  		{
   338  			name: "FilePatternFromConfig condition - false, empty value",
   339  			config: StepConfig{Config: map[string]interface{}{
   340  				"newmanCollection": "",
   341  			}},
   342  			stepCondition: StepCondition{FilePatternFromConfig: "newmanCollection"},
   343  			expected:      false,
   344  		},
   345  		{
   346  			name:          "NpmScript condition - true",
   347  			config:        StepConfig{Config: map[string]interface{}{}},
   348  			stepCondition: StepCondition{NpmScript: "testScript"},
   349  			expected:      true,
   350  		},
   351  		{
   352  			name:          "NpmScript condition - true",
   353  			config:        StepConfig{Config: map[string]interface{}{}},
   354  			stepCondition: StepCondition{NpmScript: "missingScript"},
   355  			expected:      false,
   356  		},
   357  		{
   358  			name:          "Inactive condition - false",
   359  			config:        StepConfig{Config: map[string]interface{}{}},
   360  			stepCondition: StepCondition{Inactive: true},
   361  			expected:      false,
   362  		},
   363  		{
   364  			name:          "Inactive condition - true",
   365  			config:        StepConfig{Config: map[string]interface{}{}},
   366  			stepCondition: StepCondition{Inactive: false},
   367  			expected:      true,
   368  		},
   369  		{
   370  			name:          "CommonPipelineEnvironment - true",
   371  			config:        StepConfig{Config: map[string]interface{}{}},
   372  			stepCondition: StepCondition{CommonPipelineEnvironment: map[string]interface{}{"myCpeTrueFile": "myTrueValue"}},
   373  			expected:      true,
   374  		},
   375  		{
   376  			name:          "CommonPipelineEnvironment - false",
   377  			config:        StepConfig{Config: map[string]interface{}{}},
   378  			stepCondition: StepCondition{CommonPipelineEnvironment: map[string]interface{}{"myCpeTrueFile": "notMyTrueValue"}},
   379  			expected:      false,
   380  		},
   381  		{
   382  			name:          "CommonPipelineEnvironmentVariableExists - true",
   383  			config:        StepConfig{Config: map[string]interface{}{}},
   384  			stepCondition: StepCondition{PipelineEnvironmentFilled: "custom/myCpeTrueFile"},
   385  			expected:      true,
   386  		},
   387  		{
   388  			name:          "CommonPipelineEnvironmentVariableExists - false",
   389  			config:        StepConfig{Config: map[string]interface{}{}},
   390  			stepCondition: StepCondition{PipelineEnvironmentFilled: "custom/notMyCpeTrueFile"},
   391  			expected:      false,
   392  		},
   393  		{
   394  			name:          "NotActiveCondition: all previous steps are inactive",
   395  			config:        StepConfig{Config: map[string]interface{}{}},
   396  			stepCondition: StepCondition{OnlyActiveStepInStage: true},
   397  			runSteps:      map[string]bool{"step1": false, "step2": false},
   398  			expected:      true,
   399  		},
   400  		{
   401  			name:          "NotActiveCondition: one of the previous steps is active",
   402  			config:        StepConfig{Config: map[string]interface{}{}},
   403  			stepCondition: StepCondition{OnlyActiveStepInStage: true},
   404  			runSteps:      map[string]bool{"step1": false, "step2": false, "step3": true},
   405  			expected:      false,
   406  		},
   407  		{
   408  			name:     "No condition - true",
   409  			config:   StepConfig{Config: map[string]interface{}{}},
   410  			expected: true,
   411  		},
   412  	}
   413  
   414  	packageJson := `{
   415  	"scripts": {
   416  		"testScript": "whatever"
   417  	}
   418  }`
   419  
   420  	filesMock := mock.FilesMock{}
   421  	filesMock.AddFile("conf.js", []byte("//test"))
   422  	filesMock.AddFile("my.postman_collection.json", []byte("{}"))
   423  	filesMock.AddFile("package.json", []byte(packageJson))
   424  
   425  	dir := t.TempDir()
   426  
   427  	cpeDir := filepath.Join(dir, "commonPipelineEnvironment")
   428  	err := os.MkdirAll(filepath.Join(cpeDir, "custom"), 0700)
   429  	if err != nil {
   430  		t.Fatal("Failed to create sub directories")
   431  	}
   432  	os.WriteFile(filepath.Join(cpeDir, "myCpeTrueFile"), []byte("myTrueValue"), 0700)
   433  	os.WriteFile(filepath.Join(cpeDir, "custom", "myCpeTrueFile"), []byte("myTrueValue"), 0700)
   434  
   435  	for _, test := range tt {
   436  		t.Run(test.name, func(t *testing.T) {
   437  			active, err := test.stepCondition.evaluateV1(test.config, &filesMock, "dummy", dir, test.runSteps)
   438  			if test.expectedError == nil {
   439  				assert.NoError(t, err)
   440  			} else {
   441  				assert.EqualError(t, err, fmt.Sprint(test.expectedError))
   442  			}
   443  			assert.Equal(t, test.expected, active)
   444  		})
   445  	}
   446  }
   447  
   448  func TestEvaluateConditions(t *testing.T) {
   449  	tests := []struct {
   450  		name             string
   451  		customConfig     *Config
   452  		stageConfig      io.ReadCloser
   453  		runStepsExpected map[string]map[string]bool
   454  		globFunc         func(pattern string) ([]string, error)
   455  		wantErr          bool
   456  	}{
   457  		{
   458  			name: "test config condition - success",
   459  			customConfig: &Config{
   460  				General: map[string]interface{}{
   461  					"testGeneral": "myVal1",
   462  				},
   463  				Stages: map[string]map[string]interface{}{
   464  					"testStage2": {
   465  						"testStage": "myVal2",
   466  					},
   467  				},
   468  				Steps: map[string]map[string]interface{}{
   469  					"thirdStep": {
   470  						"testStep1": "myVal3",
   471  					},
   472  				},
   473  			},
   474  			stageConfig: io.NopCloser(strings.NewReader(`
   475  stages:
   476    testStage1:
   477      stepConditions:
   478        firstStep:
   479          config: testGeneral
   480    testStage2:
   481      stepConditions:
   482        secondStep:
   483          config: testStage
   484    testStage3:
   485      stepConditions:
   486        thirdStep:
   487          config: testStep1
   488        forthStep:
   489          config: testStep2
   490              `)),
   491  			runStepsExpected: map[string]map[string]bool{
   492  				"testStage1": {"firstStep": true},
   493  				"testStage2": {"secondStep": true},
   494  				"testStage3": {
   495  					"thirdStep": true,
   496  					"forthStep": false,
   497  				},
   498  			},
   499  			wantErr: false,
   500  		},
   501  		{
   502  			name: "test config condition - wrong usage with list",
   503  			customConfig: &Config{
   504  				General: map[string]interface{}{},
   505  				Stages:  map[string]map[string]interface{}{},
   506  				Steps:   map[string]map[string]interface{}{},
   507  			},
   508  			stageConfig: io.NopCloser(strings.NewReader(`
   509  stages:
   510    testStage1:
   511      stepConditions:
   512        firstStep:
   513          config:
   514           - testGeneral
   515              `)),
   516  			runStepsExpected: map[string]map[string]bool{},
   517  			wantErr:          true,
   518  		},
   519  		{
   520  			name: "test config value condition - success",
   521  			customConfig: &Config{
   522  				General: map[string]interface{}{
   523  					"testGeneral": "myVal1",
   524  				},
   525  				Stages: map[string]map[string]interface{}{},
   526  				Steps: map[string]map[string]interface{}{
   527  					"thirdStep": {
   528  						"testStep": "myVal3",
   529  					},
   530  				},
   531  			},
   532  			stageConfig: io.NopCloser(strings.NewReader(`
   533  stages:
   534    testStage1:
   535      stepConditions:
   536        firstStep:
   537          config:
   538            testGeneral:
   539              - myValx
   540              - myVal1
   541    testStage2:
   542      stepConditions:
   543        secondStep:
   544          config:
   545            testStage:
   546              - maValXyz
   547    testStage3:
   548      stepConditions:
   549        thirdStep:
   550          config:
   551            testStep:
   552              - myVal3
   553              `)),
   554  			runStepsExpected: map[string]map[string]bool{
   555  				"testStage1": {"firstStep": true},
   556  				"testStage2": {"secondStep": false},
   557  				"testStage3": {"thirdStep": true},
   558  			},
   559  			wantErr: false,
   560  		},
   561  		{
   562  			name: "test configKey condition - success",
   563  			customConfig: &Config{
   564  				General: map[string]interface{}{
   565  					"myKey1_1": "myVal1_1",
   566  				},
   567  				Stages: map[string]map[string]interface{}{},
   568  				Steps: map[string]map[string]interface{}{
   569  					"thirdStep": {
   570  						"myKey3_1": "myVal3_1",
   571  					},
   572  				},
   573  			},
   574  			stageConfig: io.NopCloser(strings.NewReader(`
   575  stages:
   576    testStage1:
   577      stepConditions:
   578        firstStep:
   579          configKeys:
   580            - myKey1_1
   581            - myKey1_2
   582    testStage2:
   583      stepConditions:
   584        secondStep:
   585          configKeys:
   586            - myKey2_1
   587    testStage3:
   588      stepConditions:
   589        thirdStep:
   590          configKeys:
   591            - myKey3_1
   592              `)),
   593  			runStepsExpected: map[string]map[string]bool{
   594  				"testStage1": {"firstStep": true},
   595  				"testStage2": {"secondStep": false},
   596  				"testStage3": {"thirdStep": true},
   597  			},
   598  			wantErr: false,
   599  		},
   600  		{
   601  			name: "test configKey condition - not list",
   602  			customConfig: &Config{
   603  				General: map[string]interface{}{
   604  					"myKey1_1": "myVal1_1",
   605  				},
   606  				Stages: map[string]map[string]interface{}{},
   607  				Steps:  map[string]map[string]interface{}{},
   608  			},
   609  			stageConfig: io.NopCloser(strings.NewReader(`
   610  stages:
   611    testStage1:
   612      stepConditions:
   613        firstStep:
   614          configKeys: myKey1_1
   615              `)),
   616  			wantErr: true,
   617  		},
   618  		{
   619  			name: "test configKey condition - success",
   620  			customConfig: &Config{
   621  				General: map[string]interface{}{
   622  					"myKey1_1": "myVal1_1",
   623  				},
   624  				Stages: map[string]map[string]interface{}{},
   625  				Steps: map[string]map[string]interface{}{
   626  					"thirdStep": {
   627  						"myKey3_1": "myVal3_1",
   628  					},
   629  				},
   630  			},
   631  			stageConfig: io.NopCloser(strings.NewReader(`
   632  stages:
   633    testStage1:
   634      stepConditions:
   635        firstStep:
   636          configKeys:
   637            - myKey1_1
   638            - myKey1_2
   639    testStage2:
   640      stepConditions:
   641        secondStep:
   642          configKeys:
   643            - myKey2_1
   644    testStage3:
   645      stepConditions:
   646        thirdStep:
   647          configKeys:
   648            - myKey3_1
   649              `)),
   650  			runStepsExpected: map[string]map[string]bool{
   651  				"testStage1": {"firstStep": true},
   652  				"testStage2": {"secondStep": false},
   653  				"testStage3": {"thirdStep": true},
   654  			},
   655  			wantErr: false,
   656  		},
   657  		{
   658  			name:     "test filePattern condition - success",
   659  			globFunc: evaluateConditionsGlobMock,
   660  			customConfig: &Config{
   661  				General: map[string]interface{}{},
   662  				Stages:  map[string]map[string]interface{}{},
   663  				Steps:   map[string]map[string]interface{}{},
   664  			},
   665  			stageConfig: io.NopCloser(strings.NewReader(`
   666  stages:
   667    testStage1:
   668      stepConditions:
   669        firstStep:
   670          filePattern: '**/conf.js'
   671        secondStep:
   672          filePattern: '**/conf.jsx'
   673              `)),
   674  			runStepsExpected: map[string]map[string]bool{
   675  				"testStage1": {
   676  					"firstStep":  true,
   677  					"secondStep": false,
   678  				},
   679  			},
   680  			wantErr: false,
   681  		},
   682  		{
   683  			name: "test filePattern condition - error while searching files by pattern",
   684  			customConfig: &Config{
   685  				General: map[string]interface{}{},
   686  				Stages:  map[string]map[string]interface{}{},
   687  				Steps:   map[string]map[string]interface{}{},
   688  			},
   689  			stageConfig: io.NopCloser(strings.NewReader(`
   690  stages:
   691    testStage1:
   692      stepConditions:
   693        firstStep:
   694          filePattern: '**/conf.js'
   695              `)),
   696  			runStepsExpected: map[string]map[string]bool{},
   697  			globFunc: func(pattern string) ([]string, error) {
   698  				return nil, errors.New("failed to check if file exists")
   699  			},
   700  			wantErr: true,
   701  		},
   702  		{
   703  			name:     "test filePattern condition with list - success",
   704  			globFunc: evaluateConditionsGlobMock,
   705  			customConfig: &Config{
   706  				General: map[string]interface{}{},
   707  				Stages:  map[string]map[string]interface{}{},
   708  				Steps:   map[string]map[string]interface{}{},
   709  			},
   710  			stageConfig: io.NopCloser(strings.NewReader(`
   711  stages:
   712    testStage1:
   713      stepConditions:
   714        firstStep:
   715          filePattern:
   716           - '**/conf.js'
   717           - 'myCollection.json'
   718        secondStep:
   719          filePattern:
   720           - '**/conf.jsx'
   721              `)),
   722  			runStepsExpected: map[string]map[string]bool{
   723  				"testStage1": {
   724  					"firstStep":  true,
   725  					"secondStep": false,
   726  				},
   727  			},
   728  			wantErr: false,
   729  		},
   730  		{
   731  			name: "test filePattern condition with list - error while searching files by pattern",
   732  			customConfig: &Config{
   733  				General: map[string]interface{}{},
   734  				Stages:  map[string]map[string]interface{}{},
   735  				Steps:   map[string]map[string]interface{}{},
   736  			},
   737  			stageConfig: io.NopCloser(strings.NewReader(`
   738  stages:
   739    testStage1:
   740      stepConditions:
   741        firstStep:
   742          filePattern:
   743           - '**/conf.js'
   744           - 'myCollection.json'
   745              `)),
   746  			runStepsExpected: map[string]map[string]bool{},
   747  			globFunc: func(pattern string) ([]string, error) {
   748  				return nil, errors.New("failed to check if file exists")
   749  			},
   750  			wantErr: true,
   751  		},
   752  		{
   753  			name:     "test filePatternFromConfig condition - success",
   754  			globFunc: evaluateConditionsGlobMock,
   755  			customConfig: &Config{
   756  				General: map[string]interface{}{},
   757  				Stages:  map[string]map[string]interface{}{},
   758  				Steps: map[string]map[string]interface{}{
   759  					"firstStep": {
   760  						"myVal1": "**/conf.js",
   761  					},
   762  					"thirdStep": {
   763  						"myVal3": "**/conf.jsx",
   764  					},
   765  				},
   766  			},
   767  			stageConfig: io.NopCloser(strings.NewReader(`
   768  stages:
   769    testStage1:
   770      stepConditions:
   771        firstStep:
   772          filePatternFromConfig: myVal1
   773        secondStep:
   774          filePatternFromConfig: myVal2
   775        thirdStep:
   776          filePatternFromConfig: myVal3
   777              `)),
   778  			runStepsExpected: map[string]map[string]bool{
   779  				"testStage1": {
   780  					"firstStep":  true,
   781  					"secondStep": false,
   782  					"thirdStep":  false,
   783  				},
   784  			},
   785  			wantErr: false,
   786  		},
   787  		{
   788  			name: "test filePatternFromConfig condition - error while searching files by pattern",
   789  			customConfig: &Config{
   790  				General: map[string]interface{}{},
   791  				Stages:  map[string]map[string]interface{}{},
   792  				Steps: map[string]map[string]interface{}{
   793  					"firstStep": {
   794  						"myVal1": "**/conf.js",
   795  					},
   796  				},
   797  			},
   798  			stageConfig: io.NopCloser(strings.NewReader(`
   799  stages:
   800    testStage1:
   801      stepConditions:
   802        firstStep:
   803          filePatternFromConfig: myVal1
   804              `)),
   805  			runStepsExpected: map[string]map[string]bool{},
   806  			globFunc: func(pattern string) ([]string, error) {
   807  				return nil, errors.New("failed to check if file exists")
   808  			},
   809  			wantErr: true,
   810  		},
   811  		{
   812  			name:     "test npmScripts condition - success",
   813  			globFunc: evaluateConditionsGlobMock,
   814  			customConfig: &Config{
   815  				General: map[string]interface{}{},
   816  				Stages:  map[string]map[string]interface{}{},
   817  				Steps:   map[string]map[string]interface{}{},
   818  			},
   819  			stageConfig: io.NopCloser(strings.NewReader(`
   820  stages:
   821    testStage1:
   822      stepConditions:
   823        firstStep:
   824          npmScripts: 'npmScript'
   825        secondStep:
   826          npmScripts: 'npmScript1'
   827              `)),
   828  			runStepsExpected: map[string]map[string]bool{
   829  				"testStage1": {
   830  					"firstStep":  true,
   831  					"secondStep": false,
   832  				},
   833  			},
   834  			wantErr: false,
   835  		},
   836  		{
   837  			name:     "test npmScripts condition with list - success",
   838  			globFunc: evaluateConditionsGlobMock,
   839  			customConfig: &Config{
   840  				General: map[string]interface{}{},
   841  				Stages:  map[string]map[string]interface{}{},
   842  				Steps:   map[string]map[string]interface{}{},
   843  			},
   844  			stageConfig: io.NopCloser(strings.NewReader(`
   845  stages:
   846    testStage1:
   847      stepConditions:
   848        firstStep:
   849          npmScripts:
   850           - 'npmScript'
   851           - 'npmScript2'
   852        secondStep:
   853          npmScripts:
   854           - 'npmScript3'
   855           - 'npmScript4'
   856              `)),
   857  			runStepsExpected: map[string]map[string]bool{
   858  				"testStage1": {
   859  					"firstStep":  true,
   860  					"secondStep": false,
   861  				},
   862  			},
   863  			wantErr: false,
   864  		},
   865  		{
   866  			name: "test npmScripts condition - json with wrong format",
   867  			customConfig: &Config{
   868  				General: map[string]interface{}{},
   869  				Stages:  map[string]map[string]interface{}{},
   870  				Steps:   map[string]map[string]interface{}{},
   871  			},
   872  			stageConfig: io.NopCloser(strings.NewReader(`
   873  stages:
   874    testStage1:
   875      stepConditions:
   876        firstStep:
   877          npmScripts:
   878           - 'npmScript3'
   879              `)),
   880  			runStepsExpected: map[string]map[string]bool{},
   881  			globFunc: func(pattern string) ([]string, error) {
   882  				return []string{"_package.json"}, nil
   883  			},
   884  			wantErr: true,
   885  		},
   886  		{
   887  			name: "test npmScripts condition - error while searching package.json",
   888  			customConfig: &Config{
   889  				General: map[string]interface{}{},
   890  				Stages:  map[string]map[string]interface{}{},
   891  				Steps:   map[string]map[string]interface{}{},
   892  			},
   893  			stageConfig: io.NopCloser(strings.NewReader(`
   894  stages:
   895    testStage1:
   896      stepConditions:
   897        firstStep:
   898          npmScripts:
   899           - 'npmScript3'
   900              `)),
   901  			runStepsExpected: map[string]map[string]bool{},
   902  			globFunc: func(pattern string) ([]string, error) {
   903  				return nil, errors.New("failed to check if file exists")
   904  			},
   905  			wantErr: true,
   906  		},
   907  		{
   908  			name: "test explicit activation / de-activation of step",
   909  			customConfig: &Config{
   910  				Stages: map[string]map[string]interface{}{
   911  					"testStage1": {
   912  						"firstStep":    true,
   913  						"fisecondStep": false,
   914  					},
   915  				},
   916  			},
   917  			stageConfig: io.NopCloser(strings.NewReader(`
   918  stages:
   919    testStage1:
   920      stepConditions:
   921        firstStep:
   922          config: testGeneral
   923        secondStep:
   924          config: testStage
   925  `)),
   926  			runStepsExpected: map[string]map[string]bool{
   927  				"testStage1": {
   928  					"firstStep":  true,
   929  					"secondStep": false,
   930  				},
   931  			},
   932  		},
   933  	}
   934  	for _, tt := range tests {
   935  		t.Run(tt.name, func(t *testing.T) {
   936  			runConfig := RunConfig{
   937  				StageConfigFile: tt.stageConfig,
   938  				RunSteps:        map[string]map[string]bool{},
   939  				OpenFile:        evaluateConditionsOpenFileMock,
   940  			}
   941  			err := runConfig.loadConditions()
   942  			assert.NoError(t, err)
   943  			err = runConfig.evaluateConditions(tt.customConfig, nil, nil, nil, nil, tt.globFunc)
   944  			if tt.wantErr {
   945  				assert.Error(t, err)
   946  			} else {
   947  				assert.NoError(t, err)
   948  				assert.Equal(t, tt.runStepsExpected, runConfig.RunSteps)
   949  			}
   950  		})
   951  	}
   952  }
   953  
   954  func TestAnyOtherStepIsActive(t *testing.T) {
   955  	targetStep := "step3"
   956  
   957  	tests := []struct {
   958  		name     string
   959  		runSteps map[string]bool
   960  		want     bool
   961  	}{
   962  		{
   963  			name: "all steps are inactive (target active)",
   964  			runSteps: map[string]bool{
   965  				"step1": false,
   966  				"step2": false,
   967  				"step3": true,
   968  				"step4": false,
   969  			},
   970  			want: false,
   971  		},
   972  		{
   973  			name: "all steps are inactive (target inactive)",
   974  			runSteps: map[string]bool{
   975  				"step1": false,
   976  				"step2": false,
   977  				"step3": false,
   978  				"step4": false,
   979  			},
   980  			want: false,
   981  		},
   982  		{
   983  			name: "some previous step is active",
   984  			runSteps: map[string]bool{
   985  				"step1": false,
   986  				"step2": true,
   987  				"step3": false,
   988  				"step4": false,
   989  			},
   990  			want: true,
   991  		},
   992  		{
   993  			name: "some next step is active",
   994  			runSteps: map[string]bool{
   995  				"step1": false,
   996  				"step2": false,
   997  				"step3": true,
   998  				"step4": true,
   999  			},
  1000  			want: true,
  1001  		},
  1002  	}
  1003  	for _, tt := range tests {
  1004  		t.Run(tt.name, func(t *testing.T) {
  1005  			assert.Equalf(t, tt.want, anyOtherStepIsActive(targetStep, tt.runSteps), "anyOtherStepIsActive(%v, %v)", targetStep, tt.runSteps)
  1006  		})
  1007  	}
  1008  }