github.com/LukasHeimann/cloudfoundrycli@v7.1.0+incompatible/api/cloudcontroller/ccv3/application_test.go (about)

     1  package ccv3_test
     2  
     3  import (
     4  	"encoding/json"
     5  	"net/http"
     6  
     7  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccerror"
     8  	. "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3"
     9  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/ccv3fakes"
    10  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/constant"
    11  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/internal"
    12  	"code.cloudfoundry.org/cli/resources"
    13  	"code.cloudfoundry.org/cli/types"
    14  	. "github.com/onsi/ginkgo"
    15  	. "github.com/onsi/gomega"
    16  )
    17  
    18  var _ = Describe("Application", func() {
    19  	var (
    20  		client    *Client
    21  		requester *ccv3fakes.FakeRequester
    22  	)
    23  
    24  	BeforeEach(func() {
    25  		requester = new(ccv3fakes.FakeRequester)
    26  		client, _ = NewFakeRequesterTestClient(requester)
    27  	})
    28  
    29  	Describe("Application", func() {
    30  		Describe("MarshalJSON", func() {
    31  			var (
    32  				app      resources.Application
    33  				appBytes []byte
    34  				err      error
    35  			)
    36  
    37  			BeforeEach(func() {
    38  				app = resources.Application{}
    39  			})
    40  
    41  			JustBeforeEach(func() {
    42  				appBytes, err = app.MarshalJSON()
    43  				Expect(err).ToNot(HaveOccurred())
    44  			})
    45  
    46  			When("no lifecycle is provided", func() {
    47  				BeforeEach(func() {
    48  					app = resources.Application{}
    49  				})
    50  
    51  				It("omits the lifecycle from the JSON", func() {
    52  					Expect(string(appBytes)).To(Equal("{}"))
    53  				})
    54  			})
    55  
    56  			When("lifecycle type docker is provided", func() {
    57  				BeforeEach(func() {
    58  					app = resources.Application{
    59  						LifecycleType: constant.AppLifecycleTypeDocker,
    60  					}
    61  				})
    62  
    63  				It("sets lifecycle type to docker with empty data", func() {
    64  					Expect(string(appBytes)).To(MatchJSON(`{"lifecycle":{"type":"docker","data":{}}}`))
    65  				})
    66  			})
    67  
    68  			When("lifecycle type buildpack is provided", func() {
    69  				BeforeEach(func() {
    70  					app.LifecycleType = constant.AppLifecycleTypeBuildpack
    71  				})
    72  
    73  				When("no buildpacks are provided", func() {
    74  					It("omits the lifecycle from the JSON", func() {
    75  						Expect(string(appBytes)).To(MatchJSON("{}"))
    76  					})
    77  
    78  					When("but you do specify a stack", func() {
    79  						BeforeEach(func() {
    80  							app.StackName = "cflinuxfs9000"
    81  						})
    82  
    83  						It("does, in fact, send the stack in the json", func() {
    84  							Expect(string(appBytes)).To(MatchJSON(`{"lifecycle":{"data":{"stack":"cflinuxfs9000"},"type":"buildpack"}}`))
    85  						})
    86  					})
    87  				})
    88  
    89  				When("default buildpack is provided", func() {
    90  					BeforeEach(func() {
    91  						app.LifecycleBuildpacks = []string{"default"}
    92  					})
    93  
    94  					It("sets the lifecycle buildpack to be empty in the JSON", func() {
    95  						Expect(string(appBytes)).To(MatchJSON(`{"lifecycle":{"data":{"buildpacks":null},"type":"buildpack"}}`))
    96  					})
    97  				})
    98  
    99  				When("null buildpack is provided", func() {
   100  					BeforeEach(func() {
   101  						app.LifecycleBuildpacks = []string{"null"}
   102  					})
   103  
   104  					It("sets the Lifecycle buildpack to be empty in the JSON", func() {
   105  						Expect(string(appBytes)).To(MatchJSON(`{"lifecycle":{"data":{"buildpacks":null},"type":"buildpack"}}`))
   106  					})
   107  				})
   108  
   109  				When("other buildpacks are provided", func() {
   110  					BeforeEach(func() {
   111  						app.LifecycleBuildpacks = []string{"some-buildpack"}
   112  					})
   113  
   114  					It("sets them in the JSON", func() {
   115  						Expect(string(appBytes)).To(MatchJSON(`{"lifecycle":{"data":{"buildpacks":["some-buildpack"]},"type":"buildpack"}}`))
   116  					})
   117  				})
   118  			})
   119  
   120  			When("metadata is provided", func() {
   121  				BeforeEach(func() {
   122  					app = resources.Application{
   123  						Metadata: &resources.Metadata{
   124  							Labels: map[string]types.NullString{
   125  								"some-key":  types.NewNullString("some-value"),
   126  								"other-key": types.NewNullString("other-value\nwith a newline & a \" quote")},
   127  						},
   128  					}
   129  				})
   130  
   131  				It("should include the labels in the JSON", func() {
   132  					Expect(string(appBytes)).To(MatchJSON(`{
   133  						"metadata": {
   134  							"labels": {
   135  								"some-key":"some-value",
   136  								"other-key":"other-value\nwith a newline & a \" quote"
   137  							}
   138  						}
   139  					}`))
   140  				})
   141  
   142  				When("labels need to be removed", func() {
   143  					BeforeEach(func() {
   144  						app = resources.Application{
   145  							Metadata: &resources.Metadata{
   146  								Labels: map[string]types.NullString{
   147  									"some-key":      types.NewNullString("some-value"),
   148  									"other-key":     types.NewNullString("other-value\nwith a newline & a \" quote"),
   149  									"key-to-delete": types.NewNullString(),
   150  								},
   151  							},
   152  						}
   153  					})
   154  
   155  					It("should send nulls for those labels", func() {
   156  						Expect(string(appBytes)).To(MatchJSON(`{
   157  						"metadata": {
   158  							"labels": {
   159  								"some-key":"some-value",
   160  								"other-key":"other-value\nwith a newline & a \" quote",
   161  								"key-to-delete":null
   162  							}
   163  						}
   164  					}`))
   165  					})
   166  				})
   167  			})
   168  		})
   169  
   170  		Describe("UnmarshalJSON", func() {
   171  			var (
   172  				app      resources.Application
   173  				appBytes []byte
   174  				err      error
   175  			)
   176  
   177  			BeforeEach(func() {
   178  				appBytes = []byte("{}")
   179  				app = resources.Application{}
   180  			})
   181  
   182  			JustBeforeEach(func() {
   183  				err = json.Unmarshal(appBytes, &app)
   184  				Expect(err).ToNot(HaveOccurred())
   185  			})
   186  
   187  			When("no lifecycle is provided", func() {
   188  				BeforeEach(func() {
   189  					appBytes = []byte("{}")
   190  				})
   191  
   192  				It("omits the lifecycle from the JSON", func() {
   193  					Expect(app).To(Equal(resources.Application{}))
   194  				})
   195  			})
   196  
   197  			When("lifecycle type docker is provided", func() {
   198  				BeforeEach(func() {
   199  					appBytes = []byte(`{"lifecycle":{"type":"docker","data":{}}}`)
   200  				})
   201  				It("sets the lifecycle type to docker with empty data", func() {
   202  					Expect(app).To(Equal(resources.Application{
   203  						LifecycleType: constant.AppLifecycleTypeDocker,
   204  					}))
   205  				})
   206  			})
   207  
   208  			When("lifecycle type buildpack is provided", func() {
   209  
   210  				When("other buildpacks are provided", func() {
   211  					BeforeEach(func() {
   212  						appBytes = []byte(`{"lifecycle":{"data":{"buildpacks":["some-buildpack"]},"type":"buildpack"}}`)
   213  					})
   214  
   215  					It("sets them in the JSON", func() {
   216  						Expect(app).To(Equal(resources.Application{
   217  							LifecycleType:       constant.AppLifecycleTypeBuildpack,
   218  							LifecycleBuildpacks: []string{"some-buildpack"},
   219  						}))
   220  					})
   221  				})
   222  			})
   223  
   224  			When("labels are provided", func() {
   225  				BeforeEach(func() {
   226  					appBytes = []byte(`{"metadata":{"labels":{"some-key":"some-value"}}}`)
   227  				})
   228  
   229  				It("sets the labels", func() {
   230  					Expect(app).To(Equal(resources.Application{
   231  						Metadata: &resources.Metadata{
   232  							Labels: map[string]types.NullString{
   233  								"some-key": types.NewNullString("some-value"),
   234  							},
   235  						},
   236  					}))
   237  				})
   238  			})
   239  		})
   240  	})
   241  
   242  	Describe("CreateApplication", func() {
   243  		var (
   244  			appToCreate resources.Application
   245  
   246  			createdApp resources.Application
   247  			warnings   Warnings
   248  			executeErr error
   249  		)
   250  
   251  		JustBeforeEach(func() {
   252  			createdApp, warnings, executeErr = client.CreateApplication(appToCreate)
   253  		})
   254  
   255  		When("the application successfully is created", func() {
   256  			BeforeEach(func() {
   257  				requester.MakeRequestCalls(func(requestParams RequestParams) (JobURL, Warnings, error) {
   258  					requestParams.ResponseBody.(*resources.Application).GUID = "some-app-guid"
   259  					requestParams.ResponseBody.(*resources.Application).Name = requestParams.RequestBody.(resources.Application).Name
   260  					return "", Warnings{"this is a warning"}, nil
   261  				})
   262  				appToCreate = resources.Application{
   263  					Name:      "some-app-name",
   264  					SpaceGUID: "some-space-guid",
   265  				}
   266  			})
   267  
   268  			It("makes the correct request", func() {
   269  				Expect(requester.MakeRequestCallCount()).To(Equal(1))
   270  				actualParams := requester.MakeRequestArgsForCall(0)
   271  				Expect(actualParams.RequestName).To(Equal(internal.PostApplicationRequest))
   272  				Expect(actualParams.RequestBody).To(Equal(appToCreate))
   273  				_, ok := actualParams.ResponseBody.(*resources.Application)
   274  				Expect(ok).To(BeTrue())
   275  			})
   276  
   277  			It("returns the created app and warnings", func() {
   278  				Expect(executeErr).NotTo(HaveOccurred())
   279  				Expect(warnings).To(ConsistOf("this is a warning"))
   280  
   281  				Expect(createdApp).To(Equal(resources.Application{
   282  					Name: "some-app-name",
   283  					GUID: "some-app-guid",
   284  				}))
   285  			})
   286  		})
   287  
   288  		When("cc returns back an error or warnings", func() {
   289  			BeforeEach(func() {
   290  				errors := []ccerror.V3Error{
   291  					{
   292  						Code:   10008,
   293  						Detail: "The request is semantically invalid: command presence",
   294  						Title:  "CF-UnprocessableEntity",
   295  					},
   296  					{
   297  						Code:   10010,
   298  						Detail: "App not found",
   299  						Title:  "CF-ResourceNotFound",
   300  					},
   301  				}
   302  
   303  				requester.MakeRequestReturns(
   304  					"",
   305  					Warnings{"this is a warning"},
   306  					ccerror.MultiError{ResponseCode: http.StatusTeapot, Errors: errors},
   307  				)
   308  			})
   309  
   310  			It("returns the error and all warnings", func() {
   311  				Expect(executeErr).To(MatchError(ccerror.MultiError{
   312  					ResponseCode: http.StatusTeapot,
   313  					Errors: []ccerror.V3Error{
   314  						{
   315  							Code:   10008,
   316  							Detail: "The request is semantically invalid: command presence",
   317  							Title:  "CF-UnprocessableEntity",
   318  						},
   319  						{
   320  							Code:   10010,
   321  							Detail: "App not found",
   322  							Title:  "CF-ResourceNotFound",
   323  						},
   324  					},
   325  				}))
   326  				Expect(warnings).To(ConsistOf("this is a warning"))
   327  			})
   328  		})
   329  	})
   330  
   331  	Describe("GetApplicationByNameAndSpace", func() {
   332  		var (
   333  			appName   = "some-app-name"
   334  			spaceGUID = "some-space-guid"
   335  
   336  			app        resources.Application
   337  			warnings   Warnings
   338  			executeErr error
   339  		)
   340  
   341  		JustBeforeEach(func() {
   342  			app, warnings, executeErr = client.GetApplicationByNameAndSpace(appName, spaceGUID)
   343  		})
   344  
   345  		When("the application exists", func() {
   346  			BeforeEach(func() {
   347  				requester.MakeListRequestCalls(func(requestParams RequestParams) (IncludedResources, Warnings, error) {
   348  					err := requestParams.AppendToList(resources.Application{GUID: "app-guid-2"})
   349  					Expect(err).NotTo(HaveOccurred())
   350  					return IncludedResources{}, Warnings{"this is a warning"}, nil
   351  				})
   352  			})
   353  
   354  			It("makes the correct request", func() {
   355  				Expect(requester.MakeListRequestCallCount()).To(Equal(1))
   356  				actualParams := requester.MakeListRequestArgsForCall(0)
   357  				Expect(actualParams.RequestName).To(Equal(internal.GetApplicationsRequest))
   358  				Expect(actualParams.Query).To(Equal([]Query{
   359  					{Key: NameFilter, Values: []string{"some-app-name"}},
   360  					{Key: SpaceGUIDFilter, Values: []string{"some-space-guid"}},
   361  				}))
   362  				_, ok := actualParams.ResponseBody.(resources.Application)
   363  				Expect(ok).To(BeTrue())
   364  			})
   365  
   366  			It("returns the queried application and all warnings", func() {
   367  				Expect(executeErr).NotTo(HaveOccurred())
   368  				Expect(warnings).To(ConsistOf("this is a warning"))
   369  				Expect(app).To(Equal(resources.Application{GUID: "app-guid-2"}))
   370  			})
   371  		})
   372  
   373  		When("the application does not exist", func() {
   374  			BeforeEach(func() {
   375  				requester.MakeListRequestReturns(IncludedResources{}, Warnings{"this is a warning"}, nil)
   376  			})
   377  
   378  			It("returns an error and warnings", func() {
   379  				Expect(executeErr).To(MatchError(ccerror.ApplicationNotFoundError{Name: appName}))
   380  				Expect(warnings).To(ConsistOf("this is a warning"))
   381  			})
   382  		})
   383  
   384  		When("the cloud controller returns errors and warnings", func() {
   385  			BeforeEach(func() {
   386  				errors := []ccerror.V3Error{
   387  					{
   388  						Code:   10008,
   389  						Detail: "The request is semantically invalid: command presence",
   390  						Title:  "CF-UnprocessableEntity",
   391  					},
   392  					{
   393  						Code:   10010,
   394  						Detail: "App not found",
   395  						Title:  "CF-ResourceNotFound",
   396  					},
   397  				}
   398  
   399  				requester.MakeListRequestReturns(
   400  					IncludedResources{},
   401  					Warnings{"this is a warning"},
   402  					ccerror.MultiError{ResponseCode: http.StatusTeapot, Errors: errors},
   403  				)
   404  			})
   405  
   406  			It("returns the error and all warnings", func() {
   407  				Expect(executeErr).To(MatchError(ccerror.MultiError{
   408  					ResponseCode: http.StatusTeapot,
   409  					Errors: []ccerror.V3Error{
   410  						{
   411  							Code:   10008,
   412  							Detail: "The request is semantically invalid: command presence",
   413  							Title:  "CF-UnprocessableEntity",
   414  						},
   415  						{
   416  							Code:   10010,
   417  							Detail: "App not found",
   418  							Title:  "CF-ResourceNotFound",
   419  						},
   420  					},
   421  				}))
   422  				Expect(warnings).To(ConsistOf("this is a warning"))
   423  			})
   424  		})
   425  	})
   426  
   427  	Describe("GetApplications", func() {
   428  		var (
   429  			filters []Query
   430  
   431  			apps       []resources.Application
   432  			warnings   Warnings
   433  			executeErr error
   434  		)
   435  
   436  		JustBeforeEach(func() {
   437  			apps, warnings, executeErr = client.GetApplications(filters...)
   438  		})
   439  
   440  		When("applications exist", func() {
   441  			BeforeEach(func() {
   442  				requester.MakeListRequestCalls(func(requestParams RequestParams) (IncludedResources, Warnings, error) {
   443  					err := requestParams.AppendToList(resources.Application{GUID: "app-guid-1"})
   444  					Expect(err).NotTo(HaveOccurred())
   445  					return IncludedResources{}, Warnings{"this is a warning", "this is another warning"}, nil
   446  				})
   447  
   448  				filters = []Query{
   449  					{Key: SpaceGUIDFilter, Values: []string{"some-space-guid"}},
   450  					{Key: NameFilter, Values: []string{"some-app-name"}},
   451  				}
   452  			})
   453  
   454  			It("makes the correct request", func() {
   455  				Expect(requester.MakeListRequestCallCount()).To(Equal(1))
   456  				actualParams := requester.MakeListRequestArgsForCall(0)
   457  				Expect(actualParams.RequestName).To(Equal(internal.GetApplicationsRequest))
   458  				Expect(actualParams.Query).To(Equal(filters))
   459  				_, ok := actualParams.ResponseBody.(resources.Application)
   460  				Expect(ok).To(BeTrue())
   461  			})
   462  
   463  			It("returns the queried applications and all warnings", func() {
   464  				Expect(executeErr).NotTo(HaveOccurred())
   465  				Expect(warnings).To(ConsistOf("this is a warning", "this is another warning"))
   466  
   467  				Expect(apps).To(ConsistOf(resources.Application{GUID: "app-guid-1"}))
   468  			})
   469  		})
   470  
   471  		When("the cloud controller returns errors and warnings", func() {
   472  			BeforeEach(func() {
   473  				errors := []ccerror.V3Error{
   474  					{
   475  						Code:   10008,
   476  						Detail: "The request is semantically invalid: command presence",
   477  						Title:  "CF-UnprocessableEntity",
   478  					},
   479  					{
   480  						Code:   10010,
   481  						Detail: "App not found",
   482  						Title:  "CF-ResourceNotFound",
   483  					},
   484  				}
   485  
   486  				requester.MakeListRequestReturns(
   487  					IncludedResources{},
   488  					Warnings{"this is a warning"},
   489  					ccerror.MultiError{ResponseCode: http.StatusTeapot, Errors: errors},
   490  				)
   491  			})
   492  
   493  			It("returns the error and all warnings", func() {
   494  				Expect(executeErr).To(MatchError(ccerror.MultiError{
   495  					ResponseCode: http.StatusTeapot,
   496  					Errors: []ccerror.V3Error{
   497  						{
   498  							Code:   10008,
   499  							Detail: "The request is semantically invalid: command presence",
   500  							Title:  "CF-UnprocessableEntity",
   501  						},
   502  						{
   503  							Code:   10010,
   504  							Detail: "App not found",
   505  							Title:  "CF-ResourceNotFound",
   506  						},
   507  					},
   508  				}))
   509  				Expect(warnings).To(ConsistOf("this is a warning"))
   510  			})
   511  		})
   512  	})
   513  
   514  	Describe("UpdateApplication", func() {
   515  		var (
   516  			appToUpdate resources.Application
   517  
   518  			updatedApp resources.Application
   519  			warnings   Warnings
   520  			executeErr error
   521  		)
   522  
   523  		JustBeforeEach(func() {
   524  			updatedApp, warnings, executeErr = client.UpdateApplication(appToUpdate)
   525  		})
   526  
   527  		When("the application successfully is updated", func() {
   528  			BeforeEach(func() {
   529  				requester.MakeRequestCalls(func(requestParams RequestParams) (JobURL, Warnings, error) {
   530  					requestParams.ResponseBody.(*resources.Application).GUID = "some-app-guid"
   531  					requestParams.ResponseBody.(*resources.Application).Name = requestParams.RequestBody.(resources.Application).Name
   532  					requestParams.ResponseBody.(*resources.Application).StackName = requestParams.RequestBody.(resources.Application).StackName
   533  					requestParams.ResponseBody.(*resources.Application).LifecycleType = requestParams.RequestBody.(resources.Application).LifecycleType
   534  					requestParams.ResponseBody.(*resources.Application).LifecycleBuildpacks = requestParams.RequestBody.(resources.Application).LifecycleBuildpacks
   535  					requestParams.ResponseBody.(*resources.Application).SpaceGUID = requestParams.RequestBody.(resources.Application).SpaceGUID
   536  					return "", Warnings{"this is a warning"}, nil
   537  				})
   538  
   539  				appToUpdate = resources.Application{
   540  					GUID:                "some-app-guid",
   541  					Name:                "some-app-name",
   542  					StackName:           "some-stack-name",
   543  					LifecycleType:       constant.AppLifecycleTypeBuildpack,
   544  					LifecycleBuildpacks: []string{"some-buildpack"},
   545  					SpaceGUID:           "some-space-guid",
   546  				}
   547  			})
   548  
   549  			It("makes the correct request", func() {
   550  				Expect(requester.MakeRequestCallCount()).To(Equal(1))
   551  				actualParams := requester.MakeRequestArgsForCall(0)
   552  				Expect(actualParams.RequestName).To(Equal(internal.PatchApplicationRequest))
   553  				Expect(actualParams.URIParams).To(Equal(internal.Params{"app_guid": "some-app-guid"}))
   554  				Expect(actualParams.RequestBody).To(Equal(appToUpdate))
   555  				_, ok := actualParams.ResponseBody.(*resources.Application)
   556  				Expect(ok).To(BeTrue())
   557  			})
   558  
   559  			It("returns the updated app and warnings", func() {
   560  				Expect(executeErr).NotTo(HaveOccurred())
   561  				Expect(warnings).To(ConsistOf("this is a warning"))
   562  
   563  				Expect(updatedApp).To(Equal(resources.Application{
   564  					GUID:                "some-app-guid",
   565  					StackName:           "some-stack-name",
   566  					LifecycleBuildpacks: []string{"some-buildpack"},
   567  					LifecycleType:       constant.AppLifecycleTypeBuildpack,
   568  					Name:                "some-app-name",
   569  					SpaceGUID:           "some-space-guid",
   570  				}))
   571  			})
   572  		})
   573  
   574  		When("cc returns back an error or warnings", func() {
   575  			BeforeEach(func() {
   576  				errors := []ccerror.V3Error{
   577  					{
   578  						Code:   10008,
   579  						Detail: "The request is semantically invalid: command presence",
   580  						Title:  "CF-UnprocessableEntity",
   581  					},
   582  					{
   583  						Code:   10010,
   584  						Detail: "App not found",
   585  						Title:  "CF-ResourceNotFound",
   586  					},
   587  				}
   588  
   589  				requester.MakeRequestReturns(
   590  					"",
   591  					Warnings{"this is a warning"},
   592  					ccerror.MultiError{ResponseCode: http.StatusTeapot, Errors: errors},
   593  				)
   594  			})
   595  
   596  			It("returns the error and all warnings", func() {
   597  				Expect(executeErr).To(MatchError(ccerror.MultiError{
   598  					ResponseCode: http.StatusTeapot,
   599  					Errors: []ccerror.V3Error{
   600  						{
   601  							Code:   10008,
   602  							Detail: "The request is semantically invalid: command presence",
   603  							Title:  "CF-UnprocessableEntity",
   604  						},
   605  						{
   606  							Code:   10010,
   607  							Detail: "App not found",
   608  							Title:  "CF-ResourceNotFound",
   609  						},
   610  					},
   611  				}))
   612  				Expect(warnings).To(ConsistOf("this is a warning"))
   613  			})
   614  		})
   615  	})
   616  
   617  	Describe("UpdateApplicationStop", func() {
   618  		var (
   619  			responseApp resources.Application
   620  			warnings    Warnings
   621  			executeErr  error
   622  		)
   623  
   624  		JustBeforeEach(func() {
   625  			responseApp, warnings, executeErr = client.UpdateApplicationStop("some-app-guid")
   626  		})
   627  
   628  		When("the response succeeds", func() {
   629  			BeforeEach(func() {
   630  				requester.MakeRequestCalls(func(requestParams RequestParams) (JobURL, Warnings, error) {
   631  					requestParams.ResponseBody.(*resources.Application).GUID = "some-app-guid"
   632  					requestParams.ResponseBody.(*resources.Application).Name = "some-app"
   633  					requestParams.ResponseBody.(*resources.Application).State = constant.ApplicationStopped
   634  					return "", Warnings{"this is a warning"}, nil
   635  				})
   636  			})
   637  
   638  			It("makes the correct request", func() {
   639  				Expect(requester.MakeRequestCallCount()).To(Equal(1))
   640  				actualParams := requester.MakeRequestArgsForCall(0)
   641  				Expect(actualParams.RequestName).To(Equal(internal.PostApplicationActionStopRequest))
   642  				Expect(actualParams.URIParams).To(Equal(internal.Params{"app_guid": "some-app-guid"}))
   643  				_, ok := actualParams.ResponseBody.(*resources.Application)
   644  				Expect(ok).To(BeTrue())
   645  			})
   646  
   647  			It("returns the application, warnings, and no error", func() {
   648  				Expect(responseApp).To(Equal(resources.Application{
   649  					GUID:  "some-app-guid",
   650  					Name:  "some-app",
   651  					State: constant.ApplicationStopped,
   652  				}))
   653  				Expect(executeErr).ToNot(HaveOccurred())
   654  				Expect(warnings).To(ConsistOf("this is a warning"))
   655  			})
   656  		})
   657  
   658  		When("the CC returns an error", func() {
   659  			BeforeEach(func() {
   660  				errors := []ccerror.V3Error{
   661  					{
   662  						Code:   10008,
   663  						Detail: "The request is semantically invalid: command presence",
   664  						Title:  "CF-UnprocessableEntity",
   665  					},
   666  					{
   667  						Code:   10010,
   668  						Detail: "App not found",
   669  						Title:  "CF-ResourceNotFound",
   670  					},
   671  				}
   672  
   673  				requester.MakeRequestReturns(
   674  					"",
   675  					Warnings{"this is a warning"},
   676  					ccerror.MultiError{ResponseCode: http.StatusTeapot, Errors: errors},
   677  				)
   678  			})
   679  
   680  			It("returns no app, the error and all warnings", func() {
   681  				Expect(executeErr).To(MatchError(ccerror.MultiError{
   682  					ResponseCode: http.StatusTeapot,
   683  					Errors: []ccerror.V3Error{
   684  						{
   685  							Code:   10008,
   686  							Detail: "The request is semantically invalid: command presence",
   687  							Title:  "CF-UnprocessableEntity",
   688  						},
   689  						{
   690  							Code:   10010,
   691  							Detail: "App not found",
   692  							Title:  "CF-ResourceNotFound",
   693  						},
   694  					},
   695  				}))
   696  				Expect(warnings).To(ConsistOf("this is a warning"))
   697  			})
   698  		})
   699  	})
   700  
   701  	Describe("UpdateApplicationStart", func() {
   702  		var (
   703  			responseApp resources.Application
   704  			warnings    Warnings
   705  			executeErr  error
   706  		)
   707  
   708  		JustBeforeEach(func() {
   709  			responseApp, warnings, executeErr = client.UpdateApplicationStart("some-app-guid")
   710  		})
   711  
   712  		When("the response succeeds", func() {
   713  			BeforeEach(func() {
   714  				requester.MakeRequestCalls(func(requestParams RequestParams) (JobURL, Warnings, error) {
   715  					requestParams.ResponseBody.(*resources.Application).GUID = "some-app-guid"
   716  					requestParams.ResponseBody.(*resources.Application).Name = "some-app"
   717  					return "", Warnings{"this is a warning"}, nil
   718  				})
   719  			})
   720  
   721  			It("makes the correct request", func() {
   722  				Expect(requester.MakeRequestCallCount()).To(Equal(1))
   723  				actualParams := requester.MakeRequestArgsForCall(0)
   724  				Expect(actualParams.RequestName).To(Equal(internal.PostApplicationActionStartRequest))
   725  				Expect(actualParams.URIParams).To(Equal(internal.Params{"app_guid": "some-app-guid"}))
   726  				_, ok := actualParams.ResponseBody.(*resources.Application)
   727  				Expect(ok).To(BeTrue())
   728  			})
   729  
   730  			It("returns warnings and no error", func() {
   731  				Expect(executeErr).ToNot(HaveOccurred())
   732  				Expect(warnings).To(ConsistOf("this is a warning"))
   733  				Expect(responseApp).To(Equal(resources.Application{
   734  					GUID: "some-app-guid",
   735  					Name: "some-app",
   736  				}))
   737  			})
   738  		})
   739  
   740  		When("cc returns back an error or warnings", func() {
   741  			BeforeEach(func() {
   742  				errors := []ccerror.V3Error{
   743  					{
   744  						Code:   10008,
   745  						Detail: "The request is semantically invalid: command presence",
   746  						Title:  "CF-UnprocessableEntity",
   747  					},
   748  					{
   749  						Code:   10010,
   750  						Detail: "App not found",
   751  						Title:  "CF-ResourceNotFound",
   752  					},
   753  				}
   754  
   755  				requester.MakeRequestReturns(
   756  					"",
   757  					Warnings{"this is a warning"},
   758  					ccerror.MultiError{ResponseCode: http.StatusTeapot, Errors: errors},
   759  				)
   760  			})
   761  
   762  			It("returns the error and all warnings", func() {
   763  				Expect(executeErr).To(MatchError(ccerror.MultiError{
   764  					ResponseCode: http.StatusTeapot,
   765  					Errors: []ccerror.V3Error{
   766  						{
   767  							Code:   10008,
   768  							Detail: "The request is semantically invalid: command presence",
   769  							Title:  "CF-UnprocessableEntity",
   770  						},
   771  						{
   772  							Code:   10010,
   773  							Detail: "App not found",
   774  							Title:  "CF-ResourceNotFound",
   775  						},
   776  					},
   777  				}))
   778  				Expect(warnings).To(ConsistOf("this is a warning"))
   779  			})
   780  		})
   781  	})
   782  
   783  	Describe("UpdateApplicationRestart", func() {
   784  		var (
   785  			responseApp resources.Application
   786  			warnings    Warnings
   787  			executeErr  error
   788  		)
   789  
   790  		JustBeforeEach(func() {
   791  			responseApp, warnings, executeErr = client.UpdateApplicationRestart("some-app-guid")
   792  		})
   793  
   794  		When("the response succeeds", func() {
   795  			BeforeEach(func() {
   796  				requester.MakeRequestCalls(func(requestParams RequestParams) (JobURL, Warnings, error) {
   797  					requestParams.ResponseBody.(*resources.Application).GUID = "some-app-guid"
   798  					requestParams.ResponseBody.(*resources.Application).Name = "some-app"
   799  					requestParams.ResponseBody.(*resources.Application).State = constant.ApplicationStarted
   800  					return "", Warnings{"this is a warning"}, nil
   801  				})
   802  			})
   803  
   804  			It("makes the correct request", func() {
   805  				Expect(requester.MakeRequestCallCount()).To(Equal(1))
   806  				actualParams := requester.MakeRequestArgsForCall(0)
   807  				Expect(actualParams.RequestName).To(Equal(internal.PostApplicationActionRestartRequest))
   808  				Expect(actualParams.URIParams).To(Equal(internal.Params{"app_guid": "some-app-guid"}))
   809  				_, ok := actualParams.ResponseBody.(*resources.Application)
   810  				Expect(ok).To(BeTrue())
   811  			})
   812  
   813  			It("returns the application, warnings, and no error", func() {
   814  				Expect(responseApp).To(Equal(resources.Application{
   815  					GUID:  "some-app-guid",
   816  					Name:  "some-app",
   817  					State: constant.ApplicationStarted,
   818  				}))
   819  				Expect(executeErr).ToNot(HaveOccurred())
   820  				Expect(warnings).To(ConsistOf("this is a warning"))
   821  			})
   822  		})
   823  
   824  		When("the CC returns an error", func() {
   825  			BeforeEach(func() {
   826  				errors := []ccerror.V3Error{
   827  					{
   828  						Code:   10008,
   829  						Detail: "The request is semantically invalid: command presence",
   830  						Title:  "CF-UnprocessableEntity",
   831  					},
   832  					{
   833  						Code:   10010,
   834  						Detail: "App not found",
   835  						Title:  "CF-ResourceNotFound",
   836  					},
   837  				}
   838  
   839  				requester.MakeRequestReturns(
   840  					"",
   841  					Warnings{"this is a warning"},
   842  					ccerror.MultiError{ResponseCode: http.StatusTeapot, Errors: errors},
   843  				)
   844  			})
   845  
   846  			It("returns no app, the error and all warnings", func() {
   847  				Expect(executeErr).To(MatchError(ccerror.MultiError{
   848  					ResponseCode: http.StatusTeapot,
   849  					Errors: []ccerror.V3Error{
   850  						{
   851  							Code:   10008,
   852  							Detail: "The request is semantically invalid: command presence",
   853  							Title:  "CF-UnprocessableEntity",
   854  						},
   855  						{
   856  							Code:   10010,
   857  							Detail: "App not found",
   858  							Title:  "CF-ResourceNotFound",
   859  						},
   860  					},
   861  				}))
   862  				Expect(warnings).To(ConsistOf("this is a warning"))
   863  			})
   864  		})
   865  	})
   866  })