mirror of
https://github.com/lordmathis/lemma.git
synced 2025-11-05 15:44:21 +00:00
512 lines
11 KiB
Go
512 lines
11 KiB
Go
package storage_test
|
|
|
|
import (
|
|
"io/fs"
|
|
"lemma/internal/storage"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
_ "lemma/internal/testenv"
|
|
)
|
|
|
|
// TestFileNode ensures FileNode structs are created correctly
|
|
func TestFileNode(t *testing.T) {
|
|
testCases := []struct {
|
|
name string // name of the test case
|
|
node storage.FileNode
|
|
want storage.FileNode
|
|
}{
|
|
{
|
|
name: "file without children",
|
|
node: storage.FileNode{
|
|
ID: "test.md",
|
|
Name: "test.md",
|
|
Path: "test.md",
|
|
},
|
|
want: storage.FileNode{
|
|
ID: "test.md",
|
|
Name: "test.md",
|
|
Path: "test.md",
|
|
},
|
|
},
|
|
{
|
|
name: "directory with children",
|
|
node: storage.FileNode{
|
|
ID: "dir",
|
|
Name: "dir",
|
|
Path: "dir",
|
|
Children: []storage.FileNode{
|
|
{
|
|
ID: "dir/file1.md",
|
|
Name: "file1.md",
|
|
Path: "dir/file1.md",
|
|
},
|
|
},
|
|
},
|
|
want: storage.FileNode{
|
|
ID: "dir",
|
|
Name: "dir",
|
|
Path: "dir",
|
|
Children: []storage.FileNode{
|
|
{
|
|
ID: "dir/file1.md",
|
|
Name: "file1.md",
|
|
Path: "dir/file1.md",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
got := tc.node // Now we're testing the actual node structure
|
|
|
|
if got.ID != tc.want.ID {
|
|
t.Errorf("ID = %v, want %v", got.ID, tc.want.ID)
|
|
}
|
|
if got.Name != tc.want.Name {
|
|
t.Errorf("Name = %v, want %v", got.Name, tc.want.Name)
|
|
}
|
|
if got.Path != tc.want.Path {
|
|
t.Errorf("Path = %v, want %v", got.Path, tc.want.Path)
|
|
}
|
|
if len(got.Children) != len(tc.want.Children) {
|
|
t.Errorf("len(Children) = %v, want %v", len(got.Children), len(tc.want.Children))
|
|
}
|
|
// Add deep comparison of children if they exist
|
|
if len(got.Children) > 0 {
|
|
for i := range got.Children {
|
|
if got.Children[i].ID != tc.want.Children[i].ID {
|
|
t.Errorf("Children[%d].ID = %v, want %v", i, got.Children[i].ID, tc.want.Children[i].ID)
|
|
}
|
|
if got.Children[i].Name != tc.want.Children[i].Name {
|
|
t.Errorf("Children[%d].Name = %v, want %v", i, got.Children[i].Name, tc.want.Children[i].Name)
|
|
}
|
|
if got.Children[i].Path != tc.want.Children[i].Path {
|
|
t.Errorf("Children[%d].Path = %v, want %v", i, got.Children[i].Path, tc.want.Children[i].Path)
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestListFilesRecursively(t *testing.T) {
|
|
mockFS := NewMockFS()
|
|
s := storage.NewServiceWithOptions("test-root", storage.Options{
|
|
Fs: mockFS,
|
|
NewGitClient: nil,
|
|
})
|
|
|
|
t.Run("empty directory", func(t *testing.T) {
|
|
mockFS.ReadDirReturns = map[string]struct {
|
|
entries []fs.DirEntry
|
|
err error
|
|
}{
|
|
"test-root/1/1": {
|
|
entries: []fs.DirEntry{},
|
|
err: nil,
|
|
},
|
|
}
|
|
|
|
files, err := s.ListFilesRecursively(1, 1)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if len(files) != 0 {
|
|
t.Errorf("expected empty file list, got %v", files)
|
|
}
|
|
})
|
|
|
|
t.Run("directory with files", func(t *testing.T) {
|
|
mockFS.ReadDirReturns = map[string]struct {
|
|
entries []fs.DirEntry
|
|
err error
|
|
}{
|
|
"test-root/1/1": {
|
|
entries: []fs.DirEntry{
|
|
NewMockDirEntry("file1.md", false),
|
|
NewMockDirEntry("file2.md", false),
|
|
},
|
|
err: nil,
|
|
},
|
|
}
|
|
|
|
files, err := s.ListFilesRecursively(1, 1)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if len(files) != 2 {
|
|
t.Errorf("expected 2 files, got %d", len(files))
|
|
}
|
|
})
|
|
|
|
t.Run("nested directories", func(t *testing.T) {
|
|
mockFS.ReadDirReturns = map[string]struct {
|
|
entries []fs.DirEntry
|
|
err error
|
|
}{
|
|
"test-root/1/1": {
|
|
entries: []fs.DirEntry{
|
|
NewMockDirEntry("dir1", true),
|
|
NewMockDirEntry("file1.md", false),
|
|
},
|
|
err: nil,
|
|
},
|
|
"test-root/1/1/dir1": {
|
|
entries: []fs.DirEntry{
|
|
NewMockDirEntry("file2.md", false),
|
|
},
|
|
err: nil,
|
|
},
|
|
}
|
|
|
|
files, err := s.ListFilesRecursively(1, 1)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if len(files) != 2 { // dir1 and file1.md
|
|
t.Errorf("expected 2 entries at root, got %d", len(files))
|
|
}
|
|
|
|
// Find directory and check its children
|
|
var dirFound bool
|
|
for _, f := range files {
|
|
if f.Name == "dir1" {
|
|
dirFound = true
|
|
if len(f.Children) != 1 {
|
|
t.Errorf("expected 1 child in dir1, got %d", len(f.Children))
|
|
}
|
|
}
|
|
}
|
|
if !dirFound {
|
|
t.Error("directory 'dir1' not found in results")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestGetFileContent(t *testing.T) {
|
|
mockFS := NewMockFS()
|
|
s := storage.NewServiceWithOptions("test-root", storage.Options{
|
|
Fs: mockFS,
|
|
NewGitClient: nil,
|
|
})
|
|
|
|
testCases := []struct {
|
|
name string
|
|
userID int
|
|
workspaceID int
|
|
filePath string
|
|
mockData []byte
|
|
mockErr error
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "successful read",
|
|
userID: 1,
|
|
workspaceID: 1,
|
|
filePath: "test.md",
|
|
mockData: []byte("test content"),
|
|
mockErr: nil,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "file not found",
|
|
userID: 1,
|
|
workspaceID: 1,
|
|
filePath: "nonexistent.md",
|
|
mockData: nil,
|
|
mockErr: fs.ErrNotExist,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "invalid path",
|
|
userID: 1,
|
|
workspaceID: 1,
|
|
filePath: "../../../etc/passwd",
|
|
mockData: nil,
|
|
mockErr: nil,
|
|
wantErr: true,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
expectedPath := filepath.Join("test-root", "1", "1", tc.filePath)
|
|
mockFS.ReadFileReturns[expectedPath] = struct {
|
|
data []byte
|
|
err error
|
|
}{tc.mockData, tc.mockErr}
|
|
|
|
content, err := s.GetFileContent(tc.userID, tc.workspaceID, tc.filePath)
|
|
|
|
if tc.wantErr {
|
|
if err == nil {
|
|
t.Error("expected error, got nil")
|
|
}
|
|
return
|
|
}
|
|
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
if string(content) != string(tc.mockData) {
|
|
t.Errorf("content = %q, want %q", content, tc.mockData)
|
|
}
|
|
|
|
if mockFS.ReadCalls[expectedPath] != 1 {
|
|
t.Errorf("expected 1 read call for %s, got %d", expectedPath, mockFS.ReadCalls[expectedPath])
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSaveFile(t *testing.T) {
|
|
mockFS := NewMockFS()
|
|
s := storage.NewServiceWithOptions("test-root", storage.Options{
|
|
Fs: mockFS,
|
|
NewGitClient: nil,
|
|
})
|
|
|
|
testCases := []struct {
|
|
name string
|
|
userID int
|
|
workspaceID int
|
|
filePath string
|
|
content []byte
|
|
mockErr error
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "successful save",
|
|
userID: 1,
|
|
workspaceID: 1,
|
|
filePath: "test.md",
|
|
content: []byte("test content"),
|
|
mockErr: nil,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "invalid path",
|
|
userID: 1,
|
|
workspaceID: 1,
|
|
filePath: "../../../etc/passwd",
|
|
content: []byte("test content"),
|
|
mockErr: nil,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "write error",
|
|
userID: 1,
|
|
workspaceID: 1,
|
|
filePath: "test.md",
|
|
content: []byte("test content"),
|
|
mockErr: fs.ErrPermission,
|
|
wantErr: true,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
mockFS.WriteFileError = tc.mockErr
|
|
err := s.SaveFile(tc.userID, tc.workspaceID, tc.filePath, tc.content)
|
|
|
|
if tc.wantErr {
|
|
if err == nil {
|
|
t.Error("expected error, got nil")
|
|
}
|
|
return
|
|
}
|
|
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
expectedPath := filepath.Join("test-root", "1", "1", tc.filePath)
|
|
if content, ok := mockFS.WriteCalls[expectedPath]; ok {
|
|
if string(content) != string(tc.content) {
|
|
t.Errorf("written content = %q, want %q", content, tc.content)
|
|
}
|
|
} else {
|
|
t.Error("expected write call not made")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDeleteFile(t *testing.T) {
|
|
mockFS := NewMockFS()
|
|
s := storage.NewServiceWithOptions("test-root", storage.Options{
|
|
Fs: mockFS,
|
|
NewGitClient: nil,
|
|
})
|
|
|
|
testCases := []struct {
|
|
name string
|
|
userID int
|
|
workspaceID int
|
|
filePath string
|
|
mockErr error
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "successful delete",
|
|
userID: 1,
|
|
workspaceID: 1,
|
|
filePath: "test.md",
|
|
mockErr: nil,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "invalid path",
|
|
userID: 1,
|
|
workspaceID: 1,
|
|
filePath: "../../../etc/passwd",
|
|
mockErr: nil,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "file not found",
|
|
userID: 1,
|
|
workspaceID: 1,
|
|
filePath: "nonexistent.md",
|
|
mockErr: fs.ErrNotExist,
|
|
wantErr: true,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
mockFS.RemoveError = tc.mockErr
|
|
err := s.DeleteFile(tc.userID, tc.workspaceID, tc.filePath)
|
|
|
|
if tc.wantErr {
|
|
if err == nil {
|
|
t.Error("expected error, got nil")
|
|
}
|
|
return
|
|
}
|
|
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
expectedPath := filepath.Join("test-root", "1", "1", tc.filePath)
|
|
found := false
|
|
for _, p := range mockFS.RemoveCalls {
|
|
if p == expectedPath {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
t.Error("expected delete call not made")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
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")
|
|
}
|
|
})
|
|
}
|
|
}
|