github.com/pivotal-cf/go-pivnet/v6@v6.0.2/product_files_test.go (about) 1 package pivnet_test 2 3 import ( 4 "fmt" 5 "github.com/pivotal-cf/go-pivnet/v6/go-pivnetfakes" 6 "io/ioutil" 7 "net/http" 8 "regexp" 9 "strconv" 10 11 "github.com/onsi/gomega/ghttp" 12 "github.com/pivotal-cf/go-pivnet/v6" 13 "github.com/pivotal-cf/go-pivnet/v6/logger" 14 "github.com/pivotal-cf/go-pivnet/v6/logger/loggerfakes" 15 16 . "github.com/onsi/ginkgo" 17 . "github.com/onsi/gomega" 18 "github.com/pivotal-cf/go-pivnet/v6/download" 19 ) 20 21 var _ = Describe("PivnetClient - product files", func() { 22 var ( 23 server *ghttp.Server 24 client pivnet.Client 25 apiAddress string 26 userAgent string 27 28 newClientConfig pivnet.ClientConfig 29 fakeLogger logger.Logger 30 fakeAccessTokenService *gopivnetfakes.FakeAccessTokenService 31 ) 32 33 BeforeEach(func() { 34 server = ghttp.NewServer() 35 apiAddress = server.URL() 36 userAgent = "pivnet-resource/0.1.0 (some-url)" 37 38 fakeLogger = &loggerfakes.FakeLogger{} 39 fakeAccessTokenService = &gopivnetfakes.FakeAccessTokenService{} 40 newClientConfig = pivnet.ClientConfig{ 41 Host: apiAddress, 42 UserAgent: userAgent, 43 } 44 client = pivnet.NewClient(fakeAccessTokenService, newClientConfig, fakeLogger) 45 }) 46 47 AfterEach(func() { 48 server.Close() 49 }) 50 51 Describe("List product files", func() { 52 var ( 53 productSlug string 54 55 response interface{} 56 responseStatusCode int 57 ) 58 59 BeforeEach(func() { 60 productSlug = "banana" 61 62 response = pivnet.ProductFilesResponse{[]pivnet.ProductFile{ 63 { 64 ID: 1234, 65 AWSObjectKey: "something", 66 }, 67 { 68 ID: 2345, 69 AWSObjectKey: "something-else", 70 }, 71 }} 72 73 responseStatusCode = http.StatusOK 74 }) 75 76 JustBeforeEach(func() { 77 server.AppendHandlers( 78 ghttp.CombineHandlers( 79 ghttp.VerifyRequest( 80 "GET", 81 fmt.Sprintf( 82 "%s/products/%s/product_files", 83 apiPrefix, 84 productSlug, 85 ), 86 ), 87 ghttp.RespondWithJSONEncoded(responseStatusCode, response), 88 ), 89 ) 90 }) 91 92 It("returns the product files without error", func() { 93 productFiles, err := client.ProductFiles.List( 94 productSlug, 95 ) 96 Expect(err).NotTo(HaveOccurred()) 97 98 Expect(productFiles).To(HaveLen(2)) 99 Expect(productFiles[0].ID).To(Equal(1234)) 100 }) 101 102 Context("when the server responds with a non-2XX status code", func() { 103 BeforeEach(func() { 104 responseStatusCode = http.StatusTeapot 105 response = pivnetErr{Message: "foo message"} 106 }) 107 108 It("returns an error", func() { 109 _, err := client.ProductFiles.List( 110 productSlug, 111 ) 112 Expect(err).To(HaveOccurred()) 113 114 Expect(err.Error()).To(ContainSubstring("foo message")) 115 }) 116 }) 117 118 Context("when the json unmarshalling fails with error", func() { 119 BeforeEach(func() { 120 response = "%%%" 121 }) 122 123 It("forwards the error", func() { 124 _, err := client.ProductFiles.List( 125 productSlug, 126 ) 127 Expect(err).To(HaveOccurred()) 128 129 Expect(err.Error()).To(ContainSubstring("json")) 130 }) 131 }) 132 }) 133 134 Describe("List product files for release", func() { 135 var ( 136 productSlug string 137 releaseID int 138 139 response interface{} 140 responseStatusCode int 141 ) 142 143 BeforeEach(func() { 144 productSlug = "banana" 145 releaseID = 12 146 147 response = pivnet.ProductFilesResponse{[]pivnet.ProductFile{ 148 { 149 ID: 1234, 150 AWSObjectKey: "something", 151 Links: &pivnet.Links{Download: map[string]string{ 152 "href": fmt.Sprintf( 153 "/products/%s/releases/%d/product_files/%d/download", 154 productSlug, 155 releaseID, 156 1234, 157 )}, 158 }, 159 }, 160 { 161 ID: 2345, 162 AWSObjectKey: "something-else", 163 }, 164 }} 165 166 responseStatusCode = http.StatusOK 167 }) 168 169 JustBeforeEach(func() { 170 server.AppendHandlers( 171 ghttp.CombineHandlers( 172 ghttp.VerifyRequest( 173 "GET", 174 fmt.Sprintf( 175 "%s/products/%s/releases/%d/product_files", 176 apiPrefix, 177 productSlug, 178 releaseID, 179 ), 180 ), 181 ghttp.RespondWithJSONEncoded(responseStatusCode, response), 182 ), 183 ) 184 }) 185 186 It("returns the product files without error", func() { 187 productFiles, err := client.ProductFiles.ListForRelease( 188 productSlug, 189 releaseID, 190 ) 191 Expect(err).NotTo(HaveOccurred()) 192 193 Expect(productFiles).To(HaveLen(2)) 194 Expect(productFiles[0].ID).To(Equal(1234)) 195 }) 196 197 Context("when the server responds with a non-2XX status code", func() { 198 BeforeEach(func() { 199 responseStatusCode = http.StatusTeapot 200 response = pivnetErr{Message: "foo message"} 201 }) 202 203 It("returns an error", func() { 204 _, err := client.ProductFiles.ListForRelease( 205 productSlug, 206 releaseID, 207 ) 208 Expect(err).To(HaveOccurred()) 209 210 Expect(err.Error()).To(ContainSubstring("foo message")) 211 }) 212 }) 213 214 Context("when the json unmarshalling fails with error", func() { 215 BeforeEach(func() { 216 response = "%%%" 217 }) 218 219 It("forwards the error", func() { 220 _, err := client.ProductFiles.ListForRelease( 221 productSlug, 222 releaseID, 223 ) 224 Expect(err).To(HaveOccurred()) 225 226 Expect(err.Error()).To(ContainSubstring("json")) 227 }) 228 }) 229 }) 230 231 Describe("Get Product File", func() { 232 var ( 233 productSlug string 234 productFileID int 235 236 response interface{} 237 responseStatusCode int 238 ) 239 240 BeforeEach(func() { 241 productSlug = "banana" 242 productFileID = 1234 243 244 response = pivnet.ProductFileResponse{ 245 ProductFile: pivnet.ProductFile{ 246 ID: productFileID, 247 AWSObjectKey: "something", 248 }} 249 250 responseStatusCode = http.StatusOK 251 }) 252 253 JustBeforeEach(func() { 254 server.AppendHandlers( 255 ghttp.CombineHandlers( 256 ghttp.VerifyRequest( 257 "GET", 258 fmt.Sprintf( 259 "%s/products/%s/product_files/%d", 260 apiPrefix, 261 productSlug, 262 productFileID, 263 ), 264 ), 265 ghttp.RespondWithJSONEncoded(responseStatusCode, response), 266 ), 267 ) 268 }) 269 270 It("returns the product file without error", func() { 271 productFile, err := client.ProductFiles.Get( 272 productSlug, 273 productFileID, 274 ) 275 Expect(err).NotTo(HaveOccurred()) 276 277 Expect(productFile.ID).To(Equal(productFileID)) 278 Expect(productFile.AWSObjectKey).To(Equal("something")) 279 }) 280 281 Context("when the server responds with a non-2XX status code", func() { 282 BeforeEach(func() { 283 responseStatusCode = http.StatusTeapot 284 response = pivnetErr{Message: "foo message"} 285 }) 286 287 It("returns an error", func() { 288 _, err := client.ProductFiles.Get( 289 productSlug, 290 productFileID, 291 ) 292 Expect(err).To(HaveOccurred()) 293 294 Expect(err.Error()).To(ContainSubstring("foo message")) 295 }) 296 }) 297 298 Context("when the json unmarshalling fails with error", func() { 299 BeforeEach(func() { 300 response = "%%%" 301 }) 302 303 It("forwards the error", func() { 304 _, err := client.ProductFiles.Get( 305 productSlug, 306 productFileID, 307 ) 308 Expect(err).To(HaveOccurred()) 309 310 Expect(err.Error()).To(ContainSubstring("json")) 311 }) 312 }) 313 }) 314 315 Describe("Get product file for release", func() { 316 var ( 317 productSlug string 318 releaseID int 319 productFileID int 320 321 response interface{} 322 responseStatusCode int 323 ) 324 325 BeforeEach(func() { 326 productSlug = "banana" 327 releaseID = 12 328 productFileID = 1234 329 330 response = pivnet.ProductFileResponse{ 331 ProductFile: pivnet.ProductFile{ 332 ID: productFileID, 333 AWSObjectKey: "something", 334 Links: &pivnet.Links{Download: map[string]string{ 335 "href": fmt.Sprintf( 336 "/products/%s/releases/%d/product_files/%d/download", 337 productSlug, 338 releaseID, 339 productFileID, 340 )}, 341 }, 342 }} 343 344 responseStatusCode = http.StatusOK 345 }) 346 347 JustBeforeEach(func() { 348 server.AppendHandlers( 349 ghttp.CombineHandlers( 350 ghttp.VerifyRequest( 351 "GET", 352 fmt.Sprintf( 353 "%s/products/%s/releases/%d/product_files/%d", 354 apiPrefix, 355 productSlug, 356 releaseID, 357 productFileID, 358 ), 359 ), 360 ghttp.RespondWithJSONEncoded(responseStatusCode, response), 361 ), 362 ) 363 }) 364 365 It("returns the product file without error", func() { 366 productFile, err := client.ProductFiles.GetForRelease( 367 productSlug, 368 releaseID, 369 productFileID, 370 ) 371 Expect(err).NotTo(HaveOccurred()) 372 373 Expect(productFile.ID).To(Equal(productFileID)) 374 Expect(productFile.AWSObjectKey).To(Equal("something")) 375 376 Expect(productFile.Links.Download["href"]). 377 To(Equal(fmt.Sprintf( 378 "/products/%s/releases/%d/product_files/%d/download", 379 productSlug, 380 releaseID, 381 productFileID, 382 ))) 383 }) 384 385 Context("when the server responds with a non-2XX status code", func() { 386 BeforeEach(func() { 387 responseStatusCode = http.StatusTeapot 388 response = pivnetErr{Message: "foo message"} 389 }) 390 391 It("returns an error", func() { 392 _, err := client.ProductFiles.GetForRelease( 393 productSlug, 394 releaseID, 395 productFileID, 396 ) 397 Expect(err).To(HaveOccurred()) 398 399 Expect(err.Error()).To(ContainSubstring("foo message")) 400 }) 401 }) 402 403 Context("when the json unmarshalling fails with error", func() { 404 BeforeEach(func() { 405 response = "%%%" 406 }) 407 408 It("forwards the error", func() { 409 _, err := client.ProductFiles.GetForRelease( 410 productSlug, 411 releaseID, 412 productFileID, 413 ) 414 Expect(err).To(HaveOccurred()) 415 416 Expect(err.Error()).To(ContainSubstring("json")) 417 }) 418 }) 419 }) 420 421 Describe("Create Product File", func() { 422 type requestBody struct { 423 ProductFile pivnet.ProductFile `json:"product_file"` 424 } 425 426 var ( 427 createProductFileConfig pivnet.CreateProductFileConfig 428 429 expectedRequestBody requestBody 430 431 productFileResponse pivnet.ProductFileResponse 432 ) 433 434 BeforeEach(func() { 435 createProductFileConfig = pivnet.CreateProductFileConfig{ 436 ProductSlug: productSlug, 437 AWSObjectKey: "some-aws-object-key", 438 Description: "some\nmulti-line\ndescription", 439 DocsURL: "some-docs-url", 440 FileType: "some-file-type", 441 FileVersion: "some-file-version", 442 IncludedFiles: []string{"file1", "file2"}, 443 SHA256: "some-sha256", 444 MD5: "some-md5", 445 Name: "some-file-name", 446 Platforms: []string{"platform-1", "platform-2"}, 447 ReleasedAt: "released-at", 448 SystemRequirements: []string{"system-1", "system-2"}, 449 } 450 451 expectedRequestBody = requestBody{ 452 ProductFile: pivnet.ProductFile{ 453 AWSObjectKey: createProductFileConfig.AWSObjectKey, 454 Description: createProductFileConfig.Description, 455 DocsURL: createProductFileConfig.DocsURL, 456 FileType: createProductFileConfig.FileType, 457 FileVersion: createProductFileConfig.FileVersion, 458 IncludedFiles: createProductFileConfig.IncludedFiles, 459 SHA256: createProductFileConfig.SHA256, 460 MD5: createProductFileConfig.MD5, 461 Name: createProductFileConfig.Name, 462 Platforms: createProductFileConfig.Platforms, 463 ReleasedAt: createProductFileConfig.ReleasedAt, 464 SystemRequirements: createProductFileConfig.SystemRequirements, 465 }, 466 } 467 468 productFileResponse = pivnet.ProductFileResponse{ 469 ProductFile: pivnet.ProductFile{ 470 ID: 1234, 471 AWSObjectKey: createProductFileConfig.AWSObjectKey, 472 Description: createProductFileConfig.Description, 473 DocsURL: createProductFileConfig.DocsURL, 474 FileType: createProductFileConfig.FileType, 475 FileVersion: createProductFileConfig.FileVersion, 476 IncludedFiles: createProductFileConfig.IncludedFiles, 477 SHA256: createProductFileConfig.SHA256, 478 MD5: createProductFileConfig.MD5, 479 Name: createProductFileConfig.Name, 480 Platforms: createProductFileConfig.Platforms, 481 ReleasedAt: createProductFileConfig.ReleasedAt, 482 SystemRequirements: createProductFileConfig.SystemRequirements, 483 }} 484 }) 485 486 It("creates the product file", func() { 487 server.AppendHandlers( 488 ghttp.CombineHandlers( 489 ghttp.VerifyRequest("POST", fmt.Sprintf( 490 "%s/products/%s/product_files", 491 apiPrefix, 492 productSlug, 493 )), 494 ghttp.VerifyJSONRepresenting(&expectedRequestBody), 495 ghttp.RespondWithJSONEncoded(http.StatusCreated, productFileResponse), 496 ), 497 ) 498 499 productFile, err := client.ProductFiles.Create(createProductFileConfig) 500 Expect(err).NotTo(HaveOccurred()) 501 Expect(productFile.ID).To(Equal(1234)) 502 Expect(productFile).To(Equal(productFileResponse.ProductFile)) 503 }) 504 505 Context("when the server responds with a non-201 status code", func() { 506 var ( 507 response interface{} 508 ) 509 510 BeforeEach(func() { 511 response = pivnetErr{Message: "foo message"} 512 }) 513 514 It("returns an error", func() { 515 server.AppendHandlers( 516 ghttp.CombineHandlers( 517 ghttp.VerifyRequest("POST", fmt.Sprintf( 518 "%s/products/%s/product_files", 519 apiPrefix, 520 productSlug, 521 )), 522 ghttp.RespondWithJSONEncoded(http.StatusTeapot, response), 523 ), 524 ) 525 526 _, err := client.ProductFiles.Create(createProductFileConfig) 527 Expect(err.Error()).To(ContainSubstring("foo message")) 528 }) 529 }) 530 531 Context("when the server responds with a 429 status code", func() { 532 It("returns an error indicating the limit was hit", func() { 533 server.AppendHandlers( 534 ghttp.CombineHandlers( 535 ghttp.VerifyRequest("POST", fmt.Sprintf( 536 "%s/products/%s/product_files", 537 apiPrefix, 538 productSlug, 539 )), 540 ghttp.RespondWith(http.StatusTooManyRequests, "Retry later"), 541 ), 542 ) 543 544 _, err := client.ProductFiles.Create(createProductFileConfig) 545 Expect(err.Error()).To(ContainSubstring("You have hit the file creation limit. Please wait before creating more files. Contact pivnet-eng@pivotal.io with additional questions.")) 546 }) 547 }) 548 549 Context("when the json unmarshalling fails with error", func() { 550 It("forwards the error", func() { 551 server.AppendHandlers( 552 ghttp.CombineHandlers( 553 ghttp.VerifyRequest("POST", fmt.Sprintf( 554 "%s/products/%s/product_files", 555 apiPrefix, 556 productSlug, 557 )), 558 ghttp.RespondWith(http.StatusTeapot, "%%%"), 559 ), 560 ) 561 562 _, err := client.ProductFiles.Create(createProductFileConfig) 563 Expect(err).To(HaveOccurred()) 564 565 Expect(err.Error()).To(ContainSubstring("invalid character")) 566 }) 567 }) 568 569 Context("when the aws object key is empty", func() { 570 BeforeEach(func() { 571 createProductFileConfig = pivnet.CreateProductFileConfig{ 572 ProductSlug: productSlug, 573 Name: "some-file-name", 574 FileVersion: "some-file-version", 575 AWSObjectKey: "", 576 } 577 }) 578 579 It("returns an error", func() { 580 _, err := client.ProductFiles.Create(createProductFileConfig) 581 Expect(err).To(HaveOccurred()) 582 583 Expect(err.Error()).To(ContainSubstring("AWS object key")) 584 }) 585 }) 586 }) 587 588 Describe("Update Product File", func() { 589 type requestBody struct { 590 ProductFile pivnet.ProductFile `json:"product_file"` 591 } 592 593 var ( 594 expectedRequestBody requestBody 595 596 productFile pivnet.ProductFile 597 598 validResponse = `{"product_file":{"id":1234,"docs_url":"http://self-docs.com/","system_requirements": ["1", "2"]}}` 599 ) 600 601 BeforeEach(func() { 602 productFile = pivnet.ProductFile{ 603 ID: 1234, 604 Description: "some-description", 605 FileVersion: "some-file-version", 606 SHA256: "some-sha256", 607 MD5: "some-md5", 608 Name: "some-file-name", 609 DocsURL: "http://self-docs.com/", 610 SystemRequirements: []string{"1", "2"}, 611 } 612 613 expectedRequestBody = requestBody{ 614 ProductFile: pivnet.ProductFile{ 615 Description: productFile.Description, 616 FileVersion: productFile.FileVersion, 617 SHA256: productFile.SHA256, 618 MD5: productFile.MD5, 619 Name: productFile.Name, 620 DocsURL: productFile.DocsURL, 621 SystemRequirements: productFile.SystemRequirements, 622 }, 623 } 624 }) 625 626 It("updates the product file with the provided fields", func() { 627 server.AppendHandlers( 628 ghttp.CombineHandlers( 629 ghttp.VerifyRequest("PATCH", fmt.Sprintf( 630 "%s/products/%s/product_files/%d", 631 apiPrefix, 632 productSlug, 633 productFile.ID, 634 )), 635 ghttp.VerifyJSONRepresenting(&expectedRequestBody), 636 ghttp.RespondWith(http.StatusOK, validResponse), 637 ), 638 ) 639 640 updatedProductFile, err := client.ProductFiles.Update(productSlug, productFile) 641 Expect(err).NotTo(HaveOccurred()) 642 Expect(updatedProductFile.ID).To(Equal(productFile.ID)) 643 Expect(updatedProductFile.DocsURL).To(Equal(productFile.DocsURL)) 644 Expect(updatedProductFile.SystemRequirements).To(ConsistOf("2", "1")) 645 }) 646 647 Context("when the server responds with a non-200 status code", func() { 648 var ( 649 response interface{} 650 ) 651 652 BeforeEach(func() { 653 response = pivnetErr{Message: "foo message"} 654 }) 655 656 It("returns an error", func() { 657 server.AppendHandlers( 658 ghttp.CombineHandlers( 659 ghttp.VerifyRequest("PATCH", fmt.Sprintf( 660 "%s/products/%s/product_files/%d", 661 apiPrefix, 662 productSlug, 663 productFile.ID, 664 )), 665 ghttp.RespondWithJSONEncoded(http.StatusTeapot, response), 666 ), 667 ) 668 669 _, err := client.ProductFiles.Update(productSlug, productFile) 670 671 Expect(err).To(HaveOccurred()) 672 Expect(err.Error()).To(ContainSubstring("foo message")) 673 }) 674 }) 675 676 Context("when the json unmarshalling fails with error", func() { 677 It("forwards the error", func() { 678 server.AppendHandlers( 679 ghttp.CombineHandlers( 680 ghttp.VerifyRequest("PATCH", fmt.Sprintf( 681 "%s/products/%s/product_files/%d", 682 apiPrefix, 683 productSlug, 684 productFile.ID, 685 )), 686 ghttp.RespondWith(http.StatusTeapot, "%%%"), 687 ), 688 ) 689 690 _, err := client.ProductFiles.Update(productSlug, productFile) 691 Expect(err).To(HaveOccurred()) 692 693 Expect(err.Error()).To(ContainSubstring("invalid character")) 694 }) 695 }) 696 }) 697 698 Describe("Delete Product File", func() { 699 var ( 700 id = 1234 701 ) 702 703 It("deletes the product file", func() { 704 response := []byte(`{"product_file":{"id":1234}}`) 705 706 server.AppendHandlers( 707 ghttp.CombineHandlers( 708 ghttp.VerifyRequest( 709 "DELETE", 710 fmt.Sprintf("%s/products/%s/product_files/%d", apiPrefix, productSlug, id)), 711 ghttp.RespondWith(http.StatusOK, response), 712 ), 713 ) 714 715 productFile, err := client.ProductFiles.Delete(productSlug, id) 716 Expect(err).NotTo(HaveOccurred()) 717 718 Expect(productFile.ID).To(Equal(id)) 719 }) 720 721 Context("when the server responds with a non-2XX status code", func() { 722 var ( 723 response interface{} 724 ) 725 726 BeforeEach(func() { 727 response = pivnetErr{Message: "foo message"} 728 }) 729 730 It("returns an error", func() { 731 server.AppendHandlers( 732 ghttp.CombineHandlers( 733 ghttp.VerifyRequest( 734 "DELETE", 735 fmt.Sprintf("%s/products/%s/product_files/%d", apiPrefix, productSlug, id)), 736 ghttp.RespondWithJSONEncoded(http.StatusTeapot, response), 737 ), 738 ) 739 740 _, err := client.ProductFiles.Delete(productSlug, id) 741 Expect(err.Error()).To(ContainSubstring("foo message")) 742 }) 743 }) 744 745 Context("when the json unmarshalling fails with error", func() { 746 It("forwards the error", func() { 747 server.AppendHandlers( 748 ghttp.CombineHandlers( 749 ghttp.VerifyRequest( 750 "DELETE", 751 fmt.Sprintf("%s/products/%s/product_files/%d", apiPrefix, productSlug, id)), 752 ghttp.RespondWith(http.StatusTeapot, "%%%"), 753 ), 754 ) 755 756 _, err := client.ProductFiles.Delete(productSlug, id) 757 Expect(err).To(HaveOccurred()) 758 759 Expect(err.Error()).To(ContainSubstring("invalid character")) 760 }) 761 }) 762 }) 763 764 Describe("Add Product File to release", func() { 765 var ( 766 productSlug = "some-product" 767 releaseID = 2345 768 productFileID = 3456 769 770 expectedRequestBody = `{"product_file":{"id":3456}}` 771 ) 772 773 Context("when the server responds with a 204 status code", func() { 774 It("returns without error", func() { 775 server.AppendHandlers( 776 ghttp.CombineHandlers( 777 ghttp.VerifyRequest("PATCH", fmt.Sprintf( 778 "%s/products/%s/releases/%d/add_product_file", 779 apiPrefix, 780 productSlug, 781 releaseID, 782 )), 783 ghttp.VerifyJSON(expectedRequestBody), 784 ghttp.RespondWith(http.StatusNoContent, nil), 785 ), 786 ) 787 788 err := client.ProductFiles.AddToRelease(productSlug, releaseID, productFileID) 789 Expect(err).NotTo(HaveOccurred()) 790 }) 791 }) 792 793 Context("when the server responds with a non-204 status code", func() { 794 var ( 795 response interface{} 796 ) 797 798 BeforeEach(func() { 799 response = pivnetErr{Message: "foo message"} 800 }) 801 802 It("returns an error", func() { 803 server.AppendHandlers( 804 ghttp.CombineHandlers( 805 ghttp.VerifyRequest("PATCH", fmt.Sprintf( 806 "%s/products/%s/releases/%d/add_product_file", 807 apiPrefix, 808 productSlug, 809 releaseID, 810 )), 811 ghttp.RespondWithJSONEncoded(http.StatusTeapot, response), 812 ), 813 ) 814 815 err := client.ProductFiles.AddToRelease(productSlug, releaseID, productFileID) 816 Expect(err.Error()).To(ContainSubstring("foo message")) 817 }) 818 }) 819 820 Context("when the json unmarshalling fails with error", func() { 821 It("forwards the error", func() { 822 server.AppendHandlers( 823 ghttp.CombineHandlers( 824 ghttp.VerifyRequest("PATCH", fmt.Sprintf( 825 "%s/products/%s/releases/%d/add_product_file", 826 apiPrefix, 827 productSlug, 828 releaseID, 829 )), 830 ghttp.RespondWith(http.StatusTeapot, "%%%"), 831 ), 832 ) 833 834 err := client.ProductFiles.AddToRelease(productSlug, releaseID, productFileID) 835 Expect(err).To(HaveOccurred()) 836 837 Expect(err.Error()).To(ContainSubstring("invalid character")) 838 }) 839 }) 840 }) 841 842 Describe("Remove Product File from release", func() { 843 var ( 844 productSlug = "some-product" 845 releaseID = 2345 846 productFileID = 3456 847 848 expectedRequestBody = `{"product_file":{"id":3456}}` 849 ) 850 851 Context("when the server responds with a 204 status code", func() { 852 It("returns without error", func() { 853 server.AppendHandlers( 854 ghttp.CombineHandlers( 855 ghttp.VerifyRequest("PATCH", fmt.Sprintf( 856 "%s/products/%s/releases/%d/remove_product_file", 857 apiPrefix, 858 productSlug, 859 releaseID, 860 )), 861 ghttp.VerifyJSON(expectedRequestBody), 862 ghttp.RespondWith(http.StatusNoContent, nil), 863 ), 864 ) 865 866 err := client.ProductFiles.RemoveFromRelease(productSlug, releaseID, productFileID) 867 Expect(err).NotTo(HaveOccurred()) 868 }) 869 }) 870 871 Context("when the server responds with a non-204 status code", func() { 872 var ( 873 response interface{} 874 ) 875 876 BeforeEach(func() { 877 response = pivnetErr{Message: "foo message"} 878 }) 879 880 It("returns an error", func() { 881 server.AppendHandlers( 882 ghttp.CombineHandlers( 883 ghttp.VerifyRequest("PATCH", fmt.Sprintf( 884 "%s/products/%s/releases/%d/remove_product_file", 885 apiPrefix, 886 productSlug, 887 releaseID, 888 )), 889 ghttp.RespondWithJSONEncoded(http.StatusTeapot, response), 890 ), 891 ) 892 893 err := client.ProductFiles.RemoveFromRelease(productSlug, releaseID, productFileID) 894 Expect(err.Error()).To(ContainSubstring("foo message")) 895 }) 896 }) 897 898 Context("when the json unmarshalling fails with error", func() { 899 It("forwards the error", func() { 900 server.AppendHandlers( 901 ghttp.CombineHandlers( 902 ghttp.VerifyRequest("PATCH", fmt.Sprintf( 903 "%s/products/%s/releases/%d/remove_product_file", 904 apiPrefix, 905 productSlug, 906 releaseID, 907 )), 908 ghttp.RespondWith(http.StatusTeapot, "%%%"), 909 ), 910 ) 911 912 err := client.ProductFiles.RemoveFromRelease(productSlug, releaseID, productFileID) 913 Expect(err).To(HaveOccurred()) 914 915 Expect(err.Error()).To(ContainSubstring("invalid character")) 916 }) 917 }) 918 }) 919 920 Describe("Add Product File to file group", func() { 921 var ( 922 productSlug = "some-product" 923 fileGroupID = 2345 924 productFileID = 3456 925 926 expectedRequestBody = `{"product_file":{"id":3456}}` 927 ) 928 929 Context("when the server responds with a 204 status code", func() { 930 It("returns without error", func() { 931 server.AppendHandlers( 932 ghttp.CombineHandlers( 933 ghttp.VerifyRequest("PATCH", fmt.Sprintf( 934 "%s/products/%s/file_groups/%d/add_product_file", 935 apiPrefix, 936 productSlug, 937 fileGroupID, 938 )), 939 ghttp.VerifyJSON(expectedRequestBody), 940 ghttp.RespondWith(http.StatusNoContent, nil), 941 ), 942 ) 943 944 err := client.ProductFiles.AddToFileGroup(productSlug, fileGroupID, productFileID) 945 Expect(err).NotTo(HaveOccurred()) 946 }) 947 }) 948 949 Context("when the server responds with a non-204 status code", func() { 950 var ( 951 response interface{} 952 ) 953 954 BeforeEach(func() { 955 response = pivnetErr{Message: "foo message"} 956 }) 957 958 It("returns an error", func() { 959 server.AppendHandlers( 960 ghttp.CombineHandlers( 961 ghttp.VerifyRequest("PATCH", fmt.Sprintf( 962 "%s/products/%s/file_groups/%d/add_product_file", 963 apiPrefix, 964 productSlug, 965 fileGroupID, 966 )), 967 ghttp.RespondWithJSONEncoded(http.StatusTeapot, response), 968 ), 969 ) 970 971 err := client.ProductFiles.AddToFileGroup(productSlug, fileGroupID, productFileID) 972 Expect(err.Error()).To(ContainSubstring("foo message")) 973 }) 974 }) 975 976 Context("when the json unmarshalling fails with error", func() { 977 It("forwards the error", func() { 978 server.AppendHandlers( 979 ghttp.CombineHandlers( 980 ghttp.VerifyRequest("PATCH", fmt.Sprintf( 981 "%s/products/%s/file_groups/%d/add_product_file", 982 apiPrefix, 983 productSlug, 984 fileGroupID, 985 )), 986 ghttp.RespondWith(http.StatusTeapot, "%%%"), 987 ), 988 ) 989 990 err := client.ProductFiles.AddToFileGroup(productSlug, fileGroupID, productFileID) 991 Expect(err).To(HaveOccurred()) 992 993 Expect(err.Error()).To(ContainSubstring("invalid character")) 994 }) 995 }) 996 }) 997 998 Describe("Remove Product File from file group", func() { 999 var ( 1000 productSlug = "some-product" 1001 fileGroupID = 2345 1002 productFileID = 3456 1003 1004 expectedRequestBody = `{"product_file":{"id":3456}}` 1005 ) 1006 1007 Context("when the server responds with a 204 status code", func() { 1008 It("returns without error", func() { 1009 server.AppendHandlers( 1010 ghttp.CombineHandlers( 1011 ghttp.VerifyRequest("PATCH", fmt.Sprintf( 1012 "%s/products/%s/file_groups/%d/remove_product_file", 1013 apiPrefix, 1014 productSlug, 1015 fileGroupID, 1016 )), 1017 ghttp.VerifyJSON(expectedRequestBody), 1018 ghttp.RespondWith(http.StatusNoContent, nil), 1019 ), 1020 ) 1021 1022 err := client.ProductFiles.RemoveFromFileGroup(productSlug, fileGroupID, productFileID) 1023 Expect(err).NotTo(HaveOccurred()) 1024 }) 1025 }) 1026 1027 Context("when the server responds with a non-204 status code", func() { 1028 var ( 1029 response interface{} 1030 ) 1031 1032 BeforeEach(func() { 1033 response = pivnetErr{Message: "foo message"} 1034 }) 1035 1036 It("returns an error", func() { 1037 server.AppendHandlers( 1038 ghttp.CombineHandlers( 1039 ghttp.VerifyRequest("PATCH", fmt.Sprintf( 1040 "%s/products/%s/file_groups/%d/remove_product_file", 1041 apiPrefix, 1042 productSlug, 1043 fileGroupID, 1044 )), 1045 ghttp.RespondWithJSONEncoded(http.StatusTeapot, response), 1046 ), 1047 ) 1048 1049 err := client.ProductFiles.RemoveFromFileGroup(productSlug, fileGroupID, productFileID) 1050 Expect(err.Error()).To(ContainSubstring("foo message")) 1051 }) 1052 }) 1053 1054 Context("when the json unmarshalling fails with error", func() { 1055 It("forwards the error", func() { 1056 server.AppendHandlers( 1057 ghttp.CombineHandlers( 1058 ghttp.VerifyRequest("PATCH", fmt.Sprintf( 1059 "%s/products/%s/file_groups/%d/remove_product_file", 1060 apiPrefix, 1061 productSlug, 1062 fileGroupID, 1063 )), 1064 ghttp.RespondWith(http.StatusTeapot, "%%%"), 1065 ), 1066 ) 1067 1068 err := client.ProductFiles.RemoveFromFileGroup(productSlug, fileGroupID, productFileID) 1069 Expect(err).To(HaveOccurred()) 1070 1071 Expect(err.Error()).To(ContainSubstring("invalid character")) 1072 }) 1073 }) 1074 }) 1075 1076 Describe("ProductFile methods", func() { 1077 var ( 1078 productFile pivnet.ProductFile 1079 ) 1080 1081 BeforeEach(func() { 1082 productFile = pivnet.ProductFile{} 1083 }) 1084 1085 Describe("DownloadLink", func() { 1086 var ( 1087 downloadLink string 1088 ) 1089 1090 BeforeEach(func() { 1091 downloadLink = "some link" 1092 1093 productFile.Links = &pivnet.Links{ 1094 Download: map[string]string{ 1095 "href": downloadLink, 1096 }, 1097 } 1098 }) 1099 1100 It("returns download link from links map", func() { 1101 dl, err := productFile.DownloadLink() 1102 Expect(err).NotTo(HaveOccurred()) 1103 1104 Expect(dl).To(Equal(downloadLink)) 1105 }) 1106 1107 Context("when links are nil", func() { 1108 BeforeEach(func() { 1109 productFile.Links = nil 1110 }) 1111 1112 It("returns error", func() { 1113 _, err := productFile.DownloadLink() 1114 Expect(err).To(HaveOccurred()) 1115 1116 Expect(err.Error()).To(ContainSubstring("empty")) 1117 }) 1118 }) 1119 }) 1120 }) 1121 1122 Describe("DownloadForRelease", func() { 1123 var ( 1124 cloudfront *ghttp.Server 1125 releaseID int 1126 productFileID int 1127 1128 downloadLink string 1129 cloudfrontDownloadLocation string 1130 1131 downloadLinkResponseBody []byte 1132 1133 getStatusCode int 1134 getResponse interface{} 1135 1136 downloadLinkResponseStatusCode int 1137 cloudfrontDownloadPath string 1138 ) 1139 1140 BeforeEach(func() { 1141 releaseID = 1234 1142 productFileID = 2345 1143 1144 downloadLink = "/some/download/link" 1145 1146 downloadLinkResponseBody = []byte("some file contents") 1147 1148 cloudfront = ghttp.NewServer() 1149 1150 cloudfrontDownloadLocation = fmt.Sprintf("%s/%s", cloudfront.URL(), "download") 1151 1152 getStatusCode = http.StatusOK 1153 getResponse = pivnet.ProductFileResponse{ 1154 pivnet.ProductFile{ 1155 ID: 1234, 1156 AWSObjectKey: "something", 1157 Links: &pivnet.Links{ 1158 Download: map[string]string{ 1159 "href": downloadLink, 1160 }, 1161 }, 1162 }, 1163 } 1164 1165 downloadLinkResponseStatusCode = http.StatusFound 1166 cloudfrontDownloadPath = "/download" 1167 }) 1168 1169 AfterEach(func() { 1170 cloudfront.Close() 1171 }) 1172 1173 JustBeforeEach(func() { 1174 server.AppendHandlers( 1175 ghttp.CombineHandlers( 1176 ghttp.VerifyRequest( 1177 "GET", 1178 fmt.Sprintf( 1179 "%s/products/%s/releases/%d/product_files/%d", 1180 apiPrefix, 1181 productSlug, 1182 releaseID, 1183 productFileID, 1184 ), 1185 ), 1186 ghttp.RespondWithJSONEncoded(getStatusCode, getResponse), 1187 ), 1188 ) 1189 1190 server.AppendHandlers( 1191 ghttp.CombineHandlers( 1192 ghttp.VerifyRequest("POST", fmt.Sprintf( 1193 "%s%s", 1194 apiPrefix, 1195 downloadLink, 1196 )), 1197 ghttp.RespondWith(downloadLinkResponseStatusCode, []byte(`{}`), 1198 http.Header{ 1199 "Location": []string{cloudfrontDownloadLocation}, 1200 }, 1201 ), 1202 ), 1203 ) 1204 1205 cloudfront.AppendHandlers( 1206 ghttp.CombineHandlers( 1207 ghttp.VerifyRequest("HEAD", "/download"), 1208 ghttp.RespondWith(http.StatusOK, nil, 1209 http.Header{ 1210 "Content-Length": []string{"18"}, 1211 }, 1212 ), 1213 ), 1214 ) 1215 1216 cloudfront.RouteToHandler("GET", cloudfrontDownloadPath, ghttp.CombineHandlers( 1217 http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 1218 ex := regexp.MustCompile(`bytes=(\d+)-(\d+)`) 1219 matches := ex.FindStringSubmatch(req.Header.Get("Range")) 1220 1221 start, err := strconv.Atoi(matches[1]) 1222 if err != nil { 1223 Fail(err.Error()) 1224 } 1225 1226 end, err := strconv.Atoi(matches[2]) 1227 if err != nil { 1228 Fail(err.Error()) 1229 } 1230 1231 w.WriteHeader(http.StatusPartialContent) 1232 _, err = w.Write(downloadLinkResponseBody[start : end+1]) 1233 Expect(err).NotTo(HaveOccurred()) 1234 }), 1235 ), 1236 ) 1237 }) 1238 1239 It("writes file contents to provided writer", func() { 1240 tmpFile, err := ioutil.TempFile("", "") 1241 Expect(err).NotTo(HaveOccurred()) 1242 1243 tmpLocation, err := download.NewFileInfo(tmpFile) 1244 Expect(err).NotTo(HaveOccurred()) 1245 1246 err = client.ProductFiles.DownloadForRelease( 1247 tmpLocation, 1248 productSlug, 1249 releaseID, 1250 productFileID, 1251 GinkgoWriter, 1252 ) 1253 Expect(err).NotTo(HaveOccurred()) 1254 1255 contents, err := ioutil.ReadFile(tmpFile.Name()) 1256 Expect(err).NotTo(HaveOccurred()) 1257 1258 Expect(contents).To(Equal(downloadLinkResponseBody)) 1259 }) 1260 1261 Context("when productFile.DownloadLink() returns an error", func() { 1262 BeforeEach(func() { 1263 getResponse = pivnet.ProductFileResponse{ 1264 pivnet.ProductFile{ 1265 ID: 1234, 1266 }, 1267 } 1268 }) 1269 1270 It("returns the error", func() { 1271 tmpFile, err := ioutil.TempFile("", "") 1272 Expect(err).NotTo(HaveOccurred()) 1273 1274 tmpLocation, err := download.NewFileInfo(tmpFile) 1275 Expect(err).NotTo(HaveOccurred()) 1276 1277 err = client.ProductFiles.DownloadForRelease( 1278 tmpLocation, 1279 productSlug, 1280 releaseID, 1281 productFileID, 1282 GinkgoWriter, 1283 ) 1284 Expect(err).To(HaveOccurred()) 1285 }) 1286 }) 1287 1288 Context("when making the request returns an error", func() { 1289 BeforeEach(func() { 1290 downloadLinkResponseStatusCode = http.StatusTeapot 1291 }) 1292 1293 It("forwards the error", func() { 1294 tmpFile, err := ioutil.TempFile("", "") 1295 Expect(err).NotTo(HaveOccurred()) 1296 1297 tmpLocation, err := download.NewFileInfo(tmpFile) 1298 Expect(err).NotTo(HaveOccurred()) 1299 1300 err = client.ProductFiles.DownloadForRelease( 1301 tmpLocation, 1302 productSlug, 1303 releaseID, 1304 productFileID, 1305 GinkgoWriter, 1306 ) 1307 Expect(err).To(HaveOccurred()) 1308 }) 1309 }) 1310 1311 Context("when the download link returns a forbidden status code", func() { 1312 BeforeEach(func() { 1313 cloudfrontDownloadPath = "/valid-download" 1314 }) 1315 1316 JustBeforeEach(func() { 1317 server.AppendHandlers( 1318 ghttp.CombineHandlers( 1319 ghttp.VerifyRequest("POST", fmt.Sprintf( 1320 "%s%s", 1321 apiPrefix, 1322 downloadLink, 1323 )), 1324 ghttp.RespondWith(downloadLinkResponseStatusCode, []byte(`{}`), 1325 http.Header{ 1326 "Location": []string{fmt.Sprintf("%s/%s", cloudfront.URL(), "valid-download")}, 1327 }, 1328 ), 1329 ), 1330 ) 1331 1332 cloudfront.RouteToHandler("GET", "/download", ghttp.CombineHandlers( 1333 http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 1334 ex := regexp.MustCompile(`bytes=(\d+)-(\d+)`) 1335 matches := ex.FindStringSubmatch(req.Header.Get("Range")) 1336 1337 start, err := strconv.Atoi(matches[1]) 1338 if err != nil { 1339 Fail(err.Error()) 1340 } 1341 1342 end, err := strconv.Atoi(matches[2]) 1343 if err != nil { 1344 Fail(err.Error()) 1345 } 1346 1347 if start == 1 && end == 1 { 1348 w.WriteHeader(http.StatusForbidden) 1349 } else { 1350 w.WriteHeader(http.StatusPartialContent) 1351 _, err = w.Write(downloadLinkResponseBody[start : end+1]) 1352 Expect(err).NotTo(HaveOccurred()) 1353 } 1354 }), 1355 )) 1356 }) 1357 1358 It("gets a new cloudfront link from pivnet and retries the download", func() { 1359 tmpFile, err := ioutil.TempFile("", "") 1360 Expect(err).NotTo(HaveOccurred()) 1361 1362 tmpLocation, err := download.NewFileInfo(tmpFile) 1363 Expect(err).NotTo(HaveOccurred()) 1364 1365 err = client.ProductFiles.DownloadForRelease( 1366 tmpLocation, 1367 productSlug, 1368 releaseID, 1369 productFileID, 1370 GinkgoWriter, 1371 ) 1372 Expect(err).NotTo(HaveOccurred()) 1373 1374 contents, err := ioutil.ReadFile(tmpFile.Name()) 1375 Expect(err).NotTo(HaveOccurred()) 1376 1377 Expect(contents).To(Equal(downloadLinkResponseBody)) 1378 }) 1379 }) 1380 1381 Context("when there is an error getting the release", func() { 1382 BeforeEach(func() { 1383 getStatusCode = http.StatusTeapot 1384 }) 1385 1386 It("forwards the error", func() { 1387 tmpFile, err := ioutil.TempFile("", "") 1388 Expect(err).NotTo(HaveOccurred()) 1389 1390 tmpLocation, err := download.NewFileInfo(tmpFile) 1391 Expect(err).NotTo(HaveOccurred()) 1392 1393 err = client.ProductFiles.DownloadForRelease( 1394 tmpLocation, 1395 productSlug, 1396 releaseID, 1397 productFileID, 1398 GinkgoWriter, 1399 ) 1400 1401 Expect(err).To(HaveOccurred()) 1402 }) 1403 }) 1404 }) 1405 })