github.com/cloudfoundry-attic/cli-with-i18n@v6.32.1-0.20171002233121-7401370d3b85+incompatible/api/cloudcontroller/ccv3/application_test.go (about)

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