github.com/wanddynosios/cli/v8@v8.7.9-0.20240221182337-1a92e3a7017f/actor/v7action/application_test.go (about)

     1  package v7action_test
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"time"
     7  
     8  	"code.cloudfoundry.org/cli/actor/actionerror"
     9  	. "code.cloudfoundry.org/cli/actor/v7action"
    10  	"code.cloudfoundry.org/cli/actor/v7action/v7actionfakes"
    11  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccerror"
    12  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccv3"
    13  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/constant"
    14  	"code.cloudfoundry.org/cli/resources"
    15  	"code.cloudfoundry.org/cli/types"
    16  	"code.cloudfoundry.org/cli/util/batcher"
    17  	"code.cloudfoundry.org/clock/fakeclock"
    18  	. "github.com/onsi/ginkgo"
    19  	. "github.com/onsi/gomega"
    20  )
    21  
    22  var _ = Describe("Application Actions", func() {
    23  	var (
    24  		actor                     *Actor
    25  		fakeCloudControllerClient *v7actionfakes.FakeCloudControllerClient
    26  		fakeConfig                *v7actionfakes.FakeConfig
    27  		fakeClock                 *fakeclock.FakeClock
    28  	)
    29  
    30  	BeforeEach(func() {
    31  		fakeCloudControllerClient = new(v7actionfakes.FakeCloudControllerClient)
    32  		fakeConfig = new(v7actionfakes.FakeConfig)
    33  		fakeClock = fakeclock.NewFakeClock(time.Now())
    34  		actor = NewActor(fakeCloudControllerClient, fakeConfig, nil, nil, nil, fakeClock)
    35  	})
    36  
    37  	Describe("DeleteApplicationByNameAndSpace", func() {
    38  		var (
    39  			warnings           Warnings
    40  			executeErr         error
    41  			deleteMappedRoutes bool
    42  			appName            string
    43  		)
    44  
    45  		JustBeforeEach(func() {
    46  			appName = "some-app"
    47  			warnings, executeErr = actor.DeleteApplicationByNameAndSpace(appName, "some-space-guid", deleteMappedRoutes)
    48  		})
    49  
    50  		When("looking up the app guid fails", func() {
    51  			BeforeEach(func() {
    52  				fakeCloudControllerClient.GetApplicationsReturns([]resources.Application{}, ccv3.Warnings{"some-get-app-warning"}, errors.New("some-get-app-error"))
    53  			})
    54  
    55  			It("returns the warnings and error", func() {
    56  				Expect(warnings).To(ConsistOf("some-get-app-warning"))
    57  				Expect(executeErr).To(MatchError("some-get-app-error"))
    58  			})
    59  		})
    60  
    61  		When("looking up the app guid succeeds without routes", func() {
    62  			BeforeEach(func() {
    63  				fakeCloudControllerClient.GetApplicationsReturns([]resources.Application{{Name: "some-app", GUID: "abc123"}}, ccv3.Warnings{"some-get-app-warning"}, nil)
    64  				deleteMappedRoutes = false
    65  			})
    66  
    67  			When("sending the delete fails", func() {
    68  				BeforeEach(func() {
    69  					fakeCloudControllerClient.DeleteApplicationReturns("", ccv3.Warnings{"some-delete-app-warning"}, errors.New("some-delete-app-error"))
    70  				})
    71  
    72  				It("returns the warnings and error", func() {
    73  					Expect(warnings).To(ConsistOf("some-get-app-warning", "some-delete-app-warning"))
    74  					Expect(executeErr).To(MatchError("some-delete-app-error"))
    75  				})
    76  			})
    77  
    78  			When("sending the delete succeeds", func() {
    79  				BeforeEach(func() {
    80  					fakeCloudControllerClient.DeleteApplicationReturns("/some-job-url", ccv3.Warnings{"some-delete-app-warning"}, nil)
    81  				})
    82  
    83  				When("polling fails", func() {
    84  					BeforeEach(func() {
    85  						fakeCloudControllerClient.PollJobReturns(ccv3.Warnings{"some-poll-warning"}, errors.New("some-poll-error"))
    86  					})
    87  
    88  					It("returns the warnings and poll error", func() {
    89  						Expect(warnings).To(ConsistOf("some-get-app-warning", "some-delete-app-warning", "some-poll-warning"))
    90  						Expect(executeErr).To(MatchError("some-poll-error"))
    91  					})
    92  				})
    93  
    94  				When("polling succeeds", func() {
    95  					BeforeEach(func() {
    96  						fakeCloudControllerClient.PollJobReturns(ccv3.Warnings{"some-poll-warning"}, nil)
    97  					})
    98  
    99  					It("returns all the warnings and no error", func() {
   100  						Expect(warnings).To(ConsistOf("some-get-app-warning", "some-delete-app-warning", "some-poll-warning"))
   101  						Expect(executeErr).ToNot(HaveOccurred())
   102  					})
   103  				})
   104  			})
   105  		})
   106  
   107  		When("looking up the app guid succeeds with routes", func() {
   108  			BeforeEach(func() {
   109  				deleteMappedRoutes = true
   110  				fakeCloudControllerClient.GetApplicationsReturns([]resources.Application{{Name: "some-app", GUID: "abc123"}}, nil, nil)
   111  			})
   112  
   113  			When("getting the routes fails", func() {
   114  				BeforeEach(func() {
   115  					fakeCloudControllerClient.GetApplicationRoutesReturns(nil, ccv3.Warnings{"get-routes-warning"}, errors.New("get-routes-error"))
   116  				})
   117  
   118  				It("returns the warnings and an error", func() {
   119  					Expect(warnings).To(ConsistOf("get-routes-warning"))
   120  					Expect(executeErr).To(MatchError("get-routes-error"))
   121  				})
   122  			})
   123  
   124  			When("getting the routes succeeds", func() {
   125  				When("there are no routes", func() {
   126  					BeforeEach(func() {
   127  						fakeCloudControllerClient.GetApplicationRoutesReturns([]resources.Route{}, nil, nil)
   128  					})
   129  
   130  					It("does not delete any routes", func() {
   131  						Expect(fakeCloudControllerClient.DeleteRouteCallCount()).To(Equal(0))
   132  					})
   133  				})
   134  
   135  				When("there are routes", func() {
   136  					BeforeEach(func() {
   137  						fakeCloudControllerClient.GetApplicationRoutesReturns([]resources.Route{{GUID: "route-1-guid"}, {GUID: "route-2-guid", URL: "route-2.example.com"}}, nil, nil)
   138  					})
   139  
   140  					It("deletes the routes", func() {
   141  						Expect(fakeCloudControllerClient.GetApplicationRoutesCallCount()).To(Equal(1))
   142  						Expect(fakeCloudControllerClient.GetApplicationRoutesArgsForCall(0)).To(Equal("abc123"))
   143  						Expect(fakeCloudControllerClient.DeleteRouteCallCount()).To(Equal(2))
   144  						guids := []string{fakeCloudControllerClient.DeleteRouteArgsForCall(0), fakeCloudControllerClient.DeleteRouteArgsForCall(1)}
   145  						Expect(guids).To(ConsistOf("route-1-guid", "route-2-guid"))
   146  					})
   147  
   148  					When("the route has already been deleted", func() {
   149  						BeforeEach(func() {
   150  							fakeCloudControllerClient.DeleteRouteReturnsOnCall(0,
   151  								"",
   152  								ccv3.Warnings{"delete-route-1-warning"},
   153  								ccerror.ResourceNotFoundError{},
   154  							)
   155  							fakeCloudControllerClient.DeleteRouteReturnsOnCall(1,
   156  								"poll-job-url",
   157  								ccv3.Warnings{"delete-route-2-warning"},
   158  								nil,
   159  							)
   160  							fakeCloudControllerClient.PollJobReturnsOnCall(1, ccv3.Warnings{"poll-job-warning"}, nil)
   161  						})
   162  
   163  						It("does **not** fail", func() {
   164  							Expect(executeErr).ToNot(HaveOccurred())
   165  							Expect(warnings).To(ConsistOf("delete-route-1-warning", "delete-route-2-warning", "poll-job-warning"))
   166  							Expect(fakeCloudControllerClient.DeleteRouteCallCount()).To(Equal(2))
   167  							Expect(fakeCloudControllerClient.PollJobCallCount()).To(Equal(2))
   168  							Expect(fakeCloudControllerClient.PollJobArgsForCall(1)).To(BeEquivalentTo("poll-job-url"))
   169  						})
   170  					})
   171  
   172  					When("app to delete has a route bound to another app", func() {
   173  						BeforeEach(func() {
   174  							fakeCloudControllerClient.GetApplicationRoutesReturns(
   175  								[]resources.Route{
   176  									{GUID: "route-1-guid"},
   177  									{GUID: "route-2-guid",
   178  										URL: "route-2.example.com",
   179  										Destinations: []resources.RouteDestination{
   180  											{App: resources.RouteDestinationApp{GUID: "abc123"}},
   181  											{App: resources.RouteDestinationApp{GUID: "different-app-guid"}},
   182  										},
   183  									},
   184  								},
   185  								nil,
   186  								nil,
   187  							)
   188  						})
   189  
   190  						It("refuses the entire operation", func() {
   191  							Expect(executeErr).To(MatchError(actionerror.RouteBoundToMultipleAppsError{AppName: "some-app", RouteURL: "route-2.example.com"}))
   192  							Expect(warnings).To(BeEmpty())
   193  							Expect(fakeCloudControllerClient.DeleteApplicationCallCount()).To(Equal(0))
   194  							Expect(fakeCloudControllerClient.DeleteRouteCallCount()).To(Equal(0))
   195  						})
   196  					})
   197  
   198  					When("deleting the route fails", func() {
   199  						BeforeEach(func() {
   200  							fakeCloudControllerClient.DeleteRouteReturnsOnCall(0,
   201  								"poll-job-url",
   202  								ccv3.Warnings{"delete-route-1-warning"},
   203  								nil,
   204  							)
   205  							fakeCloudControllerClient.DeleteRouteReturnsOnCall(1,
   206  								"",
   207  								ccv3.Warnings{"delete-route-2-warning"},
   208  								errors.New("delete-route-2-error"),
   209  							)
   210  						})
   211  
   212  						It("returns the error", func() {
   213  							Expect(executeErr).To(MatchError("delete-route-2-error"))
   214  							Expect(warnings).To(ConsistOf("delete-route-1-warning", "delete-route-2-warning"))
   215  						})
   216  					})
   217  
   218  					When("the polling job fails", func() {
   219  						BeforeEach(func() {
   220  							fakeCloudControllerClient.PollJobReturns(ccv3.Warnings{"poll-job-warning"}, errors.New("poll-job-error"))
   221  						})
   222  
   223  						It("returns the error", func() {
   224  							Expect(executeErr).To(MatchError("poll-job-error"))
   225  						})
   226  					})
   227  
   228  				})
   229  			})
   230  		})
   231  	})
   232  
   233  	Describe("GetApplicationsByGUIDs", func() {
   234  		When("all of the requested apps exist", func() {
   235  			BeforeEach(func() {
   236  				fakeCloudControllerClient.GetApplicationsReturns(
   237  					[]resources.Application{
   238  						{
   239  							Name: "some-app-name",
   240  							GUID: "some-app-guid",
   241  						},
   242  						{
   243  							Name: "other-app-name",
   244  							GUID: "other-app-guid",
   245  						},
   246  					},
   247  					ccv3.Warnings{"some-warning"},
   248  					nil,
   249  				)
   250  			})
   251  
   252  			It("returns the applications and warnings", func() {
   253  				apps, warnings, err := actor.GetApplicationsByGUIDs([]string{"some-app-guid", "other-app-guid"})
   254  				Expect(err).ToNot(HaveOccurred())
   255  				Expect(apps).To(ConsistOf(
   256  					resources.Application{
   257  						Name: "some-app-name",
   258  						GUID: "some-app-guid",
   259  					},
   260  					resources.Application{
   261  						Name: "other-app-name",
   262  						GUID: "other-app-guid",
   263  					},
   264  				))
   265  				Expect(warnings).To(ConsistOf("some-warning"))
   266  
   267  				Expect(fakeCloudControllerClient.GetApplicationsCallCount()).To(Equal(1))
   268  				Expect(fakeCloudControllerClient.GetApplicationsArgsForCall(0)).To(ConsistOf(
   269  					ccv3.Query{Key: ccv3.GUIDFilter, Values: []string{"some-app-guid", "other-app-guid"}},
   270  				))
   271  			})
   272  		})
   273  
   274  		When("at least one of the requested apps does not exist", func() {
   275  			BeforeEach(func() {
   276  				fakeCloudControllerClient.GetApplicationsReturns(
   277  					[]resources.Application{
   278  						{
   279  							Name: "some-app-name",
   280  							GUID: "some-app-guid",
   281  						},
   282  					},
   283  					ccv3.Warnings{"some-warning"},
   284  					nil,
   285  				)
   286  			})
   287  
   288  			It("returns an ApplicationNotFoundError and the warnings", func() {
   289  				_, warnings, err := actor.GetApplicationsByGUIDs([]string{"some-app-guid", "nonexistent-app-guid"})
   290  				Expect(warnings).To(ConsistOf("some-warning"))
   291  				Expect(err).To(MatchError(actionerror.ApplicationsNotFoundError{}))
   292  			})
   293  		})
   294  
   295  		When("a single app has two routes", func() {
   296  			BeforeEach(func() {
   297  				fakeCloudControllerClient.GetApplicationsReturns(
   298  					[]resources.Application{
   299  						{
   300  							Name: "some-app-name",
   301  							GUID: "some-app-guid",
   302  						},
   303  					},
   304  					ccv3.Warnings{"some-warning"},
   305  					nil,
   306  				)
   307  			})
   308  
   309  			It("returns an ApplicationNotFoundError and the warnings", func() {
   310  				_, warnings, err := actor.GetApplicationsByGUIDs([]string{"some-app-guid", "some-app-guid"})
   311  				Expect(err).ToNot(HaveOccurred())
   312  				Expect(warnings).To(ConsistOf("some-warning"))
   313  			})
   314  		})
   315  
   316  		When("the cloud controller client returns an error", func() {
   317  			var expectedError error
   318  
   319  			BeforeEach(func() {
   320  				expectedError = errors.New("I am a CloudControllerClient Error")
   321  				fakeCloudControllerClient.GetApplicationsReturns(
   322  					[]resources.Application{},
   323  					ccv3.Warnings{"some-warning"},
   324  					expectedError)
   325  			})
   326  
   327  			It("returns the warnings and the error", func() {
   328  				_, warnings, err := actor.GetApplicationsByGUIDs([]string{"some-app-guid"})
   329  				Expect(warnings).To(ConsistOf("some-warning"))
   330  				Expect(err).To(MatchError(expectedError))
   331  			})
   332  		})
   333  
   334  		When("there are many guids", func() {
   335  			const batches = 10
   336  			var guids []string
   337  
   338  			BeforeEach(func() {
   339  				var apps []resources.Application
   340  
   341  				for i := 0; i < batcher.BatchSize*batches; i++ {
   342  					guids = append(guids, fmt.Sprintf("app-%d-guid", i))
   343  					apps = append(apps, resources.Application{
   344  						Name: fmt.Sprintf("app-%d-name", i),
   345  						GUID: fmt.Sprintf("app-%d-guid", i),
   346  					})
   347  				}
   348  
   349  				for b := 0; b < batches; b++ {
   350  					fakeCloudControllerClient.GetApplicationsReturnsOnCall(
   351  						b,
   352  						apps[:batcher.BatchSize],
   353  						ccv3.Warnings{"some-warning"},
   354  						nil,
   355  					)
   356  					apps = apps[batcher.BatchSize:]
   357  				}
   358  			})
   359  
   360  			It("makes many calls", func() {
   361  				apps, warnings, err := actor.GetApplicationsByGUIDs(guids)
   362  				Expect(len(apps)).To(Equal(batches * batcher.BatchSize))
   363  				Expect(apps).To(HaveLen(batcher.BatchSize * 10))
   364  				Expect(warnings).To(HaveLen(10))
   365  				Expect(err).NotTo(HaveOccurred())
   366  
   367  				Expect(fakeCloudControllerClient.GetApplicationsCallCount()).To(Equal(10))
   368  				Expect(fakeCloudControllerClient.GetApplicationsArgsForCall(0)).
   369  					NotTo(Equal(fakeCloudControllerClient.GetApplicationsArgsForCall(1)))
   370  			})
   371  		})
   372  	})
   373  
   374  	Describe("GetApplicationsByNameAndSpace", func() {
   375  		When("all of the requested apps exist", func() {
   376  			BeforeEach(func() {
   377  				fakeCloudControllerClient.GetApplicationsReturns(
   378  					[]resources.Application{
   379  						{
   380  							Name: "some-app-name",
   381  							GUID: "some-app-guid",
   382  						},
   383  						{
   384  							Name: "other-app-name",
   385  							GUID: "other-app-guid",
   386  						},
   387  					},
   388  					ccv3.Warnings{"some-warning"},
   389  					nil,
   390  				)
   391  			})
   392  
   393  			It("returns the applications and warnings", func() {
   394  				apps, warnings, err := actor.GetApplicationsByNamesAndSpace([]string{"some-app-name", "other-app-name"}, "some-space-guid")
   395  				Expect(err).ToNot(HaveOccurred())
   396  				Expect(apps).To(ConsistOf(
   397  					resources.Application{
   398  						Name: "some-app-name",
   399  						GUID: "some-app-guid",
   400  					},
   401  					resources.Application{
   402  						Name: "other-app-name",
   403  						GUID: "other-app-guid",
   404  					},
   405  				))
   406  				Expect(warnings).To(ConsistOf("some-warning"))
   407  
   408  				Expect(fakeCloudControllerClient.GetApplicationsCallCount()).To(Equal(1))
   409  				Expect(fakeCloudControllerClient.GetApplicationsArgsForCall(0)).To(ConsistOf(
   410  					ccv3.Query{Key: ccv3.NameFilter, Values: []string{"some-app-name", "other-app-name"}},
   411  					ccv3.Query{Key: ccv3.SpaceGUIDFilter, Values: []string{"some-space-guid"}},
   412  				))
   413  			})
   414  		})
   415  
   416  		When("at least one of the requested apps does not exist", func() {
   417  			BeforeEach(func() {
   418  				fakeCloudControllerClient.GetApplicationsReturns(
   419  					[]resources.Application{
   420  						{
   421  							Name: "some-app-name",
   422  						},
   423  					},
   424  					ccv3.Warnings{"some-warning"},
   425  					nil,
   426  				)
   427  			})
   428  
   429  			It("returns an ApplicationNotFoundError and the warnings", func() {
   430  				_, warnings, err := actor.GetApplicationsByNamesAndSpace([]string{"some-app-name", "other-app-name"}, "some-space-guid")
   431  				Expect(warnings).To(ConsistOf("some-warning"))
   432  				Expect(err).To(MatchError(actionerror.ApplicationsNotFoundError{}))
   433  			})
   434  		})
   435  
   436  		When("a given app has two routes", func() {
   437  			BeforeEach(func() {
   438  				fakeCloudControllerClient.GetApplicationsReturns(
   439  					[]resources.Application{
   440  						{
   441  							Name: "some-app-name",
   442  						},
   443  					},
   444  					ccv3.Warnings{"some-warning"},
   445  					nil,
   446  				)
   447  			})
   448  
   449  			It("returns an ApplicationNotFoundError and the warnings", func() {
   450  				_, warnings, err := actor.GetApplicationsByNamesAndSpace([]string{"some-app-name", "some-app-name"}, "some-space-guid")
   451  				Expect(err).ToNot(HaveOccurred())
   452  				Expect(warnings).To(ConsistOf("some-warning"))
   453  			})
   454  		})
   455  
   456  		When("the cloud controller client returns an error", func() {
   457  			var expectedError error
   458  
   459  			BeforeEach(func() {
   460  				expectedError = errors.New("I am a CloudControllerClient Error")
   461  				fakeCloudControllerClient.GetApplicationsReturns(
   462  					[]resources.Application{},
   463  					ccv3.Warnings{"some-warning"},
   464  					expectedError)
   465  			})
   466  
   467  			It("returns the warnings and the error", func() {
   468  				_, warnings, err := actor.GetApplicationsByNamesAndSpace([]string{"some-app-name"}, "some-space-guid")
   469  				Expect(warnings).To(ConsistOf("some-warning"))
   470  				Expect(err).To(MatchError(expectedError))
   471  			})
   472  		})
   473  	})
   474  
   475  	Describe("GetApplicationByNameAndSpace", func() {
   476  		When("the app exists", func() {
   477  			BeforeEach(func() {
   478  				fakeCloudControllerClient.GetApplicationsReturns(
   479  					[]resources.Application{
   480  						{
   481  							Name: "some-app-name",
   482  							GUID: "some-app-guid",
   483  							Metadata: &resources.Metadata{
   484  								Labels: map[string]types.NullString{
   485  									"some-key": types.NewNullString("some-value"),
   486  								},
   487  							},
   488  						},
   489  					},
   490  					ccv3.Warnings{"some-warning"},
   491  					nil,
   492  				)
   493  			})
   494  
   495  			It("returns the application and warnings", func() {
   496  				app, warnings, err := actor.GetApplicationByNameAndSpace("some-app-name", "some-space-guid")
   497  				Expect(err).ToNot(HaveOccurred())
   498  				Expect(app).To(Equal(resources.Application{
   499  					Name: "some-app-name",
   500  					GUID: "some-app-guid",
   501  					Metadata: &resources.Metadata{
   502  						Labels: map[string]types.NullString{"some-key": types.NewNullString("some-value")},
   503  					},
   504  				}))
   505  				Expect(warnings).To(ConsistOf("some-warning"))
   506  
   507  				Expect(fakeCloudControllerClient.GetApplicationsCallCount()).To(Equal(1))
   508  				Expect(fakeCloudControllerClient.GetApplicationsArgsForCall(0)).To(ConsistOf(
   509  					ccv3.Query{Key: ccv3.NameFilter, Values: []string{"some-app-name"}},
   510  					ccv3.Query{Key: ccv3.SpaceGUIDFilter, Values: []string{"some-space-guid"}},
   511  				))
   512  			})
   513  		})
   514  
   515  		When("the cloud controller client returns an error", func() {
   516  			var expectedError error
   517  
   518  			BeforeEach(func() {
   519  				expectedError = errors.New("I am a CloudControllerClient Error")
   520  				fakeCloudControllerClient.GetApplicationsReturns(
   521  					[]resources.Application{},
   522  					ccv3.Warnings{"some-warning"},
   523  					expectedError)
   524  			})
   525  
   526  			It("returns the warnings and the error", func() {
   527  				_, warnings, err := actor.GetApplicationByNameAndSpace("some-app-name", "some-space-guid")
   528  				Expect(warnings).To(ConsistOf("some-warning"))
   529  				Expect(err).To(MatchError(expectedError))
   530  			})
   531  		})
   532  
   533  		When("the app does not exist", func() {
   534  			BeforeEach(func() {
   535  				fakeCloudControllerClient.GetApplicationsReturns(
   536  					[]resources.Application{},
   537  					ccv3.Warnings{"some-warning"},
   538  					nil,
   539  				)
   540  			})
   541  
   542  			It("returns an ApplicationNotFoundError and the warnings", func() {
   543  				_, warnings, err := actor.GetApplicationByNameAndSpace("some-app-name", "some-space-guid")
   544  				Expect(warnings).To(ConsistOf("some-warning"))
   545  				Expect(err).To(MatchError(actionerror.ApplicationNotFoundError{Name: "some-app-name"}))
   546  			})
   547  		})
   548  	})
   549  
   550  	Describe("GetApplicationsBySpace", func() {
   551  		When("the there are applications in the space", func() {
   552  			BeforeEach(func() {
   553  				fakeCloudControllerClient.GetApplicationsReturns(
   554  					[]resources.Application{
   555  						{
   556  							GUID: "some-app-guid-1",
   557  							Name: "some-app-1",
   558  						},
   559  						{
   560  							GUID: "some-app-guid-2",
   561  							Name: "some-app-2",
   562  						},
   563  					},
   564  					ccv3.Warnings{"warning-1", "warning-2"},
   565  					nil,
   566  				)
   567  			})
   568  
   569  			It("returns the application and warnings", func() {
   570  				apps, warnings, err := actor.GetApplicationsBySpace("some-space-guid")
   571  				Expect(err).ToNot(HaveOccurred())
   572  				Expect(apps).To(ConsistOf(
   573  					resources.Application{
   574  						GUID: "some-app-guid-1",
   575  						Name: "some-app-1",
   576  					},
   577  					resources.Application{
   578  						GUID: "some-app-guid-2",
   579  						Name: "some-app-2",
   580  					},
   581  				))
   582  				Expect(warnings).To(ConsistOf("warning-1", "warning-2"))
   583  
   584  				Expect(fakeCloudControllerClient.GetApplicationsCallCount()).To(Equal(1))
   585  				Expect(fakeCloudControllerClient.GetApplicationsArgsForCall(0)).To(ConsistOf(
   586  					ccv3.Query{Key: ccv3.SpaceGUIDFilter, Values: []string{"some-space-guid"}},
   587  				))
   588  			})
   589  		})
   590  
   591  		When("the cloud controller client returns an error", func() {
   592  			var expectedError error
   593  
   594  			BeforeEach(func() {
   595  				expectedError = errors.New("I am a CloudControllerClient Error")
   596  				fakeCloudControllerClient.GetApplicationsReturns(
   597  					[]resources.Application{},
   598  					ccv3.Warnings{"some-warning"},
   599  					expectedError)
   600  			})
   601  
   602  			It("returns the error and warnings", func() {
   603  				_, warnings, err := actor.GetApplicationsBySpace("some-space-guid")
   604  				Expect(warnings).To(ConsistOf("some-warning"))
   605  				Expect(err).To(MatchError(expectedError))
   606  			})
   607  		})
   608  	})
   609  
   610  	Describe("CreateApplicationInSpace", func() {
   611  		var (
   612  			application resources.Application
   613  			warnings    Warnings
   614  			err         error
   615  		)
   616  
   617  		JustBeforeEach(func() {
   618  			application, warnings, err = actor.CreateApplicationInSpace(resources.Application{
   619  				Name:                "some-app-name",
   620  				LifecycleType:       constant.AppLifecycleTypeBuildpack,
   621  				LifecycleBuildpacks: []string{"buildpack-1", "buildpack-2"},
   622  			}, "some-space-guid")
   623  		})
   624  
   625  		When("the app successfully gets created", func() {
   626  			BeforeEach(func() {
   627  				fakeCloudControllerClient.CreateApplicationReturns(
   628  					resources.Application{
   629  						Name:                "some-app-name",
   630  						GUID:                "some-app-guid",
   631  						LifecycleType:       constant.AppLifecycleTypeBuildpack,
   632  						LifecycleBuildpacks: []string{"buildpack-1", "buildpack-2"},
   633  					},
   634  					ccv3.Warnings{"some-warning"},
   635  					nil,
   636  				)
   637  			})
   638  
   639  			It("creates and returns the application and warnings", func() {
   640  				Expect(err).ToNot(HaveOccurred())
   641  				Expect(application).To(Equal(resources.Application{
   642  					Name:                "some-app-name",
   643  					GUID:                "some-app-guid",
   644  					LifecycleType:       constant.AppLifecycleTypeBuildpack,
   645  					LifecycleBuildpacks: []string{"buildpack-1", "buildpack-2"},
   646  				}))
   647  				Expect(warnings).To(ConsistOf("some-warning"))
   648  
   649  				Expect(fakeCloudControllerClient.CreateApplicationCallCount()).To(Equal(1))
   650  				Expect(fakeCloudControllerClient.CreateApplicationArgsForCall(0)).To(Equal(resources.Application{
   651  					Name:                "some-app-name",
   652  					SpaceGUID:           "some-space-guid",
   653  					LifecycleType:       constant.AppLifecycleTypeBuildpack,
   654  					LifecycleBuildpacks: []string{"buildpack-1", "buildpack-2"},
   655  				}))
   656  			})
   657  		})
   658  
   659  		When("the cc client returns an error", func() {
   660  			var expectedError error
   661  
   662  			BeforeEach(func() {
   663  				expectedError = errors.New("I am a CloudControllerClient Error")
   664  				fakeCloudControllerClient.CreateApplicationReturns(
   665  					resources.Application{},
   666  					ccv3.Warnings{"some-warning"},
   667  					expectedError,
   668  				)
   669  			})
   670  
   671  			It("raises the error and warnings", func() {
   672  				Expect(err).To(MatchError(expectedError))
   673  				Expect(warnings).To(ConsistOf("some-warning"))
   674  			})
   675  		})
   676  
   677  		When("the cc client returns an NameNotUniqueInSpaceError", func() {
   678  			BeforeEach(func() {
   679  				fakeCloudControllerClient.CreateApplicationReturns(
   680  					resources.Application{},
   681  					ccv3.Warnings{"some-warning"},
   682  					ccerror.NameNotUniqueInSpaceError{},
   683  				)
   684  			})
   685  
   686  			It("returns the NameNotUniqueInSpaceError and warnings", func() {
   687  				Expect(err).To(MatchError(ccerror.NameNotUniqueInSpaceError{}))
   688  				Expect(warnings).To(ConsistOf("some-warning"))
   689  			})
   690  		})
   691  	})
   692  
   693  	Describe("UpdateApplication", func() {
   694  		var (
   695  			submitApp, resultApp resources.Application
   696  			warnings             Warnings
   697  			err                  error
   698  		)
   699  
   700  		JustBeforeEach(func() {
   701  			submitApp = resources.Application{
   702  				GUID:                "some-app-guid",
   703  				StackName:           "some-stack-name",
   704  				Name:                "some-app-name",
   705  				LifecycleType:       constant.AppLifecycleTypeBuildpack,
   706  				LifecycleBuildpacks: []string{"buildpack-1", "buildpack-2"},
   707  				Metadata: &resources.Metadata{Labels: map[string]types.NullString{
   708  					"some-label":  types.NewNullString("some-value"),
   709  					"other-label": types.NewNullString("other-value"),
   710  				}},
   711  			}
   712  
   713  			resultApp, warnings, err = actor.UpdateApplication(submitApp)
   714  		})
   715  
   716  		When("the app successfully gets updated", func() {
   717  			var apiResponseApp resources.Application
   718  
   719  			BeforeEach(func() {
   720  				apiResponseApp = resources.Application{
   721  					GUID:                "response-app-guid",
   722  					StackName:           "response-stack-name",
   723  					Name:                "response-app-name",
   724  					LifecycleType:       constant.AppLifecycleTypeBuildpack,
   725  					LifecycleBuildpacks: []string{"response-buildpack-1", "response-buildpack-2"},
   726  				}
   727  				fakeCloudControllerClient.UpdateApplicationReturns(
   728  					apiResponseApp,
   729  					ccv3.Warnings{"some-warning"},
   730  					nil,
   731  				)
   732  			})
   733  
   734  			It("creates and returns the application and warnings", func() {
   735  				Expect(err).ToNot(HaveOccurred())
   736  				Expect(resultApp).To(Equal(resources.Application{
   737  					Name:                apiResponseApp.Name,
   738  					GUID:                apiResponseApp.GUID,
   739  					StackName:           apiResponseApp.StackName,
   740  					LifecycleType:       apiResponseApp.LifecycleType,
   741  					LifecycleBuildpacks: apiResponseApp.LifecycleBuildpacks,
   742  				}))
   743  				Expect(warnings).To(ConsistOf("some-warning"))
   744  
   745  				Expect(fakeCloudControllerClient.UpdateApplicationCallCount()).To(Equal(1))
   746  				Expect(fakeCloudControllerClient.UpdateApplicationArgsForCall(0)).To(Equal(resources.Application{
   747  					GUID:                submitApp.GUID,
   748  					StackName:           submitApp.StackName,
   749  					LifecycleType:       submitApp.LifecycleType,
   750  					LifecycleBuildpacks: submitApp.LifecycleBuildpacks,
   751  					Name:                submitApp.Name,
   752  					Metadata:            submitApp.Metadata,
   753  				}))
   754  			})
   755  		})
   756  
   757  		When("the cc client returns an error", func() {
   758  			var expectedError error
   759  
   760  			BeforeEach(func() {
   761  				expectedError = errors.New("I am a CloudControllerClient Error")
   762  				fakeCloudControllerClient.UpdateApplicationReturns(
   763  					resources.Application{},
   764  					ccv3.Warnings{"some-warning"},
   765  					expectedError,
   766  				)
   767  			})
   768  
   769  			It("raises the error and warnings", func() {
   770  				Expect(err).To(MatchError(expectedError))
   771  				Expect(warnings).To(ConsistOf("some-warning"))
   772  			})
   773  		})
   774  	})
   775  
   776  	Describe("UpdateApplicationName", func() {
   777  		var (
   778  			resultApp           resources.Application
   779  			newAppName, appGUID string
   780  			warnings            Warnings
   781  			err                 error
   782  		)
   783  
   784  		JustBeforeEach(func() {
   785  			newAppName = "some-new-app-name"
   786  			appGUID = "some-app-guid"
   787  
   788  			resultApp, warnings, err = actor.UpdateApplicationName(newAppName, appGUID)
   789  		})
   790  
   791  		When("the app successfully gets updated", func() {
   792  			var apiResponseApp resources.Application
   793  
   794  			BeforeEach(func() {
   795  				apiResponseApp = resources.Application{
   796  					GUID:                "response-app-guid",
   797  					StackName:           "response-stack-name",
   798  					Name:                "response-app-name",
   799  					LifecycleType:       constant.AppLifecycleTypeBuildpack,
   800  					LifecycleBuildpacks: []string{"response-buildpack-1", "response-buildpack-2"},
   801  				}
   802  				fakeCloudControllerClient.UpdateApplicationNameReturns(
   803  					apiResponseApp,
   804  					ccv3.Warnings{"some-warning"},
   805  					nil,
   806  				)
   807  			})
   808  
   809  			It("creates and returns the application and warnings", func() {
   810  				Expect(err).ToNot(HaveOccurred())
   811  				Expect(resultApp).To(Equal(resources.Application{
   812  					Name:                apiResponseApp.Name,
   813  					GUID:                apiResponseApp.GUID,
   814  					StackName:           apiResponseApp.StackName,
   815  					LifecycleType:       apiResponseApp.LifecycleType,
   816  					LifecycleBuildpacks: apiResponseApp.LifecycleBuildpacks,
   817  				}))
   818  				Expect(warnings).To(ConsistOf("some-warning"))
   819  
   820  				Expect(fakeCloudControllerClient.UpdateApplicationNameCallCount()).To(Equal(1))
   821  				appName, appGuid := fakeCloudControllerClient.UpdateApplicationNameArgsForCall(0)
   822  				Expect(appName).To(Equal("some-new-app-name"))
   823  				Expect(appGuid).To(Equal("some-app-guid"))
   824  			})
   825  		})
   826  
   827  		When("the cc client returns an error", func() {
   828  			var expectedError error
   829  
   830  			BeforeEach(func() {
   831  				expectedError = errors.New("I am a CloudControllerClient Error")
   832  				fakeCloudControllerClient.UpdateApplicationNameReturns(
   833  					resources.Application{},
   834  					ccv3.Warnings{"some-warning"},
   835  					expectedError,
   836  				)
   837  			})
   838  
   839  			It("raises the error and warnings", func() {
   840  				Expect(err).To(MatchError(expectedError))
   841  				Expect(warnings).To(ConsistOf("some-warning"))
   842  			})
   843  		})
   844  	})
   845  
   846  	Describe("PollStart", func() {
   847  		var (
   848  			app                   resources.Application
   849  			noWait                bool
   850  			handleInstanceDetails func(string)
   851  
   852  			done chan bool
   853  
   854  			warnings                Warnings
   855  			executeErr              error
   856  			reportedInstanceDetails []string
   857  		)
   858  
   859  		BeforeEach(func() {
   860  			done = make(chan bool)
   861  			fakeConfig.StartupTimeoutReturns(2 * time.Second)
   862  			fakeConfig.PollingIntervalReturns(1 * time.Second)
   863  			app = resources.Application{GUID: "some-guid"}
   864  			noWait = false
   865  
   866  			reportedInstanceDetails = []string{}
   867  			handleInstanceDetails = func(instanceDetails string) {
   868  				reportedInstanceDetails = append(reportedInstanceDetails, instanceDetails)
   869  			}
   870  		})
   871  
   872  		JustBeforeEach(func() {
   873  			go func() {
   874  				defer close(done)
   875  				warnings, executeErr = actor.PollStart(app, noWait, handleInstanceDetails)
   876  				done <- true
   877  			}()
   878  		})
   879  
   880  		It("gets the apps processes", func() {
   881  			// advanced clock so function exits
   882  			fakeClock.WaitForNWatchersAndIncrement(1*time.Millisecond, 2)
   883  
   884  			// wait for function to finish
   885  			Eventually(done).Should(Receive(BeTrue()))
   886  
   887  			Expect(fakeCloudControllerClient.GetApplicationProcessesCallCount()).To(Equal(1))
   888  			Expect(fakeCloudControllerClient.GetApplicationProcessesArgsForCall(0)).To(Equal("some-guid"))
   889  
   890  		})
   891  
   892  		When("getting the application processes fails", func() {
   893  			BeforeEach(func() {
   894  				fakeCloudControllerClient.GetApplicationProcessesReturns(nil, ccv3.Warnings{"get-app-warning-1", "get-app-warning-2"}, errors.New("some-error"))
   895  			})
   896  
   897  			It("returns the error and all warnings", func() {
   898  				// wait for function to finish
   899  				Eventually(done).Should(Receive(BeTrue()))
   900  
   901  				Expect(executeErr).To(MatchError(errors.New("some-error")))
   902  				Expect(warnings).To(ConsistOf("get-app-warning-1", "get-app-warning-2"))
   903  			})
   904  		})
   905  
   906  		When("getting the application process succeeds", func() {
   907  			BeforeEach(func() {
   908  				fakeCloudControllerClient.GetApplicationProcessesReturns(
   909  					[]resources.Process{
   910  						{GUID: "process1", Type: "web"},
   911  					},
   912  					ccv3.Warnings{"get-app-warning-1"},
   913  					nil,
   914  				)
   915  
   916  			})
   917  
   918  			It("gets the startup timeout", func() {
   919  				// advanced clock so function exits
   920  				fakeClock.WaitForNWatchersAndIncrement(1*time.Millisecond, 2)
   921  
   922  				// wait for function to finish
   923  				Eventually(done).Should(Receive(BeTrue()))
   924  
   925  				Expect(fakeConfig.StartupTimeoutCallCount()).To(Equal(1))
   926  			})
   927  
   928  			When("the no-wait flag is provided", func() {
   929  				BeforeEach(func() {
   930  					noWait = true
   931  					fakeCloudControllerClient.GetApplicationProcessesReturns(
   932  						[]resources.Process{
   933  							{GUID: "process1", Type: "web"},
   934  							{GUID: "process2", Type: "worker"},
   935  						},
   936  						ccv3.Warnings{"get-app-warning-1"},
   937  						nil,
   938  					)
   939  				})
   940  
   941  				It("filters out the non web processes", func() {
   942  					// send something on the timer channel
   943  					fakeClock.WaitForNWatchersAndIncrement(1*time.Millisecond, 2)
   944  
   945  					// Wait for function to finish
   946  					Eventually(done).Should(Receive(BeTrue()))
   947  
   948  					// assert on the cc call made within poll processes to make sure there is only the web process
   949  					Expect(fakeCloudControllerClient.GetProcessInstancesCallCount()).To(Equal(1))
   950  					Expect(fakeCloudControllerClient.GetProcessInstancesArgsForCall(0)).To(Equal("process1"))
   951  
   952  				})
   953  			})
   954  
   955  			When("polling processes returns an error", func() {
   956  				BeforeEach(func() {
   957  					fakeCloudControllerClient.GetProcessInstancesReturns(nil, ccv3.Warnings{"poll-process-warning"}, errors.New("poll-process-error"))
   958  				})
   959  
   960  				It("returns the error and warnings", func() {
   961  					// send something on the timer channel
   962  					fakeClock.WaitForNWatchersAndIncrement(1*time.Millisecond, 2)
   963  
   964  					// Wait for function to finish
   965  					Eventually(done).Should(Receive(BeTrue()))
   966  
   967  					Expect(executeErr).Should(MatchError("poll-process-error"))
   968  					Expect(warnings).Should(ConsistOf("poll-process-warning", "get-app-warning-1"))
   969  				})
   970  			})
   971  
   972  			When("polling start times out", func() {
   973  				BeforeEach(func() {
   974  					fakeCloudControllerClient.GetProcessInstancesReturns(
   975  						[]ccv3.ProcessInstance{
   976  							{State: constant.ProcessInstanceStarting},
   977  						},
   978  						ccv3.Warnings{"poll-process-warning"},
   979  						nil,
   980  					)
   981  
   982  					fakeConfig.StartupTimeoutReturns(2 * time.Millisecond)
   983  				})
   984  
   985  				It("returns a timeout error and any warnings", func() {
   986  					// send something on the timer channel for first tick
   987  					fakeClock.WaitForNWatchersAndIncrement(1*time.Millisecond, 2)
   988  
   989  					fakeClock.Increment(1 * time.Millisecond)
   990  
   991  					// Wait for function to finish
   992  					Eventually(done).Should(Receive(BeTrue()))
   993  
   994  					Expect(executeErr).To(MatchError(actionerror.StartupTimeoutError{}))
   995  					Expect(warnings).To(ConsistOf("poll-process-warning", "get-app-warning-1"))
   996  				})
   997  			})
   998  
   999  			When("polling process eventually returns we should stop polling", func() {
  1000  				BeforeEach(func() {
  1001  					fakeCloudControllerClient.GetProcessInstancesReturnsOnCall(0,
  1002  						[]ccv3.ProcessInstance{
  1003  							{State: constant.ProcessInstanceStarting},
  1004  						},
  1005  						ccv3.Warnings{"poll-process-warning1"},
  1006  						nil,
  1007  					)
  1008  
  1009  					fakeCloudControllerClient.GetProcessInstancesReturnsOnCall(1,
  1010  						[]ccv3.ProcessInstance{
  1011  							{State: constant.ProcessInstanceRunning},
  1012  						},
  1013  						ccv3.Warnings{"poll-process-warning2"},
  1014  						nil,
  1015  					)
  1016  				})
  1017  
  1018  				It("returns success and any warnings", func() {
  1019  					// send something on the timer channel
  1020  					fakeClock.WaitForNWatchersAndIncrement(1*time.Millisecond, 2)
  1021  
  1022  					Eventually(fakeConfig.PollingIntervalCallCount).Should(Equal(1))
  1023  
  1024  					fakeClock.Increment(1 * time.Second)
  1025  
  1026  					// Wait for function to finish
  1027  					Eventually(done).Should(Receive(BeTrue()))
  1028  					Expect(executeErr).NotTo(HaveOccurred())
  1029  					Expect(warnings).To(ConsistOf("poll-process-warning1", "get-app-warning-1", "poll-process-warning2"))
  1030  				})
  1031  
  1032  			})
  1033  		})
  1034  	})
  1035  
  1036  	Describe("PollStartForRolling", func() {
  1037  		var (
  1038  			app                   resources.Application
  1039  			deploymentGUID        string
  1040  			noWait                bool
  1041  			handleInstanceDetails func(string)
  1042  
  1043  			done chan bool
  1044  
  1045  			warnings                Warnings
  1046  			executeErr              error
  1047  			reportedInstanceDetails []string
  1048  		)
  1049  
  1050  		BeforeEach(func() {
  1051  			reportedInstanceDetails = []string{}
  1052  			handleInstanceDetails = func(instanceDetails string) {
  1053  				reportedInstanceDetails = append(reportedInstanceDetails, instanceDetails)
  1054  			}
  1055  
  1056  			app = resources.Application{GUID: "some-rolling-app-guid"}
  1057  			deploymentGUID = "some-deployment-guid"
  1058  			noWait = false
  1059  
  1060  			done = make(chan bool)
  1061  
  1062  			fakeConfig.StartupTimeoutReturns(5 * time.Second)
  1063  			fakeConfig.PollingIntervalReturns(1 * time.Second)
  1064  		})
  1065  
  1066  		JustBeforeEach(func() {
  1067  			go func() {
  1068  				warnings, executeErr = actor.PollStartForRolling(app, deploymentGUID, noWait, handleInstanceDetails)
  1069  				done <- true
  1070  			}()
  1071  		})
  1072  
  1073  		When("There is a non-timeout failure in the loop", func() {
  1074  			// this may need to be expanded to also include when the deployment is superseded or cancelled
  1075  			When("getting the deployment fails", func() {
  1076  				When("it is because the deployment was cancelled", func() {
  1077  					BeforeEach(func() {
  1078  						fakeCloudControllerClient.GetDeploymentReturns(
  1079  							resources.Deployment{
  1080  								StatusValue:  constant.DeploymentStatusValueFinalized,
  1081  								StatusReason: constant.DeploymentStatusReasonCanceled,
  1082  							},
  1083  							ccv3.Warnings{"get-deployment-warning"},
  1084  							nil,
  1085  						)
  1086  					})
  1087  
  1088  					It("returns warnings and the error", func() {
  1089  						// initial tick
  1090  						fakeClock.WaitForNWatchersAndIncrement(1*time.Millisecond, 2)
  1091  
  1092  						// wait for func to finish
  1093  						Eventually(done).Should(Receive(BeTrue()))
  1094  
  1095  						Expect(executeErr).To(MatchError("Deployment has been canceled"))
  1096  						Expect(warnings).To(ConsistOf("get-deployment-warning"))
  1097  
  1098  						Expect(fakeCloudControllerClient.GetDeploymentCallCount()).To(Equal(1))
  1099  						Expect(fakeCloudControllerClient.GetDeploymentArgsForCall(0)).To(Equal(deploymentGUID))
  1100  
  1101  						Expect(fakeCloudControllerClient.GetApplicationProcessesCallCount()).To(Equal(0))
  1102  						Expect(fakeCloudControllerClient.GetProcessInstancesCallCount()).To(Equal(0))
  1103  
  1104  						Expect(fakeConfig.StartupTimeoutCallCount()).To(Equal(1))
  1105  					})
  1106  
  1107  				})
  1108  
  1109  				When("it is because the deployment was superseded", func() {
  1110  					BeforeEach(func() {
  1111  						fakeCloudControllerClient.GetDeploymentReturns(
  1112  							resources.Deployment{
  1113  								StatusValue:  constant.DeploymentStatusValueFinalized,
  1114  								StatusReason: constant.DeploymentStatusReasonSuperseded,
  1115  							},
  1116  							ccv3.Warnings{"get-deployment-warning"},
  1117  							nil,
  1118  						)
  1119  					})
  1120  
  1121  					It("returns warnings and the error", func() {
  1122  						// initial tick
  1123  						fakeClock.WaitForNWatchersAndIncrement(1*time.Millisecond, 2)
  1124  
  1125  						// wait for func to finish
  1126  						Eventually(done).Should(Receive(BeTrue()))
  1127  
  1128  						Expect(executeErr).To(MatchError("Deployment has been superseded"))
  1129  						Expect(warnings).To(ConsistOf("get-deployment-warning"))
  1130  
  1131  						Expect(fakeCloudControllerClient.GetDeploymentCallCount()).To(Equal(1))
  1132  						Expect(fakeCloudControllerClient.GetDeploymentArgsForCall(0)).To(Equal(deploymentGUID))
  1133  
  1134  						Expect(fakeCloudControllerClient.GetApplicationProcessesCallCount()).To(Equal(0))
  1135  						Expect(fakeCloudControllerClient.GetProcessInstancesCallCount()).To(Equal(0))
  1136  
  1137  						Expect(fakeConfig.StartupTimeoutCallCount()).To(Equal(1))
  1138  					})
  1139  
  1140  				})
  1141  
  1142  				When("it is because of an API error", func() {
  1143  					BeforeEach(func() {
  1144  						fakeCloudControllerClient.GetDeploymentReturns(
  1145  							resources.Deployment{},
  1146  							ccv3.Warnings{"get-deployment-warning"},
  1147  							errors.New("get-deployment-error"),
  1148  						)
  1149  					})
  1150  
  1151  					It("returns warnings and the error", func() {
  1152  						// initial tick
  1153  						fakeClock.WaitForNWatchersAndIncrement(1*time.Millisecond, 2)
  1154  
  1155  						// wait for func to finish
  1156  						Eventually(done).Should(Receive(BeTrue()))
  1157  
  1158  						Expect(executeErr).To(MatchError("get-deployment-error"))
  1159  						Expect(warnings).To(ConsistOf("get-deployment-warning"))
  1160  
  1161  						Expect(fakeCloudControllerClient.GetDeploymentCallCount()).To(Equal(1))
  1162  						Expect(fakeCloudControllerClient.GetDeploymentArgsForCall(0)).To(Equal(deploymentGUID))
  1163  
  1164  						Expect(fakeCloudControllerClient.GetApplicationProcessesCallCount()).To(Equal(0))
  1165  						Expect(fakeCloudControllerClient.GetProcessInstancesCallCount()).To(Equal(0))
  1166  
  1167  						Expect(fakeConfig.StartupTimeoutCallCount()).To(Equal(1))
  1168  					})
  1169  
  1170  				})
  1171  			})
  1172  
  1173  			When("getting the deployment succeeds", func() {
  1174  				BeforeEach(func() {
  1175  					// get processes requires the deployment to be deployed so we need this to indirectly test the error case
  1176  					fakeCloudControllerClient.GetDeploymentReturns(
  1177  						resources.Deployment{StatusValue: constant.DeploymentStatusValueFinalized, StatusReason: constant.DeploymentStatusReasonDeployed},
  1178  						ccv3.Warnings{"get-deployment-warning"},
  1179  						nil,
  1180  					)
  1181  
  1182  				})
  1183  
  1184  				When("getting the processes fails", func() {
  1185  					BeforeEach(func() {
  1186  						fakeCloudControllerClient.GetApplicationProcessesReturns(
  1187  							[]resources.Process{},
  1188  							ccv3.Warnings{"get-processes-warning"},
  1189  							errors.New("get-processes-error"),
  1190  						)
  1191  					})
  1192  
  1193  					It("returns warnings and the error", func() {
  1194  						// initial tick
  1195  						fakeClock.WaitForNWatchersAndIncrement(1*time.Millisecond, 2)
  1196  
  1197  						// wait for func to finish
  1198  						Eventually(done).Should(Receive(BeTrue()))
  1199  
  1200  						Expect(executeErr).To(MatchError("get-processes-error"))
  1201  						Expect(warnings).To(ConsistOf("get-deployment-warning", "get-processes-warning"))
  1202  
  1203  						Expect(fakeCloudControllerClient.GetDeploymentCallCount()).To(Equal(1))
  1204  						Expect(fakeCloudControllerClient.GetDeploymentArgsForCall(0)).To(Equal(deploymentGUID))
  1205  
  1206  						Expect(fakeCloudControllerClient.GetApplicationProcessesCallCount()).To(Equal(1))
  1207  						Expect(fakeCloudControllerClient.GetApplicationProcessesArgsForCall(0)).To(Equal(app.GUID))
  1208  
  1209  						Expect(fakeCloudControllerClient.GetProcessInstancesCallCount()).To(Equal(0))
  1210  
  1211  					})
  1212  				})
  1213  
  1214  				When("getting the processes succeeds", func() {
  1215  					BeforeEach(func() {
  1216  						fakeCloudControllerClient.GetApplicationProcessesReturns(
  1217  							[]resources.Process{{GUID: "process-guid"}},
  1218  							ccv3.Warnings{"get-processes-warning"},
  1219  							nil,
  1220  						)
  1221  					})
  1222  
  1223  					When("polling the processes fails", func() {
  1224  						BeforeEach(func() {
  1225  							fakeCloudControllerClient.GetProcessInstancesReturns(
  1226  								[]ccv3.ProcessInstance{},
  1227  								ccv3.Warnings{"poll-processes-warning"},
  1228  								errors.New("poll-processes-error"),
  1229  							)
  1230  						})
  1231  
  1232  						It("returns all warnings and errors", func() {
  1233  							// initial tick
  1234  							fakeClock.WaitForNWatchersAndIncrement(1*time.Millisecond, 2)
  1235  
  1236  							// wait for func to finish
  1237  							Eventually(done).Should(Receive(BeTrue()))
  1238  
  1239  							Expect(executeErr).To(MatchError("poll-processes-error"))
  1240  							Expect(warnings).To(ConsistOf("get-deployment-warning", "get-processes-warning", "poll-processes-warning"))
  1241  
  1242  							Expect(fakeCloudControllerClient.GetDeploymentCallCount()).To(Equal(1))
  1243  							Expect(fakeCloudControllerClient.GetDeploymentArgsForCall(0)).To(Equal(deploymentGUID))
  1244  
  1245  							Expect(fakeCloudControllerClient.GetApplicationProcessesCallCount()).To(Equal(1))
  1246  							Expect(fakeCloudControllerClient.GetApplicationProcessesArgsForCall(0)).To(Equal(app.GUID))
  1247  
  1248  							Expect(fakeCloudControllerClient.GetProcessInstancesCallCount()).To(Equal(1))
  1249  							Expect(fakeCloudControllerClient.GetProcessInstancesArgsForCall(0)).To(Equal("process-guid"))
  1250  						})
  1251  
  1252  					})
  1253  				})
  1254  
  1255  			})
  1256  
  1257  		})
  1258  
  1259  		// intentionally ignore the no-wait flag here for simplicity. One of these two things must cause timeout regardless of no-wait state
  1260  		When("there is a timeout error", func() {
  1261  			BeforeEach(func() {
  1262  				// 1 millisecond for initial tick then 1 to trigger timeout
  1263  				fakeConfig.StartupTimeoutReturns(2 * time.Millisecond)
  1264  			})
  1265  
  1266  			When("the deployment never deploys", func() {
  1267  				BeforeEach(func() {
  1268  					fakeCloudControllerClient.GetDeploymentReturns(
  1269  						resources.Deployment{StatusValue: constant.DeploymentStatusValueActive},
  1270  						ccv3.Warnings{"get-deployment-warning"},
  1271  						nil,
  1272  					)
  1273  					fakeCloudControllerClient.CancelDeploymentReturns(
  1274  						ccv3.Warnings{"cancel-deployment-warning"},
  1275  						nil,
  1276  					)
  1277  				})
  1278  
  1279  				It("returns a timeout error and any warnings and cancels the deployment", func() {
  1280  					// initial tick
  1281  					fakeClock.WaitForNWatchersAndIncrement(1*time.Millisecond, 2)
  1282  
  1283  					Eventually(fakeCloudControllerClient.GetDeploymentCallCount).Should(Equal(1))
  1284  
  1285  					// timeout tick
  1286  					fakeClock.Increment(1 * time.Millisecond)
  1287  
  1288  					Eventually(fakeCloudControllerClient.CancelDeploymentCallCount).Should(Equal(1))
  1289  
  1290  					// wait for func to finish
  1291  					Eventually(done).Should(Receive(BeTrue()))
  1292  
  1293  					Expect(executeErr).To(MatchError(actionerror.StartupTimeoutError{}))
  1294  					Expect(warnings).To(ConsistOf("get-deployment-warning", "cancel-deployment-warning"))
  1295  				})
  1296  
  1297  				When("the cancel deployment fails", func() {
  1298  					BeforeEach(func() {
  1299  						fakeCloudControllerClient.CancelDeploymentReturns(
  1300  							ccv3.Warnings{"cancel-deployment-warning"},
  1301  							errors.New("cancel-deployment-error"),
  1302  						)
  1303  					})
  1304  
  1305  					It("returns a timeout error and any warnings and cancels the deployment", func() {
  1306  						// initial tick
  1307  						fakeClock.WaitForNWatchersAndIncrement(1*time.Millisecond, 2)
  1308  
  1309  						Eventually(fakeCloudControllerClient.GetDeploymentCallCount).Should(Equal(1))
  1310  
  1311  						// timeout tick
  1312  						fakeClock.Increment(1 * time.Millisecond)
  1313  
  1314  						Eventually(fakeCloudControllerClient.CancelDeploymentCallCount).Should(Equal(1))
  1315  
  1316  						// wait for func to finish
  1317  						Eventually(done).Should(Receive(BeTrue()))
  1318  
  1319  						Expect(executeErr).To(MatchError("cancel-deployment-error"))
  1320  						Expect(warnings).To(ConsistOf("get-deployment-warning", "cancel-deployment-warning"))
  1321  					})
  1322  
  1323  				})
  1324  
  1325  			})
  1326  
  1327  			When("the processes dont become healthy", func() {
  1328  				BeforeEach(func() {
  1329  					fakeCloudControllerClient.GetDeploymentReturns(
  1330  						resources.Deployment{StatusValue: constant.DeploymentStatusValueFinalized, StatusReason: constant.DeploymentStatusReasonDeployed},
  1331  						ccv3.Warnings{"get-deployment-warning"},
  1332  						nil,
  1333  					)
  1334  
  1335  					fakeCloudControllerClient.GetApplicationProcessesReturns(
  1336  						[]resources.Process{{GUID: "process-guid"}},
  1337  						ccv3.Warnings{"get-processes-warning"},
  1338  						nil,
  1339  					)
  1340  
  1341  					fakeCloudControllerClient.GetProcessInstancesReturns(
  1342  						[]ccv3.ProcessInstance{{State: constant.ProcessInstanceStarting}},
  1343  						ccv3.Warnings{"poll-processes-warning"},
  1344  						nil,
  1345  					)
  1346  				})
  1347  
  1348  				It("returns a timeout error and any warnings", func() {
  1349  					// initial tick
  1350  					fakeClock.WaitForNWatchersAndIncrement(1*time.Millisecond, 2)
  1351  
  1352  					Eventually(fakeCloudControllerClient.GetDeploymentCallCount).Should(Equal(1))
  1353  					Eventually(fakeCloudControllerClient.GetApplicationProcessesCallCount).Should(Equal(1))
  1354  					Eventually(fakeCloudControllerClient.GetProcessInstancesCallCount).Should(Equal(1))
  1355  
  1356  					// timeout tick
  1357  					fakeClock.Increment(1 * time.Millisecond)
  1358  
  1359  					// wait for func to finish
  1360  					Eventually(done).Should(Receive(BeTrue()))
  1361  
  1362  					Expect(executeErr).To(MatchError(actionerror.StartupTimeoutError{}))
  1363  					Expect(warnings).To(ConsistOf("get-deployment-warning", "get-processes-warning", "poll-processes-warning"))
  1364  				})
  1365  
  1366  			})
  1367  		})
  1368  
  1369  		When("things eventually become healthy", func() {
  1370  			When("the no wait flag is given", func() {
  1371  				BeforeEach(func() {
  1372  					// in total three loops 1: deployment still deploying 2: deployment deployed processes starting 3: processes started
  1373  					noWait = true
  1374  
  1375  					// Always return deploying as a way to check we respect no wait
  1376  					fakeCloudControllerClient.GetDeploymentReturns(
  1377  						resources.Deployment{
  1378  							StatusValue:  constant.DeploymentStatusValueActive,
  1379  							NewProcesses: []resources.Process{{GUID: "new-deployment-process"}},
  1380  						},
  1381  						ccv3.Warnings{"get-deployment-warning"},
  1382  						nil,
  1383  					)
  1384  
  1385  					// We only poll the processes. Two loops for fun
  1386  					fakeCloudControllerClient.GetProcessInstancesReturnsOnCall(0,
  1387  						[]ccv3.ProcessInstance{{State: constant.ProcessInstanceStarting}},
  1388  						ccv3.Warnings{"poll-processes-warning-1"},
  1389  						nil,
  1390  					)
  1391  
  1392  					fakeCloudControllerClient.GetProcessInstancesReturnsOnCall(1,
  1393  						[]ccv3.ProcessInstance{{State: constant.ProcessInstanceRunning}},
  1394  						ccv3.Warnings{"poll-processes-warning-2"},
  1395  						nil,
  1396  					)
  1397  				})
  1398  
  1399  				It("polls the start of the application correctly and returns warnings and no error", func() {
  1400  					// Initial tick
  1401  					fakeClock.WaitForNWatchersAndIncrement(1*time.Millisecond, 2)
  1402  
  1403  					// assert one of our watcher is the timeout
  1404  					Expect(fakeConfig.StartupTimeoutCallCount()).To(Equal(1))
  1405  
  1406  					// the first time through we always get the deployment regardless of no-wait
  1407  					Eventually(fakeCloudControllerClient.GetDeploymentCallCount).Should(Equal(1))
  1408  					Expect(fakeCloudControllerClient.GetDeploymentArgsForCall(0)).To(Equal(deploymentGUID))
  1409  					Eventually(fakeCloudControllerClient.GetProcessInstancesCallCount).Should(Equal(1))
  1410  					Expect(fakeCloudControllerClient.GetProcessInstancesArgsForCall(0)).To(Equal("new-deployment-process"))
  1411  					Eventually(fakeConfig.PollingIntervalCallCount).Should(Equal(1))
  1412  
  1413  					fakeClock.Increment(1 * time.Second)
  1414  
  1415  					Eventually(fakeCloudControllerClient.GetDeploymentCallCount).Should(Equal(2))
  1416  					Expect(fakeCloudControllerClient.GetDeploymentArgsForCall(0)).To(Equal(deploymentGUID))
  1417  					Eventually(fakeCloudControllerClient.GetProcessInstancesCallCount).Should(Equal(2))
  1418  					Expect(fakeCloudControllerClient.GetProcessInstancesArgsForCall(0)).To(Equal("new-deployment-process"))
  1419  
  1420  					Eventually(done).Should(Receive(BeTrue()))
  1421  
  1422  					Expect(executeErr).NotTo(HaveOccurred())
  1423  					Expect(warnings).To(ConsistOf(
  1424  						"get-deployment-warning",
  1425  						"poll-processes-warning-1",
  1426  						"get-deployment-warning",
  1427  						"poll-processes-warning-2",
  1428  					))
  1429  
  1430  					Expect(fakeCloudControllerClient.GetDeploymentCallCount()).To(Equal(2))
  1431  					Expect(fakeCloudControllerClient.GetApplicationProcessesCallCount()).To(Equal(0))
  1432  					Expect(fakeCloudControllerClient.GetProcessInstancesCallCount()).To(Equal(2))
  1433  					Expect(fakeConfig.PollingIntervalCallCount()).To(Equal(1))
  1434  
  1435  				})
  1436  
  1437  			})
  1438  
  1439  			When("the no wait flag is not given", func() {
  1440  				BeforeEach(func() {
  1441  					// in total three loops 1: deployment still deploying 2: deployment deployed processes starting 3: processes started
  1442  					fakeCloudControllerClient.GetDeploymentReturnsOnCall(0,
  1443  						resources.Deployment{StatusValue: constant.DeploymentStatusValueActive},
  1444  						ccv3.Warnings{"get-deployment-warning-1"},
  1445  						nil,
  1446  					)
  1447  
  1448  					// Poll the deployment twice to make sure we are polling (one in the above before each)
  1449  					fakeCloudControllerClient.GetDeploymentReturnsOnCall(1,
  1450  						resources.Deployment{StatusValue: constant.DeploymentStatusValueFinalized, StatusReason: constant.DeploymentStatusReasonDeployed},
  1451  						ccv3.Warnings{"get-deployment-warning-2"},
  1452  						nil,
  1453  					)
  1454  
  1455  					// then we get the processes. This should only be called once
  1456  					fakeCloudControllerClient.GetApplicationProcessesReturns(
  1457  						[]resources.Process{{GUID: "process-guid"}},
  1458  						ccv3.Warnings{"get-processes-warning"},
  1459  						nil,
  1460  					)
  1461  
  1462  					// then we poll the processes. Two loops for fun
  1463  					fakeCloudControllerClient.GetProcessInstancesReturnsOnCall(0,
  1464  						[]ccv3.ProcessInstance{{State: constant.ProcessInstanceStarting}},
  1465  						ccv3.Warnings{"poll-processes-warning-1"},
  1466  						nil,
  1467  					)
  1468  
  1469  					fakeCloudControllerClient.GetProcessInstancesReturnsOnCall(1,
  1470  						[]ccv3.ProcessInstance{{State: constant.ProcessInstanceRunning}},
  1471  						ccv3.Warnings{"poll-processes-warning-2"},
  1472  						nil,
  1473  					)
  1474  				})
  1475  
  1476  				It("polls the start of the application correctly and returns warnings and no error", func() {
  1477  					// Initial tick
  1478  					fakeClock.WaitForNWatchersAndIncrement(1*time.Millisecond, 2)
  1479  
  1480  					// assert one of our watchers is for the timeout
  1481  					Expect(fakeConfig.StartupTimeoutCallCount()).To(Equal(1))
  1482  
  1483  					Eventually(fakeCloudControllerClient.GetDeploymentCallCount).Should(Equal(1))
  1484  					Expect(fakeCloudControllerClient.GetDeploymentArgsForCall(0)).To(Equal(deploymentGUID))
  1485  					Eventually(fakeConfig.PollingIntervalCallCount).Should(Equal(1))
  1486  
  1487  					// start the second loop where the deployment is deployed so we poll processes
  1488  					fakeClock.Increment(1 * time.Second)
  1489  
  1490  					Eventually(fakeCloudControllerClient.GetDeploymentCallCount).Should(Equal(2))
  1491  					Expect(fakeCloudControllerClient.GetDeploymentArgsForCall(1)).To(Equal(deploymentGUID))
  1492  					Eventually(fakeCloudControllerClient.GetApplicationProcessesCallCount).Should(Equal(1))
  1493  					Expect(fakeCloudControllerClient.GetApplicationProcessesArgsForCall(0)).To(Equal(app.GUID))
  1494  					Eventually(fakeCloudControllerClient.GetProcessInstancesCallCount).Should(Equal(1))
  1495  					Expect(fakeCloudControllerClient.GetProcessInstancesArgsForCall(0)).To(Equal("process-guid"))
  1496  					Eventually(fakeConfig.PollingIntervalCallCount).Should(Equal(2))
  1497  
  1498  					fakeClock.Increment(1 * time.Second)
  1499  
  1500  					// we should stop polling because it is deployed
  1501  					Eventually(fakeCloudControllerClient.GetProcessInstancesCallCount).Should(Equal(2))
  1502  					Expect(fakeCloudControllerClient.GetProcessInstancesArgsForCall(0)).To(Equal("process-guid"))
  1503  
  1504  					Eventually(done).Should(Receive(BeTrue()))
  1505  
  1506  					Expect(executeErr).NotTo(HaveOccurred())
  1507  					Expect(warnings).To(ConsistOf(
  1508  						"get-deployment-warning-1",
  1509  						"get-deployment-warning-2",
  1510  						"get-processes-warning",
  1511  						"poll-processes-warning-1",
  1512  						"poll-processes-warning-2",
  1513  					))
  1514  
  1515  					Expect(fakeCloudControllerClient.GetDeploymentCallCount()).To(Equal(2))
  1516  					Expect(fakeCloudControllerClient.GetApplicationProcessesCallCount()).To(Equal(1))
  1517  					Expect(fakeCloudControllerClient.GetProcessInstancesCallCount()).To(Equal(2))
  1518  					Expect(fakeConfig.PollingIntervalCallCount()).To(Equal(2))
  1519  
  1520  				})
  1521  
  1522  			})
  1523  
  1524  		})
  1525  	})
  1526  
  1527  	Describe("SetApplicationProcessHealthCheckTypeByNameAndSpace", func() {
  1528  		var (
  1529  			healthCheckType     constant.HealthCheckType
  1530  			healthCheckEndpoint string
  1531  
  1532  			warnings Warnings
  1533  			err      error
  1534  			app      resources.Application
  1535  		)
  1536  
  1537  		BeforeEach(func() {
  1538  			healthCheckType = constant.HTTP
  1539  			healthCheckEndpoint = "some-http-endpoint"
  1540  		})
  1541  
  1542  		JustBeforeEach(func() {
  1543  			app, warnings, err = actor.SetApplicationProcessHealthCheckTypeByNameAndSpace(
  1544  				"some-app-name",
  1545  				"some-space-guid",
  1546  				healthCheckType,
  1547  				healthCheckEndpoint,
  1548  				"some-process-type",
  1549  				42,
  1550  			)
  1551  		})
  1552  
  1553  		When("getting application returns an error", func() {
  1554  			var expectedErr error
  1555  
  1556  			BeforeEach(func() {
  1557  				expectedErr = errors.New("some-error")
  1558  				fakeCloudControllerClient.GetApplicationsReturns(
  1559  					[]resources.Application{},
  1560  					ccv3.Warnings{"some-warning"},
  1561  					expectedErr,
  1562  				)
  1563  			})
  1564  
  1565  			It("returns the error and warnings", func() {
  1566  				Expect(err).To(Equal(expectedErr))
  1567  				Expect(warnings).To(ConsistOf("some-warning"))
  1568  			})
  1569  		})
  1570  
  1571  		When("application exists", func() {
  1572  			var ccv3App resources.Application
  1573  
  1574  			BeforeEach(func() {
  1575  				ccv3App = resources.Application{
  1576  					GUID: "some-app-guid",
  1577  				}
  1578  
  1579  				fakeCloudControllerClient.GetApplicationsReturns(
  1580  					[]resources.Application{ccv3App},
  1581  					ccv3.Warnings{"some-warning"},
  1582  					nil,
  1583  				)
  1584  			})
  1585  
  1586  			When("setting the health check returns an error", func() {
  1587  				var expectedErr error
  1588  
  1589  				BeforeEach(func() {
  1590  					expectedErr = errors.New("some-error")
  1591  					fakeCloudControllerClient.GetApplicationProcessByTypeReturns(
  1592  						resources.Process{},
  1593  						ccv3.Warnings{"some-process-warning"},
  1594  						expectedErr,
  1595  					)
  1596  				})
  1597  
  1598  				It("returns the error and warnings", func() {
  1599  					Expect(err).To(Equal(expectedErr))
  1600  					Expect(warnings).To(ConsistOf("some-warning", "some-process-warning"))
  1601  				})
  1602  			})
  1603  
  1604  			When("application process exists", func() {
  1605  				BeforeEach(func() {
  1606  					fakeCloudControllerClient.GetApplicationProcessByTypeReturns(
  1607  						resources.Process{GUID: "some-process-guid"},
  1608  						ccv3.Warnings{"some-process-warning"},
  1609  						nil,
  1610  					)
  1611  
  1612  					fakeCloudControllerClient.UpdateProcessReturns(
  1613  						resources.Process{GUID: "some-process-guid"},
  1614  						ccv3.Warnings{"some-health-check-warning"},
  1615  						nil,
  1616  					)
  1617  				})
  1618  
  1619  				It("returns the application", func() {
  1620  					Expect(err).NotTo(HaveOccurred())
  1621  					Expect(warnings).To(ConsistOf("some-warning", "some-process-warning", "some-health-check-warning"))
  1622  
  1623  					Expect(app).To(Equal(resources.Application{
  1624  						GUID: ccv3App.GUID,
  1625  					}))
  1626  
  1627  					Expect(fakeCloudControllerClient.GetApplicationProcessByTypeCallCount()).To(Equal(1))
  1628  					appGUID, processType := fakeCloudControllerClient.GetApplicationProcessByTypeArgsForCall(0)
  1629  					Expect(appGUID).To(Equal("some-app-guid"))
  1630  					Expect(processType).To(Equal("some-process-type"))
  1631  
  1632  					Expect(fakeCloudControllerClient.UpdateProcessCallCount()).To(Equal(1))
  1633  					process := fakeCloudControllerClient.UpdateProcessArgsForCall(0)
  1634  					Expect(process.GUID).To(Equal("some-process-guid"))
  1635  					Expect(process.HealthCheckType).To(Equal(constant.HTTP))
  1636  					Expect(process.HealthCheckEndpoint).To(Equal("some-http-endpoint"))
  1637  					Expect(process.HealthCheckInvocationTimeout).To(BeEquivalentTo(42))
  1638  				})
  1639  			})
  1640  		})
  1641  	})
  1642  
  1643  	Describe("StopApplication", func() {
  1644  		var (
  1645  			warnings   Warnings
  1646  			executeErr error
  1647  		)
  1648  
  1649  		JustBeforeEach(func() {
  1650  			warnings, executeErr = actor.StopApplication("some-app-guid")
  1651  		})
  1652  
  1653  		When("there are no client errors", func() {
  1654  			BeforeEach(func() {
  1655  				fakeCloudControllerClient.UpdateApplicationStopReturns(
  1656  					resources.Application{GUID: "some-app-guid"},
  1657  					ccv3.Warnings{"stop-application-warning"},
  1658  					nil,
  1659  				)
  1660  			})
  1661  
  1662  			It("stops the application", func() {
  1663  				Expect(executeErr).ToNot(HaveOccurred())
  1664  				Expect(warnings).To(ConsistOf("stop-application-warning"))
  1665  
  1666  				Expect(fakeCloudControllerClient.UpdateApplicationStopCallCount()).To(Equal(1))
  1667  				Expect(fakeCloudControllerClient.UpdateApplicationStopArgsForCall(0)).To(Equal("some-app-guid"))
  1668  			})
  1669  		})
  1670  
  1671  		When("stopping the application fails", func() {
  1672  			var expectedErr error
  1673  			BeforeEach(func() {
  1674  				expectedErr = errors.New("some set stop-application error")
  1675  				fakeCloudControllerClient.UpdateApplicationStopReturns(
  1676  					resources.Application{},
  1677  					ccv3.Warnings{"stop-application-warning"},
  1678  					expectedErr,
  1679  				)
  1680  			})
  1681  
  1682  			It("returns the error", func() {
  1683  				Expect(executeErr).To(Equal(expectedErr))
  1684  				Expect(warnings).To(ConsistOf("stop-application-warning"))
  1685  			})
  1686  		})
  1687  	})
  1688  
  1689  	Describe("StartApplication", func() {
  1690  		var (
  1691  			warnings   Warnings
  1692  			executeErr error
  1693  		)
  1694  
  1695  		BeforeEach(func() {
  1696  			fakeConfig.StartupTimeoutReturns(time.Second)
  1697  			fakeConfig.PollingIntervalReturns(0)
  1698  		})
  1699  
  1700  		JustBeforeEach(func() {
  1701  			warnings, executeErr = actor.StartApplication("some-app-guid")
  1702  		})
  1703  
  1704  		When("there are no client errors", func() {
  1705  			BeforeEach(func() {
  1706  				fakeCloudControllerClient.UpdateApplicationStartReturns(
  1707  					resources.Application{GUID: "some-app-guid"},
  1708  					ccv3.Warnings{"start-application-warning"},
  1709  					nil,
  1710  				)
  1711  			})
  1712  
  1713  			It("starts the application", func() {
  1714  				Expect(executeErr).ToNot(HaveOccurred())
  1715  				Expect(warnings).To(ConsistOf("start-application-warning"))
  1716  
  1717  				Expect(fakeCloudControllerClient.UpdateApplicationStartCallCount()).To(Equal(1))
  1718  				Expect(fakeCloudControllerClient.UpdateApplicationStartArgsForCall(0)).To(Equal("some-app-guid"))
  1719  			})
  1720  		})
  1721  
  1722  		When("starting the application fails", func() {
  1723  			var expectedErr error
  1724  
  1725  			BeforeEach(func() {
  1726  				expectedErr = errors.New("some set start-application error")
  1727  				fakeCloudControllerClient.UpdateApplicationStartReturns(
  1728  					resources.Application{},
  1729  					ccv3.Warnings{"start-application-warning"},
  1730  					expectedErr,
  1731  				)
  1732  			})
  1733  
  1734  			It("returns the error", func() {
  1735  				warnings, err := actor.StartApplication("some-app-guid")
  1736  
  1737  				Expect(err).To(Equal(expectedErr))
  1738  				Expect(warnings).To(ConsistOf("start-application-warning"))
  1739  			})
  1740  		})
  1741  	})
  1742  
  1743  	Describe("RestartApplication", func() {
  1744  		var (
  1745  			warnings   Warnings
  1746  			executeErr error
  1747  			noWait     bool
  1748  		)
  1749  
  1750  		BeforeEach(func() {
  1751  			fakeConfig.StartupTimeoutReturns(time.Second)
  1752  			fakeConfig.PollingIntervalReturns(0)
  1753  			noWait = false
  1754  		})
  1755  
  1756  		JustBeforeEach(func() {
  1757  			warnings, executeErr = actor.RestartApplication("some-app-guid", noWait)
  1758  		})
  1759  
  1760  		When("restarting the application is successful", func() {
  1761  			BeforeEach(func() {
  1762  				fakeCloudControllerClient.UpdateApplicationRestartReturns(
  1763  					resources.Application{GUID: "some-app-guid"},
  1764  					ccv3.Warnings{"restart-application-warning"},
  1765  					nil,
  1766  				)
  1767  			})
  1768  
  1769  			It("does not error", func() {
  1770  				Expect(executeErr).ToNot(HaveOccurred())
  1771  				Expect(warnings).To(ConsistOf("restart-application-warning"))
  1772  			})
  1773  		})
  1774  
  1775  		When("restarting the application fails", func() {
  1776  			var expectedErr error
  1777  
  1778  			BeforeEach(func() {
  1779  				expectedErr = errors.New("some set restart-application error")
  1780  				fakeCloudControllerClient.UpdateApplicationRestartReturns(
  1781  					resources.Application{},
  1782  					ccv3.Warnings{"restart-application-warning"},
  1783  					expectedErr,
  1784  				)
  1785  			})
  1786  
  1787  			It("returns the warnings and error", func() {
  1788  				Expect(executeErr).To(Equal(expectedErr))
  1789  				Expect(warnings).To(ConsistOf("restart-application-warning"))
  1790  			})
  1791  		})
  1792  	})
  1793  
  1794  	Describe("PollProcesses", func() {
  1795  		var (
  1796  			processes               []resources.Process
  1797  			handleInstanceDetails   func(string)
  1798  			reportedInstanceDetails []string
  1799  
  1800  			keepPolling bool
  1801  			warnings    Warnings
  1802  			executeErr  error
  1803  		)
  1804  
  1805  		BeforeEach(func() {
  1806  			reportedInstanceDetails = []string{}
  1807  			handleInstanceDetails = func(instanceDetails string) {
  1808  				reportedInstanceDetails = append(reportedInstanceDetails, instanceDetails)
  1809  			}
  1810  
  1811  			processes = []resources.Process{
  1812  				{GUID: "process-1"},
  1813  				{GUID: "process-2"},
  1814  			}
  1815  		})
  1816  
  1817  		JustBeforeEach(func() {
  1818  			keepPolling, warnings, executeErr = actor.PollProcesses(processes, handleInstanceDetails)
  1819  		})
  1820  
  1821  		It("gets process instances for each process", func() {
  1822  			Expect(executeErr).NotTo(HaveOccurred())
  1823  			Expect(fakeCloudControllerClient.GetProcessInstancesCallCount()).To(Equal(2))
  1824  			Expect(fakeCloudControllerClient.GetProcessInstancesArgsForCall(0)).To(Equal("process-1"))
  1825  			Expect(fakeCloudControllerClient.GetProcessInstancesArgsForCall(1)).To(Equal("process-2"))
  1826  		})
  1827  
  1828  		When("getting the process instances fails", func() {
  1829  			BeforeEach(func() {
  1830  				fakeCloudControllerClient.GetProcessInstancesReturns(nil, ccv3.Warnings{"get-instances-warning"}, errors.New("get-instances-error"))
  1831  			})
  1832  
  1833  			It("returns an error and warnings and terminates the loop", func() {
  1834  				Expect(executeErr).To(MatchError("get-instances-error"))
  1835  				Expect(warnings).To(ConsistOf("get-instances-warning"))
  1836  				Expect(keepPolling).To(BeTrue())
  1837  
  1838  				Expect(fakeCloudControllerClient.GetProcessInstancesCallCount()).To(Equal(1))
  1839  				Expect(fakeCloudControllerClient.GetProcessInstancesArgsForCall(0)).To(Equal("process-1"))
  1840  			})
  1841  		})
  1842  
  1843  		When("getting the process instances is always successful", func() {
  1844  			When("a process has all instances crashed", func() {
  1845  				BeforeEach(func() {
  1846  					fakeCloudControllerClient.GetProcessInstancesReturns(
  1847  						[]ccv3.ProcessInstance{
  1848  							{State: constant.ProcessInstanceCrashed, Details: "details1"},
  1849  						},
  1850  						ccv3.Warnings{"get-process1-instances-warning"},
  1851  						nil,
  1852  					)
  1853  				})
  1854  
  1855  				It("calls the callback function with the retrieved instances", func() {
  1856  					Expect(reportedInstanceDetails).To(Equal([]string{
  1857  						"Error starting instances: 'details1'",
  1858  					}))
  1859  				})
  1860  
  1861  				It("returns an all instances crashed error", func() {
  1862  					Expect(executeErr).To(MatchError(actionerror.AllInstancesCrashedError{}))
  1863  					Expect(warnings).To(ConsistOf("get-process1-instances-warning"))
  1864  					Expect(keepPolling).To(BeTrue())
  1865  				})
  1866  			})
  1867  
  1868  			When("there are still instances in the starting state for a process", func() {
  1869  				BeforeEach(func() {
  1870  					fakeCloudControllerClient.GetProcessInstancesReturnsOnCall(0,
  1871  						[]ccv3.ProcessInstance{
  1872  							{State: constant.ProcessInstanceRunning},
  1873  						},
  1874  						ccv3.Warnings{"get-process1-instances-warning"},
  1875  						nil,
  1876  					)
  1877  
  1878  					fakeCloudControllerClient.GetProcessInstancesReturnsOnCall(1,
  1879  						[]ccv3.ProcessInstance{
  1880  							{State: constant.ProcessInstanceStarting, Details: "details2"},
  1881  						},
  1882  						ccv3.Warnings{"get-process2-instances-warning"},
  1883  						nil,
  1884  					)
  1885  				})
  1886  
  1887  				It("calls the callback function with the retrieved instances", func() {
  1888  					Expect(reportedInstanceDetails).To(Equal([]string{
  1889  						"Instances starting...",
  1890  						"Error starting instances: 'details2'",
  1891  					}))
  1892  				})
  1893  
  1894  				It("returns success and that we should keep polling", func() {
  1895  					Expect(executeErr).NotTo(HaveOccurred())
  1896  					Expect(warnings).To(ConsistOf("get-process1-instances-warning", "get-process2-instances-warning"))
  1897  					Expect(keepPolling).To(BeFalse())
  1898  				})
  1899  			})
  1900  
  1901  			When("all the instances of all processes are stable", func() {
  1902  				BeforeEach(func() {
  1903  					fakeCloudControllerClient.GetProcessInstancesReturnsOnCall(0,
  1904  						[]ccv3.ProcessInstance{
  1905  							{State: constant.ProcessInstanceRunning, Details: "details1"},
  1906  						},
  1907  						ccv3.Warnings{"get-process1-instances-warning"},
  1908  						nil,
  1909  					)
  1910  
  1911  					fakeCloudControllerClient.GetProcessInstancesReturnsOnCall(1,
  1912  						[]ccv3.ProcessInstance{
  1913  							{State: constant.ProcessInstanceRunning},
  1914  						},
  1915  						ccv3.Warnings{"get-process2-instances-warning"},
  1916  						nil,
  1917  					)
  1918  				})
  1919  
  1920  				It("calls the callback function with the retrieved instances", func() {
  1921  					Expect(reportedInstanceDetails).To(Equal([]string{
  1922  						"Error starting instances: 'details1'",
  1923  						"Instances starting...",
  1924  					}))
  1925  				})
  1926  
  1927  				It("returns success and that we should keep polling", func() {
  1928  					Expect(executeErr).NotTo(HaveOccurred())
  1929  					Expect(warnings).To(ConsistOf("get-process1-instances-warning", "get-process2-instances-warning"))
  1930  					Expect(keepPolling).To(BeTrue())
  1931  				})
  1932  
  1933  			})
  1934  		})
  1935  
  1936  	})
  1937  
  1938  	Describe("GetUnstagedNewestPackageGUID", func() {
  1939  		var (
  1940  			packageToStage string
  1941  			warnings       Warnings
  1942  			executeErr     error
  1943  		)
  1944  
  1945  		JustBeforeEach(func() {
  1946  			packageToStage, warnings, executeErr = actor.GetUnstagedNewestPackageGUID("some-app-guid")
  1947  		})
  1948  
  1949  		// Nothing to stage.
  1950  		When("There are no packages on the app", func() {
  1951  			When("getting the packages succeeds", func() {
  1952  				BeforeEach(func() {
  1953  					fakeCloudControllerClient.GetPackagesReturns([]resources.Package{}, ccv3.Warnings{"get-packages-warnings"}, nil)
  1954  				})
  1955  
  1956  				It("checks for packages", func() {
  1957  					Expect(fakeCloudControllerClient.GetPackagesCallCount()).To(Equal(1))
  1958  					Expect(fakeCloudControllerClient.GetPackagesArgsForCall(0)).To(ConsistOf(
  1959  						ccv3.Query{Key: ccv3.AppGUIDFilter, Values: []string{"some-app-guid"}},
  1960  						ccv3.Query{Key: ccv3.OrderBy, Values: []string{ccv3.CreatedAtDescendingOrder}},
  1961  						ccv3.Query{Key: ccv3.PerPage, Values: []string{"1"}},
  1962  						ccv3.Query{Key: ccv3.Page, Values: []string{"1"}},
  1963  					))
  1964  				})
  1965  
  1966  				It("returns empty string", func() {
  1967  					Expect(packageToStage).To(Equal(""))
  1968  					Expect(warnings).To(ConsistOf("get-packages-warnings"))
  1969  					Expect(executeErr).To(BeNil())
  1970  				})
  1971  			})
  1972  
  1973  			When("getting the packages fails", func() {
  1974  				BeforeEach(func() {
  1975  					fakeCloudControllerClient.GetPackagesReturns(
  1976  						nil,
  1977  						ccv3.Warnings{"get-packages-warnings"},
  1978  						errors.New("get-packages-error"),
  1979  					)
  1980  				})
  1981  
  1982  				It("returns the error", func() {
  1983  					Expect(warnings).To(ConsistOf("get-packages-warnings"))
  1984  					Expect(executeErr).To(MatchError("get-packages-error"))
  1985  				})
  1986  			})
  1987  		})
  1988  
  1989  		When("there are packages", func() {
  1990  			BeforeEach(func() {
  1991  				fakeCloudControllerClient.GetPackagesReturns(
  1992  					[]resources.Package{{GUID: "package-guid", CreatedAt: "2019-01-01T06:00:00Z"}},
  1993  					ccv3.Warnings{"get-packages-warning"},
  1994  					nil)
  1995  			})
  1996  
  1997  			It("checks for the packages latest droplet", func() {
  1998  				Expect(fakeCloudControllerClient.GetPackageDropletsCallCount()).To(Equal(1))
  1999  				packageGuid, queries := fakeCloudControllerClient.GetPackageDropletsArgsForCall(0)
  2000  				Expect(packageGuid).To(Equal("package-guid"))
  2001  				Expect(queries).To(ConsistOf(
  2002  					ccv3.Query{Key: ccv3.StatesFilter, Values: []string{"STAGED"}},
  2003  					ccv3.Query{Key: ccv3.PerPage, Values: []string{"1"}},
  2004  					ccv3.Query{Key: ccv3.Page, Values: []string{"1"}},
  2005  				))
  2006  			})
  2007  
  2008  			When("the newest package's has a STAGED droplet", func() {
  2009  				BeforeEach(func() {
  2010  					fakeCloudControllerClient.GetPackageDropletsReturns(
  2011  						[]resources.Droplet{{State: constant.DropletStaged}},
  2012  						ccv3.Warnings{"get-package-droplet-warning"},
  2013  						nil,
  2014  					)
  2015  				})
  2016  
  2017  				It("returns empty string", func() {
  2018  					Expect(packageToStage).To(Equal(""))
  2019  					Expect(warnings).To(ConsistOf("get-packages-warning", "get-package-droplet-warning"))
  2020  					Expect(executeErr).To(BeNil())
  2021  				})
  2022  			})
  2023  
  2024  			When("the package has no STAGED droplets", func() {
  2025  				BeforeEach(func() {
  2026  					fakeCloudControllerClient.GetPackageDropletsReturns(
  2027  						[]resources.Droplet{},
  2028  						ccv3.Warnings{"get-package-droplet-warning"},
  2029  						nil,
  2030  					)
  2031  				})
  2032  
  2033  				It("returns the guid of the newest package", func() {
  2034  					Expect(packageToStage).To(Equal("package-guid"))
  2035  					Expect(warnings).To(ConsistOf("get-packages-warning", "get-package-droplet-warning"))
  2036  					Expect(executeErr).To(BeNil())
  2037  				})
  2038  			})
  2039  		})
  2040  	})
  2041  
  2042  	Describe("RenameApplicationByNameAndSpaceGUID", func() {
  2043  		When("the app does not exist", func() {
  2044  			BeforeEach(func() {
  2045  				fakeCloudControllerClient.GetApplicationsReturns(
  2046  					[]resources.Application{},
  2047  					ccv3.Warnings{"some-warning"},
  2048  					nil,
  2049  				)
  2050  			})
  2051  
  2052  			It("returns an ApplicationNotFoundError and the warnings", func() {
  2053  				_, warnings, err := actor.RenameApplicationByNameAndSpaceGUID("old-app-name", "new-app-name", "space-guid")
  2054  				Expect(warnings).To(ConsistOf("some-warning"))
  2055  				Expect(err).To(MatchError(actionerror.ApplicationNotFoundError{Name: "old-app-name"}))
  2056  			})
  2057  		})
  2058  
  2059  		When("the cloud controller client returns an error on application find", func() {
  2060  			var expectedError error
  2061  
  2062  			BeforeEach(func() {
  2063  				expectedError = errors.New("I am a CloudControllerClient Error")
  2064  				fakeCloudControllerClient.GetApplicationsReturns(
  2065  					[]resources.Application{},
  2066  					ccv3.Warnings{"some-warning"},
  2067  					expectedError)
  2068  			})
  2069  
  2070  			It("returns the warnings and the error", func() {
  2071  				_, warnings, err := actor.RenameApplicationByNameAndSpaceGUID("old-app-name", "new-app-name", "space-guid")
  2072  				Expect(warnings).To(ConsistOf("some-warning"))
  2073  				Expect(err).To(MatchError(expectedError))
  2074  			})
  2075  		})
  2076  
  2077  		When("the cloud controller client returns an error on application update", func() {
  2078  			var expectedError error
  2079  
  2080  			BeforeEach(func() {
  2081  				expectedError = errors.New("I am a CloudControllerClient Error")
  2082  				fakeCloudControllerClient.GetApplicationsReturns(
  2083  					[]resources.Application{
  2084  						{
  2085  							Name: "old-app-name",
  2086  							GUID: "old-app-guid",
  2087  						},
  2088  					},
  2089  					ccv3.Warnings{"get-app-warning"},
  2090  					nil)
  2091  				fakeCloudControllerClient.UpdateApplicationNameReturns(
  2092  					resources.Application{},
  2093  					ccv3.Warnings{"update-app-warning"},
  2094  					expectedError)
  2095  			})
  2096  
  2097  			It("returns the warnings and the error", func() {
  2098  				_, warnings, err := actor.RenameApplicationByNameAndSpaceGUID("old-app-name", "new-app-name", "space-guid")
  2099  				Expect(warnings).To(ConsistOf("get-app-warning", "update-app-warning"))
  2100  				Expect(err).To(MatchError(expectedError))
  2101  			})
  2102  		})
  2103  
  2104  		When("the app exists", func() {
  2105  			BeforeEach(func() {
  2106  				fakeCloudControllerClient.GetApplicationsReturns(
  2107  					[]resources.Application{
  2108  						{
  2109  							Name: "old-app-name",
  2110  							GUID: "old-app-guid",
  2111  						},
  2112  					},
  2113  					ccv3.Warnings{"get-app-warning"},
  2114  					nil,
  2115  				)
  2116  
  2117  				fakeCloudControllerClient.UpdateApplicationNameReturns(
  2118  					resources.Application{
  2119  						Name: "new-app-name",
  2120  						GUID: "old-app-guid",
  2121  					},
  2122  					ccv3.Warnings{"update-app-warning"},
  2123  					nil,
  2124  				)
  2125  			})
  2126  
  2127  			It("changes the app name and returns the application and warnings", func() {
  2128  				app, warnings, err := actor.RenameApplicationByNameAndSpaceGUID("old-app-name", "new-app-name", "some-space-guid")
  2129  				Expect(err).ToNot(HaveOccurred())
  2130  				Expect(app).To(Equal(resources.Application{
  2131  					Name: "new-app-name",
  2132  					GUID: "old-app-guid",
  2133  				}))
  2134  				Expect(warnings).To(ConsistOf("get-app-warning", "update-app-warning"))
  2135  				appName, appGuid := fakeCloudControllerClient.UpdateApplicationNameArgsForCall(0)
  2136  				Expect(appName).To(Equal("new-app-name"))
  2137  				Expect(appGuid).To(Equal("old-app-guid"))
  2138  			})
  2139  		})
  2140  
  2141  	})
  2142  })