github.com/jenspinney/cli@v6.42.1-0.20190207184520-7450c600020e+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": APILink{ 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 ) 457 458 When("the upload has application bits to upload", func() { 459 var reader io.Reader 460 461 BeforeEach(func() { 462 resources = []Resource{ 463 {Filename: "foo"}, 464 {Filename: "bar"}, 465 } 466 467 readerBody = []byte("hello world") 468 reader = bytes.NewReader(readerBody) 469 470 verifyHeaderAndBody := func(_ http.ResponseWriter, req *http.Request) { 471 contentType := req.Header.Get("Content-Type") 472 Expect(contentType).To(MatchRegexp("multipart/form-data; boundary=[\\w\\d]+")) 473 474 defer req.Body.Close() 475 requestReader := multipart.NewReader(req.Body, contentType[30:]) 476 477 // Verify that matched resources are sent properly 478 resourcesPart, err := requestReader.NextPart() 479 Expect(err).NotTo(HaveOccurred()) 480 481 Expect(resourcesPart.FormName()).To(Equal("resources")) 482 483 defer resourcesPart.Close() 484 expectedJSON, err := json.Marshal(resources) 485 Expect(err).NotTo(HaveOccurred()) 486 Expect(ioutil.ReadAll(resourcesPart)).To(MatchJSON(expectedJSON)) 487 488 // Verify that the application bits are sent properly 489 resourcesPart, err = requestReader.NextPart() 490 Expect(err).NotTo(HaveOccurred()) 491 492 Expect(resourcesPart.FormName()).To(Equal("bits")) 493 Expect(resourcesPart.FileName()).To(Equal("package.zip")) 494 495 defer resourcesPart.Close() 496 Expect(ioutil.ReadAll(resourcesPart)).To(Equal(readerBody)) 497 } 498 499 response := `{ 500 "guid": "some-package-guid", 501 "type": "bits", 502 "state": "PROCESSING_UPLOAD" 503 }` 504 505 server.AppendHandlers( 506 CombineHandlers( 507 VerifyRequest(http.MethodPost, "/v3/my-special-endpoint/some-pkg-guid/upload"), 508 verifyHeaderAndBody, 509 RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 510 ), 511 ) 512 }) 513 514 It("returns the created job and warnings", func() { 515 pkg, warnings, err := client.UploadBitsPackage(inputPackage, resources, reader, int64(len(readerBody))) 516 Expect(err).NotTo(HaveOccurred()) 517 Expect(warnings).To(ConsistOf("this is a warning")) 518 Expect(pkg).To(Equal(Package{ 519 GUID: "some-package-guid", 520 Type: constant.PackageTypeBits, 521 State: constant.PackageProcessingUpload, 522 })) 523 }) 524 }) 525 526 When("there are no application bits to upload", func() { 527 BeforeEach(func() { 528 resources = []Resource{ 529 {Filename: "foo"}, 530 {Filename: "bar"}, 531 } 532 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 resourcesPart, err = requestReader.NextPart() 553 Expect(err).To(MatchError(io.EOF)) 554 } 555 556 response := `{ 557 "guid": "some-package-guid", 558 "type": "bits", 559 "state": "PROCESSING_UPLOAD" 560 }` 561 562 server.AppendHandlers( 563 CombineHandlers( 564 VerifyRequest(http.MethodPost, "/v3/my-special-endpoint/some-pkg-guid/upload"), 565 verifyHeaderAndBody, 566 RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 567 ), 568 ) 569 }) 570 571 It("does not send the application bits", func() { 572 pkg, warnings, err := client.UploadBitsPackage(inputPackage, resources, nil, 33513531353) 573 Expect(err).NotTo(HaveOccurred()) 574 Expect(warnings).To(ConsistOf("this is a warning")) 575 Expect(pkg).To(Equal(Package{ 576 GUID: "some-package-guid", 577 Type: constant.PackageTypeBits, 578 State: constant.PackageProcessingUpload, 579 })) 580 }) 581 }) 582 }) 583 584 When("the CC returns an error", func() { 585 BeforeEach(func() { 586 response := ` { 587 "errors": [ 588 { 589 "code": 10008, 590 "detail": "Banana", 591 "title": "CF-Banana" 592 } 593 ] 594 }` 595 596 server.AppendHandlers( 597 CombineHandlers( 598 VerifyRequest(http.MethodPost, "/v3/my-special-endpoint/some-pkg-guid/upload"), 599 RespondWith(http.StatusNotFound, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 600 ), 601 ) 602 }) 603 604 It("returns the error", func() { 605 _, warnings, err := client.UploadBitsPackage(inputPackage, []Resource{}, bytes.NewReader(nil), 0) 606 Expect(err).To(MatchError(ccerror.ResourceNotFoundError{Message: "Banana"})) 607 Expect(warnings).To(ConsistOf("this is a warning")) 608 }) 609 }) 610 611 When("passed a nil resources", func() { 612 It("returns a NilObjectError", func() { 613 _, _, err := client.UploadBitsPackage(inputPackage, nil, bytes.NewReader(nil), 0) 614 Expect(err).To(MatchError(ccerror.NilObjectError{Object: "existingResources"})) 615 }) 616 }) 617 618 When("an error is returned from the new resources reader", func() { 619 var ( 620 fakeReader *ccv3fakes.FakeReader 621 expectedErr error 622 ) 623 624 BeforeEach(func() { 625 expectedErr = errors.New("some read error") 626 fakeReader = new(ccv3fakes.FakeReader) 627 fakeReader.ReadReturns(0, expectedErr) 628 629 server.AppendHandlers( 630 VerifyRequest(http.MethodPost, "/v3/my-special-endpoint/some-pkg-guid/upload"), 631 ) 632 }) 633 634 It("returns the error", func() { 635 _, _, err := client.UploadBitsPackage(inputPackage, []Resource{}, fakeReader, 3) 636 Expect(err).To(MatchError(expectedErr)) 637 }) 638 }) 639 640 When("a retryable error occurs", func() { 641 BeforeEach(func() { 642 wrapper := &wrapper.CustomWrapper{ 643 CustomMake: func(connection cloudcontroller.Connection, request *cloudcontroller.Request, response *cloudcontroller.Response) error { 644 defer GinkgoRecover() // Since this will be running in a thread 645 646 if strings.HasSuffix(request.URL.String(), "/v3/my-special-endpoint/some-pkg-guid/upload") { 647 _, err := ioutil.ReadAll(request.Body) 648 Expect(err).ToNot(HaveOccurred()) 649 Expect(request.Body.Close()).ToNot(HaveOccurred()) 650 return request.ResetBody() 651 } 652 return connection.Make(request, response) 653 }, 654 } 655 656 client, _ = NewTestClient(Config{Wrappers: []ConnectionWrapper{wrapper}}) 657 }) 658 659 It("returns the PipeSeekError", func() { 660 _, _, err := client.UploadBitsPackage(inputPackage, []Resource{}, strings.NewReader("hello world"), 3) 661 Expect(err).To(MatchError(ccerror.PipeSeekError{})) 662 }) 663 }) 664 665 When("an http error occurs mid-transfer", func() { 666 var expectedErr error 667 const UploadSize = 33 * 1024 668 669 BeforeEach(func() { 670 expectedErr = errors.New("some read error") 671 672 wrapper := &wrapper.CustomWrapper{ 673 CustomMake: func(connection cloudcontroller.Connection, request *cloudcontroller.Request, response *cloudcontroller.Response) error { 674 defer GinkgoRecover() // Since this will be running in a thread 675 676 if strings.HasSuffix(request.URL.String(), "/v3/my-special-endpoint/some-pkg-guid/upload") { 677 defer request.Body.Close() 678 readBytes, err := ioutil.ReadAll(request.Body) 679 Expect(err).ToNot(HaveOccurred()) 680 Expect(len(readBytes)).To(BeNumerically(">", UploadSize)) 681 return expectedErr 682 } 683 return connection.Make(request, response) 684 }, 685 } 686 687 client, _ = NewTestClient(Config{Wrappers: []ConnectionWrapper{wrapper}}) 688 }) 689 690 It("returns the http error", func() { 691 _, _, err := client.UploadBitsPackage(inputPackage, []Resource{}, strings.NewReader(strings.Repeat("a", UploadSize)), 3) 692 Expect(err).To(MatchError(expectedErr)) 693 }) 694 }) 695 696 When("the input package does not have an upload link", func() { 697 It("returns an UploadLinkNotFoundError", func() { 698 _, _, err := client.UploadBitsPackage(Package{GUID: "some-pkg-guid"}, nil, nil, 0) 699 Expect(err).To(MatchError(ccerror.UploadLinkNotFoundError{PackageGUID: "some-pkg-guid"})) 700 }) 701 }) 702 }) 703 704 Describe("UploadPackage", func() { 705 var ( 706 inputPackage Package 707 fileToUpload string 708 709 pkg Package 710 warnings Warnings 711 executeErr error 712 ) 713 714 JustBeforeEach(func() { 715 pkg, warnings, executeErr = client.UploadPackage(inputPackage, fileToUpload) 716 }) 717 718 When("the package successfully is created", func() { 719 var tempFile *os.File 720 721 BeforeEach(func() { 722 var err error 723 724 inputPackage = Package{ 725 State: constant.PackageAwaitingUpload, 726 Links: map[string]APILink{ 727 "upload": APILink{ 728 HREF: fmt.Sprintf("%s/v3/my-special-endpoint/some-pkg-guid/upload", server.URL()), 729 Method: http.MethodPost, 730 }, 731 }, 732 } 733 734 tempFile, err = ioutil.TempFile("", "package-upload") 735 Expect(err).ToNot(HaveOccurred()) 736 defer tempFile.Close() 737 738 fileToUpload = tempFile.Name() 739 740 fileSize := 1024 741 contents := strings.Repeat("A", fileSize) 742 err = ioutil.WriteFile(tempFile.Name(), []byte(contents), 0666) 743 Expect(err).NotTo(HaveOccurred()) 744 745 verifyHeaderAndBody := func(_ http.ResponseWriter, req *http.Request) { 746 contentType := req.Header.Get("Content-Type") 747 Expect(contentType).To(MatchRegexp("multipart/form-data; boundary=[\\w\\d]+")) 748 749 boundary := contentType[30:] 750 751 defer req.Body.Close() 752 rawBody, err := ioutil.ReadAll(req.Body) 753 Expect(err).NotTo(HaveOccurred()) 754 body := BufferWithBytes(rawBody) 755 Expect(body).To(Say("--%s", boundary)) 756 Expect(body).To(Say(`name="bits"`)) 757 Expect(body).To(Say(contents)) 758 Expect(body).To(Say("--%s--", boundary)) 759 } 760 761 response := `{ 762 "guid": "some-pkg-guid", 763 "state": "PROCESSING_UPLOAD", 764 "links": { 765 "upload": { 766 "href": "some-package-upload-url", 767 "method": "POST" 768 } 769 } 770 }` 771 772 server.AppendHandlers( 773 CombineHandlers( 774 VerifyRequest(http.MethodPost, "/v3/my-special-endpoint/some-pkg-guid/upload"), 775 verifyHeaderAndBody, 776 RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 777 ), 778 ) 779 }) 780 781 AfterEach(func() { 782 if tempFile != nil { 783 Expect(os.RemoveAll(tempFile.Name())).ToNot(HaveOccurred()) 784 } 785 }) 786 787 It("returns the created package and warnings", func() { 788 Expect(executeErr).NotTo(HaveOccurred()) 789 790 expectedPackage := Package{ 791 GUID: "some-pkg-guid", 792 State: constant.PackageProcessingUpload, 793 Links: map[string]APILink{ 794 "upload": APILink{HREF: "some-package-upload-url", Method: http.MethodPost}, 795 }, 796 } 797 Expect(pkg).To(Equal(expectedPackage)) 798 Expect(warnings).To(ConsistOf("this is a warning")) 799 }) 800 }) 801 802 When("the package does not have an upload link", func() { 803 BeforeEach(func() { 804 inputPackage = Package{GUID: "some-pkg-guid", State: constant.PackageAwaitingUpload} 805 fileToUpload = "/path/to/foo" 806 }) 807 808 It("returns an UploadLinkNotFoundError", func() { 809 Expect(executeErr).To(MatchError(ccerror.UploadLinkNotFoundError{PackageGUID: "some-pkg-guid"})) 810 }) 811 }) 812 813 When("cc returns back an error or warnings", func() { 814 var tempFile *os.File 815 816 BeforeEach(func() { 817 var err error 818 819 inputPackage = Package{ 820 State: constant.PackageAwaitingUpload, 821 Links: map[string]APILink{ 822 "upload": APILink{ 823 HREF: fmt.Sprintf("%s/v3/my-special-endpoint/some-pkg-guid/upload", server.URL()), 824 Method: http.MethodPost, 825 }, 826 }, 827 } 828 829 tempFile, err = ioutil.TempFile("", "package-upload") 830 Expect(err).ToNot(HaveOccurred()) 831 defer tempFile.Close() 832 833 fileToUpload = tempFile.Name() 834 835 fileSize := 1024 836 contents := strings.Repeat("A", fileSize) 837 err = ioutil.WriteFile(tempFile.Name(), []byte(contents), 0666) 838 Expect(err).NotTo(HaveOccurred()) 839 840 response := ` { 841 "errors": [ 842 { 843 "code": 10008, 844 "detail": "The request is semantically invalid: command presence", 845 "title": "CF-UnprocessableEntity" 846 }, 847 { 848 "code": 10008, 849 "detail": "The request is semantically invalid: command presence", 850 "title": "CF-UnprocessableEntity" 851 } 852 ] 853 }` 854 855 server.AppendHandlers( 856 CombineHandlers( 857 VerifyRequest(http.MethodPost, "/v3/my-special-endpoint/some-pkg-guid/upload"), 858 RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 859 ), 860 ) 861 }) 862 863 AfterEach(func() { 864 if tempFile != nil { 865 Expect(os.RemoveAll(tempFile.Name())).ToNot(HaveOccurred()) 866 } 867 }) 868 869 It("returns the error and all warnings", func() { 870 Expect(executeErr).To(MatchError(ccerror.MultiError{ 871 ResponseCode: http.StatusTeapot, 872 Errors: []ccerror.V3Error{ 873 { 874 Code: 10008, 875 Detail: "The request is semantically invalid: command presence", 876 Title: "CF-UnprocessableEntity", 877 }, 878 { 879 Code: 10008, 880 Detail: "The request is semantically invalid: command presence", 881 Title: "CF-UnprocessableEntity", 882 }, 883 }, 884 })) 885 Expect(warnings).To(ConsistOf("this is a warning")) 886 }) 887 888 }) 889 }) 890 })