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 }