diff options
| author | Peter Stone <thepeterstone@gmail.com> | 2026-03-22 23:45:19 +0000 |
|---|---|---|
| committer | Peter Stone <thepeterstone@gmail.com> | 2026-03-22 23:45:19 +0000 |
| commit | 8abc63efdbc0bb96cd6c9aa99d6e9166e0bcabae (patch) | |
| tree | f4d6a082eed9b10bc67436a3ca5188e0182961eb /internal/handlers/handlers_test.go | |
| parent | 11b905fd437d651b2e39745aa82a5dd36f70331e (diff) | |
chore: unify and centralize agent configuration in .agent/
Diffstat (limited to 'internal/handlers/handlers_test.go')
| -rw-r--r-- | internal/handlers/handlers_test.go | 216 |
1 files changed, 42 insertions, 174 deletions
diff --git a/internal/handlers/handlers_test.go b/internal/handlers/handlers_test.go index 793ccdd..0d097c8 100644 --- a/internal/handlers/handlers_test.go +++ b/internal/handlers/handlers_test.go @@ -15,7 +15,6 @@ import ( "github.com/go-chi/chi/v5" - "task-dashboard/internal/api" "task-dashboard/internal/config" "task-dashboard/internal/models" "task-dashboard/internal/store" @@ -118,31 +117,6 @@ func (m *mockTodoistClient) ReopenTask(ctx context.Context, taskID string) error return nil } -func (m *mockTodoistClient) Sync(ctx context.Context, syncToken string) (*api.TodoistSyncResponse, error) { - if m.err != nil { - return nil, m.err - } - // Return a mock sync response with tasks converted to sync items - items := make([]api.SyncItemResponse, 0, len(m.tasks)) - for _, task := range m.tasks { - items = append(items, api.SyncItemResponse{ - ID: task.ID, - Content: task.Content, - Description: task.Description, - ProjectID: task.ProjectID, - Priority: task.Priority, - Labels: task.Labels, - IsCompleted: task.Completed, - IsDeleted: false, - }) - } - return &api.TodoistSyncResponse{ - SyncToken: "test-sync-token", - FullSync: true, - Items: items, - Projects: []api.SyncProjectResponse{}, - }, nil -} // mockTrelloClient creates a mock Trello client for testing type mockTrelloClient struct { @@ -2236,210 +2210,104 @@ func TestHandleTimeline_InvalidParams(t *testing.T) { } } -// syncAwareMockTodoist records the sync token passed to Sync and returns a configurable response. -type syncAwareMockTodoist struct { - mockTodoistClient - syncResponse *api.TodoistSyncResponse - receivedTokens []string // tracks tokens passed to Sync -} - -func (m *syncAwareMockTodoist) Sync(ctx context.Context, syncToken string) (*api.TodoistSyncResponse, error) { - m.receivedTokens = append(m.receivedTokens, syncToken) - if m.err != nil { - return nil, m.err - } - return m.syncResponse, nil -} - -func TestFetchTasks_IncrementalSync_UpsertsActiveTasks(t *testing.T) { +func TestFetchTasks_FetchesAndSavesTasks(t *testing.T) { h, cleanup := setupTestHandler(t) defer cleanup() - // Seed DB with an existing task (simulating previous full sync) - existingTask := models.Task{ID: "existing-1", Content: "Old task", Priority: 1} - if err := h.store.SaveTasks([]models.Task{existingTask}); err != nil { - t.Fatalf("Failed to seed task: %v", err) - } - // Set a sync token so fetchTasks uses incremental sync - if err := h.store.SetSyncToken("todoist", "previous-token"); err != nil { - t.Fatalf("Failed to set sync token: %v", err) - } - - mock := &syncAwareMockTodoist{ - syncResponse: &api.TodoistSyncResponse{ - SyncToken: "new-token-1", - FullSync: false, - Items: []api.SyncItemResponse{ - {ID: "new-1", Content: "New task", Priority: 2}, - {ID: "existing-1", Content: "Updated task", Priority: 3}, - }, - Projects: []api.SyncProjectResponse{}, + h.todoistClient = &mockTodoistClient{ + tasks: []models.Task{ + {ID: "t1", Content: "Task one", Priority: 1}, + {ID: "t2", Content: "Task two", Priority: 2}, }, } - h.todoistClient = mock tasks, err := h.fetchTasks(context.Background(), false) if err != nil { t.Fatalf("fetchTasks returned error: %v", err) } - - // Should have 2 tasks: the upserted existing-1 (updated) and new-1 if len(tasks) != 2 { t.Fatalf("Expected 2 tasks, got %d", len(tasks)) } - // Verify the existing task was updated - taskMap := make(map[string]models.Task) - for _, task := range tasks { - taskMap[task.ID] = task - } - if taskMap["existing-1"].Content != "Updated task" { - t.Errorf("Expected existing-1 content 'Updated task', got %q", taskMap["existing-1"].Content) + // 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 taskMap["new-1"].Content != "New task" { - t.Errorf("Expected new-1 content 'New task', got %q", taskMap["new-1"].Content) + if len(stored) != 2 { + t.Errorf("Expected 2 stored tasks, got %d", len(stored)) } } -func TestFetchTasks_IncrementalSync_DeletesCompletedAndDeletedTasks(t *testing.T) { +func TestFetchTasks_ReturnsCachedTasksWhenValid(t *testing.T) { h, cleanup := setupTestHandler(t) defer cleanup() - // Seed DB with tasks - seeds := []models.Task{ - {ID: "keep-1", Content: "Keep me"}, - {ID: "complete-1", Content: "Will be completed"}, - {ID: "delete-1", Content: "Will be deleted"}, - } - if err := h.store.SaveTasks(seeds); err != nil { + // 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.SetSyncToken("todoist", "prev-token"); err != nil { - t.Fatalf("Failed to set sync token: %v", err) + if err := h.store.UpdateCacheMetadata(store.CacheKeyTodoistTasks, 60); err != nil { + t.Fatalf("Failed to set cache metadata: %v", err) } - mock := &syncAwareMockTodoist{ - syncResponse: &api.TodoistSyncResponse{ - SyncToken: "new-token-2", - FullSync: false, - Items: []api.SyncItemResponse{ - {ID: "complete-1", Content: "Will be completed", IsCompleted: true}, - {ID: "delete-1", Content: "Will be deleted", IsDeleted: true}, - }, - Projects: []api.SyncProjectResponse{}, - }, + // API would return different tasks — should not be called + h.todoistClient = &mockTodoistClient{ + tasks: []models.Task{{ID: "api-1", Content: "API task"}}, } - h.todoistClient = mock tasks, err := h.fetchTasks(context.Background(), false) if err != nil { t.Fatalf("fetchTasks returned error: %v", err) } - - // Only keep-1 should remain - if len(tasks) != 1 { - t.Fatalf("Expected 1 task, got %d: %+v", len(tasks), tasks) - } - if tasks[0].ID != "keep-1" { - t.Errorf("Expected remaining task ID 'keep-1', got %q", tasks[0].ID) + if len(tasks) != 1 || tasks[0].ID != "cached-1" { + t.Errorf("Expected cached task, got %+v", tasks) } } -func TestFetchTasks_IncrementalSync_StoresNewSyncToken(t *testing.T) { +func TestFetchTasks_ForceRefresh_BypassesCache(t *testing.T) { h, cleanup := setupTestHandler(t) defer cleanup() - if err := h.store.SetSyncToken("todoist", "old-token"); err != nil { - t.Fatalf("Failed to set sync token: %v", err) - } - - mock := &syncAwareMockTodoist{ - syncResponse: &api.TodoistSyncResponse{ - SyncToken: "brand-new-token", - FullSync: false, - Items: []api.SyncItemResponse{}, - Projects: []api.SyncProjectResponse{}, - }, - } - h.todoistClient = mock - - _, err := h.fetchTasks(context.Background(), false) - if err != nil { - t.Fatalf("fetchTasks returned error: %v", err) - } - - // Verify the new sync token was stored - token, err := h.store.GetSyncToken("todoist") - if err != nil { - t.Fatalf("Failed to get sync token: %v", err) + // 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 token != "brand-new-token" { - t.Errorf("Expected sync token 'brand-new-token', got %q", token) + if err := h.store.UpdateCacheMetadata(store.CacheKeyTodoistTasks, 60); err != nil { + t.Fatalf("Failed to set cache metadata: %v", err) } -} - -func TestFetchTasks_IncrementalSync_UsesSavedSyncToken(t *testing.T) { - h, cleanup := setupTestHandler(t) - defer cleanup() - // Set a known sync token - if err := h.store.SetSyncToken("todoist", "my-saved-token"); err != nil { - t.Fatalf("Failed to set sync token: %v", err) + h.todoistClient = &mockTodoistClient{ + tasks: []models.Task{{ID: "fresh-1", Content: "Fresh"}}, } - mock := &syncAwareMockTodoist{ - syncResponse: &api.TodoistSyncResponse{ - SyncToken: "next-token", - FullSync: false, - Items: []api.SyncItemResponse{}, - Projects: []api.SyncProjectResponse{}, - }, - } - h.todoistClient = mock - - _, err := h.fetchTasks(context.Background(), false) + tasks, err := h.fetchTasks(context.Background(), true) if err != nil { t.Fatalf("fetchTasks returned error: %v", err) } - - // Verify the saved token was passed to Sync - if len(mock.receivedTokens) != 1 { - t.Fatalf("Expected 1 Sync call, got %d", len(mock.receivedTokens)) - } - if mock.receivedTokens[0] != "my-saved-token" { - t.Errorf("Expected Sync to receive token 'my-saved-token', got %q", mock.receivedTokens[0]) + if len(tasks) != 1 || tasks[0].ID != "fresh-1" { + t.Errorf("Expected fresh task from API, got %+v", tasks) } } -func TestFetchTasks_ForceRefresh_ClearsSyncToken(t *testing.T) { +func TestFetchTasks_FallsBackToCacheOnAPIError(t *testing.T) { h, cleanup := setupTestHandler(t) defer cleanup() - if err := h.store.SetSyncToken("todoist", "existing-token"); err != nil { - t.Fatalf("Failed to set sync token: %v", err) + // 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) } - mock := &syncAwareMockTodoist{ - syncResponse: &api.TodoistSyncResponse{ - SyncToken: "fresh-token", - FullSync: true, - Items: []api.SyncItemResponse{}, - Projects: []api.SyncProjectResponse{}, - }, - } - h.todoistClient = mock + h.todoistClient = &mockTodoistClient{err: fmt.Errorf("API error")} - _, err := h.fetchTasks(context.Background(), true) + tasks, err := h.fetchTasks(context.Background(), false) if err != nil { - t.Fatalf("fetchTasks returned error: %v", err) - } - - // forceRefresh should send empty token (full sync) - if len(mock.receivedTokens) != 1 { - t.Fatalf("Expected 1 Sync call, got %d", len(mock.receivedTokens)) + t.Fatalf("Expected fallback to cache, got error: %v", err) } - if mock.receivedTokens[0] != "" { - t.Errorf("Expected empty sync token for forceRefresh, got %q", mock.receivedTokens[0]) + if len(tasks) != 1 || tasks[0].ID != "stale-1" { + t.Errorf("Expected stale cached task as fallback, got %+v", tasks) } } |
