package sidekick import ( "context" "os" "strings" "testing" ) func TestSanitizeToolName(t *testing.T) { tests := []struct{ i, e string }{ {"mcp/tool", "mcp_tool"}, {"my.tool-name", "my_tool-name"}, {"a space", "a_space"}, {"already_safe", "already_safe"}, } for _, tt := range tests { if a := SanitizeToolName(tt.i); a != tt.e { t.Errorf("expected %s, got %s", tt.e, a) } } } func TestBufferGate(t *testing.T) { var tfs []string threshold := 10 s := "small" if o, _ := BufferGate(s, &tfs, threshold); o != s { t.Errorf("expected small") } l := strings.Repeat("A", threshold+1) o, err := BufferGate(l, &tfs, threshold) if err != nil { t.Fatalf("err: %v", err) } if !strings.Contains(o, "file '") { t.Errorf("no file pointer") } if len(tfs) != 1 { t.Fatalf("no temp file") } c, _ := os.ReadFile(tfs[0]) if string(c) != l { t.Errorf("content mismatch") } os.Remove(tfs[0]) } func TestJSONActionParsing(t *testing.T) { a := &Sidekick{} m := Message{Content: `{"action": "RESPOND", "params": {"response": "Hi"}}`} act, p, ok, err := a.parseAction(m) if err != nil || !ok || act != ActionTypeRespond { t.Errorf("parse fail") } if p["response"] != "Hi" { t.Errorf("param mismatch") } } func TestDualResponseMode(t *testing.T) { a := &Sidekick{} m := Message{ToolCalls: []ToolCall{{Function: FunctionCall{Name: "rf", Arguments: "{}"}}}} act, _, ok, _ := a.parseAction(m) if !ok || act != ActionTypeToolCall { t.Errorf("dual fail") } } func TestSingleAgentLoop(t *testing.T) { c := AgentConfig{ID: "a1", Role: RoleSpecialist, MaxIterations: 5} a := NewSidekick(c, ModelConfig{}, nil) mc := &mockLLMClient{responses: []Message{ {Content: `{"action": "TOOL_CALL", "params": {"tool_name": "unknown", "arguments": {}}}`}, {Content: `{"action": "RESPOND", "params": {"response": "Done"}}`}, }} SetLLMClient(mc) ans, _ := a.Run(context.Background(), nil, nil) if ans != "Done" { t.Errorf("expected Done, got %s", ans) } } func TestDelegation(t *testing.T) { sc := AgentConfig{ID: "s1", Role: RoleSpecialist, MaxIterations: 2} sa := NewSidekick(sc, ModelConfig{}, nil) cc := AgentConfig{ID: "c1", Role: RoleCoordinator, Subagents: []string{"s1"}, MaxIterations: 3} coord := NewSidekick(cc, ModelConfig{}, nil) mc := &mockLLMClient{responses: []Message{ {Content: `{"action": "DELEGATE", "params": {"subagent_id": "s1", "task": "do"}}`}, {Content: `{"action": "RESPOND", "params": {"response": "Sub done"}}`}, {Content: `{"action": "RESPOND", "params": {"response": "Coord done"}}`}, }} SetLLMClient(mc) p := map[string]*Sidekick{"s1": sa} ans, _ := coord.Run(context.Background(), nil, p) if ans != "Coord done" { t.Errorf("expected Coord done, got %s", ans) } } func TestTempFileCleanup(t *testing.T) { threshold := 10 a := NewSidekick(AgentConfig{MaxIterations: 2, ToolResponseThreshold: threshold}, ModelConfig{}, nil) h := strings.Repeat("B", threshold+1) a.ToolRegistry["huge"] = ToolDefinition{Name: "huge", Internal: true, Handler: func(m map[string]interface{}) (string, error) { return h, nil }} a.ToolMapping["huge"] = "huge" mc := &mockLLMClient{responses: []Message{ {Content: `{"action": "TOOL_CALL", "params": {"tool_name": "huge", "arguments": {}}}`}, {Content: `{"action": "RESPOND", "params": {"response": "Ok"}}`}, }} SetLLMClient(mc) bfs, _ := os.ReadDir(os.TempDir()) a.Run(context.Background(), nil, nil) afs, _ := os.ReadDir(os.TempDir()) bc, ac := 0, 0 for _, f := range bfs { if strings.HasPrefix(f.Name(), "sidekick-buf-") { bc++ } } for _, f := range afs { if strings.HasPrefix(f.Name(), "sidekick-buf-") { ac++ } } if ac > bc { t.Errorf("leak: before %d, after %d", bc, ac) } }