github.com/jenspinney/cli@v6.42.1-0.20190207184520-7450c600020e+incompatible/api/cloudcontroller/ccv3/application_test.go (about)

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