github.com/cloudfoundry-community/cloudfoundry-cli@v6.44.1-0.20240130060226-cda5ed8e89a5+incompatible/api/cloudcontroller/ccv3/application_test.go (about)

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