subagent_test.go

  1package claudetool
  2
  3import (
  4	"context"
  5	"encoding/json"
  6	"testing"
  7	"time"
  8)
  9
 10// mockSubagentDB implements SubagentDB for testing.
 11type mockSubagentDB struct {
 12	conversations map[string]string // slug -> conversationID
 13}
 14
 15func newMockSubagentDB() *mockSubagentDB {
 16	return &mockSubagentDB{
 17		conversations: make(map[string]string),
 18	}
 19}
 20
 21func (m *mockSubagentDB) GetOrCreateSubagentConversation(ctx context.Context, slug, parentID, cwd string) (string, string, error) {
 22	key := parentID + ":" + slug
 23	if id, ok := m.conversations[key]; ok {
 24		return id, slug, nil
 25	}
 26	id := "subagent-" + slug
 27	m.conversations[key] = id
 28	return id, slug, nil
 29}
 30
 31// mockSubagentRunner implements SubagentRunner for testing.
 32type mockSubagentRunner struct {
 33	response string
 34	err      error
 35}
 36
 37func (m *mockSubagentRunner) RunSubagent(ctx context.Context, conversationID, prompt string, wait bool, timeout time.Duration) (string, error) {
 38	if m.err != nil {
 39		return "", m.err
 40	}
 41	return m.response, nil
 42}
 43
 44func TestSubagentTool_SanitizeSlug(t *testing.T) {
 45	tests := []struct {
 46		input    string
 47		expected string
 48	}{
 49		{"test-slug", "test-slug"},
 50		{"Test Slug", "test-slug"},
 51		{"test_slug", "test-slug"},
 52		{"test--slug", "test-slug"},
 53		{"-test-slug-", "test-slug"},
 54		{"test@slug!", "testslug"},
 55		{"123-abc", "123-abc"},
 56		{"", ""},
 57	}
 58
 59	for _, tt := range tests {
 60		t.Run(tt.input, func(t *testing.T) {
 61			result := sanitizeSlug(tt.input)
 62			if result != tt.expected {
 63				t.Errorf("sanitizeSlug(%q) = %q, want %q", tt.input, result, tt.expected)
 64			}
 65		})
 66	}
 67}
 68
 69func TestSubagentTool_Run(t *testing.T) {
 70	wd := NewMutableWorkingDir("/tmp")
 71	db := newMockSubagentDB()
 72	runner := &mockSubagentRunner{response: "Task completed successfully"}
 73
 74	tool := &SubagentTool{
 75		DB:                   db,
 76		ParentConversationID: "parent-123",
 77		WorkingDir:           wd,
 78		Runner:               runner,
 79	}
 80
 81	input := subagentInput{
 82		Slug:   "test-task",
 83		Prompt: "Do something useful",
 84	}
 85	inputJSON, _ := json.Marshal(input)
 86
 87	result := tool.Run(context.Background(), inputJSON)
 88	if result.Error != nil {
 89		t.Fatalf("unexpected error: %v", result.Error)
 90	}
 91
 92	if len(result.LLMContent) == 0 {
 93		t.Fatal("expected LLM content")
 94	}
 95
 96	if result.LLMContent[0].Text == "" {
 97		t.Error("expected non-empty response text")
 98	}
 99
100	// Check display data
101	if result.Display == nil {
102		t.Error("expected display data")
103	}
104	displayData, ok := result.Display.(SubagentDisplayData)
105	if !ok {
106		t.Error("display data should be SubagentDisplayData")
107	}
108	if displayData.Slug != "test-task" {
109		t.Errorf("expected slug 'test-task', got %q", displayData.Slug)
110	}
111}
112
113func TestSubagentTool_Validation(t *testing.T) {
114	wd := NewMutableWorkingDir("/tmp")
115	db := newMockSubagentDB()
116	runner := &mockSubagentRunner{response: "OK"}
117
118	tool := &SubagentTool{
119		DB:                   db,
120		ParentConversationID: "parent-123",
121		WorkingDir:           wd,
122		Runner:               runner,
123	}
124
125	// Test empty slug
126	t.Run("empty slug", func(t *testing.T) {
127		input := subagentInput{Slug: "", Prompt: "test"}
128		inputJSON, _ := json.Marshal(input)
129		result := tool.Run(context.Background(), inputJSON)
130		if result.Error == nil {
131			t.Error("expected error for empty slug")
132		}
133	})
134
135	// Test empty prompt
136	t.Run("empty prompt", func(t *testing.T) {
137		input := subagentInput{Slug: "test", Prompt: ""}
138		inputJSON, _ := json.Marshal(input)
139		result := tool.Run(context.Background(), inputJSON)
140		if result.Error == nil {
141			t.Error("expected error for empty prompt")
142		}
143	})
144
145	// Test invalid slug (only special chars)
146	t.Run("invalid slug", func(t *testing.T) {
147		input := subagentInput{Slug: "@#$%", Prompt: "test"}
148		inputJSON, _ := json.Marshal(input)
149		result := tool.Run(context.Background(), inputJSON)
150		if result.Error == nil {
151			t.Error("expected error for invalid slug")
152		}
153	})
154}