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 }