mirror of
https://github.com/rclone/rclone.git
synced 2024-12-21 11:06:55 +08:00
ef2c5a1998
* serve restic: return internal error if listing failed If listing a remote failed, then rclone returned http status "not found". This has become a problem since restic 0.16.0 which ignores "not found"-errors while listing a directory. Just return internal server error, if something unexpected happens while listing a directory. * serve restic: fix error handling if getting a file fails If the call to `newObject` in `serveObject` fails, then rclone always returned a "not found" error. This prevents restic from distinguishing permanent "not found" errors from everything else. Thus, only return "not found" if the object is not found and an internal server error otherwise.
173 lines
4.3 KiB
Go
173 lines
4.3 KiB
Go
// Serve restic tests set up a server and run the integration tests
|
|
// for restic against it.
|
|
|
|
package restic
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"os/exec"
|
|
"testing"
|
|
|
|
_ "github.com/rclone/rclone/backend/all"
|
|
"github.com/rclone/rclone/cmd"
|
|
"github.com/rclone/rclone/fs"
|
|
"github.com/rclone/rclone/fstest"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
const (
|
|
testBindAddress = "localhost:0"
|
|
resticSource = "../../../../../restic/restic"
|
|
)
|
|
|
|
func newOpt() Options {
|
|
opt := DefaultOpt
|
|
opt.HTTP.ListenAddr = []string{testBindAddress}
|
|
return opt
|
|
}
|
|
|
|
// TestRestic runs the restic server then runs the unit tests for the
|
|
// restic remote against it.
|
|
//
|
|
// Requires the restic source code in the location indicated by resticSource.
|
|
func TestResticIntegration(t *testing.T) {
|
|
ctx := context.Background()
|
|
_, err := os.Stat(resticSource)
|
|
if err != nil {
|
|
t.Skipf("Skipping test as restic source not found: %v", err)
|
|
}
|
|
|
|
opt := newOpt()
|
|
|
|
fstest.Initialise()
|
|
|
|
fremote, _, clean, err := fstest.RandomRemote()
|
|
assert.NoError(t, err)
|
|
defer clean()
|
|
|
|
err = fremote.Mkdir(context.Background(), "")
|
|
assert.NoError(t, err)
|
|
|
|
// Start the server
|
|
s, err := newServer(ctx, fremote, &opt)
|
|
require.NoError(t, err)
|
|
testURL := s.Server.URLs()[0]
|
|
defer func() {
|
|
_ = s.Shutdown()
|
|
}()
|
|
|
|
// Change directory to run the tests
|
|
err = os.Chdir(resticSource)
|
|
require.NoError(t, err, "failed to cd to restic source code")
|
|
|
|
// Run the restic tests
|
|
runTests := func(path string) {
|
|
args := []string{"test", "./internal/backend/rest", "-run", "TestBackendRESTExternalServer", "-count=1"}
|
|
if testing.Verbose() {
|
|
args = append(args, "-v")
|
|
}
|
|
cmd := exec.Command("go", args...)
|
|
cmd.Env = append(os.Environ(),
|
|
"RESTIC_TEST_REST_REPOSITORY=rest:"+testURL+path,
|
|
"GO111MODULE=on",
|
|
)
|
|
out, err := cmd.CombinedOutput()
|
|
if len(out) != 0 {
|
|
t.Logf("\n----------\n%s----------\n", string(out))
|
|
}
|
|
assert.NoError(t, err, "Running restic integration tests")
|
|
}
|
|
|
|
// Run the tests with no path
|
|
runTests("")
|
|
//... and again with a path
|
|
runTests("potato/sausage/")
|
|
|
|
}
|
|
|
|
func TestMakeRemote(t *testing.T) {
|
|
for _, test := range []struct {
|
|
in, want string
|
|
}{
|
|
{"/", ""},
|
|
{"/data", "data"},
|
|
{"/data/", "data"},
|
|
{"/data/1", "data/1"},
|
|
{"/data/12", "data/12/12"},
|
|
{"/data/123", "data/12/123"},
|
|
{"/data/123/", "data/12/123"},
|
|
{"/keys", "keys"},
|
|
{"/keys/1", "keys/1"},
|
|
{"/keys/12", "keys/12"},
|
|
{"/keys/123", "keys/123"},
|
|
} {
|
|
r := httptest.NewRequest("GET", test.in, nil)
|
|
w := httptest.NewRecorder()
|
|
next := http.HandlerFunc(func(_ http.ResponseWriter, request *http.Request) {
|
|
remote, ok := request.Context().Value(ContextRemoteKey).(string)
|
|
assert.True(t, ok, "Failed to get remote from context")
|
|
assert.Equal(t, test.want, remote, test.in)
|
|
})
|
|
got := WithRemote(next)
|
|
got.ServeHTTP(w, r)
|
|
}
|
|
}
|
|
|
|
type listErrorFs struct {
|
|
fs.Fs
|
|
}
|
|
|
|
func (f *listErrorFs) List(ctx context.Context, dir string) (entries fs.DirEntries, err error) {
|
|
return fs.DirEntries{}, errors.New("oops")
|
|
}
|
|
|
|
func TestListErrors(t *testing.T) {
|
|
ctx := context.Background()
|
|
// setup rclone with a local backend in a temporary directory
|
|
tempdir := t.TempDir()
|
|
opt := newOpt()
|
|
|
|
// make a new file system in the temp dir
|
|
f := &listErrorFs{Fs: cmd.NewFsSrc([]string{tempdir})}
|
|
s, err := newServer(ctx, f, &opt)
|
|
require.NoError(t, err)
|
|
router := s.Server.Router()
|
|
|
|
req := newRequest(t, "GET", "/test/snapshots/", nil)
|
|
checkRequest(t, router.ServeHTTP, req, []wantFunc{wantCode(http.StatusInternalServerError)})
|
|
}
|
|
|
|
type newObjectErrorFs struct {
|
|
fs.Fs
|
|
err error
|
|
}
|
|
|
|
func (f *newObjectErrorFs) NewObject(ctx context.Context, remote string) (fs.Object, error) {
|
|
return nil, f.err
|
|
}
|
|
|
|
func TestServeErrors(t *testing.T) {
|
|
ctx := context.Background()
|
|
// setup rclone with a local backend in a temporary directory
|
|
tempdir := t.TempDir()
|
|
opt := newOpt()
|
|
|
|
// make a new file system in the temp dir
|
|
f := &newObjectErrorFs{Fs: cmd.NewFsSrc([]string{tempdir})}
|
|
s, err := newServer(ctx, f, &opt)
|
|
require.NoError(t, err)
|
|
router := s.Server.Router()
|
|
|
|
f.err = errors.New("oops")
|
|
req := newRequest(t, "GET", "/test/config", nil)
|
|
checkRequest(t, router.ServeHTTP, req, []wantFunc{wantCode(http.StatusInternalServerError)})
|
|
|
|
f.err = fs.ErrorObjectNotFound
|
|
checkRequest(t, router.ServeHTTP, req, []wantFunc{wantCode(http.StatusNotFound)})
|
|
}
|