diff --git a/download/download.go b/download/download.go index 5da3fcd..7473db3 100644 --- a/download/download.go +++ b/download/download.go @@ -244,11 +244,19 @@ func getCliName(targetOS string) string { return name } +type httpClient interface { + Get(url string) (resp *http.Response, err error) +} + func fetchZip(url string, useEtag bool) (string, error) { + return fetchZipWithClient(url, useEtag, http.DefaultClient) +} + +func fetchZipWithClient(url string, useEtag bool, client httpClient) (string, error) { // It *may* be more efficient (for whom?) to issue a HEAD request first for the ETag and Content-Length. // We can't use If-None-Match because we don't know in advance which cached file is for which spec. // We could encode the entire spec in the cached file name but the complexity would not be worth it. - resp, err := http.Get(url) + resp, err := client.Get(url) if err != nil { return "", genericDownloadErr(url, err) } @@ -259,7 +267,7 @@ func fetchZip(url string, useEtag bool) (string, error) { contentLength := resp.ContentLength defer helperr.CloseQuietly(resp.Body) var tmpZip *os.File - if !useEtag && etagHeader != "" { + if !useEtag || etagHeader == "" { tmpZip, err = os.CreateTemp("", "getaduck") } else { // ETag may contain chars not allowed in filenames. @@ -275,16 +283,19 @@ func fetchZip(url string, useEtag bool) (string, error) { tmpZip, err = os.Create(fileName) } if err != nil { - return "", fmt.Errorf("failed to create temp file: %w", err) + return "", merry.Wrap(fmt.Errorf("failed to create temp file: %w", err)) } defer helperr.CloseQuietly(tmpZip) + if resp.Body == nil { + return "", merry.Wrap(fmt.Errorf("no response body available")) + } _, err = io.Copy(tmpZip, resp.Body) if err != nil { return "", genericDownloadErr(url, err) } err = tmpZip.Close() if err != nil { - return "", fmt.Errorf("failed to close temp file: %w", err) + return "", merry.Wrap(fmt.Errorf("failed to close temp file: %w", err)) } return tmpZip.Name(), nil diff --git a/download/download_internal_test.go b/download/download_internal_test.go new file mode 100644 index 0000000..2c52488 --- /dev/null +++ b/download/download_internal_test.go @@ -0,0 +1,42 @@ +package download + +import ( + "io" + "net/http" + "os" + "strings" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestFetchZipWithClient(t *testing.T) { + t.Run("empty body", func(t *testing.T) { + _, err := fetchZipWithClient("", true, clientStub{ + response: &http.Response{ + StatusCode: 200, + }, + }) + require.Error(t, err) + }) + t.Run("empty etag", func(t *testing.T) { + name, err := fetchZipWithClient("", true, clientStub{ + response: &http.Response{ + StatusCode: 200, + Body: io.NopCloser(strings.NewReader("")), + }, + }) + require.NoError(t, err) + require.NoError(t, os.Remove(name)) + require.NotContains(t, name, "etag") + }) +} + +type clientStub struct { + response *http.Response + err error +} + +func (c clientStub) Get(_ string) (resp *http.Response, err error) { + return c.response, c.err +} diff --git a/go.mod b/go.mod index 0f07ae9..8bd0939 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.24.7 require ( github.com/ansel1/merry/v2 v2.2.2 - github.com/murfffi/gorich v0.2.0 + github.com/murfffi/gorich v0.2.1 github.com/stretchr/testify v1.11.1 golang.org/x/mod v0.28.0 ) diff --git a/go.sum b/go.sum index 8a859f2..958ddbf 100644 --- a/go.sum +++ b/go.sum @@ -4,8 +4,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-errors/errors v1.1.1 h1:ljK/pL5ltg3qoN+OtN6yCv9HWSfMwxSx90GJCZQxYNg= github.com/go-errors/errors v1.1.1/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs= -github.com/murfffi/gorich v0.2.0 h1:eUQ2xzjp7czPemnc0nSEDVBLz2xyFKfQg2kSgJ9ljwk= -github.com/murfffi/gorich v0.2.0/go.mod h1:o1VsmtwZ9U/E8eyiLvhUTOdV810J8v6Y0R3pi/97TdQ= +github.com/murfffi/gorich v0.2.1 h1:YKUnllpO54KMGomcTBKb8K3zLFxFZT9soEFckf7wkSU= +github.com/murfffi/gorich v0.2.1/go.mod h1:o1VsmtwZ9U/E8eyiLvhUTOdV810J8v6Y0R3pi/97TdQ= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=