go.mway.dev/x@v0.0.0-20240520034138-950aede9a3fb/net/http/get_file_internal_test.go (about)

     1  // Copyright (c) 2024 Matt Way
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to
     5  // deal in the Software without restriction, including without limitation the
     6  // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
     7  // sell copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
    18  // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
    19  // IN THE THE SOFTWARE.
    20  
    21  package http
    22  
    23  import (
    24  	"bytes"
    25  	"io"
    26  	"io/fs"
    27  	"net/http"
    28  	"os"
    29  	"path/filepath"
    30  	"testing"
    31  
    32  	"github.com/stretchr/testify/require"
    33  	"go.mway.dev/errors"
    34  	"go.mway.dev/x/os/tempdir"
    35  	"go.mway.dev/x/stub"
    36  )
    37  
    38  func TestGetFile_Nominal(t *testing.T) {
    39  	var (
    40  		giveURL     = "https://foo.bar/baz.bat"
    41  		wantFile    = "baz.bat"
    42  		wantContent = t.Name()
    43  	)
    44  
    45  	err := tempdir.With(func(dst string) {
    46  		stub.With(
    47  			&_clientDo,
    48  			newClientDoFunc(t, giveURL, t.Name(), nil),
    49  			func() {
    50  				dst = filepath.Join(dst, wantFile)
    51  				require.NoError(t, GetFile(giveURL, dst))
    52  
    53  				raw, err := os.ReadFile(dst)
    54  				require.NoError(t, err)
    55  				require.Equal(t, wantContent, string(raw))
    56  			},
    57  		)
    58  	})
    59  	require.NoError(t, err)
    60  }
    61  
    62  func TestGetFile_HTTPGetError(t *testing.T) {
    63  	wantErr := errors.New(t.Name())
    64  	stub.With(
    65  		&_clientDo,
    66  		newClientDoFunc(t, "http://foo", "", wantErr),
    67  		func() {
    68  			require.ErrorIs(t, GetFile("http://foo", ""), wantErr)
    69  		},
    70  	)
    71  }
    72  
    73  func TestGetFile_HTTPResponseBodyCloseError(t *testing.T) {
    74  	var (
    75  		giveURL     = "https://foo.bar/baz.bat"
    76  		wantFile    = "baz.bat"
    77  		wantContent = t.Name()
    78  		wantErr     = errors.New(t.Name())
    79  		newRequest  = func(*http.Request) (*http.Response, error) { //nolint:unparam
    80  			return &http.Response{
    81  				Body: testReader{
    82  					reader:   io.NopCloser(bytes.NewBufferString(t.Name())),
    83  					closeErr: wantErr,
    84  				},
    85  			}, nil
    86  		}
    87  	)
    88  
    89  	err := tempdir.With(func(dst string) {
    90  		stub.With(&_clientDo, newRequest, func() {
    91  			dst = filepath.Join(dst, wantFile)
    92  			require.ErrorIs(t, GetFile(giveURL, dst), wantErr)
    93  
    94  			raw, err := os.ReadFile(dst)
    95  			require.NoError(t, err)
    96  			require.Equal(t, wantContent, string(raw))
    97  		})
    98  	})
    99  	require.NoError(t, err)
   100  }
   101  
   102  func TestGetFile_OSStatError(t *testing.T) {
   103  	var (
   104  		wantStatErr = errors.New(t.Name())
   105  		osStat      = func(string) (fs.FileInfo, error) { //nolint:unparam
   106  			return nil, wantStatErr
   107  		}
   108  		giveURL  = "https://foo.bar/baz.bat"
   109  		wantFile = "baz.bat"
   110  	)
   111  
   112  	err := tempdir.With(func(dst string) {
   113  		stub.With(
   114  			&_clientDo,
   115  			newClientDoFunc(t, giveURL, t.Name(), nil),
   116  			func() {
   117  				stub.With(&_osStat, osStat, func() {
   118  					dst = filepath.Join(dst, wantFile)
   119  					require.ErrorIs(t, GetFile(giveURL, dst), wantStatErr)
   120  				})
   121  			},
   122  		)
   123  	})
   124  	require.NoError(t, err)
   125  }
   126  
   127  func TestGetFile_OSMkdirAllError(t *testing.T) {
   128  	var (
   129  		wantMkdirError = errors.New(t.Name())
   130  		mkdirAll       = func(string, fs.FileMode) error {
   131  			return wantMkdirError
   132  		}
   133  		giveURL  = "https://foo.bar/baz.bat"
   134  		wantFile = "baz.bat"
   135  	)
   136  
   137  	err := tempdir.With(func(dst string) {
   138  		stub.With(
   139  			&_clientDo,
   140  			newClientDoFunc(t, giveURL, t.Name(), nil),
   141  			func() {
   142  				stub.With(&_osMkdirAll, mkdirAll, func() {
   143  					dst = filepath.Join(dst, "foo", wantFile)
   144  					require.ErrorIs(t, GetFile(giveURL, dst), wantMkdirError)
   145  				})
   146  			},
   147  		)
   148  	})
   149  	require.NoError(t, err)
   150  }
   151  
   152  func TestGetFile_DestNotWritable(t *testing.T) {
   153  	var (
   154  		unixAccess = func(string, uint32) error {
   155  			return errors.New(t.Name())
   156  		}
   157  		giveURL  = "https://foo.bar/baz.bat"
   158  		wantFile = "baz.bat"
   159  	)
   160  
   161  	err := tempdir.With(func(dst string) {
   162  		stub.With(
   163  			&_clientDo,
   164  			newClientDoFunc(t, giveURL, t.Name(), nil),
   165  			func() {
   166  				stub.With(&_unixAccess, unixAccess, func() {
   167  					dst = filepath.Join(dst, "foo", wantFile)
   168  					require.ErrorIs(t, GetFile(giveURL, dst), ErrDestNotWritable)
   169  				})
   170  			},
   171  		)
   172  	})
   173  	require.NoError(t, err)
   174  }
   175  
   176  func TestGetFile_DestIsDir(t *testing.T) {
   177  	var (
   178  		giveURL  = "https://foo.bar/baz.bat"
   179  		wantFile = "baz.bat"
   180  	)
   181  
   182  	err := tempdir.With(func(dst string) {
   183  		stub.With(
   184  			&_clientDo,
   185  			newClientDoFunc(t, giveURL, t.Name(), nil),
   186  			func() {
   187  				dst = filepath.Join(dst, "foo", wantFile)
   188  				dir := filepath.Dir(dst)
   189  				require.NoError(t, os.Mkdir(dir, 0o755))
   190  				require.NoError(t, GetFile(giveURL, dir))
   191  
   192  				_, err := os.ReadFile(dst)
   193  				require.NoError(t, err)
   194  			},
   195  		)
   196  	})
   197  	require.NoError(t, err)
   198  }
   199  
   200  func newClientDoFunc(
   201  	t *testing.T,
   202  	wantURL string,
   203  	contents string,
   204  	err error,
   205  ) func(*http.Request) (*http.Response, error) {
   206  	return func(req *http.Request) (*http.Response, error) {
   207  		require.NotNil(t, req)
   208  		require.NotNil(t, req.URL)
   209  		require.Equal(t, wantURL, req.URL.String())
   210  
   211  		if err != nil {
   212  			return nil, err
   213  		}
   214  
   215  		buf := bytes.NewBufferString(contents)
   216  		return &http.Response{
   217  			Status:        "200 OK",
   218  			StatusCode:    http.StatusOK,
   219  			Proto:         "https",
   220  			Body:          io.NopCloser(buf),
   221  			ContentLength: int64(buf.Len()),
   222  		}, nil
   223  	}
   224  }
   225  
   226  type testReader struct {
   227  	reader   io.ReadCloser
   228  	readErr  error
   229  	closeErr error
   230  }
   231  
   232  func (e testReader) Read(p []byte) (int, error) {
   233  	switch {
   234  	case e.readErr != nil:
   235  		return 0, e.readErr
   236  	case e.reader == nil:
   237  		return 0, io.EOF
   238  	default:
   239  		return e.reader.Read(p)
   240  	}
   241  }
   242  
   243  func (e testReader) Close() error {
   244  	err := e.reader.Close()
   245  	if e.closeErr != nil {
   246  		err = e.closeErr
   247  	}
   248  	return err
   249  }