github.com/divyam234/rclone@v1.64.1/cmd/serve/http/http_test.go (about)

     1  package http
     2  
     3  import (
     4  	"context"
     5  	"flag"
     6  	"io"
     7  	"net/http"
     8  	"os"
     9  	"path/filepath"
    10  	"strings"
    11  	"testing"
    12  	"time"
    13  
    14  	_ "github.com/divyam234/rclone/backend/local"
    15  	"github.com/divyam234/rclone/cmd/serve/proxy/proxyflags"
    16  	"github.com/divyam234/rclone/fs"
    17  	"github.com/divyam234/rclone/fs/filter"
    18  	libhttp "github.com/divyam234/rclone/lib/http"
    19  	"github.com/stretchr/testify/assert"
    20  	"github.com/stretchr/testify/require"
    21  )
    22  
    23  var (
    24  	updateGolden = flag.Bool("updategolden", false, "update golden files for regression test")
    25  )
    26  
    27  const (
    28  	testBindAddress = "localhost:0"
    29  	testUser        = "user"
    30  	testPass        = "pass"
    31  	testTemplate    = "testdata/golden/testindex.html"
    32  )
    33  
    34  func start(ctx context.Context, t *testing.T, f fs.Fs) (s *HTTP, testURL string) {
    35  	opts := Options{
    36  		HTTP: libhttp.DefaultCfg(),
    37  		Template: libhttp.TemplateConfig{
    38  			Path: testTemplate,
    39  		},
    40  	}
    41  	opts.HTTP.ListenAddr = []string{testBindAddress}
    42  	if proxyflags.Opt.AuthProxy == "" {
    43  		opts.Auth.BasicUser = testUser
    44  		opts.Auth.BasicPass = testPass
    45  	}
    46  
    47  	s, err := run(ctx, f, opts)
    48  	require.NoError(t, err, "failed to start server")
    49  
    50  	urls := s.server.URLs()
    51  	require.Len(t, urls, 1, "expected one URL")
    52  
    53  	testURL = urls[0]
    54  
    55  	// try to connect to the test server
    56  	pause := time.Millisecond
    57  	for i := 0; i < 10; i++ {
    58  		resp, err := http.Head(testURL)
    59  		if err == nil {
    60  			_ = resp.Body.Close()
    61  			return
    62  		}
    63  		// t.Logf("couldn't connect, sleeping for %v: %v", pause, err)
    64  		time.Sleep(pause)
    65  		pause *= 2
    66  	}
    67  	t.Fatal("couldn't connect to server")
    68  
    69  	return s, testURL
    70  }
    71  
    72  var (
    73  	datedObject  = "two.txt"
    74  	expectedTime = time.Date(2000, 1, 2, 3, 4, 5, 0, time.UTC)
    75  )
    76  
    77  // check body against the file, or re-write body if -updategolden is
    78  // set.
    79  func checkGolden(t *testing.T, fileName string, got []byte) {
    80  	if *updateGolden {
    81  		t.Logf("Updating golden file %q", fileName)
    82  		err := os.WriteFile(fileName, got, 0666)
    83  		require.NoError(t, err)
    84  	} else {
    85  		want, err := os.ReadFile(fileName)
    86  		require.NoError(t, err)
    87  		wants := strings.Split(string(want), "\n")
    88  		gots := strings.Split(string(got), "\n")
    89  		assert.Equal(t, wants, gots, fileName)
    90  	}
    91  }
    92  
    93  func testGET(t *testing.T, useProxy bool) {
    94  	ctx := context.Background()
    95  	// ci := fs.GetConfig(ctx)
    96  	// ci.LogLevel = fs.LogLevelDebug
    97  
    98  	// exclude files called hidden.txt and directories called hidden
    99  	fi := filter.GetConfig(ctx)
   100  	require.NoError(t, fi.AddRule("- hidden.txt"))
   101  	require.NoError(t, fi.AddRule("- hidden/**"))
   102  
   103  	var f fs.Fs
   104  	if useProxy {
   105  		// the backend config will be made by the proxy
   106  		prog, err := filepath.Abs("../servetest/proxy_code.go")
   107  		require.NoError(t, err)
   108  		files, err := filepath.Abs("testdata/files")
   109  		require.NoError(t, err)
   110  		cmd := "go run " + prog + " " + files
   111  
   112  		// FIXME this is untidy setting a global variable!
   113  		proxyflags.Opt.AuthProxy = cmd
   114  		defer func() {
   115  			proxyflags.Opt.AuthProxy = ""
   116  		}()
   117  
   118  		f = nil
   119  	} else {
   120  		// Create a test Fs
   121  		var err error
   122  		f, err = fs.NewFs(context.Background(), "testdata/files")
   123  		require.NoError(t, err)
   124  
   125  		// set date of datedObject to expectedTime
   126  		obj, err := f.NewObject(context.Background(), datedObject)
   127  		require.NoError(t, err)
   128  		require.NoError(t, obj.SetModTime(context.Background(), expectedTime))
   129  	}
   130  
   131  	s, testURL := start(ctx, t, f)
   132  	defer func() {
   133  		assert.NoError(t, s.server.Shutdown())
   134  	}()
   135  
   136  	for _, test := range []struct {
   137  		URL    string
   138  		Status int
   139  		Golden string
   140  		Method string
   141  		Range  string
   142  	}{
   143  		{
   144  			URL:    "",
   145  			Status: http.StatusOK,
   146  			Golden: "testdata/golden/index.html",
   147  		},
   148  		{
   149  			URL:    "notfound",
   150  			Status: http.StatusNotFound,
   151  			Golden: "testdata/golden/notfound.html",
   152  		},
   153  		{
   154  			URL:    "dirnotfound/",
   155  			Status: http.StatusNotFound,
   156  			Golden: "testdata/golden/dirnotfound.html",
   157  		},
   158  		{
   159  			URL:    "hidden/",
   160  			Status: http.StatusNotFound,
   161  			Golden: "testdata/golden/hiddendir.html",
   162  		},
   163  		{
   164  			URL:    "one%25.txt",
   165  			Status: http.StatusOK,
   166  			Golden: "testdata/golden/one.txt",
   167  		},
   168  		{
   169  			URL:    "hidden.txt",
   170  			Status: http.StatusNotFound,
   171  			Golden: "testdata/golden/hidden.txt",
   172  		},
   173  		{
   174  			URL:    "three/",
   175  			Status: http.StatusOK,
   176  			Golden: "testdata/golden/three.html",
   177  		},
   178  		{
   179  			URL:    "three/a.txt",
   180  			Status: http.StatusOK,
   181  			Golden: "testdata/golden/a.txt",
   182  		},
   183  		{
   184  			URL:    "",
   185  			Method: "HEAD",
   186  			Status: http.StatusOK,
   187  			Golden: "testdata/golden/indexhead.txt",
   188  		},
   189  		{
   190  			URL:    "one%25.txt",
   191  			Method: "HEAD",
   192  			Status: http.StatusOK,
   193  			Golden: "testdata/golden/onehead.txt",
   194  		},
   195  		{
   196  			URL:    "",
   197  			Method: "POST",
   198  			Status: http.StatusMethodNotAllowed,
   199  			Golden: "testdata/golden/indexpost.txt",
   200  		},
   201  		{
   202  			URL:    "one%25.txt",
   203  			Method: "POST",
   204  			Status: http.StatusMethodNotAllowed,
   205  			Golden: "testdata/golden/onepost.txt",
   206  		},
   207  		{
   208  			URL:    "two.txt",
   209  			Status: http.StatusOK,
   210  			Golden: "testdata/golden/two.txt",
   211  		},
   212  		{
   213  			URL:    "two.txt",
   214  			Status: http.StatusPartialContent,
   215  			Range:  "bytes=2-5",
   216  			Golden: "testdata/golden/two2-5.txt",
   217  		},
   218  		{
   219  			URL:    "two.txt",
   220  			Status: http.StatusPartialContent,
   221  			Range:  "bytes=0-6",
   222  			Golden: "testdata/golden/two-6.txt",
   223  		},
   224  		{
   225  			URL:    "two.txt",
   226  			Status: http.StatusPartialContent,
   227  			Range:  "bytes=3-",
   228  			Golden: "testdata/golden/two3-.txt",
   229  		},
   230  	} {
   231  		method := test.Method
   232  		if method == "" {
   233  			method = "GET"
   234  		}
   235  		req, err := http.NewRequest(method, testURL+test.URL, nil)
   236  		require.NoError(t, err)
   237  		if test.Range != "" {
   238  			req.Header.Add("Range", test.Range)
   239  		}
   240  		req.SetBasicAuth(testUser, testPass)
   241  		resp, err := http.DefaultClient.Do(req)
   242  		require.NoError(t, err)
   243  		assert.Equal(t, test.Status, resp.StatusCode, test.Golden)
   244  		body, err := io.ReadAll(resp.Body)
   245  		require.NoError(t, err)
   246  
   247  		// Check we got a Last-Modified header and that it is a valid date
   248  		if test.Status == http.StatusOK || test.Status == http.StatusPartialContent {
   249  			lastModified := resp.Header.Get("Last-Modified")
   250  			assert.NotEqual(t, "", lastModified, test.Golden)
   251  			modTime, err := http.ParseTime(lastModified)
   252  			assert.NoError(t, err, test.Golden)
   253  			// check the actual date on our special file
   254  			if test.URL == datedObject {
   255  				assert.Equal(t, expectedTime, modTime, test.Golden)
   256  			}
   257  		}
   258  
   259  		checkGolden(t, test.Golden, body)
   260  	}
   261  }
   262  
   263  func TestGET(t *testing.T) {
   264  	testGET(t, false)
   265  }
   266  
   267  func TestAuthProxy(t *testing.T) {
   268  	testGET(t, true)
   269  }