package agentic import ( "context" "encoding/json" "net/http" "net/http/httptest" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // Test fixtures var testTask = Task{ ID: "task-123", Title: "Implement feature X", Description: "Add the new feature X to the system", Priority: PriorityHigh, Status: StatusPending, Labels: []string{"feature", "backend"}, Files: []string{"pkg/feature/feature.go"}, CreatedAt: time.Now().Add(-24 * time.Hour), Project: "core", } var testTasks = []Task{ testTask, { ID: "task-456", Title: "Fix bug Y", Description: "Fix the bug in component Y", Priority: PriorityCritical, Status: StatusPending, Labels: []string{"bug", "urgent"}, CreatedAt: time.Now().Add(-2 * time.Hour), Project: "core", }, } func TestNewClient_Good(t *testing.T) { client := NewClient("https://api.example.com", "test-token") assert.Equal(t, "https://api.example.com", client.BaseURL) assert.Equal(t, "test-token", client.Token) assert.NotNil(t, client.HTTPClient) } func TestNewClient_Good_TrailingSlash(t *testing.T) { client := NewClient("https://api.example.com/", "test-token") assert.Equal(t, "https://api.example.com", client.BaseURL) } func TestNewClientFromConfig_Good(t *testing.T) { cfg := &Config{ BaseURL: "https://api.example.com", Token: "config-token", AgentID: "agent-001", } client := NewClientFromConfig(cfg) assert.Equal(t, "https://api.example.com", client.BaseURL) assert.Equal(t, "config-token", client.Token) assert.Equal(t, "agent-001", client.AgentID) } func TestClient_ListTasks_Good(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, http.MethodGet, r.Method) assert.Equal(t, "/api/tasks", r.URL.Path) assert.Equal(t, "Bearer test-token", r.Header.Get("Authorization")) w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(testTasks) })) defer server.Close() client := NewClient(server.URL, "test-token") tasks, err := client.ListTasks(context.Background(), ListOptions{}) require.NoError(t, err) assert.Len(t, tasks, 2) assert.Equal(t, "task-123", tasks[0].ID) assert.Equal(t, "task-456", tasks[1].ID) } func TestClient_ListTasks_Good_WithFilters(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { query := r.URL.Query() assert.Equal(t, "pending", query.Get("status")) assert.Equal(t, "high", query.Get("priority")) assert.Equal(t, "core", query.Get("project")) assert.Equal(t, "10", query.Get("limit")) assert.Equal(t, "bug,urgent", query.Get("labels")) w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode([]Task{testTask}) })) defer server.Close() client := NewClient(server.URL, "test-token") opts := ListOptions{ Status: StatusPending, Priority: PriorityHigh, Project: "core", Limit: 10, Labels: []string{"bug", "urgent"}, } tasks, err := client.ListTasks(context.Background(), opts) require.NoError(t, err) assert.Len(t, tasks, 1) } func TestClient_ListTasks_Bad_ServerError(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusInternalServerError) _ = json.NewEncoder(w).Encode(APIError{Message: "internal error"}) })) defer server.Close() client := NewClient(server.URL, "test-token") tasks, err := client.ListTasks(context.Background(), ListOptions{}) assert.Error(t, err) assert.Nil(t, tasks) assert.Contains(t, err.Error(), "internal error") } func TestClient_GetTask_Good(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, http.MethodGet, r.Method) assert.Equal(t, "/api/tasks/task-123", r.URL.Path) w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(testTask) })) defer server.Close() client := NewClient(server.URL, "test-token") task, err := client.GetTask(context.Background(), "task-123") require.NoError(t, err) assert.Equal(t, "task-123", task.ID) assert.Equal(t, "Implement feature X", task.Title) assert.Equal(t, PriorityHigh, task.Priority) } func TestClient_GetTask_Bad_EmptyID(t *testing.T) { client := NewClient("https://api.example.com", "test-token") task, err := client.GetTask(context.Background(), "") assert.Error(t, err) assert.Nil(t, task) assert.Contains(t, err.Error(), "task ID is required") } func TestClient_GetTask_Bad_NotFound(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotFound) _ = json.NewEncoder(w).Encode(APIError{Message: "task not found"}) })) defer server.Close() client := NewClient(server.URL, "test-token") task, err := client.GetTask(context.Background(), "nonexistent") assert.Error(t, err) assert.Nil(t, task) assert.Contains(t, err.Error(), "task not found") } func TestClient_ClaimTask_Good(t *testing.T) { claimedTask := testTask claimedTask.Status = StatusInProgress claimedTask.ClaimedBy = "agent-001" server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, http.MethodPost, r.Method) assert.Equal(t, "/api/tasks/task-123/claim", r.URL.Path) w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(ClaimResponse{Task: &claimedTask}) })) defer server.Close() client := NewClient(server.URL, "test-token") client.AgentID = "agent-001" task, err := client.ClaimTask(context.Background(), "task-123") require.NoError(t, err) assert.Equal(t, StatusInProgress, task.Status) assert.Equal(t, "agent-001", task.ClaimedBy) } func TestClient_ClaimTask_Good_SimpleResponse(t *testing.T) { // Some APIs might return just the task without wrapping claimedTask := testTask claimedTask.Status = StatusInProgress server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(claimedTask) })) defer server.Close() client := NewClient(server.URL, "test-token") task, err := client.ClaimTask(context.Background(), "task-123") require.NoError(t, err) assert.Equal(t, "task-123", task.ID) } func TestClient_ClaimTask_Bad_EmptyID(t *testing.T) { client := NewClient("https://api.example.com", "test-token") task, err := client.ClaimTask(context.Background(), "") assert.Error(t, err) assert.Nil(t, task) assert.Contains(t, err.Error(), "task ID is required") } func TestClient_ClaimTask_Bad_AlreadyClaimed(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusConflict) _ = json.NewEncoder(w).Encode(APIError{Message: "task already claimed"}) })) defer server.Close() client := NewClient(server.URL, "test-token") task, err := client.ClaimTask(context.Background(), "task-123") assert.Error(t, err) assert.Nil(t, task) assert.Contains(t, err.Error(), "task already claimed") } func TestClient_UpdateTask_Good(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, http.MethodPatch, r.Method) assert.Equal(t, "/api/tasks/task-123", r.URL.Path) assert.Equal(t, "application/json", r.Header.Get("Content-Type")) var update TaskUpdate err := json.NewDecoder(r.Body).Decode(&update) require.NoError(t, err) assert.Equal(t, StatusInProgress, update.Status) assert.Equal(t, 50, update.Progress) w.WriteHeader(http.StatusOK) })) defer server.Close() client := NewClient(server.URL, "test-token") err := client.UpdateTask(context.Background(), "task-123", TaskUpdate{ Status: StatusInProgress, Progress: 50, Notes: "Making progress", }) assert.NoError(t, err) } func TestClient_UpdateTask_Bad_EmptyID(t *testing.T) { client := NewClient("https://api.example.com", "test-token") err := client.UpdateTask(context.Background(), "", TaskUpdate{}) assert.Error(t, err) assert.Contains(t, err.Error(), "task ID is required") } func TestClient_CompleteTask_Good(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, http.MethodPost, r.Method) assert.Equal(t, "/api/tasks/task-123/complete", r.URL.Path) var result TaskResult err := json.NewDecoder(r.Body).Decode(&result) require.NoError(t, err) assert.True(t, result.Success) assert.Equal(t, "Feature implemented", result.Output) w.WriteHeader(http.StatusOK) })) defer server.Close() client := NewClient(server.URL, "test-token") err := client.CompleteTask(context.Background(), "task-123", TaskResult{ Success: true, Output: "Feature implemented", Artifacts: []string{"pkg/feature/feature.go"}, }) assert.NoError(t, err) } func TestClient_CompleteTask_Bad_EmptyID(t *testing.T) { client := NewClient("https://api.example.com", "test-token") err := client.CompleteTask(context.Background(), "", TaskResult{}) assert.Error(t, err) assert.Contains(t, err.Error(), "task ID is required") } func TestClient_Ping_Good(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, "/api/health", r.URL.Path) w.WriteHeader(http.StatusOK) })) defer server.Close() client := NewClient(server.URL, "test-token") err := client.Ping(context.Background()) assert.NoError(t, err) } func TestClient_Ping_Bad_ServerDown(t *testing.T) { client := NewClient("http://localhost:99999", "test-token") client.HTTPClient.Timeout = 100 * time.Millisecond err := client.Ping(context.Background()) assert.Error(t, err) assert.Contains(t, err.Error(), "request failed") } func TestAPIError_Error_Good(t *testing.T) { err := &APIError{ Code: 404, Message: "task not found", } assert.Equal(t, "task not found", err.Error()) err.Details = "task-123 does not exist" assert.Equal(t, "task not found: task-123 does not exist", err.Error()) } func TestTaskStatus_Good(t *testing.T) { assert.Equal(t, TaskStatus("pending"), StatusPending) assert.Equal(t, TaskStatus("in_progress"), StatusInProgress) assert.Equal(t, TaskStatus("completed"), StatusCompleted) assert.Equal(t, TaskStatus("blocked"), StatusBlocked) } func TestTaskPriority_Good(t *testing.T) { assert.Equal(t, TaskPriority("critical"), PriorityCritical) assert.Equal(t, TaskPriority("high"), PriorityHigh) assert.Equal(t, TaskPriority("medium"), PriorityMedium) assert.Equal(t, TaskPriority("low"), PriorityLow) }