Files
egutierrez 3638529468 feat: loader y executor de skills en shell
Agregar componentes impuros para manejo de skills en shell/skills/:

Loader (filesystem I/O):
- LoadMeta(): carga metadata de todas las skills
- LoadSkill(): carga skill completa con instrucciones
- ReadResource(): lee recursos con path traversal protection
- Parsing de SKILL.md con frontmatter YAML

Executor (script execution):
- Ejecucion segura de scripts con allowlist de interpreters
- Timeout obligatorio por script
- Inferencia de interpreter desde extension
- Proteccion contra scripts maliciosos

Incluye tests completos con tmpdir para loader y executor.

Arquitectura: impure shell, todo I/O aislado en shell/.
2026-03-08 22:13:12 +00:00

128 lines
3.1 KiB
Go

package skills
import (
"context"
"os"
"path/filepath"
"strings"
"testing"
"time"
)
func TestExecutor(t *testing.T) {
tmpDir := t.TempDir()
// Create a simple bash script
scriptPath := filepath.Join(tmpDir, "test.sh")
scriptContent := `#!/bin/bash
echo "Hello from script"
echo "Args: $@"
`
if err := os.WriteFile(scriptPath, []byte(scriptContent), 0755); err != nil {
t.Fatal(err)
}
// Create a script that times out
timeoutScriptPath := filepath.Join(tmpDir, "timeout.sh")
timeoutContent := `#!/bin/bash
sleep 10
`
if err := os.WriteFile(timeoutScriptPath, []byte(timeoutContent), 0755); err != nil {
t.Fatal(err)
}
// Create a failing script
failScriptPath := filepath.Join(tmpDir, "fail.sh")
failContent := `#!/bin/bash
exit 1
`
if err := os.WriteFile(failScriptPath, []byte(failContent), 0755); err != nil {
t.Fatal(err)
}
executor := NewExecutor([]string{"bash", "sh"}, 2*time.Second)
// Test successful execution
t.Run("successful_execution", func(t *testing.T) {
ctx := context.Background()
output, err := executor.Run(ctx, scriptPath, []string{"arg1", "arg2"})
if err != nil {
t.Fatalf("Run failed: %v", err)
}
if !strings.Contains(output, "Hello from script") {
t.Errorf("expected 'Hello from script' in output, got: %q", output)
}
if !strings.Contains(output, "Args: arg1 arg2") {
t.Errorf("expected 'Args: arg1 arg2' in output, got: %q", output)
}
})
// Test timeout
t.Run("timeout", func(t *testing.T) {
ctx := context.Background()
_, err := executor.Run(ctx, timeoutScriptPath, nil)
if err == nil {
t.Error("expected timeout error")
}
if !strings.Contains(err.Error(), "timeout") {
t.Errorf("expected timeout error, got: %v", err)
}
})
// Test script failure
t.Run("script_failure", func(t *testing.T) {
ctx := context.Background()
_, err := executor.Run(ctx, failScriptPath, nil)
if err == nil {
t.Error("expected script failure error")
}
})
// Test disallowed interpreter
t.Run("disallowed_interpreter", func(t *testing.T) {
pyScriptPath := filepath.Join(tmpDir, "test.py")
pyContent := `#!/usr/bin/env python3
print("hello")
`
if err := os.WriteFile(pyScriptPath, []byte(pyContent), 0755); err != nil {
t.Fatal(err)
}
ctx := context.Background()
_, err := executor.Run(ctx, pyScriptPath, nil)
if err == nil {
t.Error("expected error for disallowed interpreter")
}
if !strings.Contains(err.Error(), "not allowed") {
t.Errorf("expected 'not allowed' error, got: %v", err)
}
})
// Test allowed python interpreter
t.Run("allowed_python", func(t *testing.T) {
pyExecutor := NewExecutor([]string{"python3"}, 2*time.Second)
pyScriptPath := filepath.Join(tmpDir, "hello.py")
pyContent := `#!/usr/bin/env python3
print("Hello from Python")
`
if err := os.WriteFile(pyScriptPath, []byte(pyContent), 0755); err != nil {
t.Fatal(err)
}
ctx := context.Background()
output, err := pyExecutor.Run(ctx, pyScriptPath, nil)
if err != nil {
t.Fatalf("Run failed: %v", err)
}
if !strings.Contains(output, "Hello from Python") {
t.Errorf("expected 'Hello from Python' in output, got: %q", output)
}
})
}