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  }