github.com/xgoffin/jenkins-library@v1.154.0/cmd/piper_test.go (about) 1 package cmd 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "io/ioutil" 8 "os" 9 "path/filepath" 10 "strings" 11 "testing" 12 13 "github.com/ghodss/yaml" 14 "github.com/spf13/cobra" 15 flag "github.com/spf13/pflag" 16 "github.com/stretchr/testify/assert" 17 "github.com/stretchr/testify/require" 18 19 "github.com/SAP/jenkins-library/pkg/config" 20 "github.com/SAP/jenkins-library/pkg/log" 21 "github.com/SAP/jenkins-library/pkg/mock" 22 ) 23 24 func resetEnv(e []string) { 25 for _, val := range e { 26 tmp := strings.Split(val, "=") 27 os.Setenv(tmp[0], tmp[1]) 28 } 29 } 30 31 func TestAddRootFlags(t *testing.T) { 32 var testRootCmd = &cobra.Command{Use: "test", Short: "This is just a test"} 33 addRootFlags(testRootCmd) 34 35 assert.NotNil(t, testRootCmd.Flag("customConfig"), "expected flag not available") 36 assert.NotNil(t, testRootCmd.Flag("defaultConfig"), "expected flag not available") 37 assert.NotNil(t, testRootCmd.Flag("parametersJSON"), "expected flag not available") 38 assert.NotNil(t, testRootCmd.Flag("stageName"), "expected flag not available") 39 assert.NotNil(t, testRootCmd.Flag("stepConfigJSON"), "expected flag not available") 40 assert.NotNil(t, testRootCmd.Flag("verbose"), "expected flag not available") 41 42 } 43 44 func TestAdoptStageNameFromParametersJSON(t *testing.T) { 45 tt := []struct { 46 name string 47 stageNameArg string 48 stageNameEnv string 49 stageNameJSON string 50 }{ 51 {name: "no stage name", stageNameArg: "", stageNameEnv: "", stageNameJSON: ""}, 52 {name: "stage name arg+env", stageNameArg: "arg", stageNameEnv: "env", stageNameJSON: "json"}, 53 {name: "stage name env", stageNameArg: "", stageNameEnv: "env", stageNameJSON: "json"}, 54 {name: "stage name json", stageNameArg: "", stageNameEnv: "", stageNameJSON: "json"}, 55 {name: "stage name arg", stageNameArg: "arg", stageNameEnv: "", stageNameJSON: "json"}, 56 } 57 58 for _, test := range tt { 59 t.Run(test.name, func(t *testing.T) { 60 // init 61 defer resetEnv(os.Environ()) 62 os.Clearenv() 63 64 //mock Jenkins env 65 os.Setenv("JENKINS_HOME", "anything") 66 require.NotEmpty(t, os.Getenv("JENKINS_HOME")) 67 os.Setenv("STAGE_NAME", test.stageNameEnv) 68 69 GeneralConfig.StageName = test.stageNameArg 70 71 if test.stageNameJSON != "" { 72 GeneralConfig.ParametersJSON = fmt.Sprintf("{\"stageName\":\"%s\"}", test.stageNameJSON) 73 } else { 74 GeneralConfig.ParametersJSON = "{}" 75 } 76 // test 77 initStageName(false) 78 79 // assert 80 // Order of if-clauses reflects wanted precedence. 81 if test.stageNameArg != "" { 82 assert.Equal(t, test.stageNameArg, GeneralConfig.StageName) 83 } else if test.stageNameJSON != "" { 84 assert.Equal(t, test.stageNameJSON, GeneralConfig.StageName) 85 } else if test.stageNameEnv != "" { 86 assert.Equal(t, test.stageNameEnv, GeneralConfig.StageName) 87 } else { 88 assert.Equal(t, "", GeneralConfig.StageName) 89 } 90 }) 91 } 92 } 93 94 func TestPrepareConfig(t *testing.T) { 95 defaultsBak := GeneralConfig.DefaultConfig 96 GeneralConfig.DefaultConfig = []string{"testDefaults.yml"} 97 defer func() { GeneralConfig.DefaultConfig = defaultsBak }() 98 99 t.Run("using stepConfigJSON", func(t *testing.T) { 100 stepConfigJSONBak := GeneralConfig.StepConfigJSON 101 GeneralConfig.StepConfigJSON = `{"testParam": "testValueJSON"}` 102 defer func() { GeneralConfig.StepConfigJSON = stepConfigJSONBak }() 103 testOptions := mock.StepOptions{} 104 var testCmd = &cobra.Command{Use: "test", Short: "This is just a test"} 105 testCmd.Flags().StringVar(&testOptions.TestParam, "testParam", "", "test usage") 106 metadata := config.StepData{ 107 Spec: config.StepSpec{ 108 Inputs: config.StepInputs{ 109 Parameters: []config.StepParameters{ 110 {Name: "testParam", Scope: []string{"GENERAL"}}, 111 }, 112 }, 113 }, 114 } 115 116 PrepareConfig(testCmd, &metadata, "testStep", &testOptions, mock.OpenFileMock) 117 assert.Equal(t, "testValueJSON", testOptions.TestParam, "wrong value retrieved from config") 118 }) 119 120 t.Run("using config files", func(t *testing.T) { 121 t.Run("success case", func(t *testing.T) { 122 testOptions := mock.StepOptions{} 123 var testCmd = &cobra.Command{Use: "test", Short: "This is just a test"} 124 testCmd.Flags().StringVar(&testOptions.TestParam, "testParam", "", "test usage") 125 metadata := config.StepData{ 126 Spec: config.StepSpec{ 127 Inputs: config.StepInputs{ 128 Parameters: []config.StepParameters{ 129 {Name: "testParam", Scope: []string{"GENERAL"}}, 130 }, 131 }, 132 }, 133 } 134 135 err := PrepareConfig(testCmd, &metadata, "testStep", &testOptions, mock.OpenFileMock) 136 assert.NoError(t, err, "no error expected but error occurred") 137 138 //assert config 139 assert.Equal(t, "testValue", testOptions.TestParam, "wrong value retrieved from config") 140 141 //assert that flag has been marked as changed 142 testCmd.Flags().VisitAll(func(pflag *flag.Flag) { 143 if pflag.Name == "testParam" { 144 assert.True(t, pflag.Changed, "flag should be marked as changed") 145 } 146 }) 147 }) 148 149 t.Run("error case", func(t *testing.T) { 150 GeneralConfig.DefaultConfig = []string{"testDefaultsInvalid.yml"} 151 testOptions := mock.StepOptions{} 152 var testCmd = &cobra.Command{Use: "test", Short: "This is just a test"} 153 metadata := config.StepData{} 154 155 err := PrepareConfig(testCmd, &metadata, "testStep", &testOptions, mock.OpenFileMock) 156 assert.Error(t, err, "error expected but none occurred") 157 }) 158 }) 159 } 160 161 func TestRetrieveHookConfig(t *testing.T) { 162 tt := []struct { 163 hookJSON []byte 164 expectedHookConfig HookConfiguration 165 }{ 166 {hookJSON: []byte(""), expectedHookConfig: HookConfiguration{}}, 167 {hookJSON: []byte(`{"sentry":{"dsn":"https://my.sentry.dsn"}}`), expectedHookConfig: HookConfiguration{SentryConfig: SentryConfiguration{Dsn: "https://my.sentry.dsn"}}}, 168 {hookJSON: []byte(`{"sentry":{"dsn":"https://my.sentry.dsn"}, "splunk":{"dsn":"https://my.splunk.dsn", "token": "mytoken", "index": "myindex", "sendLogs": true}}`), 169 expectedHookConfig: HookConfiguration{SentryConfig: SentryConfiguration{Dsn: "https://my.sentry.dsn"}, 170 SplunkConfig: SplunkConfiguration{ 171 Dsn: "https://my.splunk.dsn", 172 Token: "mytoken", 173 Index: "myindex", 174 SendLogs: true, 175 }, 176 }, 177 }, 178 } 179 180 for _, test := range tt { 181 var target HookConfiguration 182 var hookJSONinterface map[string]interface{} 183 if len(test.hookJSON) > 0 { 184 err := json.Unmarshal(test.hookJSON, &hookJSONinterface) 185 assert.NoError(t, err) 186 } 187 retrieveHookConfig(hookJSONinterface, &target) 188 assert.Equal(t, test.expectedHookConfig, target) 189 } 190 } 191 192 func TestGetProjectConfigFile(t *testing.T) { 193 194 tt := []struct { 195 filename string 196 filesAvailable []string 197 expected string 198 }{ 199 {filename: ".pipeline/config.yml", filesAvailable: []string{}, expected: ".pipeline/config.yml"}, 200 {filename: ".pipeline/config.yml", filesAvailable: []string{".pipeline/config.yml"}, expected: ".pipeline/config.yml"}, 201 {filename: ".pipeline/config.yml", filesAvailable: []string{".pipeline/config.yaml"}, expected: ".pipeline/config.yaml"}, 202 {filename: ".pipeline/config.yaml", filesAvailable: []string{".pipeline/config.yml", ".pipeline/config.yaml"}, expected: ".pipeline/config.yaml"}, 203 {filename: ".pipeline/config.yml", filesAvailable: []string{".pipeline/config.yml", ".pipeline/config.yaml"}, expected: ".pipeline/config.yml"}, 204 } 205 206 for run, test := range tt { 207 t.Run(fmt.Sprintf("Run %v", run), func(t *testing.T) { 208 dir, err := ioutil.TempDir("", "") 209 defer os.RemoveAll(dir) // clean up 210 assert.NoError(t, err) 211 212 if len(test.filesAvailable) > 0 { 213 configFolder := filepath.Join(dir, filepath.Dir(test.filesAvailable[0])) 214 err = os.MkdirAll(configFolder, 0700) 215 assert.NoError(t, err) 216 } 217 218 for _, file := range test.filesAvailable { 219 ioutil.WriteFile(filepath.Join(dir, file), []byte("general:"), 0700) 220 } 221 222 assert.Equal(t, filepath.Join(dir, test.expected), getProjectConfigFile(filepath.Join(dir, test.filename))) 223 }) 224 } 225 } 226 227 func TestConvertTypes(t *testing.T) { 228 t.Run("Converts strings to booleans", func(t *testing.T) { 229 // Init 230 hasFailed := false 231 232 exitFunc := log.Entry().Logger.ExitFunc 233 log.Entry().Logger.ExitFunc = func(int) { 234 hasFailed = true 235 } 236 defer func() { log.Entry().Logger.ExitFunc = exitFunc }() 237 238 options := struct { 239 Foo bool `json:"foo,omitempty"` 240 Bar bool `json:"bar,omitempty"` 241 }{} 242 options.Foo = true 243 options.Bar = false 244 245 stepConfig := map[string]interface{}{} 246 stepConfig["foo"] = "False" 247 stepConfig["bar"] = "True" 248 249 // Test 250 stepConfig = checkTypes(stepConfig, options) 251 252 confJSON, _ := json.Marshal(stepConfig) 253 _ = json.Unmarshal(confJSON, &options) 254 255 // Assert 256 assert.Equal(t, false, stepConfig["foo"]) 257 assert.Equal(t, true, stepConfig["bar"]) 258 assert.Equal(t, false, options.Foo) 259 assert.Equal(t, true, options.Bar) 260 assert.False(t, hasFailed, "Expected checkTypes() NOT to exit via logging framework") 261 }) 262 t.Run("Converts numbers to strings", func(t *testing.T) { 263 // Init 264 hasFailed := false 265 266 exitFunc := log.Entry().Logger.ExitFunc 267 log.Entry().Logger.ExitFunc = func(int) { 268 hasFailed = true 269 } 270 defer func() { log.Entry().Logger.ExitFunc = exitFunc }() 271 272 options := struct { 273 Foo string `json:"foo,omitempty"` 274 Bar string `json:"bar,omitempty"` 275 }{} 276 277 stepConfig := map[string]interface{}{} 278 stepConfig["foo"] = 1.5 279 stepConfig["bar"] = 42 280 281 // Test 282 stepConfig = checkTypes(stepConfig, options) 283 284 confJSON, _ := json.Marshal(stepConfig) 285 _ = json.Unmarshal(confJSON, &options) 286 287 // Assert 288 assert.Equal(t, "1.5", stepConfig["foo"]) 289 assert.Equal(t, "42", stepConfig["bar"]) 290 assert.Equal(t, "1.5", options.Foo) 291 assert.Equal(t, "42", options.Bar) 292 assert.False(t, hasFailed, "Expected checkTypes() NOT to exit via logging framework") 293 }) 294 t.Run("Keeps numbers", func(t *testing.T) { 295 // Init 296 hasFailed := false 297 298 exitFunc := log.Entry().Logger.ExitFunc 299 log.Entry().Logger.ExitFunc = func(int) { 300 hasFailed = true 301 } 302 defer func() { log.Entry().Logger.ExitFunc = exitFunc }() 303 304 options := struct { 305 Foo int `json:"foo,omitempty"` 306 Bar float32 `json:"bar,omitempty"` 307 }{} 308 309 stepConfig := map[string]interface{}{} 310 311 content := []byte(` 312 foo: 1 313 bar: 42 314 `) 315 err := yaml.Unmarshal(content, &stepConfig) 316 assert.NoError(t, err) 317 318 // Test 319 stepConfig = checkTypes(stepConfig, options) 320 321 confJSON, _ := json.Marshal(stepConfig) 322 _ = json.Unmarshal(confJSON, &options) 323 324 // Assert 325 assert.Equal(t, 1, stepConfig["foo"]) 326 assert.Equal(t, float32(42.0), stepConfig["bar"]) 327 assert.Equal(t, 1, options.Foo) 328 assert.Equal(t, float32(42.0), options.Bar) 329 assert.False(t, hasFailed, "Expected checkTypes() NOT to exit via logging framework") 330 }) 331 t.Run("Exits because string found, slice expected", func(t *testing.T) { 332 // Init 333 hasFailed := false 334 335 exitFunc := log.Entry().Logger.ExitFunc 336 log.Entry().Logger.ExitFunc = func(int) { 337 hasFailed = true 338 } 339 defer func() { log.Entry().Logger.ExitFunc = exitFunc }() 340 341 options := struct { 342 Foo []string `json:"foo,omitempty"` 343 }{} 344 345 stepConfig := map[string]interface{}{} 346 stepConfig["foo"] = "entry" 347 348 // Test 349 stepConfig = checkTypes(stepConfig, options) 350 351 // Assert 352 assert.True(t, hasFailed, "Expected checkTypes() to exit via logging framework") 353 }) 354 t.Run("Exits because float found, int expected", func(t *testing.T) { 355 // Init 356 hasFailed := false 357 358 exitFunc := log.Entry().Logger.ExitFunc 359 log.Entry().Logger.ExitFunc = func(int) { 360 hasFailed = true 361 } 362 defer func() { log.Entry().Logger.ExitFunc = exitFunc }() 363 364 options := struct { 365 Foo int `json:"foo,omitempty"` 366 }{} 367 368 stepConfig := map[string]interface{}{} 369 370 content := []byte("foo: 1.1") 371 err := yaml.Unmarshal(content, &stepConfig) 372 assert.NoError(t, err) 373 374 // Test 375 stepConfig = checkTypes(stepConfig, options) 376 377 // Assert 378 assert.Equal(t, 1.1, stepConfig["foo"]) 379 assert.True(t, hasFailed, "Expected checkTypes() to exit via logging framework") 380 }) 381 t.Run("Exits in case number beyond length", func(t *testing.T) { 382 // Init 383 hasFailed := false 384 385 exitFunc := log.Entry().Logger.ExitFunc 386 log.Entry().Logger.ExitFunc = func(int) { 387 hasFailed = true 388 } 389 defer func() { log.Entry().Logger.ExitFunc = exitFunc }() 390 391 options := struct { 392 Foo string `json:"foo,omitempty"` 393 }{} 394 395 stepConfig := map[string]interface{}{} 396 397 content := []byte("foo: 73554900100200011600") 398 err := yaml.Unmarshal(content, &stepConfig) 399 assert.NoError(t, err) 400 401 // Test 402 stepConfig = checkTypes(stepConfig, options) 403 404 // Assert 405 assert.True(t, hasFailed, "Expected checkTypes() to exit via logging framework") 406 }) 407 t.Run("Properly handle small ints", func(t *testing.T) { 408 // Init 409 hasFailed := false 410 411 exitFunc := log.Entry().Logger.ExitFunc 412 log.Entry().Logger.ExitFunc = func(int) { 413 hasFailed = true 414 } 415 defer func() { log.Entry().Logger.ExitFunc = exitFunc }() 416 417 options := struct { 418 Foo string `json:"foo,omitempty"` 419 }{} 420 421 stepConfig := map[string]interface{}{} 422 423 content := []byte("foo: 11") 424 err := yaml.Unmarshal(content, &stepConfig) 425 assert.NoError(t, err) 426 427 // Test 428 stepConfig = checkTypes(stepConfig, options) 429 430 // Assert 431 assert.False(t, hasFailed, "Expected checkTypes() NOT to exit via logging framework") 432 }) 433 t.Run("Ignores nil values", func(t *testing.T) { 434 // Init 435 hasFailed := false 436 437 exitFunc := log.Entry().Logger.ExitFunc 438 log.Entry().Logger.ExitFunc = func(int) { 439 hasFailed = true 440 } 441 defer func() { log.Entry().Logger.ExitFunc = exitFunc }() 442 443 options := struct { 444 Foo []string `json:"foo,omitempty"` 445 Bar string `json:"bar,omitempty"` 446 }{} 447 448 stepConfig := map[string]interface{}{} 449 stepConfig["foo"] = nil 450 stepConfig["bar"] = nil 451 452 // Test 453 stepConfig = checkTypes(stepConfig, options) 454 confJSON, _ := json.Marshal(stepConfig) 455 _ = json.Unmarshal(confJSON, &options) 456 457 // Assert 458 assert.Nil(t, stepConfig["foo"]) 459 assert.Nil(t, stepConfig["bar"]) 460 assert.Equal(t, []string(nil), options.Foo) 461 assert.Equal(t, "", options.Bar) 462 assert.False(t, hasFailed, "Expected checkTypes() NOT to exit via logging framework") 463 }) 464 t.Run("Logs warning for unknown type-mismatches", func(t *testing.T) { 465 // Init 466 hasFailed := false 467 468 exitFunc := log.Entry().Logger.ExitFunc 469 log.Entry().Logger.ExitFunc = func(int) { 470 hasFailed = true 471 } 472 defer func() { log.Entry().Logger.ExitFunc = exitFunc }() 473 474 logBuffer := new(bytes.Buffer) 475 476 logOutput := log.Entry().Logger.Out 477 log.Entry().Logger.Out = logBuffer 478 defer func() { log.Entry().Logger.Out = logOutput }() 479 480 options := struct { 481 Foo string `json:"foo,omitempty"` 482 }{} 483 484 stepConfig := map[string]interface{}{} 485 stepConfig["foo"] = true 486 487 // Test 488 stepConfig = checkTypes(stepConfig, options) 489 confJSON, _ := json.Marshal(stepConfig) 490 _ = json.Unmarshal(confJSON, &options) 491 492 // Assert 493 assert.Equal(t, true, stepConfig["foo"]) 494 assert.Equal(t, "", options.Foo) 495 assert.Contains(t, logBuffer.String(), "The value may be ignored as a result") 496 assert.False(t, hasFailed, "Expected checkTypes() NOT to exit via logging framework") 497 }) 498 } 499 500 func TestResolveAccessTokens(t *testing.T) { 501 tt := []struct { 502 description string 503 tokenList []string 504 expectedTokenMap map[string]string 505 }{ 506 {description: "empty tokens", tokenList: []string{}, expectedTokenMap: map[string]string{}}, 507 {description: "invalid token", tokenList: []string{"onlyToken"}, expectedTokenMap: map[string]string{}}, 508 {description: "one token", tokenList: []string{"github.com:token1"}, expectedTokenMap: map[string]string{"github.com": "token1"}}, 509 {description: "more tokens", tokenList: []string{"github.com:token1", "github.corp:token2"}, expectedTokenMap: map[string]string{"github.com": "token1", "github.corp": "token2"}}, 510 } 511 512 for _, test := range tt { 513 assert.Equal(t, test.expectedTokenMap, ResolveAccessTokens(test.tokenList), test.description) 514 } 515 } 516 517 func TestAccessTokensFromEnvJSON(t *testing.T) { 518 tt := []struct { 519 description string 520 inputJSON string 521 expectedTokenList []string 522 }{ 523 {description: "empty ENV", inputJSON: "", expectedTokenList: []string{}}, 524 {description: "invalid JSON", inputJSON: "{", expectedTokenList: []string{}}, 525 {description: "empty JSON 1", inputJSON: "{}", expectedTokenList: []string{}}, 526 {description: "empty JSON 2", inputJSON: "[]]", expectedTokenList: []string{}}, 527 {description: "invalid JSON format", inputJSON: `{"test":"test"}`, expectedTokenList: []string{}}, 528 {description: "one token", inputJSON: `["github.com:token1"]`, expectedTokenList: []string{"github.com:token1"}}, 529 {description: "more tokens", inputJSON: `["github.com:token1","github.corp:token2"]`, expectedTokenList: []string{"github.com:token1", "github.corp:token2"}}, 530 } 531 532 for _, test := range tt { 533 assert.Equal(t, test.expectedTokenList, AccessTokensFromEnvJSON(test.inputJSON), test.description) 534 } 535 }