github.com/jaylevin/jenkins-library@v1.230.4/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  }