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 }