rclone/fs/operations/lsjson_test.go
Nick Craig-Wood fd83071b6b rc: fix operations/stat with trailing /
Before this change using operations/stat with a remote pointing to a
dir with a trailing / would return a null output rather than the
correct info.

This was because the directory was not found with a trailing slash in
the directory listing.

Fixes #6817
2023-03-22 16:22:45 +00:00

394 lines
9.5 KiB
Go

package operations_test
import (
"context"
"sort"
"testing"
"time"
"github.com/rclone/rclone/fs"
"github.com/rclone/rclone/fs/operations"
"github.com/rclone/rclone/fstest"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// Compare a and b in a file system independent way
func compareListJSONItem(t *testing.T, a, b *operations.ListJSONItem, precision time.Duration) {
assert.Equal(t, a.Path, b.Path, "Path")
assert.Equal(t, a.Name, b.Name, "Name")
// assert.Equal(t, a.EncryptedPath, b.EncryptedPath, "EncryptedPath")
// assert.Equal(t, a.Encrypted, b.Encrypted, "Encrypted")
if !a.IsDir {
assert.Equal(t, a.Size, b.Size, "Size")
}
// assert.Equal(t, a.MimeType, a.Mib.MimeType, "MimeType")
if !a.IsDir {
fstest.AssertTimeEqualWithPrecision(t, "ListJSON", a.ModTime.When, b.ModTime.When, precision)
}
assert.Equal(t, a.IsDir, b.IsDir, "IsDir")
// assert.Equal(t, a.Hashes, a.b.Hashes, "Hashes")
// assert.Equal(t, a.ID, b.ID, "ID")
// assert.Equal(t, a.OrigID, a.b.OrigID, "OrigID")
// assert.Equal(t, a.Tier, b.Tier, "Tier")
// assert.Equal(t, a.IsBucket, a.Isb.IsBucket, "IsBucket")
}
func TestListJSON(t *testing.T) {
ctx := context.Background()
r := fstest.NewRun(t)
file1 := r.WriteBoth(ctx, "file1", "file1", t1)
file2 := r.WriteBoth(ctx, "sub/file2", "sub/file2", t2)
r.CheckRemoteItems(t, file1, file2)
precision := fs.GetModifyWindow(ctx, r.Fremote)
for _, test := range []struct {
name string
remote string
opt operations.ListJSONOpt
want []*operations.ListJSONItem
}{
{
name: "Default",
opt: operations.ListJSONOpt{},
want: []*operations.ListJSONItem{{
Path: "file1",
Name: "file1",
Size: 5,
ModTime: operations.Timestamp{When: t1},
IsDir: false,
}, {
Path: "sub",
Name: "sub",
IsDir: true,
}},
}, {
name: "FilesOnly",
opt: operations.ListJSONOpt{
FilesOnly: true,
},
want: []*operations.ListJSONItem{{
Path: "file1",
Name: "file1",
Size: 5,
ModTime: operations.Timestamp{When: t1},
IsDir: false,
}},
}, {
name: "DirsOnly",
opt: operations.ListJSONOpt{
DirsOnly: true,
},
want: []*operations.ListJSONItem{{
Path: "sub",
Name: "sub",
IsDir: true,
}},
}, {
name: "Recurse",
opt: operations.ListJSONOpt{
Recurse: true,
},
want: []*operations.ListJSONItem{{
Path: "file1",
Name: "file1",
Size: 5,
ModTime: operations.Timestamp{When: t1},
IsDir: false,
}, {
Path: "sub",
Name: "sub",
IsDir: true,
}, {
Path: "sub/file2",
Name: "file2",
Size: 9,
ModTime: operations.Timestamp{When: t2},
IsDir: false,
}},
}, {
name: "SubDir",
remote: "sub",
opt: operations.ListJSONOpt{},
want: []*operations.ListJSONItem{{
Path: "sub/file2",
Name: "file2",
Size: 9,
ModTime: operations.Timestamp{When: t2},
IsDir: false,
}},
}, {
name: "NoModTime",
opt: operations.ListJSONOpt{
FilesOnly: true,
NoModTime: true,
},
want: []*operations.ListJSONItem{{
Path: "file1",
Name: "file1",
Size: 5,
ModTime: operations.Timestamp{When: time.Time{}},
IsDir: false,
}},
}, {
name: "NoMimeType",
opt: operations.ListJSONOpt{
FilesOnly: true,
NoMimeType: true,
},
want: []*operations.ListJSONItem{{
Path: "file1",
Name: "file1",
Size: 5,
ModTime: operations.Timestamp{When: t1},
IsDir: false,
}},
}, {
name: "ShowHash",
opt: operations.ListJSONOpt{
FilesOnly: true,
ShowHash: true,
},
want: []*operations.ListJSONItem{{
Path: "file1",
Name: "file1",
Size: 5,
ModTime: operations.Timestamp{When: t1},
IsDir: false,
}},
}, {
name: "HashTypes",
opt: operations.ListJSONOpt{
FilesOnly: true,
ShowHash: true,
HashTypes: []string{"MD5"},
},
want: []*operations.ListJSONItem{{
Path: "file1",
Name: "file1",
Size: 5,
ModTime: operations.Timestamp{When: t1},
IsDir: false,
}},
}, {
name: "Metadata",
opt: operations.ListJSONOpt{
FilesOnly: true,
Metadata: true,
},
want: []*operations.ListJSONItem{{
Path: "file1",
Name: "file1",
Size: 5,
ModTime: operations.Timestamp{When: t1},
IsDir: false,
}},
},
} {
t.Run(test.name, func(t *testing.T) {
var got []*operations.ListJSONItem
require.NoError(t, operations.ListJSON(ctx, r.Fremote, test.remote, &test.opt, func(item *operations.ListJSONItem) error {
got = append(got, item)
return nil
}))
sort.Slice(got, func(i, j int) bool {
return got[i].Path < got[j].Path
})
require.Equal(t, len(test.want), len(got), "Wrong number of results")
for i := range test.want {
compareListJSONItem(t, test.want[i], got[i], precision)
if test.opt.NoMimeType {
assert.Equal(t, "", got[i].MimeType)
} else {
assert.NotEqual(t, "", got[i].MimeType)
}
if test.opt.ShowHash {
hashes := got[i].Hashes
assert.NotNil(t, hashes)
if len(test.opt.HashTypes) > 0 && len(hashes) > 0 {
assert.Equal(t, 1, len(hashes))
}
if hashes["crc32"] != "" {
assert.Equal(t, "9ee760e5", hashes["crc32"])
}
if hashes["dropbox"] != "" {
assert.Equal(t, "f4d62afeaee6f35d3efdd8c66623360395165473bcc958f835343eb3f542f983", hashes["dropbox"])
}
if hashes["mailru"] != "" {
assert.Equal(t, "66696c6531000000000000000000000000000000", hashes["mailru"])
}
if hashes["md5"] != "" {
assert.Equal(t, "826e8142e6baabe8af779f5f490cf5f5", hashes["md5"])
}
if hashes["quickxor"] != "" {
assert.Equal(t, "6648031bca100300000000000500000000000000", hashes["quickxor"])
}
if hashes["sha1"] != "" {
assert.Equal(t, "60b27f004e454aca81b0480209cce5081ec52390", hashes["sha1"])
}
if hashes["sha256"] != "" {
assert.Equal(t, "c147efcfc2d7ea666a9e4f5187b115c90903f0fc896a56df9a6ef5d8f3fc9f31", hashes["sha256"])
}
if hashes["whirlpool"] != "" {
assert.Equal(t, "02fa11755b6470bfc5aab6d94cde5cf2939474fb5b0ebbf8ddf3d32bf06aa438eb92eac097047c02017dc1c317ee83fa8a2717ca4d544b4ee75b3231d1c466b0", hashes["whirlpool"])
}
} else {
assert.Nil(t, got[i].Hashes)
}
}
})
}
}
func TestStatJSON(t *testing.T) {
ctx := context.Background()
r := fstest.NewRun(t)
file1 := r.WriteBoth(ctx, "file1", "file1", t1)
file2 := r.WriteBoth(ctx, "sub/file2", "sub/file2", t2)
r.CheckRemoteItems(t, file1, file2)
precision := fs.GetModifyWindow(ctx, r.Fremote)
for _, test := range []struct {
name string
remote string
opt operations.ListJSONOpt
want *operations.ListJSONItem
}{
{
name: "Root",
remote: "",
opt: operations.ListJSONOpt{},
want: &operations.ListJSONItem{
Path: "",
Name: "",
IsDir: true,
},
}, {
name: "RootFilesOnly",
remote: "",
opt: operations.ListJSONOpt{
FilesOnly: true,
},
want: nil,
}, {
name: "RootDirsOnly",
remote: "",
opt: operations.ListJSONOpt{
DirsOnly: true,
},
want: &operations.ListJSONItem{
Path: "",
Name: "",
IsDir: true,
},
}, {
name: "Dir",
remote: "sub",
opt: operations.ListJSONOpt{},
want: &operations.ListJSONItem{
Path: "sub",
Name: "sub",
IsDir: true,
},
}, {
name: "DirWithTrailingSlash",
remote: "sub/",
opt: operations.ListJSONOpt{},
want: &operations.ListJSONItem{
Path: "sub",
Name: "sub",
IsDir: true,
},
}, {
name: "File",
remote: "file1",
opt: operations.ListJSONOpt{},
want: &operations.ListJSONItem{
Path: "file1",
Name: "file1",
Size: 5,
ModTime: operations.Timestamp{When: t1},
IsDir: false,
},
}, {
name: "NotFound",
remote: "notfound",
opt: operations.ListJSONOpt{},
want: nil,
}, {
name: "DirFilesOnly",
remote: "sub",
opt: operations.ListJSONOpt{
FilesOnly: true,
},
want: nil,
}, {
name: "FileFilesOnly",
remote: "file1",
opt: operations.ListJSONOpt{
FilesOnly: true,
},
want: &operations.ListJSONItem{
Path: "file1",
Name: "file1",
Size: 5,
ModTime: operations.Timestamp{When: t1},
IsDir: false,
},
}, {
name: "NotFoundFilesOnly",
remote: "notfound",
opt: operations.ListJSONOpt{
FilesOnly: true,
},
want: nil,
}, {
name: "DirDirsOnly",
remote: "sub",
opt: operations.ListJSONOpt{
DirsOnly: true,
},
want: &operations.ListJSONItem{
Path: "sub",
Name: "sub",
IsDir: true,
},
}, {
name: "FileDirsOnly",
remote: "file1",
opt: operations.ListJSONOpt{
DirsOnly: true,
},
want: nil,
}, {
name: "NotFoundDirsOnly",
remote: "notfound",
opt: operations.ListJSONOpt{
DirsOnly: true,
},
want: nil,
},
} {
t.Run(test.name, func(t *testing.T) {
got, err := operations.StatJSON(ctx, r.Fremote, test.remote, &test.opt)
require.NoError(t, err)
if test.want == nil {
assert.Nil(t, got)
return
}
require.NotNil(t, got)
compareListJSONItem(t, test.want, got, precision)
})
}
t.Run("RootNotFound", func(t *testing.T) {
f, err := fs.NewFs(ctx, r.FremoteName+"/notfound")
require.NoError(t, err)
_, err = operations.StatJSON(ctx, f, "", &operations.ListJSONOpt{})
// This should return an error except for bucket based remotes
assert.True(t, err != nil || f.Features().BucketBased, "Need an error for non bucket based backends")
})
}