package agenttool import ( "context" "encoding/json" "testing" "github.com/smallnest/pigo/internal/agentcore" ) // stubTool is a minimal AgentTool for registry tests. type stubTool struct { name string schema string } func (s stubTool) Name() string { return s.name } func (s stubTool) Description() string { return "stub" } func (s stubTool) Schema() json.RawMessage { return json.RawMessage(s.schema) } func (s stubTool) ExecutionMode() agentcore.ToolExecutionMode { return agentcore.ToolExecutionParallel } func (s stubTool) Execute(ctx context.Context, id string, args json.RawMessage, onUpdate agentcore.ToolUpdateFunc) (agentcore.AgentToolResult, error) { return agentcore.AgentToolResult{Content: agentcore.ContentList{agentcore.NewTextContent("ok")}}, nil } const personSchema = `{ "type": "object", "properties": { "name": {"type": "string"}, "age": {"type": "integer"} }, "required": ["name"], "additionalProperties": false }` func newTestRegistry(t *testing.T) *ToolRegistry { t.Helper() r := NewToolRegistry() if err := r.Register(stubTool{name: "person", schema: personSchema}); err != nil { t.Fatalf("register: %v", err) } return r } func TestRegistryRegisterAndGet(t *testing.T) { r := newTestRegistry(t) tool, ok := r.Get("person") if !ok || tool.Name() != "person" { t.Fatalf("Get(person) failed: %v %v", tool, ok) } if _, ok := r.Get("missing"); ok { t.Error("Get(missing) should report not found") } if got := r.List(); len(got) != 1 || got[0].Name() != "person" { t.Errorf("List wrong: %+v", got) } } func TestRegistryDuplicateRejected(t *testing.T) { r := newTestRegistry(t) if err := r.Register(stubTool{name: "person", schema: personSchema}); err == nil { t.Fatal("expected duplicate registration to error") } } func TestRegistryValidArgs(t *testing.T) { r := newTestRegistry(t) errs := r.Validate("person", json.RawMessage(`{"name":"ada","age":36}`)) if errs != nil { t.Fatalf("valid args reported errors: %+v", errs) } } func TestRegistryMissingRequiredField(t *testing.T) { r := newTestRegistry(t) errs := r.Validate("person", json.RawMessage(`{"age":36}`)) if len(errs) == 0 { t.Fatal("expected error for missing required field 'name'") } } func TestRegistryTypeError(t *testing.T) { r := newTestRegistry(t) errs := r.Validate("person", json.RawMessage(`{"name":"ada","age":"old"}`)) if len(errs) == 0 { t.Fatal("expected type error for age") } // The offending field should be located at /age. found := false for _, e := range errs { if e.Field == "/age" { found = true } } if !found { t.Errorf("expected a field error at /age, got %+v", errs) } } func TestRegistryUnknownTool(t *testing.T) { r := newTestRegistry(t) errs := r.Validate("nope", json.RawMessage(`{}`)) if len(errs) != 1 || errs[0].Field != "" { t.Fatalf("expected single root error for unknown tool, got %+v", errs) } } func TestRegistryNoSchemaSkipsValidation(t *testing.T) { r := NewToolRegistry() if err := r.Register(stubTool{name: "free", schema: ""}); err != nil { t.Fatalf("register: %v", err) } if errs := r.Validate("free", json.RawMessage(`{"anything":true}`)); errs != nil { t.Fatalf("no-schema tool should skip validation, got %+v", errs) } } func TestValidationErrorResultShape(t *testing.T) { r := newTestRegistry(t) errs := r.Validate("person", json.RawMessage(`{"age":36}`)) res := ValidationErrorResult("person", errs) if len(res.Content) == 0 { t.Fatal("expected content in validation error result") } if _, ok := res.Details.([]FieldError); !ok { t.Errorf("expected Details to carry []FieldError, got %T", res.Details) } if res.Terminate != nil { t.Error("validation failure must not terminate the run") } } func TestRegistryBadSchemaRejectedAtRegister(t *testing.T) { r := NewToolRegistry() err := r.Register(stubTool{name: "bad", schema: `{"type": 123}`}) if err == nil { t.Fatal("expected invalid schema to fail at registration") } }