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

     1  package ccv3_test
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"errors"
     7  	"io"
     8  	"io/ioutil"
     9  	"mime/multipart"
    10  	"net/http"
    11  	"os"
    12  	"strings"
    13  
    14  	"code.cloudfoundry.org/cli/api/cloudcontroller"
    15  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccerror"
    16  	. "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3"
    17  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/ccv3fakes"
    18  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/constant"
    19  	"code.cloudfoundry.org/cli/api/cloudcontroller/wrapper"
    20  	. "github.com/onsi/ginkgo"
    21  	. "github.com/onsi/gomega"
    22  	. "github.com/onsi/gomega/gbytes"
    23  	. "github.com/onsi/gomega/ghttp"
    24  )
    25  
    26  var _ = Describe("Package", func() {
    27  	var client *Client
    28  
    29  	BeforeEach(func() {
    30  		client, _ = NewTestClient()
    31  	})
    32  
    33  	Describe("CreatePackage", func() {
    34  		var (
    35  			inputPackage Package
    36  
    37  			pkg        Package
    38  			warnings   Warnings
    39  			executeErr error
    40  		)
    41  
    42  		JustBeforeEach(func() {
    43  			pkg, warnings, executeErr = client.CreatePackage(inputPackage)
    44  		})
    45  
    46  		When("the package successfully is created", func() {
    47  			When("creating a docker package", func() {
    48  				BeforeEach(func() {
    49  					inputPackage = Package{
    50  						Type: constant.PackageTypeDocker,
    51  						Relationships: Relationships{
    52  							constant.RelationshipTypeApplication: Relationship{GUID: "some-app-guid"},
    53  						},
    54  						DockerImage:    "some-docker-image",
    55  						DockerUsername: "some-username",
    56  						DockerPassword: "some-password",
    57  					}
    58  
    59  					response := `{
    60  					"data": {
    61  						"image": "some-docker-image",
    62  						"username": "some-username",
    63  						"password": "some-password"
    64  					},
    65  					"guid": "some-pkg-guid",
    66  					"type": "docker",
    67  					"state": "PROCESSING_UPLOAD",
    68  					"links": {
    69  						"upload": {
    70  							"href": "some-package-upload-url",
    71  							"method": "POST"
    72  						}
    73  					}
    74  				}`
    75  
    76  					expectedBody := map[string]interface{}{
    77  						"type": "docker",
    78  						"data": map[string]string{
    79  							"image":    "some-docker-image",
    80  							"username": "some-username",
    81  							"password": "some-password",
    82  						},
    83  						"relationships": map[string]interface{}{
    84  							"app": map[string]interface{}{
    85  								"data": map[string]string{
    86  									"guid": "some-app-guid",
    87  								},
    88  							},
    89  						},
    90  					}
    91  					server.AppendHandlers(
    92  						CombineHandlers(
    93  							VerifyRequest(http.MethodPost, "/v3/packages"),
    94  							VerifyJSONRepresenting(expectedBody),
    95  							RespondWith(http.StatusCreated, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
    96  						),
    97  					)
    98  				})
    99  
   100  				It("returns the created package and warnings", func() {
   101  					Expect(executeErr).NotTo(HaveOccurred())
   102  					Expect(warnings).To(ConsistOf("this is a warning"))
   103  
   104  					expectedPackage := Package{
   105  						GUID:  "some-pkg-guid",
   106  						Type:  constant.PackageTypeDocker,
   107  						State: constant.PackageProcessingUpload,
   108  						Links: map[string]APILink{
   109  							"upload": APILink{HREF: "some-package-upload-url", Method: http.MethodPost},
   110  						},
   111  						DockerImage:    "some-docker-image",
   112  						DockerUsername: "some-username",
   113  						DockerPassword: "some-password",
   114  					}
   115  					Expect(pkg).To(Equal(expectedPackage))
   116  				})
   117  			})
   118  
   119  			When("creating a bits package", func() {
   120  				BeforeEach(func() {
   121  					inputPackage = Package{
   122  						Type: constant.PackageTypeBits,
   123  						Relationships: Relationships{
   124  							constant.RelationshipTypeApplication: Relationship{GUID: "some-app-guid"},
   125  						},
   126  					}
   127  					response := `{
   128  					"guid": "some-pkg-guid",
   129  					"type": "bits",
   130  					"state": "PROCESSING_UPLOAD",
   131  					"links": {
   132  						"upload": {
   133  							"href": "some-package-upload-url",
   134  							"method": "POST"
   135  						}
   136  					}
   137  				}`
   138  
   139  					expectedBody := map[string]interface{}{
   140  						"type": "bits",
   141  						"relationships": map[string]interface{}{
   142  							"app": map[string]interface{}{
   143  								"data": map[string]string{
   144  									"guid": "some-app-guid",
   145  								},
   146  							},
   147  						},
   148  					}
   149  					server.AppendHandlers(
   150  						CombineHandlers(
   151  							VerifyRequest(http.MethodPost, "/v3/packages"),
   152  							VerifyJSONRepresenting(expectedBody),
   153  							RespondWith(http.StatusCreated, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   154  						),
   155  					)
   156  				})
   157  
   158  				It("omits data, and returns the created package and warnings", func() {
   159  					Expect(executeErr).NotTo(HaveOccurred())
   160  					Expect(warnings).To(ConsistOf("this is a warning"))
   161  
   162  					expectedPackage := Package{
   163  						GUID:  "some-pkg-guid",
   164  						Type:  constant.PackageTypeBits,
   165  						State: constant.PackageProcessingUpload,
   166  						Links: map[string]APILink{
   167  							"upload": APILink{HREF: "some-package-upload-url", Method: http.MethodPost},
   168  						},
   169  					}
   170  					Expect(pkg).To(Equal(expectedPackage))
   171  				})
   172  			})
   173  		})
   174  
   175  		When("cc returns back an error or warnings", func() {
   176  			BeforeEach(func() {
   177  				inputPackage = Package{}
   178  				response := ` {
   179    "errors": [
   180      {
   181        "code": 10008,
   182        "detail": "The request is semantically invalid: command presence",
   183        "title": "CF-UnprocessableEntity"
   184      },
   185      {
   186        "code": 10010,
   187        "detail": "Package not found",
   188        "title": "CF-ResourceNotFound"
   189      }
   190    ]
   191  }`
   192  				server.AppendHandlers(
   193  					CombineHandlers(
   194  						VerifyRequest(http.MethodPost, "/v3/packages"),
   195  						RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   196  					),
   197  				)
   198  			})
   199  
   200  			It("returns the error and all warnings", func() {
   201  				Expect(executeErr).To(MatchError(ccerror.MultiError{
   202  					ResponseCode: http.StatusTeapot,
   203  					Errors: []ccerror.V3Error{
   204  						{
   205  							Code:   10008,
   206  							Detail: "The request is semantically invalid: command presence",
   207  							Title:  "CF-UnprocessableEntity",
   208  						},
   209  						{
   210  							Code:   10010,
   211  							Detail: "Package not found",
   212  							Title:  "CF-ResourceNotFound",
   213  						},
   214  					},
   215  				}))
   216  				Expect(warnings).To(ConsistOf("this is a warning"))
   217  			})
   218  		})
   219  	})
   220  
   221  	Describe("GetPackage", func() {
   222  		var (
   223  			pkg        Package
   224  			warnings   Warnings
   225  			executeErr error
   226  		)
   227  
   228  		JustBeforeEach(func() {
   229  			pkg, warnings, executeErr = client.GetPackage("some-pkg-guid")
   230  		})
   231  
   232  		When("the package exists", func() {
   233  			BeforeEach(func() {
   234  				response := `{
   235    "guid": "some-pkg-guid",
   236    "state": "PROCESSING_UPLOAD",
   237  	"links": {
   238      "upload": {
   239        "href": "some-package-upload-url",
   240        "method": "POST"
   241      }
   242  	}
   243  }`
   244  				server.AppendHandlers(
   245  					CombineHandlers(
   246  						VerifyRequest(http.MethodGet, "/v3/packages/some-pkg-guid"),
   247  						RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   248  					),
   249  				)
   250  			})
   251  
   252  			It("returns the queried package and all warnings", func() {
   253  				Expect(executeErr).NotTo(HaveOccurred())
   254  
   255  				expectedPackage := Package{
   256  					GUID:  "some-pkg-guid",
   257  					State: constant.PackageProcessingUpload,
   258  					Links: map[string]APILink{
   259  						"upload": APILink{HREF: "some-package-upload-url", Method: http.MethodPost},
   260  					},
   261  				}
   262  				Expect(pkg).To(Equal(expectedPackage))
   263  				Expect(warnings).To(ConsistOf("this is a warning"))
   264  			})
   265  		})
   266  
   267  		When("the cloud controller returns errors and warnings", func() {
   268  			BeforeEach(func() {
   269  				response := `{
   270    "errors": [
   271      {
   272        "code": 10008,
   273        "detail": "The request is semantically invalid: command presence",
   274        "title": "CF-UnprocessableEntity"
   275      },
   276      {
   277        "code": 10010,
   278        "detail": "Package not found",
   279        "title": "CF-ResourceNotFound"
   280      }
   281    ]
   282  }`
   283  				server.AppendHandlers(
   284  					CombineHandlers(
   285  						VerifyRequest(http.MethodGet, "/v3/packages/some-pkg-guid"),
   286  						RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   287  					),
   288  				)
   289  			})
   290  
   291  			It("returns the error and all warnings", func() {
   292  				Expect(executeErr).To(MatchError(ccerror.MultiError{
   293  					ResponseCode: http.StatusTeapot,
   294  					Errors: []ccerror.V3Error{
   295  						{
   296  							Code:   10008,
   297  							Detail: "The request is semantically invalid: command presence",
   298  							Title:  "CF-UnprocessableEntity",
   299  						},
   300  						{
   301  							Code:   10010,
   302  							Detail: "Package not found",
   303  							Title:  "CF-ResourceNotFound",
   304  						},
   305  					},
   306  				}))
   307  				Expect(warnings).To(ConsistOf("this is a warning"))
   308  			})
   309  		})
   310  	})
   311  
   312  	Describe("GetPackages", func() {
   313  		var (
   314  			pkgs       []Package
   315  			warnings   Warnings
   316  			executeErr error
   317  		)
   318  
   319  		JustBeforeEach(func() {
   320  			pkgs, warnings, executeErr = client.GetPackages(Query{Key: AppGUIDFilter, Values: []string{"some-app-guid"}})
   321  		})
   322  
   323  		When("cloud controller returns list of packages", func() {
   324  			BeforeEach(func() {
   325  				response := `{
   326  					"resources": [
   327  					  {
   328  						  "guid": "some-pkg-guid-1",
   329  							"type": "bits",
   330  						  "state": "PROCESSING_UPLOAD",
   331  							"created_at": "2017-08-14T21:16:12Z",
   332  							"links": {
   333  								"upload": {
   334  									"href": "some-pkg-upload-url-1",
   335  									"method": "POST"
   336  								}
   337  							}
   338  					  },
   339  					  {
   340  						  "guid": "some-pkg-guid-2",
   341  							"type": "bits",
   342  						  "state": "READY",
   343  							"created_at": "2017-08-14T21:20:13Z",
   344  							"links": {
   345  								"upload": {
   346  									"href": "some-pkg-upload-url-2",
   347  									"method": "POST"
   348  								}
   349  							}
   350  					  }
   351  					]
   352  				}`
   353  				server.AppendHandlers(
   354  					CombineHandlers(
   355  						VerifyRequest(http.MethodGet, "/v3/packages", "app_guids=some-app-guid"),
   356  						RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   357  					),
   358  				)
   359  			})
   360  
   361  			It("returns the queried packages and all warnings", func() {
   362  				Expect(executeErr).NotTo(HaveOccurred())
   363  
   364  				Expect(pkgs).To(Equal([]Package{
   365  					{
   366  						GUID:      "some-pkg-guid-1",
   367  						Type:      constant.PackageTypeBits,
   368  						State:     constant.PackageProcessingUpload,
   369  						CreatedAt: "2017-08-14T21:16:12Z",
   370  						Links: map[string]APILink{
   371  							"upload": APILink{HREF: "some-pkg-upload-url-1", Method: http.MethodPost},
   372  						},
   373  					},
   374  					{
   375  						GUID:      "some-pkg-guid-2",
   376  						Type:      constant.PackageTypeBits,
   377  						State:     constant.PackageReady,
   378  						CreatedAt: "2017-08-14T21:20:13Z",
   379  						Links: map[string]APILink{
   380  							"upload": APILink{HREF: "some-pkg-upload-url-2", Method: http.MethodPost},
   381  						},
   382  					},
   383  				}))
   384  				Expect(warnings).To(ConsistOf("this is a warning"))
   385  			})
   386  		})
   387  
   388  		When("the cloud controller returns errors and warnings", func() {
   389  			BeforeEach(func() {
   390  				response := `{
   391  					"errors": [
   392  						{
   393  							"code": 10008,
   394  							"detail": "The request is semantically invalid: command presence",
   395  							"title": "CF-UnprocessableEntity"
   396  						},
   397  						{
   398  							"code": 10010,
   399  							"detail": "Package not found",
   400  							"title": "CF-ResourceNotFound"
   401  						}
   402  					]
   403  				}`
   404  				server.AppendHandlers(
   405  					CombineHandlers(
   406  						VerifyRequest(http.MethodGet, "/v3/packages", "app_guids=some-app-guid"),
   407  						RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   408  					),
   409  				)
   410  			})
   411  
   412  			It("returns the error and all warnings", func() {
   413  				Expect(executeErr).To(MatchError(ccerror.MultiError{
   414  					ResponseCode: http.StatusTeapot,
   415  					Errors: []ccerror.V3Error{
   416  						{
   417  							Code:   10008,
   418  							Detail: "The request is semantically invalid: command presence",
   419  							Title:  "CF-UnprocessableEntity",
   420  						},
   421  						{
   422  							Code:   10010,
   423  							Detail: "Package not found",
   424  							Title:  "CF-ResourceNotFound",
   425  						},
   426  					},
   427  				}))
   428  				Expect(warnings).To(ConsistOf("this is a warning"))
   429  			})
   430  		})
   431  	})
   432  
   433  	Describe("UploadBitsPackage", func() {
   434  		var (
   435  			inputPackage Package
   436  		)
   437  
   438  		BeforeEach(func() {
   439  			client, _ = NewTestClient()
   440  
   441  			inputPackage = Package{
   442  				GUID: "package-guid",
   443  			}
   444  		})
   445  
   446  		When("the upload is successful", func() {
   447  			var (
   448  				resources           []Resource
   449  				readerBody          []byte
   450  				verifyHeaderAndBody func(http.ResponseWriter, *http.Request)
   451  			)
   452  
   453  			BeforeEach(func() {
   454  				resources = []Resource{
   455  					{FilePath: "foo"},
   456  					{FilePath: "bar"},
   457  				}
   458  
   459  				response := `{
   460  						"guid": "some-package-guid",
   461  						"type": "bits",
   462  						"state": "PROCESSING_UPLOAD"
   463  					}`
   464  
   465  				server.AppendHandlers(
   466  					CombineHandlers(
   467  						VerifyRequest(http.MethodPost, "/v3/packages/package-guid/upload"),
   468  						func(writer http.ResponseWriter, req *http.Request) {
   469  							verifyHeaderAndBody(writer, req)
   470  						},
   471  						RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   472  					),
   473  				)
   474  			})
   475  
   476  			When("the upload has application bits to upload", func() {
   477  				var reader io.Reader
   478  
   479  				BeforeEach(func() {
   480  					readerBody = []byte("hello world")
   481  					reader = bytes.NewReader(readerBody)
   482  
   483  					verifyHeaderAndBody = func(_ http.ResponseWriter, req *http.Request) {
   484  						contentType := req.Header.Get("Content-Type")
   485  						Expect(contentType).To(MatchRegexp("multipart/form-data; boundary=[\\w\\d]+"))
   486  
   487  						defer req.Body.Close()
   488  						requestReader := multipart.NewReader(req.Body, contentType[30:])
   489  
   490  						// Verify that matched resources are sent properly
   491  						resourcesPart, err := requestReader.NextPart()
   492  						Expect(err).NotTo(HaveOccurred())
   493  
   494  						Expect(resourcesPart.FormName()).To(Equal("resources"))
   495  
   496  						defer resourcesPart.Close()
   497  						expectedJSON, err := json.Marshal(resources)
   498  						Expect(err).NotTo(HaveOccurred())
   499  						Expect(ioutil.ReadAll(resourcesPart)).To(MatchJSON(expectedJSON))
   500  
   501  						// Verify that the application bits are sent properly
   502  						resourcesPart, err = requestReader.NextPart()
   503  						Expect(err).NotTo(HaveOccurred())
   504  
   505  						Expect(resourcesPart.FormName()).To(Equal("bits"))
   506  						Expect(resourcesPart.FileName()).To(Equal("package.zip"))
   507  
   508  						defer resourcesPart.Close()
   509  						Expect(ioutil.ReadAll(resourcesPart)).To(Equal(readerBody))
   510  					}
   511  				})
   512  
   513  				It("returns the created job and warnings", func() {
   514  					pkg, warnings, err := client.UploadBitsPackage(inputPackage, resources, reader, int64(len(readerBody)))
   515  					Expect(err).NotTo(HaveOccurred())
   516  					Expect(warnings).To(ConsistOf("this is a warning"))
   517  					Expect(pkg).To(Equal(Package{
   518  						GUID:  "some-package-guid",
   519  						Type:  constant.PackageTypeBits,
   520  						State: constant.PackageProcessingUpload,
   521  					}))
   522  				})
   523  			})
   524  
   525  			When("there are no application bits to upload", func() {
   526  				BeforeEach(func() {
   527  					verifyHeaderAndBody = func(_ http.ResponseWriter, req *http.Request) {
   528  						contentType := req.Header.Get("Content-Type")
   529  						Expect(contentType).To(MatchRegexp("multipart/form-data; boundary=[\\w\\d]+"))
   530  
   531  						defer req.Body.Close()
   532  						requestReader := multipart.NewReader(req.Body, contentType[30:])
   533  
   534  						// Verify that matched resources are sent properly
   535  						resourcesPart, err := requestReader.NextPart()
   536  						Expect(err).NotTo(HaveOccurred())
   537  
   538  						Expect(resourcesPart.FormName()).To(Equal("resources"))
   539  
   540  						defer resourcesPart.Close()
   541  						expectedJSON, err := json.Marshal(resources)
   542  						Expect(err).NotTo(HaveOccurred())
   543  						Expect(ioutil.ReadAll(resourcesPart)).To(MatchJSON(expectedJSON))
   544  
   545  						// Verify that the application bits are not sent
   546  						_, err = requestReader.NextPart()
   547  						Expect(err).To(MatchError(io.EOF))
   548  					}
   549  				})
   550  
   551  				It("does not send the application bits", func() {
   552  					pkg, warnings, err := client.UploadBitsPackage(inputPackage, resources, nil, 33513531353)
   553  					Expect(err).NotTo(HaveOccurred())
   554  					Expect(warnings).To(ConsistOf("this is a warning"))
   555  					Expect(pkg).To(Equal(Package{
   556  						GUID:  "some-package-guid",
   557  						Type:  constant.PackageTypeBits,
   558  						State: constant.PackageProcessingUpload,
   559  					}))
   560  				})
   561  			})
   562  		})
   563  
   564  		When("the CC returns an error", func() {
   565  			BeforeEach(func() {
   566  				response := ` {
   567  					"errors": [
   568  						{
   569  							"code": 10008,
   570  							"detail": "Banana",
   571  							"title": "CF-Banana"
   572  						}
   573  					]
   574  				}`
   575  
   576  				server.AppendHandlers(
   577  					CombineHandlers(
   578  						VerifyRequest(http.MethodPost, "/v3/packages/package-guid/upload"),
   579  						RespondWith(http.StatusNotFound, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   580  					),
   581  				)
   582  			})
   583  
   584  			It("returns the error", func() {
   585  				_, warnings, err := client.UploadBitsPackage(inputPackage, []Resource{}, bytes.NewReader(nil), 0)
   586  				Expect(err).To(MatchError(ccerror.ResourceNotFoundError{Message: "Banana"}))
   587  				Expect(warnings).To(ConsistOf("this is a warning"))
   588  			})
   589  		})
   590  
   591  		When("passed a nil resources", func() {
   592  			It("returns a NilObjectError", func() {
   593  				_, _, err := client.UploadBitsPackage(inputPackage, nil, bytes.NewReader(nil), 0)
   594  				Expect(err).To(MatchError(ccerror.NilObjectError{Object: "matchedResources"}))
   595  			})
   596  		})
   597  
   598  		When("an error is returned from the new resources reader", func() {
   599  			var (
   600  				fakeReader  *ccv3fakes.FakeReader
   601  				expectedErr error
   602  			)
   603  
   604  			BeforeEach(func() {
   605  				expectedErr = errors.New("some read error")
   606  				fakeReader = new(ccv3fakes.FakeReader)
   607  				fakeReader.ReadReturns(0, expectedErr)
   608  
   609  				server.AppendHandlers(
   610  					VerifyRequest(http.MethodPost, "/v3/packages/package-guid/upload"),
   611  				)
   612  			})
   613  
   614  			It("returns the error", func() {
   615  				_, _, err := client.UploadBitsPackage(inputPackage, []Resource{}, fakeReader, 3)
   616  				Expect(err).To(MatchError(expectedErr))
   617  			})
   618  		})
   619  
   620  		When("a retryable error occurs", func() {
   621  			BeforeEach(func() {
   622  				wrapper := &wrapper.CustomWrapper{
   623  					CustomMake: func(connection cloudcontroller.Connection, request *cloudcontroller.Request, response *cloudcontroller.Response) error {
   624  						defer GinkgoRecover() // Since this will be running in a thread
   625  
   626  						if strings.HasSuffix(request.URL.String(), "/v3/packages/package-guid/upload") {
   627  							_, err := ioutil.ReadAll(request.Body)
   628  							Expect(err).ToNot(HaveOccurred())
   629  							Expect(request.Body.Close()).ToNot(HaveOccurred())
   630  							return request.ResetBody()
   631  						}
   632  						return connection.Make(request, response)
   633  					},
   634  				}
   635  
   636  				client, _ = NewTestClient(Config{Wrappers: []ConnectionWrapper{wrapper}})
   637  			})
   638  
   639  			It("returns the PipeSeekError", func() {
   640  				_, _, err := client.UploadBitsPackage(inputPackage, []Resource{}, strings.NewReader("hello world"), 3)
   641  				Expect(err).To(MatchError(ccerror.PipeSeekError{}))
   642  			})
   643  		})
   644  
   645  		When("an http error occurs mid-transfer", func() {
   646  			var expectedErr error
   647  			const UploadSize = 33 * 1024
   648  
   649  			BeforeEach(func() {
   650  				expectedErr = errors.New("some read error")
   651  
   652  				wrapper := &wrapper.CustomWrapper{
   653  					CustomMake: func(connection cloudcontroller.Connection, request *cloudcontroller.Request, response *cloudcontroller.Response) error {
   654  						defer GinkgoRecover() // Since this will be running in a thread
   655  
   656  						if strings.HasSuffix(request.URL.String(), "/v3/packages/package-guid/upload") {
   657  							defer request.Body.Close()
   658  							readBytes, err := ioutil.ReadAll(request.Body)
   659  							Expect(err).ToNot(HaveOccurred())
   660  							Expect(len(readBytes)).To(BeNumerically(">", UploadSize))
   661  							return expectedErr
   662  						}
   663  						return connection.Make(request, response)
   664  					},
   665  				}
   666  
   667  				client, _ = NewTestClient(Config{Wrappers: []ConnectionWrapper{wrapper}})
   668  			})
   669  
   670  			It("returns the http error", func() {
   671  				_, _, err := client.UploadBitsPackage(inputPackage, []Resource{}, strings.NewReader(strings.Repeat("a", UploadSize)), 3)
   672  				Expect(err).To(MatchError(expectedErr))
   673  			})
   674  		})
   675  	})
   676  
   677  	Describe("UploadPackage", func() {
   678  		var (
   679  			inputPackage Package
   680  			fileToUpload string
   681  
   682  			pkg        Package
   683  			warnings   Warnings
   684  			executeErr error
   685  		)
   686  
   687  		JustBeforeEach(func() {
   688  			pkg, warnings, executeErr = client.UploadPackage(inputPackage, fileToUpload)
   689  		})
   690  
   691  		When("the package successfully is created", func() {
   692  			var tempFile *os.File
   693  
   694  			BeforeEach(func() {
   695  				var err error
   696  
   697  				inputPackage = Package{
   698  					State: constant.PackageAwaitingUpload,
   699  					GUID:  "package-guid",
   700  				}
   701  
   702  				tempFile, err = ioutil.TempFile("", "package-upload")
   703  				Expect(err).ToNot(HaveOccurred())
   704  				defer tempFile.Close()
   705  
   706  				fileToUpload = tempFile.Name()
   707  
   708  				fileSize := 1024
   709  				contents := strings.Repeat("A", fileSize)
   710  				err = ioutil.WriteFile(tempFile.Name(), []byte(contents), 0666)
   711  				Expect(err).NotTo(HaveOccurred())
   712  
   713  				verifyHeaderAndBody := func(_ http.ResponseWriter, req *http.Request) {
   714  					contentType := req.Header.Get("Content-Type")
   715  					Expect(contentType).To(MatchRegexp("multipart/form-data; boundary=[\\w\\d]+"))
   716  
   717  					boundary := contentType[30:]
   718  
   719  					defer req.Body.Close()
   720  					rawBody, err := ioutil.ReadAll(req.Body)
   721  					Expect(err).NotTo(HaveOccurred())
   722  					body := BufferWithBytes(rawBody)
   723  					Expect(body).To(Say("--%s", boundary))
   724  					Expect(body).To(Say(`name="bits"`))
   725  					Expect(body).To(Say(contents))
   726  					Expect(body).To(Say("--%s--", boundary))
   727  				}
   728  
   729  				response := `{
   730  					"guid": "some-pkg-guid",
   731  					"state": "PROCESSING_UPLOAD",
   732  					"links": {
   733  						"upload": {
   734  							"href": "some-package-upload-url",
   735  							"method": "POST"
   736  						}
   737  					}
   738  				}`
   739  
   740  				server.AppendHandlers(
   741  					CombineHandlers(
   742  						VerifyRequest(http.MethodPost, "/v3/packages/package-guid/upload"),
   743  						verifyHeaderAndBody,
   744  						RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   745  					),
   746  				)
   747  			})
   748  
   749  			AfterEach(func() {
   750  				if tempFile != nil {
   751  					Expect(os.RemoveAll(tempFile.Name())).ToNot(HaveOccurred())
   752  				}
   753  			})
   754  
   755  			It("returns the created package and warnings", func() {
   756  				Expect(executeErr).NotTo(HaveOccurred())
   757  
   758  				expectedPackage := Package{
   759  					GUID:  "some-pkg-guid",
   760  					State: constant.PackageProcessingUpload,
   761  					Links: map[string]APILink{
   762  						"upload": APILink{HREF: "some-package-upload-url", Method: http.MethodPost},
   763  					},
   764  				}
   765  				Expect(pkg).To(Equal(expectedPackage))
   766  				Expect(warnings).To(ConsistOf("this is a warning"))
   767  			})
   768  		})
   769  
   770  		When("cc returns back an error or warnings", func() {
   771  			var tempFile *os.File
   772  
   773  			BeforeEach(func() {
   774  				var err error
   775  
   776  				inputPackage = Package{
   777  					GUID:  "package-guid",
   778  					State: constant.PackageAwaitingUpload,
   779  				}
   780  
   781  				tempFile, err = ioutil.TempFile("", "package-upload")
   782  				Expect(err).ToNot(HaveOccurred())
   783  				defer tempFile.Close()
   784  
   785  				fileToUpload = tempFile.Name()
   786  
   787  				fileSize := 1024
   788  				contents := strings.Repeat("A", fileSize)
   789  				err = ioutil.WriteFile(tempFile.Name(), []byte(contents), 0666)
   790  				Expect(err).NotTo(HaveOccurred())
   791  
   792  				response := ` {
   793  					"errors": [
   794  						{
   795  							"code": 10008,
   796  							"detail": "The request is semantically invalid: command presence",
   797  							"title": "CF-UnprocessableEntity"
   798  						},
   799  						{
   800  							"code": 10008,
   801  							"detail": "The request is semantically invalid: command presence",
   802  							"title": "CF-UnprocessableEntity"
   803  						}
   804  					]
   805  				}`
   806  
   807  				server.AppendHandlers(
   808  					CombineHandlers(
   809  						VerifyRequest(http.MethodPost, "/v3/packages/package-guid/upload"),
   810  						RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   811  					),
   812  				)
   813  			})
   814  
   815  			AfterEach(func() {
   816  				if tempFile != nil {
   817  					Expect(os.RemoveAll(tempFile.Name())).ToNot(HaveOccurred())
   818  				}
   819  			})
   820  
   821  			It("returns the error and all warnings", func() {
   822  				Expect(executeErr).To(MatchError(ccerror.MultiError{
   823  					ResponseCode: http.StatusTeapot,
   824  					Errors: []ccerror.V3Error{
   825  						{
   826  							Code:   10008,
   827  							Detail: "The request is semantically invalid: command presence",
   828  							Title:  "CF-UnprocessableEntity",
   829  						},
   830  						{
   831  							Code:   10008,
   832  							Detail: "The request is semantically invalid: command presence",
   833  							Title:  "CF-UnprocessableEntity",
   834  						},
   835  					},
   836  				}))
   837  				Expect(warnings).To(ConsistOf("this is a warning"))
   838  			})
   839  
   840  		})
   841  	})
   842  })