From c9076e84c498fb39c3a0947b23ed6bc439d1931c Mon Sep 17 00:00:00 2001 From: Steve Dudenhoeffer Date: Sun, 23 Aug 2026 02:11:27 -0400 Subject: [PATCH] Address #130 review: one toolCaller helper for the tool tests The call/mustCall closures were copied between TestRecordKeepingTools and TestCatalogAndGardenTools; both now use a file-level toolCaller. The other notes are left as they are: the 'nothing to change' guard enumerates the args on purpose (it is the tool's own contract, next to the struct it checks), and wrapping a sentinel with %w is how every readable refusal in this package is built. Co-Authored-By: Claude Fable 5 --- internal/agent/tools_test.go | 62 +++++++++++++++++------------------- 1 file changed, 30 insertions(+), 32 deletions(-) diff --git a/internal/agent/tools_test.go b/internal/agent/tools_test.go index 6e5cffd..1a8b0b5 100644 --- a/internal/agent/tools_test.go +++ b/internal/agent/tools_test.go @@ -123,6 +123,34 @@ func TestToolboxScenario(t *testing.T) { } } +// toolCaller returns the two ways a test drives a toolbox: call executes a +// tool with JSON-encoded args and hands back the raw result (for asserting on +// refusals), and mustCall fails the test on a tool error and decodes the +// result into `into` when one is given. +func toolCaller(t *testing.T, ctx context.Context, box *llm.Toolbox) ( + call func(name string, args any) llm.ToolResult, + mustCall func(name string, args any, into any), +) { + t.Helper() + call = func(name string, args any) llm.ToolResult { + t.Helper() + return box.Execute(ctx, llm.ToolCall{ID: "1", Name: name, Arguments: mustJSON(t, args)}) + } + mustCall = func(name string, args any, into any) { + t.Helper() + res := call(name, args) + if res.IsError { + t.Fatalf("%s: %s", name, res.Content) + } + if into != nil { + if err := json.Unmarshal([]byte(res.Content), into); err != nil { + t.Fatalf("decode %s: %v (%s)", name, err, res.Content) + } + } + } + return call, mustCall +} + func mustJSON(t *testing.T, v any) json.RawMessage { t.Helper() b, err := json.Marshal(v) @@ -750,22 +778,7 @@ func TestRecordKeepingTools(t *testing.T) { svc, owner := newAgentTestService(t) box := NewToolbox(svc, owner, "2026-08-23") - call := func(name string, args any) llm.ToolResult { - t.Helper() - return box.Execute(ctx, llm.ToolCall{ID: "1", Name: name, Arguments: mustJSON(t, args)}) - } - mustCall := func(name string, args any, into any) { - t.Helper() - res := call(name, args) - if res.IsError { - t.Fatalf("%s: %s", name, res.Content) - } - if into != nil { - if err := json.Unmarshal([]byte(res.Content), into); err != nil { - t.Fatalf("decode %s: %v (%s)", name, err, res.Content) - } - } - } + call, mustCall := toolCaller(t, ctx, box) g, err := svc.CreateGarden(ctx, owner, service.GardenInput{Name: "Home", WidthCM: 1200, HeightCM: 800, Notes: "Zone 6a."}) if err != nil { @@ -940,22 +953,7 @@ func TestCatalogAndGardenTools(t *testing.T) { svc, owner := newAgentTestService(t) box := NewToolbox(svc, owner, "2026-08-23") - call := func(name string, args any) llm.ToolResult { - t.Helper() - return box.Execute(ctx, llm.ToolCall{ID: "1", Name: name, Arguments: mustJSON(t, args)}) - } - mustCall := func(name string, args any, into any) { - t.Helper() - res := call(name, args) - if res.IsError { - t.Fatalf("%s: %s", name, res.Content) - } - if into != nil { - if err := json.Unmarshal([]byte(res.Content), into); err != nil { - t.Fatalf("decode %s: %v (%s)", name, err, res.Content) - } - } - } + call, mustCall := toolCaller(t, ctx, box) // --- create_garden: defaults, then an imperial one with notes. var g domain.Garden