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  })