github.com/wanddynosios/cli/v8@v8.7.9-0.20240221182337-1a92e3a7017f/api/cloudcontroller/ccv3/droplet_test.go (about)

     1  package ccv3_test
     2  
     3  import (
     4  	"errors"
     5  	"io"
     6  	"io/ioutil"
     7  	"mime/multipart"
     8  	"net/http"
     9  	"strings"
    10  
    11  	"code.cloudfoundry.org/cli/api/cloudcontroller"
    12  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccerror"
    13  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccv3"
    14  	. "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3"
    15  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/ccv3fakes"
    16  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/constant"
    17  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/internal"
    18  	"code.cloudfoundry.org/cli/api/cloudcontroller/wrapper"
    19  	"code.cloudfoundry.org/cli/resources"
    20  	. "github.com/onsi/ginkgo"
    21  	. "github.com/onsi/gomega"
    22  	. "github.com/onsi/gomega/ghttp"
    23  )
    24  
    25  var _ = Describe("Droplet", func() {
    26  	var (
    27  		client    *Client
    28  		requester *ccv3fakes.FakeRequester
    29  	)
    30  
    31  	BeforeEach(func() {
    32  		requester = new(ccv3fakes.FakeRequester)
    33  		client, _ = NewFakeRequesterTestClient(requester)
    34  	})
    35  
    36  	Describe("CreateDroplet", func() {
    37  		var (
    38  			droplet    resources.Droplet
    39  			warnings   Warnings
    40  			executeErr error
    41  		)
    42  
    43  		JustBeforeEach(func() {
    44  			droplet, warnings, executeErr = client.CreateDroplet("app-guid")
    45  		})
    46  
    47  		BeforeEach(func() {
    48  			requester.MakeRequestCalls(func(requestParams RequestParams) (JobURL, Warnings, error) {
    49  				requestParams.ResponseBody.(*resources.Droplet).GUID = "some-guid"
    50  				return "", Warnings{"some-warning"}, errors.New("some-error")
    51  			})
    52  		})
    53  
    54  		It("makes the correct request", func() {
    55  			Expect(requester.MakeRequestCallCount()).To(Equal(1))
    56  			actualParams := requester.MakeRequestArgsForCall(0)
    57  			Expect(actualParams.RequestName).To(Equal(internal.PostDropletRequest))
    58  			Expect(actualParams.RequestBody).To(Equal(DropletCreateRequest{
    59  				Relationships: resources.Relationships{
    60  					constant.RelationshipTypeApplication: resources.Relationship{GUID: "app-guid"},
    61  				},
    62  			}))
    63  			_, ok := actualParams.ResponseBody.(*resources.Droplet)
    64  			Expect(ok).To(BeTrue())
    65  		})
    66  
    67  		It("returns the given droplet and all warnings", func() {
    68  			Expect(droplet).To(Equal(resources.Droplet{GUID: "some-guid"}))
    69  			Expect(warnings).To(ConsistOf("some-warning"))
    70  			Expect(executeErr).To(MatchError("some-error"))
    71  		})
    72  	})
    73  
    74  	Describe("GetApplicationDropletCurrent", func() {
    75  		var (
    76  			droplet    resources.Droplet
    77  			warnings   Warnings
    78  			executeErr error
    79  		)
    80  
    81  		JustBeforeEach(func() {
    82  			droplet, warnings, executeErr = client.GetApplicationDropletCurrent("some-app-guid")
    83  		})
    84  
    85  		BeforeEach(func() {
    86  			requester.MakeRequestCalls(func(requestParams RequestParams) (JobURL, Warnings, error) {
    87  				requestParams.ResponseBody.(*resources.Droplet).GUID = "some-guid"
    88  				return "", Warnings{"some-warning"}, errors.New("some-error")
    89  			})
    90  		})
    91  
    92  		It("makes the correct request", func() {
    93  			Expect(requester.MakeRequestCallCount()).To(Equal(1))
    94  			actualParams := requester.MakeRequestArgsForCall(0)
    95  			Expect(actualParams.RequestName).To(Equal(internal.GetApplicationDropletCurrentRequest))
    96  			Expect(actualParams.URIParams).To(Equal(internal.Params{"app_guid": "some-app-guid"}))
    97  			_, ok := actualParams.ResponseBody.(*resources.Droplet)
    98  			Expect(ok).To(BeTrue())
    99  		})
   100  
   101  		It("returns the given droplet and all warnings", func() {
   102  			Expect(droplet).To(Equal(resources.Droplet{GUID: "some-guid"}))
   103  			Expect(warnings).To(ConsistOf("some-warning"))
   104  			Expect(executeErr).To(MatchError("some-error"))
   105  		})
   106  	})
   107  
   108  	Describe("GetPackageDroplets", func() {
   109  		var (
   110  			droplets   []resources.Droplet
   111  			warnings   Warnings
   112  			executeErr error
   113  		)
   114  
   115  		JustBeforeEach(func() {
   116  			droplets, warnings, executeErr = client.GetPackageDroplets(
   117  				"package-guid",
   118  				Query{Key: PerPage, Values: []string{"2"}},
   119  			)
   120  		})
   121  
   122  		BeforeEach(func() {
   123  			requester.MakeListRequestCalls(func(requestParams RequestParams) (IncludedResources, Warnings, error) {
   124  				err := requestParams.AppendToList(resources.Droplet{GUID: "some-droplet-guid"})
   125  				Expect(err).NotTo(HaveOccurred())
   126  				return IncludedResources{}, Warnings{"some-warning"}, errors.New("some-error")
   127  			})
   128  		})
   129  
   130  		It("makes the correct request", func() {
   131  			Expect(requester.MakeListRequestCallCount()).To(Equal(1))
   132  			actualParams := requester.MakeListRequestArgsForCall(0)
   133  			Expect(actualParams.RequestName).To(Equal(internal.GetPackageDropletsRequest))
   134  			Expect(actualParams.URIParams).To(Equal(internal.Params{"package_guid": "package-guid"}))
   135  			_, ok := actualParams.ResponseBody.(resources.Droplet)
   136  			Expect(ok).To(BeTrue())
   137  		})
   138  
   139  		It("returns the given droplet and all warnings", func() {
   140  			Expect(droplets).To(Equal([]resources.Droplet{{GUID: "some-droplet-guid"}}))
   141  			Expect(warnings).To(ConsistOf("some-warning"))
   142  			Expect(executeErr).To(MatchError("some-error"))
   143  		})
   144  	})
   145  
   146  	Describe("GetDroplet", func() {
   147  		var (
   148  			droplet    resources.Droplet
   149  			warnings   Warnings
   150  			executeErr error
   151  		)
   152  
   153  		JustBeforeEach(func() {
   154  			droplet, warnings, executeErr = client.GetDroplet("some-guid")
   155  		})
   156  
   157  		BeforeEach(func() {
   158  			requester.MakeRequestCalls(func(requestParams RequestParams) (JobURL, Warnings, error) {
   159  				requestParams.ResponseBody.(*resources.Droplet).GUID = "some-droplet-guid"
   160  				return "", Warnings{"some-warning"}, errors.New("some-error")
   161  			})
   162  		})
   163  
   164  		It("makes the correct request", func() {
   165  			Expect(requester.MakeRequestCallCount()).To(Equal(1))
   166  			actualParams := requester.MakeRequestArgsForCall(0)
   167  			Expect(actualParams.RequestName).To(Equal(internal.GetDropletRequest))
   168  			Expect(actualParams.URIParams).To(Equal(internal.Params{"droplet_guid": "some-guid"}))
   169  			_, ok := actualParams.ResponseBody.(*resources.Droplet)
   170  			Expect(ok).To(BeTrue())
   171  		})
   172  
   173  		It("returns the given droplet and all warnings", func() {
   174  			Expect(droplet).To(Equal(resources.Droplet{GUID: "some-droplet-guid"}))
   175  			Expect(warnings).To(ConsistOf("some-warning"))
   176  			Expect(executeErr).To(MatchError("some-error"))
   177  		})
   178  	})
   179  
   180  	Describe("GetDroplets", func() {
   181  		var (
   182  			droplets   []resources.Droplet
   183  			warnings   Warnings
   184  			executeErr error
   185  		)
   186  
   187  		JustBeforeEach(func() {
   188  			droplets, warnings, executeErr = client.GetDroplets(
   189  				Query{Key: AppGUIDFilter, Values: []string{"some-app-guid"}},
   190  				Query{Key: PerPage, Values: []string{"2"}},
   191  			)
   192  		})
   193  
   194  		BeforeEach(func() {
   195  			requester.MakeListRequestCalls(func(requestParams RequestParams) (IncludedResources, Warnings, error) {
   196  				err := requestParams.AppendToList(resources.Droplet{GUID: "some-droplet-guid"})
   197  				Expect(err).NotTo(HaveOccurred())
   198  				return IncludedResources{}, Warnings{"some-warning"}, errors.New("some-error")
   199  			})
   200  		})
   201  
   202  		It("makes the correct request", func() {
   203  			Expect(requester.MakeListRequestCallCount()).To(Equal(1))
   204  			actualParams := requester.MakeListRequestArgsForCall(0)
   205  			Expect(actualParams.RequestName).To(Equal(internal.GetDropletsRequest))
   206  			Expect(actualParams.Query).To(Equal([]Query{
   207  				{Key: AppGUIDFilter, Values: []string{"some-app-guid"}},
   208  				{Key: PerPage, Values: []string{"2"}},
   209  			}))
   210  			_, ok := actualParams.ResponseBody.(resources.Droplet)
   211  			Expect(ok).To(BeTrue())
   212  		})
   213  
   214  		It("returns the given droplet and all warnings", func() {
   215  			Expect(droplets).To(Equal([]resources.Droplet{{GUID: "some-droplet-guid"}}))
   216  			Expect(warnings).To(ConsistOf("some-warning"))
   217  			Expect(executeErr).To(MatchError("some-error"))
   218  		})
   219  	})
   220  
   221  	Describe("UploadDropletBits", func() {
   222  		var (
   223  			dropletGUID     string
   224  			dropletFile     io.Reader
   225  			dropletFilePath string
   226  			dropletContent  string
   227  			jobURL          JobURL
   228  			warnings        Warnings
   229  			executeErr      error
   230  		)
   231  
   232  		BeforeEach(func() {
   233  			dropletGUID = "some-droplet-guid"
   234  			dropletContent = "some-content"
   235  			dropletFile = strings.NewReader(dropletContent)
   236  			dropletFilePath = "some/fake-droplet.tgz"
   237  
   238  			client, _ = NewTestClient()
   239  		})
   240  
   241  		JustBeforeEach(func() {
   242  			jobURL, warnings, executeErr = client.UploadDropletBits(dropletGUID, dropletFilePath, dropletFile, int64(len(dropletContent)))
   243  		})
   244  
   245  		When("the upload is successful", func() {
   246  			BeforeEach(func() {
   247  				response := `{
   248  					"guid": "some-droplet-guid",
   249  					"state": "PROCESSING_UPLOAD"
   250  				}`
   251  
   252  				verifyHeaderAndBody := func(_ http.ResponseWriter, req *http.Request) {
   253  					contentType := req.Header.Get("Content-Type")
   254  					Expect(contentType).To(MatchRegexp("multipart/form-data; boundary=[\\w\\d]+"))
   255  
   256  					defer req.Body.Close()
   257  					requestReader := multipart.NewReader(req.Body, contentType[30:])
   258  
   259  					dropletPart, err := requestReader.NextPart()
   260  					Expect(err).NotTo(HaveOccurred())
   261  
   262  					Expect(dropletPart.FormName()).To(Equal("bits"))
   263  					Expect(dropletPart.FileName()).To(Equal("fake-droplet.tgz"))
   264  
   265  					defer dropletPart.Close()
   266  					partContents, err := ioutil.ReadAll(dropletPart)
   267  					Expect(err).ToNot(HaveOccurred())
   268  					Expect(string(partContents)).To(Equal(dropletContent))
   269  				}
   270  
   271  				server.AppendHandlers(
   272  					CombineHandlers(
   273  						VerifyRequest(http.MethodPost, "/v3/droplets/some-droplet-guid/upload"),
   274  						verifyHeaderAndBody,
   275  						RespondWith(
   276  							http.StatusAccepted,
   277  							response,
   278  							http.Header{
   279  								"X-Cf-Warnings": {"this is a warning"},
   280  								"Location":      {"http://example.com/job-guid"},
   281  							},
   282  						),
   283  					),
   284  				)
   285  			})
   286  
   287  			It("returns the processing job URL and warnings", func() {
   288  				Expect(executeErr).ToNot(HaveOccurred())
   289  				Expect(warnings).To(ConsistOf(Warnings{"this is a warning"}))
   290  				Expect(jobURL).To(Equal(JobURL("http://example.com/job-guid")))
   291  			})
   292  		})
   293  
   294  		When("there is an error reading the buildpack", func() {
   295  			var (
   296  				fakeReader  *ccv3fakes.FakeReader
   297  				expectedErr error
   298  			)
   299  
   300  			BeforeEach(func() {
   301  				expectedErr = errors.New("droplet read error")
   302  				fakeReader = new(ccv3fakes.FakeReader)
   303  				fakeReader.ReadReturns(0, expectedErr)
   304  				dropletFile = fakeReader
   305  
   306  				server.AppendHandlers(
   307  					VerifyRequest(http.MethodPost, "/v3/droplets/some-droplet-guid/upload"),
   308  				)
   309  			})
   310  
   311  			It("returns the error", func() {
   312  				Expect(executeErr).To(MatchError(expectedErr))
   313  			})
   314  		})
   315  
   316  		When("the upload returns an error", func() {
   317  			BeforeEach(func() {
   318  				response := `{
   319  					"errors": [{
   320                          "detail": "The droplet could not be found: some-droplet-guid",
   321                          "title": "CF-ResourceNotFound",
   322                          "code": 10010
   323                      }]
   324                  }`
   325  
   326  				server.AppendHandlers(
   327  					CombineHandlers(
   328  						VerifyRequest(http.MethodPost, "/v3/droplets/some-droplet-guid/upload"),
   329  						RespondWith(http.StatusNotFound, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   330  					),
   331  				)
   332  			})
   333  
   334  			It("returns the error and warnings", func() {
   335  				Expect(executeErr).To(MatchError(
   336  					ccerror.ResourceNotFoundError{
   337  						Message: "The droplet could not be found: some-droplet-guid",
   338  					},
   339  				))
   340  				Expect(warnings).To(ConsistOf(Warnings{"this is a warning"}))
   341  			})
   342  		})
   343  
   344  		When("cloud controller returns an error", func() {
   345  			BeforeEach(func() {
   346  				dropletGUID = "some-guid"
   347  
   348  				response := `{
   349  					"errors": [
   350  						{
   351  							"code": 10010,
   352  							"detail": "Droplet not found",
   353  							"title": "CF-ResourceNotFound"
   354  						}
   355  					]
   356  				}`
   357  				server.AppendHandlers(
   358  					CombineHandlers(
   359  						VerifyRequest(http.MethodPost, "/v3/droplets/some-guid/upload"),
   360  						RespondWith(http.StatusNotFound, response, http.Header{"X-Cf-Warnings": {"warning-1"}}),
   361  					),
   362  				)
   363  			})
   364  
   365  			It("returns the error", func() {
   366  				Expect(executeErr).To(MatchError(ccerror.DropletNotFoundError{}))
   367  				Expect(warnings).To(ConsistOf("warning-1"))
   368  			})
   369  		})
   370  
   371  		When("a retryable error occurs", func() {
   372  			BeforeEach(func() {
   373  				wrapper := &wrapper.CustomWrapper{
   374  					CustomMake: func(connection cloudcontroller.Connection, request *cloudcontroller.Request, response *cloudcontroller.Response) error {
   375  						defer GinkgoRecover() // Since this will be running in a thread
   376  
   377  						if strings.HasSuffix(request.URL.String(), "/v3/droplets/some-droplet-guid/upload") {
   378  							_, err := ioutil.ReadAll(request.Body)
   379  							Expect(err).ToNot(HaveOccurred())
   380  							Expect(request.Body.Close()).ToNot(HaveOccurred())
   381  							return request.ResetBody()
   382  						}
   383  						return connection.Make(request, response)
   384  					},
   385  				}
   386  
   387  				client, _ = NewTestClient(Config{Wrappers: []ConnectionWrapper{wrapper}})
   388  			})
   389  
   390  			It("returns the PipeSeekError", func() {
   391  				Expect(executeErr).To(MatchError(ccerror.PipeSeekError{}))
   392  			})
   393  		})
   394  
   395  		When("an http error occurs mid-transfer", func() {
   396  			var expectedErr error
   397  
   398  			BeforeEach(func() {
   399  				expectedErr = errors.New("some read error")
   400  
   401  				wrapper := &wrapper.CustomWrapper{
   402  					CustomMake: func(connection cloudcontroller.Connection, request *cloudcontroller.Request, response *cloudcontroller.Response) error {
   403  						defer GinkgoRecover() // Since this will be running in a thread
   404  
   405  						if strings.HasSuffix(request.URL.String(), "/v3/droplets/some-droplet-guid/upload") {
   406  							defer request.Body.Close()
   407  							readBytes, err := ioutil.ReadAll(request.Body)
   408  							Expect(err).ToNot(HaveOccurred())
   409  							Expect(len(readBytes)).To(BeNumerically(">", len(dropletContent)))
   410  							return expectedErr
   411  						}
   412  						return connection.Make(request, response)
   413  					},
   414  				}
   415  
   416  				client, _ = NewTestClient(Config{Wrappers: []ConnectionWrapper{wrapper}})
   417  			})
   418  
   419  			It("returns the http error", func() {
   420  				Expect(executeErr).To(MatchError(expectedErr))
   421  			})
   422  		})
   423  	})
   424  
   425  	Describe("DownloadDroplet", func() {
   426  		var (
   427  			dropletBytes []byte
   428  			warnings     Warnings
   429  			executeErr   error
   430  		)
   431  
   432  		JustBeforeEach(func() {
   433  			dropletBytes, warnings, executeErr = client.DownloadDroplet("some-droplet-guid")
   434  		})
   435  
   436  		BeforeEach(func() {
   437  			requester.MakeRequestReceiveRawCalls(func(string, internal.Params, string) ([]byte, ccv3.Warnings, error) {
   438  				return []byte{'d', 'r', 'o', 'p'}, Warnings{"some-warning"}, errors.New("some-error")
   439  			})
   440  		})
   441  
   442  		It("makes the correct request", func() {
   443  			Expect(requester.MakeRequestReceiveRawCallCount()).To(Equal(1))
   444  			requestType, requestParams, responseType := requester.MakeRequestReceiveRawArgsForCall(0)
   445  			Expect(requestType).To(Equal(internal.GetDropletBitsRequest))
   446  			Expect(requestParams).To(Equal(internal.Params{"droplet_guid": "some-droplet-guid"}))
   447  			Expect(responseType).To(Equal("application/json"))
   448  		})
   449  
   450  		It("returns the given droplet and all warnings", func() {
   451  			Expect(dropletBytes).To(Equal([]byte{'d', 'r', 'o', 'p'}))
   452  			Expect(warnings).To(ConsistOf("some-warning"))
   453  			Expect(executeErr).To(MatchError("some-error"))
   454  		})
   455  	})
   456  })