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

     1  package move
     2  
     3  import (
     4  	"math/rand"
     5  	"path"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/cozy/cozy-stack/model/vfs"
    10  	"github.com/cozy/cozy-stack/pkg/config/config"
    11  	"github.com/cozy/cozy-stack/pkg/consts"
    12  	"github.com/cozy/cozy-stack/pkg/couchdb"
    13  	"github.com/cozy/cozy-stack/pkg/crypto"
    14  	"github.com/cozy/cozy-stack/tests/testutils"
    15  	"github.com/stretchr/testify/assert"
    16  )
    17  
    18  type Stats struct {
    19  	TotalSize int64
    20  	Dirs      map[string]struct{}
    21  	Files     map[string][]byte
    22  }
    23  
    24  func TestExport(t *testing.T) {
    25  	if testing.Short() {
    26  		t.Skip("an instance is required for this test: test skipped due to the use of --short flag")
    27  	}
    28  
    29  	seed := time.Now().UTC().Unix()
    30  	t.Logf("seed = %d\n", seed)
    31  	rand.Seed(seed)
    32  	config.UseTestFile(t)
    33  	setup := testutils.NewSetup(t, t.Name())
    34  	inst := setup.GetTestInstance()
    35  
    36  	t.Run("ExportFiles", func(t *testing.T) {
    37  		fs := inst.VFS()
    38  
    39  		// The partsSize is voluntary really small to have a lot of parts,
    40  		// which can help to test the edge cases
    41  		exportDoc := &ExportDoc{
    42  			PartsSize: 10,
    43  		}
    44  
    45  		nbFiles := rand.Intn(100)
    46  		root, err := fs.DirByID(consts.RootDirID)
    47  		assert.NoError(t, err)
    48  		populateTree(t, fs, root, nbFiles)
    49  
    50  		nbVersions, err := couchdb.CountNormalDocs(inst, consts.FilesVersions)
    51  		assert.NoError(t, err)
    52  
    53  		// /* Uncomment this section for debug */
    54  		// vfs.Walk(fs, root.Fullpath, func(fpath string, dir *vfs.DirDoc, file *vfs.FileDoc, err error) error {
    55  		// 	if err != nil {
    56  		// 		return err
    57  		// 	}
    58  		// 	if fpath == root.Fullpath {
    59  		// 		return nil
    60  		// 	}
    61  		// 	level := strings.Count(fpath, "/")
    62  		// 	for i := 0; i < level; i++ {
    63  		// 		if i == level-1 {
    64  		// 			_, err = t.Logf("└── ")
    65  		// 		} else {
    66  		// 			_, err = t.Logf("|  ")
    67  		// 		}
    68  		// 		if err != nil {
    69  		// 			return err
    70  		// 		}
    71  		// 	}
    72  		// 	if dir != nil {
    73  		// 		_, err = t.Log(dir.DocName)
    74  		// 	} else {
    75  		// 		_, err = t.Loff("%s (%d)\n", file.DocName, file.ByteSize)
    76  		// 	}
    77  		// 	return err
    78  		// })
    79  		// t.Logf("nb files = %d\n", nbFiles)
    80  
    81  		// Build the cursors
    82  		_, err = exportFiles(inst, exportDoc, nil)
    83  		assert.NoError(t, err)
    84  
    85  		// Check files
    86  		cursors := append(exportDoc.PartsCursors, "")
    87  		fileIDs := map[string]bool{}
    88  		for _, c := range cursors {
    89  			cursor, err := ParseCursor(exportDoc, c)
    90  			assert.NoError(t, err)
    91  			list, err := listFilesFromCursor(inst, exportDoc, cursor)
    92  			assert.NoError(t, err)
    93  			for _, f := range list {
    94  				assert.False(t, fileIDs[f.DocID])
    95  				fileIDs[f.DocID] = true
    96  			}
    97  		}
    98  		assert.Len(t, fileIDs, nbFiles)
    99  
   100  		// Check file versions
   101  		versionsIDs := map[string]bool{}
   102  		for _, c := range cursors {
   103  			cursor, err := ParseCursor(exportDoc, c)
   104  			assert.NoError(t, err)
   105  			list, err := listVersionsFromCursor(inst, exportDoc, cursor)
   106  			assert.NoError(t, err)
   107  			for _, v := range list {
   108  				assert.False(t, versionsIDs[v.DocID])
   109  				versionsIDs[v.DocID] = true
   110  			}
   111  		}
   112  		assert.Len(t, versionsIDs, nbVersions)
   113  	})
   114  }
   115  
   116  func createFile(t *testing.T, fs vfs.VFS, parent *vfs.DirDoc) {
   117  	size := 1 + rand.Intn(25)
   118  	name := crypto.GenerateRandomString(8)
   119  	doc, err := vfs.NewFileDoc(name, parent.DocID, -1, nil, "application/octet-stream", "application", time.Now(), false, false, false, nil)
   120  	assert.NoError(t, err)
   121  	doc.CozyMetadata = vfs.NewCozyMetadata("")
   122  	file, err := fs.CreateFile(doc, nil)
   123  	assert.NoError(t, err)
   124  	buf := make([]byte, size)
   125  	_, err = file.Write(buf)
   126  	assert.NoError(t, err)
   127  	assert.NoError(t, file.Close())
   128  
   129  	// Create some file versions
   130  	nb := rand.Intn(3)
   131  	for i := 0; i < nb; i++ {
   132  		size = 1 + rand.Intn(25)
   133  		olddoc := doc.Clone().(*vfs.FileDoc)
   134  		doc.CozyMetadata = olddoc.CozyMetadata.Clone()
   135  		doc.CozyMetadata.UpdatedAt = doc.CozyMetadata.UpdatedAt.Add(1 * time.Hour)
   136  		doc.MD5Sum = nil
   137  		doc.ByteSize = int64(size)
   138  		file, err = fs.CreateFile(doc, olddoc)
   139  		assert.NoError(t, err)
   140  		buf := make([]byte, size)
   141  		_, err = file.Write(buf)
   142  		assert.NoError(t, err)
   143  		assert.NoError(t, file.Close())
   144  	}
   145  }
   146  
   147  func populateTree(t *testing.T, fs vfs.VFS, parent *vfs.DirDoc, nb int) {
   148  	nbDirs := rand.Intn(5)
   149  	if nbDirs > nb {
   150  		nbDirs %= (nb + 1)
   151  	}
   152  
   153  	// Create the sub-directories
   154  	for i := 0; i < nbDirs; i++ {
   155  		name := crypto.GenerateRandomString(6)
   156  		fullpath := path.Join(parent.Fullpath, name)
   157  		dir, err := vfs.Mkdir(fs, fullpath, nil)
   158  		assert.NoError(t, err)
   159  		nbFiles := rand.Intn(nb)
   160  		populateTree(t, fs, dir, nbFiles)
   161  		nb -= nbFiles
   162  	}
   163  
   164  	// Create some files
   165  	for j := 0; j < nb; j++ {
   166  		createFile(t, fs, parent)
   167  	}
   168  }