package config import ( "fmt" "io" "io/ioutil" "reflect" "strings" "testing" "github.com/SAP/jenkins-library/pkg/mock" "github.com/stretchr/testify/assert" ) func initRunConfigGlobMock(pattern string) ([]string, error) { matches := []string{} switch pattern { case "**/file1": matches = append(matches, "file1") case "directory/file2": matches = append(matches, "file2") } return matches, nil } func TestInitRunConfigV1(t *testing.T) { tt := []struct { name string config Config stageConfig string runStagesExpected map[string]bool runStepsExpected map[string]map[string]bool expectedError error errorContains string }{ { name: "success", config: Config{Stages: map[string]map[string]interface{}{"testStage": {"testKey": "testVal"}}}, stageConfig: "spec:\n stages:\n - name: testStage\n displayName: testStage\n steps:\n - name: testStep\n conditions:\n - configKey: testKey", runStepsExpected: map[string]map[string]bool{}, }, { name: "error - load conditions", stageConfig: "wrong stage config format", runStepsExpected: map[string]map[string]bool{}, errorContains: "failed to load pipeline run conditions", }, { name: "error - evaluate conditions", config: Config{Stages: map[string]map[string]interface{}{"testStage": {"testKey": "testVal"}}}, runStepsExpected: map[string]map[string]bool{}, stageConfig: "spec:\n stages:\n - name: testStage\n displayName: testStage\n steps:\n - name: testStep\n conditions:\n - config:\n configKey1:\n - configVal1\n configKey2:\n - configVal2", errorContains: "failed to evaluate step conditions", }, } filesMock := mock.FilesMock{} for _, test := range tt { stageConfig := ioutil.NopCloser(strings.NewReader(test.stageConfig)) runConfig := RunConfig{StageConfigFile: stageConfig} runConfigV1 := RunConfigV1{RunConfig: runConfig} err := runConfigV1.InitRunConfigV1(&test.config, nil, nil, nil, nil, &filesMock, ".pipeline") if len(test.errorContains) > 0 { assert.Contains(t, fmt.Sprint(err), test.errorContains) } else { assert.NoError(t, err) } } } func TestInitRunConfig(t *testing.T) { tests := []struct { name string customConfig io.ReadCloser stageConfig io.ReadCloser runStepsExpected map[string]map[string]bool wantErr bool }{ { name: "init run config with config condition - success", customConfig: ioutil.NopCloser(strings.NewReader(` general: testGeneral: 'myVal1' stages: testStage2: testStage: 'myVal2' steps: thirdStep: testStep: 'myVal3' `)), stageConfig: ioutil.NopCloser(strings.NewReader(` stages: testStage1: stepConditions: firstStep: config: testGeneral testStage2: stepConditions: secondStep: config: testStage testStage3: stepConditions: thirdStep: config: testStep `)), runStepsExpected: map[string]map[string]bool{ "testStage1": { "firstStep": true, }, "testStage2": { "secondStep": true, }, "testStage3": { "thirdStep": true, }, }, wantErr: false, }, { name: "init run config with filePattern condition - success", customConfig: ioutil.NopCloser(strings.NewReader(` general: testGeneral: 'myVal1' stages: testStage2: testStage: 'myVal2' steps: thirdStep: testStep: 'myVal3' `)), stageConfig: ioutil.NopCloser(strings.NewReader(` stages: testStage1: stepConditions: firstStep: filePattern: "**/file1" testStage2: stepConditions: secondStep: filePattern: "directory/file2" testStage3: stepConditions: thirdStep: filePattern: "file3" `)), runStepsExpected: map[string]map[string]bool{ "testStage1": { "firstStep": true, }, "testStage2": { "secondStep": true, }, "testStage3": { "thirdStep": false, }, }, wantErr: false, }, { name: "init run config - unknown condition in stage config", customConfig: ioutil.NopCloser(strings.NewReader(` steps: testStep: testConfig: 'testVal' `)), stageConfig: ioutil.NopCloser(strings.NewReader(` stages: testStage: stepConditions: testStep: wrongCondition: "condVal" `)), runStepsExpected: map[string]map[string]bool{}, wantErr: true, }, { name: "init run config - load conditions with invalid format", stageConfig: ioutil.NopCloser(strings.NewReader("wrong stage config format")), runStepsExpected: map[string]map[string]bool{}, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { runConfig := RunConfig{StageConfigFile: tt.stageConfig} filter := StepFilters{All: []string{}, General: []string{}, Stages: []string{}, Steps: []string{}, Env: []string{}} projectConfig := Config{} _, err := projectConfig.GetStepConfig(map[string]interface{}{}, "", tt.customConfig, []io.ReadCloser{}, false, filter, StepData{}, nil, "", "") assert.NoError(t, err) err = runConfig.InitRunConfig(&projectConfig, nil, nil, nil, nil, initRunConfigGlobMock, nil) if tt.wantErr { assert.Error(t, err) } else { assert.NoError(t, err) assert.Equal(t, tt.runStepsExpected, runConfig.RunSteps) } }) } } func TestRunConfigLoadConditions(t *testing.T) { stageConfigContent := `stages: 'testStage1': stepConditions: firstStep: filePattern: '**/my.file' ` t.Run("load conditions - file of invalid format", func(t *testing.T) { runConfig := &RunConfig{StageConfigFile: ioutil.NopCloser(strings.NewReader("-- {{ \\ wrong } file format }"))} err := runConfig.loadConditions() assert.Error(t, err, "format of configuration is invalid") }) t.Run("load conditions - success", func(t *testing.T) { runConfig := &RunConfig{StageConfigFile: ioutil.NopCloser(strings.NewReader(stageConfigContent))} err := runConfig.loadConditions() assert.NoError(t, err) condition := map[string]interface{}{ "filePattern": "**/my.file", } assert.Equal(t, 1, len(runConfig.StageConfig.Stages)) assert.Equal(t, 1, len(runConfig.StageConfig.Stages["testStage1"].Conditions)) assert.Equal(t, condition, runConfig.StageConfig.Stages["testStage1"].Conditions["firstStep"]) }) } func Test_stepConfigLookup(t *testing.T) { testConfig := map[string]interface{}{ "general": map[string]interface{}{ "generalKey": "generalValue", }, "stages": map[string]interface{}{ "testStep": map[string]interface{}{ "stagesKey": "stagesValue", }, }, "steps": map[string]interface{}{ "testStep": map[string]interface{}{ "stepKey": "stepValue", "stepKeyStringSlice": []string{"val1", "val2"}, }, }, "configKey": "configValue", } type args struct { m map[string]interface{} stepName string key string } tests := []struct { name string args args want interface{} }{ { name: "empty map", args: args{nil, "", ""}, want: nil, }, { name: "key not in map, invalid stepName", args: args{testConfig, "some step", "some key"}, want: nil, }, { name: "key not in map, valid stepName", args: args{testConfig, "testStep", "some key"}, want: nil, }, { name: "key in map under general", args: args{testConfig, "some step", "generalKey"}, want: "generalValue", }, { name: "key in map under stages", args: args{testConfig, "testStep", "stagesKey"}, want: "stagesValue", }, { name: "key in map under general", args: args{testConfig, "testStep", "stepKey"}, want: "stepValue", }, { name: "key in map under general", args: args{testConfig, "testStep", "stepKeyStringSlice"}, want: []string{"val1", "val2"}, }, { name: "key in map on top level string", args: args{testConfig, "", "configKey"}, want: "configValue", }, { name: "key in map on top level map", args: args{testConfig, "", "general"}, want: map[string]interface{}{"generalKey": "generalValue"}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := stepConfigLookup(tt.args.m, tt.args.stepName, tt.args.key); !reflect.DeepEqual(got, tt.want) { t.Errorf("stepConfigLookup() = %v, want %v", got, tt.want) } }) } }