summaryrefslogtreecommitdiff
path: root/internal/api/todoist_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/api/todoist_test.go')
-rw-r--r--internal/api/todoist_test.go399
1 files changed, 0 insertions, 399 deletions
diff --git a/internal/api/todoist_test.go b/internal/api/todoist_test.go
deleted file mode 100644
index d2c8da5..0000000
--- a/internal/api/todoist_test.go
+++ /dev/null
@@ -1,399 +0,0 @@
-package api
-
-import (
- "context"
- "encoding/json"
- "net/http"
- "net/http/httptest"
- "strings"
- "testing"
- "time"
-)
-
-// newTestTodoistClient creates a TodoistClient for testing with custom base URL
-func newTestTodoistClient(baseURL, apiKey string) *TodoistClient {
- client := NewTodoistClient(apiKey)
- client.BaseURL = baseURL
- return client
-}
-
-func TestTodoistClient_CreateTask(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 != "/tasks" {
- t.Errorf("Expected path /tasks, got %s", r.URL.Path)
- }
-
- // Verify Authorization header
- if !strings.HasPrefix(r.Header.Get("Authorization"), "Bearer ") {
- t.Errorf("Expected Bearer token in Authorization header")
- }
-
- // Parse JSON body
- var payload map[string]interface{}
- if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
- t.Errorf("Failed to decode request body: %v", err)
- }
-
- // Verify content
- if payload["content"] != "Test Task" {
- t.Errorf("Expected content=Test Task, got %v", payload["content"])
- }
-
- if payload["project_id"] != "project-123" {
- t.Errorf("Expected project_id=project-123, got %v", payload["project_id"])
- }
-
- // Return mock response
- response := todoistTaskResponse{
- ID: "task-456",
- Content: "Test Task",
- Description: "",
- ProjectID: "project-123",
- Priority: 1,
- Labels: []string{},
- URL: "https://todoist.com/task/456",
- CreatedAt: time.Now().Format(time.RFC3339),
- }
-
- w.Header().Set("Content-Type", "application/json")
- _ = json.NewEncoder(w).Encode(response)
- }))
- defer server.Close()
-
- // Create client with mock server URL
- client := newTestTodoistClient(server.URL, "test-key")
-
- // Test CreateTask
- ctx := context.Background()
- task, err := client.CreateTask(ctx, "Test Task", "project-123", nil, 0)
-
- if err != nil {
- t.Fatalf("CreateTask failed: %v", err)
- }
-
- // Verify response
- if task.ID != "task-456" {
- t.Errorf("Expected task ID task-456, got %s", task.ID)
- }
- if task.Content != "Test Task" {
- t.Errorf("Expected task content Test Task, got %s", task.Content)
- }
- if task.ProjectID != "project-123" {
- t.Errorf("Expected project ID project-123, got %s", task.ProjectID)
- }
-}
-
-func TestTodoistClient_CreateTask_WithDueDate(t *testing.T) {
- dueDate := time.Date(2026, 1, 15, 0, 0, 0, 0, time.UTC)
-
- // Mock server
- server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- // Parse JSON body
- var payload map[string]interface{}
- _ = json.NewDecoder(r.Body).Decode(&payload)
-
- // Verify due_date
- if payload["due_date"] != "2026-01-15" {
- t.Errorf("Expected due_date=2026-01-15, got %v", payload["due_date"])
- }
-
- // Return mock response with due date
- response := todoistTaskResponse{
- ID: "task-789",
- Content: "Task with Due Date",
- ProjectID: "project-456",
- URL: "https://todoist.com/task/789",
- CreatedAt: time.Now().Format(time.RFC3339),
- Due: &dueInfo{
- Date: "2026-01-15",
- Datetime: "",
- },
- }
-
- w.Header().Set("Content-Type", "application/json")
- _ = json.NewEncoder(w).Encode(response)
- }))
- defer server.Close()
-
- // Create client
- client := newTestTodoistClient(server.URL, "test-key")
-
- // Test CreateTask with due date
- ctx := context.Background()
- task, err := client.CreateTask(ctx, "Task with Due Date", "project-456", &dueDate, 0)
-
- if err != nil {
- t.Fatalf("CreateTask failed: %v", err)
- }
-
- // Verify due date is set
- if task.DueDate == nil {
- t.Error("Expected due date to be set")
- } else {
- expectedDate := time.Date(2026, 1, 15, 0, 0, 0, 0, time.UTC)
- if !task.DueDate.Equal(expectedDate) {
- t.Errorf("Expected due date %v, got %v", expectedDate, *task.DueDate)
- }
- }
-}
-
-func TestParseDueDate_LocalDatetimeInDateField(t *testing.T) {
- // Todoist REST API v1 puts local datetime (no tz offset) in the "date" field
- // when datetime is not in UTC (e.g. recurring tasks with a set time)
- // e.g. due={date="2026-03-22T19:00:00" datetime="" is_recurring=true}
- due := &dueInfo{
- Date: "2026-03-22T19:00:00",
- Datetime: "",
- IsRecurring: true,
- }
- result := parseDueDate(due)
- if result == nil {
- t.Fatal("parseDueDate returned nil for date field containing local datetime — must parse YYYY-MM-DDTHH:MM:SS format")
- }
- if result.Hour() != 19 || result.Minute() != 0 {
- t.Errorf("Expected 19:00, got %02d:%02d", result.Hour(), result.Minute())
- }
-}
-
-func TestParseDueDate_MicrosecondDatetime(t *testing.T) {
- // Todoist REST API v1 returns datetime with microseconds: "2023-01-15T10:00:00.000000Z"
- // time.RFC3339 cannot parse fractional seconds — parseDueDate must use RFC3339Nano
- due := &dueInfo{
- Date: "2023-01-15",
- Datetime: "2023-01-15T10:00:00.000000Z",
- IsRecurring: true,
- }
- result := parseDueDate(due)
- if result == nil {
- t.Fatal("parseDueDate returned nil for datetime with microseconds — RFC3339Nano required")
- }
- if result.Hour() != 10 || result.Minute() != 0 {
- t.Errorf("Expected 10:00, got %02d:%02d", result.Hour(), result.Minute())
- }
-}
-
-func TestParseDueDate_RFC3339Datetime(t *testing.T) {
- // Standard RFC3339 without fractional seconds should also work
- due := &dueInfo{
- Date: "2023-01-15",
- Datetime: "2023-01-15T10:00:00Z",
- }
- result := parseDueDate(due)
- if result == nil {
- t.Fatal("parseDueDate returned nil for standard RFC3339 datetime")
- }
-}
-
-func TestTodoistClient_UsesAPIv1BaseURL(t *testing.T) {
- client := NewTodoistClient("test-key")
- const want = "https://api.todoist.com/api/v1"
- if client.BaseURL != want {
- t.Errorf("expected base URL %q, got %q — Todoist REST v2 is deprecated (HTTP 410)", want, client.BaseURL)
- }
-}
-
-func TestTodoistClient_CompleteTask(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)
- }
- expectedPath := "/tasks/task-123/close"
- if r.URL.Path != expectedPath {
- t.Errorf("Expected path %s, got %s", expectedPath, r.URL.Path)
- }
-
- // Verify Authorization header
- if !strings.HasPrefix(r.Header.Get("Authorization"), "Bearer ") {
- t.Errorf("Expected Bearer token in Authorization header")
- }
-
- // Return 204 No Content
- w.WriteHeader(http.StatusNoContent)
- }))
- defer server.Close()
-
- // Create client
- client := newTestTodoistClient(server.URL, "test-key")
-
- // Test CompleteTask
- ctx := context.Background()
- err := client.CompleteTask(ctx, "task-123")
-
- if err != nil {
- t.Fatalf("CompleteTask failed: %v", err)
- }
-}
-
-func TestTodoistClient_CompleteTask_Error(t *testing.T) {
- // Mock server that returns error
- server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- w.WriteHeader(http.StatusNotFound)
- _, _ = w.Write([]byte(`{"error":"Task not found"}`))
- }))
- defer server.Close()
-
- // Create client
- client := newTestTodoistClient(server.URL, "test-key")
-
- // Test CompleteTask with error
- ctx := context.Background()
- err := client.CompleteTask(ctx, "invalid-task")
-
- if err == nil {
- t.Error("Expected error, got nil")
- }
- if !strings.Contains(err.Error(), "404") {
- t.Errorf("Expected error to contain status 404, got: %v", err)
- }
-}
-
-func TestTodoistClient_GetProjects(t *testing.T) {
- // Mock server
- server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- // Verify method and path
- if r.Method != "GET" {
- t.Errorf("Expected GET request, got %s", r.Method)
- }
- if r.URL.Path != "/projects" {
- t.Errorf("Expected path /projects, got %s", r.URL.Path)
- }
-
- // Return mock response
- response := todoistProjectsPage{
- Results: []todoistProjectResponse{
- {ID: "proj-1", Name: "Project 1"},
- {ID: "proj-2", Name: "Project 2"},
- },
- }
-
- w.Header().Set("Content-Type", "application/json")
- _ = json.NewEncoder(w).Encode(response)
- }))
- defer server.Close()
-
- // Create client
- client := newTestTodoistClient(server.URL, "test-key")
-
- // Test GetProjects
- ctx := context.Background()
- projects, err := client.GetProjects(ctx)
-
- if err != nil {
- t.Fatalf("GetProjects failed: %v", err)
- }
-
- // Verify response
- if len(projects) != 2 {
- t.Errorf("Expected 2 projects, got %d", len(projects))
- }
-
- if projects[0].ID != "proj-1" || projects[0].Name != "Project 1" {
- t.Errorf("Project 1 mismatch: got ID=%s Name=%s", projects[0].ID, projects[0].Name)
- }
-
- if projects[1].ID != "proj-2" || projects[1].Name != "Project 2" {
- t.Errorf("Project 2 mismatch: got ID=%s Name=%s", projects[1].ID, projects[1].Name)
- }
-}
-
-func TestTodoistClient_GetTasks(t *testing.T) {
- server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- if r.Method != "GET" {
- t.Errorf("Expected GET, got %s", r.Method)
- }
-
- w.Header().Set("Content-Type", "application/json")
-
- // GetTasks also calls GetProjects internally
- if r.URL.Path == "/projects" {
- response := todoistProjectsPage{
- Results: []todoistProjectResponse{
- {ID: "proj-1", Name: "Project 1"},
- },
- }
- json.NewEncoder(w).Encode(response)
- return
- }
-
- if r.URL.Path == "/tasks" {
- response := todoistTasksPage{
- Results: []todoistTaskResponse{
- {ID: "task-1", Content: "Task 1", ProjectID: "proj-1", CreatedAt: time.Now().Format(time.RFC3339)},
- {ID: "task-2", Content: "Task 2", ProjectID: "proj-1", CreatedAt: time.Now().Format(time.RFC3339)},
- },
- }
- json.NewEncoder(w).Encode(response)
- return
- }
-
- t.Errorf("Unexpected path: %s", r.URL.Path)
- }))
- defer server.Close()
-
- client := newTestTodoistClient(server.URL, "test-key")
- tasks, err := client.GetTasks(context.Background())
- if err != nil {
- t.Fatalf("GetTasks failed: %v", err)
- }
-
- if len(tasks) != 2 {
- t.Errorf("Expected 2 tasks, got %d", len(tasks))
- }
-}
-
-func TestTodoistClient_ReopenTask(t *testing.T) {
- server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- if r.Method != "POST" {
- t.Errorf("Expected POST, got %s", r.Method)
- }
- expectedPath := "/tasks/task-123/reopen"
- if r.URL.Path != expectedPath {
- t.Errorf("Expected path %s, got %s", expectedPath, r.URL.Path)
- }
-
- w.WriteHeader(http.StatusNoContent)
- }))
- defer server.Close()
-
- client := newTestTodoistClient(server.URL, "test-key")
- err := client.ReopenTask(context.Background(), "task-123")
- if err != nil {
- t.Fatalf("ReopenTask failed: %v", err)
- }
-}
-
-func TestTodoistClient_UpdateTask(t *testing.T) {
- server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- if r.Method != "POST" {
- t.Errorf("Expected POST, got %s", r.Method)
- }
- expectedPath := "/tasks/task-123"
- if r.URL.Path != expectedPath {
- t.Errorf("Expected path %s, got %s", expectedPath, r.URL.Path)
- }
-
- var payload map[string]interface{}
- json.NewDecoder(r.Body).Decode(&payload)
- if payload["content"] != "Updated Content" {
- t.Errorf("Expected content 'Updated Content', got %v", payload["content"])
- }
-
- w.Header().Set("Content-Type", "application/json")
- json.NewEncoder(w).Encode(map[string]string{"id": "task-123"})
- }))
- defer server.Close()
-
- client := newTestTodoistClient(server.URL, "test-key")
- err := client.UpdateTask(context.Background(), "task-123", map[string]interface{}{"content": "Updated Content"})
- if err != nil {
- t.Fatalf("UpdateTask failed: %v", err)
- }
-}
-