feat(io): extend Medium interface with Delete, Rename, List, Stat operations
Adds the following methods to the Medium interface: - Delete(path) - remove a file or empty directory - DeleteAll(path) - recursively remove a file or directory - Rename(old, new) - move/rename a file or directory - List(path) - list directory entries (returns []fs.DirEntry) - Stat(path) - get file information (returns fs.FileInfo) - Exists(path) - check if path exists - IsDir(path) - check if path is a directory Implements these methods in both local.Medium (using os package) and MockMedium (in-memory for testing). Includes FileInfo and DirEntry types for mock implementations. This enables migration of direct os.* calls to the Medium abstraction for consistent path validation and testability. Refs #101 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
ea6d045327
commit
b053206e95
4 changed files with 694 additions and 0 deletions
|
|
@ -73,6 +73,127 @@ func TestMockMedium_FileSet_Good(t *testing.T) {
|
|||
assert.Equal(t, "content", m.Files["test.txt"])
|
||||
}
|
||||
|
||||
func TestMockMedium_Delete_Good(t *testing.T) {
|
||||
m := NewMockMedium()
|
||||
m.Files["test.txt"] = "content"
|
||||
|
||||
err := m.Delete("test.txt")
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, m.IsFile("test.txt"))
|
||||
}
|
||||
|
||||
func TestMockMedium_Delete_Bad_NotFound(t *testing.T) {
|
||||
m := NewMockMedium()
|
||||
err := m.Delete("nonexistent.txt")
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestMockMedium_Delete_Bad_DirNotEmpty(t *testing.T) {
|
||||
m := NewMockMedium()
|
||||
m.Dirs["mydir"] = true
|
||||
m.Files["mydir/file.txt"] = "content"
|
||||
|
||||
err := m.Delete("mydir")
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestMockMedium_DeleteAll_Good(t *testing.T) {
|
||||
m := NewMockMedium()
|
||||
m.Dirs["mydir"] = true
|
||||
m.Dirs["mydir/subdir"] = true
|
||||
m.Files["mydir/file.txt"] = "content"
|
||||
m.Files["mydir/subdir/nested.txt"] = "nested"
|
||||
|
||||
err := m.DeleteAll("mydir")
|
||||
assert.NoError(t, err)
|
||||
assert.Empty(t, m.Dirs)
|
||||
assert.Empty(t, m.Files)
|
||||
}
|
||||
|
||||
func TestMockMedium_Rename_Good(t *testing.T) {
|
||||
m := NewMockMedium()
|
||||
m.Files["old.txt"] = "content"
|
||||
|
||||
err := m.Rename("old.txt", "new.txt")
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, m.IsFile("old.txt"))
|
||||
assert.True(t, m.IsFile("new.txt"))
|
||||
assert.Equal(t, "content", m.Files["new.txt"])
|
||||
}
|
||||
|
||||
func TestMockMedium_Rename_Good_Dir(t *testing.T) {
|
||||
m := NewMockMedium()
|
||||
m.Dirs["olddir"] = true
|
||||
m.Files["olddir/file.txt"] = "content"
|
||||
|
||||
err := m.Rename("olddir", "newdir")
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, m.Dirs["olddir"])
|
||||
assert.True(t, m.Dirs["newdir"])
|
||||
assert.Equal(t, "content", m.Files["newdir/file.txt"])
|
||||
}
|
||||
|
||||
func TestMockMedium_List_Good(t *testing.T) {
|
||||
m := NewMockMedium()
|
||||
m.Dirs["mydir"] = true
|
||||
m.Files["mydir/file1.txt"] = "content1"
|
||||
m.Files["mydir/file2.txt"] = "content2"
|
||||
m.Dirs["mydir/subdir"] = true
|
||||
|
||||
entries, err := m.List("mydir")
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, entries, 3)
|
||||
|
||||
names := make(map[string]bool)
|
||||
for _, e := range entries {
|
||||
names[e.Name()] = true
|
||||
}
|
||||
assert.True(t, names["file1.txt"])
|
||||
assert.True(t, names["file2.txt"])
|
||||
assert.True(t, names["subdir"])
|
||||
}
|
||||
|
||||
func TestMockMedium_Stat_Good(t *testing.T) {
|
||||
m := NewMockMedium()
|
||||
m.Files["test.txt"] = "hello world"
|
||||
|
||||
info, err := m.Stat("test.txt")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "test.txt", info.Name())
|
||||
assert.Equal(t, int64(11), info.Size())
|
||||
assert.False(t, info.IsDir())
|
||||
}
|
||||
|
||||
func TestMockMedium_Stat_Good_Dir(t *testing.T) {
|
||||
m := NewMockMedium()
|
||||
m.Dirs["mydir"] = true
|
||||
|
||||
info, err := m.Stat("mydir")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "mydir", info.Name())
|
||||
assert.True(t, info.IsDir())
|
||||
}
|
||||
|
||||
func TestMockMedium_Exists_Good(t *testing.T) {
|
||||
m := NewMockMedium()
|
||||
m.Files["file.txt"] = "content"
|
||||
m.Dirs["mydir"] = true
|
||||
|
||||
assert.True(t, m.Exists("file.txt"))
|
||||
assert.True(t, m.Exists("mydir"))
|
||||
assert.False(t, m.Exists("nonexistent"))
|
||||
}
|
||||
|
||||
func TestMockMedium_IsDir_Good(t *testing.T) {
|
||||
m := NewMockMedium()
|
||||
m.Files["file.txt"] = "content"
|
||||
m.Dirs["mydir"] = true
|
||||
|
||||
assert.False(t, m.IsDir("file.txt"))
|
||||
assert.True(t, m.IsDir("mydir"))
|
||||
assert.False(t, m.IsDir("nonexistent"))
|
||||
}
|
||||
|
||||
// --- Wrapper Function Tests ---
|
||||
|
||||
func TestRead_Good(t *testing.T) {
|
||||
|
|
|
|||
305
pkg/io/io.go
305
pkg/io/io.go
|
|
@ -1,7 +1,11 @@
|
|||
package io
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
coreerr "github.com/host-uk/core/pkg/framework/core"
|
||||
"github.com/host-uk/core/pkg/io/local"
|
||||
|
|
@ -28,8 +32,58 @@ type Medium interface {
|
|||
|
||||
// FileSet is a convenience function that writes a file to the medium.
|
||||
FileSet(path, content string) error
|
||||
|
||||
// Delete removes a file or empty directory.
|
||||
Delete(path string) error
|
||||
|
||||
// DeleteAll removes a file or directory and all its contents recursively.
|
||||
DeleteAll(path string) error
|
||||
|
||||
// Rename moves a file or directory from oldPath to newPath.
|
||||
Rename(oldPath, newPath string) error
|
||||
|
||||
// List returns the directory entries for the given path.
|
||||
List(path string) ([]fs.DirEntry, error)
|
||||
|
||||
// Stat returns file information for the given path.
|
||||
Stat(path string) (fs.FileInfo, error)
|
||||
|
||||
// Exists checks if a path exists (file or directory).
|
||||
Exists(path string) bool
|
||||
|
||||
// IsDir checks if a path exists and is a directory.
|
||||
IsDir(path string) bool
|
||||
}
|
||||
|
||||
// FileInfo provides a simple implementation of fs.FileInfo for mock testing.
|
||||
type FileInfo struct {
|
||||
name string
|
||||
size int64
|
||||
mode fs.FileMode
|
||||
modTime time.Time
|
||||
isDir bool
|
||||
}
|
||||
|
||||
func (fi FileInfo) Name() string { return fi.name }
|
||||
func (fi FileInfo) Size() int64 { return fi.size }
|
||||
func (fi FileInfo) Mode() fs.FileMode { return fi.mode }
|
||||
func (fi FileInfo) ModTime() time.Time { return fi.modTime }
|
||||
func (fi FileInfo) IsDir() bool { return fi.isDir }
|
||||
func (fi FileInfo) Sys() any { return nil }
|
||||
|
||||
// DirEntry provides a simple implementation of fs.DirEntry for mock testing.
|
||||
type DirEntry struct {
|
||||
name string
|
||||
isDir bool
|
||||
mode fs.FileMode
|
||||
info fs.FileInfo
|
||||
}
|
||||
|
||||
func (de DirEntry) Name() string { return de.name }
|
||||
func (de DirEntry) IsDir() bool { return de.isDir }
|
||||
func (de DirEntry) Type() fs.FileMode { return de.mode.Type() }
|
||||
func (de DirEntry) Info() (fs.FileInfo, error) { return de.info, nil }
|
||||
|
||||
// Local is a pre-initialized medium for the local filesystem.
|
||||
// It uses "/" as root, providing unsandboxed access to the filesystem.
|
||||
// For sandboxed access, use NewSandboxed with a specific root path.
|
||||
|
|
@ -136,3 +190,254 @@ func (m *MockMedium) FileGet(path string) (string, error) {
|
|||
func (m *MockMedium) FileSet(path, content string) error {
|
||||
return m.Write(path, content)
|
||||
}
|
||||
|
||||
// Delete removes a file or empty directory from the mock filesystem.
|
||||
func (m *MockMedium) Delete(path string) error {
|
||||
if _, ok := m.Files[path]; ok {
|
||||
delete(m.Files, path)
|
||||
return nil
|
||||
}
|
||||
if _, ok := m.Dirs[path]; ok {
|
||||
// Check if directory is empty (no files or subdirs with this prefix)
|
||||
prefix := path
|
||||
if !strings.HasSuffix(prefix, "/") {
|
||||
prefix += "/"
|
||||
}
|
||||
for f := range m.Files {
|
||||
if strings.HasPrefix(f, prefix) {
|
||||
return coreerr.E("io.MockMedium.Delete", "directory not empty: "+path, os.ErrExist)
|
||||
}
|
||||
}
|
||||
for d := range m.Dirs {
|
||||
if d != path && strings.HasPrefix(d, prefix) {
|
||||
return coreerr.E("io.MockMedium.Delete", "directory not empty: "+path, os.ErrExist)
|
||||
}
|
||||
}
|
||||
delete(m.Dirs, path)
|
||||
return nil
|
||||
}
|
||||
return coreerr.E("io.MockMedium.Delete", "path not found: "+path, os.ErrNotExist)
|
||||
}
|
||||
|
||||
// DeleteAll removes a file or directory and all contents from the mock filesystem.
|
||||
func (m *MockMedium) DeleteAll(path string) error {
|
||||
found := false
|
||||
if _, ok := m.Files[path]; ok {
|
||||
delete(m.Files, path)
|
||||
found = true
|
||||
}
|
||||
if _, ok := m.Dirs[path]; ok {
|
||||
delete(m.Dirs, path)
|
||||
found = true
|
||||
}
|
||||
|
||||
// Delete all entries under this path
|
||||
prefix := path
|
||||
if !strings.HasSuffix(prefix, "/") {
|
||||
prefix += "/"
|
||||
}
|
||||
for f := range m.Files {
|
||||
if strings.HasPrefix(f, prefix) {
|
||||
delete(m.Files, f)
|
||||
found = true
|
||||
}
|
||||
}
|
||||
for d := range m.Dirs {
|
||||
if strings.HasPrefix(d, prefix) {
|
||||
delete(m.Dirs, d)
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return coreerr.E("io.MockMedium.DeleteAll", "path not found: "+path, os.ErrNotExist)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Rename moves a file or directory in the mock filesystem.
|
||||
func (m *MockMedium) Rename(oldPath, newPath string) error {
|
||||
if content, ok := m.Files[oldPath]; ok {
|
||||
m.Files[newPath] = content
|
||||
delete(m.Files, oldPath)
|
||||
return nil
|
||||
}
|
||||
if _, ok := m.Dirs[oldPath]; ok {
|
||||
// Move directory and all contents
|
||||
m.Dirs[newPath] = true
|
||||
delete(m.Dirs, oldPath)
|
||||
|
||||
oldPrefix := oldPath
|
||||
if !strings.HasSuffix(oldPrefix, "/") {
|
||||
oldPrefix += "/"
|
||||
}
|
||||
newPrefix := newPath
|
||||
if !strings.HasSuffix(newPrefix, "/") {
|
||||
newPrefix += "/"
|
||||
}
|
||||
|
||||
// Move files under this directory
|
||||
for f, content := range m.Files {
|
||||
if strings.HasPrefix(f, oldPrefix) {
|
||||
newF := newPrefix + strings.TrimPrefix(f, oldPrefix)
|
||||
m.Files[newF] = content
|
||||
delete(m.Files, f)
|
||||
}
|
||||
}
|
||||
// Move subdirectories
|
||||
for d := range m.Dirs {
|
||||
if strings.HasPrefix(d, oldPrefix) {
|
||||
newD := newPrefix + strings.TrimPrefix(d, oldPrefix)
|
||||
m.Dirs[newD] = true
|
||||
delete(m.Dirs, d)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return coreerr.E("io.MockMedium.Rename", "path not found: "+oldPath, os.ErrNotExist)
|
||||
}
|
||||
|
||||
// List returns directory entries for the mock filesystem.
|
||||
func (m *MockMedium) List(path string) ([]fs.DirEntry, error) {
|
||||
if _, ok := m.Dirs[path]; !ok {
|
||||
// Check if it's the root or has children
|
||||
hasChildren := false
|
||||
prefix := path
|
||||
if path != "" && !strings.HasSuffix(prefix, "/") {
|
||||
prefix += "/"
|
||||
}
|
||||
for f := range m.Files {
|
||||
if strings.HasPrefix(f, prefix) {
|
||||
hasChildren = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !hasChildren {
|
||||
for d := range m.Dirs {
|
||||
if strings.HasPrefix(d, prefix) {
|
||||
hasChildren = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasChildren && path != "" {
|
||||
return nil, coreerr.E("io.MockMedium.List", "directory not found: "+path, os.ErrNotExist)
|
||||
}
|
||||
}
|
||||
|
||||
prefix := path
|
||||
if path != "" && !strings.HasSuffix(prefix, "/") {
|
||||
prefix += "/"
|
||||
}
|
||||
|
||||
seen := make(map[string]bool)
|
||||
var entries []fs.DirEntry
|
||||
|
||||
// Find immediate children (files)
|
||||
for f, content := range m.Files {
|
||||
if !strings.HasPrefix(f, prefix) {
|
||||
continue
|
||||
}
|
||||
rest := strings.TrimPrefix(f, prefix)
|
||||
if rest == "" || strings.Contains(rest, "/") {
|
||||
// Skip if it's not an immediate child
|
||||
if idx := strings.Index(rest, "/"); idx != -1 {
|
||||
// This is a subdirectory
|
||||
dirName := rest[:idx]
|
||||
if !seen[dirName] {
|
||||
seen[dirName] = true
|
||||
entries = append(entries, DirEntry{
|
||||
name: dirName,
|
||||
isDir: true,
|
||||
mode: fs.ModeDir | 0755,
|
||||
info: FileInfo{
|
||||
name: dirName,
|
||||
isDir: true,
|
||||
mode: fs.ModeDir | 0755,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
if !seen[rest] {
|
||||
seen[rest] = true
|
||||
entries = append(entries, DirEntry{
|
||||
name: rest,
|
||||
isDir: false,
|
||||
mode: 0644,
|
||||
info: FileInfo{
|
||||
name: rest,
|
||||
size: int64(len(content)),
|
||||
mode: 0644,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Find immediate subdirectories
|
||||
for d := range m.Dirs {
|
||||
if !strings.HasPrefix(d, prefix) {
|
||||
continue
|
||||
}
|
||||
rest := strings.TrimPrefix(d, prefix)
|
||||
if rest == "" {
|
||||
continue
|
||||
}
|
||||
// Get only immediate child
|
||||
if idx := strings.Index(rest, "/"); idx != -1 {
|
||||
rest = rest[:idx]
|
||||
}
|
||||
if !seen[rest] {
|
||||
seen[rest] = true
|
||||
entries = append(entries, DirEntry{
|
||||
name: rest,
|
||||
isDir: true,
|
||||
mode: fs.ModeDir | 0755,
|
||||
info: FileInfo{
|
||||
name: rest,
|
||||
isDir: true,
|
||||
mode: fs.ModeDir | 0755,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
// Stat returns file information for the mock filesystem.
|
||||
func (m *MockMedium) Stat(path string) (fs.FileInfo, error) {
|
||||
if content, ok := m.Files[path]; ok {
|
||||
return FileInfo{
|
||||
name: filepath.Base(path),
|
||||
size: int64(len(content)),
|
||||
mode: 0644,
|
||||
}, nil
|
||||
}
|
||||
if _, ok := m.Dirs[path]; ok {
|
||||
return FileInfo{
|
||||
name: filepath.Base(path),
|
||||
isDir: true,
|
||||
mode: fs.ModeDir | 0755,
|
||||
}, nil
|
||||
}
|
||||
return nil, coreerr.E("io.MockMedium.Stat", "path not found: "+path, os.ErrNotExist)
|
||||
}
|
||||
|
||||
// Exists checks if a path exists in the mock filesystem.
|
||||
func (m *MockMedium) Exists(path string) bool {
|
||||
if _, ok := m.Files[path]; ok {
|
||||
return true
|
||||
}
|
||||
if _, ok := m.Dirs[path]; ok {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsDir checks if a path is a directory in the mock filesystem.
|
||||
func (m *MockMedium) IsDir(path string) bool {
|
||||
_, ok := m.Dirs[path]
|
||||
return ok
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package local
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
|
@ -167,3 +168,75 @@ func (m *Medium) FileGet(relativePath string) (string, error) {
|
|||
func (m *Medium) FileSet(relativePath, content string) error {
|
||||
return m.Write(relativePath, content)
|
||||
}
|
||||
|
||||
// Delete removes a file or empty directory.
|
||||
func (m *Medium) Delete(relativePath string) error {
|
||||
fullPath, err := m.path(relativePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.Remove(fullPath)
|
||||
}
|
||||
|
||||
// DeleteAll removes a file or directory and all its contents recursively.
|
||||
func (m *Medium) DeleteAll(relativePath string) error {
|
||||
fullPath, err := m.path(relativePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.RemoveAll(fullPath)
|
||||
}
|
||||
|
||||
// Rename moves a file or directory from oldPath to newPath.
|
||||
func (m *Medium) Rename(oldPath, newPath string) error {
|
||||
fullOldPath, err := m.path(oldPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fullNewPath, err := m.path(newPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.Rename(fullOldPath, fullNewPath)
|
||||
}
|
||||
|
||||
// List returns the directory entries for the given path.
|
||||
func (m *Medium) List(relativePath string) ([]fs.DirEntry, error) {
|
||||
fullPath, err := m.path(relativePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return os.ReadDir(fullPath)
|
||||
}
|
||||
|
||||
// Stat returns file information for the given path.
|
||||
func (m *Medium) Stat(relativePath string) (fs.FileInfo, error) {
|
||||
fullPath, err := m.path(relativePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return os.Stat(fullPath)
|
||||
}
|
||||
|
||||
// Exists checks if a path exists (file or directory).
|
||||
func (m *Medium) Exists(relativePath string) bool {
|
||||
fullPath, err := m.path(relativePath)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
_, err = os.Stat(fullPath)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// IsDir checks if a path exists and is a directory.
|
||||
func (m *Medium) IsDir(relativePath string) bool {
|
||||
fullPath, err := m.path(relativePath)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
info, err := os.Stat(fullPath)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return info.IsDir()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -199,3 +199,198 @@ func TestFileGetFileSet_Good(t *testing.T) {
|
|||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "path traversal attempt detected")
|
||||
}
|
||||
|
||||
func TestDelete_Good(t *testing.T) {
|
||||
testRoot, err := os.MkdirTemp("", "local_delete_test")
|
||||
assert.NoError(t, err)
|
||||
defer os.RemoveAll(testRoot)
|
||||
|
||||
medium, err := New(testRoot)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Create and delete a file
|
||||
err = medium.Write("file.txt", "content")
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, medium.IsFile("file.txt"))
|
||||
|
||||
err = medium.Delete("file.txt")
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, medium.IsFile("file.txt"))
|
||||
|
||||
// Create and delete an empty directory
|
||||
err = medium.EnsureDir("emptydir")
|
||||
assert.NoError(t, err)
|
||||
err = medium.Delete("emptydir")
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, medium.IsDir("emptydir"))
|
||||
}
|
||||
|
||||
func TestDelete_Bad_NotEmpty(t *testing.T) {
|
||||
testRoot, err := os.MkdirTemp("", "local_delete_notempty_test")
|
||||
assert.NoError(t, err)
|
||||
defer os.RemoveAll(testRoot)
|
||||
|
||||
medium, err := New(testRoot)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Create a directory with a file
|
||||
err = medium.Write("mydir/file.txt", "content")
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Try to delete non-empty directory
|
||||
err = medium.Delete("mydir")
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestDeleteAll_Good(t *testing.T) {
|
||||
testRoot, err := os.MkdirTemp("", "local_deleteall_test")
|
||||
assert.NoError(t, err)
|
||||
defer os.RemoveAll(testRoot)
|
||||
|
||||
medium, err := New(testRoot)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Create nested structure
|
||||
err = medium.Write("mydir/file1.txt", "content1")
|
||||
assert.NoError(t, err)
|
||||
err = medium.Write("mydir/subdir/file2.txt", "content2")
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Delete all
|
||||
err = medium.DeleteAll("mydir")
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, medium.Exists("mydir"))
|
||||
assert.False(t, medium.Exists("mydir/file1.txt"))
|
||||
assert.False(t, medium.Exists("mydir/subdir/file2.txt"))
|
||||
}
|
||||
|
||||
func TestRename_Good(t *testing.T) {
|
||||
testRoot, err := os.MkdirTemp("", "local_rename_test")
|
||||
assert.NoError(t, err)
|
||||
defer os.RemoveAll(testRoot)
|
||||
|
||||
medium, err := New(testRoot)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Rename a file
|
||||
err = medium.Write("old.txt", "content")
|
||||
assert.NoError(t, err)
|
||||
err = medium.Rename("old.txt", "new.txt")
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, medium.IsFile("old.txt"))
|
||||
assert.True(t, medium.IsFile("new.txt"))
|
||||
|
||||
content, err := medium.Read("new.txt")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "content", content)
|
||||
}
|
||||
|
||||
func TestRename_Bad_Traversal(t *testing.T) {
|
||||
testRoot, err := os.MkdirTemp("", "local_rename_traversal_test")
|
||||
assert.NoError(t, err)
|
||||
defer os.RemoveAll(testRoot)
|
||||
|
||||
medium, err := New(testRoot)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = medium.Write("file.txt", "content")
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = medium.Rename("file.txt", "../escaped.txt")
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "path traversal")
|
||||
}
|
||||
|
||||
func TestList_Good(t *testing.T) {
|
||||
testRoot, err := os.MkdirTemp("", "local_list_test")
|
||||
assert.NoError(t, err)
|
||||
defer os.RemoveAll(testRoot)
|
||||
|
||||
medium, err := New(testRoot)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Create some files and directories
|
||||
err = medium.Write("file1.txt", "content1")
|
||||
assert.NoError(t, err)
|
||||
err = medium.Write("file2.txt", "content2")
|
||||
assert.NoError(t, err)
|
||||
err = medium.EnsureDir("subdir")
|
||||
assert.NoError(t, err)
|
||||
|
||||
// List root
|
||||
entries, err := medium.List(".")
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, entries, 3)
|
||||
|
||||
names := make(map[string]bool)
|
||||
for _, e := range entries {
|
||||
names[e.Name()] = true
|
||||
}
|
||||
assert.True(t, names["file1.txt"])
|
||||
assert.True(t, names["file2.txt"])
|
||||
assert.True(t, names["subdir"])
|
||||
}
|
||||
|
||||
func TestStat_Good(t *testing.T) {
|
||||
testRoot, err := os.MkdirTemp("", "local_stat_test")
|
||||
assert.NoError(t, err)
|
||||
defer os.RemoveAll(testRoot)
|
||||
|
||||
medium, err := New(testRoot)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Stat a file
|
||||
err = medium.Write("file.txt", "hello world")
|
||||
assert.NoError(t, err)
|
||||
info, err := medium.Stat("file.txt")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "file.txt", info.Name())
|
||||
assert.Equal(t, int64(11), info.Size())
|
||||
assert.False(t, info.IsDir())
|
||||
|
||||
// Stat a directory
|
||||
err = medium.EnsureDir("mydir")
|
||||
assert.NoError(t, err)
|
||||
info, err = medium.Stat("mydir")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "mydir", info.Name())
|
||||
assert.True(t, info.IsDir())
|
||||
}
|
||||
|
||||
func TestExists_Good(t *testing.T) {
|
||||
testRoot, err := os.MkdirTemp("", "local_exists_test")
|
||||
assert.NoError(t, err)
|
||||
defer os.RemoveAll(testRoot)
|
||||
|
||||
medium, err := New(testRoot)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.False(t, medium.Exists("nonexistent"))
|
||||
|
||||
err = medium.Write("file.txt", "content")
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, medium.Exists("file.txt"))
|
||||
|
||||
err = medium.EnsureDir("mydir")
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, medium.Exists("mydir"))
|
||||
}
|
||||
|
||||
func TestIsDir_Good(t *testing.T) {
|
||||
testRoot, err := os.MkdirTemp("", "local_isdir_test")
|
||||
assert.NoError(t, err)
|
||||
defer os.RemoveAll(testRoot)
|
||||
|
||||
medium, err := New(testRoot)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = medium.Write("file.txt", "content")
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, medium.IsDir("file.txt"))
|
||||
|
||||
err = medium.EnsureDir("mydir")
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, medium.IsDir("mydir"))
|
||||
|
||||
assert.False(t, medium.IsDir("nonexistent"))
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue