github.com/wanddynosios/cli/v8@v8.7.9-0.20240221182337-1a92e3a7017f/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  					{Key: PerPage, Values: []string{"1"}},
   362  					{Key: Page, Values: []string{"1"}},
   363  				}))
   364  				_, ok := actualParams.ResponseBody.(resources.Application)
   365  				Expect(ok).To(BeTrue())
   366  			})
   367  
   368  			It("returns the queried application and all warnings", func() {
   369  				Expect(executeErr).NotTo(HaveOccurred())
   370  				Expect(warnings).To(ConsistOf("this is a warning"))
   371  				Expect(app).To(Equal(resources.Application{GUID: "app-guid-2"}))
   372  			})
   373  		})
   374  
   375  		When("the application does not exist", func() {
   376  			BeforeEach(func() {
   377  				requester.MakeListRequestReturns(IncludedResources{}, Warnings{"this is a warning"}, nil)
   378  			})
   379  
   380  			It("returns an error and warnings", func() {
   381  				Expect(executeErr).To(MatchError(ccerror.ApplicationNotFoundError{Name: appName}))
   382  				Expect(warnings).To(ConsistOf("this is a warning"))
   383  			})
   384  		})
   385  
   386  		When("the cloud controller returns errors and warnings", func() {
   387  			BeforeEach(func() {
   388  				errors := []ccerror.V3Error{
   389  					{
   390  						Code:   10008,
   391  						Detail: "The request is semantically invalid: command presence",
   392  						Title:  "CF-UnprocessableEntity",
   393  					},
   394  					{
   395  						Code:   10010,
   396  						Detail: "App not found",
   397  						Title:  "CF-ResourceNotFound",
   398  					},
   399  				}
   400  
   401  				requester.MakeListRequestReturns(
   402  					IncludedResources{},
   403  					Warnings{"this is a warning"},
   404  					ccerror.MultiError{ResponseCode: http.StatusTeapot, Errors: errors},
   405  				)
   406  			})
   407  
   408  			It("returns the error and all warnings", func() {
   409  				Expect(executeErr).To(MatchError(ccerror.MultiError{
   410  					ResponseCode: http.StatusTeapot,
   411  					Errors: []ccerror.V3Error{
   412  						{
   413  							Code:   10008,
   414  							Detail: "The request is semantically invalid: command presence",
   415  							Title:  "CF-UnprocessableEntity",
   416  						},
   417  						{
   418  							Code:   10010,
   419  							Detail: "App not found",
   420  							Title:  "CF-ResourceNotFound",
   421  						},
   422  					},
   423  				}))
   424  				Expect(warnings).To(ConsistOf("this is a warning"))
   425  			})
   426  		})
   427  	})
   428  
   429  	Describe("GetApplications", func() {
   430  		var (
   431  			filters []Query
   432  
   433  			apps       []resources.Application
   434  			warnings   Warnings
   435  			executeErr error
   436  		)
   437  
   438  		JustBeforeEach(func() {
   439  			apps, warnings, executeErr = client.GetApplications(filters...)
   440  		})
   441  
   442  		When("applications exist", func() {
   443  			BeforeEach(func() {
   444  				requester.MakeListRequestCalls(func(requestParams RequestParams) (IncludedResources, Warnings, error) {
   445  					err := requestParams.AppendToList(resources.Application{GUID: "app-guid-1"})
   446  					Expect(err).NotTo(HaveOccurred())
   447  					return IncludedResources{}, Warnings{"this is a warning", "this is another warning"}, nil
   448  				})
   449  
   450  				filters = []Query{
   451  					{Key: SpaceGUIDFilter, Values: []string{"some-space-guid"}},
   452  					{Key: NameFilter, Values: []string{"some-app-name"}},
   453  				}
   454  			})
   455  
   456  			It("makes the correct request", func() {
   457  				Expect(requester.MakeListRequestCallCount()).To(Equal(1))
   458  				actualParams := requester.MakeListRequestArgsForCall(0)
   459  				Expect(actualParams.RequestName).To(Equal(internal.GetApplicationsRequest))
   460  				Expect(actualParams.Query).To(Equal(filters))
   461  				_, ok := actualParams.ResponseBody.(resources.Application)
   462  				Expect(ok).To(BeTrue())
   463  			})
   464  
   465  			It("returns the queried applications and all warnings", func() {
   466  				Expect(executeErr).NotTo(HaveOccurred())
   467  				Expect(warnings).To(ConsistOf("this is a warning", "this is another warning"))
   468  
   469  				Expect(apps).To(ConsistOf(resources.Application{GUID: "app-guid-1"}))
   470  			})
   471  		})
   472  
   473  		When("the cloud controller returns errors and warnings", func() {
   474  			BeforeEach(func() {
   475  				errors := []ccerror.V3Error{
   476  					{
   477  						Code:   10008,
   478  						Detail: "The request is semantically invalid: command presence",
   479  						Title:  "CF-UnprocessableEntity",
   480  					},
   481  					{
   482  						Code:   10010,
   483  						Detail: "App not found",
   484  						Title:  "CF-ResourceNotFound",
   485  					},
   486  				}
   487  
   488  				requester.MakeListRequestReturns(
   489  					IncludedResources{},
   490  					Warnings{"this is a warning"},
   491  					ccerror.MultiError{ResponseCode: http.StatusTeapot, Errors: errors},
   492  				)
   493  			})
   494  
   495  			It("returns the error and all warnings", func() {
   496  				Expect(executeErr).To(MatchError(ccerror.MultiError{
   497  					ResponseCode: http.StatusTeapot,
   498  					Errors: []ccerror.V3Error{
   499  						{
   500  							Code:   10008,
   501  							Detail: "The request is semantically invalid: command presence",
   502  							Title:  "CF-UnprocessableEntity",
   503  						},
   504  						{
   505  							Code:   10010,
   506  							Detail: "App not found",
   507  							Title:  "CF-ResourceNotFound",
   508  						},
   509  					},
   510  				}))
   511  				Expect(warnings).To(ConsistOf("this is a warning"))
   512  			})
   513  		})
   514  	})
   515  
   516  	Describe("UpdateApplication", func() {
   517  		var (
   518  			appToUpdate resources.Application
   519  
   520  			updatedApp resources.Application
   521  			warnings   Warnings
   522  			executeErr error
   523  		)
   524  
   525  		JustBeforeEach(func() {
   526  			updatedApp, warnings, executeErr = client.UpdateApplication(appToUpdate)
   527  		})
   528  
   529  		When("the application successfully is updated", func() {
   530  			BeforeEach(func() {
   531  				requester.MakeRequestCalls(func(requestParams RequestParams) (JobURL, Warnings, error) {
   532  					requestParams.ResponseBody.(*resources.Application).GUID = "some-app-guid"
   533  					requestParams.ResponseBody.(*resources.Application).Name = requestParams.RequestBody.(resources.Application).Name
   534  					requestParams.ResponseBody.(*resources.Application).StackName = requestParams.RequestBody.(resources.Application).StackName
   535  					requestParams.ResponseBody.(*resources.Application).LifecycleType = requestParams.RequestBody.(resources.Application).LifecycleType
   536  					requestParams.ResponseBody.(*resources.Application).LifecycleBuildpacks = requestParams.RequestBody.(resources.Application).LifecycleBuildpacks
   537  					requestParams.ResponseBody.(*resources.Application).SpaceGUID = requestParams.RequestBody.(resources.Application).SpaceGUID
   538  					return "", Warnings{"this is a warning"}, nil
   539  				})
   540  
   541  				appToUpdate = resources.Application{
   542  					GUID:                "some-app-guid",
   543  					Name:                "some-app-name",
   544  					StackName:           "some-stack-name",
   545  					LifecycleType:       constant.AppLifecycleTypeBuildpack,
   546  					LifecycleBuildpacks: []string{"some-buildpack"},
   547  					SpaceGUID:           "some-space-guid",
   548  				}
   549  			})
   550  
   551  			It("makes the correct request", func() {
   552  				Expect(requester.MakeRequestCallCount()).To(Equal(1))
   553  				actualParams := requester.MakeRequestArgsForCall(0)
   554  				Expect(actualParams.RequestName).To(Equal(internal.PatchApplicationRequest))
   555  				Expect(actualParams.URIParams).To(Equal(internal.Params{"app_guid": "some-app-guid"}))
   556  				Expect(actualParams.RequestBody).To(Equal(appToUpdate))
   557  				_, ok := actualParams.ResponseBody.(*resources.Application)
   558  				Expect(ok).To(BeTrue())
   559  			})
   560  
   561  			It("returns the updated app and warnings", func() {
   562  				Expect(executeErr).NotTo(HaveOccurred())
   563  				Expect(warnings).To(ConsistOf("this is a warning"))
   564  
   565  				Expect(updatedApp).To(Equal(resources.Application{
   566  					GUID:                "some-app-guid",
   567  					StackName:           "some-stack-name",
   568  					LifecycleBuildpacks: []string{"some-buildpack"},
   569  					LifecycleType:       constant.AppLifecycleTypeBuildpack,
   570  					Name:                "some-app-name",
   571  					SpaceGUID:           "some-space-guid",
   572  				}))
   573  			})
   574  		})
   575  
   576  		When("cc returns back an error or warnings", func() {
   577  			BeforeEach(func() {
   578  				errors := []ccerror.V3Error{
   579  					{
   580  						Code:   10008,
   581  						Detail: "The request is semantically invalid: command presence",
   582  						Title:  "CF-UnprocessableEntity",
   583  					},
   584  					{
   585  						Code:   10010,
   586  						Detail: "App not found",
   587  						Title:  "CF-ResourceNotFound",
   588  					},
   589  				}
   590  
   591  				requester.MakeRequestReturns(
   592  					"",
   593  					Warnings{"this is a warning"},
   594  					ccerror.MultiError{ResponseCode: http.StatusTeapot, Errors: errors},
   595  				)
   596  			})
   597  
   598  			It("returns the error and all warnings", func() {
   599  				Expect(executeErr).To(MatchError(ccerror.MultiError{
   600  					ResponseCode: http.StatusTeapot,
   601  					Errors: []ccerror.V3Error{
   602  						{
   603  							Code:   10008,
   604  							Detail: "The request is semantically invalid: command presence",
   605  							Title:  "CF-UnprocessableEntity",
   606  						},
   607  						{
   608  							Code:   10010,
   609  							Detail: "App not found",
   610  							Title:  "CF-ResourceNotFound",
   611  						},
   612  					},
   613  				}))
   614  				Expect(warnings).To(ConsistOf("this is a warning"))
   615  			})
   616  		})
   617  	})
   618  
   619  	Describe("UpdateApplicationName", func() {
   620  		var (
   621  			newAppName string
   622  			appGUID    string
   623  
   624  			updatedApp resources.Application
   625  			warnings   Warnings
   626  			executeErr error
   627  		)
   628  
   629  		JustBeforeEach(func() {
   630  			newAppName = "some-new-app-name"
   631  			appGUID = "some-app-guid"
   632  
   633  			updatedApp, warnings, executeErr = client.UpdateApplicationName(newAppName, appGUID)
   634  		})
   635  
   636  		When("the application successfully is updated", func() {
   637  			BeforeEach(func() {
   638  				requester.MakeRequestCalls(func(requestParams RequestParams) (JobURL, Warnings, error) {
   639  					requestParams.ResponseBody.(*resources.Application).GUID = appGUID
   640  					requestParams.ResponseBody.(*resources.Application).Name = requestParams.RequestBody.(resources.ApplicationNameOnly).Name
   641  					requestParams.ResponseBody.(*resources.Application).StackName = "some-stack-name"
   642  					requestParams.ResponseBody.(*resources.Application).LifecycleType = constant.AppLifecycleTypeBuildpack
   643  					requestParams.ResponseBody.(*resources.Application).LifecycleBuildpacks = []string{"some-buildpack"}
   644  					requestParams.ResponseBody.(*resources.Application).SpaceGUID = "some-space-guid"
   645  					return "", Warnings{"this is a warning"}, nil
   646  				})
   647  			})
   648  
   649  			It("makes the correct request", func() {
   650  				Expect(requester.MakeRequestCallCount()).To(Equal(1))
   651  				actualParams := requester.MakeRequestArgsForCall(0)
   652  				Expect(actualParams.RequestName).To(Equal(internal.PatchApplicationRequest))
   653  				Expect(actualParams.URIParams).To(Equal(internal.Params{"app_guid": "some-app-guid"}))
   654  				Expect(actualParams.RequestBody).To(Equal(resources.ApplicationNameOnly{Name: newAppName}))
   655  				_, ok := actualParams.ResponseBody.(*resources.Application)
   656  				Expect(ok).To(BeTrue())
   657  			})
   658  
   659  			It("returns the updated app and warnings", func() {
   660  				Expect(executeErr).NotTo(HaveOccurred())
   661  				Expect(warnings).To(ConsistOf("this is a warning"))
   662  
   663  				Expect(updatedApp).To(Equal(resources.Application{
   664  					GUID:                "some-app-guid",
   665  					StackName:           "some-stack-name",
   666  					LifecycleBuildpacks: []string{"some-buildpack"},
   667  					LifecycleType:       constant.AppLifecycleTypeBuildpack,
   668  					Name:                "some-new-app-name",
   669  					SpaceGUID:           "some-space-guid",
   670  				}))
   671  			})
   672  		})
   673  
   674  		When("cc returns back an error or warnings", func() {
   675  			BeforeEach(func() {
   676  				errors := []ccerror.V3Error{
   677  					{
   678  						Code:   10008,
   679  						Detail: "The request is semantically invalid: command presence",
   680  						Title:  "CF-UnprocessableEntity",
   681  					},
   682  					{
   683  						Code:   10010,
   684  						Detail: "App not found",
   685  						Title:  "CF-ResourceNotFound",
   686  					},
   687  				}
   688  
   689  				requester.MakeRequestReturns(
   690  					"",
   691  					Warnings{"this is a warning"},
   692  					ccerror.MultiError{ResponseCode: http.StatusTeapot, Errors: errors},
   693  				)
   694  			})
   695  
   696  			It("returns the error and all warnings", func() {
   697  				Expect(executeErr).To(MatchError(ccerror.MultiError{
   698  					ResponseCode: http.StatusTeapot,
   699  					Errors: []ccerror.V3Error{
   700  						{
   701  							Code:   10008,
   702  							Detail: "The request is semantically invalid: command presence",
   703  							Title:  "CF-UnprocessableEntity",
   704  						},
   705  						{
   706  							Code:   10010,
   707  							Detail: "App not found",
   708  							Title:  "CF-ResourceNotFound",
   709  						},
   710  					},
   711  				}))
   712  				Expect(warnings).To(ConsistOf("this is a warning"))
   713  			})
   714  		})
   715  	})
   716  
   717  	Describe("UpdateApplicationStop", func() {
   718  		var (
   719  			responseApp resources.Application
   720  			warnings    Warnings
   721  			executeErr  error
   722  		)
   723  
   724  		JustBeforeEach(func() {
   725  			responseApp, warnings, executeErr = client.UpdateApplicationStop("some-app-guid")
   726  		})
   727  
   728  		When("the response succeeds", func() {
   729  			BeforeEach(func() {
   730  				requester.MakeRequestCalls(func(requestParams RequestParams) (JobURL, Warnings, error) {
   731  					requestParams.ResponseBody.(*resources.Application).GUID = "some-app-guid"
   732  					requestParams.ResponseBody.(*resources.Application).Name = "some-app"
   733  					requestParams.ResponseBody.(*resources.Application).State = constant.ApplicationStopped
   734  					return "", Warnings{"this is a warning"}, nil
   735  				})
   736  			})
   737  
   738  			It("makes the correct request", func() {
   739  				Expect(requester.MakeRequestCallCount()).To(Equal(1))
   740  				actualParams := requester.MakeRequestArgsForCall(0)
   741  				Expect(actualParams.RequestName).To(Equal(internal.PostApplicationActionStopRequest))
   742  				Expect(actualParams.URIParams).To(Equal(internal.Params{"app_guid": "some-app-guid"}))
   743  				_, ok := actualParams.ResponseBody.(*resources.Application)
   744  				Expect(ok).To(BeTrue())
   745  			})
   746  
   747  			It("returns the application, warnings, and no error", func() {
   748  				Expect(responseApp).To(Equal(resources.Application{
   749  					GUID:  "some-app-guid",
   750  					Name:  "some-app",
   751  					State: constant.ApplicationStopped,
   752  				}))
   753  				Expect(executeErr).ToNot(HaveOccurred())
   754  				Expect(warnings).To(ConsistOf("this is a warning"))
   755  			})
   756  		})
   757  
   758  		When("the CC returns an error", func() {
   759  			BeforeEach(func() {
   760  				errors := []ccerror.V3Error{
   761  					{
   762  						Code:   10008,
   763  						Detail: "The request is semantically invalid: command presence",
   764  						Title:  "CF-UnprocessableEntity",
   765  					},
   766  					{
   767  						Code:   10010,
   768  						Detail: "App not found",
   769  						Title:  "CF-ResourceNotFound",
   770  					},
   771  				}
   772  
   773  				requester.MakeRequestReturns(
   774  					"",
   775  					Warnings{"this is a warning"},
   776  					ccerror.MultiError{ResponseCode: http.StatusTeapot, Errors: errors},
   777  				)
   778  			})
   779  
   780  			It("returns no app, the error and all warnings", func() {
   781  				Expect(executeErr).To(MatchError(ccerror.MultiError{
   782  					ResponseCode: http.StatusTeapot,
   783  					Errors: []ccerror.V3Error{
   784  						{
   785  							Code:   10008,
   786  							Detail: "The request is semantically invalid: command presence",
   787  							Title:  "CF-UnprocessableEntity",
   788  						},
   789  						{
   790  							Code:   10010,
   791  							Detail: "App not found",
   792  							Title:  "CF-ResourceNotFound",
   793  						},
   794  					},
   795  				}))
   796  				Expect(warnings).To(ConsistOf("this is a warning"))
   797  			})
   798  		})
   799  	})
   800  
   801  	Describe("UpdateApplicationStart", func() {
   802  		var (
   803  			responseApp resources.Application
   804  			warnings    Warnings
   805  			executeErr  error
   806  		)
   807  
   808  		JustBeforeEach(func() {
   809  			responseApp, warnings, executeErr = client.UpdateApplicationStart("some-app-guid")
   810  		})
   811  
   812  		When("the response succeeds", func() {
   813  			BeforeEach(func() {
   814  				requester.MakeRequestCalls(func(requestParams RequestParams) (JobURL, Warnings, error) {
   815  					requestParams.ResponseBody.(*resources.Application).GUID = "some-app-guid"
   816  					requestParams.ResponseBody.(*resources.Application).Name = "some-app"
   817  					return "", Warnings{"this is a warning"}, nil
   818  				})
   819  			})
   820  
   821  			It("makes the correct request", func() {
   822  				Expect(requester.MakeRequestCallCount()).To(Equal(1))
   823  				actualParams := requester.MakeRequestArgsForCall(0)
   824  				Expect(actualParams.RequestName).To(Equal(internal.PostApplicationActionStartRequest))
   825  				Expect(actualParams.URIParams).To(Equal(internal.Params{"app_guid": "some-app-guid"}))
   826  				_, ok := actualParams.ResponseBody.(*resources.Application)
   827  				Expect(ok).To(BeTrue())
   828  			})
   829  
   830  			It("returns warnings and no error", func() {
   831  				Expect(executeErr).ToNot(HaveOccurred())
   832  				Expect(warnings).To(ConsistOf("this is a warning"))
   833  				Expect(responseApp).To(Equal(resources.Application{
   834  					GUID: "some-app-guid",
   835  					Name: "some-app",
   836  				}))
   837  			})
   838  		})
   839  
   840  		When("cc returns back an error or warnings", func() {
   841  			BeforeEach(func() {
   842  				errors := []ccerror.V3Error{
   843  					{
   844  						Code:   10008,
   845  						Detail: "The request is semantically invalid: command presence",
   846  						Title:  "CF-UnprocessableEntity",
   847  					},
   848  					{
   849  						Code:   10010,
   850  						Detail: "App not found",
   851  						Title:  "CF-ResourceNotFound",
   852  					},
   853  				}
   854  
   855  				requester.MakeRequestReturns(
   856  					"",
   857  					Warnings{"this is a warning"},
   858  					ccerror.MultiError{ResponseCode: http.StatusTeapot, Errors: errors},
   859  				)
   860  			})
   861  
   862  			It("returns the error and all warnings", func() {
   863  				Expect(executeErr).To(MatchError(ccerror.MultiError{
   864  					ResponseCode: http.StatusTeapot,
   865  					Errors: []ccerror.V3Error{
   866  						{
   867  							Code:   10008,
   868  							Detail: "The request is semantically invalid: command presence",
   869  							Title:  "CF-UnprocessableEntity",
   870  						},
   871  						{
   872  							Code:   10010,
   873  							Detail: "App not found",
   874  							Title:  "CF-ResourceNotFound",
   875  						},
   876  					},
   877  				}))
   878  				Expect(warnings).To(ConsistOf("this is a warning"))
   879  			})
   880  		})
   881  	})
   882  
   883  	Describe("UpdateApplicationRestart", func() {
   884  		var (
   885  			responseApp resources.Application
   886  			warnings    Warnings
   887  			executeErr  error
   888  		)
   889  
   890  		JustBeforeEach(func() {
   891  			responseApp, warnings, executeErr = client.UpdateApplicationRestart("some-app-guid")
   892  		})
   893  
   894  		When("the response succeeds", func() {
   895  			BeforeEach(func() {
   896  				requester.MakeRequestCalls(func(requestParams RequestParams) (JobURL, Warnings, error) {
   897  					requestParams.ResponseBody.(*resources.Application).GUID = "some-app-guid"
   898  					requestParams.ResponseBody.(*resources.Application).Name = "some-app"
   899  					requestParams.ResponseBody.(*resources.Application).State = constant.ApplicationStarted
   900  					return "", Warnings{"this is a warning"}, nil
   901  				})
   902  			})
   903  
   904  			It("makes the correct request", func() {
   905  				Expect(requester.MakeRequestCallCount()).To(Equal(1))
   906  				actualParams := requester.MakeRequestArgsForCall(0)
   907  				Expect(actualParams.RequestName).To(Equal(internal.PostApplicationActionRestartRequest))
   908  				Expect(actualParams.URIParams).To(Equal(internal.Params{"app_guid": "some-app-guid"}))
   909  				_, ok := actualParams.ResponseBody.(*resources.Application)
   910  				Expect(ok).To(BeTrue())
   911  			})
   912  
   913  			It("returns the application, warnings, and no error", func() {
   914  				Expect(responseApp).To(Equal(resources.Application{
   915  					GUID:  "some-app-guid",
   916  					Name:  "some-app",
   917  					State: constant.ApplicationStarted,
   918  				}))
   919  				Expect(executeErr).ToNot(HaveOccurred())
   920  				Expect(warnings).To(ConsistOf("this is a warning"))
   921  			})
   922  		})
   923  
   924  		When("the CC returns an error", func() {
   925  			BeforeEach(func() {
   926  				errors := []ccerror.V3Error{
   927  					{
   928  						Code:   10008,
   929  						Detail: "The request is semantically invalid: command presence",
   930  						Title:  "CF-UnprocessableEntity",
   931  					},
   932  					{
   933  						Code:   10010,
   934  						Detail: "App not found",
   935  						Title:  "CF-ResourceNotFound",
   936  					},
   937  				}
   938  
   939  				requester.MakeRequestReturns(
   940  					"",
   941  					Warnings{"this is a warning"},
   942  					ccerror.MultiError{ResponseCode: http.StatusTeapot, Errors: errors},
   943  				)
   944  			})
   945  
   946  			It("returns no app, the error and all warnings", func() {
   947  				Expect(executeErr).To(MatchError(ccerror.MultiError{
   948  					ResponseCode: http.StatusTeapot,
   949  					Errors: []ccerror.V3Error{
   950  						{
   951  							Code:   10008,
   952  							Detail: "The request is semantically invalid: command presence",
   953  							Title:  "CF-UnprocessableEntity",
   954  						},
   955  						{
   956  							Code:   10010,
   957  							Detail: "App not found",
   958  							Title:  "CF-ResourceNotFound",
   959  						},
   960  					},
   961  				}))
   962  				Expect(warnings).To(ConsistOf("this is a warning"))
   963  			})
   964  		})
   965  	})
   966  })