github.com/DaAlbrecht/cf-cli@v0.0.0-20231128151943-1fe19bb400b9/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("UpdateApplicationName", func() {
   618  		var (
   619  			newAppName string
   620  			appGUID    string
   621  
   622  			updatedApp resources.Application
   623  			warnings   Warnings
   624  			executeErr error
   625  		)
   626  
   627  		JustBeforeEach(func() {
   628  			newAppName = "some-new-app-name"
   629  			appGUID = "some-app-guid"
   630  
   631  			updatedApp, warnings, executeErr = client.UpdateApplicationName(newAppName, appGUID)
   632  		})
   633  
   634  		When("the application successfully is updated", func() {
   635  			BeforeEach(func() {
   636  				requester.MakeRequestCalls(func(requestParams RequestParams) (JobURL, Warnings, error) {
   637  					requestParams.ResponseBody.(*resources.Application).GUID = appGUID
   638  					requestParams.ResponseBody.(*resources.Application).Name = requestParams.RequestBody.(resources.ApplicationNameOnly).Name
   639  					requestParams.ResponseBody.(*resources.Application).StackName = "some-stack-name"
   640  					requestParams.ResponseBody.(*resources.Application).LifecycleType = constant.AppLifecycleTypeBuildpack
   641  					requestParams.ResponseBody.(*resources.Application).LifecycleBuildpacks = []string{"some-buildpack"}
   642  					requestParams.ResponseBody.(*resources.Application).SpaceGUID = "some-space-guid"
   643  					return "", Warnings{"this is a warning"}, nil
   644  				})
   645  			})
   646  
   647  			It("makes the correct request", func() {
   648  				Expect(requester.MakeRequestCallCount()).To(Equal(1))
   649  				actualParams := requester.MakeRequestArgsForCall(0)
   650  				Expect(actualParams.RequestName).To(Equal(internal.PatchApplicationRequest))
   651  				Expect(actualParams.URIParams).To(Equal(internal.Params{"app_guid": "some-app-guid"}))
   652  				Expect(actualParams.RequestBody).To(Equal(resources.ApplicationNameOnly{Name: newAppName}))
   653  				_, ok := actualParams.ResponseBody.(*resources.Application)
   654  				Expect(ok).To(BeTrue())
   655  			})
   656  
   657  			It("returns the updated app and warnings", func() {
   658  				Expect(executeErr).NotTo(HaveOccurred())
   659  				Expect(warnings).To(ConsistOf("this is a warning"))
   660  
   661  				Expect(updatedApp).To(Equal(resources.Application{
   662  					GUID:                "some-app-guid",
   663  					StackName:           "some-stack-name",
   664  					LifecycleBuildpacks: []string{"some-buildpack"},
   665  					LifecycleType:       constant.AppLifecycleTypeBuildpack,
   666  					Name:                "some-new-app-name",
   667  					SpaceGUID:           "some-space-guid",
   668  				}))
   669  			})
   670  		})
   671  
   672  		When("cc returns back an error or warnings", func() {
   673  			BeforeEach(func() {
   674  				errors := []ccerror.V3Error{
   675  					{
   676  						Code:   10008,
   677  						Detail: "The request is semantically invalid: command presence",
   678  						Title:  "CF-UnprocessableEntity",
   679  					},
   680  					{
   681  						Code:   10010,
   682  						Detail: "App not found",
   683  						Title:  "CF-ResourceNotFound",
   684  					},
   685  				}
   686  
   687  				requester.MakeRequestReturns(
   688  					"",
   689  					Warnings{"this is a warning"},
   690  					ccerror.MultiError{ResponseCode: http.StatusTeapot, Errors: errors},
   691  				)
   692  			})
   693  
   694  			It("returns the error and all warnings", func() {
   695  				Expect(executeErr).To(MatchError(ccerror.MultiError{
   696  					ResponseCode: http.StatusTeapot,
   697  					Errors: []ccerror.V3Error{
   698  						{
   699  							Code:   10008,
   700  							Detail: "The request is semantically invalid: command presence",
   701  							Title:  "CF-UnprocessableEntity",
   702  						},
   703  						{
   704  							Code:   10010,
   705  							Detail: "App not found",
   706  							Title:  "CF-ResourceNotFound",
   707  						},
   708  					},
   709  				}))
   710  				Expect(warnings).To(ConsistOf("this is a warning"))
   711  			})
   712  		})
   713  	})
   714  
   715  	Describe("UpdateApplicationStop", func() {
   716  		var (
   717  			responseApp resources.Application
   718  			warnings    Warnings
   719  			executeErr  error
   720  		)
   721  
   722  		JustBeforeEach(func() {
   723  			responseApp, warnings, executeErr = client.UpdateApplicationStop("some-app-guid")
   724  		})
   725  
   726  		When("the response succeeds", func() {
   727  			BeforeEach(func() {
   728  				requester.MakeRequestCalls(func(requestParams RequestParams) (JobURL, Warnings, error) {
   729  					requestParams.ResponseBody.(*resources.Application).GUID = "some-app-guid"
   730  					requestParams.ResponseBody.(*resources.Application).Name = "some-app"
   731  					requestParams.ResponseBody.(*resources.Application).State = constant.ApplicationStopped
   732  					return "", Warnings{"this is a warning"}, nil
   733  				})
   734  			})
   735  
   736  			It("makes the correct request", func() {
   737  				Expect(requester.MakeRequestCallCount()).To(Equal(1))
   738  				actualParams := requester.MakeRequestArgsForCall(0)
   739  				Expect(actualParams.RequestName).To(Equal(internal.PostApplicationActionStopRequest))
   740  				Expect(actualParams.URIParams).To(Equal(internal.Params{"app_guid": "some-app-guid"}))
   741  				_, ok := actualParams.ResponseBody.(*resources.Application)
   742  				Expect(ok).To(BeTrue())
   743  			})
   744  
   745  			It("returns the application, warnings, and no error", func() {
   746  				Expect(responseApp).To(Equal(resources.Application{
   747  					GUID:  "some-app-guid",
   748  					Name:  "some-app",
   749  					State: constant.ApplicationStopped,
   750  				}))
   751  				Expect(executeErr).ToNot(HaveOccurred())
   752  				Expect(warnings).To(ConsistOf("this is a warning"))
   753  			})
   754  		})
   755  
   756  		When("the CC returns an error", func() {
   757  			BeforeEach(func() {
   758  				errors := []ccerror.V3Error{
   759  					{
   760  						Code:   10008,
   761  						Detail: "The request is semantically invalid: command presence",
   762  						Title:  "CF-UnprocessableEntity",
   763  					},
   764  					{
   765  						Code:   10010,
   766  						Detail: "App not found",
   767  						Title:  "CF-ResourceNotFound",
   768  					},
   769  				}
   770  
   771  				requester.MakeRequestReturns(
   772  					"",
   773  					Warnings{"this is a warning"},
   774  					ccerror.MultiError{ResponseCode: http.StatusTeapot, Errors: errors},
   775  				)
   776  			})
   777  
   778  			It("returns no app, the error and all warnings", func() {
   779  				Expect(executeErr).To(MatchError(ccerror.MultiError{
   780  					ResponseCode: http.StatusTeapot,
   781  					Errors: []ccerror.V3Error{
   782  						{
   783  							Code:   10008,
   784  							Detail: "The request is semantically invalid: command presence",
   785  							Title:  "CF-UnprocessableEntity",
   786  						},
   787  						{
   788  							Code:   10010,
   789  							Detail: "App not found",
   790  							Title:  "CF-ResourceNotFound",
   791  						},
   792  					},
   793  				}))
   794  				Expect(warnings).To(ConsistOf("this is a warning"))
   795  			})
   796  		})
   797  	})
   798  
   799  	Describe("UpdateApplicationStart", func() {
   800  		var (
   801  			responseApp resources.Application
   802  			warnings    Warnings
   803  			executeErr  error
   804  		)
   805  
   806  		JustBeforeEach(func() {
   807  			responseApp, warnings, executeErr = client.UpdateApplicationStart("some-app-guid")
   808  		})
   809  
   810  		When("the response succeeds", func() {
   811  			BeforeEach(func() {
   812  				requester.MakeRequestCalls(func(requestParams RequestParams) (JobURL, Warnings, error) {
   813  					requestParams.ResponseBody.(*resources.Application).GUID = "some-app-guid"
   814  					requestParams.ResponseBody.(*resources.Application).Name = "some-app"
   815  					return "", Warnings{"this is a warning"}, nil
   816  				})
   817  			})
   818  
   819  			It("makes the correct request", func() {
   820  				Expect(requester.MakeRequestCallCount()).To(Equal(1))
   821  				actualParams := requester.MakeRequestArgsForCall(0)
   822  				Expect(actualParams.RequestName).To(Equal(internal.PostApplicationActionStartRequest))
   823  				Expect(actualParams.URIParams).To(Equal(internal.Params{"app_guid": "some-app-guid"}))
   824  				_, ok := actualParams.ResponseBody.(*resources.Application)
   825  				Expect(ok).To(BeTrue())
   826  			})
   827  
   828  			It("returns warnings and no error", func() {
   829  				Expect(executeErr).ToNot(HaveOccurred())
   830  				Expect(warnings).To(ConsistOf("this is a warning"))
   831  				Expect(responseApp).To(Equal(resources.Application{
   832  					GUID: "some-app-guid",
   833  					Name: "some-app",
   834  				}))
   835  			})
   836  		})
   837  
   838  		When("cc returns back an error or warnings", func() {
   839  			BeforeEach(func() {
   840  				errors := []ccerror.V3Error{
   841  					{
   842  						Code:   10008,
   843  						Detail: "The request is semantically invalid: command presence",
   844  						Title:  "CF-UnprocessableEntity",
   845  					},
   846  					{
   847  						Code:   10010,
   848  						Detail: "App not found",
   849  						Title:  "CF-ResourceNotFound",
   850  					},
   851  				}
   852  
   853  				requester.MakeRequestReturns(
   854  					"",
   855  					Warnings{"this is a warning"},
   856  					ccerror.MultiError{ResponseCode: http.StatusTeapot, Errors: errors},
   857  				)
   858  			})
   859  
   860  			It("returns the error and all warnings", func() {
   861  				Expect(executeErr).To(MatchError(ccerror.MultiError{
   862  					ResponseCode: http.StatusTeapot,
   863  					Errors: []ccerror.V3Error{
   864  						{
   865  							Code:   10008,
   866  							Detail: "The request is semantically invalid: command presence",
   867  							Title:  "CF-UnprocessableEntity",
   868  						},
   869  						{
   870  							Code:   10010,
   871  							Detail: "App not found",
   872  							Title:  "CF-ResourceNotFound",
   873  						},
   874  					},
   875  				}))
   876  				Expect(warnings).To(ConsistOf("this is a warning"))
   877  			})
   878  		})
   879  	})
   880  
   881  	Describe("UpdateApplicationRestart", func() {
   882  		var (
   883  			responseApp resources.Application
   884  			warnings    Warnings
   885  			executeErr  error
   886  		)
   887  
   888  		JustBeforeEach(func() {
   889  			responseApp, warnings, executeErr = client.UpdateApplicationRestart("some-app-guid")
   890  		})
   891  
   892  		When("the response succeeds", func() {
   893  			BeforeEach(func() {
   894  				requester.MakeRequestCalls(func(requestParams RequestParams) (JobURL, Warnings, error) {
   895  					requestParams.ResponseBody.(*resources.Application).GUID = "some-app-guid"
   896  					requestParams.ResponseBody.(*resources.Application).Name = "some-app"
   897  					requestParams.ResponseBody.(*resources.Application).State = constant.ApplicationStarted
   898  					return "", Warnings{"this is a warning"}, nil
   899  				})
   900  			})
   901  
   902  			It("makes the correct request", func() {
   903  				Expect(requester.MakeRequestCallCount()).To(Equal(1))
   904  				actualParams := requester.MakeRequestArgsForCall(0)
   905  				Expect(actualParams.RequestName).To(Equal(internal.PostApplicationActionRestartRequest))
   906  				Expect(actualParams.URIParams).To(Equal(internal.Params{"app_guid": "some-app-guid"}))
   907  				_, ok := actualParams.ResponseBody.(*resources.Application)
   908  				Expect(ok).To(BeTrue())
   909  			})
   910  
   911  			It("returns the application, warnings, and no error", func() {
   912  				Expect(responseApp).To(Equal(resources.Application{
   913  					GUID:  "some-app-guid",
   914  					Name:  "some-app",
   915  					State: constant.ApplicationStarted,
   916  				}))
   917  				Expect(executeErr).ToNot(HaveOccurred())
   918  				Expect(warnings).To(ConsistOf("this is a warning"))
   919  			})
   920  		})
   921  
   922  		When("the CC returns an error", func() {
   923  			BeforeEach(func() {
   924  				errors := []ccerror.V3Error{
   925  					{
   926  						Code:   10008,
   927  						Detail: "The request is semantically invalid: command presence",
   928  						Title:  "CF-UnprocessableEntity",
   929  					},
   930  					{
   931  						Code:   10010,
   932  						Detail: "App not found",
   933  						Title:  "CF-ResourceNotFound",
   934  					},
   935  				}
   936  
   937  				requester.MakeRequestReturns(
   938  					"",
   939  					Warnings{"this is a warning"},
   940  					ccerror.MultiError{ResponseCode: http.StatusTeapot, Errors: errors},
   941  				)
   942  			})
   943  
   944  			It("returns no app, the error and all warnings", func() {
   945  				Expect(executeErr).To(MatchError(ccerror.MultiError{
   946  					ResponseCode: http.StatusTeapot,
   947  					Errors: []ccerror.V3Error{
   948  						{
   949  							Code:   10008,
   950  							Detail: "The request is semantically invalid: command presence",
   951  							Title:  "CF-UnprocessableEntity",
   952  						},
   953  						{
   954  							Code:   10010,
   955  							Detail: "App not found",
   956  							Title:  "CF-ResourceNotFound",
   957  						},
   958  					},
   959  				}))
   960  				Expect(warnings).To(ConsistOf("this is a warning"))
   961  			})
   962  		})
   963  	})
   964  })