1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2024-12-14 11:03:09 +02:00
sap-jenkins-library/pkg/config/evaluation_test.go

914 lines
23 KiB
Go
Raw Normal View History

package config
import (
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"
"github.com/SAP/jenkins-library/pkg/mock"
"github.com/stretchr/testify/assert"
)
func evaluateConditionsGlobMock(pattern string) ([]string, error) {
matches := []string{}
switch pattern {
case "**/conf.js":
matches = append(matches, "conf.js")
case "**/package.json":
matches = append(matches, "package.json", "package/node_modules/lib/package.json", "node_modules/package.json", "test/package.json")
}
return matches, nil
}
func evaluateConditionsOpenFileMock(name string, _ map[string]string) (io.ReadCloser, error) {
var fileContent io.ReadCloser
switch name {
case "package.json":
fileContent = ioutil.NopCloser(strings.NewReader(`
{
"scripts": {
"npmScript": "echo test",
"npmScript2": "echo test"
}
}
`))
case "_package.json":
fileContent = ioutil.NopCloser(strings.NewReader("wrong json format"))
case "test/package.json":
fileContent = ioutil.NopCloser(strings.NewReader("{}"))
}
return fileContent, nil
}
func TestEvaluateConditionsV1(t *testing.T) {
filesMock := mock.FilesMock{}
runConfig := RunConfigV1{
PipelineConfig: PipelineDefinitionV1{
Spec: Spec{
Stages: []Stage{
{
Name: "stage1",
DisplayName: "Test Stage 1",
Steps: []Step{
{
Name: "step1_1",
Conditions: []StepCondition{},
Orchestrators: []string{"Jenkins"},
},
{
Name: "step1_2",
Conditions: []StepCondition{
{ConfigKey: "testKey"},
},
},
{
Name: "step1_3",
Conditions: []StepCondition{},
},
},
},
{
Name: "stage2",
DisplayName: "Test Stage 2",
Steps: []Step{
{
Name: "step2_1",
Conditions: []StepCondition{
{ConfigKey: "testKeyNotExisting"},
{ConfigKey: "testKey"},
},
},
{
Name: "step2_2",
},
},
},
{
Name: "stage3",
DisplayName: "Test Stage 3",
Steps: []Step{
{
Name: "step3_1",
Conditions: []StepCondition{
{ConfigKey: "testKeyNotExisting"},
{ConfigKey: "testKey"},
},
},
},
},
},
},
},
}
config := Config{Stages: map[string]map[string]interface{}{
"Test Stage 1": {"step1_3": false, "testKey": "testVal"},
"Test Stage 2": {"testKey": "testVal"},
}}
expectedSteps := map[string]map[string]bool{
"Test Stage 1": {
"step1_2": true,
"step1_3": false,
},
"Test Stage 2": {
"step2_1": true,
"step2_2": true,
},
"Test Stage 3": {
"step3_1": false,
},
}
expectedStages := map[string]bool{
"Test Stage 1": true,
"Test Stage 2": true,
"Test Stage 3": false,
}
err := runConfig.evaluateConditionsV1(&config, nil, nil, nil, nil, &filesMock, ".pipeline")
assert.NoError(t, err)
assert.Equal(t, expectedSteps, runConfig.RunSteps)
assert.Equal(t, expectedStages, runConfig.RunStages)
}
func TestNotActiveEvaluateConditionsV1(t *testing.T) {
filesMock := mock.FilesMock{}
runConfig := RunConfigV1{
PipelineConfig: PipelineDefinitionV1{
Spec: Spec{
Stages: []Stage{
{
Name: "stage1",
DisplayName: "Test Stage 1",
Steps: []Step{
{
Name: "step1_1",
Conditions: []StepCondition{},
Orchestrators: []string{"Jenkins"},
},
{
Name: "step1_2",
Conditions: []StepCondition{
{ConfigKey: "testKey"},
},
NotActiveConditions: []StepCondition{
{ConfigKey: "testKeyNotExisting"},
},
},
{
Name: "step1_3",
Conditions: []StepCondition{},
NotActiveConditions: []StepCondition{
{ConfigKey: "testKeyNotExisting"},
{ConfigKey: "testKey"},
},
},
},
},
{
Name: "stage2",
DisplayName: "Test Stage 2",
Steps: []Step{
{
Name: "step2_1",
Conditions: []StepCondition{
{ConfigKey: "testKeyNotExisting"},
{ConfigKey: "testKey"},
},
},
},
},
{
Name: "stage3",
DisplayName: "Test Stage 3",
Steps: []Step{
{
Name: "step3_1",
NotActiveConditions: []StepCondition{
{ConfigKey: "testKey"},
},
},
},
},
},
},
},
}
config := Config{Stages: map[string]map[string]interface{}{
"Test Stage 1": {"testKey": "testVal"},
"Test Stage 2": {"testKey": "testVal"},
"Test Stage 3": {"testKey": "testVal"},
}}
expectedSteps := map[string]map[string]bool{
"Test Stage 1": {
"step1_2": true,
"step1_3": false,
},
"Test Stage 2": {
"step2_1": true,
},
"Test Stage 3": {
"step3_1": false,
},
}
expectedStages := map[string]bool{
"Test Stage 1": true,
"Test Stage 2": true,
"Test Stage 3": false,
}
err := runConfig.evaluateConditionsV1(&config, nil, nil, nil, nil, &filesMock, ".pipeline")
assert.NoError(t, err)
assert.Equal(t, expectedSteps, runConfig.RunSteps)
assert.Equal(t, expectedStages, runConfig.RunStages)
}
func TestEvaluateV1(t *testing.T) {
tt := []struct {
name string
config StepConfig
stepCondition StepCondition
expected bool
expectedError error
}{
{
name: "Config condition - true",
config: StepConfig{Config: map[string]interface{}{
"deployTool": "helm3",
}},
stepCondition: StepCondition{Config: map[string][]interface{}{"deployTool": {"helm", "helm3", "kubectl"}}},
expected: true,
},
{
name: "Config condition - false",
config: StepConfig{Config: map[string]interface{}{
"deployTool": "notsupported",
}},
stepCondition: StepCondition{Config: map[string][]interface{}{"deployTool": {"helm", "helm3", "kubectl"}}},
expected: false,
},
{
name: "Config condition - integer - true",
config: StepConfig{Config: map[string]interface{}{
"executors": 1,
}},
stepCondition: StepCondition{Config: map[string][]interface{}{"executors": {1}}},
expected: true,
},
{
name: "Config condition - wrong condition definition",
config: StepConfig{Config: map[string]interface{}{
"deployTool": "helm3",
}},
stepCondition: StepCondition{Config: map[string][]interface{}{"deployTool": {"helm", "helm3", "kubectl"}, "deployTool2": {"myTool"}}},
expectedError: fmt.Errorf("only one config key allowed per condition but 2 provided"),
},
{
name: "ConfigKey condition - true",
config: StepConfig{Config: map[string]interface{}{
"dockerRegistryUrl": "https://my.docker.registry.url",
}},
stepCondition: StepCondition{ConfigKey: "dockerRegistryUrl"},
expected: true,
},
{
name: "ConfigKey condition - false",
config: StepConfig{Config: map[string]interface{}{}},
stepCondition: StepCondition{ConfigKey: "dockerRegistryUrl"},
expected: false,
},
{
name: "FilePattern condition - true",
config: StepConfig{Config: map[string]interface{}{}},
stepCondition: StepCondition{FilePattern: "**/conf.js"},
expected: true,
},
{
name: "FilePattern condition - false",
config: StepConfig{Config: map[string]interface{}{}},
stepCondition: StepCondition{FilePattern: "**/confx.js"},
expected: false,
},
{
name: "FilePatternFromConfig condition - true",
config: StepConfig{Config: map[string]interface{}{
"newmanCollection": "**/*.postman_collection.json",
}},
stepCondition: StepCondition{FilePatternFromConfig: "newmanCollection"},
expected: true,
},
{
name: "FilePatternFromConfig condition - false",
config: StepConfig{Config: map[string]interface{}{
"newmanCollection": "**/*.postmanx_collection.json",
}},
stepCondition: StepCondition{FilePatternFromConfig: "newmanCollection"},
expected: false,
},
{
name: "FilePatternFromConfig condition - false, empty value",
config: StepConfig{Config: map[string]interface{}{
"newmanCollection": "",
}},
stepCondition: StepCondition{FilePatternFromConfig: "newmanCollection"},
expected: false,
},
{
name: "NpmScript condition - true",
config: StepConfig{Config: map[string]interface{}{}},
stepCondition: StepCondition{NpmScript: "testScript"},
expected: true,
},
{
name: "NpmScript condition - true",
config: StepConfig{Config: map[string]interface{}{}},
stepCondition: StepCondition{NpmScript: "missingScript"},
expected: false,
},
{
name: "Inactive condition - false",
config: StepConfig{Config: map[string]interface{}{}},
stepCondition: StepCondition{Inactive: true},
expected: false,
},
{
name: "Inactive condition - true",
config: StepConfig{Config: map[string]interface{}{}},
stepCondition: StepCondition{Inactive: false},
expected: true,
},
{
name: "CommonPipelineEnvironment - true",
config: StepConfig{Config: map[string]interface{}{}},
stepCondition: StepCondition{CommonPipelineEnvironment: map[string]interface{}{"myCpeTrueFile": "myTrueValue"}},
expected: true,
},
{
name: "CommonPipelineEnvironment - false",
config: StepConfig{Config: map[string]interface{}{}},
stepCondition: StepCondition{CommonPipelineEnvironment: map[string]interface{}{"myCpeTrueFile": "notMyTrueValue"}},
expected: false,
},
{
name: "No condition - true",
config: StepConfig{Config: map[string]interface{}{}},
expected: true,
},
}
packageJson := `{
"scripts": {
"testScript": "whatever"
}
}`
filesMock := mock.FilesMock{}
filesMock.AddFile("conf.js", []byte("//test"))
filesMock.AddFile("my.postman_collection.json", []byte("{}"))
filesMock.AddFile("package.json", []byte(packageJson))
dir, err := ioutil.TempDir("", "")
if err != nil {
t.Fatal("Failed to create temporary directory")
}
// clean up tmp dir
defer os.RemoveAll(dir)
cpeDir := filepath.Join(dir, "commonPipelineEnvironment")
err = os.MkdirAll(cpeDir, 0700)
if err != nil {
t.Fatal("Failed to create sub directory")
}
ioutil.WriteFile(filepath.Join(cpeDir, "myCpeTrueFile"), []byte("myTrueValue"), 0700)
for _, test := range tt {
t.Run(test.name, func(t *testing.T) {
active, err := test.stepCondition.evaluateV1(test.config, &filesMock, "dummy", dir)
if test.expectedError == nil {
assert.NoError(t, err)
} else {
assert.EqualError(t, err, fmt.Sprint(test.expectedError))
}
assert.Equal(t, test.expected, active)
})
}
}
func TestEvaluateConditions(t *testing.T) {
tests := []struct {
name string
customConfig *Config
stageConfig io.ReadCloser
runStepsExpected map[string]map[string]bool
globFunc func(pattern string) ([]string, error)
wantErr bool
}{
{
name: "test config condition - success",
customConfig: &Config{
General: map[string]interface{}{
"testGeneral": "myVal1",
},
Stages: map[string]map[string]interface{}{
"testStage2": {
"testStage": "myVal2",
},
},
Steps: map[string]map[string]interface{}{
"thirdStep": {
"testStep1": "myVal3",
},
},
},
stageConfig: ioutil.NopCloser(strings.NewReader(`
stages:
testStage1:
stepConditions:
firstStep:
config: testGeneral
testStage2:
stepConditions:
secondStep:
config: testStage
testStage3:
stepConditions:
thirdStep:
config: testStep1
forthStep:
config: testStep2
`)),
runStepsExpected: map[string]map[string]bool{
"testStage1": {"firstStep": true},
"testStage2": {"secondStep": true},
"testStage3": {
"thirdStep": true,
"forthStep": false,
},
},
wantErr: false,
},
{
name: "test config condition - wrong usage with list",
customConfig: &Config{
General: map[string]interface{}{},
Stages: map[string]map[string]interface{}{},
Steps: map[string]map[string]interface{}{},
},
stageConfig: ioutil.NopCloser(strings.NewReader(`
stages:
testStage1:
stepConditions:
firstStep:
config:
- testGeneral
`)),
runStepsExpected: map[string]map[string]bool{},
wantErr: true,
},
{
name: "test config value condition - success",
customConfig: &Config{
General: map[string]interface{}{
"testGeneral": "myVal1",
},
Stages: map[string]map[string]interface{}{},
Steps: map[string]map[string]interface{}{
"thirdStep": {
"testStep": "myVal3",
},
},
},
stageConfig: ioutil.NopCloser(strings.NewReader(`
stages:
testStage1:
stepConditions:
firstStep:
config:
testGeneral:
- myValx
- myVal1
testStage2:
stepConditions:
secondStep:
config:
testStage:
- maValXyz
testStage3:
stepConditions:
thirdStep:
config:
testStep:
- myVal3
`)),
runStepsExpected: map[string]map[string]bool{
"testStage1": {"firstStep": true},
"testStage2": {"secondStep": false},
"testStage3": {"thirdStep": true},
},
wantErr: false,
},
{
name: "test configKey condition - success",
customConfig: &Config{
General: map[string]interface{}{
"myKey1_1": "myVal1_1",
},
Stages: map[string]map[string]interface{}{},
Steps: map[string]map[string]interface{}{
"thirdStep": {
"myKey3_1": "myVal3_1",
},
},
},
stageConfig: ioutil.NopCloser(strings.NewReader(`
stages:
testStage1:
stepConditions:
firstStep:
configKeys:
- myKey1_1
- myKey1_2
testStage2:
stepConditions:
secondStep:
configKeys:
- myKey2_1
testStage3:
stepConditions:
thirdStep:
configKeys:
- myKey3_1
`)),
runStepsExpected: map[string]map[string]bool{
"testStage1": {"firstStep": true},
"testStage2": {"secondStep": false},
"testStage3": {"thirdStep": true},
},
wantErr: false,
},
{
name: "test configKey condition - not list",
customConfig: &Config{
General: map[string]interface{}{
"myKey1_1": "myVal1_1",
},
Stages: map[string]map[string]interface{}{},
Steps: map[string]map[string]interface{}{},
},
stageConfig: ioutil.NopCloser(strings.NewReader(`
stages:
testStage1:
stepConditions:
firstStep:
configKeys: myKey1_1
`)),
wantErr: true,
},
{
name: "test configKey condition - success",
customConfig: &Config{
General: map[string]interface{}{
"myKey1_1": "myVal1_1",
},
Stages: map[string]map[string]interface{}{},
Steps: map[string]map[string]interface{}{
"thirdStep": {
"myKey3_1": "myVal3_1",
},
},
},
stageConfig: ioutil.NopCloser(strings.NewReader(`
stages:
testStage1:
stepConditions:
firstStep:
configKeys:
- myKey1_1
- myKey1_2
testStage2:
stepConditions:
secondStep:
configKeys:
- myKey2_1
testStage3:
stepConditions:
thirdStep:
configKeys:
- myKey3_1
`)),
runStepsExpected: map[string]map[string]bool{
"testStage1": {"firstStep": true},
"testStage2": {"secondStep": false},
"testStage3": {"thirdStep": true},
},
wantErr: false,
},
{
name: "test filePattern condition - success",
globFunc: evaluateConditionsGlobMock,
customConfig: &Config{
General: map[string]interface{}{},
Stages: map[string]map[string]interface{}{},
Steps: map[string]map[string]interface{}{},
},
stageConfig: ioutil.NopCloser(strings.NewReader(`
stages:
testStage1:
stepConditions:
firstStep:
filePattern: '**/conf.js'
secondStep:
filePattern: '**/conf.jsx'
`)),
runStepsExpected: map[string]map[string]bool{
"testStage1": {
"firstStep": true,
"secondStep": false,
},
},
wantErr: false,
},
{
name: "test filePattern condition - error while searching files by pattern",
customConfig: &Config{
General: map[string]interface{}{},
Stages: map[string]map[string]interface{}{},
Steps: map[string]map[string]interface{}{},
},
stageConfig: ioutil.NopCloser(strings.NewReader(`
stages:
testStage1:
stepConditions:
firstStep:
filePattern: '**/conf.js'
`)),
runStepsExpected: map[string]map[string]bool{},
globFunc: func(pattern string) ([]string, error) {
return nil, errors.New("failed to check if file exists")
},
wantErr: true,
},
{
name: "test filePattern condition with list - success",
globFunc: evaluateConditionsGlobMock,
customConfig: &Config{
General: map[string]interface{}{},
Stages: map[string]map[string]interface{}{},
Steps: map[string]map[string]interface{}{},
},
stageConfig: ioutil.NopCloser(strings.NewReader(`
stages:
testStage1:
stepConditions:
firstStep:
filePattern:
- '**/conf.js'
- 'myCollection.json'
secondStep:
filePattern:
- '**/conf.jsx'
`)),
runStepsExpected: map[string]map[string]bool{
"testStage1": {
"firstStep": true,
"secondStep": false,
},
},
wantErr: false,
},
{
name: "test filePattern condition with list - error while searching files by pattern",
customConfig: &Config{
General: map[string]interface{}{},
Stages: map[string]map[string]interface{}{},
Steps: map[string]map[string]interface{}{},
},
stageConfig: ioutil.NopCloser(strings.NewReader(`
stages:
testStage1:
stepConditions:
firstStep:
filePattern:
- '**/conf.js'
- 'myCollection.json'
`)),
runStepsExpected: map[string]map[string]bool{},
globFunc: func(pattern string) ([]string, error) {
return nil, errors.New("failed to check if file exists")
},
wantErr: true,
},
{
name: "test filePatternFromConfig condition - success",
globFunc: evaluateConditionsGlobMock,
customConfig: &Config{
General: map[string]interface{}{},
Stages: map[string]map[string]interface{}{},
Steps: map[string]map[string]interface{}{
"firstStep": {
"myVal1": "**/conf.js",
},
"thirdStep": {
"myVal3": "**/conf.jsx",
},
},
},
stageConfig: ioutil.NopCloser(strings.NewReader(`
stages:
testStage1:
stepConditions:
firstStep:
filePatternFromConfig: myVal1
secondStep:
filePatternFromConfig: myVal2
thirdStep:
filePatternFromConfig: myVal3
`)),
runStepsExpected: map[string]map[string]bool{
"testStage1": {
"firstStep": true,
"secondStep": false,
"thirdStep": false,
},
},
wantErr: false,
},
{
name: "test filePatternFromConfig condition - error while searching files by pattern",
customConfig: &Config{
General: map[string]interface{}{},
Stages: map[string]map[string]interface{}{},
Steps: map[string]map[string]interface{}{
"firstStep": {
"myVal1": "**/conf.js",
},
},
},
stageConfig: ioutil.NopCloser(strings.NewReader(`
stages:
testStage1:
stepConditions:
firstStep:
filePatternFromConfig: myVal1
`)),
runStepsExpected: map[string]map[string]bool{},
globFunc: func(pattern string) ([]string, error) {
return nil, errors.New("failed to check if file exists")
},
wantErr: true,
},
{
name: "test npmScripts condition - success",
globFunc: evaluateConditionsGlobMock,
customConfig: &Config{
General: map[string]interface{}{},
Stages: map[string]map[string]interface{}{},
Steps: map[string]map[string]interface{}{},
},
stageConfig: ioutil.NopCloser(strings.NewReader(`
stages:
testStage1:
stepConditions:
firstStep:
npmScripts: 'npmScript'
secondStep:
npmScripts: 'npmScript1'
`)),
runStepsExpected: map[string]map[string]bool{
"testStage1": {
"firstStep": true,
"secondStep": false,
},
},
wantErr: false,
},
{
name: "test npmScripts condition with list - success",
globFunc: evaluateConditionsGlobMock,
customConfig: &Config{
General: map[string]interface{}{},
Stages: map[string]map[string]interface{}{},
Steps: map[string]map[string]interface{}{},
},
stageConfig: ioutil.NopCloser(strings.NewReader(`
stages:
testStage1:
stepConditions:
firstStep:
npmScripts:
- 'npmScript'
- 'npmScript2'
secondStep:
npmScripts:
- 'npmScript3'
- 'npmScript4'
`)),
runStepsExpected: map[string]map[string]bool{
"testStage1": {
"firstStep": true,
"secondStep": false,
},
},
wantErr: false,
},
{
name: "test npmScripts condition - json with wrong format",
customConfig: &Config{
General: map[string]interface{}{},
Stages: map[string]map[string]interface{}{},
Steps: map[string]map[string]interface{}{},
},
stageConfig: ioutil.NopCloser(strings.NewReader(`
stages:
testStage1:
stepConditions:
firstStep:
npmScripts:
- 'npmScript3'
`)),
runStepsExpected: map[string]map[string]bool{},
globFunc: func(pattern string) ([]string, error) {
return []string{"_package.json"}, nil
},
wantErr: true,
},
{
name: "test npmScripts condition - error while searching package.json",
customConfig: &Config{
General: map[string]interface{}{},
Stages: map[string]map[string]interface{}{},
Steps: map[string]map[string]interface{}{},
},
stageConfig: ioutil.NopCloser(strings.NewReader(`
stages:
testStage1:
stepConditions:
firstStep:
npmScripts:
- 'npmScript3'
`)),
runStepsExpected: map[string]map[string]bool{},
globFunc: func(pattern string) ([]string, error) {
return nil, errors.New("failed to check if file exists")
},
wantErr: true,
},
{
name: "test explicit activation / de-activation of step",
customConfig: &Config{
Stages: map[string]map[string]interface{}{
"testStage1": {
"firstStep": true,
"fisecondStep": false,
},
},
},
stageConfig: ioutil.NopCloser(strings.NewReader(`
stages:
testStage1:
stepConditions:
firstStep:
config: testGeneral
secondStep:
config: testStage
`)),
runStepsExpected: map[string]map[string]bool{
"testStage1": {
"firstStep": true,
"secondStep": false,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
runConfig := RunConfig{
StageConfigFile: tt.stageConfig,
RunSteps: map[string]map[string]bool{},
OpenFile: evaluateConditionsOpenFileMock,
}
err := runConfig.loadConditions()
assert.NoError(t, err)
err = runConfig.evaluateConditions(tt.customConfig, nil, nil, nil, nil, tt.globFunc)
if tt.wantErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
assert.Equal(t, tt.runStepsExpected, runConfig.RunSteps)
}
})
}
}