From 77e352a101f7eb11218998c379b5349535e0221a Mon Sep 17 00:00:00 2001 From: Xavier Michelon Date: Fri, 8 Oct 2021 08:19:15 +0200 Subject: [PATCH] GODT-1332 Added tests for cache move functions. --- internal/users/cache.go | 45 ++++---- internal/users/cache_test.go | 192 +++++++++++++++++++++++++++++++++++ 2 files changed, 217 insertions(+), 20 deletions(-) create mode 100644 internal/users/cache_test.go diff --git a/internal/users/cache.go b/internal/users/cache.go index 0767a2d7..1bc4e182 100644 --- a/internal/users/cache.go +++ b/internal/users/cache.go @@ -27,24 +27,6 @@ import ( "github.com/sirupsen/logrus" ) -func (u *Users) EnableCache() error { - // NOTE(GODT-1158): Check for available size before enabling. - - return nil -} - -func (u *Users) DisableCache() error { - // NOTE(GODT-1158): Is it an error if we can't remove a user's cache? - - for _, user := range u.users { - if err := user.store.RemoveCache(); err != nil { - logrus.WithError(err).Error("Failed to remove user's message cache") - } - } - - return nil -} - // isFolderEmpty checks whether a folder is empty. // path must point to an existing folder. func isFolderEmpty(path string) (bool, error) { @@ -171,8 +153,13 @@ func copyFile(srcPath, dstPath string) error { } dstInfo, err := os.Stat(dstPath) - if err == nil && !dstInfo.Mode().IsRegular() { - return errors.New("destination exists and is not a regular file") + if err == nil { + if !dstInfo.Mode().IsRegular() { + return errors.New("destination exists and is not a regular file") + } + if os.SameFile(srcInfo, dstInfo) { + return errors.New("source and destination are the same") + } } src, err := os.Open(filepath.Clean(srcPath)) @@ -194,6 +181,24 @@ func copyFile(srcPath, dstPath string) error { return err } +func (u *Users) EnableCache() error { + // NOTE(GODT-1158): Check for available size before enabling. + + return nil +} + +func (u *Users) DisableCache() error { + // NOTE(GODT-1158): Is it an error if we can't remove a user's cache? + + for _, user := range u.users { + if err := user.store.RemoveCache(); err != nil { + logrus.WithError(err).Error("Failed to remove user's message cache") + } + } + + return nil +} + // MigrateCache moves the message cache folder from folder srcPath to folder dstPath. // srcPath must point to an existing folder. dstPath must be an empty folder or not exist. func (u *Users) MigrateCache(srcPath, dstPath string) error { diff --git a/internal/users/cache_test.go b/internal/users/cache_test.go new file mode 100644 index 00000000..b9add609 --- /dev/null +++ b/internal/users/cache_test.go @@ -0,0 +1,192 @@ +// Copyright (c) 2021 Proton Technologies AG +// +// This file is part of ProtonMail Bridge. +// +// ProtonMail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ProtonMail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with ProtonMail Bridge. If not, see . + +package users + +import ( + "crypto/sha1" + "encoding/hex" + "io" + "io/ioutil" + "os" + "path/filepath" + "testing" + + r "github.com/stretchr/testify/require" +) + +const ( + str1 = "Lorem ipsum dolor sit amet" + str2 = "consectetur adipisicing elit" +) + +// tempFileWithContent() creates a temporary file in folderPath containing the string content. +// Returns the path of the created file. +func tempFileWithContent(folderPath, content string) (string, error) { + file, err := ioutil.TempFile(folderPath, "") + if err != nil { + return "", err + } + defer func() { _ = file.Close() }() + _, err = file.WriteString(content) + return file.Name(), err +} + +// itemCountInFolder() counts the number of items (files, folders, etc) in a folder. +// Returns -1 if an error occurred. +func itemCountInFolder(path string) int { + files, err := ioutil.ReadDir(path) + if err != nil { + return -1 + } + return len(files) +} + +// hashForFile returns the sha1 hash for the given file. +func hashForFile(path string) (string, error) { + hash := sha1.New() + file, err := os.Open(path) + if err != nil { + return "", err + } + defer func() { _ = file.Close() }() + if _, err = io.Copy(hash, file); err != nil { + return "", err + } + return hex.EncodeToString(hash.Sum(nil)), nil +} + +// filesAreIdentical() returns true if the two given files exist and have the same content. +func filesAreIdentical(path1, path2 string) bool { + hash1, err := hashForFile(path1) + if err != nil { + return false + } + hash2, err := hashForFile(path2) + return (err == nil) && hash1 == hash2 +} + +func TestCache_IsFolderEmpty(t *testing.T) { + _, err := isFolderEmpty("") + r.Error(t, err) + tempDirPath, err := ioutil.TempDir("", "") + defer func() { r.NoError(t, os.Remove(tempDirPath)) }() + r.NoError(t, err) + result, err := isFolderEmpty(tempDirPath) + r.NoError(t, err) + r.True(t, result) + tempFile, err := ioutil.TempFile(tempDirPath, "") + r.NoError(t, err) + defer func() { r.NoError(t, os.Remove(tempFile.Name())) }() + r.NoError(t, tempFile.Close()) + _, err = isFolderEmpty(tempFile.Name()) + r.Error(t, err) + result, err = isFolderEmpty(tempDirPath) + r.NoError(t, err) + r.False(t, result) +} + +func TestCache_CheckFolderIsSuitableDestinationForCache(t *testing.T) { + tempDirPath, err := ioutil.TempDir("", "") + defer func() { _ = os.Remove(tempDirPath) }() // cleanup in case we fail before removing it. + r.NoError(t, err) + tempFile, err := ioutil.TempFile(tempDirPath, "") + r.NoError(t, err) + defer func() { _ = os.Remove(tempFile.Name()) }() // cleanup in case we fail before removing it. + r.NoError(t, tempFile.Close()) + r.Error(t, checkFolderIsSuitableDestinationForCache(tempDirPath)) + r.NoError(t, os.Remove(tempFile.Name())) + r.NoError(t, checkFolderIsSuitableDestinationForCache(tempDirPath)) + r.NoDirExists(t, tempDirPath) // previous call to checkFolderIsSuitableDestinationForCache should have removed the folder + r.NoError(t, checkFolderIsSuitableDestinationForCache(tempDirPath)) +} + +func TestCache_CopyFolder(t *testing.T) { + // create a simple tree structure + // srcDir/ + // |-file1 + // |-srcSubDir/ + // |-file2 + + srcDir, err := ioutil.TempDir("", "") + defer func() { r.NoError(t, os.RemoveAll(srcDir)) }() + r.NoError(t, err) + srcSubDir, err := ioutil.TempDir(srcDir, "") + r.NoError(t, err) + subDirName := filepath.Base(srcSubDir) + file1, err := tempFileWithContent(srcDir, str1) + r.NoError(t, err) + file2, err := tempFileWithContent(srcSubDir, str2) + r.NoError(t, err) + + // copy it + dstDir := srcDir + "_" + r.NoDirExists(t, dstDir) + r.NoFileExists(t, dstDir) + r.Error(t, copyFolder(srcDir, srcDir)) + r.NoError(t, copyFolder(srcDir, dstDir)) + defer func() { r.NoError(t, os.RemoveAll(dstDir)) }() + + // check copy and original + r.DirExists(t, srcDir) + r.DirExists(t, srcSubDir) + r.FileExists(t, file1) + r.FileExists(t, file2) + r.True(t, itemCountInFolder(srcDir) == 2) + r.True(t, itemCountInFolder(srcSubDir) == 1) + r.DirExists(t, dstDir) + dstSubDir := filepath.Join(dstDir, subDirName) + r.DirExists(t, dstSubDir) + dstFile1 := filepath.Join(dstDir, filepath.Base(file1)) + r.FileExists(t, dstFile1) + dstFile2 := filepath.Join(dstDir, subDirName, filepath.Base(file2)) + r.FileExists(t, dstFile2) + r.True(t, itemCountInFolder(dstDir) == 2) + r.True(t, itemCountInFolder(dstSubDir) == 1) + r.True(t, filesAreIdentical(file1, dstFile1)) + r.True(t, filesAreIdentical(file2, dstFile2)) +} + +func TestCache_IsSubfolderOf(t *testing.T) { + dir, err := ioutil.TempDir("", "") + defer func() { r.NoError(t, os.Remove(dir)) }() + r.NoError(t, err) + r.True(t, isSubfolderOf(dir, dir)) + fakeDir := dir + "_" + r.False(t, isSubfolderOf(dir, fakeDir+"_")) + subDir := filepath.Join(dir, "A", "B") + r.True(t, isSubfolderOf(subDir, dir)) + r.True(t, isSubfolderOf(filepath.Dir(subDir), dir)) + r.False(t, isSubfolderOf(dir, subDir)) +} + +func TestCache_CopyFile(t *testing.T) { + file1, err := tempFileWithContent("", str1) + r.NoError(t, err) + defer func() { r.NoError(t, os.Remove(file1)) }() + file2, err := tempFileWithContent("", str2) + r.NoError(t, err) + defer func() { r.NoError(t, os.Remove(file2)) }() + r.Error(t, copyFile(file1, file1)) + r.Error(t, copyFile(file1, filepath.Dir(file1))) + r.Error(t, copyFile(file1, file1)) + r.NoError(t, copyFile(file1, file2)) + file3 := file2 + "_" + r.NoFileExists(t, file3) + r.NoError(t, copyFile(file1, file3)) + defer func() { r.NoError(t, os.Remove(file3)) }() +}