mirror of
https://github.com/SAP/jenkins-library.git
synced 2024-12-12 10:55:20 +02:00
ac5cf17317
* rename interface, types and methods. some type changes and refactor * update dependent methods and variables * fix unit tests * a bit more refactor and fix * concurrent safe singleton * return old Options struct * refactor creating config provider and fix nil pointer derefernce * fix unit test and linter errors * introduce resetting config provider (for unit tests) * fix annoying error message when config provider is not configured --------- Co-authored-by: Gulom Alimov <gulomjon.alimov@sap.com> Co-authored-by: Muhammadali Nazarov <muhammadalinazarov@gmail.com>
540 lines
16 KiB
Go
540 lines
16 KiB
Go
//go:build unit
|
|
// +build unit
|
|
|
|
package cmd
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"github.com/SAP/jenkins-library/pkg/orchestrator"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/ghodss/yaml"
|
|
"github.com/spf13/cobra"
|
|
flag "github.com/spf13/pflag"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/SAP/jenkins-library/pkg/config"
|
|
"github.com/SAP/jenkins-library/pkg/log"
|
|
"github.com/SAP/jenkins-library/pkg/mock"
|
|
)
|
|
|
|
func resetEnv(e []string) {
|
|
for _, val := range e {
|
|
tmp := strings.Split(val, "=")
|
|
os.Setenv(tmp[0], tmp[1])
|
|
}
|
|
}
|
|
|
|
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 TestAdoptStageNameFromParametersJSON(t *testing.T) {
|
|
tt := []struct {
|
|
name string
|
|
stageNameArg string
|
|
stageNameEnv string
|
|
stageNameJSON string
|
|
}{
|
|
{name: "no stage name", stageNameArg: "", stageNameEnv: "", stageNameJSON: ""},
|
|
{name: "stage name arg+env", stageNameArg: "arg", stageNameEnv: "env", stageNameJSON: "json"},
|
|
{name: "stage name env", stageNameArg: "", stageNameEnv: "env", stageNameJSON: "json"},
|
|
{name: "stage name json", stageNameArg: "", stageNameEnv: "", stageNameJSON: "json"},
|
|
{name: "stage name arg", stageNameArg: "arg", stageNameEnv: "", stageNameJSON: "json"},
|
|
}
|
|
|
|
for _, test := range tt {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
// init
|
|
defer resetEnv(os.Environ())
|
|
os.Clearenv()
|
|
orchestrator.ResetConfigProvider()
|
|
|
|
//mock Jenkins env
|
|
os.Setenv("JENKINS_HOME", "anything")
|
|
require.NotEmpty(t, os.Getenv("JENKINS_HOME"))
|
|
os.Setenv("STAGE_NAME", test.stageNameEnv)
|
|
|
|
GeneralConfig.StageName = test.stageNameArg
|
|
|
|
if test.stageNameJSON != "" {
|
|
GeneralConfig.ParametersJSON = fmt.Sprintf("{\"stageName\":\"%s\"}", test.stageNameJSON)
|
|
} else {
|
|
GeneralConfig.ParametersJSON = "{}"
|
|
}
|
|
// test
|
|
initStageName(false)
|
|
|
|
// assert
|
|
// Order of if-clauses reflects wanted precedence.
|
|
if test.stageNameArg != "" {
|
|
assert.Equal(t, test.stageNameArg, GeneralConfig.StageName)
|
|
} else if test.stageNameJSON != "" {
|
|
assert.Equal(t, test.stageNameJSON, GeneralConfig.StageName)
|
|
} else if test.stageNameEnv != "" {
|
|
assert.Equal(t, test.stageNameEnv, GeneralConfig.StageName)
|
|
} else {
|
|
assert.Equal(t, "", GeneralConfig.StageName)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
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 occurred")
|
|
|
|
//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 occurred")
|
|
})
|
|
})
|
|
}
|
|
|
|
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"}}},
|
|
{hookJSON: []byte(`{"sentry":{"dsn":"https://my.sentry.dsn"}, "splunk":{"dsn":"https://my.splunk.dsn", "token": "mytoken", "index": "myindex", "sendLogs": true}}`),
|
|
expectedHookConfig: HookConfiguration{SentryConfig: SentryConfiguration{Dsn: "https://my.sentry.dsn"},
|
|
SplunkConfig: SplunkConfiguration{
|
|
Dsn: "https://my.splunk.dsn",
|
|
Token: "mytoken",
|
|
Index: "myindex",
|
|
SendLogs: true,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, test := range tt {
|
|
var target HookConfiguration
|
|
var hookJSONinterface map[string]interface{}
|
|
if len(test.hookJSON) > 0 {
|
|
err := json.Unmarshal(test.hookJSON, &hookJSONinterface)
|
|
assert.NoError(t, err)
|
|
}
|
|
retrieveHookConfig(hookJSONinterface, &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 := t.TempDir()
|
|
|
|
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 {
|
|
if err := os.WriteFile(filepath.Join(dir, file), []byte("general:"), 0700); err != nil {
|
|
t.Fail()
|
|
}
|
|
}
|
|
|
|
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
|
|
_ = 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("Exits in case number beyond length", 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{}{}
|
|
|
|
content := []byte("foo: 73554900100200011600")
|
|
err := yaml.Unmarshal(content, &stepConfig)
|
|
assert.NoError(t, err)
|
|
|
|
// Test
|
|
stepConfig = checkTypes(stepConfig, options)
|
|
|
|
// Assert
|
|
assert.True(t, hasFailed, "Expected checkTypes() to exit via logging framework")
|
|
})
|
|
t.Run("Properly handle small ints", 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{}{}
|
|
|
|
content := []byte("foo: 11")
|
|
err := yaml.Unmarshal(content, &stepConfig)
|
|
assert.NoError(t, err)
|
|
|
|
// Test
|
|
stepConfig = checkTypes(stepConfig, options)
|
|
|
|
// Assert
|
|
assert.False(t, hasFailed, "Expected checkTypes() NOT 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")
|
|
})
|
|
}
|
|
|
|
func TestResolveAccessTokens(t *testing.T) {
|
|
tt := []struct {
|
|
description string
|
|
tokenList []string
|
|
expectedTokenMap map[string]string
|
|
}{
|
|
{description: "empty tokens", tokenList: []string{}, expectedTokenMap: map[string]string{}},
|
|
{description: "invalid token", tokenList: []string{"onlyToken"}, expectedTokenMap: map[string]string{}},
|
|
{description: "one token", tokenList: []string{"github.com:token1"}, expectedTokenMap: map[string]string{"github.com": "token1"}},
|
|
{description: "more tokens", tokenList: []string{"github.com:token1", "github.corp:token2"}, expectedTokenMap: map[string]string{"github.com": "token1", "github.corp": "token2"}},
|
|
}
|
|
|
|
for _, test := range tt {
|
|
assert.Equal(t, test.expectedTokenMap, ResolveAccessTokens(test.tokenList), test.description)
|
|
}
|
|
}
|
|
|
|
func TestAccessTokensFromEnvJSON(t *testing.T) {
|
|
tt := []struct {
|
|
description string
|
|
inputJSON string
|
|
expectedTokenList []string
|
|
}{
|
|
{description: "empty ENV", inputJSON: "", expectedTokenList: []string{}},
|
|
{description: "invalid JSON", inputJSON: "{", expectedTokenList: []string{}},
|
|
{description: "empty JSON 1", inputJSON: "{}", expectedTokenList: []string{}},
|
|
{description: "empty JSON 2", inputJSON: "[]]", expectedTokenList: []string{}},
|
|
{description: "invalid JSON format", inputJSON: `{"test":"test"}`, expectedTokenList: []string{}},
|
|
{description: "one token", inputJSON: `["github.com:token1"]`, expectedTokenList: []string{"github.com:token1"}},
|
|
{description: "more tokens", inputJSON: `["github.com:token1","github.corp:token2"]`, expectedTokenList: []string{"github.com:token1", "github.corp:token2"}},
|
|
}
|
|
|
|
for _, test := range tt {
|
|
assert.Equal(t, test.expectedTokenList, AccessTokensFromEnvJSON(test.inputJSON), test.description)
|
|
}
|
|
}
|