github.com/arunkumar7540/cli@v6.45.0+incompatible/api/cloudcontroller/ccv3/buildpack_test.go (about)

     1  package ccv3_test
     2  
     3  import (
     4  	"code.cloudfoundry.org/cli/api/cloudcontroller"
     5  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/ccv3fakes"
     6  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/constant"
     7  	"code.cloudfoundry.org/cli/api/cloudcontroller/wrapper"
     8  	"code.cloudfoundry.org/cli/types"
     9  	"errors"
    10  	"fmt"
    11  	"io"
    12  	"io/ioutil"
    13  	"mime/multipart"
    14  	"net/http"
    15  	"strings"
    16  
    17  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccerror"
    18  	. "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3"
    19  	. "github.com/onsi/ginkgo"
    20  	. "github.com/onsi/gomega"
    21  	. "github.com/onsi/gomega/ghttp"
    22  )
    23  
    24  var _ = Describe("Buildpacks", func() {
    25  	var client *Client
    26  
    27  	BeforeEach(func() {
    28  		client, _ = NewTestClient()
    29  	})
    30  
    31  	Describe("GetBuildpacks", func() {
    32  		var (
    33  			query Query
    34  
    35  			buildpacks []Buildpack
    36  			warnings   Warnings
    37  			executeErr error
    38  		)
    39  
    40  		JustBeforeEach(func() {
    41  			buildpacks, warnings, executeErr = client.GetBuildpacks(query)
    42  		})
    43  
    44  		When("buildpacks exist", func() {
    45  			BeforeEach(func() {
    46  				response1 := fmt.Sprintf(`{
    47  					"pagination": {
    48  						"next": {
    49  							"href": "%s/v3/buildpacks?names=some-buildpack-name&page=2&per_page=2"
    50  						}
    51  					},
    52  					"resources": [
    53  						{
    54  							"guid": "guid1",
    55  							"name": "ruby_buildpack",
    56  							"state": "AWAITING_UPLOAD",
    57  							"stack": "windows64",
    58  							"position": 1,
    59  							"enabled": true,
    60  							"locked": false
    61  						},
    62  						{
    63  							"guid": "guid2",
    64  							"name": "staticfile_buildpack",
    65  							"state": "AWAITING_UPLOAD",
    66  							"stack": "cflinuxfs3",
    67  							"position": 2,
    68  							"enabled": false,
    69  							"locked": true
    70  						}
    71  					]
    72  				}`, server.URL())
    73  				response2 := `{
    74  					"pagination": {
    75  						"next": null
    76  					},
    77  					"resources": [
    78  						{
    79  							"guid": "guid3",
    80  							"name": "go_buildpack",
    81  							"state": "AWAITING_UPLOAD",
    82  							"stack": "cflinuxfs2",
    83  							"position": 3,
    84  							"enabled": true,
    85  							"locked": false
    86  						}
    87  					]
    88  				}`
    89  
    90  				server.AppendHandlers(
    91  					CombineHandlers(
    92  						VerifyRequest(http.MethodGet, "/v3/buildpacks", "names=some-buildpack-name"),
    93  						RespondWith(http.StatusOK, response1, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
    94  					),
    95  				)
    96  				server.AppendHandlers(
    97  					CombineHandlers(
    98  						VerifyRequest(http.MethodGet, "/v3/buildpacks", "names=some-buildpack-name&page=2&per_page=2"),
    99  						RespondWith(http.StatusOK, response2, http.Header{"X-Cf-Warnings": {"this is another warning"}}),
   100  					),
   101  				)
   102  
   103  				query = Query{
   104  					Key:    NameFilter,
   105  					Values: []string{"some-buildpack-name"},
   106  				}
   107  			})
   108  
   109  			It("returns the queried buildpacks and all warnings", func() {
   110  				Expect(executeErr).NotTo(HaveOccurred())
   111  
   112  				Expect(buildpacks).To(ConsistOf(
   113  					Buildpack{
   114  						Name:     "ruby_buildpack",
   115  						GUID:     "guid1",
   116  						Position: types.NullInt{Value: 1, IsSet: true},
   117  						Enabled:  types.NullBool{Value: true, IsSet: true},
   118  						Locked:   types.NullBool{Value: false, IsSet: true},
   119  						Stack:    "windows64",
   120  						State:    "AWAITING_UPLOAD",
   121  					},
   122  					Buildpack{
   123  						Name:     "staticfile_buildpack",
   124  						GUID:     "guid2",
   125  						Position: types.NullInt{Value: 2, IsSet: true},
   126  						Enabled:  types.NullBool{Value: false, IsSet: true},
   127  						Locked:   types.NullBool{Value: true, IsSet: true},
   128  						Stack:    "cflinuxfs3",
   129  						State:    "AWAITING_UPLOAD",
   130  					},
   131  					Buildpack{
   132  						Name:     "go_buildpack",
   133  						GUID:     "guid3",
   134  						Position: types.NullInt{Value: 3, IsSet: true},
   135  						Enabled:  types.NullBool{Value: true, IsSet: true},
   136  						Locked:   types.NullBool{Value: false, IsSet: true},
   137  						Stack:    "cflinuxfs2",
   138  						State:    "AWAITING_UPLOAD",
   139  					},
   140  				))
   141  				Expect(warnings).To(ConsistOf("this is a warning", "this is another warning"))
   142  			})
   143  		})
   144  
   145  		When("the cloud controller returns errors and warnings", func() {
   146  			BeforeEach(func() {
   147  				response := `{
   148    "errors": [
   149      {
   150        "code": 10008,
   151        "detail": "The request is semantically invalid: command presence",
   152        "title": "CF-UnprocessableEntity"
   153      },
   154      {
   155        "code": 10010,
   156        "detail": "buildpack not found",
   157        "title": "CF-buildpackNotFound"
   158      }
   159    ]
   160  }`
   161  				server.AppendHandlers(
   162  					CombineHandlers(
   163  						VerifyRequest(http.MethodGet, "/v3/buildpacks"),
   164  						RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   165  					),
   166  				)
   167  			})
   168  
   169  			It("returns the error and all warnings", func() {
   170  				Expect(executeErr).To(MatchError(ccerror.MultiError{
   171  					ResponseCode: http.StatusTeapot,
   172  					Errors: []ccerror.V3Error{
   173  						{
   174  							Code:   10008,
   175  							Detail: "The request is semantically invalid: command presence",
   176  							Title:  "CF-UnprocessableEntity",
   177  						},
   178  						{
   179  							Code:   10010,
   180  							Detail: "buildpack not found",
   181  							Title:  "CF-buildpackNotFound",
   182  						},
   183  					},
   184  				}))
   185  				Expect(warnings).To(ConsistOf("this is a warning"))
   186  			})
   187  		})
   188  	})
   189  
   190  	Describe("CreateBuildpack", func() {
   191  		var (
   192  			inputBuildpack Buildpack
   193  
   194  			bp         Buildpack
   195  			warnings   Warnings
   196  			executeErr error
   197  		)
   198  
   199  		JustBeforeEach(func() {
   200  			bp, warnings, executeErr = client.CreateBuildpack(inputBuildpack)
   201  		})
   202  
   203  		When("the buildpack is successfully created", func() {
   204  			BeforeEach(func() {
   205  				inputBuildpack = Buildpack{
   206  					Name:  "some-buildpack",
   207  					Stack: "some-stack",
   208  				}
   209  				response := `{
   210      				"guid": "some-bp-guid",
   211      				"created_at": "2016-03-18T23:26:46Z",
   212      				"updated_at": "2016-10-17T20:00:42Z",
   213      				"name": "some-buildpack",
   214      				"state": "AWAITING_UPLOAD",
   215      				"filename": null,
   216      				"stack": "some-stack",
   217      				"position": 42,
   218      				"enabled": true,
   219      				"locked": false,
   220      				"links": {
   221      				  "self": {
   222      				    "href": "/v3/buildpacks/some-bp-guid"
   223      				  },
   224  						"upload": {
   225  							"href": "/v3/buildpacks/some-bp-guid/upload",
   226  							"method": "POST"
   227  						}
   228      				}
   229  				}`
   230  
   231  				expectedBody := map[string]interface{}{
   232  					"name":  "some-buildpack",
   233  					"stack": "some-stack",
   234  				}
   235  				server.AppendHandlers(
   236  					CombineHandlers(
   237  						VerifyRequest(http.MethodPost, "/v3/buildpacks"),
   238  						VerifyJSONRepresenting(expectedBody),
   239  						RespondWith(http.StatusCreated, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   240  					),
   241  				)
   242  			})
   243  
   244  			It("returns the created buildpack and warnings", func() {
   245  				Expect(executeErr).NotTo(HaveOccurred())
   246  				Expect(warnings).To(ConsistOf("this is a warning"))
   247  
   248  				expectedBuildpack := Buildpack{
   249  					GUID:     "some-bp-guid",
   250  					Name:     "some-buildpack",
   251  					Stack:    "some-stack",
   252  					Enabled:  types.NullBool{Value: true, IsSet: true},
   253  					Filename: "",
   254  					Locked:   types.NullBool{Value: false, IsSet: true},
   255  					State:    constant.BuildpackAwaitingUpload,
   256  					Position: types.NullInt{Value: 42, IsSet: true},
   257  					Links: APILinks{
   258  						"upload": APILink{
   259  							Method: "POST",
   260  							HREF:   "/v3/buildpacks/some-bp-guid/upload",
   261  						},
   262  						"self": APILink{
   263  							HREF: "/v3/buildpacks/some-bp-guid",
   264  						},
   265  					},
   266  				}
   267  				Expect(bp).To(Equal(expectedBuildpack))
   268  			})
   269  		})
   270  
   271  		When("cc returns back an error or warnings", func() {
   272  			BeforeEach(func() {
   273  				inputBuildpack = Buildpack{}
   274  				response := ` {
   275    "errors": [
   276      {
   277        "code": 10008,
   278        "detail": "The request is semantically invalid: command presence",
   279        "title": "CF-UnprocessableEntity"
   280      },
   281      {
   282        "code": 10010,
   283        "detail": "Buildpack not found",
   284        "title": "CF-ResourceNotFound"
   285      }
   286    ]
   287  }`
   288  				server.AppendHandlers(
   289  					CombineHandlers(
   290  						VerifyRequest(http.MethodPost, "/v3/buildpacks"),
   291  						RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   292  					),
   293  				)
   294  			})
   295  
   296  			It("returns the error and all warnings", func() {
   297  				Expect(executeErr).To(MatchError(ccerror.MultiError{
   298  					ResponseCode: http.StatusTeapot,
   299  					Errors: []ccerror.V3Error{
   300  						{
   301  							Code:   10008,
   302  							Detail: "The request is semantically invalid: command presence",
   303  							Title:  "CF-UnprocessableEntity",
   304  						},
   305  						{
   306  							Code:   10010,
   307  							Detail: "Buildpack not found",
   308  							Title:  "CF-ResourceNotFound",
   309  						},
   310  					},
   311  				}))
   312  				Expect(warnings).To(ConsistOf("this is a warning"))
   313  			})
   314  		})
   315  	})
   316  
   317  	Describe("UploadBuildpack", func() {
   318  		var (
   319  			jobURL     JobURL
   320  			warnings   Warnings
   321  			executeErr error
   322  			bpFile     io.Reader
   323  			bpFilePath string
   324  			bpContent  string
   325  		)
   326  
   327  		BeforeEach(func() {
   328  			bpContent = "some-content"
   329  			bpFile = strings.NewReader(bpContent)
   330  			bpFilePath = "some/fake-buildpack.zip"
   331  		})
   332  
   333  		JustBeforeEach(func() {
   334  			jobURL, warnings, executeErr = client.UploadBuildpack("some-buildpack-guid", bpFilePath, bpFile, int64(len(bpContent)))
   335  		})
   336  
   337  		When("the upload is successful", func() {
   338  			BeforeEach(func() {
   339  				response := `{
   340  										"metadata": {
   341  											"guid": "some-buildpack-guid",
   342  											"url": "/v3/buildpacks/buildpack-guid/upload"
   343  										},
   344  										"entity": {
   345  											"guid": "some-buildpack-guid",
   346  											"status": "queued"
   347  										}
   348  									}`
   349  
   350  				verifyHeaderAndBody := func(_ http.ResponseWriter, req *http.Request) {
   351  					contentType := req.Header.Get("Content-Type")
   352  					Expect(contentType).To(MatchRegexp("multipart/form-data; boundary=[\\w\\d]+"))
   353  
   354  					defer req.Body.Close()
   355  					requestReader := multipart.NewReader(req.Body, contentType[30:])
   356  
   357  					buildpackPart, err := requestReader.NextPart()
   358  					Expect(err).NotTo(HaveOccurred())
   359  
   360  					Expect(buildpackPart.FormName()).To(Equal("bits"))
   361  					Expect(buildpackPart.FileName()).To(Equal("fake-buildpack.zip"))
   362  
   363  					defer buildpackPart.Close()
   364  					partContents, err := ioutil.ReadAll(buildpackPart)
   365  					Expect(err).ToNot(HaveOccurred())
   366  					Expect(string(partContents)).To(Equal(bpContent))
   367  				}
   368  
   369  				server.AppendHandlers(
   370  					CombineHandlers(
   371  						VerifyRequest(http.MethodPost, "/v3/buildpacks/some-buildpack-guid/upload"),
   372  						verifyHeaderAndBody,
   373  						RespondWith(
   374  							http.StatusAccepted,
   375  							response,
   376  							http.Header{
   377  								"X-Cf-Warnings": {"this is a warning"},
   378  								"Location":      {"http://example.com/job-guid"},
   379  							},
   380  						),
   381  					),
   382  				)
   383  			})
   384  
   385  			It("returns the processing job URL and warnings", func() {
   386  				Expect(executeErr).ToNot(HaveOccurred())
   387  				Expect(warnings).To(ConsistOf(Warnings{"this is a warning"}))
   388  				Expect(jobURL).To(Equal(JobURL("http://example.com/job-guid")))
   389  			})
   390  		})
   391  
   392  		When("there is an error reading the buildpack", func() {
   393  			var (
   394  				fakeReader  *ccv3fakes.FakeReader
   395  				expectedErr error
   396  			)
   397  
   398  			BeforeEach(func() {
   399  				expectedErr = errors.New("some read error")
   400  				fakeReader = new(ccv3fakes.FakeReader)
   401  				fakeReader.ReadReturns(0, expectedErr)
   402  				bpFile = fakeReader
   403  
   404  				server.AppendHandlers(
   405  					VerifyRequest(http.MethodPost, "/v3/buildpacks/some-buildpack-guid/upload"),
   406  				)
   407  			})
   408  
   409  			It("returns the error", func() {
   410  				Expect(executeErr).To(MatchError(expectedErr))
   411  			})
   412  		})
   413  
   414  		When("the upload returns an error", func() {
   415  			BeforeEach(func() {
   416  				response := `{
   417  					"errors": [{
   418                          "detail": "The buildpack could not be found: some-buildpack-guid",
   419                          "title": "CF-ResourceNotFound",
   420                          "code": 10010
   421                      }]
   422                  }`
   423  
   424  				server.AppendHandlers(
   425  					CombineHandlers(
   426  						VerifyRequest(http.MethodPost, "/v3/buildpacks/some-buildpack-guid/upload"),
   427  						RespondWith(http.StatusNotFound, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   428  					),
   429  				)
   430  			})
   431  
   432  			It("returns the error and warnings", func() {
   433  				Expect(executeErr).To(MatchError(
   434  					ccerror.ResourceNotFoundError{
   435  						Message: "The buildpack could not be found: some-buildpack-guid",
   436  					},
   437  				))
   438  				Expect(warnings).To(ConsistOf(Warnings{"this is a warning"}))
   439  			})
   440  		})
   441  
   442  		When("a retryable error occurs", func() {
   443  			BeforeEach(func() {
   444  				wrapper := &wrapper.CustomWrapper{
   445  					CustomMake: func(connection cloudcontroller.Connection, request *cloudcontroller.Request, response *cloudcontroller.Response) error {
   446  						defer GinkgoRecover() // Since this will be running in a thread
   447  
   448  						if strings.HasSuffix(request.URL.String(), "/v3/buildpacks/some-buildpack-guid/upload") {
   449  							_, err := ioutil.ReadAll(request.Body)
   450  							Expect(err).ToNot(HaveOccurred())
   451  							Expect(request.Body.Close()).ToNot(HaveOccurred())
   452  							return request.ResetBody()
   453  						}
   454  						return connection.Make(request, response)
   455  					},
   456  				}
   457  
   458  				client, _ = NewTestClient(Config{Wrappers: []ConnectionWrapper{wrapper}})
   459  			})
   460  
   461  			It("returns the PipeSeekError", func() {
   462  				Expect(executeErr).To(MatchError(ccerror.PipeSeekError{}))
   463  			})
   464  		})
   465  
   466  		When("an http error occurs mid-transfer", func() {
   467  			var expectedErr error
   468  
   469  			BeforeEach(func() {
   470  				expectedErr = errors.New("some read error")
   471  
   472  				wrapper := &wrapper.CustomWrapper{
   473  					CustomMake: func(connection cloudcontroller.Connection, request *cloudcontroller.Request, response *cloudcontroller.Response) error {
   474  						defer GinkgoRecover() // Since this will be running in a thread
   475  
   476  						if strings.HasSuffix(request.URL.String(), "/v3/buildpacks/some-buildpack-guid/upload") {
   477  							defer request.Body.Close()
   478  							readBytes, err := ioutil.ReadAll(request.Body)
   479  							Expect(err).ToNot(HaveOccurred())
   480  							Expect(len(readBytes)).To(BeNumerically(">", len(bpContent)))
   481  							return expectedErr
   482  						}
   483  						return connection.Make(request, response)
   484  					},
   485  				}
   486  
   487  				client, _ = NewTestClient(Config{Wrappers: []ConnectionWrapper{wrapper}})
   488  			})
   489  
   490  			It("returns the http error", func() {
   491  				Expect(executeErr).To(MatchError(expectedErr))
   492  			})
   493  		})
   494  	})
   495  
   496  	Describe("UpdateBuildpack", func() {
   497  		var (
   498  			inputBuildpack Buildpack
   499  
   500  			bp         Buildpack
   501  			warnings   Warnings
   502  			executeErr error
   503  		)
   504  
   505  		JustBeforeEach(func() {
   506  			bp, warnings, executeErr = client.UpdateBuildpack(inputBuildpack)
   507  		})
   508  
   509  		When("the buildpack is successfully created", func() {
   510  			BeforeEach(func() {
   511  				inputBuildpack = Buildpack{
   512  					Name:   "some-buildpack",
   513  					GUID:   "some-bp-guid",
   514  					Stack:  "some-stack",
   515  					Locked: types.NullBool{IsSet: true, Value: true},
   516  				}
   517  				response := `{
   518      				"guid": "some-bp-guid",
   519      				"created_at": "2016-03-18T23:26:46Z",
   520      				"updated_at": "2016-10-17T20:00:42Z",
   521      				"name": "some-buildpack",
   522      				"state": "AWAITING_UPLOAD",
   523      				"filename": null,
   524      				"stack": "some-stack",
   525      				"position": 42,
   526      				"enabled": true,
   527      				"locked": true,
   528      				"links": {
   529      				  "self": {
   530      				    "href": "/v3/buildpacks/some-bp-guid"
   531      				  },
   532  						"upload": {
   533  							"href": "/v3/buildpacks/some-bp-guid/upload",
   534  							"method": "POST"
   535  						}
   536      				}
   537  				}`
   538  
   539  				expectedBody := map[string]interface{}{
   540  					"name":   "some-buildpack",
   541  					"stack":  "some-stack",
   542  					"locked": true,
   543  				}
   544  				server.AppendHandlers(
   545  					CombineHandlers(
   546  						VerifyRequest(http.MethodPatch, "/v3/buildpacks/some-bp-guid"),
   547  						VerifyJSONRepresenting(expectedBody),
   548  						RespondWith(http.StatusAccepted, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   549  					),
   550  				)
   551  			})
   552  
   553  			It("returns the created buildpack and warnings", func() {
   554  				Expect(executeErr).NotTo(HaveOccurred())
   555  				Expect(warnings).To(ConsistOf("this is a warning"))
   556  
   557  				expectedBuildpack := Buildpack{
   558  					GUID:     "some-bp-guid",
   559  					Name:     "some-buildpack",
   560  					Stack:    "some-stack",
   561  					Enabled:  types.NullBool{Value: true, IsSet: true},
   562  					Filename: "",
   563  					Locked:   types.NullBool{Value: true, IsSet: true},
   564  					State:    constant.BuildpackAwaitingUpload,
   565  					Position: types.NullInt{Value: 42, IsSet: true},
   566  					Links: APILinks{
   567  						"upload": APILink{
   568  							Method: "POST",
   569  							HREF:   "/v3/buildpacks/some-bp-guid/upload",
   570  						},
   571  						"self": APILink{
   572  							HREF: "/v3/buildpacks/some-bp-guid",
   573  						},
   574  					},
   575  				}
   576  				Expect(bp).To(Equal(expectedBuildpack))
   577  			})
   578  		})
   579  
   580  		When("cc returns back an error or warnings", func() {
   581  			BeforeEach(func() {
   582  				inputBuildpack = Buildpack{
   583  					GUID: "some-bp-guid",
   584  				}
   585  				response := ` {
   586    "errors": [
   587      {
   588        "code": 10008,
   589        "detail": "The request is semantically invalid: command presence",
   590        "title": "CF-UnprocessableEntity"
   591      },
   592      {
   593        "code": 10010,
   594        "detail": "Buildpack not found",
   595        "title": "CF-ResourceNotFound"
   596      }
   597    ]
   598  }`
   599  				server.AppendHandlers(
   600  					CombineHandlers(
   601  						VerifyRequest(http.MethodPatch, "/v3/buildpacks/some-bp-guid"),
   602  						RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   603  					),
   604  				)
   605  			})
   606  
   607  			It("returns the error and all warnings", func() {
   608  				Expect(executeErr).To(MatchError(ccerror.MultiError{
   609  					ResponseCode: http.StatusTeapot,
   610  					Errors: []ccerror.V3Error{
   611  						{
   612  							Code:   10008,
   613  							Detail: "The request is semantically invalid: command presence",
   614  							Title:  "CF-UnprocessableEntity",
   615  						},
   616  						{
   617  							Code:   10010,
   618  							Detail: "Buildpack not found",
   619  							Title:  "CF-ResourceNotFound",
   620  						},
   621  					},
   622  				}))
   623  				Expect(warnings).To(ConsistOf("this is a warning"))
   624  			})
   625  		})
   626  	})
   627  
   628  	Describe("DeleteBuildpacks", func() {
   629  		var (
   630  			buildpackGUID = "some-guid"
   631  
   632  			jobURL     JobURL
   633  			warnings   Warnings
   634  			executeErr error
   635  		)
   636  
   637  		JustBeforeEach(func() {
   638  			jobURL, warnings, executeErr = client.DeleteBuildpack(buildpackGUID)
   639  		})
   640  
   641  		When("buildpacks exist", func() {
   642  			BeforeEach(func() {
   643  				server.AppendHandlers(
   644  					CombineHandlers(
   645  						VerifyRequest(http.MethodDelete, "/v3/buildpacks/"+buildpackGUID),
   646  						RespondWith(http.StatusAccepted, "{}", http.Header{"X-Cf-Warnings": {"this is a warning"}, "Location": {"some-job-url"}}),
   647  					),
   648  				)
   649  			})
   650  
   651  			It("returns the delete job URL and all warnings", func() {
   652  				Expect(executeErr).NotTo(HaveOccurred())
   653  
   654  				Expect(jobURL).To(Equal(JobURL("some-job-url")))
   655  				Expect(warnings).To(ConsistOf("this is a warning"))
   656  			})
   657  		})
   658  
   659  		When("the cloud controller returns errors and warnings", func() {
   660  			BeforeEach(func() {
   661  				response := `{
   662    "errors": [
   663      {
   664        "code": 10008,
   665        "detail": "The request is semantically invalid: command presence",
   666        "title": "CF-UnprocessableEntity"
   667      },
   668      {
   669        "code": 10010,
   670        "detail": "buildpack not found",
   671        "title": "CF-buildpackNotFound"
   672      }
   673    ]
   674  }`
   675  				server.AppendHandlers(
   676  					CombineHandlers(
   677  						VerifyRequest(http.MethodDelete, "/v3/buildpacks/"+buildpackGUID),
   678  						RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   679  					),
   680  				)
   681  			})
   682  
   683  			It("returns the error and all warnings", func() {
   684  				Expect(executeErr).To(MatchError(ccerror.MultiError{
   685  					ResponseCode: http.StatusTeapot,
   686  					Errors: []ccerror.V3Error{
   687  						{
   688  							Code:   10008,
   689  							Detail: "The request is semantically invalid: command presence",
   690  							Title:  "CF-UnprocessableEntity",
   691  						},
   692  						{
   693  							Code:   10010,
   694  							Detail: "buildpack not found",
   695  							Title:  "CF-buildpackNotFound",
   696  						},
   697  					},
   698  				}))
   699  				Expect(warnings).To(ConsistOf("this is a warning"))
   700  			})
   701  		})
   702  	})
   703  })