mirror of
https://github.com/lordmathis/lemma.git
synced 2025-11-05 15:44:21 +00:00
Implement MoveFile functionality in FileManager and corresponding tests
This commit is contained in:
@@ -14,6 +14,7 @@ type FileManager interface {
|
||||
FindFileByName(userID, workspaceID int, filename string) ([]string, error)
|
||||
GetFileContent(userID, workspaceID int, filePath string) ([]byte, error)
|
||||
SaveFile(userID, workspaceID int, filePath string, content []byte) error
|
||||
MoveFile(userID, workspaceID int, srcPath string, dstPath string) error
|
||||
DeleteFile(userID, workspaceID int, filePath string) error
|
||||
GetFileStats(userID, workspaceID int) (*FileCountStats, error)
|
||||
GetTotalFileStats() (*FileCountStats, error)
|
||||
@@ -174,6 +175,34 @@ func (s *Service) SaveFile(userID, workspaceID int, filePath string, content []b
|
||||
return nil
|
||||
}
|
||||
|
||||
// MoveFile moves a file from srcPath to dstPath within the workspace directory.
|
||||
// Both paths must be relative to the workspace directory given by userID and workspaceID.
|
||||
// If the destination file already exists, it will be overwritten.
|
||||
func (s *Service) MoveFile(userID, workspaceID int, srcPath string, dstPath string) error {
|
||||
log := getLogger()
|
||||
|
||||
srcFullPath, err := s.ValidatePath(userID, workspaceID, srcPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dstFullPath, err := s.ValidatePath(userID, workspaceID, dstPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.fs.MoveFile(srcFullPath, dstFullPath); err != nil {
|
||||
return fmt.Errorf("failed to move file: %w", err)
|
||||
}
|
||||
|
||||
log.Debug("file moved",
|
||||
"userID", userID,
|
||||
"workspaceID", workspaceID,
|
||||
"src", srcPath,
|
||||
"dst", dstPath)
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteFile deletes the file at the given filePath.
|
||||
// Path must be a relative path within the workspace directory given by userID and workspaceID.
|
||||
func (s *Service) DeleteFile(userID, workspaceID int, filePath string) error {
|
||||
|
||||
@@ -407,3 +407,105 @@ func TestDeleteFile(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMoveFile(t *testing.T) {
|
||||
mockFS := NewMockFS()
|
||||
s := storage.NewServiceWithOptions("test-root", storage.Options{
|
||||
Fs: mockFS,
|
||||
NewGitClient: nil,
|
||||
})
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
userID int
|
||||
workspaceID int
|
||||
srcPath string
|
||||
dstPath string
|
||||
mockErr error
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "successful move",
|
||||
userID: 1,
|
||||
workspaceID: 1,
|
||||
srcPath: "test.md",
|
||||
dstPath: "moved.md",
|
||||
mockErr: nil,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "move to subdirectory",
|
||||
userID: 1,
|
||||
workspaceID: 1,
|
||||
srcPath: "test.md",
|
||||
dstPath: "subdir/test.md",
|
||||
mockErr: nil,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "invalid source path",
|
||||
userID: 1,
|
||||
workspaceID: 1,
|
||||
srcPath: "../../../etc/passwd",
|
||||
dstPath: "test.md",
|
||||
mockErr: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid destination path",
|
||||
userID: 1,
|
||||
workspaceID: 1,
|
||||
srcPath: "test.md",
|
||||
dstPath: "../../../etc/passwd",
|
||||
mockErr: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "filesystem move error",
|
||||
userID: 1,
|
||||
workspaceID: 1,
|
||||
srcPath: "test.md",
|
||||
dstPath: "moved.md",
|
||||
mockErr: fs.ErrPermission,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "same source and destination",
|
||||
userID: 1,
|
||||
workspaceID: 1,
|
||||
srcPath: "test.md",
|
||||
dstPath: "test.md",
|
||||
mockErr: nil,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
mockFS.MoveFileError = tc.mockErr
|
||||
err := s.MoveFile(tc.userID, tc.workspaceID, tc.srcPath, tc.dstPath)
|
||||
|
||||
if tc.wantErr {
|
||||
if err == nil {
|
||||
t.Error("expected error, got nil")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
expectedSrcPath := filepath.Join("test-root", "1", "1", tc.srcPath)
|
||||
expectedDstPath := filepath.Join("test-root", "1", "1", tc.dstPath)
|
||||
|
||||
if dstPath, ok := mockFS.MoveCalls[expectedSrcPath]; ok {
|
||||
if dstPath != expectedDstPath {
|
||||
t.Errorf("move destination = %q, want %q", dstPath, expectedDstPath)
|
||||
}
|
||||
} else {
|
||||
t.Error("expected move call not made")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
type fileSystem interface {
|
||||
ReadFile(path string) ([]byte, error)
|
||||
WriteFile(path string, data []byte, perm fs.FileMode) error
|
||||
MoveFile(src, dst string) error
|
||||
Remove(path string) error
|
||||
MkdirAll(path string, perm fs.FileMode) error
|
||||
RemoveAll(path string) error
|
||||
@@ -38,6 +39,21 @@ func (f *osFS) WriteFile(path string, data []byte, perm fs.FileMode) error {
|
||||
return os.WriteFile(path, data, perm)
|
||||
}
|
||||
|
||||
// MoveFile moves the file from src to dst, overwriting if necessary.
|
||||
func (f *osFS) MoveFile(src, dst string) error {
|
||||
if err := os.Rename(src, dst); err != nil {
|
||||
if os.IsExist(err) {
|
||||
// If the destination exists, remove it and try again
|
||||
if err := os.Remove(dst); err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
return os.Rename(src, dst)
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove deletes the file at the given path.
|
||||
func (f *osFS) Remove(path string) error { return os.Remove(path) }
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ type mockFS struct {
|
||||
// Record operations for verification
|
||||
ReadCalls map[string]int
|
||||
WriteCalls map[string][]byte
|
||||
MoveCalls map[string]string
|
||||
RemoveCalls []string
|
||||
MkdirCalls []string
|
||||
|
||||
@@ -56,6 +57,7 @@ type mockFS struct {
|
||||
err error
|
||||
}
|
||||
WriteFileError error
|
||||
MoveFileError error
|
||||
RemoveError error
|
||||
MkdirError error
|
||||
StatError error
|
||||
@@ -66,6 +68,7 @@ func NewMockFS() *mockFS {
|
||||
return &mockFS{
|
||||
ReadCalls: make(map[string]int),
|
||||
WriteCalls: make(map[string][]byte),
|
||||
MoveCalls: make(map[string]string),
|
||||
RemoveCalls: make([]string, 0),
|
||||
MkdirCalls: make([]string, 0),
|
||||
ReadFileReturns: make(map[string]struct {
|
||||
@@ -88,6 +91,14 @@ func (m *mockFS) WriteFile(path string, data []byte, _ fs.FileMode) error {
|
||||
return m.WriteFileError
|
||||
}
|
||||
|
||||
func (m *mockFS) MoveFile(src, dst string) error {
|
||||
m.MoveCalls[src] = dst
|
||||
if src == dst {
|
||||
return nil // No-op if source and destination are the same
|
||||
}
|
||||
return m.MoveFileError
|
||||
}
|
||||
|
||||
func (m *mockFS) Remove(path string) error {
|
||||
m.RemoveCalls = append(m.RemoveCalls, path)
|
||||
return m.RemoveError
|
||||
|
||||
Reference in New Issue
Block a user