github.com/rclone/rclone@v1.66.1-0.20240517100346-7b89735ae726/backend/googlephotos/pattern_test.go (about) 1 package googlephotos 2 3 import ( 4 "context" 5 "fmt" 6 "testing" 7 "time" 8 9 "github.com/rclone/rclone/backend/googlephotos/api" 10 "github.com/rclone/rclone/fs" 11 "github.com/rclone/rclone/fs/dirtree" 12 "github.com/rclone/rclone/fstest" 13 "github.com/rclone/rclone/fstest/mockobject" 14 "github.com/stretchr/testify/assert" 15 "github.com/stretchr/testify/require" 16 ) 17 18 // time for directories 19 var startTime = fstest.Time("2019-06-24T15:53:05.999999999Z") 20 21 // mock Fs for testing patterns 22 type testLister struct { 23 t *testing.T 24 albums *albums 25 names []string 26 uploaded dirtree.DirTree 27 } 28 29 // newTestLister makes a mock for testing 30 func newTestLister(t *testing.T) *testLister { 31 return &testLister{ 32 t: t, 33 albums: newAlbums(), 34 uploaded: dirtree.New(), 35 } 36 } 37 38 // mock listDir for testing 39 func (f *testLister) listDir(ctx context.Context, prefix string, filter api.SearchFilter) (entries fs.DirEntries, err error) { 40 for _, name := range f.names { 41 entries = append(entries, mockobject.New(prefix+name)) 42 } 43 return entries, nil 44 } 45 46 // mock listAlbums for testing 47 func (f *testLister) listAlbums(ctx context.Context, shared bool) (all *albums, err error) { 48 return f.albums, nil 49 } 50 51 // mock listUploads for testing 52 func (f *testLister) listUploads(ctx context.Context, dir string) (entries fs.DirEntries, err error) { 53 entries = f.uploaded[dir] 54 return entries, nil 55 } 56 57 // mock dirTime for testing 58 func (f *testLister) dirTime() time.Time { 59 return startTime 60 } 61 62 // mock startYear for testing 63 func (f *testLister) startYear() int { 64 return 2000 65 } 66 67 // mock includeArchived for testing 68 func (f *testLister) includeArchived() bool { 69 return false 70 } 71 72 func TestPatternMatch(t *testing.T) { 73 for testNumber, test := range []struct { 74 // input 75 root string 76 itemPath string 77 isFile bool 78 // expected output 79 wantMatch []string 80 wantPrefix string 81 wantPattern *dirPattern 82 }{ 83 { 84 root: "", 85 itemPath: "", 86 isFile: false, 87 wantMatch: []string{""}, 88 wantPrefix: "", 89 wantPattern: &patterns[0], 90 }, 91 { 92 root: "", 93 itemPath: "", 94 isFile: true, 95 wantMatch: nil, 96 wantPrefix: "", 97 wantPattern: nil, 98 }, 99 { 100 root: "upload", 101 itemPath: "", 102 isFile: false, 103 wantMatch: []string{"upload", ""}, 104 wantPrefix: "", 105 wantPattern: &patterns[1], 106 }, 107 { 108 root: "upload/dir", 109 itemPath: "", 110 isFile: false, 111 wantMatch: []string{"upload/dir", "dir"}, 112 wantPrefix: "", 113 wantPattern: &patterns[1], 114 }, 115 { 116 root: "upload/file.jpg", 117 itemPath: "", 118 isFile: true, 119 wantMatch: []string{"upload/file.jpg", "file.jpg"}, 120 wantPrefix: "", 121 wantPattern: &patterns[2], 122 }, 123 { 124 root: "media", 125 itemPath: "", 126 isFile: false, 127 wantMatch: []string{"media"}, 128 wantPrefix: "", 129 wantPattern: &patterns[3], 130 }, 131 { 132 root: "", 133 itemPath: "media", 134 isFile: false, 135 wantMatch: []string{"media"}, 136 wantPrefix: "media/", 137 wantPattern: &patterns[3], 138 }, 139 { 140 root: "media/all", 141 itemPath: "", 142 isFile: false, 143 wantMatch: []string{"media/all"}, 144 wantPrefix: "", 145 wantPattern: &patterns[4], 146 }, 147 { 148 root: "media", 149 itemPath: "all", 150 isFile: false, 151 wantMatch: []string{"media/all"}, 152 wantPrefix: "all/", 153 wantPattern: &patterns[4], 154 }, 155 { 156 root: "media/all", 157 itemPath: "file.jpg", 158 isFile: true, 159 wantMatch: []string{"media/all/file.jpg", "file.jpg"}, 160 wantPrefix: "file.jpg/", 161 wantPattern: &patterns[5], 162 }, 163 { 164 root: "", 165 itemPath: "feature", 166 isFile: false, 167 wantMatch: []string{"feature"}, 168 wantPrefix: "feature/", 169 wantPattern: &patterns[23], 170 }, 171 { 172 root: "feature/favorites", 173 itemPath: "", 174 isFile: false, 175 wantMatch: []string{"feature/favorites"}, 176 wantPrefix: "", 177 wantPattern: &patterns[24], 178 }, 179 { 180 root: "feature", 181 itemPath: "favorites", 182 isFile: false, 183 wantMatch: []string{"feature/favorites"}, 184 wantPrefix: "favorites/", 185 wantPattern: &patterns[24], 186 }, 187 { 188 root: "feature/favorites", 189 itemPath: "file.jpg", 190 isFile: true, 191 wantMatch: []string{"feature/favorites/file.jpg", "file.jpg"}, 192 wantPrefix: "file.jpg/", 193 wantPattern: &patterns[25], 194 }, 195 } { 196 t.Run(fmt.Sprintf("#%d,root=%q,itemPath=%q,isFile=%v", testNumber, test.root, test.itemPath, test.isFile), func(t *testing.T) { 197 gotMatch, gotPrefix, gotPattern := patterns.match(test.root, test.itemPath, test.isFile) 198 assert.Equal(t, test.wantMatch, gotMatch) 199 assert.Equal(t, test.wantPrefix, gotPrefix) 200 assert.Equal(t, test.wantPattern, gotPattern) 201 }) 202 } 203 } 204 205 func TestPatternMatchToEntries(t *testing.T) { 206 ctx := context.Background() 207 f := newTestLister(t) 208 f.names = []string{"file.jpg"} 209 f.albums.add(&api.Album{ 210 ID: "1", 211 Title: "sub/one", 212 }) 213 f.albums.add(&api.Album{ 214 ID: "2", 215 Title: "sub", 216 }) 217 f.uploaded.AddEntry(mockobject.New("upload/file1.jpg")) 218 f.uploaded.AddEntry(mockobject.New("upload/dir/file2.jpg")) 219 220 for testNumber, test := range []struct { 221 // input 222 root string 223 itemPath string 224 // expected output 225 wantMatch []string 226 wantPrefix string 227 remotes []string 228 }{ 229 { 230 root: "", 231 itemPath: "", 232 wantMatch: []string{""}, 233 wantPrefix: "", 234 remotes: []string{"media/", "album/", "shared-album/", "upload/"}, 235 }, 236 { 237 root: "upload", 238 itemPath: "", 239 wantMatch: []string{"upload", ""}, 240 wantPrefix: "", 241 remotes: []string{"upload/file1.jpg", "upload/dir/"}, 242 }, 243 { 244 root: "upload", 245 itemPath: "dir", 246 wantMatch: []string{"upload/dir", "dir"}, 247 wantPrefix: "dir/", 248 remotes: []string{"upload/dir/file2.jpg"}, 249 }, 250 { 251 root: "media", 252 itemPath: "", 253 wantMatch: []string{"media"}, 254 wantPrefix: "", 255 remotes: []string{"all/", "by-year/", "by-month/", "by-day/"}, 256 }, 257 { 258 root: "media/all", 259 itemPath: "", 260 wantMatch: []string{"media/all"}, 261 wantPrefix: "", 262 remotes: []string{"file.jpg"}, 263 }, 264 { 265 root: "media", 266 itemPath: "all", 267 wantMatch: []string{"media/all"}, 268 wantPrefix: "all/", 269 remotes: []string{"all/file.jpg"}, 270 }, 271 { 272 root: "media/by-year", 273 itemPath: "", 274 wantMatch: []string{"media/by-year"}, 275 wantPrefix: "", 276 remotes: []string{"2000/", "2001/", "2002/", "2003/"}, 277 }, 278 { 279 root: "media/by-year/2000", 280 itemPath: "", 281 wantMatch: []string{"media/by-year/2000", "2000"}, 282 wantPrefix: "", 283 remotes: []string{"file.jpg"}, 284 }, 285 { 286 root: "media/by-month", 287 itemPath: "", 288 wantMatch: []string{"media/by-month"}, 289 wantPrefix: "", 290 remotes: []string{"2000/", "2001/", "2002/", "2003/"}, 291 }, 292 { 293 root: "media/by-month/2001", 294 itemPath: "", 295 wantMatch: []string{"media/by-month/2001", "2001"}, 296 wantPrefix: "", 297 remotes: []string{"2001-01/", "2001-02/", "2001-03/", "2001-04/"}, 298 }, 299 { 300 root: "media/by-month/2001/2001-01", 301 itemPath: "", 302 wantMatch: []string{"media/by-month/2001/2001-01", "2001", "01"}, 303 wantPrefix: "", 304 remotes: []string{"file.jpg"}, 305 }, 306 { 307 root: "media/by-day", 308 itemPath: "", 309 wantMatch: []string{"media/by-day"}, 310 wantPrefix: "", 311 remotes: []string{"2000/", "2001/", "2002/", "2003/"}, 312 }, 313 { 314 root: "media/by-day/2001", 315 itemPath: "", 316 wantMatch: []string{"media/by-day/2001", "2001"}, 317 wantPrefix: "", 318 remotes: []string{"2001-01-01/", "2001-01-02/", "2001-01-03/", "2001-01-04/"}, 319 }, 320 { 321 root: "media/by-day/2001/2001-01-02", 322 itemPath: "", 323 wantMatch: []string{"media/by-day/2001/2001-01-02", "2001", "01", "02"}, 324 wantPrefix: "", 325 remotes: []string{"file.jpg"}, 326 }, 327 { 328 root: "album", 329 itemPath: "", 330 wantMatch: []string{"album"}, 331 wantPrefix: "", 332 remotes: []string{"sub/"}, 333 }, 334 { 335 root: "album/sub", 336 itemPath: "", 337 wantMatch: []string{"album/sub", "sub"}, 338 wantPrefix: "", 339 remotes: []string{"one/", "file.jpg"}, 340 }, 341 { 342 root: "album/sub/one", 343 itemPath: "", 344 wantMatch: []string{"album/sub/one", "sub/one"}, 345 wantPrefix: "", 346 remotes: []string{"file.jpg"}, 347 }, 348 { 349 root: "shared-album", 350 itemPath: "", 351 wantMatch: []string{"shared-album"}, 352 wantPrefix: "", 353 remotes: []string{"sub/"}, 354 }, 355 { 356 root: "shared-album/sub", 357 itemPath: "", 358 wantMatch: []string{"shared-album/sub", "sub"}, 359 wantPrefix: "", 360 remotes: []string{"one/", "file.jpg"}, 361 }, 362 { 363 root: "shared-album/sub/one", 364 itemPath: "", 365 wantMatch: []string{"shared-album/sub/one", "sub/one"}, 366 wantPrefix: "", 367 remotes: []string{"file.jpg"}, 368 }, 369 } { 370 t.Run(fmt.Sprintf("#%d,root=%q,itemPath=%q", testNumber, test.root, test.itemPath), func(t *testing.T) { 371 match, prefix, pattern := patterns.match(test.root, test.itemPath, false) 372 assert.Equal(t, test.wantMatch, match) 373 assert.Equal(t, test.wantPrefix, prefix) 374 assert.NotNil(t, pattern) 375 assert.NotNil(t, pattern.toEntries) 376 377 entries, err := pattern.toEntries(ctx, f, prefix, match) 378 assert.NoError(t, err) 379 var remotes = []string{} 380 for _, entry := range entries { 381 remote := entry.Remote() 382 if _, isDir := entry.(fs.Directory); isDir { 383 remote += "/" 384 } 385 remotes = append(remotes, remote) 386 if len(remotes) >= 4 { 387 break // only test first 4 entries 388 } 389 } 390 assert.Equal(t, test.remotes, remotes) 391 }) 392 } 393 } 394 395 func TestPatternYears(t *testing.T) { 396 f := newTestLister(t) 397 entries, err := years(context.Background(), f, "potato/", nil) 398 require.NoError(t, err) 399 400 year := 2000 401 for _, entry := range entries { 402 assert.Equal(t, "potato/"+fmt.Sprint(year), entry.Remote()) 403 year++ 404 } 405 } 406 407 func TestPatternMonths(t *testing.T) { 408 f := newTestLister(t) 409 entries, err := months(context.Background(), f, "potato/", []string{"", "2020"}) 410 require.NoError(t, err) 411 412 assert.Equal(t, 12, len(entries)) 413 for i, entry := range entries { 414 assert.Equal(t, fmt.Sprintf("potato/2020-%02d", i+1), entry.Remote()) 415 } 416 } 417 418 func TestPatternDays(t *testing.T) { 419 f := newTestLister(t) 420 entries, err := days(context.Background(), f, "potato/", []string{"", "2020"}) 421 require.NoError(t, err) 422 423 assert.Equal(t, 366, len(entries)) 424 assert.Equal(t, "potato/2020-01-01", entries[0].Remote()) 425 assert.Equal(t, "potato/2020-12-31", entries[len(entries)-1].Remote()) 426 } 427 428 func TestPatternYearMonthDayFilter(t *testing.T) { 429 ctx := context.Background() 430 f := newTestLister(t) 431 432 // Years 433 sf, err := yearMonthDayFilter(ctx, f, []string{"", "2000"}) 434 require.NoError(t, err) 435 assert.Equal(t, api.SearchFilter{ 436 Filters: &api.Filters{ 437 DateFilter: &api.DateFilter{ 438 Dates: []api.Date{ 439 { 440 Year: 2000, 441 }, 442 }, 443 }, 444 }, 445 }, sf) 446 447 _, err = yearMonthDayFilter(ctx, f, []string{"", "potato"}) 448 require.Error(t, err) 449 _, err = yearMonthDayFilter(ctx, f, []string{"", "999"}) 450 require.Error(t, err) 451 _, err = yearMonthDayFilter(ctx, f, []string{"", "4000"}) 452 require.Error(t, err) 453 454 // Months 455 sf, err = yearMonthDayFilter(ctx, f, []string{"", "2000", "01"}) 456 require.NoError(t, err) 457 assert.Equal(t, api.SearchFilter{ 458 Filters: &api.Filters{ 459 DateFilter: &api.DateFilter{ 460 Dates: []api.Date{ 461 { 462 Month: 1, 463 Year: 2000, 464 }, 465 }, 466 }, 467 }, 468 }, sf) 469 470 _, err = yearMonthDayFilter(ctx, f, []string{"", "2000", "potato"}) 471 require.Error(t, err) 472 _, err = yearMonthDayFilter(ctx, f, []string{"", "2000", "0"}) 473 require.Error(t, err) 474 _, err = yearMonthDayFilter(ctx, f, []string{"", "2000", "13"}) 475 require.Error(t, err) 476 477 // Days 478 sf, err = yearMonthDayFilter(ctx, f, []string{"", "2000", "01", "02"}) 479 require.NoError(t, err) 480 assert.Equal(t, api.SearchFilter{ 481 Filters: &api.Filters{ 482 DateFilter: &api.DateFilter{ 483 Dates: []api.Date{ 484 { 485 Day: 2, 486 Month: 1, 487 Year: 2000, 488 }, 489 }, 490 }, 491 }, 492 }, sf) 493 494 _, err = yearMonthDayFilter(ctx, f, []string{"", "2000", "01", "potato"}) 495 require.Error(t, err) 496 _, err = yearMonthDayFilter(ctx, f, []string{"", "2000", "01", "0"}) 497 require.Error(t, err) 498 _, err = yearMonthDayFilter(ctx, f, []string{"", "2000", "01", "32"}) 499 require.Error(t, err) 500 } 501 502 func TestPatternAlbumsToEntries(t *testing.T) { 503 f := newTestLister(t) 504 ctx := context.Background() 505 506 _, err := albumsToEntries(ctx, f, false, "potato/", "sub") 507 assert.Equal(t, fs.ErrorDirNotFound, err) 508 509 f.albums.add(&api.Album{ 510 ID: "1", 511 Title: "sub/one", 512 }) 513 514 entries, err := albumsToEntries(ctx, f, false, "potato/", "sub") 515 assert.NoError(t, err) 516 assert.Equal(t, 1, len(entries)) 517 assert.Equal(t, "potato/one", entries[0].Remote()) 518 _, ok := entries[0].(fs.Directory) 519 assert.Equal(t, true, ok) 520 521 f.albums.add(&api.Album{ 522 ID: "1", 523 Title: "sub", 524 }) 525 f.names = []string{"file.jpg"} 526 527 entries, err = albumsToEntries(ctx, f, false, "potato/", "sub") 528 assert.NoError(t, err) 529 assert.Equal(t, 2, len(entries)) 530 assert.Equal(t, "potato/one", entries[0].Remote()) 531 _, ok = entries[0].(fs.Directory) 532 assert.Equal(t, true, ok) 533 assert.Equal(t, "potato/file.jpg", entries[1].Remote()) 534 _, ok = entries[1].(fs.Object) 535 assert.Equal(t, true, ok) 536 537 }