summaryrefslogtreecommitdiff
path: root/internal/handlers/handlers_test.go
diff options
context:
space:
mode:
authorPeter Stone <thepeterstone@gmail.com>2026-03-22 23:45:19 +0000
committerPeter Stone <thepeterstone@gmail.com>2026-03-22 23:45:19 +0000
commit8abc63efdbc0bb96cd6c9aa99d6e9166e0bcabae (patch)
treef4d6a082eed9b10bc67436a3ca5188e0182961eb /internal/handlers/handlers_test.go
parent11b905fd437d651b2e39745aa82a5dd36f70331e (diff)
chore: unify and centralize agent configuration in .agent/
Diffstat (limited to 'internal/handlers/handlers_test.go')
-rw-r--r--internal/handlers/handlers_test.go216
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)
}
}