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

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