- Update all Tool implementations to return *ToolResult instead of (string, error) - ShellTool: returns UserResult for command output, ErrorResult for failures - SpawnTool: returns NewToolResult on success, ErrorResult on failure - WebTool: returns ToolResult with ForUser=content, ForLLM=summary - EditTool: returns SilentResult for silent edits, ErrorResult on failure - FilesystemTool: returns SilentResult/NewToolResult for operations, ErrorResult on failure - Temporarily disable cronTool in main.go (will be re-enabled in US-016) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
230 lines
5.6 KiB
Go
230 lines
5.6 KiB
Go
package tools
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"testing"
|
|
)
|
|
|
|
func TestNewToolResult(t *testing.T) {
|
|
result := NewToolResult("test content")
|
|
|
|
if result.ForLLM != "test content" {
|
|
t.Errorf("Expected ForLLM 'test content', got '%s'", result.ForLLM)
|
|
}
|
|
if result.Silent {
|
|
t.Error("Expected Silent to be false")
|
|
}
|
|
if result.IsError {
|
|
t.Error("Expected IsError to be false")
|
|
}
|
|
if result.Async {
|
|
t.Error("Expected Async to be false")
|
|
}
|
|
}
|
|
|
|
func TestSilentResult(t *testing.T) {
|
|
result := SilentResult("silent operation")
|
|
|
|
if result.ForLLM != "silent operation" {
|
|
t.Errorf("Expected ForLLM 'silent operation', got '%s'", result.ForLLM)
|
|
}
|
|
if !result.Silent {
|
|
t.Error("Expected Silent to be true")
|
|
}
|
|
if result.IsError {
|
|
t.Error("Expected IsError to be false")
|
|
}
|
|
if result.Async {
|
|
t.Error("Expected Async to be false")
|
|
}
|
|
}
|
|
|
|
func TestAsyncResult(t *testing.T) {
|
|
result := AsyncResult("async task started")
|
|
|
|
if result.ForLLM != "async task started" {
|
|
t.Errorf("Expected ForLLM 'async task started', got '%s'", result.ForLLM)
|
|
}
|
|
if result.Silent {
|
|
t.Error("Expected Silent to be false")
|
|
}
|
|
if result.IsError {
|
|
t.Error("Expected IsError to be false")
|
|
}
|
|
if !result.Async {
|
|
t.Error("Expected Async to be true")
|
|
}
|
|
}
|
|
|
|
func TestErrorResult(t *testing.T) {
|
|
result := ErrorResult("operation failed")
|
|
|
|
if result.ForLLM != "operation failed" {
|
|
t.Errorf("Expected ForLLM 'operation failed', got '%s'", result.ForLLM)
|
|
}
|
|
if result.Silent {
|
|
t.Error("Expected Silent to be false")
|
|
}
|
|
if !result.IsError {
|
|
t.Error("Expected IsError to be true")
|
|
}
|
|
if result.Async {
|
|
t.Error("Expected Async to be false")
|
|
}
|
|
}
|
|
|
|
func TestUserResult(t *testing.T) {
|
|
content := "user visible message"
|
|
result := UserResult(content)
|
|
|
|
if result.ForLLM != content {
|
|
t.Errorf("Expected ForLLM '%s', got '%s'", content, result.ForLLM)
|
|
}
|
|
if result.ForUser != content {
|
|
t.Errorf("Expected ForUser '%s', got '%s'", content, result.ForUser)
|
|
}
|
|
if result.Silent {
|
|
t.Error("Expected Silent to be false")
|
|
}
|
|
if result.IsError {
|
|
t.Error("Expected IsError to be false")
|
|
}
|
|
if result.Async {
|
|
t.Error("Expected Async to be false")
|
|
}
|
|
}
|
|
|
|
func TestToolResultJSONSerialization(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
result *ToolResult
|
|
}{
|
|
{
|
|
name: "basic result",
|
|
result: NewToolResult("basic content"),
|
|
},
|
|
{
|
|
name: "silent result",
|
|
result: SilentResult("silent content"),
|
|
},
|
|
{
|
|
name: "async result",
|
|
result: AsyncResult("async content"),
|
|
},
|
|
{
|
|
name: "error result",
|
|
result: ErrorResult("error content"),
|
|
},
|
|
{
|
|
name: "user result",
|
|
result: UserResult("user content"),
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Marshal to JSON
|
|
data, err := json.Marshal(tt.result)
|
|
if err != nil {
|
|
t.Fatalf("Failed to marshal: %v", err)
|
|
}
|
|
|
|
// Unmarshal back
|
|
var decoded ToolResult
|
|
if err := json.Unmarshal(data, &decoded); err != nil {
|
|
t.Fatalf("Failed to unmarshal: %v", err)
|
|
}
|
|
|
|
// Verify fields match (Err should be excluded)
|
|
if decoded.ForLLM != tt.result.ForLLM {
|
|
t.Errorf("ForLLM mismatch: got '%s', want '%s'", decoded.ForLLM, tt.result.ForLLM)
|
|
}
|
|
if decoded.ForUser != tt.result.ForUser {
|
|
t.Errorf("ForUser mismatch: got '%s', want '%s'", decoded.ForUser, tt.result.ForUser)
|
|
}
|
|
if decoded.Silent != tt.result.Silent {
|
|
t.Errorf("Silent mismatch: got %v, want %v", decoded.Silent, tt.result.Silent)
|
|
}
|
|
if decoded.IsError != tt.result.IsError {
|
|
t.Errorf("IsError mismatch: got %v, want %v", decoded.IsError, tt.result.IsError)
|
|
}
|
|
if decoded.Async != tt.result.Async {
|
|
t.Errorf("Async mismatch: got %v, want %v", decoded.Async, tt.result.Async)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestToolResultWithErrors(t *testing.T) {
|
|
err := errors.New("underlying error")
|
|
result := ErrorResult("error message").WithError(err)
|
|
|
|
if result.Err == nil {
|
|
t.Error("Expected Err to be set")
|
|
}
|
|
if result.Err.Error() != "underlying error" {
|
|
t.Errorf("Expected Err message 'underlying error', got '%s'", result.Err.Error())
|
|
}
|
|
|
|
// Verify Err is not serialized
|
|
data, marshalErr := json.Marshal(result)
|
|
if marshalErr != nil {
|
|
t.Fatalf("Failed to marshal: %v", marshalErr)
|
|
}
|
|
|
|
var decoded ToolResult
|
|
if unmarshalErr := json.Unmarshal(data, &decoded); unmarshalErr != nil {
|
|
t.Fatalf("Failed to unmarshal: %v", unmarshalErr)
|
|
}
|
|
|
|
if decoded.Err != nil {
|
|
t.Error("Expected Err to be nil after JSON round-trip (should not be serialized)")
|
|
}
|
|
}
|
|
|
|
func TestToolResultJSONStructure(t *testing.T) {
|
|
result := UserResult("test content")
|
|
|
|
data, err := json.Marshal(result)
|
|
if err != nil {
|
|
t.Fatalf("Failed to marshal: %v", err)
|
|
}
|
|
|
|
// Verify JSON structure
|
|
var parsed map[string]interface{}
|
|
if err := json.Unmarshal(data, &parsed); err != nil {
|
|
t.Fatalf("Failed to parse JSON: %v", err)
|
|
}
|
|
|
|
// Check expected keys exist
|
|
if _, ok := parsed["for_llm"]; !ok {
|
|
t.Error("Expected 'for_llm' key in JSON")
|
|
}
|
|
if _, ok := parsed["for_user"]; !ok {
|
|
t.Error("Expected 'for_user' key in JSON")
|
|
}
|
|
if _, ok := parsed["silent"]; !ok {
|
|
t.Error("Expected 'silent' key in JSON")
|
|
}
|
|
if _, ok := parsed["is_error"]; !ok {
|
|
t.Error("Expected 'is_error' key in JSON")
|
|
}
|
|
if _, ok := parsed["async"]; !ok {
|
|
t.Error("Expected 'async' key in JSON")
|
|
}
|
|
|
|
// Check that 'err' is NOT present (it should have json:"-" tag)
|
|
if _, ok := parsed["err"]; ok {
|
|
t.Error("Expected 'err' key to be excluded from JSON")
|
|
}
|
|
|
|
// Verify values
|
|
if parsed["for_llm"] != "test content" {
|
|
t.Errorf("Expected for_llm 'test content', got %v", parsed["for_llm"])
|
|
}
|
|
if parsed["silent"] != false {
|
|
t.Errorf("Expected silent false, got %v", parsed["silent"])
|
|
}
|
|
}
|