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