github.com/sleungcy-sap/cli@v7.1.0+incompatible/cf/commands/application/start_test.go (about) 1 package application_test 2 3 import ( 4 "os" 5 "runtime" 6 "time" 7 8 . "code.cloudfoundry.org/cli/cf/commands/application" 9 "code.cloudfoundry.org/cli/cf/commands/application/applicationfakes" 10 "code.cloudfoundry.org/cli/cf/requirements" 11 "code.cloudfoundry.org/cli/cf/requirements/requirementsfakes" 12 "code.cloudfoundry.org/cli/cf/trace/tracefakes" 13 14 "code.cloudfoundry.org/cli/cf/commandregistry" 15 "code.cloudfoundry.org/cli/cf/configuration/coreconfig" 16 "code.cloudfoundry.org/cli/cf/errors" 17 "code.cloudfoundry.org/cli/cf/models" 18 19 "code.cloudfoundry.org/cli/cf/api/appinstances/appinstancesfakes" 20 "code.cloudfoundry.org/cli/cf/api/applications/applicationsfakes" 21 "code.cloudfoundry.org/cli/cf/api/logs" 22 "code.cloudfoundry.org/cli/cf/api/logs/logsfakes" 23 testcmd "code.cloudfoundry.org/cli/cf/util/testhelpers/commands" 24 testconfig "code.cloudfoundry.org/cli/cf/util/testhelpers/configuration" 25 testterm "code.cloudfoundry.org/cli/cf/util/testhelpers/terminal" 26 27 . "code.cloudfoundry.org/cli/cf/util/testhelpers/matchers" 28 29 "sync" 30 31 "sync/atomic" 32 33 . "github.com/onsi/ginkgo" 34 . "github.com/onsi/gomega" 35 ) 36 37 var _ = Describe("start command", func() { 38 var ( 39 ui *testterm.FakeUI 40 configRepo coreconfig.Repository 41 defaultAppForStart models.Application 42 defaultInstanceResponses [][]models.AppInstanceFields 43 defaultInstanceErrorCodes []string 44 requirementsFactory *requirementsfakes.FakeFactory 45 logMessages atomic.Value 46 logRepo *logsfakes.FakeRepository 47 48 appInstancesRepo *appinstancesfakes.FakeAppInstancesRepository 49 appRepo *applicationsfakes.FakeRepository 50 originalAppCommand commandregistry.Command 51 deps commandregistry.Dependency 52 displayApp *applicationfakes.FakeAppDisplayer 53 ) 54 55 updateCommandDependency := func(logsRepo logs.Repository) { 56 deps.UI = ui 57 deps.Config = configRepo 58 deps.RepoLocator = deps.RepoLocator.SetLogsRepository(logsRepo) 59 deps.RepoLocator = deps.RepoLocator.SetApplicationRepository(appRepo) 60 deps.RepoLocator = deps.RepoLocator.SetAppInstancesRepository(appInstancesRepo) 61 62 //inject fake 'Start' into registry 63 commandregistry.Register(displayApp) 64 65 commandregistry.Commands.SetCommand(commandregistry.Commands.FindCommand("start").SetDependency(deps, false)) 66 } 67 68 getInstance := func(appGUID string) ([]models.AppInstanceFields, error) { 69 var apiErr error 70 var instances []models.AppInstanceFields 71 72 if len(defaultInstanceResponses) > 0 { 73 instances, defaultInstanceResponses = defaultInstanceResponses[0], defaultInstanceResponses[1:] 74 } 75 if len(defaultInstanceErrorCodes) > 0 { 76 var errorCode string 77 errorCode, defaultInstanceErrorCodes = defaultInstanceErrorCodes[0], defaultInstanceErrorCodes[1:] 78 79 if errorCode != "" { 80 apiErr = errors.NewHTTPError(400, errorCode, "Error staging app") 81 } 82 } 83 84 return instances, apiErr 85 } 86 87 AfterEach(func() { 88 commandregistry.Register(originalAppCommand) 89 }) 90 91 BeforeEach(func() { 92 if runtime.GOOS == "windows" { 93 Skip("cf start command test permanently disabled on Windows https://www.pivotaltracker.com/story/show/172687176") 94 } 95 96 deps = commandregistry.NewDependency(os.Stdout, new(tracefakes.FakePrinter), "") 97 ui = new(testterm.FakeUI) 98 requirementsFactory = new(requirementsfakes.FakeFactory) 99 100 configRepo = testconfig.NewRepository() 101 102 appInstancesRepo = new(appinstancesfakes.FakeAppInstancesRepository) 103 appRepo = new(applicationsfakes.FakeRepository) 104 105 displayApp = new(applicationfakes.FakeAppDisplayer) 106 107 //save original command dependency and restore later 108 originalAppCommand = commandregistry.Commands.FindCommand("app") 109 110 defaultInstanceErrorCodes = []string{"", ""} 111 112 defaultAppForStart = models.Application{ 113 ApplicationFields: models.ApplicationFields{ 114 Name: "my-app", 115 GUID: "my-app-guid", 116 InstanceCount: 2, 117 PackageState: "STAGED", 118 }, 119 } 120 121 defaultAppForStart.Routes = []models.RouteSummary{ 122 { 123 Host: "my-app", 124 Domain: models.DomainFields{ 125 Name: "example.com", 126 }, 127 }, 128 } 129 130 instance1 := models.AppInstanceFields{ 131 State: models.InstanceStarting, 132 } 133 134 instance2 := models.AppInstanceFields{ 135 State: models.InstanceStarting, 136 } 137 138 instance3 := models.AppInstanceFields{ 139 State: models.InstanceRunning, 140 } 141 142 instance4 := models.AppInstanceFields{ 143 State: models.InstanceStarting, 144 } 145 146 defaultInstanceResponses = [][]models.AppInstanceFields{ 147 {instance1, instance2}, 148 {instance1, instance2}, 149 {instance3, instance4}, 150 } 151 152 logRepo = new(logsfakes.FakeRepository) 153 logMessages.Store([]logs.Loggable{}) 154 155 closeWait := sync.WaitGroup{} 156 closeWait.Add(1) 157 158 logRepo.TailLogsForStub = func(appGUID string, onConnect func(), logChan chan<- logs.Loggable, errChan chan<- error) { 159 onConnect() 160 161 go func() { 162 for _, log := range logMessages.Load().([]logs.Loggable) { 163 logChan <- log 164 } 165 166 closeWait.Wait() 167 close(logChan) 168 }() 169 } 170 171 logRepo.CloseStub = func() { 172 closeWait.Done() 173 } 174 }) 175 176 callStart := func(args []string) bool { 177 updateCommandDependency(logRepo) 178 cmd := commandregistry.Commands.FindCommand("start").(*Start) 179 cmd.StagingTimeout = 100 * time.Millisecond 180 cmd.StartupTimeout = 500 * time.Millisecond 181 cmd.PingerThrottle = 10 * time.Millisecond 182 commandregistry.Register(cmd) 183 return testcmd.RunCLICommandWithoutDependency("start", args, requirementsFactory, ui) 184 } 185 186 callStartWithLoggingTimeout := func(args []string) bool { 187 188 logRepoWithTimeout := logsfakes.FakeRepository{} 189 updateCommandDependency(&logRepoWithTimeout) 190 191 cmd := commandregistry.Commands.FindCommand("start").(*Start) 192 cmd.LogServerConnectionTimeout = 100 * time.Millisecond 193 cmd.StagingTimeout = 100 * time.Millisecond 194 cmd.StartupTimeout = 200 * time.Millisecond 195 cmd.PingerThrottle = 10 * time.Millisecond 196 commandregistry.Register(cmd) 197 198 return testcmd.RunCLICommandWithoutDependency("start", args, requirementsFactory, ui) 199 } 200 201 startAppWithInstancesAndErrors := func(app models.Application, requirementsFactory *requirementsfakes.FakeFactory) (*testterm.FakeUI, *applicationsfakes.FakeRepository, *appinstancesfakes.FakeAppInstancesRepository) { 202 appRepo.UpdateReturns(app, nil) 203 appRepo.ReadReturns(app, nil) 204 appRepo.GetAppReturns(app, nil) 205 appInstancesRepo.GetInstancesStub = getInstance 206 207 args := []string{"my-app"} 208 209 applicationReq := new(requirementsfakes.FakeApplicationRequirement) 210 applicationReq.GetApplicationReturns(app) 211 requirementsFactory.NewApplicationRequirementReturns(applicationReq) 212 callStart(args) 213 return ui, appRepo, appInstancesRepo 214 } 215 216 It("fails requirements when not logged in", func() { 217 requirementsFactory.NewLoginRequirementReturns(requirements.Failing{Message: "not logged in"}) 218 219 Expect(callStart([]string{"some-app-name"})).To(BeFalse()) 220 }) 221 222 It("fails requirements when a space is not targeted", func() { 223 requirementsFactory.NewLoginRequirementReturns(requirements.Passing{}) 224 requirementsFactory.NewTargetedSpaceRequirementReturns(requirements.Failing{Message: "not targeting space"}) 225 226 Expect(callStart([]string{"some-app-name"})).To(BeFalse()) 227 }) 228 229 Describe("timeouts", func() { 230 It("has sane default timeout values", func() { 231 updateCommandDependency(logRepo) 232 cmd := commandregistry.Commands.FindCommand("start").(*Start) 233 Expect(cmd.StagingTimeout).To(Equal(15 * time.Minute)) 234 Expect(cmd.StartupTimeout).To(Equal(5 * time.Minute)) 235 }) 236 237 It("can read timeout values from environment variables", func() { 238 oldStaging := os.Getenv("CF_STAGING_TIMEOUT") 239 oldStart := os.Getenv("CF_STARTUP_TIMEOUT") 240 defer func() { 241 os.Setenv("CF_STAGING_TIMEOUT", oldStaging) 242 os.Setenv("CF_STARTUP_TIMEOUT", oldStart) 243 }() 244 245 os.Setenv("CF_STAGING_TIMEOUT", "6") 246 os.Setenv("CF_STARTUP_TIMEOUT", "3") 247 248 updateCommandDependency(logRepo) 249 cmd := commandregistry.Commands.FindCommand("start").(*Start) 250 Expect(cmd.StagingTimeout).To(Equal(6 * time.Minute)) 251 Expect(cmd.StartupTimeout).To(Equal(3 * time.Minute)) 252 }) 253 254 Describe("when the staging timeout is zero seconds", func() { 255 var ( 256 app models.Application 257 ) 258 259 BeforeEach(func() { 260 app = defaultAppForStart 261 262 appRepo.UpdateReturns(app, nil) 263 264 app.PackageState = "FAILED" 265 app.StagingFailedReason = "BLAH, FAILED" 266 appRepo.GetAppReturns(app, nil) 267 268 requirementsFactory.NewLoginRequirementReturns(requirements.Passing{}) 269 requirementsFactory.NewTargetedSpaceRequirementReturns(requirements.Passing{}) 270 applicationReq := new(requirementsfakes.FakeApplicationRequirement) 271 applicationReq.GetApplicationReturns(app) 272 requirementsFactory.NewApplicationRequirementReturns(applicationReq) 273 274 updateCommandDependency(logRepo) 275 cmd := commandregistry.Commands.FindCommand("start").(*Start) 276 cmd.StagingTimeout = 0 277 cmd.PingerThrottle = 1 278 cmd.StartupTimeout = 1 279 commandregistry.Register(cmd) 280 }) 281 282 It("can still respond to staging failures", func() { 283 testcmd.RunCLICommandWithoutDependency("start", []string{"my-app"}, requirementsFactory, ui) 284 285 Expect(ui.Outputs()).To(ContainSubstrings( 286 []string{"my-app"}, 287 []string{"FAILED"}, 288 []string{"BLAH, FAILED"}, 289 )) 290 }) 291 }) 292 293 Context("when the timeout happens exactly when the connection is established", func() { 294 //var startWait *sync.WaitGroup 295 296 BeforeEach(func() { 297 requirementsFactory.NewLoginRequirementReturns(requirements.Passing{}) 298 requirementsFactory.NewTargetedSpaceRequirementReturns(requirements.Passing{}) 299 configRepo = testconfig.NewRepositoryWithDefaults() 300 logRepo.TailLogsForStub = func(appGUID string, onConnect func(), logChan chan<- logs.Loggable, errChan chan<- error) { 301 //startWait.Wait() 302 onConnect() 303 } 304 }) 305 306 // Xing this out due to difficulty unit testing legacy commands, see integration/shared/plugin/logs_test.go for relevant tests since this command is only used for plugins 307 XIt("times out gracefully", func() { 308 updateCommandDependency(logRepo) 309 cmd := commandregistry.Commands.FindCommand("start").(*Start) 310 cmd.LogServerConnectionTimeout = 10 * time.Millisecond 311 doneWait := new(sync.WaitGroup) 312 doneWait.Add(1) 313 cmd.TailStagingLogs(defaultAppForStart, make(chan bool, 1), doneWait) 314 }) 315 }) 316 317 Context("when the noaa library reconnects", func() { 318 var app models.Application 319 BeforeEach(func() { 320 app = defaultAppForStart 321 app.PackageState = "FAILED" 322 app.StagingFailedReason = "BLAH, FAILED" 323 324 requirementsFactory.NewLoginRequirementReturns(requirements.Passing{}) 325 requirementsFactory.NewTargetedSpaceRequirementReturns(requirements.Passing{}) 326 applicationReq := new(requirementsfakes.FakeApplicationRequirement) 327 applicationReq.GetApplicationReturns(app) 328 requirementsFactory.NewApplicationRequirementReturns(applicationReq) 329 330 appRepo.GetAppReturns(app, nil) 331 appRepo.UpdateReturns(app, nil) 332 333 cmd := commandregistry.Commands.FindCommand("start").(*Start) 334 cmd.StagingTimeout = 1 335 cmd.PingerThrottle = 1 336 cmd.StartupTimeout = 1 337 commandregistry.Register(cmd) 338 339 logRepo.TailLogsForStub = func(appGUID string, onConnect func(), logChan chan<- logs.Loggable, errChan chan<- error) { 340 onConnect() 341 onConnect() 342 onConnect() 343 } 344 updateCommandDependency(logRepo) 345 }) 346 347 It("it doesn't cause a negative wait group - github#1019", func() { 348 testcmd.RunCLICommandWithoutDependency("start", []string{"my-app"}, requirementsFactory, ui) 349 }) 350 }) 351 }) 352 353 Context("when logged in", func() { 354 BeforeEach(func() { 355 requirementsFactory.NewLoginRequirementReturns(requirements.Passing{}) 356 requirementsFactory.NewTargetedSpaceRequirementReturns(requirements.Passing{}) 357 configRepo = testconfig.NewRepositoryWithDefaults() 358 }) 359 360 It("fails with usage when not provided exactly one arg", func() { 361 callStart([]string{}) 362 Expect(ui.Outputs()).To(ContainSubstrings( 363 []string{"Incorrect Usage", "Requires an argument"}, 364 )) 365 }) 366 367 It("uses proper org name and space name", func() { 368 appRepo.ReadReturns(defaultAppForStart, nil) 369 appRepo.GetAppReturns(defaultAppForStart, nil) 370 appInstancesRepo.GetInstancesStub = getInstance 371 372 updateCommandDependency(logRepo) 373 cmd := commandregistry.Commands.FindCommand("start").(*Start) 374 cmd.PingerThrottle = 10 * time.Millisecond 375 376 cmd.ApplicationStart(defaultAppForStart, "some-org", "some-space") 377 378 Expect(ui.Outputs()).To(ContainSubstrings( 379 []string{"my-app", "some-org", "some-space", "my-user"}, 380 []string{"OK"}, 381 )) 382 }) 383 384 It("starts an app, when given the app's name", func() { 385 ui, appRepo, _ := startAppWithInstancesAndErrors(defaultAppForStart, requirementsFactory) 386 387 Expect(ui.Outputs()).To(ContainSubstrings( 388 []string{"my-app", "my-org", "my-space", "my-user"}, 389 []string{"OK"}, 390 []string{"0 of 2 instances running", "2 starting"}, 391 []string{"started"}, 392 )) 393 394 appGUID, _ := appRepo.UpdateArgsForCall(0) 395 Expect(appGUID).To(Equal("my-app-guid")) 396 Expect(displayApp.AppToDisplay).To(Equal(defaultAppForStart)) 397 }) 398 399 Context("when app instance count is zero", func() { 400 var zeroInstanceApp models.Application 401 BeforeEach(func() { 402 zeroInstanceApp = models.Application{ 403 ApplicationFields: models.ApplicationFields{ 404 Name: "my-app", 405 GUID: "my-app-guid", 406 InstanceCount: 0, 407 PackageState: "STAGED", 408 }, 409 } 410 defaultInstanceResponses = [][]models.AppInstanceFields{{}} 411 }) 412 413 It("exit without polling for the app, and warns the user", func() { 414 ui, _, _ := startAppWithInstancesAndErrors(zeroInstanceApp, requirementsFactory) 415 416 Expect(ui.Outputs()).To(ContainSubstrings( 417 []string{"my-app", "my-org", "my-space", "my-user"}, 418 []string{"OK"}, 419 []string{"App state changed to started, but note that it has 0 instances."}, 420 )) 421 422 Expect(appRepo.UpdateCallCount()).To(Equal(1)) 423 appGuid, appParams := appRepo.UpdateArgsForCall(0) 424 Expect(appGuid).To(Equal(zeroInstanceApp.GUID)) 425 startedState := "started" 426 Expect(appParams).To(Equal(models.AppParams{State: &startedState})) 427 428 zeroInstanceApp.State = startedState 429 ui, _, _ = startAppWithInstancesAndErrors(zeroInstanceApp, requirementsFactory) 430 Expect(ui.Outputs()).To(ContainSubstrings( 431 []string{"App my-app is already started"}, 432 )) 433 Expect(appRepo.UpdateCallCount()).To(Equal(1)) 434 }) 435 }) 436 It("displays the command start command instead of the detected start command when set", func() { 437 defaultAppForStart.Command = "command start command" 438 defaultAppForStart.DetectedStartCommand = "detected start command" 439 ui, appRepo, _ = startAppWithInstancesAndErrors(defaultAppForStart, requirementsFactory) 440 441 Expect(appRepo.GetAppCallCount()).To(Equal(1)) 442 Expect(appRepo.GetAppArgsForCall(0)).To(Equal("my-app-guid")) 443 Expect(ui.Outputs()).To(ContainSubstrings( 444 []string{"App my-app was started using this command `command start command`"}, 445 )) 446 }) 447 448 It("displays the detected start command when no other command is set", func() { 449 defaultAppForStart.DetectedStartCommand = "detected start command" 450 defaultAppForStart.Command = "" 451 ui, appRepo, _ := startAppWithInstancesAndErrors(defaultAppForStart, requirementsFactory) 452 453 Expect(appRepo.GetAppCallCount()).To(Equal(1)) 454 Expect(appRepo.GetAppArgsForCall(0)).To(Equal("my-app-guid")) 455 Expect(ui.Outputs()).To(ContainSubstrings( 456 []string{"App my-app was started using this command `detected start command`"}, 457 )) 458 }) 459 460 // Xing this out due to difficulty unit testing legacy commands, see integration/shared/plugin/logs_test.go for relevant tests since this command is only used for plugins 461 XIt("handles timeouts gracefully", func() { 462 applicationReq := new(requirementsfakes.FakeApplicationRequirement) 463 applicationReq.GetApplicationReturns(defaultAppForStart) 464 requirementsFactory.NewApplicationRequirementReturns(applicationReq) 465 appRepo.UpdateReturns(defaultAppForStart, nil) 466 appRepo.ReadReturns(defaultAppForStart, nil) 467 468 callStartWithLoggingTimeout([]string{"my-app"}) 469 Expect(ui.Outputs()).To(ContainSubstrings( 470 []string{"timeout connecting to log server"}, 471 )) 472 }) 473 474 // Xing this out due to difficulty unit testing legacy commands, see integration/shared/plugin/logs_test.go for relevant tests since this command is only used for plugins 475 XIt("only displays staging logs when an app is starting", func() { 476 applicationReq := new(requirementsfakes.FakeApplicationRequirement) 477 applicationReq.GetApplicationReturns(defaultAppForStart) 478 requirementsFactory.NewApplicationRequirementReturns(applicationReq) 479 appRepo.UpdateReturns(defaultAppForStart, nil) 480 appRepo.ReadReturns(defaultAppForStart, nil) 481 482 message1 := logsfakes.FakeLoggable{} 483 message1.ToSimpleLogReturns("Log Line 1") 484 485 message2 := logsfakes.FakeLoggable{} 486 message2.GetSourceNameReturns("STG") 487 message2.ToSimpleLogReturns("Log Line 2") 488 489 message3 := logsfakes.FakeLoggable{} 490 message3.GetSourceNameReturns("STG") 491 message3.ToSimpleLogReturns("Log Line 3") 492 493 message4 := logsfakes.FakeLoggable{} 494 message4.ToSimpleLogReturns("Log Line 4") 495 496 logMessages.Store([]logs.Loggable{ 497 &message1, 498 &message2, 499 &message3, 500 &message4, 501 }) 502 503 callStart([]string{"my-app"}) 504 505 Eventually(ui.Outputs()).Should(ContainSubstrings( 506 []string{"Log Line 2"}, 507 []string{"Log Line 3"}, 508 )) 509 Expect(ui.Outputs()).ToNot(ContainSubstrings( 510 []string{"Log Line 1"}, 511 []string{"Log Line 4"}, 512 )) 513 }) 514 515 // Xing this out due to difficulty unit testing legacy commands, see integration/shared/plugin/logs_test.go for relevant tests since this command is only used for plugins 516 XIt("gracefully handles starting an app that is still staging", func() { 517 closeWait := sync.WaitGroup{} 518 closeWait.Add(1) 519 520 logRepo.TailLogsForStub = func(appGUID string, onConnect func(), logChan chan<- logs.Loggable, errChan chan<- error) { 521 onConnect() 522 523 go func() { 524 message1 := logsfakes.FakeLoggable{} 525 message1.ToSimpleLogReturns("Before close") 526 message1.GetSourceNameReturns("STG") 527 528 logChan <- &message1 529 530 closeWait.Wait() 531 532 message2 := logsfakes.FakeLoggable{} 533 message2.ToSimpleLogReturns("After close 1") 534 message2.GetSourceNameReturns("STG") 535 536 message3 := logsfakes.FakeLoggable{} 537 message3.ToSimpleLogReturns("After close 2") 538 message3.GetSourceNameReturns("STG") 539 540 logChan <- &message2 541 logChan <- &message3 542 543 close(logChan) 544 }() 545 } 546 547 logRepo.CloseStub = func() { 548 closeWait.Done() 549 } 550 551 defaultInstanceResponses = [][]models.AppInstanceFields{ 552 {}, 553 {}, 554 {{State: models.InstanceDown}, {State: models.InstanceStarting}}, 555 {{State: models.InstanceStarting}, {State: models.InstanceStarting}}, 556 {{State: models.InstanceRunning}, {State: models.InstanceRunning}}, 557 } 558 559 defaultInstanceErrorCodes = []string{errors.NotStaged, errors.NotStaged, "", "", ""} 560 defaultAppForStart.PackageState = "PENDING" 561 ui, appRepo, _ := startAppWithInstancesAndErrors(defaultAppForStart, requirementsFactory) 562 563 Expect(appRepo.GetAppArgsForCall(0)).To(Equal("my-app-guid")) 564 565 Expect(ui.Outputs()).To(ContainSubstrings( 566 []string{"Before close"}, 567 []string{"After close 1"}, 568 []string{"After close 2"}, 569 []string{"my-app failed to stage within", "minutes"}, 570 )) 571 }) 572 573 It("displays an error message when staging fails", func() { 574 defaultAppForStart.PackageState = "FAILED" 575 defaultAppForStart.StagingFailedReason = "AWWW, FAILED" 576 577 ui, _, _ := startAppWithInstancesAndErrors(defaultAppForStart, requirementsFactory) 578 579 Expect(ui.Outputs()).To(ContainSubstrings( 580 []string{"my-app"}, 581 []string{"FAILED"}, 582 []string{"AWWW, FAILED"}, 583 )) 584 }) 585 586 It("displays an TIP about needing to push from source directory when staging fails with NoAppDetectedError", func() { 587 defaultAppForStart.PackageState = "FAILED" 588 defaultAppForStart.StagingFailedReason = "NoAppDetectedError" 589 590 ui, _, _ := startAppWithInstancesAndErrors(defaultAppForStart, requirementsFactory) 591 592 Expect(ui.Outputs()).To(ContainSubstrings( 593 []string{"my-app"}, 594 []string{"FAILED"}, 595 []string{"is executed from within the directory"}, 596 )) 597 }) 598 599 It("Display a TIP when starting the app timeout", func() { 600 appInstance := models.AppInstanceFields{} 601 appInstance.State = models.InstanceStarting 602 appInstance2 := models.AppInstanceFields{} 603 appInstance2.State = models.InstanceStarting 604 appInstance3 := models.AppInstanceFields{} 605 appInstance3.State = models.InstanceStarting 606 appInstance4 := models.AppInstanceFields{} 607 appInstance4.State = models.InstanceStarting 608 defaultInstanceResponses = [][]models.AppInstanceFields{ 609 {appInstance, appInstance2}, 610 {appInstance3, appInstance4}, 611 } 612 613 defaultInstanceErrorCodes = []string{"some error", ""} 614 615 ui, _, _ := startAppWithInstancesAndErrors(defaultAppForStart, requirementsFactory) 616 617 Expect(ui.Outputs()).To(ContainSubstrings( 618 []string{"TIP: Application must be listening on the right port."}, 619 )) 620 }) 621 622 It("prints a warning when failing to fetch instance count", func() { 623 defaultInstanceResponses = [][]models.AppInstanceFields{} 624 defaultInstanceErrorCodes = []string{"an-error"} 625 626 ui, _, _ := startAppWithInstancesAndErrors(defaultAppForStart, requirementsFactory) 627 628 Expect(ui.Outputs()).To(ContainSubstrings( 629 []string{"an-error"}, 630 )) 631 }) 632 633 Context("when an app instance is flapping", func() { 634 It("fails and alerts the user", func() { 635 appInstance := models.AppInstanceFields{} 636 appInstance.State = models.InstanceStarting 637 appInstance2 := models.AppInstanceFields{} 638 appInstance2.State = models.InstanceStarting 639 appInstance3 := models.AppInstanceFields{} 640 appInstance3.State = models.InstanceStarting 641 appInstance4 := models.AppInstanceFields{} 642 appInstance4.State = models.InstanceFlapping 643 defaultInstanceResponses = [][]models.AppInstanceFields{ 644 {appInstance, appInstance2}, 645 {appInstance3, appInstance4}, 646 } 647 648 defaultInstanceErrorCodes = []string{"", ""} 649 650 ui, _, _ := startAppWithInstancesAndErrors(defaultAppForStart, requirementsFactory) 651 652 Expect(ui.Outputs()).To(ContainSubstrings( 653 []string{"my-app"}, 654 []string{"0 of 2 instances running", "1 starting", "1 failing"}, 655 []string{"FAILED"}, 656 []string{"Start unsuccessful"}, 657 )) 658 }) 659 }) 660 661 Context("when an app instance is crashed", func() { 662 It("fails and alerts the user", func() { 663 appInstance := models.AppInstanceFields{} 664 appInstance.State = models.InstanceStarting 665 appInstance2 := models.AppInstanceFields{} 666 appInstance2.State = models.InstanceStarting 667 appInstance3 := models.AppInstanceFields{} 668 appInstance3.State = models.InstanceStarting 669 appInstance4 := models.AppInstanceFields{} 670 appInstance4.State = models.InstanceCrashed 671 defaultInstanceResponses = [][]models.AppInstanceFields{ 672 {appInstance, appInstance2}, 673 {appInstance3, appInstance4}, 674 } 675 676 defaultInstanceErrorCodes = []string{"", ""} 677 678 ui, _, _ := startAppWithInstancesAndErrors(defaultAppForStart, requirementsFactory) 679 680 Expect(ui.Outputs()).To(ContainSubstrings( 681 []string{"my-app"}, 682 []string{"0 of 2 instances running", "1 starting", "1 crashed"}, 683 []string{"FAILED"}, 684 []string{"Start unsuccessful"}, 685 )) 686 }) 687 }) 688 689 Context("when an app instance is starting", func() { 690 It("reports any additional details", func() { 691 appInstance := models.AppInstanceFields{ 692 State: models.InstanceStarting, 693 } 694 appInstance2 := models.AppInstanceFields{ 695 State: models.InstanceStarting, 696 } 697 698 appInstance3 := models.AppInstanceFields{ 699 State: models.InstanceDown, 700 } 701 appInstance4 := models.AppInstanceFields{ 702 State: models.InstanceStarting, 703 Details: "no compatible cell", 704 } 705 706 appInstance5 := models.AppInstanceFields{ 707 State: models.InstanceStarting, 708 Details: "insufficient resources", 709 } 710 appInstance6 := models.AppInstanceFields{ 711 State: models.InstanceStarting, 712 Details: "no compatible cell", 713 } 714 715 appInstance7 := models.AppInstanceFields{ 716 State: models.InstanceRunning, 717 } 718 appInstance8 := models.AppInstanceFields{ 719 State: models.InstanceRunning, 720 } 721 722 defaultInstanceResponses = [][]models.AppInstanceFields{ 723 {appInstance, appInstance2}, 724 {appInstance3, appInstance4}, 725 {appInstance5, appInstance6}, 726 {appInstance7, appInstance8}, 727 } 728 729 defaultInstanceErrorCodes = []string{"", ""} 730 731 ui, _, _ := startAppWithInstancesAndErrors(defaultAppForStart, requirementsFactory) 732 Expect(ui.Outputs()).To(ContainSubstrings( 733 []string{"my-app"}, 734 []string{"0 of 2 instances running", "2 starting"}, 735 []string{"0 of 2 instances running", "1 starting (no compatible cell)", "1 down"}, 736 []string{"0 of 2 instances running", "2 starting (insufficient resources, no compatible cell)"}, 737 []string{"2 of 2 instances running"}, 738 []string{"App started"}, 739 )) 740 }) 741 }) 742 743 It("tells the user about the failure when waiting for the app to stage times out", func() { 744 defaultInstanceErrorCodes = []string{errors.NotStaged, errors.NotStaged, errors.NotStaged} 745 746 defaultAppForStart.PackageState = "PENDING" 747 ui, _, _ := startAppWithInstancesAndErrors(defaultAppForStart, requirementsFactory) 748 749 Expect(ui.Outputs()).To(ContainSubstrings( 750 []string{"Starting", "my-app"}, 751 []string{"FAILED"}, 752 []string{"my-app failed to stage within", "minutes"}, 753 )) 754 Expect(ui.Outputs()).ToNot(ContainSubstrings([]string{"instances running"})) 755 }) 756 757 XIt("tells the user about the failure when starting the app fails", func() { 758 app := models.Application{} 759 app.Name = "my-app" 760 app.GUID = "my-app-guid" 761 appRepo.UpdateReturns(models.Application{}, errors.New("Error updating app.")) 762 appRepo.ReadReturns(app, nil) 763 args := []string{"my-app"} 764 applicationReq := new(requirementsfakes.FakeApplicationRequirement) 765 applicationReq.GetApplicationReturns(app) 766 requirementsFactory.NewApplicationRequirementReturns(applicationReq) 767 callStart(args) 768 769 Expect(ui.Outputs()).To(ContainSubstrings( 770 []string{"my-app"}, 771 []string{"FAILED"}, 772 []string{"Error updating app."}, 773 )) 774 appGUID, _ := appRepo.UpdateArgsForCall(0) 775 Expect(appGUID).To(Equal("my-app-guid")) 776 }) 777 778 It("warns the user when the app is already running", func() { 779 app := models.Application{} 780 app.Name = "my-app" 781 app.GUID = "my-app-guid" 782 app.State = "started" 783 appRepo := new(applicationsfakes.FakeRepository) 784 appRepo.ReadReturns(app, nil) 785 786 applicationReq := new(requirementsfakes.FakeApplicationRequirement) 787 applicationReq.GetApplicationReturns(app) 788 requirementsFactory.NewApplicationRequirementReturns(applicationReq) 789 790 args := []string{"my-app"} 791 callStart(args) 792 793 Expect(ui.Outputs()).To(ContainSubstrings([]string{"my-app", "is already started"})) 794 795 Expect(appRepo.UpdateCallCount()).To(BeZero()) 796 }) 797 798 // Xing this out due to difficulty unit testing legacy commands, see integration/shared/plugin/logs_test.go for relevant tests since this command is only used for plugins 799 XIt("tells the user when connecting to the log server fails", func() { 800 appRepo.ReadReturns(defaultAppForStart, nil) 801 802 logRepo.TailLogsForStub = func(appGUID string, onConnect func(), logChan chan<- logs.Loggable, errChan chan<- error) { 803 errChan <- errors.New("Ooops") 804 } 805 806 applicationReq := new(requirementsfakes.FakeApplicationRequirement) 807 applicationReq.GetApplicationReturns(defaultAppForStart) 808 requirementsFactory.NewApplicationRequirementReturns(applicationReq) 809 810 callStart([]string{"my-app"}) 811 812 Expect(ui.Outputs()).To(ContainSubstrings( 813 []string{"error tailing logs"}, 814 []string{"Ooops"}, 815 )) 816 }) 817 }) 818 })