orca.ai/pkg/tool/builtin_test.go
大森 6b94476347 Initial commit: Orca Agent Framework
Core features:
- Microkernel architecture with Actor model
- Session management with JSONL persistence
- Tool system (5 built-in tools)
- Skill system with SKILL.md parsing
- Sandbox security execution
- Ollama integration with gemma4:e4b
- Prompt-based tool calling (compatible with native function calling)
- REPL interface

11 packages, all tests passing
2026-05-08 00:55:48 +08:00

400 lines
9.7 KiB
Go

package tool
import (
"context"
"os"
"path/filepath"
"strings"
"testing"
"github.com/orca/orca/pkg/sandbox"
)
func TestExecTool(t *testing.T) {
sb := sandbox.NewProcessSandbox()
execT := NewExecTool(sb)
if execT.Name() != "exec" {
t.Errorf("expected name 'exec', got %q", execT.Name())
}
if execT.Description() == "" {
t.Error("expected non-empty description")
}
params := execT.Parameters()
if _, ok := params["command"]; !ok {
t.Error("expected 'command' parameter")
}
}
func TestExecToolExecute(t *testing.T) {
sb := sandbox.NewProcessSandbox()
execT := NewExecTool(sb)
ctx := context.Background()
result, err := execT.Execute(ctx, map[string]interface{}{
"command": "echo hello",
})
if err != nil {
t.Fatalf("Execute failed: %v", err)
}
if !result.Success {
t.Errorf("expected success, got error: %s", result.Error)
}
data := result.Data.(map[string]interface{})
stdout := data["stdout"].(string)
if strings.TrimSpace(stdout) != "hello" {
t.Errorf("expected stdout 'hello', got %q", stdout)
}
}
func TestExecToolMissingCommand(t *testing.T) {
sb := sandbox.NewProcessSandbox()
execT := NewExecTool(sb)
ctx := context.Background()
result, err := execT.Execute(ctx, map[string]interface{}{})
if err != nil {
t.Fatalf("Execute should not error for invalid args: %v", err)
}
if result.Success {
t.Error("expected failure for missing command")
}
if !strings.Contains(result.Error, "command") {
t.Errorf("error should mention 'command', got: %s", result.Error)
}
}
func TestReadFileTool(t *testing.T) {
readT := NewReadFileTool()
if readT.Name() != "read_file" {
t.Errorf("expected name 'read_file', got %q", readT.Name())
}
}
func TestReadFileToolExecute(t *testing.T) {
// Create a temp file
tmpFile, err := os.CreateTemp("", "orca-test-*")
if err != nil {
t.Fatalf("failed to create temp file: %v", err)
}
content := "test content\nline 2\n"
if _, err := tmpFile.WriteString(content); err != nil {
t.Fatalf("failed to write temp file: %v", err)
}
tmpFile.Close()
defer os.Remove(tmpFile.Name())
readT := NewReadFileTool()
ctx := context.Background()
result, err := readT.Execute(ctx, map[string]interface{}{
"path": tmpFile.Name(),
})
if err != nil {
t.Fatalf("Execute failed: %v", err)
}
if !result.Success {
t.Errorf("expected success, got error: %s", result.Error)
}
data := result.Data.(map[string]interface{})
gotContent := data["content"].(string)
if gotContent != content {
t.Errorf("expected content %q, got %q", content, gotContent)
}
}
func TestReadFileToolMissingPath(t *testing.T) {
readT := NewReadFileTool()
ctx := context.Background()
result, err := readT.Execute(ctx, map[string]interface{}{})
if err != nil {
t.Fatalf("Execute should not error for invalid args: %v", err)
}
if result.Success {
t.Error("expected failure for missing path")
}
}
func TestReadFileToolNonexistent(t *testing.T) {
readT := NewReadFileTool()
ctx := context.Background()
result, err := readT.Execute(ctx, map[string]interface{}{
"path": "/nonexistent/path/that/does/not/exist.txt",
})
if err != nil {
t.Fatalf("Execute should not error for missing file: %v", err)
}
if result.Success {
t.Error("expected failure for nonexistent file")
}
}
func TestWriteFileTool(t *testing.T) {
writeT := NewWriteFileTool()
if writeT.Name() != "write_file" {
t.Errorf("expected name 'write_file', got %q", writeT.Name())
}
}
func TestWriteFileToolExecute(t *testing.T) {
tmpDir, err := os.MkdirTemp("", "orca-write-test-*")
if err != nil {
t.Fatalf("failed to create temp dir: %v", err)
}
defer os.RemoveAll(tmpDir)
testPath := filepath.Join(tmpDir, "nested", "test.txt")
content := "hello world"
writeT := NewWriteFileTool()
ctx := context.Background()
result, err := writeT.Execute(ctx, map[string]interface{}{
"path": testPath,
"content": content,
})
if err != nil {
t.Fatalf("Execute failed: %v", err)
}
if !result.Success {
t.Errorf("expected success, got error: %s", result.Error)
}
// Verify the file was written
data, err := os.ReadFile(testPath)
if err != nil {
t.Fatalf("failed to read written file: %v", err)
}
if string(data) != content {
t.Errorf("expected content %q, got %q", content, string(data))
}
}
func TestWriteFileToolMissingArgs(t *testing.T) {
writeT := NewWriteFileTool()
ctx := context.Background()
// Missing path
result, err := writeT.Execute(ctx, map[string]interface{}{
"content": "test",
})
if err != nil {
t.Fatalf("Execute should not error for invalid args: %v", err)
}
if result.Success {
t.Error("expected failure for missing path")
}
// Missing content
result, err = writeT.Execute(ctx, map[string]interface{}{
"path": "/tmp/test.txt",
})
if err != nil {
t.Fatalf("Execute should not error for invalid args: %v", err)
}
if result.Success {
t.Error("expected failure for missing content")
}
}
func TestListDirTool(t *testing.T) {
listT := NewListDirTool()
if listT.Name() != "list_dir" {
t.Errorf("expected name 'list_dir', got %q", listT.Name())
}
}
func TestListDirToolExecute(t *testing.T) {
tmpDir, err := os.MkdirTemp("", "orca-list-test-*")
if err != nil {
t.Fatalf("failed to create temp dir: %v", err)
}
defer os.RemoveAll(tmpDir)
// Create some test files
os.WriteFile(filepath.Join(tmpDir, "a.txt"), []byte("a"), 0644)
os.WriteFile(filepath.Join(tmpDir, "b.txt"), []byte("bb"), 0644)
os.Mkdir(filepath.Join(tmpDir, "subdir"), 0755)
listT := NewListDirTool()
ctx := context.Background()
result, err := listT.Execute(ctx, map[string]interface{}{
"path": tmpDir,
})
if err != nil {
t.Fatalf("Execute failed: %v", err)
}
if !result.Success {
t.Errorf("expected success, got error: %s", result.Error)
}
data := result.Data.(map[string]interface{})
count := data["count"].(int)
if count != 3 {
t.Errorf("expected 3 entries, got %d", count)
}
}
func TestListDirToolRecursive(t *testing.T) {
tmpDir, err := os.MkdirTemp("", "orca-list-rec-*")
if err != nil {
t.Fatalf("failed to create temp dir: %v", err)
}
defer os.RemoveAll(tmpDir)
os.MkdirAll(filepath.Join(tmpDir, "a", "b"), 0755)
os.WriteFile(filepath.Join(tmpDir, "a", "b", "c.txt"), []byte("c"), 0644)
listT := NewListDirTool()
ctx := context.Background()
result, err := listT.Execute(ctx, map[string]interface{}{
"path": tmpDir,
"recursive": true,
})
if err != nil {
t.Fatalf("Execute failed: %v", err)
}
if !result.Success {
t.Errorf("expected success, got error: %s", result.Error)
}
data := result.Data.(map[string]interface{})
entries := data["entries"].([]map[string]interface{})
if len(entries) < 2 {
t.Errorf("expected at least 2 recursive entries, got %d", len(entries))
}
}
func TestListDirToolNonexistent(t *testing.T) {
listT := NewListDirTool()
ctx := context.Background()
result, err := listT.Execute(ctx, map[string]interface{}{
"path": "/nonexistent/path",
})
if err != nil {
t.Fatalf("Execute should not error for missing path: %v", err)
}
if result.Success {
t.Error("expected failure for nonexistent path")
}
}
func TestSearchFilesTool(t *testing.T) {
searchT := NewSearchFilesTool()
if searchT.Name() != "search_files" {
t.Errorf("expected name 'search_files', got %q", searchT.Name())
}
}
func TestSearchFilesToolExecute(t *testing.T) {
tmpDir, err := os.MkdirTemp("", "orca-search-test-*")
if err != nil {
t.Fatalf("failed to create temp dir: %v", err)
}
defer os.RemoveAll(tmpDir)
// Create test files
os.WriteFile(filepath.Join(tmpDir, "findme.go"), []byte("package main\nfunc hello() {\n}\n"), 0644)
os.WriteFile(filepath.Join(tmpDir, "other.py"), []byte("def world():\n pass\n"), 0644)
searchT := NewSearchFilesTool()
ctx := context.Background()
result, err := searchT.Execute(ctx, map[string]interface{}{
"pattern": "hello",
"path": tmpDir,
})
if err != nil {
t.Fatalf("Execute failed: %v", err)
}
if !result.Success {
t.Errorf("expected success, got error: %s", result.Error)
}
data := result.Data.(map[string]interface{})
count := data["count"].(int)
if count != 1 {
t.Errorf("expected 1 match for 'hello', got %d", count)
}
}
func TestSearchFilesToolNoMatch(t *testing.T) {
tmpDir, err := os.MkdirTemp("", "orca-search-nomatch-*")
if err != nil {
t.Fatalf("failed to create temp dir: %v", err)
}
defer os.RemoveAll(tmpDir)
os.WriteFile(filepath.Join(tmpDir, "test.txt"), []byte("nothing here"), 0644)
searchT := NewSearchFilesTool()
ctx := context.Background()
result, err := searchT.Execute(ctx, map[string]interface{}{
"pattern": "nonexistent-pattern-xyz",
"path": tmpDir,
})
if err != nil {
t.Fatalf("Execute failed: %v", err)
}
if !result.Success {
t.Errorf("expected success even with no matches, got error: %s", result.Error)
}
data := result.Data.(map[string]interface{})
count := data["count"].(int)
if count != 0 {
t.Errorf("expected 0 matches, got %d", count)
}
}
func TestSearchFilesToolMissingPattern(t *testing.T) {
searchT := NewSearchFilesTool()
ctx := context.Background()
result, err := searchT.Execute(ctx, map[string]interface{}{})
if err != nil {
t.Fatalf("Execute should not error for invalid args: %v", err)
}
if result.Success {
t.Error("expected failure for missing pattern")
}
}
func TestToolInterfaceSatisfied(t *testing.T) {
sb := sandbox.NewProcessSandbox()
tools := []Tool{
NewExecTool(sb),
NewReadFileTool(),
NewWriteFileTool(),
NewListDirTool(),
NewSearchFilesTool(),
}
names := []string{"exec", "read_file", "write_file", "list_dir", "search_files"}
for i, tool := range tools {
if tool.Name() != names[i] {
t.Errorf("expected name %q, got %q", names[i], tool.Name())
}
if tool.Description() == "" {
t.Errorf("tool %q has empty description", names[i])
}
if tool.Parameters() == nil {
t.Errorf("tool %q has nil parameters", names[i])
}
}
}