fix: update tests to match current API after refactor

- node: add ReadFile (fs.ReadFileFS), Walk with WalkOptions, CopyFile
- node_test: fix Exists to single-return bool, FromTar as method call
- cache_test: remove Medium parameter, use t.TempDir()
- daemon_test: remove Medium from NewPIDFile/DaemonOptions, use os pkg

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Snider 2026-02-17 22:14:06 +00:00
parent 9b7a0bc30a
commit 8172824b42
4 changed files with 132 additions and 53 deletions

View file

@ -5,14 +5,11 @@ import (
"time"
"forge.lthn.ai/core/go/pkg/cache"
"forge.lthn.ai/core/go/pkg/io"
)
func TestCache(t *testing.T) {
m := io.NewMockMedium()
// Use a path that MockMedium will understand
baseDir := "/tmp/cache"
c, err := cache.New(m, baseDir, 1*time.Minute)
baseDir := t.TempDir()
c, err := cache.New(baseDir, 1*time.Minute)
if err != nil {
t.Fatalf("failed to create cache: %v", err)
}
@ -57,7 +54,7 @@ func TestCache(t *testing.T) {
}
// Test Expiry
cshort, err := cache.New(m, "/tmp/cache-short", 10*time.Millisecond)
cshort, err := cache.New(t.TempDir(), 10*time.Millisecond)
if err != nil {
t.Fatalf("failed to create short-lived cache: %v", err)
}
@ -93,8 +90,8 @@ func TestCache(t *testing.T) {
}
func TestCacheDefaults(t *testing.T) {
// Test default Medium (io.Local) and default TTL
c, err := cache.New(nil, "", 0)
// Test default TTL (uses cwd/.core/cache)
c, err := cache.New("", 0)
if err != nil {
t.Fatalf("failed to create cache with defaults: %v", err)
}

View file

@ -3,10 +3,10 @@ package cli
import (
"context"
"net/http"
"os"
"testing"
"time"
"forge.lthn.ai/core/go/pkg/io"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -27,17 +27,16 @@ func TestDetectMode(t *testing.T) {
func TestPIDFile(t *testing.T) {
t.Run("acquire and release", func(t *testing.T) {
m := io.NewMockMedium()
pidPath := "/tmp/test.pid"
pidPath := t.TempDir() + "/test.pid"
pid := NewPIDFile(m, pidPath)
pid := NewPIDFile(pidPath)
// Acquire should succeed
err := pid.Acquire()
require.NoError(t, err)
// File should exist with our PID
data, err := m.Read(pidPath)
data, err := os.ReadFile(pidPath)
require.NoError(t, err)
assert.NotEmpty(t, data)
@ -45,18 +44,18 @@ func TestPIDFile(t *testing.T) {
err = pid.Release()
require.NoError(t, err)
assert.False(t, m.Exists(pidPath))
_, statErr := os.Stat(pidPath)
assert.True(t, os.IsNotExist(statErr))
})
t.Run("stale pid file", func(t *testing.T) {
m := io.NewMockMedium()
pidPath := "/tmp/stale.pid"
pidPath := t.TempDir() + "/stale.pid"
// Write a stale PID (non-existent process)
err := m.Write(pidPath, "999999999")
err := os.WriteFile(pidPath, []byte("999999999"), 0644)
require.NoError(t, err)
pid := NewPIDFile(m, pidPath)
pid := NewPIDFile(pidPath)
// Should acquire successfully (stale PID removed)
err = pid.Acquire()
@ -67,23 +66,22 @@ func TestPIDFile(t *testing.T) {
})
t.Run("creates parent directory", func(t *testing.T) {
m := io.NewMockMedium()
pidPath := "/tmp/subdir/nested/test.pid"
pidPath := t.TempDir() + "/subdir/nested/test.pid"
pid := NewPIDFile(m, pidPath)
pid := NewPIDFile(pidPath)
err := pid.Acquire()
require.NoError(t, err)
assert.True(t, m.Exists(pidPath))
_, statErr := os.Stat(pidPath)
assert.NoError(t, statErr)
err = pid.Release()
require.NoError(t, err)
})
t.Run("path getter", func(t *testing.T) {
m := io.NewMockMedium()
pid := NewPIDFile(m, "/tmp/test.pid")
pid := NewPIDFile("/tmp/test.pid")
assert.Equal(t, "/tmp/test.pid", pid.Path())
})
}
@ -155,11 +153,9 @@ func TestHealthServer(t *testing.T) {
func TestDaemon(t *testing.T) {
t.Run("start and stop", func(t *testing.T) {
m := io.NewMockMedium()
pidPath := "/tmp/test.pid"
pidPath := t.TempDir() + "/test.pid"
d := NewDaemon(DaemonOptions{
Medium: m,
PIDFile: pidPath,
HealthAddr: "127.0.0.1:0",
ShutdownTimeout: 5 * time.Second,
@ -182,7 +178,8 @@ func TestDaemon(t *testing.T) {
require.NoError(t, err)
// PID file should be removed
assert.False(t, m.Exists(pidPath))
_, statErr := os.Stat(pidPath)
assert.True(t, os.IsNotExist(statErr))
})
t.Run("double start fails", func(t *testing.T) {

View file

@ -118,6 +118,89 @@ func (n *Node) WalkNode(root string, fn fs.WalkDirFunc) error {
return fs.WalkDir(n, root, fn)
}
// WalkOptions configures optional behaviour for Walk.
type WalkOptions struct {
// MaxDepth limits traversal depth (0 = unlimited, 1 = root children only).
MaxDepth int
// Filter, when non-nil, is called before visiting each entry.
// Return false to skip the entry (and its subtree if a directory).
Filter func(path string, d fs.DirEntry) bool
// SkipErrors suppresses errors from the root lookup and doesn't call fn.
SkipErrors bool
}
// Walk walks the in-memory tree with optional WalkOptions.
func (n *Node) Walk(root string, fn fs.WalkDirFunc, opts ...WalkOptions) error {
var opt WalkOptions
if len(opts) > 0 {
opt = opts[0]
}
if opt.SkipErrors {
// Check root exists — if not, silently skip.
if _, err := n.Stat(root); err != nil {
return nil
}
}
rootDepth := 0
if root != "." && root != "" {
rootDepth = strings.Count(root, "/") + 1
}
return fs.WalkDir(n, root, func(p string, d fs.DirEntry, err error) error {
if err != nil {
return fn(p, d, err)
}
// MaxDepth check.
if opt.MaxDepth > 0 {
depth := 0
if p != "." && p != "" {
depth = strings.Count(p, "/") + 1
}
if depth-rootDepth > opt.MaxDepth {
if d.IsDir() {
return fs.SkipDir
}
return nil
}
}
// Filter check.
if opt.Filter != nil && !opt.Filter(p, d) {
if d.IsDir() {
return fs.SkipDir
}
return nil
}
return fn(p, d, err)
})
}
// CopyFile copies a single file from the node to the OS filesystem.
func (n *Node) CopyFile(src, dst string, perm os.FileMode) error {
src = strings.TrimPrefix(src, "/")
f, ok := n.files[src]
if !ok {
// Check if it's a directory — can't copy a directory as a file.
if info, err := n.Stat(src); err == nil && info.IsDir() {
return &fs.PathError{Op: "copyfile", Path: src, Err: fs.ErrInvalid}
}
return &fs.PathError{Op: "copyfile", Path: src, Err: fs.ErrNotExist}
}
dir := path.Dir(dst)
if dir != "." {
if err := os.MkdirAll(dir, 0755); err != nil {
return err
}
}
return os.WriteFile(dst, f.content, perm)
}
// CopyTo copies a file (or directory tree) from the node to any Medium.
func (n *Node) CopyTo(target coreio.Medium, sourcePath, destPath string) error {
sourcePath = strings.TrimPrefix(sourcePath, "/")
@ -247,6 +330,20 @@ func (n *Node) ReadDir(name string) ([]fs.DirEntry, error) {
return entries, nil
}
// ReadFile returns the content of a file as a byte slice.
// Implements fs.ReadFileFS.
func (n *Node) ReadFile(name string) ([]byte, error) {
name = strings.TrimPrefix(name, "/")
f, ok := n.files[name]
if !ok {
return nil, fs.ErrNotExist
}
// Return a copy to prevent mutation of internal state.
out := make([]byte, len(f.content))
copy(out, f.content)
return out, nil
}
// ---------- Medium interface: read/write ----------
// Read retrieves the content of a file as a string.

View file

@ -243,33 +243,21 @@ func TestExists_Good(t *testing.T) {
n.AddData("foo.txt", []byte("foo"))
n.AddData("bar/baz.txt", []byte("baz"))
exists, err := n.Exists("foo.txt")
require.NoError(t, err)
assert.True(t, exists)
exists, err = n.Exists("bar")
require.NoError(t, err)
assert.True(t, exists)
assert.True(t, n.Exists("foo.txt"))
assert.True(t, n.Exists("bar"))
}
func TestExists_Bad(t *testing.T) {
n := New()
exists, err := n.Exists("nonexistent")
require.NoError(t, err)
assert.False(t, exists)
assert.False(t, n.Exists("nonexistent"))
}
func TestExists_Ugly(t *testing.T) {
n := New()
n.AddData("dummy.txt", []byte("dummy"))
exists, err := n.Exists(".")
require.NoError(t, err)
assert.True(t, exists, "root '.' must exist")
exists, err = n.Exists("")
require.NoError(t, err)
assert.True(t, exists, "empty path (root) must exist")
assert.True(t, n.Exists("."), "root '.' must exist")
assert.True(t, n.Exists(""), "empty path (root) must exist")
}
// ---------------------------------------------------------------------------
@ -463,20 +451,19 @@ func TestFromTar_Good(t *testing.T) {
}
require.NoError(t, tw.Close())
n, err := FromTar(buf.Bytes())
n := New()
err := n.FromTar(buf.Bytes())
require.NoError(t, err)
exists, _ := n.Exists("foo.txt")
assert.True(t, exists, "foo.txt should exist")
exists, _ = n.Exists("bar/baz.txt")
assert.True(t, exists, "bar/baz.txt should exist")
assert.True(t, n.Exists("foo.txt"), "foo.txt should exist")
assert.True(t, n.Exists("bar/baz.txt"), "bar/baz.txt should exist")
}
func TestFromTar_Bad(t *testing.T) {
// Truncated data that cannot be a valid tar.
truncated := make([]byte, 100)
_, err := FromTar(truncated)
n := New()
err := n.FromTar(truncated)
assert.Error(t, err, "truncated data should produce an error")
}
@ -488,7 +475,8 @@ func TestTarRoundTrip_Good(t *testing.T) {
tarball, err := n1.ToTar()
require.NoError(t, err)
n2, err := FromTar(tarball)
n2 := New()
err = n2.FromTar(tarball)
require.NoError(t, err)
// Verify n2 matches n1.