github.com/cozy/cozy-stack@v0.0.0-20240603063001-31110fa4cae1/model/vfs/vfs_test.go (about)

     1  package vfs_test
     2  
     3  import (
     4  	"archive/zip"
     5  	"bytes"
     6  	"context"
     7  	"errors"
     8  	"fmt"
     9  	"io"
    10  	"net/http/httptest"
    11  	"net/url"
    12  	"os"
    13  	"path"
    14  	"strings"
    15  	"testing"
    16  	"time"
    17  
    18  	"github.com/cozy/cozy-stack/model/vfs"
    19  	"github.com/cozy/cozy-stack/model/vfs/vfsafero"
    20  	"github.com/cozy/cozy-stack/model/vfs/vfsswift"
    21  	"github.com/cozy/cozy-stack/pkg/config/config"
    22  	"github.com/cozy/cozy-stack/pkg/consts"
    23  	"github.com/cozy/cozy-stack/pkg/couchdb"
    24  	"github.com/cozy/cozy-stack/pkg/crypto"
    25  	"github.com/cozy/cozy-stack/pkg/lock"
    26  	"github.com/cozy/cozy-stack/tests/testutils"
    27  	"github.com/ncw/swift/v2/swifttest"
    28  	"github.com/stretchr/testify/assert"
    29  	"github.com/stretchr/testify/require"
    30  	"golang.org/x/sync/errgroup"
    31  )
    32  
    33  var mutex lock.ErrorRWLocker
    34  var diskQuota int64
    35  
    36  type diskImpl struct{}
    37  
    38  type H map[string]H
    39  
    40  type contexter struct {
    41  	cluster int
    42  	domain  string
    43  	prefix  string
    44  	context string
    45  }
    46  
    47  func TestVfs(t *testing.T) {
    48  	if testing.Short() {
    49  		t.Skip("an instance is required for this test: test skipped due to the use of --short flag")
    50  	}
    51  
    52  	config.UseTestFile(t)
    53  	testutils.NeedCouchdb(t)
    54  
    55  	aferoFS := makeAferoFS(t)
    56  	swiftFS := makeSwiftFS(t)
    57  
    58  	var tests = []struct {
    59  		name string
    60  		fs   vfs.VFS
    61  	}{
    62  		{"afero", aferoFS},
    63  		{"swift", swiftFS},
    64  	}
    65  
    66  	for _, tt := range tests {
    67  		fs := tt.fs
    68  
    69  		t.Run(tt.name, func(t *testing.T) {
    70  			t.Run("DiskUsageIsInitiallyZero", func(t *testing.T) {
    71  				used, err := fs.DiskUsage()
    72  				assert.NoError(t, err)
    73  				assert.Equal(t, int64(0), used)
    74  			})
    75  
    76  			t.Run("GetFileDocFromPathAtRoot", func(t *testing.T) {
    77  				doc, err := vfs.NewFileDoc("toto", "", -1, nil, "foo/bar", "foo", time.Now(), false, false, false, []string{})
    78  				assert.NoError(t, err)
    79  
    80  				body := bytes.NewReader([]byte("hello !"))
    81  
    82  				file, err := fs.CreateFile(doc, nil)
    83  				assert.NoError(t, err)
    84  
    85  				n, err := io.Copy(file, body)
    86  				assert.NoError(t, err)
    87  				assert.Equal(t, len("hello !"), int(n))
    88  
    89  				err = file.Close()
    90  				assert.NoError(t, err)
    91  
    92  				_, err = fs.FileByPath("/toto")
    93  				assert.NoError(t, err)
    94  
    95  				_, err = fs.FileByPath("/noooo")
    96  				assert.Error(t, err)
    97  			})
    98  
    99  			t.Run("Remove", func(t *testing.T) {
   100  				err := vfs.Remove(fs, "foo/bar", fs.EnsureErased)
   101  				assert.Error(t, err)
   102  				assert.Equal(t, vfs.ErrNonAbsolutePath, err)
   103  
   104  				err = vfs.Remove(fs, "/foo", fs.EnsureErased)
   105  				assert.Error(t, err)
   106  				assert.Equal(t, "file does not exist", err.Error())
   107  
   108  				_, err = vfs.Mkdir(fs, "/removeme", nil)
   109  				if !assert.NoError(t, err) {
   110  					err = vfs.Remove(fs, "/removeme", fs.EnsureErased)
   111  					assert.NoError(t, err)
   112  				}
   113  			})
   114  
   115  			t.Run("RemoveAll", func(t *testing.T) {
   116  				origtree := H{
   117  					"removemeall/": H{
   118  						"dirchild1/": H{
   119  							"food/": H{},
   120  							"bard/": H{},
   121  						},
   122  						"dirchild2/": H{
   123  							"foof": nil,
   124  							"barf": nil,
   125  						},
   126  						"dirchild3/": H{},
   127  						"filechild1": nil,
   128  					},
   129  				}
   130  				_ = createTree(t, fs, origtree, consts.RootDirID)
   131  
   132  				err := vfs.RemoveAll(fs, "/removemeall", fs.EnsureErased)
   133  				require.NoError(t, err)
   134  
   135  				_, err = fs.DirByPath("/removemeall/dirchild1")
   136  				assert.Error(t, err)
   137  				_, err = fs.DirByPath("/removemeall")
   138  				assert.Error(t, err)
   139  			})
   140  
   141  			t.Run("DiskUsage", func(t *testing.T) {
   142  				used, err := fs.DiskUsage()
   143  				assert.NoError(t, err)
   144  				assert.Equal(t, len("hello !"), int(used))
   145  			})
   146  
   147  			t.Run("GetFileDocFromPath", func(t *testing.T) {
   148  				dir, _ := vfs.NewDirDoc(fs, "container", "", nil)
   149  				err := fs.CreateDir(dir)
   150  				assert.NoError(t, err)
   151  
   152  				doc, err := vfs.NewFileDoc("toto", dir.ID(), -1, nil, "foo/bar", "foo", time.Now(), false, false, false, []string{})
   153  				assert.NoError(t, err)
   154  
   155  				body := bytes.NewReader([]byte("hello !"))
   156  
   157  				file, err := fs.CreateFile(doc, nil)
   158  				assert.NoError(t, err)
   159  
   160  				n, err := io.Copy(file, body)
   161  				assert.NoError(t, err)
   162  				assert.Equal(t, len("hello !"), int(n))
   163  
   164  				err = file.Close()
   165  				assert.NoError(t, err)
   166  
   167  				_, err = fs.FileByPath("/container/toto")
   168  				assert.NoError(t, err)
   169  
   170  				_, err = fs.FileByPath("/container/noooo")
   171  				assert.Error(t, err)
   172  			})
   173  
   174  			t.Run("CreateGetAndModifyFile", func(t *testing.T) {
   175  				origtree := H{
   176  					"createandget1/": H{
   177  						"dirchild1/": H{
   178  							"food/": H{},
   179  							"bard/": H{},
   180  						},
   181  						"dirchild2/": H{
   182  							"foof": nil,
   183  							"barf": nil,
   184  						},
   185  						"dirchild3/": H{},
   186  						"filechild1": nil,
   187  					},
   188  				}
   189  
   190  				olddoc := createTree(t, fs, origtree, consts.RootDirID)
   191  
   192  				newname := "createandget2"
   193  				_, err := vfs.ModifyDirMetadata(fs, olddoc, &vfs.DocPatch{
   194  					Name: &newname,
   195  				})
   196  				require.NoError(t, err)
   197  
   198  				tree, err := fetchTree(fs, "/createandget2")
   199  				require.NoError(t, err)
   200  
   201  				assert.EqualValues(t, origtree["createandget1/"], tree["createandget2/"], "should have same tree")
   202  
   203  				fileBefore, err := fs.FileByPath("/createandget2/dirchild2/foof")
   204  				require.NoError(t, err)
   205  
   206  				newfilename := "foof.jpg"
   207  				_, err = vfs.ModifyFileMetadata(fs, fileBefore, &vfs.DocPatch{
   208  					Name: &newfilename,
   209  				})
   210  				require.NoError(t, err)
   211  
   212  				fileAfter, err := fs.FileByPath("/createandget2/dirchild2/foof.jpg")
   213  				require.NoError(t, err)
   214  
   215  				assert.Equal(t, "files", fileBefore.Class)
   216  				assert.Equal(t, "application/octet-stream", fileBefore.Mime)
   217  				assert.Equal(t, "image", fileAfter.Class)
   218  				assert.Equal(t, "image/jpeg", fileAfter.Mime)
   219  			})
   220  
   221  			t.Run("UpdateDir", func(t *testing.T) {
   222  				origtree := H{
   223  					"update1/": H{
   224  						"dirchild1/": H{
   225  							"food/": H{},
   226  							"bard/": H{},
   227  						},
   228  						"dirchild2/": H{
   229  							"foof": nil,
   230  							"barf": nil,
   231  						},
   232  						"dirchild3/": H{},
   233  						"filechild1": nil,
   234  					},
   235  				}
   236  
   237  				doc1 := createTree(t, fs, origtree, consts.RootDirID)
   238  
   239  				newname := "update2"
   240  				_, err := vfs.ModifyDirMetadata(fs, doc1, &vfs.DocPatch{
   241  					Name: &newname,
   242  				})
   243  				require.NoError(t, err)
   244  
   245  				tree, err := fetchTree(fs, "/update2")
   246  				require.NoError(t, err)
   247  
   248  				if !assert.EqualValues(t, origtree["update1/"], tree["update2/"], "should have same tree") {
   249  					return
   250  				}
   251  
   252  				dirchild2, err := fs.DirByPath("/update2/dirchild2")
   253  				require.NoError(t, err)
   254  
   255  				dirchild3, err := fs.DirByPath("/update2/dirchild3")
   256  				require.NoError(t, err)
   257  
   258  				newfolid := dirchild2.ID()
   259  				_, err = vfs.ModifyDirMetadata(fs, dirchild3, &vfs.DocPatch{
   260  					DirID: &newfolid,
   261  				})
   262  				require.NoError(t, err)
   263  
   264  				tree, err = fetchTree(fs, "/update2")
   265  				require.NoError(t, err)
   266  
   267  				assert.EqualValues(t, H{
   268  					"update2/": H{
   269  						"dirchild1/": H{
   270  							"bard/": H{},
   271  							"food/": H{},
   272  						},
   273  						"filechild1": nil,
   274  						"dirchild2/": H{
   275  							"barf":       nil,
   276  							"foof":       nil,
   277  							"dirchild3/": H{},
   278  						},
   279  					},
   280  				}, tree)
   281  			})
   282  
   283  			t.Run("EncodingOfDirName", func(t *testing.T) {
   284  				base := "encoding-dir"
   285  				nfc := "chaîne"
   286  				nfd := "chaîne"
   287  
   288  				origtree := H{base + "/": H{
   289  					nfc: H{},
   290  					nfd: H{},
   291  				}}
   292  				_ = createTree(t, fs, origtree, consts.RootDirID)
   293  
   294  				f1, err := fs.FileByPath("/" + base + "/" + nfc)
   295  				require.NoError(t, err)
   296  				assert.Equal(t, nfc, f1.DocName)
   297  
   298  				f2, err := fs.FileByPath("/" + base + "/" + nfd)
   299  				require.NoError(t, err)
   300  				assert.Equal(t, nfd, f2.DocName)
   301  
   302  				assert.NotEqual(t, f1.DocID, f2.DocID)
   303  			})
   304  
   305  			t.Run("ChangeEncodingOfDirName", func(t *testing.T) {
   306  				nfc := "dir-nfc-to-nfd-é"
   307  				nfd := "dir-nfc-to-nfd-é"
   308  
   309  				origtree := H{nfc + "/": H{
   310  					"dirchild1/": H{
   311  						"food/": H{},
   312  						"bard/": H{},
   313  					},
   314  					"dirchild2/": H{},
   315  					"filechild1": nil,
   316  				}}
   317  				doc := createTree(t, fs, origtree, consts.RootDirID)
   318  
   319  				newname := nfd
   320  				doc, err := vfs.ModifyDirMetadata(fs, doc, &vfs.DocPatch{
   321  					Name: &newname,
   322  				})
   323  				require.NoError(t, err)
   324  				d, err := fs.DirByPath("/" + newname)
   325  				require.NoError(t, err)
   326  				assert.Equal(t, newname, d.DocName)
   327  
   328  				newname = nfc
   329  				_, err = vfs.ModifyDirMetadata(fs, doc, &vfs.DocPatch{
   330  					Name: &newname,
   331  				})
   332  				require.NoError(t, err)
   333  				d, err = fs.DirByPath("/" + newname)
   334  				require.NoError(t, err)
   335  				assert.Equal(t, newname, d.DocName)
   336  			})
   337  
   338  			t.Run("Walk", func(t *testing.T) {
   339  				walktree := H{
   340  					"walk/": H{
   341  						"dirchild1/": H{
   342  							"food/": H{},
   343  							"bard/": H{},
   344  						},
   345  						"dirchild2/": H{
   346  							"foof": nil,
   347  							"barf": nil,
   348  						},
   349  						"dirchild3/": H{},
   350  						"filechild1": nil,
   351  					},
   352  				}
   353  
   354  				_ = createTree(t, fs, walktree, consts.RootDirID)
   355  
   356  				walked := H{}
   357  				err := vfs.Walk(fs, "/walk", func(name string, dir *vfs.DirDoc, file *vfs.FileDoc, err error) error {
   358  					if !assert.NoError(t, err) {
   359  						return err
   360  					}
   361  
   362  					if dir != nil && !assert.Equal(t, dir.Fullpath, name) {
   363  						return fmt.Errorf("Bad fullpath")
   364  					}
   365  
   366  					if file != nil && !assert.True(t, strings.HasSuffix(name, file.DocName)) {
   367  						return fmt.Errorf("Bad fullpath")
   368  					}
   369  
   370  					walked[name] = nil
   371  					return nil
   372  				})
   373  				assert.NoError(t, err)
   374  
   375  				expectedWalk := H{
   376  					"/walk":                nil,
   377  					"/walk/dirchild1":      nil,
   378  					"/walk/dirchild1/food": nil,
   379  					"/walk/dirchild1/bard": nil,
   380  					"/walk/dirchild2":      nil,
   381  					"/walk/dirchild2/foof": nil,
   382  					"/walk/dirchild2/barf": nil,
   383  					"/walk/dirchild3":      nil,
   384  					"/walk/filechild1":     nil,
   385  				}
   386  
   387  				assert.Equal(t, expectedWalk, walked)
   388  			})
   389  
   390  			t.Run("WalkAlreadyLocked", func(t *testing.T) {
   391  				walktree := H{
   392  					"walk2/": H{
   393  						"dirchild1/": H{
   394  							"food/": H{},
   395  							"bard/": H{},
   396  						},
   397  						"dirchild2/": H{
   398  							"foof": nil,
   399  							"barf": nil,
   400  						},
   401  						"dirchild3/": H{},
   402  						"filechild1": nil,
   403  					},
   404  				}
   405  
   406  				_ = createTree(t, fs, walktree, consts.RootDirID)
   407  
   408  				done := make(chan bool)
   409  
   410  				go func() {
   411  					dir, err := fs.DirByPath("/walk2")
   412  					assert.NoError(t, err)
   413  
   414  					assert.NoError(t, mutex.Lock())
   415  					defer mutex.Unlock()
   416  
   417  					err = vfs.WalkAlreadyLocked(fs, dir, func(_ string, _ *vfs.DirDoc, _ *vfs.FileDoc, err error) error {
   418  						assert.NoError(t, err)
   419  						return err
   420  					})
   421  					assert.NoError(t, err)
   422  					done <- true
   423  				}()
   424  
   425  				select {
   426  				case <-done:
   427  					return
   428  				case <-time.After(3 * time.Second):
   429  					t.Fatal("deadline: WalkAlreadyLocked is probably trying to acquire the VFS lock")
   430  				}
   431  			})
   432  
   433  			t.Run("ContentDisposition", func(t *testing.T) {
   434  				foo := vfs.ContentDisposition("inline", "foo.jpg")
   435  				assert.Equal(t, `inline; filename="foo.jpg"`, foo)
   436  				space := vfs.ContentDisposition("inline", "foo bar.jpg")
   437  				assert.Equal(t, `inline; filename="foobar.jpg"; filename*=UTF-8''foo%20bar.jpg`, space)
   438  				accents := vfs.ContentDisposition("inline", "héçà")
   439  				assert.Equal(t, `inline; filename="h"; filename*=UTF-8''h%C3%A9%C3%A7%C3%A0`, accents)
   440  				tab := vfs.ContentDisposition("inline", "tab\t")
   441  				assert.Equal(t, `inline; filename="tab"; filename*=UTF-8''tab%09`, tab)
   442  				emoji := vfs.ContentDisposition("inline", "🐧")
   443  				assert.Equal(t, `inline; filename="download"; filename*=UTF-8''%F0%9F%90%A7`, emoji)
   444  			})
   445  
   446  			t.Run("Archive", func(t *testing.T) {
   447  				tree := H{
   448  					"archive/": H{
   449  						"foo.jpg":    nil,
   450  						"foobar.jpg": nil,
   451  						"hello.jpg":  nil,
   452  						"bar/": H{
   453  							"baz/": H{
   454  								"one.png": nil,
   455  								"two.png": nil,
   456  							},
   457  							"z.gif": nil,
   458  						},
   459  						"qux/": H{
   460  							"quux":   nil,
   461  							"courge": nil,
   462  						},
   463  					},
   464  				}
   465  				dirdoc := createTree(t, fs, tree, consts.RootDirID)
   466  
   467  				foobar, err := fs.FileByPath("/archive/foobar.jpg")
   468  				assert.NoError(t, err)
   469  
   470  				a := &vfs.Archive{
   471  					Name: "test",
   472  					IDs: []string{
   473  						foobar.ID(),
   474  					},
   475  					Files: []string{
   476  						"/archive/foo.jpg",
   477  						"/archive/bar",
   478  					},
   479  				}
   480  				w := httptest.NewRecorder()
   481  				err = a.Serve(fs, w)
   482  				assert.NoError(t, err)
   483  
   484  				res := w.Result()
   485  				disposition := res.Header.Get("Content-Disposition")
   486  				assert.Equal(t, `attachment; filename="test.zip"`, disposition)
   487  				assert.Equal(t, "application/zip", res.Header.Get("Content-Type"))
   488  
   489  				b, err := io.ReadAll(res.Body)
   490  				assert.NoError(t, err)
   491  				z, err := zip.NewReader(bytes.NewReader(b), int64(len(b)))
   492  				assert.NoError(t, err)
   493  				assert.Equal(t, 7, len(z.File))
   494  				zipfiles := H{}
   495  				for _, f := range z.File {
   496  					zipfiles[f.Name] = nil
   497  				}
   498  				assert.EqualValues(t, H{
   499  					"test/foobar.jpg":      nil,
   500  					"test/foo.jpg":         nil,
   501  					"test/bar/":            nil,
   502  					"test/bar/baz/":        nil,
   503  					"test/bar/baz/one.png": nil,
   504  					"test/bar/baz/two.png": nil,
   505  					"test/bar/z.gif":       nil,
   506  				}, zipfiles)
   507  				assert.NoError(t, fs.DestroyDirAndContent(dirdoc, fs.EnsureErased))
   508  			})
   509  
   510  			t.Run("CreateFileTooBig", func(t *testing.T) {
   511  				diskQuota = 1 << (1 * 10) // 1KB
   512  				defer func() { diskQuota = 0 }()
   513  
   514  				diskUsage1, err := fs.DiskUsage()
   515  				require.NoError(t, err)
   516  
   517  				doc1, err := vfs.NewFileDoc(
   518  					"too-big",
   519  					consts.RootDirID,
   520  					diskQuota+1,
   521  					nil,
   522  					"application/octet-stream",
   523  					"application",
   524  					time.Now(),
   525  					false,
   526  					false,
   527  					false,
   528  					nil,
   529  				)
   530  				require.NoError(t, err)
   531  
   532  				_, err = fs.CreateFile(doc1, nil)
   533  				assert.Equal(t, vfs.ErrFileTooBig, err)
   534  
   535  				doc2, err := vfs.NewFileDoc(
   536  					"too-big",
   537  					consts.RootDirID,
   538  					diskQuota/2,
   539  					nil,
   540  					"application/octet-stream",
   541  					"application",
   542  					time.Now(),
   543  					false,
   544  					false,
   545  					false,
   546  					nil,
   547  				)
   548  				require.NoError(t, err)
   549  
   550  				f, err := fs.CreateFile(doc2, nil)
   551  				assert.NoError(t, err)
   552  				assert.Error(t, f.Close())
   553  
   554  				_, err = fs.FileByPath("/too-big")
   555  				assert.True(t, os.IsNotExist(err))
   556  
   557  				doc3, err := vfs.NewFileDoc(
   558  					"too-big",
   559  					consts.RootDirID,
   560  					diskQuota/2,
   561  					nil,
   562  					"application/octet-stream",
   563  					"application",
   564  					time.Now(),
   565  					false,
   566  					false,
   567  					false,
   568  					nil,
   569  				)
   570  				require.NoError(t, err)
   571  
   572  				f, err = fs.CreateFile(doc3, nil)
   573  				assert.NoError(t, err)
   574  				_, err = io.Copy(f, bytes.NewReader(crypto.GenerateRandomBytes(int(doc3.ByteSize))))
   575  				assert.NoError(t, err)
   576  				err = f.Close()
   577  				assert.NoError(t, err)
   578  
   579  				diskUsage2, err := fs.DiskUsage()
   580  				assert.NoError(t, err)
   581  				assert.Equal(t, diskUsage1+diskQuota/2, diskUsage2)
   582  
   583  				doc4, err := vfs.NewFileDoc(
   584  					"too-big2",
   585  					consts.RootDirID,
   586  					-1,
   587  					nil,
   588  					"application/octet-stream",
   589  					"application",
   590  					time.Now(),
   591  					false,
   592  					false,
   593  					false,
   594  					nil,
   595  				)
   596  				require.NoError(t, err)
   597  
   598  				f, err = fs.CreateFile(doc4, nil)
   599  				assert.NoError(t, err)
   600  				_, err = io.Copy(f, bytes.NewReader(crypto.GenerateRandomBytes(int(diskQuota/2+1))))
   601  				assert.Error(t, err)
   602  				assert.Equal(t, vfs.ErrFileTooBig, err)
   603  				err = f.Close()
   604  				assert.Error(t, err)
   605  				assert.Equal(t, vfs.ErrFileTooBig, err)
   606  
   607  				_, err = fs.FileByPath("/too-big2")
   608  				assert.True(t, os.IsNotExist(err))
   609  
   610  				root, err := fs.DirByPath("/")
   611  				require.NoError(t, err)
   612  
   613  				assert.NoError(t, fs.DestroyDirContent(root, fs.EnsureErased))
   614  			})
   615  
   616  			t.Run("CreateFileDocCopy", func(t *testing.T) {
   617  				md5sum := []byte("md5sum")
   618  				file, err := vfs.NewFileDoc("file", consts.RootDirID, -1, md5sum, "foo/bar", "foo", time.Now(), false, false, false, []string{})
   619  				require.NoError(t, err)
   620  
   621  				newname := "file (copy).txt"
   622  				newdoc := vfs.CreateFileDocCopy(file, "12345", newname)
   623  				assert.Empty(t, newdoc.DocID)
   624  				assert.Empty(t, newdoc.DocRev)
   625  				assert.Equal(t, "12345", newdoc.DirID)
   626  				assert.Equal(t, newname, newdoc.DocName)
   627  				assert.Equal(t, "text/plain", newdoc.Mime)
   628  				assert.Equal(t, "text", newdoc.Class)
   629  				assert.Equal(t, file.ByteSize, newdoc.ByteSize)
   630  				assert.Equal(t, file.MD5Sum, newdoc.MD5Sum)
   631  				assert.NotEqual(t, file.CreatedAt, newdoc.CreatedAt)
   632  				assert.Empty(t, newdoc.ReferencedBy)
   633  			})
   634  
   635  			t.Run("ConflictName", func(t *testing.T) {
   636  				tree := H{"existing": nil}
   637  				_ = createTree(t, fs, tree, consts.RootDirID)
   638  
   639  				newname := vfs.ConflictName(fs, consts.RootDirID, "existing", true)
   640  				assert.Equal(t, "existing (2)", newname)
   641  
   642  				tree = H{"existing (2)": nil}
   643  				_ = createTree(t, fs, tree, consts.RootDirID)
   644  
   645  				newname = vfs.ConflictName(fs, consts.RootDirID, "existing", true)
   646  				assert.Equal(t, "existing (3)", newname)
   647  
   648  				tree = H{"existing (3)": nil}
   649  				_ = createTree(t, fs, tree, consts.RootDirID)
   650  
   651  				newname = vfs.ConflictName(fs, consts.RootDirID, "existing (3)", true)
   652  				assert.Equal(t, "existing (4)", newname)
   653  
   654  				tree = H{"existing (copy)": nil}
   655  				_ = createTree(t, fs, tree, consts.RootDirID)
   656  
   657  				newname = vfs.ConflictName(fs, consts.RootDirID, "existing (copy)", true)
   658  				assert.Equal(t, "existing (copy) (2)", newname)
   659  			})
   660  
   661  			t.Run("CheckAvailableSpace", func(t *testing.T) {
   662  				diskQuota = 0
   663  
   664  				doc, err := vfs.NewFileDoc("toto", consts.RootDirID, 100, nil, "foo/bar", "foo", time.Now(), false, false, false, []string{})
   665  				require.NoError(t, err)
   666  				_, _, _, err = vfs.CheckAvailableDiskSpace(fs, doc)
   667  				require.NoError(t, err)
   668  
   669  				diskQuota = 100
   670  
   671  				doc, err = vfs.NewFileDoc("toto", consts.RootDirID, 100, nil, "foo/bar", "foo", time.Now(), false, false, false, []string{})
   672  				require.NoError(t, err)
   673  				_, _, _, err = vfs.CheckAvailableDiskSpace(fs, doc)
   674  				require.NoError(t, err)
   675  
   676  				doc, err = vfs.NewFileDoc("toto", consts.RootDirID, 101, nil, "foo/bar", "foo", time.Now(), false, false, false, []string{})
   677  				require.NoError(t, err)
   678  				_, _, _, err = vfs.CheckAvailableDiskSpace(fs, doc)
   679  				assert.Error(t, err)
   680  				assert.Equal(t, vfs.ErrFileTooBig, err)
   681  
   682  				maxFileSize := fs.MaxFileSize()
   683  				if maxFileSize > 0 {
   684  					doc, err = vfs.NewFileDoc("toto", consts.RootDirID, maxFileSize+1, nil, "foo/bar", "foo", time.Now(), false, false, false, []string{})
   685  					require.NoError(t, err)
   686  					_, _, _, err = vfs.CheckAvailableDiskSpace(fs, doc)
   687  					assert.Error(t, err)
   688  					assert.Equal(t, vfs.ErrMaxFileSize, err)
   689  				}
   690  			})
   691  		})
   692  	}
   693  }
   694  
   695  func (d *diskImpl) DiskQuota() int64 {
   696  	return diskQuota
   697  }
   698  
   699  func (h H) String() string {
   700  	return printH(h, "", 0)
   701  }
   702  
   703  func printH(h H, str string, count int) string {
   704  	for name, hh := range h {
   705  		for i := 0; i < count; i++ {
   706  			str += "\t"
   707  		}
   708  		str += fmt.Sprintf("%s:\n", name)
   709  		str += printH(hh, "", count+1)
   710  	}
   711  	return str
   712  }
   713  
   714  func createTree(t *testing.T, fs vfs.VFS, tree H, dirID string) *vfs.DirDoc {
   715  	t.Helper()
   716  
   717  	if tree == nil {
   718  		return nil
   719  	}
   720  
   721  	if dirID == "" {
   722  		dirID = consts.RootDirID
   723  	}
   724  
   725  	var err error
   726  	var dirdoc *vfs.DirDoc
   727  	for name, children := range tree {
   728  		if name[len(name)-1] == '/' {
   729  			dirdoc, err = vfs.NewDirDoc(fs, name[:len(name)-1], dirID, nil)
   730  			require.NoError(t, err)
   731  
   732  			err = fs.CreateDir(dirdoc)
   733  			require.NoError(t, err)
   734  
   735  			createTree(t, fs, children, dirdoc.ID())
   736  		} else {
   737  			mime, class := vfs.ExtractMimeAndClassFromFilename(name)
   738  			filedoc, err := vfs.NewFileDoc(name, dirID, -1, nil, mime, class, time.Now(), false, false, false, nil)
   739  			require.NoError(t, err)
   740  
   741  			f, err := fs.CreateFile(filedoc, nil)
   742  			require.NoError(t, err)
   743  
   744  			err = f.Close()
   745  			require.NoError(t, err)
   746  		}
   747  	}
   748  	return dirdoc
   749  }
   750  
   751  func fetchTree(fs vfs.VFS, root string) (H, error) {
   752  	parent, err := fs.DirByPath(root)
   753  	if err != nil {
   754  		return nil, err
   755  	}
   756  	h, err := recFetchTree(fs, parent, path.Clean(root))
   757  	if err != nil {
   758  		return nil, err
   759  	}
   760  	hh := make(H)
   761  	hh[parent.DocName+"/"] = h
   762  	return hh, nil
   763  }
   764  
   765  func recFetchTree(fs vfs.VFS, parent *vfs.DirDoc, name string) (H, error) {
   766  	h := make(H)
   767  	iter := fs.DirIterator(parent, nil)
   768  	for {
   769  		d, f, err := iter.Next()
   770  		if errors.Is(err, vfs.ErrIteratorDone) {
   771  			break
   772  		}
   773  		if err != nil {
   774  			return nil, err
   775  		}
   776  		if d != nil {
   777  			if path.Join(name, d.DocName) != d.Fullpath {
   778  				return nil, fmt.Errorf("Bad fullpath: %s instead of %s", d.Fullpath, path.Join(name, d.DocName))
   779  			}
   780  			children, err := recFetchTree(fs, d, d.Fullpath)
   781  			if err != nil {
   782  				return nil, err
   783  			}
   784  			h[d.DocName+"/"] = children
   785  		} else {
   786  			h[f.DocName] = nil
   787  		}
   788  	}
   789  	return h, nil
   790  }
   791  
   792  func (c *contexter) DBCluster() int         { return c.cluster }
   793  func (c *contexter) DomainName() string     { return c.domain }
   794  func (c *contexter) DBPrefix() string       { return c.prefix }
   795  func (c *contexter) GetContextName() string { return c.context }
   796  
   797  func makeAferoFS(t *testing.T) vfs.VFS {
   798  	t.Helper()
   799  
   800  	tempdir := t.TempDir()
   801  
   802  	db := &contexter{0, "swift.testvfs.example.org", "swift.testvfs.example.org", "cozy_beta"}
   803  	index := vfs.NewCouchdbIndexer(db)
   804  	mutex = config.Lock().ReadWrite(db, "vfs-afero-test")
   805  	aferoFs, err := vfsafero.New(db, index, &diskImpl{}, mutex,
   806  		&url.URL{Scheme: "file", Host: "localhost", Path: tempdir}, "io.cozy.vfs.test")
   807  	require.NoError(t, err)
   808  
   809  	require.NoError(t, couchdb.ResetDB(db, consts.Files))
   810  	t.Cleanup(func() { _ = couchdb.DeleteDB(db, consts.Files) })
   811  
   812  	g, _ := errgroup.WithContext(context.Background())
   813  	couchdb.DefineIndexes(g, db, couchdb.IndexesByDoctype(consts.Files))
   814  	couchdb.DefineViews(g, db, couchdb.ViewsByDoctype(consts.Files))
   815  
   816  	require.NoError(t, g.Wait())
   817  	require.NoError(t, aferoFs.InitFs())
   818  
   819  	return aferoFs
   820  }
   821  
   822  func makeSwiftFS(t *testing.T) vfs.VFS {
   823  	t.Helper()
   824  
   825  	db := &contexter{0, "io.cozy.vfs.test", "io.cozy.vfs.test", "cozy_beta"}
   826  	index := vfs.NewCouchdbIndexer(db)
   827  
   828  	swiftSrv, err := swifttest.NewSwiftServer("localhost")
   829  	require.NoError(t, err, "failed to create swift server")
   830  
   831  	require.NoError(t, config.InitSwiftConnection(config.Fs{
   832  		URL: &url.URL{
   833  			Scheme:   "swift",
   834  			Host:     "localhost",
   835  			RawQuery: "UserName=swifttest&Password=swifttest&AuthURL=" + url.QueryEscape(swiftSrv.AuthURL),
   836  		},
   837  	}))
   838  
   839  	mutex = config.Lock().ReadWrite(db, "vfs-swiftv3-test")
   840  	swiftFs, err := vfsswift.NewV3(db, index, &diskImpl{}, mutex)
   841  	require.NoError(t, err)
   842  
   843  	require.NoError(t, couchdb.ResetDB(db, consts.Files))
   844  
   845  	g, _ := errgroup.WithContext(context.Background())
   846  	couchdb.DefineIndexes(g, db, couchdb.IndexesByDoctype(consts.Files))
   847  	couchdb.DefineViews(g, db, couchdb.ViewsByDoctype(consts.Files))
   848  	require.NoError(t, g.Wait())
   849  
   850  	require.NoError(t, swiftFs.InitFs())
   851  
   852  	t.Cleanup(func() {
   853  		_ = couchdb.DeleteDB(db, consts.Files)
   854  		if swiftSrv != nil {
   855  			swiftSrv.Close()
   856  		}
   857  	})
   858  
   859  	return swiftFs
   860  }