summaryrefslogtreecommitdiff
path: root/internal/handlers/handlers_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/handlers/handlers_test.go')
-rw-r--r--internal/handlers/handlers_test.go802
1 files changed, 50 insertions, 752 deletions
diff --git a/internal/handlers/handlers_test.go b/internal/handlers/handlers_test.go
index b640c78..1f66378 100644
--- a/internal/handlers/handlers_test.go
+++ b/internal/handlers/handlers_test.go
@@ -84,40 +84,6 @@ func setupTestHandler(t *testing.T) (*Handler, func()) {
return h, cleanup
}
-// mockTodoistClient creates a mock Todoist client for testing
-type mockTodoistClient struct {
- tasks []models.Task
- err error
-}
-
-func (m *mockTodoistClient) GetTasks(ctx context.Context) ([]models.Task, error) {
- if m.err != nil {
- return nil, m.err
- }
- return m.tasks, nil
-}
-
-func (m *mockTodoistClient) GetProjects(ctx context.Context) ([]models.Project, error) {
- return []models.Project{}, nil
-}
-
-func (m *mockTodoistClient) CreateTask(ctx context.Context, content, projectID string, dueDate *time.Time, priority int) (*models.Task, error) {
- return nil, nil
-}
-
-func (m *mockTodoistClient) UpdateTask(ctx context.Context, taskID string, updates map[string]interface{}) error {
- return m.err
-}
-
-func (m *mockTodoistClient) CompleteTask(ctx context.Context, taskID string) error {
- return nil
-}
-
-func (m *mockTodoistClient) ReopenTask(ctx context.Context, taskID string) error {
- return nil
-}
-
-
// mockTrelloClient creates a mock Trello client for testing
type mockTrelloClient struct {
boards []models.Board
@@ -154,79 +120,6 @@ func (m *mockTrelloClient) UpdateCard(ctx context.Context, cardID string, update
return nil
}
-// TestHandleGetTasks tests the HandleGetTasks handler
-func TestHandleGetTasks(t *testing.T) {
- db, cleanup := setupTestDB(t)
- defer cleanup()
-
- // Create test tasks
- testTasks := []models.Task{
- {
- ID: "1",
- Content: "Test task 1",
- Description: "Description 1",
- ProjectID: "proj1",
- ProjectName: "Project 1",
- Priority: 1,
- Completed: false,
- Labels: []string{"label1"},
- URL: "https://todoist.com/task/1",
- CreatedAt: time.Now(),
- },
- {
- ID: "2",
- Content: "Test task 2",
- Description: "Description 2",
- ProjectID: "proj2",
- ProjectName: "Project 2",
- Priority: 2,
- Completed: true,
- Labels: []string{"label2"},
- URL: "https://todoist.com/task/2",
- CreatedAt: time.Now(),
- },
- }
-
- // Save tasks to database
- if err := db.SaveTasks(testTasks); err != nil {
- t.Fatalf("Failed to save test tasks: %v", err)
- }
-
- // Create handler with mock client
- cfg := &config.Config{
- CacheTTLMinutes: 5,
- }
- mockTodoist := &mockTodoistClient{}
- h := &Handler{
- store: db,
- todoistClient: mockTodoist,
- config: cfg,
- }
-
- // Create test request
- req := httptest.NewRequest("GET", "/api/tasks", nil)
- w := httptest.NewRecorder()
-
- // Execute handler
- h.HandleGetTasks(w, req)
-
- // Check response
- if w.Code != http.StatusOK {
- t.Errorf("Expected status 200, got %d", w.Code)
- }
-
- // Parse response
- var tasks []models.Task
- if err := json.NewDecoder(w.Body).Decode(&tasks); err != nil {
- t.Fatalf("Failed to decode response: %v", err)
- }
-
- // Verify tasks
- if len(tasks) != 2 {
- t.Errorf("Expected 2 tasks, got %d", len(tasks))
- }
-}
-
// TestHandleGetBoards tests the HandleGetBoards handler
func TestHandleGetBoards(t *testing.T) {
db, cleanup := setupTestDB(t)
@@ -309,16 +202,6 @@ func TestHandleRefresh(t *testing.T) {
db, cleanup := setupTestDB(t)
defer cleanup()
- // Create mock clients
- mockTodoist := &mockTodoistClient{
- tasks: []models.Task{
- {
- ID: "1",
- Content: "Test task",
- },
- },
- }
-
mockTrello := &mockTrelloClient{
boards: []models.Board{
{
@@ -333,10 +216,9 @@ func TestHandleRefresh(t *testing.T) {
CacheTTLMinutes: 5,
}
h := &Handler{
- store: db,
- todoistClient: mockTodoist,
- trelloClient: mockTrello,
- config: cfg,
+ store: db,
+ trelloClient: mockTrello,
+ config: cfg,
}
// Create test request
@@ -397,21 +279,6 @@ func TestHandleGetMeals(t *testing.T) {
}
}
-// mockTodoistClientWithComplete tracks CompleteTask calls
-type mockTodoistClientWithComplete struct {
- mockTodoistClient
- completedTaskIDs []string
- completeErr error
-}
-
-func (m *mockTodoistClientWithComplete) CompleteTask(ctx context.Context, taskID string) error {
- if m.completeErr != nil {
- return m.completeErr
- }
- m.completedTaskIDs = append(m.completedTaskIDs, taskID)
- return nil
-}
-
// mockTrelloClientWithUpdate tracks UpdateCard calls
type mockTrelloClientWithUpdate struct {
mockTrelloClient
@@ -427,138 +294,6 @@ func (m *mockTrelloClientWithUpdate) UpdateCard(ctx context.Context, cardID stri
return nil
}
-// TestHandleCompleteAtom_Todoist tests completing a Todoist task
-func TestHandleCompleteAtom_Todoist(t *testing.T) {
- db, cleanup := setupTestDB(t)
- defer cleanup()
-
- // Save a task to the cache
- tasks := []models.Task{
- {
- ID: "task123",
- Content: "Test task",
- Completed: false,
- Labels: []string{},
- CreatedAt: time.Now(),
- },
- }
- if err := db.SaveTasks(tasks); err != nil {
- t.Fatalf("Failed to save test task: %v", err)
- }
-
- // Verify task exists in cache
- cachedTasks, _ := db.GetTasks()
- if len(cachedTasks) != 1 {
- t.Fatalf("Expected 1 task in cache, got %d", len(cachedTasks))
- }
-
- // Create handler with mock client
- mockTodoist := &mockTodoistClientWithComplete{}
- h := &Handler{
- store: db,
- todoistClient: mockTodoist,
- config: &config.Config{},
- renderer: newTestRenderer(),
- }
-
- // Create request
- req := httptest.NewRequest("POST", "/complete-atom", nil)
- req.Form = map[string][]string{
- "id": {"task123"},
- "source": {"todoist"},
- }
- w := httptest.NewRecorder()
-
- // Execute handler
- h.HandleCompleteAtom(w, req)
-
- // Check response
- if w.Code != http.StatusOK {
- t.Errorf("Expected status 200, got %d", w.Code)
- }
-
- // Verify response body contains rendered completed-atom template
- body := w.Body.String()
- if !strings.Contains(body, "rendered:completed-atom") {
- t.Errorf("Expected response body to contain 'rendered:completed-atom', got %q", body)
- }
-
- // Verify Content-Type header
- ct := w.Header().Get("Content-Type")
- if ct != "text/html; charset=utf-8" {
- t.Errorf("Expected Content-Type 'text/html; charset=utf-8', got %q", ct)
- }
-
- // Verify CompleteTask was called on the API
- if len(mockTodoist.completedTaskIDs) != 1 || mockTodoist.completedTaskIDs[0] != "task123" {
- t.Errorf("Expected CompleteTask to be called with 'task123', got %v", mockTodoist.completedTaskIDs)
- }
-
- // Verify task was deleted from cache
- cachedTasks, _ = db.GetTasks()
- if len(cachedTasks) != 0 {
- t.Errorf("Expected task to be deleted from cache, but found %d tasks", len(cachedTasks))
- }
-}
-
-func TestHandleCompleteAtom_RendersCorrectTemplateData(t *testing.T) {
- db, cleanup := setupTestDB(t)
- defer cleanup()
-
- // Seed a task with known title
- tasks := []models.Task{
- {ID: "task-data-1", Content: "Buy groceries", Labels: []string{}, CreatedAt: time.Now()},
- }
- if err := db.SaveTasks(tasks); err != nil {
- t.Fatalf("Failed to seed: %v", err)
- }
-
- renderer := NewMockRenderer()
- h := &Handler{
- store: db,
- todoistClient: &mockTodoistClientWithComplete{},
- config: &config.Config{},
- renderer: renderer,
- }
-
- req := httptest.NewRequest("POST", "/complete-atom", nil)
- req.Form = map[string][]string{"id": {"task-data-1"}, "source": {"todoist"}}
- w := httptest.NewRecorder()
-
- h.HandleCompleteAtom(w, req)
-
- // Verify renderer was called with "completed-atom" and correct data
- if len(renderer.Calls) != 1 {
- t.Fatalf("Expected 1 render call, got %d", len(renderer.Calls))
- }
- call := renderer.Calls[0]
- if call.Name != "completed-atom" {
- t.Errorf("Expected template 'completed-atom', got %q", call.Name)
- }
-
- // The data should contain the task ID, source, and title
- type atomData struct {
- ID string
- Source string
- Title string
- }
- // Use JSON round-trip to extract fields from the anonymous struct
- jsonBytes, _ := json.Marshal(call.Data)
- var got atomData
- if err := json.Unmarshal(jsonBytes, &got); err != nil {
- t.Fatalf("Failed to unmarshal render data: %v", err)
- }
- if got.ID != "task-data-1" {
- t.Errorf("Expected data.ID 'task-data-1', got %q", got.ID)
- }
- if got.Source != "todoist" {
- t.Errorf("Expected data.Source 'todoist', got %q", got.Source)
- }
- if got.Title != "Buy groceries" {
- t.Errorf("Expected data.Title 'Buy groceries', got %q", got.Title)
- }
-}
-
// TestHandleCompleteAtom_Trello tests completing a Trello card
func TestHandleCompleteAtom_Trello(t *testing.T) {
db, cleanup := setupTestDB(t)
@@ -641,7 +376,7 @@ func TestHandleCompleteAtom_MissingParams(t *testing.T) {
id string
source string
}{
- {"missing id", "", "todoist"},
+ {"missing id", "", "doot"},
{"missing source", "task123", ""},
{"both missing", "", ""},
}
@@ -688,56 +423,6 @@ func TestHandleCompleteAtom_UnknownSource(t *testing.T) {
}
}
-// TestHandleCompleteAtom_APIError tests that API errors are handled correctly
-func TestHandleCompleteAtom_APIError(t *testing.T) {
- db, cleanup := setupTestDB(t)
- defer cleanup()
-
- // Save a task to the cache
- tasks := []models.Task{
- {
- ID: "task123",
- Content: "Test task",
- Completed: false,
- Labels: []string{},
- CreatedAt: time.Now(),
- },
- }
- if err := db.SaveTasks(tasks); err != nil {
- t.Fatalf("Failed to save test task: %v", err)
- }
-
- // Create handler with mock client that returns an error
- mockTodoist := &mockTodoistClientWithComplete{
- completeErr: context.DeadlineExceeded,
- }
- h := &Handler{
- store: db,
- todoistClient: mockTodoist,
- config: &config.Config{},
- }
-
- req := httptest.NewRequest("POST", "/complete-atom", nil)
- req.Form = map[string][]string{
- "id": {"task123"},
- "source": {"todoist"},
- }
- w := httptest.NewRecorder()
-
- h.HandleCompleteAtom(w, req)
-
- // Should return 500 on API error
- if w.Code != http.StatusInternalServerError {
- t.Errorf("Expected status 500 on API error, got %d", w.Code)
- }
-
- // Verify task was NOT deleted from cache (rollback behavior)
- cachedTasks, _ := db.GetTasks()
- if len(cachedTasks) != 1 {
- t.Errorf("Task should NOT be deleted from cache on API error, found %d tasks", len(cachedTasks))
- }
-}
-
// TestHandleShoppingModeComplete tests completing (removing) shopping items
func TestHandleShoppingModeComplete(t *testing.T) {
db, cleanup := setupTestDB(t)
@@ -1354,12 +1039,6 @@ func TestHandleTabTasks(t *testing.T) {
config: &config.Config{CacheTTLMinutes: 5},
}
- // Add some tasks
- tasks := []models.Task{
- {ID: "1", Content: "Task 1", Labels: []string{}, CreatedAt: time.Now()},
- }
- _ = db.SaveTasks(tasks)
-
req := httptest.NewRequest("GET", "/tabs/tasks", nil)
w := httptest.NewRecorder()
@@ -1599,69 +1278,6 @@ func TestHTMLString(t *testing.T) {
// Uncomplete Atom Tests
// =============================================================================
-// mockTodoistClientWithReopen tracks ReopenTask calls
-type mockTodoistClientWithReopen struct {
- mockTodoistClient
- reopenedTaskIDs []string
- reopenErr error
-}
-
-func (m *mockTodoistClientWithReopen) ReopenTask(ctx context.Context, taskID string) error {
- if m.reopenErr != nil {
- return m.reopenErr
- }
- m.reopenedTaskIDs = append(m.reopenedTaskIDs, taskID)
- return nil
-}
-
-func TestHandleUncompleteAtom_Todoist(t *testing.T) {
- db, cleanup := setupTestDB(t)
- defer cleanup()
-
- mockTodoist := &mockTodoistClientWithReopen{}
- h := &Handler{
- store: db,
- todoistClient: mockTodoist,
- config: &config.Config{},
- renderer: newTestRenderer(),
- }
-
- req := httptest.NewRequest("POST", "/uncomplete-atom", nil)
- req.Form = map[string][]string{
- "id": {"task123"},
- "source": {"todoist"},
- }
- w := httptest.NewRecorder()
-
- h.HandleUncompleteAtom(w, req)
-
- if w.Code != http.StatusOK {
- t.Errorf("Expected status 200, got %d", w.Code)
- }
-
- // Verify HX-Reswap header set to "none"
- reswap := w.Header().Get("HX-Reswap")
- if reswap != "none" {
- t.Errorf("Expected HX-Reswap header 'none', got %q", reswap)
- }
-
- // Verify HX-Trigger header set to "refresh-tasks"
- trigger := w.Header().Get("HX-Trigger")
- if trigger != "refresh-tasks" {
- t.Errorf("Expected HX-Trigger header 'refresh-tasks', got %q", trigger)
- }
-
- // Verify response body is empty (no template rendered)
- body := w.Body.String()
- if body != "" {
- t.Errorf("Expected empty response body for uncomplete, got %q", body)
- }
-
- if len(mockTodoist.reopenedTaskIDs) != 1 || mockTodoist.reopenedTaskIDs[0] != "task123" {
- t.Errorf("Expected ReopenTask to be called with 'task123', got %v", mockTodoist.reopenedTaskIDs)
- }
-}
-
// =============================================================================
// Settings Handler Tests
// =============================================================================
@@ -1769,16 +1385,6 @@ func TestHandleGetSourceOptions_MissingSource(t *testing.T) {
}
}
-// mockTodoistClientWithProjects returns mock projects
-type mockTodoistClientWithProjects struct {
- mockTodoistClient
- projects []models.Project
-}
-
-func (m *mockTodoistClientWithProjects) GetProjects(ctx context.Context) ([]models.Project, error) {
- return m.projects, nil
-}
-
// mockTrelloClientWithBoards returns mock boards
type mockTrelloClientWithBoards struct {
mockTrelloClient
@@ -1788,14 +1394,6 @@ func TestHandleSyncSources(t *testing.T) {
h, cleanup := setupTestHandler(t)
defer cleanup()
- // Seed tasks table with project info (projects now come from store, not REST API)
- tasks := []models.Task{
- {ID: "t1", Content: "Task 1", ProjectID: "proj1", ProjectName: "Project 1", Labels: []string{}},
- {ID: "t2", Content: "Task 2", ProjectID: "proj2", ProjectName: "Project 2", Labels: []string{}},
- }
- _ = h.store.SaveTasks(tasks)
-
- mockTodoist := &mockTodoistClient{}
mockTrello := &mockTrelloClientWithBoards{
mockTrelloClient: mockTrelloClient{
boards: []models.Board{
@@ -1804,7 +1402,6 @@ func TestHandleSyncSources(t *testing.T) {
},
}
- h.todoistClient = mockTodoist
h.trelloClient = mockTrello
req := httptest.NewRequest("POST", "/settings/sync", nil)
@@ -1816,10 +1413,10 @@ func TestHandleSyncSources(t *testing.T) {
t.Errorf("Expected status 200, got %d", w.Code)
}
- // Verify configs were synced from store (not REST API)
- configs, _ := h.store.GetSourceConfigsBySource("todoist")
- if len(configs) != 2 {
- t.Errorf("Expected 2 todoist configs, got %d", len(configs))
+ // Verify Trello configs were synced
+ configs, _ := h.store.GetSourceConfigsBySource("trello")
+ if len(configs) != 1 {
+ t.Errorf("Expected 1 trello config, got %d", len(configs))
}
}
@@ -1921,14 +1518,6 @@ func TestHandleTimeline_RendersDataToTemplate(t *testing.T) {
h, cleanup := setupTestHandler(t)
defer cleanup()
- // Seed tasks with due dates in range
- now := config.Now()
- todayNoon := time.Date(now.Year(), now.Month(), now.Day(), 12, 0, 0, 0, now.Location())
- tasks := []models.Task{
- {ID: "t1", Content: "Today task", DueDate: &todayNoon, Labels: []string{}, CreatedAt: now},
- }
- _ = h.store.SaveTasks(tasks)
-
req := httptest.NewRequest("GET", "/tabs/timeline", nil)
w := httptest.NewRecorder()
@@ -1949,14 +1538,10 @@ func TestHandleTimeline_RendersDataToTemplate(t *testing.T) {
t.Errorf("Expected template 'timeline-tab', got '%s'", lastCall.Name)
}
- data, ok := lastCall.Data.(TimelineData)
+ _, ok := lastCall.Data.(TimelineData)
if !ok {
t.Fatalf("Expected TimelineData, got %T", lastCall.Data)
}
-
- if len(data.TodayItems) == 0 {
- t.Error("Expected TodayItems to contain at least one item, got 0")
- }
}
// assertTemplateContains reads a template file and asserts it contains the expected string.
@@ -1979,7 +1564,6 @@ func TestHandleDashboard_ContainsSettingsLink(t *testing.T) {
db, cleanup := setupTestDB(t)
defer cleanup()
- mockTodoist := &mockTodoistClient{}
mockTrello := &mockTrelloClient{}
// Use a renderer that outputs actual content so we can check for settings link
@@ -1994,11 +1578,10 @@ func TestHandleDashboard_ContainsSettingsLink(t *testing.T) {
}
h := &Handler{
- store: db,
- todoistClient: mockTodoist,
- trelloClient: mockTrello,
- config: &config.Config{CacheTTLMinutes: 5},
- renderer: renderer,
+ store: db,
+ trelloClient: mockTrello,
+ config: &config.Config{CacheTTLMinutes: 5},
+ renderer: renderer,
}
req := httptest.NewRequest("GET", "/", nil)
@@ -2112,17 +1695,15 @@ func TestHandleDashboard_PassesBuildVersion(t *testing.T) {
db, cleanup := setupTestDB(t)
defer cleanup()
- mockTodoist := &mockTodoistClient{}
mockTrello := &mockTrelloClient{}
renderer := NewMockRenderer()
h := &Handler{
- store: db,
- todoistClient: mockTodoist,
- trelloClient: mockTrello,
- config: &config.Config{CacheTTLMinutes: 5},
- renderer: renderer,
- BuildVersion: "abc123",
+ store: db,
+ trelloClient: mockTrello,
+ config: &config.Config{CacheTTLMinutes: 5},
+ renderer: renderer,
+ BuildVersion: "abc123",
}
req := httptest.NewRequest("GET", "/", nil)
@@ -2210,123 +1791,20 @@ func TestHandleTimeline_InvalidParams(t *testing.T) {
}
}
-func TestFetchTasks_FetchesAndSavesTasks(t *testing.T) {
- h, cleanup := setupTestHandler(t)
- defer cleanup()
-
- h.todoistClient = &mockTodoistClient{
- tasks: []models.Task{
- {ID: "t1", Content: "Task one", Priority: 1},
- {ID: "t2", Content: "Task two", Priority: 2},
- },
- }
-
- tasks, err := h.fetchTasks(context.Background(), false)
- if err != nil {
- t.Fatalf("fetchTasks returned error: %v", err)
- }
- if len(tasks) != 2 {
- t.Fatalf("Expected 2 tasks, got %d", len(tasks))
- }
-
- // Verify tasks are persisted in the store
- stored, err := h.store.GetTasks()
- if err != nil {
- t.Fatalf("Failed to get tasks from store: %v", err)
- }
- if len(stored) != 2 {
- t.Errorf("Expected 2 stored tasks, got %d", len(stored))
- }
-}
-
-func TestFetchTasks_ReturnsCachedTasksWhenValid(t *testing.T) {
- h, cleanup := setupTestHandler(t)
- defer cleanup()
-
- // Seed cache with tasks and mark it valid
- cached := []models.Task{{ID: "cached-1", Content: "Cached task"}}
- if err := h.store.SaveTasks(cached); err != nil {
- t.Fatalf("Failed to seed tasks: %v", err)
- }
- if err := h.store.UpdateCacheMetadata(store.CacheKeyTodoistTasks, 60); err != nil {
- t.Fatalf("Failed to set cache metadata: %v", err)
- }
-
- // API would return different tasks — should not be called
- h.todoistClient = &mockTodoistClient{
- tasks: []models.Task{{ID: "api-1", Content: "API task"}},
- }
-
- tasks, err := h.fetchTasks(context.Background(), false)
- if err != nil {
- t.Fatalf("fetchTasks returned error: %v", err)
- }
- if len(tasks) != 1 || tasks[0].ID != "cached-1" {
- t.Errorf("Expected cached task, got %+v", tasks)
- }
-}
-
-func TestFetchTasks_ForceRefresh_BypassesCache(t *testing.T) {
- h, cleanup := setupTestHandler(t)
- defer cleanup()
-
- // Seed cache and mark it valid
- if err := h.store.SaveTasks([]models.Task{{ID: "old-1", Content: "Old"}}); err != nil {
- t.Fatalf("Failed to seed tasks: %v", err)
- }
- if err := h.store.UpdateCacheMetadata(store.CacheKeyTodoistTasks, 60); err != nil {
- t.Fatalf("Failed to set cache metadata: %v", err)
- }
-
- h.todoistClient = &mockTodoistClient{
- tasks: []models.Task{{ID: "fresh-1", Content: "Fresh"}},
- }
-
- tasks, err := h.fetchTasks(context.Background(), true)
- if err != nil {
- t.Fatalf("fetchTasks returned error: %v", err)
- }
- if len(tasks) != 1 || tasks[0].ID != "fresh-1" {
- t.Errorf("Expected fresh task from API, got %+v", tasks)
- }
-}
-
-func TestFetchTasks_FallsBackToCacheOnAPIError(t *testing.T) {
- h, cleanup := setupTestHandler(t)
- defer cleanup()
-
- // Seed stale cache
- if err := h.store.SaveTasks([]models.Task{{ID: "stale-1", Content: "Stale"}}); err != nil {
- t.Fatalf("Failed to seed tasks: %v", err)
- }
-
- h.todoistClient = &mockTodoistClient{err: fmt.Errorf("API error")}
-
- tasks, err := h.fetchTasks(context.Background(), false)
- if err != nil {
- t.Fatalf("Expected fallback to cache, got error: %v", err)
- }
- if len(tasks) != 1 || tasks[0].ID != "stale-1" {
- t.Errorf("Expected stale cached task as fallback, got %+v", tasks)
- }
-}
-
// =============================================================================
-// Clear Cache + GetProjects removal tests
+// Clear Cache tests
// =============================================================================
// TestHandleClearCache verifies that POST /settings/clear-cache invalidates
-// all cache keys, clears the todoist sync token, and returns 200 with HX-Trigger.
+// all cache keys and returns 200 with HX-Trigger.
func TestHandleClearCache(t *testing.T) {
h, cleanup := setupTestHandler(t)
defer cleanup()
- // Seed cache metadata and sync token so we can verify they get cleared
- _ = h.store.UpdateCacheMetadata("todoist_tasks", 5)
+ // Seed cache metadata so we can verify it gets cleared
_ = h.store.UpdateCacheMetadata("trello_boards", 5)
_ = h.store.UpdateCacheMetadata("plantoeat_meals", 5)
_ = h.store.UpdateCacheMetadata("google_calendar", 5)
- _ = h.store.SetSyncToken("todoist", "some-sync-token")
req := httptest.NewRequest("POST", "/settings/clear-cache", nil)
w := httptest.NewRecorder()
@@ -2343,62 +1821,20 @@ func TestHandleClearCache(t *testing.T) {
}
// Verify all cache keys invalidated
- for _, key := range []string{"todoist_tasks", "trello_boards", "plantoeat_meals", "google_calendar"} {
+ for _, key := range []string{"trello_boards", "plantoeat_meals", "google_calendar"} {
valid, _ := h.store.IsCacheValid(key)
if valid {
t.Errorf("Cache key %q should be invalidated but is still valid", key)
}
}
-
- // Verify sync token cleared
- token, _ := h.store.GetSyncToken("todoist")
- if token != "" {
- t.Errorf("Expected empty sync token after clear cache, got %q", token)
- }
}
-// TestGetProjectsFromTasks verifies that distinct project pairs are extracted
-// from the tasks table.
-func TestGetProjectsFromTasks(t *testing.T) {
- h, cleanup := setupTestHandler(t)
- defer cleanup()
-
- // Seed tasks with different projects (including duplicates and empty)
- tasks := []models.Task{
- {ID: "1", Content: "Task 1", ProjectID: "p1", ProjectName: "Inbox", Labels: []string{}},
- {ID: "2", Content: "Task 2", ProjectID: "p1", ProjectName: "Inbox", Labels: []string{}},
- {ID: "3", Content: "Task 3", ProjectID: "p2", ProjectName: "Work", Labels: []string{}},
- {ID: "4", Content: "Task 4", ProjectID: "", ProjectName: "", Labels: []string{}},
- }
- if err := h.store.SaveTasks(tasks); err != nil {
- t.Fatalf("Failed to save tasks: %v", err)
- }
-
- projects, err := h.store.GetProjectsFromTasks()
- if err != nil {
- t.Fatalf("GetProjectsFromTasks returned error: %v", err)
- }
-
- if len(projects) != 2 {
- t.Fatalf("Expected 2 distinct projects, got %d: %v", len(projects), projects)
- }
-
- // Verify both projects are present
- found := map[string]bool{}
- for _, p := range projects {
- found[p.ID] = true
- }
- if !found["p1"] || !found["p2"] {
- t.Errorf("Expected projects p1 and p2, got %v", projects)
- }
-}
-
-// TestInvalidateAllCaches verifies the convenience method clears all 4 keys.
+// TestInvalidateAllCaches verifies the convenience method clears all cache keys.
func TestInvalidateAllCaches(t *testing.T) {
h, cleanup := setupTestHandler(t)
defer cleanup()
- keys := []string{"todoist_tasks", "trello_boards", "plantoeat_meals", "google_calendar"}
+ keys := []string{"trello_boards", "plantoeat_meals", "google_calendar"}
for _, key := range keys {
_ = h.store.UpdateCacheMetadata(key, 5)
}
@@ -2423,116 +1859,6 @@ func TestSettingsTemplate_HasClearCacheButton(t *testing.T) {
"settings.html must contain a clear-cache button/endpoint")
}
-// TestAggregateData_DoesNotCallGetProjects verifies that aggregateData no longer
-// calls the deprecated GetProjects REST API.
-func TestAggregateData_DoesNotCallGetProjects(t *testing.T) {
- h, cleanup := setupTestHandler(t)
- defer cleanup()
-
- getProjectsCalled := false
- mock := &mockTodoistClientTracksGetProjects{
- mockTodoistClient: mockTodoistClient{
- tasks: []models.Task{
- {ID: "1", Content: "Test", ProjectID: "p1", ProjectName: "Inbox", Labels: []string{}},
- },
- },
- onGetProjects: func() { getProjectsCalled = true },
- }
- h.todoistClient = mock
- h.trelloClient = &mockTrelloClient{boards: []models.Board{}}
-
- _, err := h.aggregateData(context.Background(), false)
- if err != nil {
- t.Fatalf("aggregateData returned error: %v", err)
- }
-
- if getProjectsCalled {
- t.Error("aggregateData should NOT call GetProjects (deprecated REST API)")
- }
-}
-
-// TestHandleCreateTask_DoesNotCallGetProjects verifies that HandleCreateTask
-// populates projects from the store, not the REST API.
-func TestHandleCreateTask_DoesNotCallGetProjects(t *testing.T) {
- h, cleanup := setupTestHandler(t)
- defer cleanup()
-
- // Seed tasks with project info
- tasks := []models.Task{
- {ID: "1", Content: "Existing", ProjectID: "p1", ProjectName: "Inbox", Labels: []string{}},
- }
- _ = h.store.SaveTasks(tasks)
-
- getProjectsCalled := false
- mock := &mockTodoistClientTracksGetProjects{
- mockTodoistClient: mockTodoistClient{
- tasks: tasks,
- },
- onGetProjects: func() { getProjectsCalled = true },
- }
- h.todoistClient = mock
-
- body := strings.NewReader("content=New+Task&project_id=p1")
- req := httptest.NewRequest("POST", "/tasks", body)
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- w := httptest.NewRecorder()
-
- h.HandleCreateTask(w, req)
-
- if getProjectsCalled {
- t.Error("HandleCreateTask should NOT call GetProjects (deprecated REST API)")
- }
-}
-
-// TestHandleSyncSources_UsesStoreForProjects verifies that HandleSyncSources
-// reads projects from the tasks table instead of the REST API.
-func TestHandleSyncSources_UsesStoreForProjects(t *testing.T) {
- h, cleanup := setupTestHandler(t)
- defer cleanup()
-
- // Seed tasks with project info
- tasks := []models.Task{
- {ID: "1", Content: "Task1", ProjectID: "p1", ProjectName: "Inbox", Labels: []string{}},
- {ID: "2", Content: "Task2", ProjectID: "p2", ProjectName: "Work", Labels: []string{}},
- }
- _ = h.store.SaveTasks(tasks)
-
- getProjectsCalled := false
- mock := &mockTodoistClientTracksGetProjects{
- mockTodoistClient: mockTodoistClient{tasks: tasks},
- onGetProjects: func() { getProjectsCalled = true },
- }
- h.todoistClient = mock
-
- req := httptest.NewRequest("POST", "/settings/sync", nil)
- w := httptest.NewRecorder()
-
- h.HandleSyncSources(w, req)
-
- if getProjectsCalled {
- t.Error("HandleSyncSources should NOT call GetProjects (deprecated REST API)")
- }
-
- // Verify todoist project configs were still synced from store
- configs, _ := h.store.GetSourceConfigsBySource("todoist")
- if len(configs) != 2 {
- t.Errorf("Expected 2 todoist configs from store, got %d", len(configs))
- }
-}
-
-// mockTodoistClientTracksGetProjects wraps mockTodoistClient and tracks GetProjects calls.
-type mockTodoistClientTracksGetProjects struct {
- mockTodoistClient
- onGetProjects func()
-}
-
-func (m *mockTodoistClientTracksGetProjects) GetProjects(ctx context.Context) ([]models.Project, error) {
- if m.onGetProjects != nil {
- m.onGetProjects()
- }
- return []models.Project{}, nil
-}
-
// --- Sync Log Tests ---
// TestStore_AddAndGetSyncLog verifies that sync log entries can be added and retrieved.
@@ -2678,7 +2004,7 @@ func TestHandleGetTaskDetail_RendersTemplate(t *testing.T) {
h, cleanup := setupTestHandler(t)
defer cleanup()
- req := httptest.NewRequest("GET", "/tasks/detail?id=abc&source=todoist", nil)
+ req := httptest.NewRequest("GET", "/tasks/detail?id=abc&source=doot", nil)
w := httptest.NewRecorder()
h.HandleGetTaskDetail(w, req)
@@ -2749,7 +2075,7 @@ func TestHandleSyncSources_AddsLogEntry(t *testing.T) {
// HandleTabPlanning data tests
// =============================================================================
-// TestHandleTabPlanning_HappyPath verifies that tasks, events, and cards are
+// TestHandleTabPlanning_HappyPath verifies that cards are
// placed into the correct planning sections (scheduled/unscheduled/upcoming).
func TestHandleTabPlanning_HappyPath(t *testing.T) {
db, cleanup := setupTestDB(t)
@@ -2757,29 +2083,16 @@ func TestHandleTabPlanning_HappyPath(t *testing.T) {
today := config.Today()
- // Task at 10am today (has time) → goes to Scheduled
- taskWithTime := today.Add(10 * time.Hour)
- // Task at midnight today (no time component) → goes to Unscheduled
- taskNoTime := today
- // Task 2 days from now → goes to Upcoming (today+4 is the upper bound)
- taskUpcoming := today.AddDate(0, 0, 2)
-
- tasks := []models.Task{
- {ID: "t-sched", Content: "Scheduled Task", DueDate: &taskWithTime, Labels: []string{}, CreatedAt: time.Now()},
- {ID: "t-unsched", Content: "Unscheduled Task", DueDate: &taskNoTime, Labels: []string{}, CreatedAt: time.Now()},
- {ID: "t-upcoming", Content: "Upcoming Task", DueDate: &taskUpcoming, Labels: []string{}, CreatedAt: time.Now()},
- }
- if err := db.SaveTasks(tasks); err != nil {
- t.Fatalf("Failed to save tasks: %v", err)
- }
-
// Card at 2pm today → goes to Scheduled
cardWithTime := today.Add(14 * time.Hour)
+ // Card 2 days from now → goes to Upcoming
+ cardUpcoming := today.AddDate(0, 0, 2)
boards := []models.Board{{
ID: "board1",
Name: "Board 1",
Cards: []models.Card{
{ID: "c-sched", Name: "Scheduled Card", DueDate: &cardWithTime, URL: "http://trello.com/c1"},
+ {ID: "c-upcoming", Name: "Upcoming Card", DueDate: &cardUpcoming, URL: "http://trello.com/c2"},
},
}}
if err := db.SaveBoards(boards); err != nil {
@@ -2818,65 +2131,51 @@ func TestHandleTabPlanning_HappyPath(t *testing.T) {
Unscheduled []models.Atom
Upcoming []ScheduledItem
Boards []models.Board
- Projects []models.Project
Today string
})
if !ok {
t.Fatalf("Expected planning data struct, got %T", planningCall.Data)
}
- // t-sched (10am) and c-sched (2pm) should be in Scheduled
- var foundTaskSched, foundCardSched bool
+ // c-sched (2pm) should be in Scheduled
+ var foundCardSched bool
for _, s := range data.Scheduled {
- if s.ID == "t-sched" {
- foundTaskSched = true
- }
if s.ID == "c-sched" {
foundCardSched = true
}
}
- if !foundTaskSched {
- t.Error("Expected t-sched (task with time today) in Scheduled")
- }
if !foundCardSched {
t.Error("Expected c-sched (card with time today) in Scheduled")
}
- // t-unsched (midnight, no time) should be in Unscheduled
- var foundUnsched bool
- for _, u := range data.Unscheduled {
- if u.ID == "t-unsched" {
- foundUnsched = true
- }
- }
- if !foundUnsched {
- t.Error("Expected t-unsched (midnight task) in Unscheduled")
- }
-
- // t-upcoming (2 days out) should be in Upcoming
+ // c-upcoming (2 days out) should be in Upcoming
var foundUpcoming bool
for _, u := range data.Upcoming {
- if u.ID == "t-upcoming" {
+ if u.ID == "c-upcoming" {
foundUpcoming = true
}
}
if !foundUpcoming {
- t.Error("Expected t-upcoming (task in 2 days) in Upcoming")
+ t.Error("Expected c-upcoming (card in 2 days) in Upcoming")
}
}
-// TestHandleTabPlanning_TomorrowBoundary verifies that a task due exactly at
+// TestHandleTabPlanning_TomorrowBoundary verifies that a card due exactly at
// midnight of "tomorrow" is NOT before tomorrow, so it lands in Upcoming.
func TestHandleTabPlanning_TomorrowBoundary(t *testing.T) {
db, cleanup := setupTestDB(t)
defer cleanup()
tomorrow := config.Today().AddDate(0, 0, 1) // midnight tomorrow
- tasks := []models.Task{
- {ID: "t-boundary", Content: "Midnight Tomorrow", DueDate: &tomorrow, Labels: []string{}, CreatedAt: time.Now()},
- }
- if err := db.SaveTasks(tasks); err != nil {
- t.Fatalf("Failed to save tasks: %v", err)
+ boards := []models.Board{{
+ ID: "board1",
+ Name: "Board 1",
+ Cards: []models.Card{
+ {ID: "c-boundary", Name: "Midnight Tomorrow", DueDate: &tomorrow, URL: "http://trello.com/c1"},
+ },
+ }}
+ if err := db.SaveBoards(boards); err != nil {
+ t.Fatalf("Failed to save boards: %v", err)
}
renderer := NewMockRenderer()
@@ -2911,7 +2210,6 @@ func TestHandleTabPlanning_TomorrowBoundary(t *testing.T) {
Unscheduled []models.Atom
Upcoming []ScheduledItem
Boards []models.Board
- Projects []models.Project
Today string
})
if !ok {
@@ -2920,24 +2218,24 @@ func TestHandleTabPlanning_TomorrowBoundary(t *testing.T) {
// midnight-tomorrow: !dueDate.Before(tomorrow) → not in scheduled/unscheduled
for _, s := range data.Scheduled {
- if s.ID == "t-boundary" {
- t.Error("Midnight-tomorrow task must not appear in Scheduled")
+ if s.ID == "c-boundary" {
+ t.Error("Midnight-tomorrow card must not appear in Scheduled")
}
}
for _, u := range data.Unscheduled {
- if u.ID == "t-boundary" {
- t.Error("Midnight-tomorrow task must not appear in Unscheduled")
+ if u.ID == "c-boundary" {
+ t.Error("Midnight-tomorrow card must not appear in Unscheduled")
}
}
// dueDate.Before(in3Days=today+4) → lands in Upcoming
var foundUpcoming bool
for _, u := range data.Upcoming {
- if u.ID == "t-boundary" {
+ if u.ID == "c-boundary" {
foundUpcoming = true
}
}
if !foundUpcoming {
- t.Error("Expected midnight-tomorrow task in Upcoming")
+ t.Error("Expected midnight-tomorrow card in Upcoming")
}
}