diff options
| author | Peter Stone <thepeterstone@gmail.com> | 2026-01-13 13:58:53 -1000 |
|---|---|---|
| committer | Peter Stone <thepeterstone@gmail.com> | 2026-01-13 13:58:53 -1000 |
| commit | 1c79f105c960ddab2265cbfd8dfd728630b1ebfb (patch) | |
| tree | 5c0c5f6bad50fb3214d75ac29de292c74fe2267d /internal/api/trello_test.go | |
| parent | 429476f5ac97f56c7f6a755d6dd565767d31dfb6 (diff) | |
Implement Trello write operations (Phase 3 Step 1)
Add CreateCard and UpdateCard methods to Trello API client with
full testability support:
- Refactor TrelloClient with configurable baseURL for testing
- Replace hardcoded trelloBaseURL constant with c.baseURL
- Implement CreateCard with support for description and due date
- Implement UpdateCard with flexible field updates
- Add comprehensive test suite using httptest.NewServer
- Tests cover success cases, error handling, and edge cases
All tests pass. Write operations ready for Phase 3 UI integration.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Diffstat (limited to 'internal/api/trello_test.go')
| -rw-r--r-- | internal/api/trello_test.go | 254 |
1 files changed, 254 insertions, 0 deletions
diff --git a/internal/api/trello_test.go b/internal/api/trello_test.go new file mode 100644 index 0000000..b43b55e --- /dev/null +++ b/internal/api/trello_test.go @@ -0,0 +1,254 @@ +package api + +import ( + "context" + "encoding/json" + "io" + "net/http" + "net/http/httptest" + "net/url" + "strings" + "testing" + "time" +) + +func TestTrelloClient_CreateCard(t *testing.T) { + // Mock server + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Verify method and path + if r.Method != "POST" { + t.Errorf("Expected POST request, got %s", r.Method) + } + if r.URL.Path != "/cards" { + t.Errorf("Expected path /cards, got %s", r.URL.Path) + } + + // Verify Content-Type + if r.Header.Get("Content-Type") != "application/x-www-form-urlencoded" { + t.Errorf("Expected Content-Type application/x-www-form-urlencoded, got %s", r.Header.Get("Content-Type")) + } + + // Parse form data + body, _ := io.ReadAll(r.Body) + values, _ := parseFormData(string(body)) + + // Verify required fields + if values["key"] != "test-key" { + t.Errorf("Expected key=test-key, got %s", values["key"]) + } + if values["token"] != "test-token" { + t.Errorf("Expected token=test-token, got %s", values["token"]) + } + if values["idList"] != "list-123" { + t.Errorf("Expected idList=list-123, got %s", values["idList"]) + } + if values["name"] != "Test Card" { + t.Errorf("Expected name=Test Card, got %s", values["name"]) + } + if values["desc"] != "Test description" { + t.Errorf("Expected desc=Test description, got %s", values["desc"]) + } + + // Return mock response + response := trelloCardResponse{ + ID: "card-456", + Name: "Test Card", + IDList: "list-123", + URL: "https://trello.com/c/card-456", + Desc: "Test description", + IDBoard: "board-789", + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(response) + })) + defer server.Close() + + // Create client with mock server URL + client := &TrelloClient{ + apiKey: "test-key", + token: "test-token", + baseURL: server.URL, + httpClient: &http.Client{}, + } + + // Test CreateCard + ctx := context.Background() + card, err := client.CreateCard(ctx, "list-123", "Test Card", "Test description", nil) + + if err != nil { + t.Fatalf("CreateCard failed: %v", err) + } + + // Verify response + if card.ID != "card-456" { + t.Errorf("Expected card ID card-456, got %s", card.ID) + } + if card.Name != "Test Card" { + t.Errorf("Expected card name Test Card, got %s", card.Name) + } + if card.ListID != "list-123" { + t.Errorf("Expected list ID list-123, got %s", card.ListID) + } + if card.URL != "https://trello.com/c/card-456" { + t.Errorf("Expected URL https://trello.com/c/card-456, got %s", card.URL) + } +} + +func TestTrelloClient_CreateCard_WithDueDate(t *testing.T) { + dueDate := time.Date(2026, 1, 15, 12, 0, 0, 0, time.UTC) + + // Mock server + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Parse form data + body, _ := io.ReadAll(r.Body) + values, _ := parseFormData(string(body)) + + // Verify due date is present + if values["due"] != dueDate.Format(time.RFC3339) { + t.Errorf("Expected due=%s, got %s", dueDate.Format(time.RFC3339), values["due"]) + } + + // Return mock response with due date + dueString := dueDate.Format(time.RFC3339) + response := trelloCardResponse{ + ID: "card-789", + Name: "Card with Due Date", + IDList: "list-456", + URL: "https://trello.com/c/card-789", + Due: &dueString, + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(response) + })) + defer server.Close() + + // Create client + client := &TrelloClient{ + apiKey: "test-key", + token: "test-token", + baseURL: server.URL, + httpClient: &http.Client{}, + } + + // Test CreateCard with due date + ctx := context.Background() + card, err := client.CreateCard(ctx, "list-456", "Card with Due Date", "", &dueDate) + + if err != nil { + t.Fatalf("CreateCard failed: %v", err) + } + + // Verify due date is set + if card.DueDate == nil { + t.Error("Expected due date to be set") + } else if !card.DueDate.Equal(dueDate) { + t.Errorf("Expected due date %v, got %v", dueDate, *card.DueDate) + } +} + +func TestTrelloClient_UpdateCard(t *testing.T) { + // Mock server + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Verify method and path + if r.Method != "PUT" { + t.Errorf("Expected PUT request, got %s", r.Method) + } + if !strings.HasPrefix(r.URL.Path, "/cards/") { + t.Errorf("Expected path to start with /cards/, got %s", r.URL.Path) + } + + // Extract card ID from path + cardID := strings.TrimPrefix(r.URL.Path, "/cards/") + if cardID != "card-123" { + t.Errorf("Expected card ID card-123, got %s", cardID) + } + + // Parse form data + body, _ := io.ReadAll(r.Body) + values, _ := parseFormData(string(body)) + + // Verify updated fields + if values["name"] != "Updated Name" { + t.Errorf("Expected name=Updated Name, got %s", values["name"]) + } + if values["desc"] != "Updated description" { + t.Errorf("Expected desc=Updated description, got %s", values["desc"]) + } + + // Return 200 OK + w.WriteHeader(http.StatusOK) + w.Write([]byte(`{"id":"card-123","name":"Updated Name"}`)) + })) + defer server.Close() + + // Create client + client := &TrelloClient{ + apiKey: "test-key", + token: "test-token", + baseURL: server.URL, + httpClient: &http.Client{}, + } + + // Test UpdateCard + ctx := context.Background() + updates := map[string]interface{}{ + "name": "Updated Name", + "desc": "Updated description", + } + + err := client.UpdateCard(ctx, "card-123", updates) + + if err != nil { + t.Fatalf("UpdateCard failed: %v", err) + } +} + +func TestTrelloClient_UpdateCard_Error(t *testing.T) { + // Mock server that returns error + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(`{"error":"Invalid card ID"}`)) + })) + defer server.Close() + + // Create client + client := &TrelloClient{ + apiKey: "test-key", + token: "test-token", + baseURL: server.URL, + httpClient: &http.Client{}, + } + + // Test UpdateCard with error + ctx := context.Background() + updates := map[string]interface{}{ + "name": "Should Fail", + } + + err := client.UpdateCard(ctx, "invalid-card", updates) + + if err == nil { + t.Error("Expected error, got nil") + } + if !strings.Contains(err.Error(), "400") { + t.Errorf("Expected error to contain status 400, got: %v", err) + } +} + +// Helper function to parse form-encoded data +func parseFormData(data string) (map[string]string, error) { + values, err := url.ParseQuery(data) + if err != nil { + return nil, err + } + + result := make(map[string]string) + for key, vals := range values { + if len(vals) > 0 { + result[key] = vals[0] + } + } + return result, nil +} |
