From 565acfb07b06ddf4b17cfea2e93eced8264e46c1 Mon Sep 17 00:00:00 2001 From: Levi Neely Date: Mon, 10 Aug 2026 19:15:56 +0200 Subject: [PATCH] toolsrv: improve integration tests to match real usage - Parse ToolResult JSON the same way agent/runtime.go does - Verify Content structure, Type field, and IsError flag - Add TestIntegration_ToolExecutionError for failed commands - Tests now verify the wire format matches what olliesrv expects --- toolsrv/integration_test.go | 67 +++++++++++++++++++++++++++++++++---- 1 file changed, 61 insertions(+), 6 deletions(-) diff --git a/toolsrv/integration_test.go b/toolsrv/integration_test.go index 4a248fc..0a93a5e 100644 --- a/toolsrv/integration_test.go +++ b/toolsrv/integration_test.go @@ -241,7 +241,7 @@ func TestIntegration_ToolExecution(t *testing.T) { t.Fatal("shell tool not found after loading") } - // Execute a tool + // Execute a tool - this mimics what agent/runtime.go does ctx := context.Background() args := json.RawMessage(`{"cmd": "echo hello from test"}`) result, err := conn.CallTool(ctx, "shell", args) @@ -249,12 +249,67 @@ func TestIntegration_ToolExecution(t *testing.T) { t.Fatalf("CallTool failed: %v", err) } - // Result should be JSON containing the output - resultStr := string(result) - if !strings.Contains(resultStr, "hello from test") { - t.Errorf("unexpected result: %s", resultStr) + // Parse the result the same way agent/runtime.go does + var toolResult ToolResult + if err := json.Unmarshal(result, &toolResult); err != nil { + t.Fatalf("failed to parse ToolResult: %v (raw: %s)", err, string(result)) } - t.Logf("Tool result: %s", resultStr) + + // Verify structure + if len(toolResult.Content) == 0 { + t.Fatal("ToolResult.Content is empty") + } + if toolResult.Content[0].Type != "text" { + t.Errorf("expected type 'text', got %q", toolResult.Content[0].Type) + } + if !strings.Contains(toolResult.Content[0].Text, "hello from test") { + t.Errorf("output doesn't contain expected text: %q", toolResult.Content[0].Text) + } + if toolResult.IsError { + t.Error("ToolResult.IsError should be false") + } + + t.Logf("Tool result: %+v", toolResult) +} + +func TestIntegration_ToolExecutionError(t *testing.T) { + socketPath, cleanup := startTestServer(t) + defer cleanup() + + conn, err := Dial(socketPath, "test-secret-exec-err") + if err != nil { + t.Fatalf("Dial failed: %v", err) + } + defer conn.Close() + + // Load the shell tool + if err := conn.LoadTool("shell"); err != nil { + t.Fatalf("LoadTool failed: %v", err) + } + + // Execute a command that fails + ctx := context.Background() + args := json.RawMessage(`{"cmd": "exit 42"}`) + result, err := conn.CallTool(ctx, "shell", args) + if err != nil { + t.Fatalf("CallTool failed: %v", err) + } + + // Parse the result + var toolResult ToolResult + if err := json.Unmarshal(result, &toolResult); err != nil { + t.Fatalf("failed to parse ToolResult: %v (raw: %s)", err, string(result)) + } + + // Should have exit code in output + if len(toolResult.Content) == 0 { + t.Fatal("ToolResult.Content is empty") + } + if !strings.Contains(toolResult.Content[0].Text, "exit: 42") { + t.Errorf("output doesn't contain exit code: %q", toolResult.Content[0].Text) + } + + t.Logf("Error tool result: %+v", toolResult) } func TestIntegration_ProcessKeeperReconnect(t *testing.T) {