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