github.com/franc20/ayesa_sap@v7.0.0-beta.28.0.20200124003224-302d4d52fa6c+incompatible/api/cloudcontroller/ccv3/buildpack_test.go (about)

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