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 })