aboutsummaryrefslogtreecommitdiff
path: root/fs/operations/rc_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'fs/operations/rc_test.go')
-rw-r--r--fs/operations/rc_test.go892
1 files changed, 892 insertions, 0 deletions
diff --git a/fs/operations/rc_test.go b/fs/operations/rc_test.go
new file mode 100644
index 0000000..04357fb
--- /dev/null
+++ b/fs/operations/rc_test.go
@@ -0,0 +1,892 @@
+package operations_test
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "net/http/httptest"
+ "net/url"
+ "os"
+ "path"
+ "sort"
+ "strings"
+ "testing"
+ "time"
+
+ "github.com/rclone/rclone/fs"
+ "github.com/rclone/rclone/fs/cache"
+ "github.com/rclone/rclone/fs/hash"
+ "github.com/rclone/rclone/fs/operations"
+ "github.com/rclone/rclone/fs/rc"
+ "github.com/rclone/rclone/fstest"
+ "github.com/rclone/rclone/lib/diskusage"
+ "github.com/rclone/rclone/lib/rest"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func rcNewRun(t *testing.T, method string) (*fstest.Run, *rc.Call) {
+ if *fstest.RemoteName != "" {
+ t.Skip("Skipping test on non local remote")
+ }
+ r := fstest.NewRun(t)
+ call := rc.Calls.Get(method)
+ assert.NotNil(t, call)
+ cache.Put(r.LocalName, r.Flocal)
+ cache.Put(r.FremoteName, r.Fremote)
+ return r, call
+}
+
+// operations/about: Return the space used on the remote
+func TestRcAbout(t *testing.T) {
+ r, call := rcNewRun(t, "operations/about")
+ r.Mkdir(context.Background(), r.Fremote)
+
+ // Will get an error if remote doesn't support About
+ expectedErr := r.Fremote.Features().About == nil
+
+ in := rc.Params{
+ "fs": r.FremoteName,
+ }
+ out, err := call.Fn(context.Background(), in)
+ if expectedErr {
+ assert.Error(t, err)
+ return
+ }
+ require.NoError(t, err)
+
+ // Can't really check the output much!
+ assert.NotEqual(t, int64(0), out["Total"])
+}
+
+// operations/cleanup: Remove trashed files in the remote or path
+func TestRcCleanup(t *testing.T) {
+ r, call := rcNewRun(t, "operations/cleanup")
+
+ in := rc.Params{
+ "fs": r.LocalName,
+ }
+ out, err := call.Fn(context.Background(), in)
+ require.Error(t, err)
+ assert.Equal(t, rc.Params(nil), out)
+ assert.Contains(t, err.Error(), "doesn't support cleanup")
+}
+
+// operations/copyfile: Copy a file from source remote to destination remote
+func TestRcCopyfile(t *testing.T) {
+ r, call := rcNewRun(t, "operations/copyfile")
+ file1 := r.WriteFile("file1", "file1 contents", t1)
+ r.Mkdir(context.Background(), r.Fremote)
+ r.CheckLocalItems(t, file1)
+ r.CheckRemoteItems(t)
+
+ in := rc.Params{
+ "srcFs": r.LocalName,
+ "srcRemote": "file1",
+ "dstFs": r.FremoteName,
+ "dstRemote": "file1-renamed",
+ }
+ out, err := call.Fn(context.Background(), in)
+ require.NoError(t, err)
+ assert.Equal(t, rc.Params(nil), out)
+
+ r.CheckLocalItems(t, file1)
+ file1.Path = "file1-renamed"
+ r.CheckRemoteItems(t, file1)
+}
+
+// operations/copyurl: Copy the URL to the object
+func TestRcCopyurl(t *testing.T) {
+ r, call := rcNewRun(t, "operations/copyurl")
+ contents := "file1 contents\n"
+ file1 := r.WriteFile("file1", contents, t1)
+ r.Mkdir(context.Background(), r.Fremote)
+ r.CheckRemoteItems(t)
+
+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ _, err := w.Write([]byte(contents))
+ assert.NoError(t, err)
+ }))
+ defer ts.Close()
+
+ in := rc.Params{
+ "fs": r.FremoteName,
+ "remote": "file1",
+ "url": ts.URL,
+ "autoFilename": false,
+ "noClobber": false,
+ }
+ out, err := call.Fn(context.Background(), in)
+ require.NoError(t, err)
+ assert.Equal(t, rc.Params(nil), out)
+
+ in = rc.Params{
+ "fs": r.FremoteName,
+ "remote": "file1",
+ "url": ts.URL,
+ "autoFilename": false,
+ "noClobber": true,
+ }
+ out, err = call.Fn(context.Background(), in)
+ require.Error(t, err)
+ assert.Equal(t, rc.Params(nil), out)
+
+ urlFileName := "filename.txt"
+ in = rc.Params{
+ "fs": r.FremoteName,
+ "remote": "",
+ "url": ts.URL + "/" + urlFileName,
+ "autoFilename": true,
+ "noClobber": false,
+ }
+ out, err = call.Fn(context.Background(), in)
+ require.NoError(t, err)
+ assert.Equal(t, rc.Params(nil), out)
+
+ in = rc.Params{
+ "fs": r.FremoteName,
+ "remote": "",
+ "url": ts.URL,
+ "autoFilename": true,
+ "noClobber": false,
+ }
+ out, err = call.Fn(context.Background(), in)
+ require.Error(t, err)
+ assert.Equal(t, rc.Params(nil), out)
+
+ fstest.CheckListingWithPrecision(t, r.Fremote, []fstest.Item{file1, fstest.NewItem(urlFileName, contents, t1)}, nil, fs.ModTimeNotSupported)
+}
+
+// operations/delete: Remove files in the path
+func TestRcDelete(t *testing.T) {
+ r, call := rcNewRun(t, "operations/delete")
+
+ file1 := r.WriteObject(context.Background(), "small", "1234567890", t2) // 10 bytes
+ file2 := r.WriteObject(context.Background(), "medium", "------------------------------------------------------------", t1) // 60 bytes
+ file3 := r.WriteObject(context.Background(), "large", "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", t1) // 100 bytes
+ r.CheckRemoteItems(t, file1, file2, file3)
+
+ in := rc.Params{
+ "fs": r.FremoteName,
+ }
+ out, err := call.Fn(context.Background(), in)
+ require.NoError(t, err)
+ assert.Equal(t, rc.Params(nil), out)
+
+ r.CheckRemoteItems(t)
+}
+
+// operations/deletefile: Remove the single file pointed to
+func TestRcDeletefile(t *testing.T) {
+ r, call := rcNewRun(t, "operations/deletefile")
+
+ file1 := r.WriteObject(context.Background(), "small", "1234567890", t2) // 10 bytes
+ file2 := r.WriteObject(context.Background(), "medium", "------------------------------------------------------------", t1) // 60 bytes
+ r.CheckRemoteItems(t, file1, file2)
+
+ in := rc.Params{
+ "fs": r.FremoteName,
+ "remote": "small",
+ }
+ out, err := call.Fn(context.Background(), in)
+ require.NoError(t, err)
+ assert.Equal(t, rc.Params(nil), out)
+
+ r.CheckRemoteItems(t, file2)
+}
+
+// operations/list: List the given remote and path in JSON format.
+func TestRcList(t *testing.T) {
+ r, call := rcNewRun(t, "operations/list")
+
+ file1 := r.WriteObject(context.Background(), "a", "a", t1)
+ file2 := r.WriteObject(context.Background(), "subdir/b", "bb", t2)
+
+ r.CheckRemoteItems(t, file1, file2)
+
+ in := rc.Params{
+ "fs": r.FremoteName,
+ "remote": "",
+ }
+ out, err := call.Fn(context.Background(), in)
+ require.NoError(t, err)
+
+ list := out["list"].([]*operations.ListJSONItem)
+ assert.Equal(t, 2, len(list))
+
+ checkFile1 := func(got *operations.ListJSONItem) {
+ assert.WithinDuration(t, t1, got.ModTime.When, time.Second)
+ assert.Equal(t, "a", got.Path)
+ assert.Equal(t, "a", got.Name)
+ assert.Equal(t, int64(1), got.Size)
+ assert.Equal(t, "application/octet-stream", got.MimeType)
+ assert.Equal(t, false, got.IsDir)
+ }
+ checkFile1(list[0])
+
+ checkSubdir := func(got *operations.ListJSONItem) {
+ assert.Equal(t, "subdir", got.Path)
+ assert.Equal(t, "subdir", got.Name)
+ // assert.Equal(t, int64(-1), got.Size) // size can vary for directories
+ assert.Equal(t, "inode/directory", got.MimeType)
+ assert.Equal(t, true, got.IsDir)
+ }
+ checkSubdir(list[1])
+
+ in = rc.Params{
+ "fs": r.FremoteName,
+ "remote": "",
+ "opt": rc.Params{
+ "recurse": true,
+ },
+ }
+ out, err = call.Fn(context.Background(), in)
+ require.NoError(t, err)
+
+ list = out["list"].([]*operations.ListJSONItem)
+ assert.Equal(t, 3, len(list))
+ checkFile1(list[0])
+ checkSubdir(list[1])
+
+ checkFile2 := func(got *operations.ListJSONItem) {
+ assert.WithinDuration(t, t2, got.ModTime.When, time.Second)
+ assert.Equal(t, "subdir/b", got.Path)
+ assert.Equal(t, "b", got.Name)
+ assert.Equal(t, int64(2), got.Size)
+ assert.Equal(t, "application/octet-stream", got.MimeType)
+ assert.Equal(t, false, got.IsDir)
+ }
+ checkFile2(list[2])
+}
+
+// operations/stat: Stat the given remote and path in JSON format.
+func TestRcStat(t *testing.T) {
+ r, call := rcNewRun(t, "operations/stat")
+
+ file1 := r.WriteObject(context.Background(), "subdir/a", "a", t1)
+
+ r.CheckRemoteItems(t, file1)
+
+ fetch := func(t *testing.T, remotePath string) *operations.ListJSONItem {
+ in := rc.Params{
+ "fs": r.FremoteName,
+ "remote": remotePath,
+ }
+ out, err := call.Fn(context.Background(), in)
+ require.NoError(t, err)
+ return out["item"].(*operations.ListJSONItem)
+ }
+
+ t.Run("Root", func(t *testing.T) {
+ stat := fetch(t, "")
+ assert.Equal(t, "", stat.Path)
+ assert.Equal(t, "", stat.Name)
+ assert.Equal(t, int64(-1), stat.Size)
+ assert.Equal(t, "inode/directory", stat.MimeType)
+ assert.Equal(t, true, stat.IsDir)
+ })
+
+ t.Run("File", func(t *testing.T) {
+ stat := fetch(t, "subdir/a")
+ assert.WithinDuration(t, t1, stat.ModTime.When, time.Second)
+ assert.Equal(t, "subdir/a", stat.Path)
+ assert.Equal(t, "a", stat.Name)
+ assert.Equal(t, int64(1), stat.Size)
+ assert.Equal(t, "application/octet-stream", stat.MimeType)
+ assert.Equal(t, false, stat.IsDir)
+ })
+
+ t.Run("Subdir", func(t *testing.T) {
+ stat := fetch(t, "subdir")
+ assert.Equal(t, "subdir", stat.Path)
+ assert.Equal(t, "subdir", stat.Name)
+ // assert.Equal(t, int64(-1), stat.Size) // size can vary for directories
+ assert.Equal(t, "inode/directory", stat.MimeType)
+ assert.Equal(t, true, stat.IsDir)
+ })
+
+ t.Run("NotFound", func(t *testing.T) {
+ stat := fetch(t, "notfound")
+ assert.Nil(t, stat)
+ })
+}
+
+// operations/settier: Set the storage tier of a fs
+func TestRcSetTier(t *testing.T) {
+ ctx := context.Background()
+ r, call := rcNewRun(t, "operations/settier")
+ if !r.Fremote.Features().SetTier {
+ t.Skip("settier not supported")
+ }
+ file1 := r.WriteObject(context.Background(), "file1", "file1 contents", t1)
+ r.CheckRemoteItems(t, file1)
+
+ // Because we don't know what the current tier options here are, let's
+ // just get the current tier, and reuse that
+ o, err := r.Fremote.NewObject(ctx, file1.Path)
+ require.NoError(t, err)
+ trr, ok := o.(fs.GetTierer)
+ require.True(t, ok)
+ ctier := trr.GetTier()
+ in := rc.Params{
+ "fs": r.FremoteName,
+ "tier": ctier,
+ }
+ out, err := call.Fn(context.Background(), in)
+ require.NoError(t, err)
+ assert.Equal(t, rc.Params(nil), out)
+
+}
+
+// operations/settier: Set the storage tier of a file
+func TestRcSetTierFile(t *testing.T) {
+ ctx := context.Background()
+ r, call := rcNewRun(t, "operations/settierfile")
+ if !r.Fremote.Features().SetTier {
+ t.Skip("settier not supported")
+ }
+ file1 := r.WriteObject(context.Background(), "file1", "file1 contents", t1)
+ r.CheckRemoteItems(t, file1)
+
+ // Because we don't know what the current tier options here are, let's
+ // just get the current tier, and reuse that
+ o, err := r.Fremote.NewObject(ctx, file1.Path)
+ require.NoError(t, err)
+ trr, ok := o.(fs.GetTierer)
+ require.True(t, ok)
+ ctier := trr.GetTier()
+ in := rc.Params{
+ "fs": r.FremoteName,
+ "remote": "file1",
+ "tier": ctier,
+ }
+ out, err := call.Fn(context.Background(), in)
+ require.NoError(t, err)
+ assert.Equal(t, rc.Params(nil), out)
+
+}
+
+// operations/mkdir: Make a destination directory or container
+func TestRcMkdir(t *testing.T) {
+ ctx := context.Background()
+ r, call := rcNewRun(t, "operations/mkdir")
+ r.Mkdir(context.Background(), r.Fremote)
+
+ fstest.CheckListingWithPrecision(t, r.Fremote, []fstest.Item{}, []string{}, fs.GetModifyWindow(ctx, r.Fremote))
+
+ in := rc.Params{
+ "fs": r.FremoteName,
+ "remote": "subdir",
+ }
+ out, err := call.Fn(context.Background(), in)
+ require.NoError(t, err)
+ assert.Equal(t, rc.Params(nil), out)
+
+ fstest.CheckListingWithPrecision(t, r.Fremote, []fstest.Item{}, []string{"subdir"}, fs.GetModifyWindow(ctx, r.Fremote))
+}
+
+// operations/movefile: Move a file from source remote to destination remote
+func TestRcMovefile(t *testing.T) {
+ r, call := rcNewRun(t, "operations/movefile")
+ file1 := r.WriteFile("file1", "file1 contents", t1)
+ r.Mkdir(context.Background(), r.Fremote)
+ r.CheckLocalItems(t, file1)
+ r.CheckRemoteItems(t)
+
+ in := rc.Params{
+ "srcFs": r.LocalName,
+ "srcRemote": "file1",
+ "dstFs": r.FremoteName,
+ "dstRemote": "file1-renamed",
+ }
+ out, err := call.Fn(context.Background(), in)
+ require.NoError(t, err)
+ assert.Equal(t, rc.Params(nil), out)
+
+ r.CheckLocalItems(t)
+ file1.Path = "file1-renamed"
+ r.CheckRemoteItems(t, file1)
+}
+
+// operations/purge: Remove a directory or container and all of its contents
+func TestRcPurge(t *testing.T) {
+ ctx := context.Background()
+ r, call := rcNewRun(t, "operations/purge")
+ file1 := r.WriteObject(context.Background(), "subdir/file1", "subdir/file1 contents", t1)
+
+ fstest.CheckListingWithPrecision(t, r.Fremote, []fstest.Item{file1}, []string{"subdir"}, fs.GetModifyWindow(ctx, r.Fremote))
+
+ in := rc.Params{
+ "fs": r.FremoteName,
+ "remote": "subdir",
+ }
+ out, err := call.Fn(context.Background(), in)
+ require.NoError(t, err)
+ assert.Equal(t, rc.Params(nil), out)
+
+ fstest.CheckListingWithPrecision(t, r.Fremote, []fstest.Item{}, []string{}, fs.GetModifyWindow(ctx, r.Fremote))
+}
+
+// operations/rmdir: Remove an empty directory or container
+func TestRcRmdir(t *testing.T) {
+ ctx := context.Background()
+ r, call := rcNewRun(t, "operations/rmdir")
+ r.Mkdir(context.Background(), r.Fremote)
+ assert.NoError(t, r.Fremote.Mkdir(context.Background(), "subdir"))
+
+ fstest.CheckListingWithPrecision(t, r.Fremote, []fstest.Item{}, []string{"subdir"}, fs.GetModifyWindow(ctx, r.Fremote))
+
+ in := rc.Params{
+ "fs": r.FremoteName,
+ "remote": "subdir",
+ }
+ out, err := call.Fn(context.Background(), in)
+ require.NoError(t, err)
+ assert.Equal(t, rc.Params(nil), out)
+
+ fstest.CheckListingWithPrecision(t, r.Fremote, []fstest.Item{}, []string{}, fs.GetModifyWindow(ctx, r.Fremote))
+}
+
+// operations/rmdirs: Remove all the empty directories in the path
+func TestRcRmdirs(t *testing.T) {
+ ctx := context.Background()
+ r, call := rcNewRun(t, "operations/rmdirs")
+ r.Mkdir(context.Background(), r.Fremote)
+ assert.NoError(t, r.Fremote.Mkdir(context.Background(), "subdir"))
+ assert.NoError(t, r.Fremote.Mkdir(context.Background(), "subdir/subsubdir"))
+
+ fstest.CheckListingWithPrecision(t, r.Fremote, []fstest.Item{}, []string{"subdir", "subdir/subsubdir"}, fs.GetModifyWindow(ctx, r.Fremote))
+
+ in := rc.Params{
+ "fs": r.FremoteName,
+ "remote": "subdir",
+ }
+ out, err := call.Fn(context.Background(), in)
+ require.NoError(t, err)
+ assert.Equal(t, rc.Params(nil), out)
+
+ fstest.CheckListingWithPrecision(t, r.Fremote, []fstest.Item{}, []string{}, fs.GetModifyWindow(ctx, r.Fremote))
+
+ assert.NoError(t, r.Fremote.Mkdir(context.Background(), "subdir"))
+ assert.NoError(t, r.Fremote.Mkdir(context.Background(), "subdir/subsubdir"))
+
+ in = rc.Params{
+ "fs": r.FremoteName,
+ "remote": "subdir",
+ "leaveRoot": true,
+ }
+ out, err = call.Fn(context.Background(), in)
+ require.NoError(t, err)
+ assert.Equal(t, rc.Params(nil), out)
+
+ fstest.CheckListingWithPrecision(t, r.Fremote, []fstest.Item{}, []string{"subdir"}, fs.GetModifyWindow(ctx, r.Fremote))
+
+}
+
+// operations/size: Count the number of bytes and files in remote
+func TestRcSize(t *testing.T) {
+ r, call := rcNewRun(t, "operations/size")
+ file1 := r.WriteObject(context.Background(), "small", "1234567890", t2) // 10 bytes
+ file2 := r.WriteObject(context.Background(), "subdir/medium", "------------------------------------------------------------", t1) // 60 bytes
+ file3 := r.WriteObject(context.Background(), "subdir/subsubdir/large", "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", t1) // 50 bytes
+ r.CheckRemoteItems(t, file1, file2, file3)
+
+ in := rc.Params{
+ "fs": r.FremoteName,
+ }
+ out, err := call.Fn(context.Background(), in)
+ require.NoError(t, err)
+ assert.Equal(t, rc.Params{
+ "count": int64(3),
+ "bytes": int64(120),
+ "sizeless": int64(0),
+ }, out)
+}
+
+// operations/publiclink: Create or retrieve a public link to the given file or folder.
+func TestRcPublicLink(t *testing.T) {
+ r, call := rcNewRun(t, "operations/publiclink")
+ in := rc.Params{
+ "fs": r.FremoteName,
+ "remote": "",
+ "expire": "5m",
+ "unlink": false,
+ }
+ _, err := call.Fn(context.Background(), in)
+ require.Error(t, err)
+ assert.Contains(t, err.Error(), "doesn't support public links")
+}
+
+// operations/fsinfo: Return information about the remote
+func TestRcFsInfo(t *testing.T) {
+ r, call := rcNewRun(t, "operations/fsinfo")
+ in := rc.Params{
+ "fs": r.FremoteName,
+ }
+ got, err := call.Fn(context.Background(), in)
+ require.NoError(t, err)
+ want := operations.GetFsInfo(r.Fremote)
+ assert.Equal(t, want.Name, got["Name"])
+ assert.Equal(t, want.Root, got["Root"])
+ assert.Equal(t, want.String, got["String"])
+ assert.Equal(t, float64(want.Precision), got["Precision"])
+ var hashes []any
+ for _, hash := range want.Hashes {
+ hashes = append(hashes, hash)
+ }
+ assert.Equal(t, hashes, got["Hashes"])
+ var features = map[string]any{}
+ for k, v := range want.Features {
+ features[k] = v
+ }
+ assert.Equal(t, features, got["Features"])
+
+}
+
+// operations/uploadfile : Tests if upload file succeeds
+func TestUploadFile(t *testing.T) {
+ r, call := rcNewRun(t, "operations/uploadfile")
+ ctx := context.Background()
+
+ testFileName := "uploadfile-test.txt"
+ testFileContent := "Hello World"
+ r.WriteFile(testFileName, testFileContent, t1)
+ testItem1 := fstest.NewItem(testFileName, testFileContent, t1)
+ testItem2 := fstest.NewItem(path.Join("subdir", testFileName), testFileContent, t1)
+
+ currentFile, err := os.Open(path.Join(r.LocalName, testFileName))
+ require.NoError(t, err)
+
+ defer func() {
+ assert.NoError(t, currentFile.Close())
+ }()
+
+ formReader, contentType, _, err := rest.MultipartUpload(ctx, currentFile, url.Values{}, "file", testFileName, "application/octet-stream")
+ require.NoError(t, err)
+
+ httpReq := httptest.NewRequest("POST", "/", formReader)
+ httpReq.Header.Add("Content-Type", contentType)
+
+ in := rc.Params{
+ "_request": httpReq,
+ "fs": r.FremoteName,
+ "remote": "",
+ }
+
+ _, err = call.Fn(context.Background(), in)
+ require.NoError(t, err)
+
+ fstest.CheckListingWithPrecision(t, r.Fremote, []fstest.Item{testItem1}, nil, fs.ModTimeNotSupported)
+
+ assert.NoError(t, r.Fremote.Mkdir(context.Background(), "subdir"))
+
+ currentFile2, err := os.Open(path.Join(r.LocalName, testFileName))
+ require.NoError(t, err)
+
+ defer func() {
+ assert.NoError(t, currentFile2.Close())
+ }()
+
+ formReader, contentType, _, err = rest.MultipartUpload(ctx, currentFile2, url.Values{}, "file", testFileName, "application/octet-stream")
+ require.NoError(t, err)
+
+ httpReq = httptest.NewRequest("POST", "/", formReader)
+ httpReq.Header.Add("Content-Type", contentType)
+
+ in = rc.Params{
+ "_request": httpReq,
+ "fs": r.FremoteName,
+ "remote": "subdir",
+ }
+
+ _, err = call.Fn(context.Background(), in)
+ require.NoError(t, err)
+
+ fstest.CheckListingWithPrecision(t, r.Fremote, []fstest.Item{testItem1, testItem2}, nil, fs.ModTimeNotSupported)
+
+}
+
+// operations/command: Runs a backend command
+func TestRcCommand(t *testing.T) {
+ r, call := rcNewRun(t, "backend/command")
+ in := rc.Params{
+ "fs": r.FremoteName,
+ "command": "noop",
+ "opt": map[string]string{
+ "echo": "true",
+ "blue": "",
+ },
+ "arg": []string{
+ "path1",
+ "path2",
+ },
+ }
+ got, err := call.Fn(context.Background(), in)
+ if err != nil {
+ assert.False(t, r.Fremote.Features().IsLocal, "mustn't fail on local remote")
+ assert.Contains(t, err.Error(), "command not found")
+ return
+ }
+ want := rc.Params{"result": map[string]any{
+ "arg": []string{
+ "path1",
+ "path2",
+ },
+ "name": "noop",
+ "opt": map[string]string{
+ "blue": "",
+ "echo": "true",
+ },
+ }}
+ assert.Equal(t, want, got)
+ errTxt := "explosion in the sausage factory"
+ in["opt"].(map[string]string)["error"] = errTxt
+ _, err = call.Fn(context.Background(), in)
+ assert.Error(t, err)
+ assert.Contains(t, err.Error(), errTxt)
+}
+
+// operations/command: Runs a backend command
+func TestRcDu(t *testing.T) {
+ ctx := context.Background()
+ _, call := rcNewRun(t, "core/du")
+ in := rc.Params{}
+ out, err := call.Fn(ctx, in)
+ if err == diskusage.ErrUnsupported {
+ t.Skip(err)
+ }
+ assert.NotEqual(t, "", out["dir"])
+ info := out["info"].(diskusage.Info)
+ assert.True(t, info.Total != 0)
+ assert.True(t, info.Total > info.Free)
+ assert.True(t, info.Total > info.Available)
+ assert.True(t, info.Free >= info.Available)
+}
+
+// operations/check: check the source and destination are the same
+func TestRcCheck(t *testing.T) {
+ ctx := context.Background()
+ r, call := rcNewRun(t, "operations/check")
+ r.Mkdir(ctx, r.Fremote)
+
+ MD5SUMS := `
+0ef726ce9b1a7692357ff70dd321d595 file1
+deadbeefcafe00000000000000000000 subdir/file2
+0386a8b8fcf672c326845c00ba41b9e2 subdir/subsubdir/file4
+`
+
+ file1 := r.WriteBoth(ctx, "file1", "file1 contents", t1)
+ file2 := r.WriteFile("subdir/file2", MD5SUMS, t2)
+ file3 := r.WriteObject(ctx, "subdir/subsubdir/file3", "file3 contents", t3)
+ file4a := r.WriteFile("subdir/subsubdir/file4", "file4 contents", t3)
+ file4b := r.WriteObject(ctx, "subdir/subsubdir/file4", "file4 different contents", t3)
+ // operations.HashLister(ctx, hash.MD5, false, false, r.Fremote, os.Stdout)
+
+ r.CheckLocalItems(t, file1, file2, file4a)
+ r.CheckRemoteItems(t, file1, file3, file4b)
+
+ pstring := func(items ...fstest.Item) *[]string {
+ xs := make([]string, len(items))
+ for i, item := range items {
+ xs[i] = item.Path
+ }
+ return &xs
+ }
+
+ for _, testName := range []string{"Normal", "Download"} {
+ t.Run(testName, func(t *testing.T) {
+ in := rc.Params{
+ "srcFs": r.LocalName,
+ "dstFs": r.FremoteName,
+ "combined": true,
+ "missingOnSrc": true,
+ "missingOnDst": true,
+ "match": true,
+ "differ": true,
+ "error": true,
+ }
+ if testName == "Download" {
+ in["download"] = true
+ }
+ out, err := call.Fn(ctx, in)
+ require.NoError(t, err)
+
+ combined := []string{
+ "= " + file1.Path,
+ "+ " + file2.Path,
+ "- " + file3.Path,
+ "* " + file4a.Path,
+ }
+ sort.Strings(combined)
+ sort.Strings(*out["combined"].(*[]string))
+ want := rc.Params{
+ "missingOnSrc": pstring(file3),
+ "missingOnDst": pstring(file2),
+ "differ": pstring(file4a),
+ "error": pstring(),
+ "match": pstring(file1),
+ "combined": &combined,
+ "status": "3 differences found",
+ "success": false,
+ }
+ if testName == "Normal" {
+ want["hashType"] = "md5"
+ }
+
+ assert.Equal(t, want, out)
+ })
+ }
+
+ t.Run("CheckFile", func(t *testing.T) {
+ // The checksum file is treated as the source and srcFs is not used
+ in := rc.Params{
+ "dstFs": r.FremoteName,
+ "combined": true,
+ "missingOnSrc": true,
+ "missingOnDst": true,
+ "match": true,
+ "differ": true,
+ "error": true,
+ "checkFileFs": r.LocalName,
+ "checkFileRemote": file2.Path,
+ "checkFileHash": "md5",
+ }
+ out, err := call.Fn(ctx, in)
+ require.NoError(t, err)
+
+ combined := []string{
+ "= " + file1.Path,
+ "+ " + file2.Path,
+ "- " + file3.Path,
+ "* " + file4a.Path,
+ }
+ sort.Strings(combined)
+ sort.Strings(*out["combined"].(*[]string))
+ if strings.HasPrefix(out["status"].(string), "file not in") {
+ out["status"] = "file not in"
+ }
+ want := rc.Params{
+ "missingOnSrc": pstring(file3),
+ "missingOnDst": pstring(file2),
+ "differ": pstring(file4a),
+ "error": pstring(),
+ "match": pstring(file1),
+ "combined": &combined,
+ "hashType": "md5",
+ "status": "file not in",
+ "success": false,
+ }
+
+ assert.Equal(t, want, out)
+ })
+
+}
+
+// operations/hashsum: hashsum a directory
+func TestRcHashsum(t *testing.T) {
+ ctx := context.Background()
+ r, call := rcNewRun(t, "operations/hashsum")
+ r.Mkdir(ctx, r.Fremote)
+
+ file1Contents := "file1 contents"
+ file1 := r.WriteBoth(ctx, "hashsum-file1", file1Contents, t1)
+ r.CheckLocalItems(t, file1)
+ r.CheckRemoteItems(t, file1)
+
+ hasher := hash.NewMultiHasher()
+ _, err := hasher.Write([]byte(file1Contents))
+ require.NoError(t, err)
+
+ for _, test := range []struct {
+ ht hash.Type
+ base64 bool
+ download bool
+ }{
+ {
+ ht: r.Fremote.Hashes().GetOne(),
+ }, {
+ ht: r.Fremote.Hashes().GetOne(),
+ base64: true,
+ }, {
+ ht: hash.Whirlpool,
+ base64: false,
+ download: true,
+ }, {
+ ht: hash.Whirlpool,
+ base64: true,
+ download: true,
+ },
+ } {
+ t.Run(fmt.Sprintf("hash=%v,base64=%v,download=%v", test.ht, test.base64, test.download), func(t *testing.T) {
+ file1Hash, err := hasher.SumString(test.ht, test.base64)
+ require.NoError(t, err)
+
+ in := rc.Params{
+ "fs": r.FremoteName,
+ "hashType": test.ht.String(),
+ "base64": test.base64,
+ "download": test.download,
+ }
+
+ out, err := call.Fn(ctx, in)
+ require.NoError(t, err)
+ assert.Equal(t, test.ht.String(), out["hashType"])
+ want := []string{
+ fmt.Sprintf("%s hashsum-file1", file1Hash),
+ }
+ assert.Equal(t, want, out["hashsum"])
+ })
+ }
+}
+
+// operations/hashsum: hashsum a single file
+func TestRcHashsumSingleFile(t *testing.T) {
+ ctx := context.Background()
+ r, call := rcNewRun(t, "operations/hashsum")
+ r.Mkdir(ctx, r.Fremote)
+
+ file1Contents := "file1 contents"
+ file1 := r.WriteBoth(ctx, "hashsum-file1", file1Contents, t1)
+ file2Contents := "file2 contents"
+ file2 := r.WriteBoth(ctx, "hashsum-file2", file2Contents, t1)
+ r.CheckLocalItems(t, file1, file2)
+ r.CheckRemoteItems(t, file1, file2)
+
+ // Make an fs pointing to just the file
+ fsString := path.Join(r.FremoteName, file1.Path)
+
+ in := rc.Params{
+ "fs": fsString,
+ "hashType": "MD5",
+ "download": true,
+ }
+
+ out, err := call.Fn(ctx, in)
+ require.NoError(t, err)
+ assert.Equal(t, "md5", out["hashType"])
+ assert.Equal(t, []string{"0ef726ce9b1a7692357ff70dd321d595 hashsum-file1"}, out["hashsum"])
+}
+
+// operations/hashsumfile: hashsum a single file
+func TestRcHashsumFile(t *testing.T) {
+ ctx := context.Background()
+ r, call := rcNewRun(t, "operations/hashsumfile")
+ r.Mkdir(ctx, r.Fremote)
+
+ file1Contents := "file1 contents"
+ file1 := r.WriteBoth(ctx, "hashsumfile-file1", file1Contents, t1)
+ r.CheckLocalItems(t, file1)
+ r.CheckRemoteItems(t, file1)
+
+ in := rc.Params{
+ "fs": r.FremoteName,
+ "remote": file1.Path,
+ "hashType": "MD5",
+ "download": true,
+ }
+
+ out, err := call.Fn(ctx, in)
+ require.NoError(t, err)
+ assert.Equal(t, "md5", out["hashType"])
+ assert.Equal(t, "0ef726ce9b1a7692357ff70dd321d595", out["hash"])
+}