summaryrefslogtreecommitdiff
path: root/internal/api
diff options
context:
space:
mode:
Diffstat (limited to 'internal/api')
-rw-r--r--internal/api/research_test.go241
1 files changed, 241 insertions, 0 deletions
diff --git a/internal/api/research_test.go b/internal/api/research_test.go
new file mode 100644
index 0000000..83a52b4
--- /dev/null
+++ b/internal/api/research_test.go
@@ -0,0 +1,241 @@
+package api
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+ "net/url"
+ "os"
+ "testing"
+ "time"
+)
+
+// Research tests for API optimization
+// Run with: go test -v -run TestResearch ./internal/api/
+// Requires TODOIST_API_KEY and TRELLO_API_KEY/TRELLO_TOKEN environment variables
+
+func TestTodoistSyncResearch(t *testing.T) {
+ apiKey := os.Getenv("TODOIST_API_KEY")
+ if apiKey == "" {
+ t.Skip("TODOIST_API_KEY not set, skipping research test")
+ }
+
+ ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
+ defer cancel()
+
+ // Todoist Sync API endpoint
+ syncURL := "https://api.todoist.com/sync/v9/sync"
+
+ // First sync with "*" to get all data
+ params := url.Values{}
+ params.Set("sync_token", "*")
+ params.Set("resource_types", `["items"]`) // "items" = tasks in Sync API
+
+ req, err := http.NewRequestWithContext(ctx, "POST", syncURL, nil)
+ if err != nil {
+ t.Fatalf("Failed to create request: %v", err)
+ }
+ req.URL.RawQuery = params.Encode()
+ req.Header.Set("Authorization", "Bearer "+apiKey)
+
+ client := &http.Client{Timeout: 30 * time.Second}
+ resp, err := client.Do(req)
+ if err != nil {
+ t.Fatalf("Failed to call Sync API: %v", err)
+ }
+ defer resp.Body.Close()
+
+ body, err := io.ReadAll(resp.Body)
+ if err != nil {
+ t.Fatalf("Failed to read response: %v", err)
+ }
+
+ if resp.StatusCode != http.StatusOK {
+ t.Fatalf("Sync API error (status %d): %s", resp.StatusCode, string(body))
+ }
+
+ // Parse response
+ var syncResp map[string]interface{}
+ if err := json.Unmarshal(body, &syncResp); err != nil {
+ t.Fatalf("Failed to parse response: %v", err)
+ }
+
+ // Log findings
+ t.Logf("=== TODOIST SYNC API RESEARCH ===")
+ t.Logf("Response size: %d bytes", len(body))
+
+ if syncToken, ok := syncResp["sync_token"].(string); ok {
+ t.Logf("sync_token: %s... (length: %d)", syncToken[:min(20, len(syncToken))], len(syncToken))
+ }
+
+ if items, ok := syncResp["items"].([]interface{}); ok {
+ t.Logf("Number of items (tasks): %d", len(items))
+ if len(items) > 0 {
+ // Show structure of first item
+ firstItem, _ := json.MarshalIndent(items[0], "", " ")
+ t.Logf("First item structure:\n%s", string(firstItem))
+ }
+ }
+
+ // List top-level keys
+ t.Logf("Top-level response keys:")
+ for key := range syncResp {
+ t.Logf(" - %s", key)
+ }
+
+ // Save sync_token for incremental sync test
+ syncToken := syncResp["sync_token"].(string)
+
+ // Test incremental sync (should return minimal data if nothing changed)
+ t.Logf("\n=== INCREMENTAL SYNC TEST ===")
+ params2 := url.Values{}
+ params2.Set("sync_token", syncToken)
+ params2.Set("resource_types", `["items"]`)
+
+ req2, _ := http.NewRequestWithContext(ctx, "POST", syncURL, nil)
+ req2.URL.RawQuery = params2.Encode()
+ req2.Header.Set("Authorization", "Bearer "+apiKey)
+
+ resp2, err := client.Do(req2)
+ if err != nil {
+ t.Fatalf("Incremental sync failed: %v", err)
+ }
+ defer resp2.Body.Close()
+
+ body2, _ := io.ReadAll(resp2.Body)
+ t.Logf("Incremental response size: %d bytes (vs full: %d bytes)", len(body2), len(body))
+
+ var syncResp2 map[string]interface{}
+ json.Unmarshal(body2, &syncResp2)
+ if items2, ok := syncResp2["items"].([]interface{}); ok {
+ t.Logf("Incremental items count: %d", len(items2))
+ }
+}
+
+func TestTrelloOptimizationResearch(t *testing.T) {
+ apiKey := os.Getenv("TRELLO_API_KEY")
+ token := os.Getenv("TRELLO_TOKEN")
+ if apiKey == "" || token == "" {
+ t.Skip("TRELLO_API_KEY or TRELLO_TOKEN not set, skipping research test")
+ }
+
+ ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
+ defer cancel()
+
+ client := &http.Client{Timeout: 30 * time.Second}
+ baseURL := "https://api.trello.com/1"
+
+ // Test 1: Full response (current implementation)
+ t.Logf("=== TRELLO OPTIMIZATION RESEARCH ===")
+
+ params := url.Values{}
+ params.Set("key", apiKey)
+ params.Set("token", token)
+ params.Set("filter", "open")
+
+ start := time.Now()
+ req, _ := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/members/me/boards?%s", baseURL, params.Encode()), nil)
+ resp, err := client.Do(req)
+ if err != nil {
+ t.Fatalf("Failed to fetch boards: %v", err)
+ }
+ body, _ := io.ReadAll(resp.Body)
+ resp.Body.Close()
+ fullDuration := time.Since(start)
+
+ t.Logf("Test 1 - Full boards response:")
+ t.Logf(" Size: %d bytes", len(body))
+ t.Logf(" Duration: %v", fullDuration)
+
+ var boards []map[string]interface{}
+ json.Unmarshal(body, &boards)
+ t.Logf(" Board count: %d", len(boards))
+ if len(boards) > 0 {
+ t.Logf(" Fields in first board: %v", getKeys(boards[0]))
+ }
+
+ // Test 2: Limited fields
+ params2 := url.Values{}
+ params2.Set("key", apiKey)
+ params2.Set("token", token)
+ params2.Set("filter", "open")
+ params2.Set("fields", "id,name") // Only essential fields
+
+ start = time.Now()
+ req2, _ := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/members/me/boards?%s", baseURL, params2.Encode()), nil)
+ resp2, err := client.Do(req2)
+ if err != nil {
+ t.Fatalf("Failed to fetch limited boards: %v", err)
+ }
+ body2, _ := io.ReadAll(resp2.Body)
+ resp2.Body.Close()
+ limitedDuration := time.Since(start)
+
+ t.Logf("\nTest 2 - Limited fields (id,name):")
+ t.Logf(" Size: %d bytes (%.1f%% of full)", len(body2), float64(len(body2))/float64(len(body))*100)
+ t.Logf(" Duration: %v", limitedDuration)
+
+ // Test 3: Batch request with cards included
+ if len(boards) > 0 {
+ boardID := boards[0]["id"].(string)
+
+ params3 := url.Values{}
+ params3.Set("key", apiKey)
+ params3.Set("token", token)
+ params3.Set("cards", "visible")
+ params3.Set("card_fields", "id,name,idList,due,url")
+ params3.Set("lists", "open")
+ params3.Set("list_fields", "id,name")
+
+ start = time.Now()
+ req3, _ := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/boards/%s?%s", baseURL, boardID, params3.Encode()), nil)
+ resp3, err := client.Do(req3)
+ if err != nil {
+ t.Fatalf("Failed to fetch board with cards: %v", err)
+ }
+ body3, _ := io.ReadAll(resp3.Body)
+ resp3.Body.Close()
+ batchDuration := time.Since(start)
+
+ t.Logf("\nTest 3 - Single board with cards/lists embedded:")
+ t.Logf(" Size: %d bytes", len(body3))
+ t.Logf(" Duration: %v", batchDuration)
+
+ var boardWithCards map[string]interface{}
+ json.Unmarshal(body3, &boardWithCards)
+ if cards, ok := boardWithCards["cards"].([]interface{}); ok {
+ t.Logf(" Cards count: %d", len(cards))
+ }
+ if lists, ok := boardWithCards["lists"].([]interface{}); ok {
+ t.Logf(" Lists count: %d", len(lists))
+ }
+ }
+
+ // Test 4: Check for webhooks/since parameter
+ t.Logf("\nTest 4 - Checking for 'since' parameter support:")
+ params4 := url.Values{}
+ params4.Set("key", apiKey)
+ params4.Set("token", token)
+ params4.Set("filter", "open")
+ params4.Set("since", time.Now().Add(-24*time.Hour).Format(time.RFC3339))
+
+ req4, _ := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/members/me/boards?%s", baseURL, params4.Encode()), nil)
+ resp4, err := client.Do(req4)
+ if err != nil {
+ t.Logf(" 'since' parameter: Error - %v", err)
+ } else {
+ body4, _ := io.ReadAll(resp4.Body)
+ resp4.Body.Close()
+ t.Logf(" 'since' parameter: Status %d, Size %d bytes", resp4.StatusCode, len(body4))
+ }
+}
+
+func getKeys(m map[string]interface{}) []string {
+ keys := make([]string, 0, len(m))
+ for k := range m {
+ keys = append(keys, k)
+ }
+ return keys
+}