1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2024-12-12 10:55:20 +02:00
sap-jenkins-library/cmd/piper_test.go
Stephan Aßmus 40440f8f01
Bugfix: Convert int/float parameters (2nd try) (#1851)
* If a step declares a parameter of type string, depending on how the config is written, it is no longer ignored, if it is interpreted by the yaml parser as integer or float value.
* If an expected parameter is present in the configuration, step execution will consistently fail if the parameter has the wrong type, no sensible conversion can take place, and it is known that the parameter will be ignored.
* For all type-mismatches that have no implemented conversion, a warning is logged. (It isn't known whether the conversion actually works, since it depends on both the yaml and json packages and future changes there.)
* Entries in the evaluated config with a value of nil are ignored.
2020-07-29 09:22:10 +02:00

377 lines
11 KiB
Go

package cmd
import (
"bytes"
"encoding/json"
"fmt"
"github.com/SAP/jenkins-library/pkg/log"
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/SAP/jenkins-library/pkg/config"
"github.com/SAP/jenkins-library/pkg/mock"
"github.com/ghodss/yaml"
"github.com/spf13/cobra"
flag "github.com/spf13/pflag"
"github.com/stretchr/testify/assert"
)
func TestAddRootFlags(t *testing.T) {
var testRootCmd = &cobra.Command{Use: "test", Short: "This is just a test"}
addRootFlags(testRootCmd)
assert.NotNil(t, testRootCmd.Flag("customConfig"), "expected flag not available")
assert.NotNil(t, testRootCmd.Flag("defaultConfig"), "expected flag not available")
assert.NotNil(t, testRootCmd.Flag("parametersJSON"), "expected flag not available")
assert.NotNil(t, testRootCmd.Flag("stageName"), "expected flag not available")
assert.NotNil(t, testRootCmd.Flag("stepConfigJSON"), "expected flag not available")
assert.NotNil(t, testRootCmd.Flag("verbose"), "expected flag not available")
}
func TestPrepareConfig(t *testing.T) {
defaultsBak := GeneralConfig.DefaultConfig
GeneralConfig.DefaultConfig = []string{"testDefaults.yml"}
defer func() { GeneralConfig.DefaultConfig = defaultsBak }()
t.Run("using stepConfigJSON", func(t *testing.T) {
stepConfigJSONBak := GeneralConfig.StepConfigJSON
GeneralConfig.StepConfigJSON = `{"testParam": "testValueJSON"}`
defer func() { GeneralConfig.StepConfigJSON = stepConfigJSONBak }()
testOptions := mock.StepOptions{}
var testCmd = &cobra.Command{Use: "test", Short: "This is just a test"}
testCmd.Flags().StringVar(&testOptions.TestParam, "testParam", "", "test usage")
metadata := config.StepData{
Spec: config.StepSpec{
Inputs: config.StepInputs{
Parameters: []config.StepParameters{
{Name: "testParam", Scope: []string{"GENERAL"}},
},
},
},
}
PrepareConfig(testCmd, &metadata, "testStep", &testOptions, mock.OpenFileMock)
assert.Equal(t, "testValueJSON", testOptions.TestParam, "wrong value retrieved from config")
})
t.Run("using config files", func(t *testing.T) {
t.Run("success case", func(t *testing.T) {
testOptions := mock.StepOptions{}
var testCmd = &cobra.Command{Use: "test", Short: "This is just a test"}
testCmd.Flags().StringVar(&testOptions.TestParam, "testParam", "", "test usage")
metadata := config.StepData{
Spec: config.StepSpec{
Inputs: config.StepInputs{
Parameters: []config.StepParameters{
{Name: "testParam", Scope: []string{"GENERAL"}},
},
},
},
}
err := PrepareConfig(testCmd, &metadata, "testStep", &testOptions, mock.OpenFileMock)
assert.NoError(t, err, "no error expected but error occured")
//assert config
assert.Equal(t, "testValue", testOptions.TestParam, "wrong value retrieved from config")
//assert that flag has been marked as changed
testCmd.Flags().VisitAll(func(pflag *flag.Flag) {
if pflag.Name == "testParam" {
assert.True(t, pflag.Changed, "flag should be marked as changed")
}
})
})
t.Run("error case", func(t *testing.T) {
GeneralConfig.DefaultConfig = []string{"testDefaultsInvalid.yml"}
testOptions := mock.StepOptions{}
var testCmd = &cobra.Command{Use: "test", Short: "This is just a test"}
metadata := config.StepData{}
err := PrepareConfig(testCmd, &metadata, "testStep", &testOptions, mock.OpenFileMock)
assert.Error(t, err, "error expected but none occured")
})
})
}
func TestRetrieveHookConfig(t *testing.T) {
tt := []struct {
hookJSON []byte
expectedHookConfig HookConfiguration
}{
{hookJSON: []byte(""), expectedHookConfig: HookConfiguration{}},
{hookJSON: []byte(`{"sentry":{"dsn":"https://my.sentry.dsn"}}`), expectedHookConfig: HookConfiguration{SentryConfig: SentryConfiguration{Dsn: "https://my.sentry.dsn"}}},
}
for _, test := range tt {
var target HookConfiguration
var hookJSONRaw json.RawMessage
if len(test.hookJSON) > 0 {
err := json.Unmarshal(test.hookJSON, &hookJSONRaw)
assert.NoError(t, err)
}
retrieveHookConfig(&hookJSONRaw, &target)
assert.Equal(t, test.expectedHookConfig, target)
}
}
func TestGetProjectConfigFile(t *testing.T) {
tt := []struct {
filename string
filesAvailable []string
expected string
}{
{filename: ".pipeline/config.yml", filesAvailable: []string{}, expected: ".pipeline/config.yml"},
{filename: ".pipeline/config.yml", filesAvailable: []string{".pipeline/config.yml"}, expected: ".pipeline/config.yml"},
{filename: ".pipeline/config.yml", filesAvailable: []string{".pipeline/config.yaml"}, expected: ".pipeline/config.yaml"},
{filename: ".pipeline/config.yaml", filesAvailable: []string{".pipeline/config.yml", ".pipeline/config.yaml"}, expected: ".pipeline/config.yaml"},
{filename: ".pipeline/config.yml", filesAvailable: []string{".pipeline/config.yml", ".pipeline/config.yaml"}, expected: ".pipeline/config.yml"},
}
for run, test := range tt {
t.Run(fmt.Sprintf("Run %v", run), func(t *testing.T) {
dir, err := ioutil.TempDir("", "")
defer os.RemoveAll(dir) // clean up
assert.NoError(t, err)
if len(test.filesAvailable) > 0 {
configFolder := filepath.Join(dir, filepath.Dir(test.filesAvailable[0]))
err = os.MkdirAll(configFolder, 0700)
assert.NoError(t, err)
}
for _, file := range test.filesAvailable {
ioutil.WriteFile(filepath.Join(dir, file), []byte("general:"), 0700)
}
assert.Equal(t, filepath.Join(dir, test.expected), getProjectConfigFile(filepath.Join(dir, test.filename)))
})
}
}
func TestConvertTypes(t *testing.T) {
t.Run("Converts strings to booleans", func(t *testing.T) {
// Init
hasFailed := false
exitFunc := log.Entry().Logger.ExitFunc
log.Entry().Logger.ExitFunc = func(int) {
hasFailed = true
}
defer func() { log.Entry().Logger.ExitFunc = exitFunc }()
options := struct {
Foo bool `json:"foo,omitempty"`
Bar bool `json:"bar,omitempty"`
}{}
options.Foo = true
options.Bar = false
stepConfig := map[string]interface{}{}
stepConfig["foo"] = "False"
stepConfig["bar"] = "True"
// Test
stepConfig = checkTypes(stepConfig, options)
confJSON, _ := json.Marshal(stepConfig)
_ = json.Unmarshal(confJSON, &options)
// Assert
assert.Equal(t, false, stepConfig["foo"])
assert.Equal(t, true, stepConfig["bar"])
assert.Equal(t, false, options.Foo)
assert.Equal(t, true, options.Bar)
assert.False(t, hasFailed, "Expected checkTypes() NOT to exit via logging framework")
})
t.Run("Converts numbers to strings", func(t *testing.T) {
// Init
hasFailed := false
exitFunc := log.Entry().Logger.ExitFunc
log.Entry().Logger.ExitFunc = func(int) {
hasFailed = true
}
defer func() { log.Entry().Logger.ExitFunc = exitFunc }()
options := struct {
Foo string `json:"foo,omitempty"`
Bar string `json:"bar,omitempty"`
}{}
stepConfig := map[string]interface{}{}
stepConfig["foo"] = 1.5
stepConfig["bar"] = 42
// Test
stepConfig = checkTypes(stepConfig, options)
confJSON, _ := json.Marshal(stepConfig)
_ = json.Unmarshal(confJSON, &options)
// Assert
assert.Equal(t, "1.5", stepConfig["foo"])
assert.Equal(t, "42", stepConfig["bar"])
assert.Equal(t, "1.5", options.Foo)
assert.Equal(t, "42", options.Bar)
assert.False(t, hasFailed, "Expected checkTypes() NOT to exit via logging framework")
})
t.Run("Keeps numbers", func(t *testing.T) {
// Init
hasFailed := false
exitFunc := log.Entry().Logger.ExitFunc
log.Entry().Logger.ExitFunc = func(int) {
hasFailed = true
}
defer func() { log.Entry().Logger.ExitFunc = exitFunc }()
options := struct {
Foo int `json:"foo,omitempty"`
Bar float32 `json:"bar,omitempty"`
}{}
stepConfig := map[string]interface{}{}
content := []byte(`
foo: 1
bar: 42
`)
err := yaml.Unmarshal(content, &stepConfig)
assert.NoError(t, err)
// Test
stepConfig = checkTypes(stepConfig, options)
confJSON, _ := json.Marshal(stepConfig)
_ = json.Unmarshal(confJSON, &options)
// Assert
assert.Equal(t, 1, stepConfig["foo"])
assert.Equal(t, float32(42.0), stepConfig["bar"])
assert.Equal(t, 1, options.Foo)
assert.Equal(t, float32(42.0), options.Bar)
assert.False(t, hasFailed, "Expected checkTypes() NOT to exit via logging framework")
})
t.Run("Exits because string found, slice expected", func(t *testing.T) {
// Init
hasFailed := false
exitFunc := log.Entry().Logger.ExitFunc
log.Entry().Logger.ExitFunc = func(int) {
hasFailed = true
}
defer func() { log.Entry().Logger.ExitFunc = exitFunc }()
options := struct {
Foo []string `json:"foo,omitempty"`
}{}
stepConfig := map[string]interface{}{}
stepConfig["foo"] = "entry"
// Test
stepConfig = checkTypes(stepConfig, options)
// Assert
assert.True(t, hasFailed, "Expected checkTypes() to exit via logging framework")
})
t.Run("Exits because float found, int expected", func(t *testing.T) {
// Init
hasFailed := false
exitFunc := log.Entry().Logger.ExitFunc
log.Entry().Logger.ExitFunc = func(int) {
hasFailed = true
}
defer func() { log.Entry().Logger.ExitFunc = exitFunc }()
options := struct {
Foo int `json:"foo,omitempty"`
}{}
stepConfig := map[string]interface{}{}
content := []byte("foo: 1.1")
err := yaml.Unmarshal(content, &stepConfig)
assert.NoError(t, err)
// Test
stepConfig = checkTypes(stepConfig, options)
// Assert
assert.Equal(t, 1.1, stepConfig["foo"])
assert.True(t, hasFailed, "Expected checkTypes() to exit via logging framework")
})
t.Run("Ignores nil values", func(t *testing.T) {
// Init
hasFailed := false
exitFunc := log.Entry().Logger.ExitFunc
log.Entry().Logger.ExitFunc = func(int) {
hasFailed = true
}
defer func() { log.Entry().Logger.ExitFunc = exitFunc }()
options := struct {
Foo []string `json:"foo,omitempty"`
Bar string `json:"bar,omitempty"`
}{}
stepConfig := map[string]interface{}{}
stepConfig["foo"] = nil
stepConfig["bar"] = nil
// Test
stepConfig = checkTypes(stepConfig, options)
confJSON, _ := json.Marshal(stepConfig)
_ = json.Unmarshal(confJSON, &options)
// Assert
assert.Nil(t, stepConfig["foo"])
assert.Nil(t, stepConfig["bar"])
assert.Equal(t, []string(nil), options.Foo)
assert.Equal(t, "", options.Bar)
assert.False(t, hasFailed, "Expected checkTypes() NOT to exit via logging framework")
})
t.Run("Logs warning for unknown type-mismatches", func(t *testing.T) {
// Init
hasFailed := false
exitFunc := log.Entry().Logger.ExitFunc
log.Entry().Logger.ExitFunc = func(int) {
hasFailed = true
}
defer func() { log.Entry().Logger.ExitFunc = exitFunc }()
logBuffer := new(bytes.Buffer)
logOutput := log.Entry().Logger.Out
log.Entry().Logger.Out = logBuffer
defer func() { log.Entry().Logger.Out = logOutput }()
options := struct {
Foo string `json:"foo,omitempty"`
}{}
stepConfig := map[string]interface{}{}
stepConfig["foo"] = true
// Test
stepConfig = checkTypes(stepConfig, options)
confJSON, _ := json.Marshal(stepConfig)
_ = json.Unmarshal(confJSON, &options)
// Assert
assert.Equal(t, true, stepConfig["foo"])
assert.Equal(t, "", options.Foo)
assert.Contains(t, logBuffer.String(), "The value may be ignored as a result")
assert.False(t, hasFailed, "Expected checkTypes() NOT to exit via logging framework")
})
}