3638529468
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/.
128 lines
3.1 KiB
Go
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)
|
|
}
|
|
})
|
|
}
|