github.com/divyam234/rclone@v1.64.1/cmd/serve/webdav/webdav_test.go (about)

     1  // Serve webdav tests set up a server and run the integration tests
     2  // for the webdav remote against it.
     3  //
     4  // We skip tests on platforms with troublesome character mappings
     5  
     6  //go:build !windows && !darwin
     7  // +build !windows,!darwin
     8  
     9  package webdav
    10  
    11  import (
    12  	"context"
    13  	"flag"
    14  	"io"
    15  	"net/http"
    16  	"os"
    17  	"strings"
    18  	"testing"
    19  	"time"
    20  
    21  	_ "github.com/divyam234/rclone/backend/local"
    22  	"github.com/divyam234/rclone/cmd/serve/servetest"
    23  	"github.com/divyam234/rclone/fs"
    24  	"github.com/divyam234/rclone/fs/config/configmap"
    25  	"github.com/divyam234/rclone/fs/config/obscure"
    26  	"github.com/divyam234/rclone/fs/filter"
    27  	"github.com/divyam234/rclone/fs/hash"
    28  	"github.com/stretchr/testify/assert"
    29  	"github.com/stretchr/testify/require"
    30  	"golang.org/x/net/webdav"
    31  )
    32  
    33  const (
    34  	testBindAddress = "localhost:0"
    35  	testUser        = "user"
    36  	testPass        = "pass"
    37  	testTemplate    = "../http/testdata/golden/testindex.html"
    38  )
    39  
    40  // check interfaces
    41  var (
    42  	_ os.FileInfo         = FileInfo{nil, nil}
    43  	_ webdav.ETager       = FileInfo{nil, nil}
    44  	_ webdav.ContentTyper = FileInfo{nil, nil}
    45  )
    46  
    47  // TestWebDav runs the webdav server then runs the unit tests for the
    48  // webdav remote against it.
    49  func TestWebDav(t *testing.T) {
    50  	// Configure and start the server
    51  	start := func(f fs.Fs) (configmap.Simple, func()) {
    52  		opt := DefaultOpt
    53  		opt.HTTP.ListenAddr = []string{testBindAddress}
    54  		opt.HTTP.BaseURL = "/prefix"
    55  		opt.Auth.BasicUser = testUser
    56  		opt.Auth.BasicPass = testPass
    57  		opt.Template.Path = testTemplate
    58  		opt.HashType = hash.MD5
    59  
    60  		// Start the server
    61  		w, err := newWebDAV(context.Background(), f, &opt)
    62  		require.NoError(t, err)
    63  		require.NoError(t, w.serve())
    64  
    65  		// Config for the backend we'll use to connect to the server
    66  		config := configmap.Simple{
    67  			"type":   "webdav",
    68  			"vendor": "owncloud",
    69  			"url":    w.Server.URLs()[0],
    70  			"user":   testUser,
    71  			"pass":   obscure.MustObscure(testPass),
    72  		}
    73  
    74  		return config, func() {
    75  			assert.NoError(t, w.Shutdown())
    76  			w.Wait()
    77  		}
    78  	}
    79  
    80  	servetest.Run(t, "webdav", start)
    81  }
    82  
    83  // Test serve http functionality in serve webdav
    84  // While similar to http serve, there are some inconsistencies
    85  // in the handling of some requests such as POST requests
    86  
    87  var (
    88  	updateGolden = flag.Bool("updategolden", false, "update golden files for regression test")
    89  )
    90  
    91  func TestHTTPFunction(t *testing.T) {
    92  	ctx := context.Background()
    93  	// exclude files called hidden.txt and directories called hidden
    94  	fi := filter.GetConfig(ctx)
    95  	require.NoError(t, fi.AddRule("- hidden.txt"))
    96  	require.NoError(t, fi.AddRule("- hidden/**"))
    97  
    98  	// Uses the same test files as http tests but with different golden.
    99  	f, err := fs.NewFs(context.Background(), "../http/testdata/files")
   100  	assert.NoError(t, err)
   101  
   102  	opt := DefaultOpt
   103  	opt.HTTP.ListenAddr = []string{testBindAddress}
   104  	opt.Template.Path = testTemplate
   105  
   106  	// Start the server
   107  	w, err := newWebDAV(context.Background(), f, &opt)
   108  	assert.NoError(t, err)
   109  	require.NoError(t, w.serve())
   110  	defer func() {
   111  		assert.NoError(t, w.Shutdown())
   112  		w.Wait()
   113  	}()
   114  	testURL := w.Server.URLs()[0]
   115  	pause := time.Millisecond
   116  	i := 0
   117  	for ; i < 10; i++ {
   118  		resp, err := http.Head(testURL)
   119  		if err == nil {
   120  			_ = resp.Body.Close()
   121  			break
   122  		}
   123  		// t.Logf("couldn't connect, sleeping for %v: %v", pause, err)
   124  		time.Sleep(pause)
   125  		pause *= 2
   126  	}
   127  	if i >= 10 {
   128  		t.Fatal("couldn't connect to server")
   129  	}
   130  
   131  	HelpTestGET(t, testURL)
   132  }
   133  
   134  // check body against the file, or re-write body if -updategolden is
   135  // set.
   136  func checkGolden(t *testing.T, fileName string, got []byte) {
   137  	if *updateGolden {
   138  		t.Logf("Updating golden file %q", fileName)
   139  		err := os.WriteFile(fileName, got, 0666)
   140  		require.NoError(t, err)
   141  	} else {
   142  		want, err := os.ReadFile(fileName)
   143  		require.NoError(t, err, "problem")
   144  		wants := strings.Split(string(want), "\n")
   145  		gots := strings.Split(string(got), "\n")
   146  		assert.Equal(t, wants, gots, fileName)
   147  	}
   148  }
   149  
   150  func HelpTestGET(t *testing.T, testURL string) {
   151  	for _, test := range []struct {
   152  		URL    string
   153  		Status int
   154  		Golden string
   155  		Method string
   156  		Range  string
   157  	}{
   158  		{
   159  			URL:    "",
   160  			Status: http.StatusOK,
   161  			Golden: "testdata/golden/index.html",
   162  		},
   163  		{
   164  			URL:    "notfound",
   165  			Status: http.StatusNotFound,
   166  			Golden: "testdata/golden/notfound.html",
   167  		},
   168  		{
   169  			URL:    "dirnotfound/",
   170  			Status: http.StatusNotFound,
   171  			Golden: "testdata/golden/dirnotfound.html",
   172  		},
   173  		{
   174  			URL:    "hidden/",
   175  			Status: http.StatusNotFound,
   176  			Golden: "testdata/golden/hiddendir.html",
   177  		},
   178  		{
   179  			URL:    "one%25.txt",
   180  			Status: http.StatusOK,
   181  			Golden: "testdata/golden/one.txt",
   182  		},
   183  		{
   184  			URL:    "hidden.txt",
   185  			Status: http.StatusNotFound,
   186  			Golden: "testdata/golden/hidden.txt",
   187  		},
   188  		{
   189  			URL:    "three/",
   190  			Status: http.StatusOK,
   191  			Golden: "testdata/golden/three.html",
   192  		},
   193  		{
   194  			URL:    "three/a.txt",
   195  			Status: http.StatusOK,
   196  			Golden: "testdata/golden/a.txt",
   197  		},
   198  		{
   199  			URL:    "",
   200  			Method: "HEAD",
   201  			Status: http.StatusOK,
   202  			Golden: "testdata/golden/indexhead.txt",
   203  		},
   204  		{
   205  			URL:    "one%25.txt",
   206  			Method: "HEAD",
   207  			Status: http.StatusOK,
   208  			Golden: "testdata/golden/onehead.txt",
   209  		},
   210  		{
   211  			URL:    "",
   212  			Method: "POST",
   213  			Status: http.StatusMethodNotAllowed,
   214  			Golden: "testdata/golden/indexpost.txt",
   215  		},
   216  		{
   217  			URL:    "one%25.txt",
   218  			Method: "POST",
   219  			Status: http.StatusOK,
   220  			Golden: "testdata/golden/onepost.txt",
   221  		},
   222  		{
   223  			URL:    "two.txt",
   224  			Status: http.StatusOK,
   225  			Golden: "testdata/golden/two.txt",
   226  		},
   227  		{
   228  			URL:    "two.txt",
   229  			Status: http.StatusPartialContent,
   230  			Range:  "bytes=2-5",
   231  			Golden: "testdata/golden/two2-5.txt",
   232  		},
   233  		{
   234  			URL:    "two.txt",
   235  			Status: http.StatusPartialContent,
   236  			Range:  "bytes=0-6",
   237  			Golden: "testdata/golden/two-6.txt",
   238  		},
   239  		{
   240  			URL:    "two.txt",
   241  			Status: http.StatusPartialContent,
   242  			Range:  "bytes=3-",
   243  			Golden: "testdata/golden/two3-.txt",
   244  		},
   245  	} {
   246  		method := test.Method
   247  		if method == "" {
   248  			method = "GET"
   249  		}
   250  		req, err := http.NewRequest(method, testURL+test.URL, nil)
   251  		require.NoError(t, err)
   252  		if test.Range != "" {
   253  			req.Header.Add("Range", test.Range)
   254  		}
   255  		resp, err := http.DefaultClient.Do(req)
   256  		require.NoError(t, err)
   257  		assert.Equal(t, test.Status, resp.StatusCode, test.Golden)
   258  		body, err := io.ReadAll(resp.Body)
   259  		require.NoError(t, err)
   260  
   261  		checkGolden(t, test.Golden, body)
   262  	}
   263  }