github.com/jaylevin/jenkins-library@v1.230.4/cmd/cloudFoundryDeploy_test.go (about)

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