github.com/ncw/rclone@v1.48.1-0.20190724201158-a35aa1360e3e/backend/http/http_internal_test.go (about) 1 package http 2 3 import ( 4 "context" 5 "fmt" 6 "io/ioutil" 7 "net/http" 8 "net/http/httptest" 9 "net/url" 10 "os" 11 "path/filepath" 12 "sort" 13 "testing" 14 "time" 15 16 "github.com/ncw/rclone/fs" 17 "github.com/ncw/rclone/fs/config" 18 "github.com/ncw/rclone/fs/config/configmap" 19 "github.com/ncw/rclone/fstest" 20 "github.com/ncw/rclone/lib/rest" 21 "github.com/stretchr/testify/assert" 22 "github.com/stretchr/testify/require" 23 ) 24 25 var ( 26 remoteName = "TestHTTP" 27 testPath = "test" 28 filesPath = filepath.Join(testPath, "files") 29 ) 30 31 // prepareServer the test server and return a function to tidy it up afterwards 32 func prepareServer(t *testing.T) (configmap.Simple, func()) { 33 // file server for test/files 34 fileServer := http.FileServer(http.Dir(filesPath)) 35 36 // Make the test server 37 ts := httptest.NewServer(fileServer) 38 39 // Configure the remote 40 config.LoadConfig() 41 // fs.Config.LogLevel = fs.LogLevelDebug 42 // fs.Config.DumpHeaders = true 43 // fs.Config.DumpBodies = true 44 // config.FileSet(remoteName, "type", "http") 45 // config.FileSet(remoteName, "url", ts.URL) 46 47 m := configmap.Simple{ 48 "type": "http", 49 "url": ts.URL, 50 } 51 52 // return a function to tidy up 53 return m, ts.Close 54 } 55 56 // prepare the test server and return a function to tidy it up afterwards 57 func prepare(t *testing.T) (fs.Fs, func()) { 58 m, tidy := prepareServer(t) 59 60 // Instantiate it 61 f, err := NewFs(remoteName, "", m) 62 require.NoError(t, err) 63 64 return f, tidy 65 } 66 67 func testListRoot(t *testing.T, f fs.Fs, noSlash bool) { 68 entries, err := f.List(context.Background(), "") 69 require.NoError(t, err) 70 71 sort.Sort(entries) 72 73 require.Equal(t, 4, len(entries)) 74 75 e := entries[0] 76 assert.Equal(t, "four", e.Remote()) 77 assert.Equal(t, int64(-1), e.Size()) 78 _, ok := e.(fs.Directory) 79 assert.True(t, ok) 80 81 e = entries[1] 82 assert.Equal(t, "one%.txt", e.Remote()) 83 assert.Equal(t, int64(6), e.Size()) 84 _, ok = e.(*Object) 85 assert.True(t, ok) 86 87 e = entries[2] 88 assert.Equal(t, "three", e.Remote()) 89 assert.Equal(t, int64(-1), e.Size()) 90 _, ok = e.(fs.Directory) 91 assert.True(t, ok) 92 93 e = entries[3] 94 assert.Equal(t, "two.html", e.Remote()) 95 if noSlash { 96 assert.Equal(t, int64(-1), e.Size()) 97 _, ok = e.(fs.Directory) 98 assert.True(t, ok) 99 } else { 100 assert.Equal(t, int64(41), e.Size()) 101 _, ok = e.(*Object) 102 assert.True(t, ok) 103 } 104 } 105 106 func TestListRoot(t *testing.T) { 107 f, tidy := prepare(t) 108 defer tidy() 109 testListRoot(t, f, false) 110 } 111 112 func TestListRootNoSlash(t *testing.T) { 113 f, tidy := prepare(t) 114 f.(*Fs).opt.NoSlash = true 115 defer tidy() 116 117 testListRoot(t, f, true) 118 } 119 120 func TestListSubDir(t *testing.T) { 121 f, tidy := prepare(t) 122 defer tidy() 123 124 entries, err := f.List(context.Background(), "three") 125 require.NoError(t, err) 126 127 sort.Sort(entries) 128 129 assert.Equal(t, 1, len(entries)) 130 131 e := entries[0] 132 assert.Equal(t, "three/underthree.txt", e.Remote()) 133 assert.Equal(t, int64(9), e.Size()) 134 _, ok := e.(*Object) 135 assert.True(t, ok) 136 } 137 138 func TestNewObject(t *testing.T) { 139 f, tidy := prepare(t) 140 defer tidy() 141 142 o, err := f.NewObject(context.Background(), "four/under four.txt") 143 require.NoError(t, err) 144 145 assert.Equal(t, "four/under four.txt", o.Remote()) 146 assert.Equal(t, int64(9), o.Size()) 147 _, ok := o.(*Object) 148 assert.True(t, ok) 149 150 // Test the time is correct on the object 151 152 tObj := o.ModTime(context.Background()) 153 154 fi, err := os.Stat(filepath.Join(filesPath, "four", "under four.txt")) 155 require.NoError(t, err) 156 tFile := fi.ModTime() 157 158 dt, ok := fstest.CheckTimeEqualWithPrecision(tObj, tFile, time.Second) 159 assert.True(t, ok, fmt.Sprintf("%s: Modification time difference too big |%s| > %s (%s vs %s) (precision %s)", o.Remote(), dt, time.Second, tObj, tFile, time.Second)) 160 161 // check object not found 162 o, err = f.NewObject(context.Background(), "not found.txt") 163 assert.Nil(t, o) 164 assert.Equal(t, fs.ErrorObjectNotFound, err) 165 } 166 167 func TestOpen(t *testing.T) { 168 f, tidy := prepare(t) 169 defer tidy() 170 171 o, err := f.NewObject(context.Background(), "four/under four.txt") 172 require.NoError(t, err) 173 174 // Test normal read 175 fd, err := o.Open(context.Background()) 176 require.NoError(t, err) 177 data, err := ioutil.ReadAll(fd) 178 require.NoError(t, err) 179 require.NoError(t, fd.Close()) 180 assert.Equal(t, "beetroot\n", string(data)) 181 182 // Test with range request 183 fd, err = o.Open(context.Background(), &fs.RangeOption{Start: 1, End: 5}) 184 require.NoError(t, err) 185 data, err = ioutil.ReadAll(fd) 186 require.NoError(t, err) 187 require.NoError(t, fd.Close()) 188 assert.Equal(t, "eetro", string(data)) 189 } 190 191 func TestMimeType(t *testing.T) { 192 f, tidy := prepare(t) 193 defer tidy() 194 195 o, err := f.NewObject(context.Background(), "four/under four.txt") 196 require.NoError(t, err) 197 198 do, ok := o.(fs.MimeTyper) 199 require.True(t, ok) 200 assert.Equal(t, "text/plain; charset=utf-8", do.MimeType(context.Background())) 201 } 202 203 func TestIsAFileRoot(t *testing.T) { 204 m, tidy := prepareServer(t) 205 defer tidy() 206 207 f, err := NewFs(remoteName, "one%.txt", m) 208 assert.Equal(t, err, fs.ErrorIsFile) 209 210 testListRoot(t, f, false) 211 } 212 213 func TestIsAFileSubDir(t *testing.T) { 214 m, tidy := prepareServer(t) 215 defer tidy() 216 217 f, err := NewFs(remoteName, "three/underthree.txt", m) 218 assert.Equal(t, err, fs.ErrorIsFile) 219 220 entries, err := f.List(context.Background(), "") 221 require.NoError(t, err) 222 223 sort.Sort(entries) 224 225 assert.Equal(t, 1, len(entries)) 226 227 e := entries[0] 228 assert.Equal(t, "underthree.txt", e.Remote()) 229 assert.Equal(t, int64(9), e.Size()) 230 _, ok := e.(*Object) 231 assert.True(t, ok) 232 } 233 234 func TestParseName(t *testing.T) { 235 for i, test := range []struct { 236 base string 237 val string 238 wantErr error 239 want string 240 }{ 241 {"http://example.com/", "potato", nil, "potato"}, 242 {"http://example.com/dir/", "potato", nil, "potato"}, 243 {"http://example.com/dir/", "potato?download=true", errFoundQuestionMark, ""}, 244 {"http://example.com/dir/", "../dir/potato", nil, "potato"}, 245 {"http://example.com/dir/", "..", errNotUnderRoot, ""}, 246 {"http://example.com/dir/", "http://example.com/", errNotUnderRoot, ""}, 247 {"http://example.com/dir/", "http://example.com/dir/", errNameIsEmpty, ""}, 248 {"http://example.com/dir/", "http://example.com/dir/potato", nil, "potato"}, 249 {"http://example.com/dir/", "https://example.com/dir/potato", errSchemeMismatch, ""}, 250 {"http://example.com/dir/", "http://notexample.com/dir/potato", errHostMismatch, ""}, 251 {"http://example.com/dir/", "/dir/", errNameIsEmpty, ""}, 252 {"http://example.com/dir/", "/dir/potato", nil, "potato"}, 253 {"http://example.com/dir/", "subdir/potato", errNameContainsSlash, ""}, 254 {"http://example.com/dir/", "With percent %25.txt", nil, "With percent %.txt"}, 255 {"http://example.com/dir/", "With colon :", errURLJoinFailed, ""}, 256 {"http://example.com/dir/", rest.URLPathEscape("With colon :"), nil, "With colon :"}, 257 {"http://example.com/Dungeons%20%26%20Dragons/", "/Dungeons%20&%20Dragons/D%26D%20Basic%20%28Holmes%2C%20B%2C%20X%2C%20BECMI%29/", nil, "D&D Basic (Holmes, B, X, BECMI)/"}, 258 } { 259 u, err := url.Parse(test.base) 260 require.NoError(t, err) 261 got, gotErr := parseName(u, test.val) 262 what := fmt.Sprintf("test %d base=%q, val=%q", i, test.base, test.val) 263 assert.Equal(t, test.wantErr, gotErr, what) 264 assert.Equal(t, test.want, got, what) 265 } 266 } 267 268 // Load HTML from the file given and parse it, checking it against the entries passed in 269 func parseHTML(t *testing.T, name string, base string, want []string) { 270 in, err := os.Open(filepath.Join(testPath, "index_files", name)) 271 require.NoError(t, err) 272 defer func() { 273 require.NoError(t, in.Close()) 274 }() 275 if base == "" { 276 base = "http://example.com/" 277 } 278 u, err := url.Parse(base) 279 require.NoError(t, err) 280 entries, err := parse(u, in) 281 require.NoError(t, err) 282 assert.Equal(t, want, entries) 283 } 284 285 func TestParseEmpty(t *testing.T) { 286 parseHTML(t, "empty.html", "", []string(nil)) 287 } 288 289 func TestParseApache(t *testing.T) { 290 parseHTML(t, "apache.html", "http://example.com/nick/pub/", []string{ 291 "SWIG-embed.tar.gz", 292 "avi2dvd.pl", 293 "cambert.exe", 294 "cambert.gz", 295 "fedora_demo.gz", 296 "gchq-challenge/", 297 "mandelterm/", 298 "pgp-key.txt", 299 "pymath/", 300 "rclone", 301 "readdir.exe", 302 "rush_hour_solver_cut_down.py", 303 "snake-puzzle/", 304 "stressdisk/", 305 "timer-test", 306 "words-to-regexp.pl", 307 "Now 100% better.mp3", 308 "Now better.mp3", 309 }) 310 } 311 312 func TestParseMemstore(t *testing.T) { 313 parseHTML(t, "memstore.html", "", []string{ 314 "test/", 315 "v1.35/", 316 "v1.36-01-g503cd84/", 317 "rclone-beta-latest-freebsd-386.zip", 318 "rclone-beta-latest-freebsd-amd64.zip", 319 "rclone-beta-latest-windows-amd64.zip", 320 }) 321 } 322 323 func TestParseNginx(t *testing.T) { 324 parseHTML(t, "nginx.html", "", []string{ 325 "deltas/", 326 "objects/", 327 "refs/", 328 "state/", 329 "config", 330 "summary", 331 }) 332 } 333 334 func TestParseCaddy(t *testing.T) { 335 parseHTML(t, "caddy.html", "", []string{ 336 "mimetype.zip", 337 "rclone-delete-empty-dirs.py", 338 "rclone-show-empty-dirs.py", 339 "stat-windows-386.zip", 340 "v1.36-155-gcf29ee8b-team-driveβ/", 341 "v1.36-156-gca76b3fb-team-driveβ/", 342 "v1.36-156-ge1f0e0f5-team-driveβ/", 343 "v1.36-22-g06ea13a-ssh-agentβ/", 344 }) 345 }