post inicial
This commit is contained in:
@@ -0,0 +1,233 @@
|
||||
package project
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func write(t *testing.T, path, content string) {
|
||||
t.Helper()
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func names(tasks []Task) string {
|
||||
var s []string
|
||||
for _, t := range tasks {
|
||||
s = append(s, t.Name)
|
||||
}
|
||||
return strings.Join(s, ",")
|
||||
}
|
||||
|
||||
func TestFindAgentFile(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
p, _ := Open(dir)
|
||||
|
||||
if _, _, err := p.FindAgentFile(); !errors.Is(err, ErrAgentNotFound) {
|
||||
t.Fatalf("expected ErrAgentNotFound, got %v", err)
|
||||
}
|
||||
|
||||
write(t, filepath.Join(dir, "Agent.md"), "x")
|
||||
name, variant, err := p.FindAgentFile()
|
||||
if err != nil || name != "Agent.md" || !variant {
|
||||
t.Fatalf("variant: name=%q variant=%v err=%v", name, variant, err)
|
||||
}
|
||||
|
||||
write(t, filepath.Join(dir, "agent.md"), "x")
|
||||
name, variant, err = p.FindAgentFile()
|
||||
if err != nil || name != "agent.md" || variant {
|
||||
t.Fatalf("exact: name=%q variant=%v err=%v", name, variant, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAgentSymlinkOutsideRejected(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
outside := t.TempDir()
|
||||
write(t, filepath.Join(outside, "agent.md"), "EVIL")
|
||||
if err := os.Symlink(filepath.Join(outside, "agent.md"), filepath.Join(dir, "agent.md")); err != nil {
|
||||
t.Skip("symlinks not supported:", err)
|
||||
}
|
||||
p, _ := Open(dir)
|
||||
if _, _, err := p.FindAgentFile(); !errors.Is(err, ErrAgentNotFound) {
|
||||
t.Fatalf("symlink to external agent.md must be rejected, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDiscoverTasks(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
outside := t.TempDir()
|
||||
for _, n := range []string{"agent.md", "memoria.md", "README.md", "02-b.md", "01-a.md", "03-c.MD", "notes.txt"} {
|
||||
write(t, filepath.Join(dir, n), "content "+n)
|
||||
}
|
||||
// Las tareas en subdirectorios nunca son instrucciones.
|
||||
write(t, filepath.Join(dir, "subdir", "00-hidden.md"), "no")
|
||||
// Un symlink a un .md externo tampoco.
|
||||
write(t, filepath.Join(outside, "evil.md"), "rm -rf /")
|
||||
symlinkOK := os.Symlink(filepath.Join(outside, "evil.md"), filepath.Join(dir, "04-evil.md")) == nil
|
||||
|
||||
p, _ := Open(dir)
|
||||
tasks, skipped, missing, err := p.DiscoverTasks("agent.md", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got := names(tasks); got != "01-a.md,02-b.md,03-c.MD" {
|
||||
t.Errorf("tasks = %s", got)
|
||||
}
|
||||
if symlinkOK && (len(skipped) != 1 || skipped[0] != "04-evil.md") {
|
||||
t.Errorf("skipped = %v", skipped)
|
||||
}
|
||||
if len(missing) != 0 {
|
||||
t.Errorf("missing = %v", missing)
|
||||
}
|
||||
if tasks[0].Content != "content 01-a.md" {
|
||||
t.Errorf("content = %q", tasks[0].Content)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDiscoverTasksExplicitOrder(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
for _, n := range []string{"Agent.md", "a.md", "b.md", "c.md"} {
|
||||
write(t, filepath.Join(dir, n), n)
|
||||
}
|
||||
p, _ := Open(dir)
|
||||
tasks, _, missing, err := p.DiscoverTasks("Agent.md", []string{"c.md", "zz.md", "a.md"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got := names(tasks); got != "c.md,a.md,b.md" {
|
||||
t.Errorf("tasks = %s", got)
|
||||
}
|
||||
if len(missing) != 1 || missing[0] != "zz.md" {
|
||||
t.Errorf("missing = %v", missing)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnsureMemoryFile(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
p, _ := Open(dir)
|
||||
created, _, err := p.EnsureMemoryFile()
|
||||
if err != nil || !created {
|
||||
t.Fatalf("created=%v err=%v", created, err)
|
||||
}
|
||||
st, err := os.Stat(p.Path(MemoryFile))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if st.Mode().Perm() != 0o600 {
|
||||
t.Errorf("perm = %o, want 600", st.Mode().Perm())
|
||||
}
|
||||
// Segunda llamada: no recrea ni reescribe.
|
||||
if err := os.WriteFile(p.Path(MemoryFile), []byte("keep"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.Chmod(p.Path(MemoryFile), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
created, tooOpen, err := p.EnsureMemoryFile()
|
||||
if err != nil || created || !tooOpen {
|
||||
t.Fatalf("created=%v tooOpen=%v err=%v", created, tooOpen, err)
|
||||
}
|
||||
if data, _ := os.ReadFile(p.Path(MemoryFile)); string(data) != "keep" {
|
||||
t.Error("existing memoria.md must not be modified")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnsureWorkDirs(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
p, _ := Open(dir)
|
||||
if err := p.EnsureWorkDirs(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
st, err := os.Stat(filepath.Join(dir, WorkDir, "downloads"))
|
||||
if err != nil || !st.IsDir() || st.Mode().Perm() != 0o700 {
|
||||
t.Fatalf("workdir: %v %v", st, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckSecurity(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
os.Chmod(dir, 0o700)
|
||||
write(t, filepath.Join(dir, "agent.md"), "x")
|
||||
write(t, filepath.Join(dir, "init.conf"), "API_KEY=x")
|
||||
os.Chmod(filepath.Join(dir, "init.conf"), 0o600)
|
||||
os.Chmod(filepath.Join(dir, "agent.md"), 0o644)
|
||||
p, _ := Open(dir)
|
||||
for _, f := range p.CheckSecurity("agent.md", nil, true) {
|
||||
if f.Fatal {
|
||||
t.Errorf("unexpected fatal finding: %s", f.Message)
|
||||
}
|
||||
}
|
||||
|
||||
os.Chmod(filepath.Join(dir, "agent.md"), 0o666)
|
||||
fatal := false
|
||||
for _, f := range p.CheckSecurity("agent.md", nil, true) {
|
||||
fatal = fatal || f.Fatal && strings.Contains(f.Message, "writable by any user")
|
||||
}
|
||||
if !fatal {
|
||||
t.Error("world-writable agent.md must be fatal")
|
||||
}
|
||||
os.Chmod(filepath.Join(dir, "agent.md"), 0o644)
|
||||
|
||||
os.Chmod(filepath.Join(dir, "init.conf"), 0o644)
|
||||
warned := false
|
||||
for _, f := range p.CheckSecurity("agent.md", nil, true) {
|
||||
warned = warned || strings.Contains(f.Message, "chmod 600 init.conf")
|
||||
}
|
||||
if !warned {
|
||||
t.Error("readable init.conf with API key must warn")
|
||||
}
|
||||
|
||||
shared := t.TempDir()
|
||||
os.Chmod(shared, 0o1777)
|
||||
ps, _ := Open(shared)
|
||||
fatal = false
|
||||
for _, f := range ps.CheckSecurity("agent.md", nil, false) {
|
||||
fatal = fatal || f.Fatal
|
||||
}
|
||||
if !fatal {
|
||||
t.Error("PROJECT_ROOT in a shared /tmp-like directory must be fatal")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLock(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
p, _ := Open(dir)
|
||||
p.EnsureWorkDirs()
|
||||
unlock, err := p.Lock()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// flock es por descriptor abierto: un segundo Open en el mismo proceso
|
||||
// simula otra instancia.
|
||||
if _, err := p.Lock(); !errors.Is(err, ErrLocked) || !strings.Contains(err.Error(), "pid") {
|
||||
t.Errorf("second lock: %v", err)
|
||||
}
|
||||
unlock()
|
||||
unlock2, err := p.Lock()
|
||||
if err != nil {
|
||||
t.Fatalf("lock after unlock: %v", err)
|
||||
}
|
||||
unlock2()
|
||||
}
|
||||
|
||||
func TestInstructionSizeLimit(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
write(t, filepath.Join(dir, "agent.md"), strings.Repeat("x", MaxInstructionSize+1))
|
||||
write(t, filepath.Join(dir, "01-big.md"), strings.Repeat("x", MaxInstructionSize+1))
|
||||
write(t, filepath.Join(dir, "02-ok.md"), "ok")
|
||||
p, _ := Open(dir)
|
||||
if _, err := p.ReadInstructionFile("agent.md"); err == nil {
|
||||
t.Error("oversized agent.md must be rejected")
|
||||
}
|
||||
tasks, skipped, _, _ := p.DiscoverTasks("agent.md", nil)
|
||||
if len(tasks) != 1 || len(skipped) != 1 {
|
||||
t.Errorf("tasks=%d skipped=%v", len(tasks), skipped)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user