This repository has been archived on 2026-08-16. You can view files and clone it, but cannot push or open issues or pull requests.
ollie-9p/fs/fs_test.go

267 lines
6.6 KiB
Go

package fs_test
import (
"fmt"
"io"
"os"
"path/filepath"
"strings"
"testing"
olog "ollie/pkg/log"
fs "olliesrv/fs"
)
// --- stub agent.Core ---
// --- helpers ---
func testSink() *olog.Sink {
return olog.NewSink(io.Discard, io.Discard, olog.LevelError)
}
func seedSkill(t *testing.T, base, name string) {
t.Helper()
d := filepath.Join(base, name)
os.MkdirAll(d, 0755)
os.WriteFile(filepath.Join(d, "SKILL.md"), []byte("---\ndescription: test\n---\n"), 0644)
}
// storeRead is a test helper: Open + Read.
func storeRead(t *testing.T, s fs.FileTree, name string) []byte {
t.Helper()
e, err := s.Open(name)
if err != nil {
t.Fatalf("Open(%q): %v", name, err)
}
data, err := e.Read()
if err != nil {
t.Fatalf("Read(%q): %v", name, err)
}
return data
}
// storeWrite is a test helper: Open + Write.
func storeWrite(t *testing.T, s fs.FileTree, name string, data []byte) {
t.Helper()
e, err := s.Open(name)
if err != nil {
t.Fatalf("Open(%q): %v", name, err)
}
if err := e.Write(data); err != nil {
t.Fatalf("Write(%q): %v", name, err)
}
}
// --- contract checks ---
func checkReadableContract(t *testing.T, s fs.FileTree, name string) {
t.Helper()
fi, err := s.Stat(name)
if err != nil {
t.Fatalf("Stat(%q): %v", name, err)
}
if fi.Name() != name {
t.Errorf("Stat(%q).Name() = %q", name, fi.Name())
}
if _, err := s.Stat("__nonexistent__"); err == nil {
t.Error("Stat(nonexistent) should error")
}
entries, err := s.List()
if err != nil {
t.Fatalf("List: %v", err)
}
found := false
for _, e := range entries {
if e.Name() == name {
found = true
}
}
if !found {
t.Errorf("List() missing %q", name)
}
e, err := s.Open(name)
if err != nil {
t.Fatalf("Open(%q): %v", name, err)
}
if _, err := e.Read(); err != nil {
t.Fatalf("Read(%q): %v", name, err)
}
}
func checkReadWriteContract(t *testing.T, s fs.FileTree, name string) {
t.Helper()
want := []byte("hello")
e, err := s.Open(name)
if err != nil {
t.Fatalf("Open: %v", err)
}
if err := e.Write(want); err != nil {
t.Fatalf("Write: %v", err)
}
got, err := e.Read()
if err != nil {
t.Fatalf("Read after Write: %v", err)
}
if string(got) != string(want) {
t.Errorf("Read = %q; want %q", got, want)
}
want2 := []byte("world")
if err := e.Write(want2); err != nil {
t.Fatalf("Write overwrite: %v", err)
}
got2, err := e.Read()
if err != nil {
t.Fatalf("Read after overwrite: %v", err)
}
if string(got2) != string(want2) {
t.Errorf("Read after overwrite = %q; want %q", got2, want2)
}
checkReadableContract(t, s, name)
}
func checkStoreContract(t *testing.T, s fs.FileTree, name string) {
t.Helper()
if err := s.Create(name); err != nil {
t.Fatalf("Create: %v", err)
}
if _, err := s.Stat(name); err != nil {
t.Fatalf("Stat after Create: %v", err)
}
renamed := name + "-renamed"
if err := s.Rename(name, renamed); err != nil {
t.Fatalf("Rename: %v", err)
}
if _, err := s.Stat(name); err == nil {
t.Error("Stat(old) should error after Rename")
}
if _, err := s.Stat(renamed); err != nil {
t.Errorf("Stat(new) after Rename: %v", err)
}
s.Delete(renamed) //nolint:errcheck
if err := s.Create(name); err != nil {
t.Fatalf("Create after rename cleanup: %v", err)
}
if err := s.Delete(name); err != nil {
t.Fatalf("Delete: %v", err)
}
if _, err := s.Stat(name); err == nil {
t.Error("Stat after Delete should error")
}
if err := s.Delete("__nonexistent__"); err == nil {
t.Error("Delete(nonexistent) should error")
}
checkReadWriteContract(t, s, name)
}
// ===== FlatDir =====
func TestFlatDirContract(t *testing.T) {
checkStoreContract(t, fs.NewTree([]string{t.TempDir()}, 0644), "test-file")
}
func TestFlatDirCreateMkdirError(t *testing.T) {
fd := fs.NewTree([]string{"/nonexistent/path"}, 0644)
if err := fd.Create("f"); err == nil {
t.Error("Create should fail when dir doesn't exist")
}
}
func TestFlatDirPutMkdirError(t *testing.T) {
fd := fs.NewTree([]string{"/nonexistent/path"}, 0644)
e, err := fd.Open("f")
if err != nil {
t.Fatalf("Open: %v", err)
}
if err := e.Write([]byte("x")); err == nil {
t.Error("Write should fail when dir doesn't exist")
}
}
// ===== SkillTree =====
func TestSkillStoreContract(t *testing.T) {
dir := t.TempDir()
t.Setenv("OLLIE_SKILLS_PATH", dir)
seedSkill(t, dir, "test-skill")
s := fs.NewSkillTree()
// Rename (needs seeded skill with valid front matter)
seedSkill(t, dir, "rename-src")
if err := s.Rename("rename-src.md", "rename-dst.md"); err != nil {
t.Fatalf("Rename: %v", err)
}
if _, err := s.Stat("rename-src.md"); err == nil {
t.Error("Stat(old) should error after Rename")
}
if _, err := s.Stat("rename-dst.md"); err != nil {
t.Errorf("Stat(new) after Rename: %v", err)
}
s.Delete("rename-dst.md") //nolint:errcheck
// Delete
seedSkill(t, dir, "del-test")
if err := s.Delete("del-test.md"); err != nil {
t.Fatalf("Delete: %v", err)
}
if _, err := s.Stat("del-test.md"); err == nil {
t.Error("Stat after Delete should error")
}
if err := s.Delete("__nonexistent__.md"); err == nil {
t.Error("Delete(nonexistent) should error")
}
// Write/Read round-trip (needs valid front matter)
content := []byte("---\ndescription: rw test\n---\nbody\n")
if err := s.Create("rw-skill.md"); err != nil {
t.Fatalf("Create: %v", err)
}
storeWrite(t, s, "rw-skill.md", content)
got := storeRead(t, s, "rw-skill.md")
if string(got) != string(content) {
t.Errorf("Read = %q; want %q", got, content)
}
// Readable
checkReadableContract(t, s, "test-skill.md")
// idx
idx := storeRead(t, s, "idx")
if len(idx) == 0 {
t.Error("idx should be non-empty with seeded skills")
}
// Rename nonexistent
if err := s.Rename("__nope__.md", "x.md"); err == nil {
t.Error("Rename(nonexistent) should error")
}
// Create calls through injected mkdirAll + writeFile
var createdDir, createdFile string
s2 := fs.NewSkillTreeWith(fs.SkillConfig{
Dirs: []string{dir},
MkdirAll: func(p string, _ os.FileMode) error { createdDir = p; return nil },
WriteFile: func(p string, _ []byte, _ os.FileMode) error { createdFile = p; return nil },
})
if err := s2.Create("newskill.md"); err != nil {
t.Fatalf("Create: %v", err)
}
if !strings.HasSuffix(createdDir, "newskill") {
t.Errorf("mkdirAll path = %q; want suffix newskill", createdDir)
}
if !strings.HasSuffix(createdFile, "SKILL.md") {
t.Errorf("writeFile path = %q; want suffix SKILL.md", createdFile)
}
// Create mkdirAll error
s3 := fs.NewSkillTreeWith(fs.SkillConfig{
Dirs: []string{dir},
MkdirAll: func(string, os.FileMode) error { return fmt.Errorf("denied") },
})
if err := s3.Create("x.md"); err == nil {
t.Error("Create should fail when mkdirAll errors")
}
}