github.com/rclone/rclone@v1.66.1-0.20240517100346-7b89735ae726/fs/operations/lsjson_test.go (about)

     1  package operations_test
     2  
     3  import (
     4  	"context"
     5  	"sort"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/rclone/rclone/fs"
    10  	"github.com/rclone/rclone/fs/operations"
    11  	"github.com/rclone/rclone/fstest"
    12  	"github.com/stretchr/testify/assert"
    13  	"github.com/stretchr/testify/require"
    14  )
    15  
    16  // Compare a and b in a file system independent way
    17  func compareListJSONItem(t *testing.T, a, b *operations.ListJSONItem, precision time.Duration) {
    18  	assert.Equal(t, a.Path, b.Path, "Path")
    19  	assert.Equal(t, a.Name, b.Name, "Name")
    20  	// assert.Equal(t, a.EncryptedPath, b.EncryptedPath, "EncryptedPath")
    21  	// assert.Equal(t, a.Encrypted, b.Encrypted, "Encrypted")
    22  	if !a.IsDir {
    23  		assert.Equal(t, a.Size, b.Size, "Size")
    24  	}
    25  	// assert.Equal(t, a.MimeType, a.Mib.MimeType, "MimeType")
    26  	if !a.IsDir {
    27  		fstest.AssertTimeEqualWithPrecision(t, "ListJSON", a.ModTime.When, b.ModTime.When, precision)
    28  	}
    29  	assert.Equal(t, a.IsDir, b.IsDir, "IsDir")
    30  	// assert.Equal(t, a.Hashes, a.b.Hashes, "Hashes")
    31  	// assert.Equal(t, a.ID, b.ID, "ID")
    32  	// assert.Equal(t, a.OrigID, a.b.OrigID, "OrigID")
    33  	// assert.Equal(t, a.Tier, b.Tier, "Tier")
    34  	// assert.Equal(t, a.IsBucket, a.Isb.IsBucket, "IsBucket")
    35  }
    36  
    37  func TestListJSON(t *testing.T) {
    38  	ctx := context.Background()
    39  	r := fstest.NewRun(t)
    40  	file1 := r.WriteBoth(ctx, "file1", "file1", t1)
    41  	file2 := r.WriteBoth(ctx, "sub/file2", "sub/file2", t2)
    42  
    43  	r.CheckRemoteItems(t, file1, file2)
    44  	precision := fs.GetModifyWindow(ctx, r.Fremote)
    45  
    46  	for _, test := range []struct {
    47  		name   string
    48  		remote string
    49  		opt    operations.ListJSONOpt
    50  		want   []*operations.ListJSONItem
    51  	}{
    52  		{
    53  			name: "Default",
    54  			opt:  operations.ListJSONOpt{},
    55  			want: []*operations.ListJSONItem{{
    56  				Path:    "file1",
    57  				Name:    "file1",
    58  				Size:    5,
    59  				ModTime: operations.Timestamp{When: t1},
    60  				IsDir:   false,
    61  			}, {
    62  				Path:  "sub",
    63  				Name:  "sub",
    64  				IsDir: true,
    65  			}},
    66  		}, {
    67  			name: "FilesOnly",
    68  			opt: operations.ListJSONOpt{
    69  				FilesOnly: true,
    70  			},
    71  			want: []*operations.ListJSONItem{{
    72  				Path:    "file1",
    73  				Name:    "file1",
    74  				Size:    5,
    75  				ModTime: operations.Timestamp{When: t1},
    76  				IsDir:   false,
    77  			}},
    78  		}, {
    79  			name: "DirsOnly",
    80  			opt: operations.ListJSONOpt{
    81  				DirsOnly: true,
    82  			},
    83  			want: []*operations.ListJSONItem{{
    84  				Path:  "sub",
    85  				Name:  "sub",
    86  				IsDir: true,
    87  			}},
    88  		}, {
    89  			name: "Recurse",
    90  			opt: operations.ListJSONOpt{
    91  				Recurse: true,
    92  			},
    93  			want: []*operations.ListJSONItem{{
    94  				Path:    "file1",
    95  				Name:    "file1",
    96  				Size:    5,
    97  				ModTime: operations.Timestamp{When: t1},
    98  				IsDir:   false,
    99  			}, {
   100  				Path:  "sub",
   101  				Name:  "sub",
   102  				IsDir: true,
   103  			}, {
   104  				Path:    "sub/file2",
   105  				Name:    "file2",
   106  				Size:    9,
   107  				ModTime: operations.Timestamp{When: t2},
   108  				IsDir:   false,
   109  			}},
   110  		}, {
   111  			name:   "SubDir",
   112  			remote: "sub",
   113  			opt:    operations.ListJSONOpt{},
   114  			want: []*operations.ListJSONItem{{
   115  				Path:    "sub/file2",
   116  				Name:    "file2",
   117  				Size:    9,
   118  				ModTime: operations.Timestamp{When: t2},
   119  				IsDir:   false,
   120  			}},
   121  		}, {
   122  			name: "NoModTime",
   123  			opt: operations.ListJSONOpt{
   124  				FilesOnly: true,
   125  				NoModTime: true,
   126  			},
   127  			want: []*operations.ListJSONItem{{
   128  				Path:    "file1",
   129  				Name:    "file1",
   130  				Size:    5,
   131  				ModTime: operations.Timestamp{When: time.Time{}},
   132  				IsDir:   false,
   133  			}},
   134  		}, {
   135  			name: "NoMimeType",
   136  			opt: operations.ListJSONOpt{
   137  				FilesOnly:  true,
   138  				NoMimeType: true,
   139  			},
   140  			want: []*operations.ListJSONItem{{
   141  				Path:    "file1",
   142  				Name:    "file1",
   143  				Size:    5,
   144  				ModTime: operations.Timestamp{When: t1},
   145  				IsDir:   false,
   146  			}},
   147  		}, {
   148  			name: "ShowHash",
   149  			opt: operations.ListJSONOpt{
   150  				FilesOnly: true,
   151  				ShowHash:  true,
   152  			},
   153  			want: []*operations.ListJSONItem{{
   154  				Path:    "file1",
   155  				Name:    "file1",
   156  				Size:    5,
   157  				ModTime: operations.Timestamp{When: t1},
   158  				IsDir:   false,
   159  			}},
   160  		}, {
   161  			name: "HashTypes",
   162  			opt: operations.ListJSONOpt{
   163  				FilesOnly: true,
   164  				ShowHash:  true,
   165  				HashTypes: []string{"MD5"},
   166  			},
   167  			want: []*operations.ListJSONItem{{
   168  				Path:    "file1",
   169  				Name:    "file1",
   170  				Size:    5,
   171  				ModTime: operations.Timestamp{When: t1},
   172  				IsDir:   false,
   173  			}},
   174  		}, {
   175  			name: "Metadata",
   176  			opt: operations.ListJSONOpt{
   177  				FilesOnly: false,
   178  				Metadata:  true,
   179  			},
   180  			want: []*operations.ListJSONItem{{
   181  				Path:    "file1",
   182  				Name:    "file1",
   183  				Size:    5,
   184  				ModTime: operations.Timestamp{When: t1},
   185  				IsDir:   false,
   186  			}, {
   187  				Path:  "sub",
   188  				Name:  "sub",
   189  				IsDir: true,
   190  			}},
   191  		},
   192  	} {
   193  		t.Run(test.name, func(t *testing.T) {
   194  			var got []*operations.ListJSONItem
   195  			require.NoError(t, operations.ListJSON(ctx, r.Fremote, test.remote, &test.opt, func(item *operations.ListJSONItem) error {
   196  				got = append(got, item)
   197  				return nil
   198  			}))
   199  			sort.Slice(got, func(i, j int) bool {
   200  				return got[i].Path < got[j].Path
   201  			})
   202  			require.Equal(t, len(test.want), len(got), "Wrong number of results")
   203  			for i := range test.want {
   204  				compareListJSONItem(t, test.want[i], got[i], precision)
   205  				if test.opt.NoMimeType {
   206  					assert.Equal(t, "", got[i].MimeType)
   207  				} else {
   208  					assert.NotEqual(t, "", got[i].MimeType)
   209  				}
   210  				if test.opt.Metadata {
   211  					features := r.Fremote.Features()
   212  					if features.ReadMetadata && !got[i].IsDir {
   213  						assert.Greater(t, len(got[i].Metadata), 0, "Expecting metadata for file")
   214  					}
   215  					if features.ReadDirMetadata && got[i].IsDir {
   216  						assert.Greater(t, len(got[i].Metadata), 0, "Expecting metadata for dir")
   217  					}
   218  				}
   219  				if test.opt.ShowHash {
   220  					hashes := got[i].Hashes
   221  					assert.NotNil(t, hashes)
   222  					if len(test.opt.HashTypes) > 0 && len(hashes) > 0 {
   223  						assert.Equal(t, 1, len(hashes))
   224  					}
   225  					if hashes["crc32"] != "" {
   226  						assert.Equal(t, "9ee760e5", hashes["crc32"])
   227  					}
   228  					if hashes["dropbox"] != "" {
   229  						assert.Equal(t, "f4d62afeaee6f35d3efdd8c66623360395165473bcc958f835343eb3f542f983", hashes["dropbox"])
   230  					}
   231  					if hashes["mailru"] != "" {
   232  						assert.Equal(t, "66696c6531000000000000000000000000000000", hashes["mailru"])
   233  					}
   234  					if hashes["md5"] != "" {
   235  						assert.Equal(t, "826e8142e6baabe8af779f5f490cf5f5", hashes["md5"])
   236  					}
   237  					if hashes["quickxor"] != "" {
   238  						assert.Equal(t, "6648031bca100300000000000500000000000000", hashes["quickxor"])
   239  					}
   240  					if hashes["sha1"] != "" {
   241  						assert.Equal(t, "60b27f004e454aca81b0480209cce5081ec52390", hashes["sha1"])
   242  					}
   243  					if hashes["sha256"] != "" {
   244  						assert.Equal(t, "c147efcfc2d7ea666a9e4f5187b115c90903f0fc896a56df9a6ef5d8f3fc9f31", hashes["sha256"])
   245  					}
   246  					if hashes["whirlpool"] != "" {
   247  						assert.Equal(t, "02fa11755b6470bfc5aab6d94cde5cf2939474fb5b0ebbf8ddf3d32bf06aa438eb92eac097047c02017dc1c317ee83fa8a2717ca4d544b4ee75b3231d1c466b0", hashes["whirlpool"])
   248  					}
   249  				} else {
   250  					assert.Nil(t, got[i].Hashes)
   251  				}
   252  			}
   253  		})
   254  	}
   255  }
   256  
   257  func TestStatJSON(t *testing.T) {
   258  	ctx := context.Background()
   259  	r := fstest.NewRun(t)
   260  	file1 := r.WriteBoth(ctx, "file1", "file1", t1)
   261  	file2 := r.WriteBoth(ctx, "sub/file2", "sub/file2", t2)
   262  
   263  	r.CheckRemoteItems(t, file1, file2)
   264  	precision := fs.GetModifyWindow(ctx, r.Fremote)
   265  
   266  	for _, test := range []struct {
   267  		name   string
   268  		remote string
   269  		opt    operations.ListJSONOpt
   270  		want   *operations.ListJSONItem
   271  	}{
   272  		{
   273  			name:   "Root",
   274  			remote: "",
   275  			opt:    operations.ListJSONOpt{},
   276  			want: &operations.ListJSONItem{
   277  				Path:  "",
   278  				Name:  "",
   279  				IsDir: true,
   280  			},
   281  		}, {
   282  			name:   "RootFilesOnly",
   283  			remote: "",
   284  			opt: operations.ListJSONOpt{
   285  				FilesOnly: true,
   286  			},
   287  			want: nil,
   288  		}, {
   289  			name:   "RootDirsOnly",
   290  			remote: "",
   291  			opt: operations.ListJSONOpt{
   292  				DirsOnly: true,
   293  			},
   294  			want: &operations.ListJSONItem{
   295  				Path:  "",
   296  				Name:  "",
   297  				IsDir: true,
   298  			},
   299  		}, {
   300  			name:   "Dir",
   301  			remote: "sub",
   302  			opt:    operations.ListJSONOpt{},
   303  			want: &operations.ListJSONItem{
   304  				Path:  "sub",
   305  				Name:  "sub",
   306  				IsDir: true,
   307  			},
   308  		}, {
   309  			name:   "DirWithTrailingSlash",
   310  			remote: "sub/",
   311  			opt:    operations.ListJSONOpt{},
   312  			want: &operations.ListJSONItem{
   313  				Path:  "sub",
   314  				Name:  "sub",
   315  				IsDir: true,
   316  			},
   317  		}, {
   318  			name:   "File",
   319  			remote: "file1",
   320  			opt:    operations.ListJSONOpt{},
   321  			want: &operations.ListJSONItem{
   322  				Path:    "file1",
   323  				Name:    "file1",
   324  				Size:    5,
   325  				ModTime: operations.Timestamp{When: t1},
   326  				IsDir:   false,
   327  			},
   328  		}, {
   329  			name:   "NotFound",
   330  			remote: "notfound",
   331  			opt:    operations.ListJSONOpt{},
   332  			want:   nil,
   333  		}, {
   334  			name:   "DirFilesOnly",
   335  			remote: "sub",
   336  			opt: operations.ListJSONOpt{
   337  				FilesOnly: true,
   338  			},
   339  			want: nil,
   340  		}, {
   341  			name:   "FileFilesOnly",
   342  			remote: "file1",
   343  			opt: operations.ListJSONOpt{
   344  				FilesOnly: true,
   345  			},
   346  			want: &operations.ListJSONItem{
   347  				Path:    "file1",
   348  				Name:    "file1",
   349  				Size:    5,
   350  				ModTime: operations.Timestamp{When: t1},
   351  				IsDir:   false,
   352  			},
   353  		}, {
   354  			name:   "NotFoundFilesOnly",
   355  			remote: "notfound",
   356  			opt: operations.ListJSONOpt{
   357  				FilesOnly: true,
   358  			},
   359  			want: nil,
   360  		}, {
   361  			name:   "DirDirsOnly",
   362  			remote: "sub",
   363  			opt: operations.ListJSONOpt{
   364  				DirsOnly: true,
   365  			},
   366  			want: &operations.ListJSONItem{
   367  				Path:  "sub",
   368  				Name:  "sub",
   369  				IsDir: true,
   370  			},
   371  		}, {
   372  			name:   "FileDirsOnly",
   373  			remote: "file1",
   374  			opt: operations.ListJSONOpt{
   375  				DirsOnly: true,
   376  			},
   377  			want: nil,
   378  		}, {
   379  			name:   "NotFoundDirsOnly",
   380  			remote: "notfound",
   381  			opt: operations.ListJSONOpt{
   382  				DirsOnly: true,
   383  			},
   384  			want: nil,
   385  		},
   386  	} {
   387  		t.Run(test.name, func(t *testing.T) {
   388  			got, err := operations.StatJSON(ctx, r.Fremote, test.remote, &test.opt)
   389  			require.NoError(t, err)
   390  			if test.want == nil {
   391  				assert.Nil(t, got)
   392  				return
   393  			}
   394  			require.NotNil(t, got)
   395  			compareListJSONItem(t, test.want, got, precision)
   396  		})
   397  	}
   398  
   399  	t.Run("RootNotFound", func(t *testing.T) {
   400  		f, err := fs.NewFs(ctx, r.FremoteName+"/notfound")
   401  		require.NoError(t, err)
   402  		_, err = operations.StatJSON(ctx, f, "", &operations.ListJSONOpt{})
   403  		// This should return an error except for bucket based remotes
   404  		assert.True(t, err != nil || f.Features().BucketBased, "Need an error for non bucket based backends")
   405  	})
   406  }