rclone/cmd/serve/webdav/webdav_test.go
albertony 5d6b8141ec Replace deprecated ioutil
As of Go 1.16, the same functionality is now provided by package io or
package os, and those implementations should be preferred in new code.
2022-11-07 11:41:47 +00:00

262 lines
6.0 KiB
Go

// Serve webdav tests set up a server and run the integration tests
// for the webdav remote against it.
//
// We skip tests on platforms with troublesome character mappings
//go:build !windows && !darwin
// +build !windows,!darwin
package webdav
import (
"context"
"flag"
"io"
"net/http"
"os"
"strings"
"testing"
"time"
_ "github.com/rclone/rclone/backend/local"
"github.com/rclone/rclone/cmd/serve/httplib"
"github.com/rclone/rclone/cmd/serve/servetest"
"github.com/rclone/rclone/fs"
"github.com/rclone/rclone/fs/config/configmap"
"github.com/rclone/rclone/fs/config/obscure"
"github.com/rclone/rclone/fs/filter"
"github.com/rclone/rclone/fs/hash"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/net/webdav"
)
const (
testBindAddress = "localhost:0"
testUser = "user"
testPass = "pass"
testTemplate = "../http/testdata/golden/testindex.html"
)
// check interfaces
var (
_ os.FileInfo = FileInfo{nil}
_ webdav.ETager = FileInfo{nil}
_ webdav.ContentTyper = FileInfo{nil}
)
// TestWebDav runs the webdav server then runs the unit tests for the
// webdav remote against it.
func TestWebDav(t *testing.T) {
// Configure and start the server
start := func(f fs.Fs) (configmap.Simple, func()) {
opt := httplib.DefaultOpt
opt.ListenAddr = testBindAddress
opt.BasicUser = testUser
opt.BasicPass = testPass
opt.Template = testTemplate
hashType = hash.MD5
// Start the server
w := newWebDAV(context.Background(), f, &opt)
assert.NoError(t, w.serve())
// Config for the backend we'll use to connect to the server
config := configmap.Simple{
"type": "webdav",
"vendor": "other",
"url": w.Server.URL(),
"user": testUser,
"pass": obscure.MustObscure(testPass),
}
return config, func() {
w.Close()
w.Wait()
}
}
servetest.Run(t, "webdav", start)
}
// Test serve http functionality in serve webdav
// While similar to http serve, there are some inconsistencies
// in the handling of some requests such as POST requests
var (
updateGolden = flag.Bool("updategolden", false, "update golden files for regression test")
)
func TestHTTPFunction(t *testing.T) {
ctx := context.Background()
// exclude files called hidden.txt and directories called hidden
fi := filter.GetConfig(ctx)
require.NoError(t, fi.AddRule("- hidden.txt"))
require.NoError(t, fi.AddRule("- hidden/**"))
// Uses the same test files as http tests but with different golden.
f, err := fs.NewFs(context.Background(), "../http/testdata/files")
assert.NoError(t, err)
opt := httplib.DefaultOpt
opt.ListenAddr = testBindAddress
opt.Template = testTemplate
// Start the server
w := newWebDAV(context.Background(), f, &opt)
assert.NoError(t, w.serve())
defer func() {
w.Close()
w.Wait()
}()
testURL := w.Server.URL()
pause := time.Millisecond
i := 0
for ; i < 10; i++ {
resp, err := http.Head(testURL)
if err == nil {
_ = resp.Body.Close()
break
}
// t.Logf("couldn't connect, sleeping for %v: %v", pause, err)
time.Sleep(pause)
pause *= 2
}
if i >= 10 {
t.Fatal("couldn't connect to server")
}
HelpTestGET(t, testURL)
}
// check body against the file, or re-write body if -updategolden is
// set.
func checkGolden(t *testing.T, fileName string, got []byte) {
if *updateGolden {
t.Logf("Updating golden file %q", fileName)
err := os.WriteFile(fileName, got, 0666)
require.NoError(t, err)
} else {
want, err := os.ReadFile(fileName)
require.NoError(t, err, "problem")
wants := strings.Split(string(want), "\n")
gots := strings.Split(string(got), "\n")
assert.Equal(t, wants, gots, fileName)
}
}
func HelpTestGET(t *testing.T, testURL string) {
for _, test := range []struct {
URL string
Status int
Golden string
Method string
Range string
}{
{
URL: "",
Status: http.StatusOK,
Golden: "testdata/golden/index.html",
},
{
URL: "notfound",
Status: http.StatusNotFound,
Golden: "testdata/golden/notfound.html",
},
{
URL: "dirnotfound/",
Status: http.StatusNotFound,
Golden: "testdata/golden/dirnotfound.html",
},
{
URL: "hidden/",
Status: http.StatusNotFound,
Golden: "testdata/golden/hiddendir.html",
},
{
URL: "one%25.txt",
Status: http.StatusOK,
Golden: "testdata/golden/one.txt",
},
{
URL: "hidden.txt",
Status: http.StatusNotFound,
Golden: "testdata/golden/hidden.txt",
},
{
URL: "three/",
Status: http.StatusOK,
Golden: "testdata/golden/three.html",
},
{
URL: "three/a.txt",
Status: http.StatusOK,
Golden: "testdata/golden/a.txt",
},
{
URL: "",
Method: "HEAD",
Status: http.StatusOK,
Golden: "testdata/golden/indexhead.txt",
},
{
URL: "one%25.txt",
Method: "HEAD",
Status: http.StatusOK,
Golden: "testdata/golden/onehead.txt",
},
{
URL: "",
Method: "POST",
Status: http.StatusMethodNotAllowed,
Golden: "testdata/golden/indexpost.txt",
},
{
URL: "one%25.txt",
Method: "POST",
Status: http.StatusOK,
Golden: "testdata/golden/onepost.txt",
},
{
URL: "two.txt",
Status: http.StatusOK,
Golden: "testdata/golden/two.txt",
},
{
URL: "two.txt",
Status: http.StatusPartialContent,
Range: "bytes=2-5",
Golden: "testdata/golden/two2-5.txt",
},
{
URL: "two.txt",
Status: http.StatusPartialContent,
Range: "bytes=0-6",
Golden: "testdata/golden/two-6.txt",
},
{
URL: "two.txt",
Status: http.StatusPartialContent,
Range: "bytes=3-",
Golden: "testdata/golden/two3-.txt",
},
} {
method := test.Method
if method == "" {
method = "GET"
}
req, err := http.NewRequest(method, testURL+test.URL, nil)
require.NoError(t, err)
if test.Range != "" {
req.Header.Add("Range", test.Range)
}
resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)
assert.Equal(t, test.Status, resp.StatusCode, test.Golden)
body, err := io.ReadAll(resp.Body)
require.NoError(t, err)
checkGolden(t, test.Golden, body)
}
}