github.com/ouraigua/jenkins-library@v0.0.0-20231028010029-fbeaf2f3aa9b/cmd/cloudFoundryDeploy_test.go (about)

     1  //go:build unit
     2  // +build unit
     3  
     4  package cmd
     5  
     6  import (
     7  	"fmt"
     8  	"os"
     9  	"path/filepath"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/SAP/jenkins-library/pkg/cloudfoundry"
    14  	"github.com/SAP/jenkins-library/pkg/command"
    15  	"github.com/SAP/jenkins-library/pkg/mock"
    16  	"github.com/SAP/jenkins-library/pkg/piperutils"
    17  	"github.com/SAP/jenkins-library/pkg/yaml"
    18  	"github.com/stretchr/testify/assert"
    19  )
    20  
    21  type manifestMock struct {
    22  	manifestFileName string
    23  	apps             []map[string]interface{}
    24  }
    25  
    26  func (m manifestMock) GetAppName(index int) (string, error) {
    27  	val, err := m.GetApplicationProperty(index, "name")
    28  	if err != nil {
    29  		return "", err
    30  	}
    31  	if v, ok := val.(string); ok {
    32  		return v, nil
    33  	}
    34  	return "", fmt.Errorf("Cannot resolve application name")
    35  }
    36  func (m manifestMock) ApplicationHasProperty(index int, name string) (bool, error) {
    37  	_, exists := m.apps[index][name]
    38  	return exists, nil
    39  }
    40  func (m manifestMock) GetApplicationProperty(index int, name string) (interface{}, error) {
    41  	return m.apps[index][name], nil
    42  }
    43  func (m manifestMock) GetFileName() string {
    44  	return m.manifestFileName
    45  }
    46  func (m manifestMock) Transform() error {
    47  	return nil
    48  }
    49  func (m manifestMock) IsModified() bool {
    50  	return false
    51  }
    52  func (m manifestMock) GetApplications() ([]map[string]interface{}, error) {
    53  	return m.apps, nil
    54  }
    55  func (m manifestMock) WriteManifest() error {
    56  	return nil
    57  }
    58  
    59  func TestCfDeployment(t *testing.T) {
    60  
    61  	defer func() {
    62  		fileUtils = &piperutils.Files{}
    63  		_replaceVariables = yaml.Substitute
    64  	}()
    65  
    66  	filesMock := mock.FilesMock{}
    67  	filesMock.AddDir("/home/me")
    68  	err := filesMock.Chdir("/home/me")
    69  	assert.NoError(t, err)
    70  	fileUtils = &filesMock
    71  
    72  	// everything below in the config map annotated with '//default' is a default in the metadata
    73  	// since we don't get injected these values during the tests we set it here.
    74  	defaultConfig := cloudFoundryDeployOptions{
    75  		Org:                 "myOrg",
    76  		Space:               "mySpace",
    77  		Username:            "me",
    78  		Password:            "******",
    79  		APIEndpoint:         "https://examples.sap.com/cf",
    80  		SmokeTestStatusCode: 200,            // default
    81  		Manifest:            "manifest.yml", //default
    82  		MtaDeployParameters: "-f",           // default
    83  		DeployType:          "standard",     // default
    84  	}
    85  
    86  	config := defaultConfig
    87  
    88  	successfulLogin := cloudfoundry.LoginOptions{
    89  		CfAPIEndpoint: "https://examples.sap.com/cf",
    90  		CfOrg:         "myOrg",
    91  		CfSpace:       "mySpace",
    92  		Username:      "me",
    93  		Password:      "******",
    94  		CfLoginOpts:   []string{},
    95  	}
    96  
    97  	var loginOpts cloudfoundry.LoginOptions
    98  	var logoutCalled bool
    99  
   100  	noopCfAPICalls := func(t *testing.T, s mock.ExecMockRunner) {
   101  		assert.Empty(t, s.Calls)   // --> in case of an invalid deploy tool there must be no cf api calls
   102  		assert.Empty(t, loginOpts) // no login options: login has not been called
   103  		assert.False(t, logoutCalled)
   104  	}
   105  
   106  	prepareDefaultManifestMocking := func(manifestName string, appNames []string) func() {
   107  
   108  		filesMock.AddFile(manifestName, []byte("file content does not matter"))
   109  
   110  		apps := []map[string]interface{}{}
   111  
   112  		for _, appName := range appNames {
   113  			apps = append(apps, map[string]interface{}{"name": appName})
   114  		}
   115  
   116  		_getManifest = func(name string) (cloudfoundry.Manifest, error) {
   117  			return manifestMock{
   118  				manifestFileName: manifestName,
   119  				apps:             apps,
   120  			}, nil
   121  		}
   122  
   123  		return func() {
   124  			_ = filesMock.FileRemove(manifestName) // slightly mis-use since that is intended to be used by code under test, not test code
   125  			_getManifest = getManifest
   126  		}
   127  	}
   128  
   129  	withLoginAndLogout := func(t *testing.T, asserts func(t *testing.T)) {
   130  		assert.Equal(t, successfulLogin, loginOpts)
   131  		asserts(t)
   132  		assert.True(t, logoutCalled)
   133  	}
   134  
   135  	cleanup := func() {
   136  		loginOpts = cloudfoundry.LoginOptions{}
   137  		logoutCalled = false
   138  		config = defaultConfig
   139  	}
   140  
   141  	defer func() {
   142  		_cfLogin = cfLogin
   143  		_cfLogout = cfLogout
   144  	}()
   145  
   146  	_cfLogin = func(c command.ExecRunner, opts cloudfoundry.LoginOptions) error {
   147  		loginOpts = opts
   148  		return nil
   149  	}
   150  
   151  	_cfLogout = func(c command.ExecRunner) error {
   152  		logoutCalled = true
   153  		return nil
   154  	}
   155  
   156  	_replaceVariables = func(manifest string, replacements map[string]interface{}, replacementsFiles []string) (bool, error) {
   157  		return false, nil
   158  	}
   159  
   160  	t.Run("Test invalid appname", func(t *testing.T) {
   161  
   162  		defer cleanup()
   163  		config.AppName = "a_z"
   164  		s := mock.ExecMockRunner{}
   165  		err := runCloudFoundryDeploy(&config, nil, nil, &s)
   166  
   167  		assert.EqualError(t, err, "Your application name 'a_z' contains a '_' (underscore) which is not allowed, only letters, dashes and numbers can be used. Please change the name to fit this requirement(s). For more details please visit https://docs.cloudfoundry.org/devguide/deploy-apps/deploy-app.html#basic-settings.")
   168  	})
   169  
   170  	t.Run("Manifest substitution", func(t *testing.T) {
   171  
   172  		defer func() {
   173  			cleanup()
   174  			_replaceVariables = func(manifest string, replacements map[string]interface{}, replacementsFiles []string) (bool, error) {
   175  				return false, nil
   176  			}
   177  		}()
   178  
   179  		s := mock.ExecMockRunner{}
   180  
   181  		var manifestForSubstitution string
   182  		var replacements map[string]interface{}
   183  		var replacementFiles []string
   184  
   185  		defer prepareDefaultManifestMocking("substitute-manifest.yml", []string{"testAppName"})()
   186  		config.DeployTool = "cf_native"
   187  		config.DeployType = "blue-green"
   188  		config.AppName = "myApp"
   189  		config.Manifest = "substitute-manifest.yml"
   190  
   191  		_replaceVariables = func(manifest string, _replacements map[string]interface{}, _replacementsFiles []string) (bool, error) {
   192  			manifestForSubstitution = manifest
   193  			replacements = _replacements
   194  			replacementFiles = _replacementsFiles
   195  			return false, nil
   196  		}
   197  
   198  		t.Run("straight forward", func(t *testing.T) {
   199  
   200  			defer func() {
   201  				config.ManifestVariables = []string{}
   202  				config.ManifestVariablesFiles = []string{}
   203  			}()
   204  
   205  			config.ManifestVariables = []string{"k1=v1"}
   206  			config.ManifestVariablesFiles = []string{"myVars.yml"}
   207  
   208  			err := runCloudFoundryDeploy(&config, nil, nil, &s)
   209  
   210  			if assert.NoError(t, err) {
   211  				assert.Equal(t, "substitute-manifest.yml", manifestForSubstitution)
   212  				assert.Equal(t, map[string]interface{}{"k1": "v1"}, replacements)
   213  				assert.Equal(t, []string{"myVars.yml"}, replacementFiles)
   214  			}
   215  		})
   216  
   217  		t.Run("empty", func(t *testing.T) {
   218  
   219  			defer func() {
   220  				config.ManifestVariables = []string{}
   221  				config.ManifestVariablesFiles = []string{}
   222  			}()
   223  
   224  			config.ManifestVariables = []string{}
   225  			config.ManifestVariablesFiles = []string{}
   226  
   227  			err := runCloudFoundryDeploy(&config, nil, nil, &s)
   228  
   229  			if assert.NoError(t, err) {
   230  				assert.Equal(t, "substitute-manifest.yml", manifestForSubstitution)
   231  				assert.Equal(t, map[string]interface{}{}, replacements)
   232  				assert.Equal(t, []string{}, replacementFiles)
   233  			}
   234  		})
   235  	})
   236  
   237  	t.Run("Invalid deploytool", func(t *testing.T) {
   238  
   239  		defer cleanup()
   240  
   241  		s := mock.ExecMockRunner{}
   242  
   243  		config.DeployTool = "invalid"
   244  
   245  		err := runCloudFoundryDeploy(&config, nil, nil, &s)
   246  
   247  		if assert.NoError(t, err) {
   248  			noopCfAPICalls(t, s)
   249  		}
   250  	})
   251  
   252  	t.Run("deploytool cf native", func(t *testing.T) {
   253  
   254  		defer cleanup()
   255  
   256  		defer prepareDefaultManifestMocking("manifest.yml", []string{"testAppName"})()
   257  
   258  		config.DeployTool = "cf_native"
   259  		config.CfHome = "/home/me1"
   260  		config.CfPluginHome = "/home/me2"
   261  
   262  		s := mock.ExecMockRunner{}
   263  
   264  		err := runCloudFoundryDeploy(&config, nil, nil, &s)
   265  
   266  		if assert.NoError(t, err) {
   267  
   268  			t.Run("check cf api calls", func(t *testing.T) {
   269  
   270  				withLoginAndLogout(t, func(t *testing.T) {
   271  					assert.Equal(t, []mock.ExecCall{
   272  						{Exec: "cf", Params: []string{"version"}},
   273  						{Exec: "cf", Params: []string{"plugins"}},
   274  						{Exec: "cf", Params: []string{"push", "-f", "manifest.yml"}},
   275  					}, s.Calls)
   276  				})
   277  			})
   278  
   279  			t.Run("check environment variables", func(t *testing.T) {
   280  				assert.Contains(t, s.Env, "CF_HOME=/home/me1")
   281  				assert.Contains(t, s.Env, "CF_PLUGIN_HOME=/home/me2")
   282  				assert.Contains(t, s.Env, "STATUS_CODE=200")
   283  			})
   284  		}
   285  	})
   286  
   287  	t.Run("influx reporting", func(t *testing.T) {
   288  
   289  		defer cleanup()
   290  
   291  		s := mock.ExecMockRunner{}
   292  
   293  		defer func() {
   294  			_now = time.Now
   295  		}()
   296  
   297  		_now = func() time.Time {
   298  			// There was the big eclipse in Karlsruhe
   299  			return time.Date(1999, time.August, 11, 12, 32, 0, 0, time.UTC)
   300  		}
   301  
   302  		defer prepareDefaultManifestMocking("manifest.yml", []string{"testAppName"})()
   303  
   304  		config.DeployTool = "cf_native"
   305  		config.ArtifactVersion = "0.1.2"
   306  		config.CommitHash = "123456"
   307  
   308  		influxData := cloudFoundryDeployInflux{}
   309  
   310  		err := runCloudFoundryDeploy(&config, nil, &influxData, &s)
   311  
   312  		if assert.NoError(t, err) {
   313  
   314  			expected := cloudFoundryDeployInflux{}
   315  
   316  			expected.deployment_data.fields.artifactURL = "n/a"
   317  			expected.deployment_data.fields.deployTime = "AUG 11 1999 12:32:00"
   318  			expected.deployment_data.fields.jobTrigger = "n/a"
   319  			expected.deployment_data.fields.commitHash = "123456"
   320  
   321  			expected.deployment_data.tags.artifactVersion = "0.1.2"
   322  			expected.deployment_data.tags.deployUser = "me"
   323  			expected.deployment_data.tags.deployResult = "SUCCESS"
   324  			expected.deployment_data.tags.cfAPIEndpoint = "https://examples.sap.com/cf"
   325  			expected.deployment_data.tags.cfOrg = "myOrg"
   326  			expected.deployment_data.tags.cfSpace = "mySpace"
   327  
   328  			assert.Equal(t, expected, influxData)
   329  
   330  		}
   331  
   332  	})
   333  
   334  	t.Run("deploy cf native with docker image and docker username", func(t *testing.T) {
   335  
   336  		defer cleanup()
   337  
   338  		config.DeployTool = "cf_native"
   339  		config.DeployDockerImage = "repo/image:tag"
   340  		config.DockerUsername = "me"
   341  		config.AppName = "testAppName"
   342  
   343  		config.Manifest = ""
   344  
   345  		s := mock.ExecMockRunner{}
   346  
   347  		err := runCloudFoundryDeploy(&config, nil, nil, &s)
   348  
   349  		if assert.NoError(t, err) {
   350  
   351  			withLoginAndLogout(t, func(t *testing.T) {
   352  				assert.Equal(t, []mock.ExecCall{
   353  					{Exec: "cf", Params: []string{"version"}},
   354  					{Exec: "cf", Params: []string{"plugins"}},
   355  					{Exec: "cf", Params: []string{"push",
   356  						"testAppName",
   357  						"--docker-image",
   358  						"repo/image:tag",
   359  						"--docker-username",
   360  						"me",
   361  					}},
   362  				}, s.Calls)
   363  			})
   364  		}
   365  	})
   366  
   367  	t.Run("deploy_cf_native with manifest and docker credentials", func(t *testing.T) {
   368  
   369  		defer cleanup()
   370  
   371  		// Docker image can be done via manifest.yml.
   372  		// if a private Docker registry is used, --docker-username and DOCKER_PASSWORD
   373  		// must be set; this is checked by this test
   374  
   375  		config.DeployTool = "cf_native"
   376  		config.DeployDockerImage = "repo/image:tag"
   377  		config.DockerUsername = "test_cf_docker"
   378  		config.DockerPassword = "********"
   379  		config.AppName = "testAppName"
   380  
   381  		config.Manifest = ""
   382  
   383  		s := mock.ExecMockRunner{}
   384  
   385  		err := runCloudFoundryDeploy(&config, nil, nil, &s)
   386  
   387  		if assert.NoError(t, err) {
   388  			t.Run("check shell calls", func(t *testing.T) {
   389  
   390  				withLoginAndLogout(t, func(t *testing.T) {
   391  
   392  					assert.Equal(t, []mock.ExecCall{
   393  						{Exec: "cf", Params: []string{"version"}},
   394  						{Exec: "cf", Params: []string{"plugins"}},
   395  						{Exec: "cf", Params: []string{"push",
   396  							"testAppName",
   397  							"--docker-image",
   398  							"repo/image:tag",
   399  							"--docker-username",
   400  							"test_cf_docker",
   401  						}},
   402  					}, s.Calls)
   403  				})
   404  			})
   405  
   406  			t.Run("check environment variables", func(t *testing.T) {
   407  				//REVISIT: in the corresponding groovy test we checked for "${'********'}"
   408  				// I don't understand why, but we should discuss ...
   409  				assert.Contains(t, s.Env, "CF_DOCKER_PASSWORD=********")
   410  			})
   411  		}
   412  	})
   413  
   414  	t.Run("deploy cf native blue green with manifest and docker credentials", func(t *testing.T) {
   415  
   416  		defer cleanup()
   417  
   418  		// Blue Green Deploy cf cli plugin does not support --docker-username and --docker-image parameters
   419  		// docker username and docker image have to be set in the manifest file
   420  		// if a private docker repository is used the CF_DOCKER_PASSWORD env variable must be set
   421  
   422  		config.DeployTool = "cf_native"
   423  		config.DeployType = "blue-green"
   424  		config.DockerUsername = "test_cf_docker"
   425  		config.DockerPassword = "********"
   426  		config.AppName = "testAppName"
   427  
   428  		defer prepareDefaultManifestMocking("manifest.yml", []string{"testAppName"})()
   429  
   430  		s := mock.ExecMockRunner{}
   431  
   432  		err := runCloudFoundryDeploy(&config, nil, nil, &s)
   433  
   434  		if assert.NoError(t, err) {
   435  
   436  			t.Run("check shell calls", func(t *testing.T) {
   437  
   438  				withLoginAndLogout(t, func(t *testing.T) {
   439  
   440  					assert.Equal(t, []mock.ExecCall{
   441  						{Exec: "cf", Params: []string{"version"}},
   442  						{Exec: "cf", Params: []string{"plugins"}},
   443  						{Exec: "cf", Params: []string{
   444  							"blue-green-deploy",
   445  							"testAppName",
   446  							"--delete-old-apps",
   447  							"-f",
   448  							"manifest.yml",
   449  						}},
   450  					}, s.Calls)
   451  				})
   452  			})
   453  
   454  			t.Run("check environment variables", func(t *testing.T) {
   455  				//REVISIT: in the corresponding groovy test we checked for "${'********'}"
   456  				// I don't understand why, but we should discuss ...
   457  				assert.Contains(t, s.Env, "CF_DOCKER_PASSWORD=********")
   458  			})
   459  		}
   460  	})
   461  
   462  	t.Run("deploy cf native app name from manifest", func(t *testing.T) {
   463  
   464  		defer cleanup()
   465  
   466  		config.DeployTool = "cf_native"
   467  		config.Manifest = "test-manifest.yml"
   468  
   469  		// app name is not asserted since it does not appear in the cf calls
   470  		// but it is checked that an app name is present, hence we need it here.
   471  		defer prepareDefaultManifestMocking("test-manifest.yml", []string{"dummyApp"})()
   472  
   473  		s := mock.ExecMockRunner{}
   474  
   475  		err := runCloudFoundryDeploy(&config, nil, nil, &s)
   476  
   477  		if assert.NoError(t, err) {
   478  
   479  			t.Run("check shell calls", func(t *testing.T) {
   480  
   481  				withLoginAndLogout(t, func(t *testing.T) {
   482  
   483  					assert.Equal(t, []mock.ExecCall{
   484  						{Exec: "cf", Params: []string{"version"}},
   485  						{Exec: "cf", Params: []string{"plugins"}},
   486  						{Exec: "cf", Params: []string{
   487  							"push",
   488  							"-f",
   489  							"test-manifest.yml",
   490  						}},
   491  					}, s.Calls)
   492  
   493  				})
   494  			})
   495  		}
   496  	})
   497  
   498  	t.Run("get app name from default manifest with cf native deployment", func(t *testing.T) {
   499  
   500  		defer cleanup()
   501  
   502  		config.DeployTool = "cf_native"
   503  		config.Manifest = ""
   504  		config.AppName = ""
   505  
   506  		//app name does not need to be set if it can be found in the manifest.yml
   507  		//manifest name does not need to be set- the default manifest.yml will be used if not set
   508  		defer prepareDefaultManifestMocking("manifest.yml", []string{"newAppName"})()
   509  
   510  		s := mock.ExecMockRunner{}
   511  
   512  		err := runCloudFoundryDeploy(&config, nil, nil, &s)
   513  
   514  		if assert.NoError(t, err) {
   515  
   516  			t.Run("check shell calls", func(t *testing.T) {
   517  
   518  				withLoginAndLogout(t, func(t *testing.T) {
   519  
   520  					assert.Equal(t, []mock.ExecCall{
   521  						{Exec: "cf", Params: []string{"version"}},
   522  						{Exec: "cf", Params: []string{"plugins"}},
   523  						{Exec: "cf", Params: []string{
   524  							"push",
   525  						}},
   526  					}, s.Calls)
   527  
   528  				})
   529  			})
   530  		}
   531  	})
   532  
   533  	t.Run("deploy cf native without app name", func(t *testing.T) {
   534  
   535  		defer cleanup()
   536  
   537  		config.DeployTool = "cf_native"
   538  		config.Manifest = "test-manifest.yml"
   539  
   540  		// Here we don't provide an application name from the mock. To make that
   541  		// more explicit we provide the empty string default explicitly.
   542  		defer prepareDefaultManifestMocking("test-manifest.yml", []string{""})()
   543  
   544  		s := mock.ExecMockRunner{}
   545  
   546  		err := runCloudFoundryDeploy(&config, nil, nil, &s)
   547  
   548  		if assert.EqualError(t, err, "appName from manifest 'test-manifest.yml' is empty") {
   549  
   550  			t.Run("check shell calls", func(t *testing.T) {
   551  				noopCfAPICalls(t, s)
   552  			})
   553  		}
   554  	})
   555  
   556  	// tests from groovy checking for keep old instances are already contained above. Search for '--delete-old-apps'
   557  
   558  	t.Run("deploy cf native blue green keep old instance", func(t *testing.T) {
   559  
   560  		defer cleanup()
   561  
   562  		config.DeployTool = "cf_native"
   563  		config.DeployType = "blue-green"
   564  		config.Manifest = "test-manifest.yml"
   565  		config.AppName = "myTestApp"
   566  		config.KeepOldInstance = true
   567  
   568  		s := mock.ExecMockRunner{}
   569  
   570  		err := runCloudFoundryDeploy(&config, nil, nil, &s)
   571  
   572  		if assert.NoError(t, err) {
   573  
   574  			t.Run("check shell calls", func(t *testing.T) {
   575  
   576  				withLoginAndLogout(t, func(t *testing.T) {
   577  
   578  					assert.Equal(t, []mock.ExecCall{
   579  						{Exec: "cf", Params: []string{"version"}},
   580  						{Exec: "cf", Params: []string{"plugins"}},
   581  						{Exec: "cf", Params: []string{
   582  							"blue-green-deploy",
   583  							"myTestApp",
   584  							"-f",
   585  							"test-manifest.yml",
   586  						}},
   587  						{Exec: "cf", Params: []string{
   588  							"stop",
   589  							"myTestApp-old",
   590  							// MIGRATE FFROM GROOVY: in contrast to groovy there is not redirect of everything &> to a file since we
   591  							// read the stream directly now.
   592  						}},
   593  					}, s.Calls)
   594  				})
   595  			})
   596  		}
   597  	})
   598  
   599  	t.Run("cf deploy blue green multiple applications", func(t *testing.T) {
   600  
   601  		defer cleanup()
   602  
   603  		config.DeployTool = "cf_native"
   604  		config.DeployType = "blue-green"
   605  		config.Manifest = "test-manifest.yml"
   606  		config.AppName = "myTestApp"
   607  
   608  		defer prepareDefaultManifestMocking("test-manifest.yml", []string{"app1", "app2"})()
   609  
   610  		s := mock.ExecMockRunner{}
   611  
   612  		err := runCloudFoundryDeploy(&config, nil, nil, &s)
   613  
   614  		if assert.EqualError(t, err, "Your manifest contains more than one application. For blue green deployments your manifest file may contain only one application") {
   615  			t.Run("check shell calls", func(t *testing.T) {
   616  				noopCfAPICalls(t, s)
   617  			})
   618  		}
   619  	})
   620  
   621  	t.Run("cf native deploy blue green with no route", func(t *testing.T) {
   622  
   623  		defer cleanup()
   624  
   625  		config.DeployTool = "cf_native"
   626  		config.DeployType = "blue-green"
   627  		config.Manifest = "test-manifest.yml"
   628  		config.AppName = "myTestApp"
   629  
   630  		defer func() {
   631  			_ = filesMock.FileRemove("test-manifest.yml")
   632  			_getManifest = getManifest
   633  		}()
   634  
   635  		filesMock.AddFile("test-manifest.yml", []byte("Content does not matter"))
   636  
   637  		_getManifest = func(name string) (cloudfoundry.Manifest, error) {
   638  			return manifestMock{
   639  					manifestFileName: "test-manifest.yml",
   640  					apps: []map[string]interface{}{
   641  						{
   642  							"name":     "app1",
   643  							"no-route": true,
   644  						},
   645  					},
   646  				},
   647  				nil
   648  		}
   649  
   650  		s := mock.ExecMockRunner{}
   651  
   652  		err := runCloudFoundryDeploy(&config, nil, nil, &s)
   653  
   654  		if assert.NoError(t, err) {
   655  
   656  			t.Run("check shell calls", func(t *testing.T) {
   657  
   658  				withLoginAndLogout(t, func(t *testing.T) {
   659  
   660  					assert.Equal(t, []mock.ExecCall{
   661  						{Exec: "cf", Params: []string{"version"}},
   662  						{Exec: "cf", Params: []string{"plugins"}},
   663  						{Exec: "cf", Params: []string{
   664  							"push",
   665  							"myTestApp",
   666  							"-f",
   667  							"test-manifest.yml",
   668  						}},
   669  					}, s.Calls)
   670  				})
   671  			})
   672  		}
   673  	})
   674  
   675  	t.Run("cf native deployment failure", func(t *testing.T) {
   676  
   677  		defer cleanup()
   678  
   679  		config.DeployTool = "cf_native"
   680  		config.DeployType = "blue-green"
   681  		config.Manifest = "test-manifest.yml"
   682  		config.AppName = "myTestApp"
   683  
   684  		defer prepareDefaultManifestMocking("test-manifest.yml", []string{"app"})()
   685  
   686  		s := mock.ExecMockRunner{}
   687  
   688  		s.ShouldFailOnCommand = map[string]error{"cf.*deploy.*": fmt.Errorf("cf deploy failed")}
   689  		err := runCloudFoundryDeploy(&config, nil, nil, &s)
   690  
   691  		if assert.EqualError(t, err, "cf deploy failed") {
   692  			t.Run("check shell calls", func(t *testing.T) {
   693  
   694  				// we should try to logout in this case
   695  				assert.True(t, logoutCalled)
   696  			})
   697  		}
   698  	})
   699  
   700  	t.Run("cf native deployment failure when logging in", func(t *testing.T) {
   701  
   702  		defer cleanup()
   703  
   704  		config.DeployTool = "cf_native"
   705  		config.DeployType = "blue-green"
   706  		config.Manifest = "test-manifest.yml"
   707  		config.AppName = "myTestApp"
   708  
   709  		defer func() {
   710  
   711  			_cfLogin = func(c command.ExecRunner, opts cloudfoundry.LoginOptions) error {
   712  				loginOpts = opts
   713  				return nil
   714  			}
   715  		}()
   716  
   717  		_cfLogin = func(c command.ExecRunner, opts cloudfoundry.LoginOptions) error {
   718  			loginOpts = opts
   719  			return fmt.Errorf("Unable to login")
   720  		}
   721  
   722  		defer prepareDefaultManifestMocking("test-manifest.yml", []string{"app1"})()
   723  
   724  		s := mock.ExecMockRunner{}
   725  
   726  		err := runCloudFoundryDeploy(&config, nil, nil, &s)
   727  
   728  		if assert.EqualError(t, err, "Unable to login") {
   729  			t.Run("check shell calls", func(t *testing.T) {
   730  
   731  				// no calls to the cf client in this case
   732  				assert.Equal(t,
   733  					[]mock.ExecCall{
   734  						{Exec: "cf", Params: []string{"version"}},
   735  					}, s.Calls)
   736  				// no logout
   737  				assert.False(t, logoutCalled)
   738  			})
   739  		}
   740  	})
   741  
   742  	// TODO testCfNativeBlueGreenKeepOldInstanceShouldThrowErrorOnStopError
   743  
   744  	t.Run("cf native deploy standard should not stop instance", func(t *testing.T) {
   745  
   746  		defer cleanup()
   747  
   748  		config.DeployTool = "cf_native"
   749  		config.DeployType = "standard"
   750  		config.Manifest = "test-manifest.yml"
   751  		config.AppName = "myTestApp"
   752  		config.KeepOldInstance = true
   753  
   754  		defer prepareDefaultManifestMocking("test-manifest.yml", []string{"app"})()
   755  
   756  		s := mock.ExecMockRunner{}
   757  
   758  		err := runCloudFoundryDeploy(&config, nil, nil, &s)
   759  
   760  		if assert.NoError(t, err) {
   761  
   762  			t.Run("check shell calls", func(t *testing.T) {
   763  
   764  				withLoginAndLogout(t, func(t *testing.T) {
   765  
   766  					assert.Equal(t, []mock.ExecCall{
   767  						{Exec: "cf", Params: []string{"version"}},
   768  						{Exec: "cf", Params: []string{"plugins"}},
   769  						{Exec: "cf", Params: []string{
   770  							"push",
   771  							"myTestApp",
   772  							"-f",
   773  							"test-manifest.yml",
   774  						}},
   775  
   776  						//
   777  						// There is no cf stop
   778  						//
   779  
   780  					}, s.Calls)
   781  				})
   782  			})
   783  		}
   784  	})
   785  
   786  	t.Run("testCfNativeWithoutAppNameBlueGreen", func(t *testing.T) {
   787  
   788  		defer cleanup()
   789  
   790  		config.DeployTool = "cf_native"
   791  		config.DeployType = "blue-green"
   792  		config.Manifest = "test-manifest.yml"
   793  
   794  		defer func() {
   795  			_ = filesMock.FileRemove("test-manifest.yml")
   796  			_getManifest = getManifest
   797  		}()
   798  
   799  		filesMock.AddFile("test-manifest.yml", []byte("The content does not matter"))
   800  
   801  		_getManifest = func(name string) (cloudfoundry.Manifest, error) {
   802  			return manifestMock{
   803  					manifestFileName: "test-manifest.yml",
   804  					apps: []map[string]interface{}{
   805  						{
   806  							"there-is": "no-app-name",
   807  						},
   808  					},
   809  				},
   810  				nil
   811  		}
   812  
   813  		s := mock.ExecMockRunner{}
   814  
   815  		err := runCloudFoundryDeploy(&config, nil, nil, &s)
   816  
   817  		if assert.EqualError(t, err, "Blue-green plugin requires app name to be passed (see https://github.com/bluemixgaragelondon/cf-blue-green-deploy/issues/27)") {
   818  
   819  			t.Run("check shell calls", func(t *testing.T) {
   820  				noopCfAPICalls(t, s)
   821  			})
   822  		}
   823  	})
   824  
   825  	// TODO add test for testCfNativeFailureInShellCall
   826  
   827  	t.Run("deploytool mtaDeployPlugin blue green", func(t *testing.T) {
   828  
   829  		defer cleanup()
   830  
   831  		config.DeployTool = "mtaDeployPlugin"
   832  		config.DeployType = "blue-green"
   833  		config.MtaPath = "target/test.mtar"
   834  
   835  		defer func() {
   836  			_ = filesMock.FileRemove("target/test.mtar")
   837  		}()
   838  
   839  		filesMock.AddFile("target/test.mtar", []byte("content does not matter"))
   840  
   841  		s := mock.ExecMockRunner{}
   842  
   843  		err := runCloudFoundryDeploy(&config, nil, nil, &s)
   844  
   845  		if assert.NoError(t, err) {
   846  
   847  			t.Run("check shell calls", func(t *testing.T) {
   848  
   849  				withLoginAndLogout(t, func(t *testing.T) {
   850  
   851  					assert.Equal(t, []mock.ExecCall{
   852  						{Exec: "cf", Params: []string{"version"}},
   853  						{Exec: "cf", Params: []string{"plugins"}},
   854  						{Exec: "cf", Params: []string{
   855  							"bg-deploy",
   856  							"target/test.mtar",
   857  							"-f",
   858  							"--no-confirm",
   859  						}},
   860  
   861  						//
   862  						// There is no cf stop
   863  						//
   864  
   865  					}, s.Calls)
   866  				})
   867  			})
   868  		}
   869  	})
   870  
   871  	// TODO: add test for influx reporting (influx reporting is missing at the moment)
   872  
   873  	t.Run("cf push with variables from file and as list", func(t *testing.T) {
   874  
   875  		defer cleanup()
   876  
   877  		config.DeployTool = "cf_native"
   878  		config.Manifest = "test-manifest.yml"
   879  		config.ManifestVariablesFiles = []string{"vars.yaml"}
   880  		config.ManifestVariables = []string{"appName=testApplicationFromVarsList"}
   881  		config.AppName = "testAppName"
   882  
   883  		defer func() {
   884  			_getManifest = getManifest
   885  			_getVarsOptions = cloudfoundry.GetVarsOptions
   886  			_getVarsFileOptions = cloudfoundry.GetVarsFileOptions
   887  		}()
   888  
   889  		_getVarsOptions = func(vars []string) ([]string, error) {
   890  			return []string{"--var", "appName=testApplicationFromVarsList"}, nil
   891  		}
   892  		_getVarsFileOptions = func(varFiles []string) ([]string, error) {
   893  			return []string{"--vars-file", "vars.yaml"}, nil
   894  		}
   895  
   896  		filesMock.AddFile("test-manifest.yml", []byte("content does not matter"))
   897  
   898  		_getManifest = func(name string) (cloudfoundry.Manifest, error) {
   899  			return manifestMock{
   900  					manifestFileName: "test-manifest.yml",
   901  					apps: []map[string]interface{}{
   902  						{
   903  							"name": "myApp",
   904  						},
   905  					},
   906  				},
   907  				nil
   908  		}
   909  
   910  		s := mock.ExecMockRunner{}
   911  
   912  		err := runCloudFoundryDeploy(&config, nil, nil, &s)
   913  
   914  		if assert.NoError(t, err) {
   915  
   916  			t.Run("check shell calls", func(t *testing.T) {
   917  
   918  				withLoginAndLogout(t, func(t *testing.T) {
   919  
   920  					// Revisit: we don't verify a log message in case of a non existing vars file
   921  
   922  					assert.Equal(t, []mock.ExecCall{
   923  						{Exec: "cf", Params: []string{"version"}},
   924  						{Exec: "cf", Params: []string{"plugins"}},
   925  						{Exec: "cf", Params: []string{
   926  							"push",
   927  							"testAppName",
   928  							"--var",
   929  							"appName=testApplicationFromVarsList",
   930  							"--vars-file",
   931  							"vars.yaml",
   932  							"-f",
   933  							"test-manifest.yml",
   934  						}},
   935  					}, s.Calls)
   936  				})
   937  			})
   938  		}
   939  	})
   940  
   941  	t.Run("cf push with variables from file which does not exist", func(t *testing.T) {
   942  
   943  		defer cleanup()
   944  
   945  		config.DeployTool = "cf_native"
   946  		config.Manifest = "test-manifest.yml"
   947  		config.ManifestVariablesFiles = []string{"vars.yaml", "vars-does-not-exist.yaml"}
   948  		config.AppName = "testAppName"
   949  
   950  		defer func() {
   951  			_ = filesMock.FileRemove("test-manifest.yml")
   952  			_ = filesMock.FileRemove("vars.yaml")
   953  			_getManifest = getManifest
   954  			_getVarsOptions = cloudfoundry.GetVarsOptions
   955  			_getVarsFileOptions = cloudfoundry.GetVarsFileOptions
   956  		}()
   957  
   958  		filesMock.AddFile("test-manifest.yml", []byte("content does not matter"))
   959  
   960  		_getManifest = func(name string) (cloudfoundry.Manifest, error) {
   961  			return manifestMock{
   962  					manifestFileName: "test-manifest.yml",
   963  					apps: []map[string]interface{}{
   964  						{
   965  							"name": "myApp",
   966  						},
   967  					},
   968  				},
   969  				nil
   970  		}
   971  
   972  		s := mock.ExecMockRunner{}
   973  
   974  		var receivedVarOptions []string
   975  		var receivedVarsFileOptions []string
   976  
   977  		_getVarsOptions = func(vars []string) ([]string, error) {
   978  			receivedVarOptions = vars
   979  			return []string{}, nil
   980  		}
   981  		_getVarsFileOptions = func(varFiles []string) ([]string, error) {
   982  			receivedVarsFileOptions = varFiles
   983  			return []string{"--vars-file", "vars.yaml"}, nil
   984  		}
   985  
   986  		err := runCloudFoundryDeploy(&config, nil, nil, &s)
   987  
   988  		if assert.NoError(t, err) {
   989  
   990  			t.Run("check received vars options", func(t *testing.T) {
   991  				assert.Empty(t, receivedVarOptions)
   992  			})
   993  
   994  			t.Run("check received vars file options", func(t *testing.T) {
   995  				assert.Equal(t, []string{"vars.yaml", "vars-does-not-exist.yaml"}, receivedVarsFileOptions)
   996  			})
   997  
   998  			t.Run("check shell calls", func(t *testing.T) {
   999  
  1000  				withLoginAndLogout(t, func(t *testing.T) {
  1001  					// Revisit: we don't verify a log message in case of a non existing vars file
  1002  
  1003  					assert.Equal(t, []mock.ExecCall{
  1004  						{Exec: "cf", Params: []string{"version"}},
  1005  						{Exec: "cf", Params: []string{"plugins"}},
  1006  						{Exec: "cf", Params: []string{
  1007  							"push",
  1008  							"testAppName",
  1009  							"--vars-file",
  1010  							"vars.yaml",
  1011  							"-f",
  1012  							"test-manifest.yml",
  1013  						}},
  1014  					}, s.Calls)
  1015  				})
  1016  			})
  1017  		}
  1018  	})
  1019  
  1020  	// TODO: testCfPushDeploymentWithoutVariableSubstitution is already handled above (?)
  1021  
  1022  	// TODO: testCfBlueGreenDeploymentWithVariableSubstitution variable substitution is not handled at the moment (pr pending).
  1023  	// but anyway we should not test the full cycle here, but only that the variables substitution tool is called in the appropriate way.
  1024  	// variable substitution should be tested at the variables substitution tool itself (yaml util)
  1025  
  1026  	t.Run("deploytool mtaDeployPlugin", func(t *testing.T) {
  1027  
  1028  		defer cleanup()
  1029  
  1030  		config.DeployTool = "mtaDeployPlugin"
  1031  		config.MtaDeployParameters = "-f"
  1032  
  1033  		t.Run("mta config file from project sources", func(t *testing.T) {
  1034  
  1035  			defer func() { _ = filesMock.FileRemove("xyz.mtar") }()
  1036  
  1037  			// The mock is inaccurat here.
  1038  			// AddFile() adds the file absolute, prefix with the current working directory
  1039  			// Glob() returns the absolute path - but without leading slash - , whereas
  1040  			// the real Glob returns the path relative to the current workdir.
  1041  			// In order to mimic the behavior in the free wild we add the mtar at the root dir.
  1042  			filesMock.AddDir("/")
  1043  			assert.NoError(t, filesMock.Chdir("/"))
  1044  			filesMock.AddFile("xyz.mtar", []byte("content does not matter"))
  1045  			// restor the expected working dir.
  1046  			assert.NoError(t, filesMock.Chdir("/home/me"))
  1047  			s := mock.ExecMockRunner{}
  1048  			err := runCloudFoundryDeploy(&config, nil, nil, &s)
  1049  
  1050  			if assert.NoError(t, err) {
  1051  
  1052  				withLoginAndLogout(t, func(t *testing.T) {
  1053  
  1054  					assert.Equal(t, s.Calls, []mock.ExecCall{
  1055  						{Exec: "cf", Params: []string{"version"}},
  1056  						{Exec: "cf", Params: []string{"plugins"}},
  1057  						{Exec: "cf", Params: []string{"deploy", "xyz.mtar", "-f"}}})
  1058  
  1059  				})
  1060  			}
  1061  		})
  1062  
  1063  		t.Run("mta config file from project config does not exist", func(t *testing.T) {
  1064  			defer func() { config.MtaPath = "" }()
  1065  			config.MtaPath = "my.mtar"
  1066  			s := mock.ExecMockRunner{}
  1067  			err := runCloudFoundryDeploy(&config, nil, nil, &s)
  1068  			assert.EqualError(t, err, "mtar file 'my.mtar' retrieved from configuration does not exist")
  1069  		})
  1070  
  1071  		// TODO: add test for mtar file from project config which does exist in project sources
  1072  	})
  1073  }
  1074  
  1075  func TestValidateDeployTool(t *testing.T) {
  1076  	testCases := []struct {
  1077  		runName            string
  1078  		deployToolGiven    string
  1079  		buildTool          string
  1080  		deployToolExpected string
  1081  	}{
  1082  		{"no params", "", "", ""},
  1083  		{"build tool MTA", "", "mta", "mtaDeployPlugin"},
  1084  		{"build tool other", "", "other", "cf_native"},
  1085  		{"deploy and build tool given", "given", "unknown", "given"},
  1086  		{"only deploy tool given", "given", "", "given"},
  1087  	}
  1088  
  1089  	t.Parallel()
  1090  
  1091  	for _, test := range testCases {
  1092  		t.Run(test.runName, func(t *testing.T) {
  1093  			config := cloudFoundryDeployOptions{BuildTool: test.buildTool, DeployTool: test.deployToolGiven}
  1094  			validateDeployTool(&config)
  1095  			assert.Equal(t, test.deployToolExpected, config.DeployTool,
  1096  				"expected different deployTool result")
  1097  		})
  1098  	}
  1099  }
  1100  
  1101  func TestMtarLookup(t *testing.T) {
  1102  
  1103  	defer func() {
  1104  		fileUtils = piperutils.Files{}
  1105  	}()
  1106  
  1107  	filesMock := mock.FilesMock{}
  1108  	fileUtils = &filesMock
  1109  
  1110  	t.Run("One MTAR", func(t *testing.T) {
  1111  
  1112  		defer func() { _ = filesMock.FileRemove("x.mtar") }()
  1113  		filesMock.AddFile("x.mtar", []byte("content does not matter"))
  1114  
  1115  		path, err := findMtar()
  1116  
  1117  		if assert.NoError(t, err) {
  1118  			assert.Equal(t, "x.mtar", path)
  1119  		}
  1120  	})
  1121  
  1122  	t.Run("No MTAR", func(t *testing.T) {
  1123  
  1124  		// nothing needs to be configures. There is simply no
  1125  		// mtar in the file system mock, so no mtar will be found.
  1126  
  1127  		_, err := findMtar()
  1128  
  1129  		assert.EqualError(t, err, "No mtar file matching pattern '**/*.mtar' found")
  1130  	})
  1131  
  1132  	t.Run("Several MTARs", func(t *testing.T) {
  1133  
  1134  		defer func() {
  1135  			_ = filesMock.FileRemove("x.mtar")
  1136  			_ = filesMock.FileRemove("y.mtar")
  1137  		}()
  1138  
  1139  		filesMock.AddFile("x.mtar", []byte("content does not matter"))
  1140  		filesMock.AddFile("y.mtar", []byte("content does not matter"))
  1141  
  1142  		_, err := findMtar()
  1143  		assert.EqualError(t, err, "Found multiple mtar files matching pattern '**/*.mtar' (x.mtar,y.mtar), please specify file via parameter 'mtarPath'")
  1144  	})
  1145  }
  1146  
  1147  func TestSmokeTestScriptHandling(t *testing.T) {
  1148  
  1149  	filesMock := mock.FilesMock{}
  1150  	filesMock.AddDir("/home/me")
  1151  	err := filesMock.Chdir("/home/me")
  1152  	assert.NoError(t, err)
  1153  	filesMock.AddFileWithMode("mySmokeTestScript.sh", []byte("Content does not matter"), 0644)
  1154  	fileUtils = &filesMock
  1155  
  1156  	var canExec os.FileMode = 0755
  1157  
  1158  	t.Run("non default existing smoke test file", func(t *testing.T) {
  1159  
  1160  		parts, err := handleSmokeTestScript("mySmokeTestScript.sh")
  1161  		if assert.NoError(t, err) {
  1162  			// when the none-default file name is provided the file must already exist
  1163  			// in the project sources.
  1164  			assert.False(t, filesMock.HasWrittenFile("mySmokeTestScript.sh"))
  1165  			info, e := filesMock.Stat("mySmokeTestScript.sh")
  1166  			if assert.NoError(t, e) {
  1167  				assert.Equal(t, canExec, info.Mode())
  1168  			}
  1169  
  1170  			assert.Equal(t, []string{
  1171  				"--smoke-test",
  1172  				filepath.FromSlash("/home/me/mySmokeTestScript.sh"),
  1173  			}, parts)
  1174  		}
  1175  	})
  1176  
  1177  	t.Run("non default not existing smoke test file", func(t *testing.T) {
  1178  
  1179  		parts, err := handleSmokeTestScript("notExistingSmokeTestScript.sh")
  1180  		if assert.EqualError(t, err, "failed to make smoke-test script executable: chmod: notExistingSmokeTestScript.sh: No such file or directory") {
  1181  			assert.False(t, filesMock.HasWrittenFile("notExistingSmokeTestScript.sh"))
  1182  			assert.Equal(t, []string{}, parts)
  1183  		}
  1184  	})
  1185  
  1186  	t.Run("default smoke test file", func(t *testing.T) {
  1187  
  1188  		parts, err := handleSmokeTestScript("blueGreenCheckScript.sh")
  1189  
  1190  		if assert.NoError(t, err) {
  1191  
  1192  			info, e := filesMock.Stat("blueGreenCheckScript.sh")
  1193  			if assert.NoError(t, e) {
  1194  				assert.Equal(t, canExec, info.Mode())
  1195  			}
  1196  
  1197  			// in this case we provide the file. We overwrite in case there is already such a file ...
  1198  			assert.True(t, filesMock.HasWrittenFile("blueGreenCheckScript.sh"))
  1199  
  1200  			content, e := filesMock.FileRead("blueGreenCheckScript.sh")
  1201  
  1202  			if assert.NoError(t, e) {
  1203  				assert.Equal(t, "#!/usr/bin/env bash\n# this is simply testing if the application root returns HTTP STATUS_CODE\ncurl -so /dev/null -w '%{response_code}' https://$1 | grep $STATUS_CODE", string(content))
  1204  			}
  1205  
  1206  			assert.Equal(t, []string{
  1207  				"--smoke-test",
  1208  				filepath.FromSlash("/home/me/blueGreenCheckScript.sh"),
  1209  			}, parts)
  1210  		}
  1211  	})
  1212  }
  1213  
  1214  func TestDefaultManifestVariableFilesHandling(t *testing.T) {
  1215  
  1216  	filesMock := mock.FilesMock{}
  1217  	filesMock.AddDir("/home/me")
  1218  	err := filesMock.Chdir("/home/me")
  1219  	assert.NoError(t, err)
  1220  	fileUtils = &filesMock
  1221  
  1222  	t.Run("default manifest variable file is the only one and exists", func(t *testing.T) {
  1223  		defer func() {
  1224  			_ = filesMock.FileRemove("manifest-variables.yml")
  1225  		}()
  1226  		filesMock.AddFile("manifest-variables.yml", []byte("Content does not matter"))
  1227  
  1228  		manifestFiles, err := validateManifestVariablesFiles(
  1229  			[]string{
  1230  				"manifest-variables.yml",
  1231  			},
  1232  		)
  1233  
  1234  		if assert.NoError(t, err) {
  1235  			assert.Equal(t,
  1236  				[]string{
  1237  					"manifest-variables.yml",
  1238  				}, manifestFiles)
  1239  		}
  1240  	})
  1241  
  1242  	t.Run("default manifest variable file is the only one and does not exist", func(t *testing.T) {
  1243  
  1244  		manifestFiles, err := validateManifestVariablesFiles(
  1245  			[]string{
  1246  				"manifest-variables.yml",
  1247  			},
  1248  		)
  1249  
  1250  		if assert.NoError(t, err) {
  1251  			assert.Equal(t, []string{}, manifestFiles)
  1252  		}
  1253  	})
  1254  
  1255  	t.Run("default manifest variable file among others remains if it does not exist", func(t *testing.T) {
  1256  
  1257  		// in this case we might fail later.
  1258  
  1259  		manifestFiles, err := validateManifestVariablesFiles(
  1260  			[]string{
  1261  				"manifest-variables.yml",
  1262  				"a-second-file.yml",
  1263  			},
  1264  		)
  1265  
  1266  		if assert.NoError(t, err) {
  1267  			// the order in which the files are returned is significant.
  1268  			assert.Equal(t, []string{
  1269  				"manifest-variables.yml",
  1270  				"a-second-file.yml",
  1271  			}, manifestFiles)
  1272  		}
  1273  	})
  1274  }
  1275  
  1276  func TestExtensionDescriptorsWithMinusE(t *testing.T) {
  1277  
  1278  	t.Run("ExtensionDescriptorsWithMinusE", func(t *testing.T) {
  1279  		extDesc, _ := handleMtaExtensionDescriptors("-e 1.yaml -e 2.yaml")
  1280  		assert.Equal(t, []string{
  1281  			"-e",
  1282  			"1.yaml,2.yaml",
  1283  		}, extDesc)
  1284  	})
  1285  
  1286  	t.Run("ExtensionDescriptorsFirstOneWithoutMinusE", func(t *testing.T) {
  1287  		extDesc, _ := handleMtaExtensionDescriptors("1.yaml -e 2.yaml")
  1288  		assert.Equal(t, []string{
  1289  			"-e",
  1290  			"1.yaml,2.yaml",
  1291  		}, extDesc)
  1292  	})
  1293  
  1294  	t.Run("NoExtensionDescriptors", func(t *testing.T) {
  1295  		extDesc, _ := handleMtaExtensionDescriptors("")
  1296  		assert.Equal(t, []string{}, extDesc)
  1297  	})
  1298  }
  1299  
  1300  func TestAppNameChecks(t *testing.T) {
  1301  
  1302  	t.Run("appName with alpha-numeric chars should work", func(t *testing.T) {
  1303  		err := validateAppName("myValidAppName123")
  1304  		assert.NoError(t, err)
  1305  	})
  1306  
  1307  	t.Run("appName with alpha-numeric chars and dash should work", func(t *testing.T) {
  1308  		err := validateAppName("my-Valid-AppName123")
  1309  		assert.NoError(t, err)
  1310  	})
  1311  
  1312  	t.Run("empty appName should work", func(t *testing.T) {
  1313  		// we consider the empty string as valid appname since we only check app names handed over from outside
  1314  		// in case there is no (real) app name provided from outside we might still find an appname in the metadata
  1315  		// That app name in turn is not checked.
  1316  		err := validateAppName("")
  1317  		assert.NoError(t, err)
  1318  	})
  1319  
  1320  	t.Run("single char appName should work", func(t *testing.T) {
  1321  		err := validateAppName("a")
  1322  		assert.NoError(t, err)
  1323  	})
  1324  
  1325  	t.Run("appName with alpha-numeric chars and trailing dash should throw an error", func(t *testing.T) {
  1326  		err := validateAppName("my-Invalid-AppName123-")
  1327  		assert.EqualError(t, err, "Your application name 'my-Invalid-AppName123-' starts or ends with a '-' (dash) which is not allowed, only letters and numbers can be used. Please change the name to fit this requirement(s). For more details please visit https://docs.cloudfoundry.org/devguide/deploy-apps/deploy-app.html#basic-settings.")
  1328  	})
  1329  
  1330  	t.Run("appName with underscores should throw an error", func(t *testing.T) {
  1331  		err := validateAppName("my_invalid_app_name")
  1332  		assert.EqualError(t, err, "Your application name 'my_invalid_app_name' contains a '_' (underscore) which is not allowed, only letters, dashes and numbers can be used. Please change the name to fit this requirement(s). For more details please visit https://docs.cloudfoundry.org/devguide/deploy-apps/deploy-app.html#basic-settings.")
  1333  	})
  1334  
  1335  }
  1336  
  1337  func TestMtaExtensionCredentials(t *testing.T) {
  1338  
  1339  	filesMock := mock.FilesMock{}
  1340  	filesMock.AddDir("/home/me")
  1341  	err := filesMock.Chdir("/home/me")
  1342  	assert.NoError(t, err)
  1343  	fileUtils = &filesMock
  1344  
  1345  	_environ = func() []string {
  1346  		return []string{
  1347  			"MY_CRED_ENV_VAR1=**$0****",
  1348  			"MY_CRED_ENV_VAR2=++$1++++",
  1349  		}
  1350  	}
  1351  
  1352  	defer func() {
  1353  		fileUtils = piperutils.Files{}
  1354  		_environ = os.Environ
  1355  	}()
  1356  
  1357  	t.Run("extension file does not exist", func(t *testing.T) {
  1358  		_, _, err := handleMtaExtensionCredentials("mtaextDoesNotExist.mtaext", map[string]interface{}{})
  1359  		assert.EqualError(t, err, "Cannot handle credentials for mta extension file 'mtaextDoesNotExist.mtaext': could not read 'mtaextDoesNotExist.mtaext'")
  1360  	})
  1361  
  1362  	t.Run("credential cannot be retrieved", func(t *testing.T) {
  1363  
  1364  		filesMock.AddFile("mtaext.mtaext", []byte(
  1365  			`'_schema-version: '3.1'
  1366  				ID: test.ext
  1367  				extends: test
  1368  				parameters
  1369  					test-credentials1: "<%= testCred1 %>"
  1370  					test-credentials2: "<%=testCred2%>"`))
  1371  		_, _, err := handleMtaExtensionCredentials(
  1372  			"mtaext.mtaext",
  1373  			map[string]interface{}{
  1374  				"testCred1": "myCredEnvVar1NotDefined",
  1375  				"testCred2": "myCredEnvVar2NotDefined",
  1376  			},
  1377  		)
  1378  		assert.EqualError(t, err, "cannot handle mta extension credentials: No credentials found for '[myCredEnvVar1NotDefined myCredEnvVar2NotDefined]'/'[MY_CRED_ENV_VAR1_NOT_DEFINED MY_CRED_ENV_VAR2_NOT_DEFINED]'. Are these credentials maintained?")
  1379  	})
  1380  
  1381  	t.Run("irrelevant credentials do not cause failures", func(t *testing.T) {
  1382  
  1383  		filesMock.AddFile("mtaext.mtaext", []byte(
  1384  			`'_schema-version: '3.1'
  1385  				ID: test.ext
  1386  				extends: test
  1387  				parameters
  1388  					test-credentials1: "<%= testCred1 %>"
  1389  					test-credentials2: "<%=testCred2%>`))
  1390  		_, _, err := handleMtaExtensionCredentials(
  1391  			"mtaext.mtaext",
  1392  			map[string]interface{}{
  1393  				"testCred1":       "myCredEnvVar1",
  1394  				"testCred2":       "myCredEnvVar2",
  1395  				"testCredNotUsed": "myCredEnvVarWhichDoesNotExist", //<-- This here is not used.
  1396  			},
  1397  		)
  1398  		assert.NoError(t, err)
  1399  	})
  1400  
  1401  	t.Run("invalid chars in credential key name", func(t *testing.T) {
  1402  		filesMock.AddFile("mtaext.mtaext", []byte(
  1403  			`'_schema-version: '3.1'
  1404  				ID: test.ext
  1405  				extends: test
  1406  				parameters
  1407  					test-credentials1: "<%= testCred1 %>"
  1408  					test-credentials2: "<%=testCred2%>`))
  1409  		_, _, err := handleMtaExtensionCredentials("mtaext.mtaext",
  1410  			map[string]interface{}{
  1411  				"test.*Cred1": "myCredEnvVar1",
  1412  			},
  1413  		)
  1414  		assert.EqualError(t, err, "credential key name 'test.*Cred1' contains unsupported character. Must contain only ^[-_A-Za-z0-9]+$")
  1415  	})
  1416  
  1417  	t.Run("unresolved placeholders does not cause an error", func(t *testing.T) {
  1418  		// we emit a log message, but it does not fail
  1419  		filesMock.AddFile("mtaext-unresolved.mtaext", []byte("<%= unresolved %>"))
  1420  		updated, containsUnresolved, err := handleMtaExtensionCredentials("mtaext-unresolved.mtaext", map[string]interface{}{})
  1421  		assert.True(t, containsUnresolved)
  1422  		assert.False(t, updated)
  1423  		assert.NoError(t, err)
  1424  	})
  1425  
  1426  	t.Run("replace straight forward", func(t *testing.T) {
  1427  		mtaFileName := "mtaext.mtaext"
  1428  		filesMock.AddFile(mtaFileName, []byte(
  1429  			`'_schema-version: '3.1'
  1430  			ID: test.ext
  1431  			extends: test
  1432  			parameters
  1433  				test-credentials1: "<%= testCred1 %>"
  1434  				test-credentials2: "<%=testCred2%>"
  1435  				test-credentials3: "<%= testCred2%>"
  1436  				test-credentials4: "<%=testCred2 %>"
  1437  				test-credentials5: "<%=  testCred2    %>"`))
  1438  		updated, containsUnresolved, err := handleMtaExtensionCredentials(
  1439  			mtaFileName,
  1440  			map[string]interface{}{
  1441  				"testCred1": "myCredEnvVar1",
  1442  				"testCred2": "myCredEnvVar2",
  1443  			},
  1444  		)
  1445  		if assert.NoError(t, err) {
  1446  			b, e := fileUtils.FileRead(mtaFileName)
  1447  			if e != nil {
  1448  				assert.Fail(t, "Cannot read mta extension file: %v", e)
  1449  			}
  1450  			content := string(b)
  1451  			assert.Contains(t, content, "test-credentials1: \"**$0****\"")
  1452  			assert.Contains(t, content, "test-credentials2: \"++$1++++\"")
  1453  			assert.Contains(t, content, "test-credentials3: \"++$1++++\"")
  1454  			assert.Contains(t, content, "test-credentials4: \"++$1++++\"")
  1455  			assert.Contains(t, content, "test-credentials5: \"++$1++++\"")
  1456  
  1457  			assert.True(t, updated)
  1458  			assert.False(t, containsUnresolved)
  1459  		}
  1460  	})
  1461  }
  1462  
  1463  func TestEnvVarKeyModification(t *testing.T) {
  1464  	envVarCompatibleKey := toEnvVarKey("Mta.EXtensionCredential~Credential_Id1Abc")
  1465  	assert.Equal(t, "MTA_EXTENSION_CREDENTIAL_CREDENTIAL_ID1_ABC", envVarCompatibleKey)
  1466  }