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