summaryrefslogtreecommitdiff
path: root/internal/handlers/heuristic_test.go
diff options
context:
space:
mode:
authorPeter Stone <thepeterstone@gmail.com>2026-01-23 21:37:18 -1000
committerPeter Stone <thepeterstone@gmail.com>2026-01-23 21:37:18 -1000
commit465093343ddd398ce5f6377fc9c472d8251c618b (patch)
treed333a2f1c8879f7b114817e929c95e9fcf5f4c3b /internal/handlers/heuristic_test.go
parente23c85577cbb0eac8b847dd989072698ff4e7a30 (diff)
Refactor: reduce code duplication with shared abstractions
- Add BaseClient HTTP abstraction (internal/api/http.go) to eliminate duplicated HTTP boilerplate across Todoist, Trello, and PlanToEat clients - Add response helpers (internal/handlers/response.go) for JSON/HTML responses - Add generic cache wrapper (internal/handlers/cache.go) using Go generics - Consolidate HandleCompleteAtom/HandleUncompleteAtom into handleAtomToggle - Merge TabsHandler into Handler, delete tabs.go - Extract sortTasksByUrgency and filterAndSortTrelloTasks helpers - Update tests to work with new BaseClient structure Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Diffstat (limited to 'internal/handlers/heuristic_test.go')
-rw-r--r--internal/handlers/heuristic_test.go152
1 files changed, 82 insertions, 70 deletions
diff --git a/internal/handlers/heuristic_test.go b/internal/handlers/heuristic_test.go
index b03b664..82f4e90 100644
--- a/internal/handlers/heuristic_test.go
+++ b/internal/handlers/heuristic_test.go
@@ -1,97 +1,109 @@
package handlers
import (
- "net/http/httptest"
- "os"
- "strings"
"testing"
"time"
"task-dashboard/internal/models"
- "task-dashboard/internal/store"
)
-func TestHandleTasks_Heuristic(t *testing.T) {
- // Create temp database file
- tmpFile, err := os.CreateTemp("", "test_heuristic_*.db")
- if err != nil {
- t.Fatalf("Failed to create temp db: %v", err)
+func TestIsActionableList(t *testing.T) {
+ tests := []struct {
+ name string
+ listName string
+ want bool
+ }{
+ {"doing list", "Doing", true},
+ {"in progress", "In Progress", true},
+ {"to do", "To Do", true},
+ {"todo", "todo", true},
+ {"tasks", "My Tasks", true},
+ {"next", "Next Up", true},
+ {"today", "Today", true},
+ {"backlog", "Backlog", false},
+ {"done", "Done", false},
+ {"ideas", "Ideas", false},
}
- tmpFile.Close()
- defer os.Remove(tmpFile.Name())
- // Save current directory and change to project root
- originalDir, err := os.Getwd()
- if err != nil {
- t.Fatalf("Failed to get working directory: %v", err)
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if got := isActionableList(tt.listName); got != tt.want {
+ t.Errorf("isActionableList(%q) = %v, want %v", tt.listName, got, tt.want)
+ }
+ })
}
+}
- // Change to project root (2 levels up from internal/handlers)
- if err := os.Chdir("../../"); err != nil {
- t.Fatalf("Failed to change to project root: %v", err)
- }
- defer os.Chdir(originalDir)
-
- // Initialize store (this runs migrations)
- db, err := store.New(tmpFile.Name(), "migrations")
- if err != nil {
- t.Fatalf("Failed to initialize store: %v", err)
- }
- defer db.Close()
-
- // Seed Data
- // Board 1: Has actionable lists
- board1 := models.Board{ID: "b1", Name: "Work Board"}
-
- // Card 1: Has Due Date (Should appear)
+func TestFilterAndSortTrelloTasks(t *testing.T) {
due := time.Now()
- card1 := models.Card{ID: "c1", Name: "Due Task", ListID: "l1", ListName: "Backlog", DueDate: &due, BoardName: "Work Board"}
-
- // Card 2: No Due Date, Actionable List (Should appear)
- card2 := models.Card{ID: "c2", Name: "Doing Task", ListID: "l2", ListName: "Doing", BoardName: "Work Board"}
-
- // Card 3: No Due Date, Non-Actionable List (Should NOT appear)
- card3 := models.Card{ID: "c3", Name: "Backlog Task", ListID: "l1", ListName: "Backlog", BoardName: "Work Board"}
-
- // Card 4: No Due Date, "To Do" List (Should appear)
- card4 := models.Card{ID: "c4", Name: "Todo Task", ListID: "l3", ListName: "To Do", BoardName: "Work Board"}
-
- board1.Cards = []models.Card{card1, card2, card3, card4}
-
- if err := db.SaveBoards([]models.Board{board1}); err != nil {
- t.Fatalf("Failed to save boards: %v", err)
+ boards := []models.Board{
+ {
+ ID: "b1",
+ Name: "Work Board",
+ Cards: []models.Card{
+ {ID: "c1", Name: "Due Task", ListName: "Backlog", DueDate: &due, BoardName: "Work Board"},
+ {ID: "c2", Name: "Doing Task", ListName: "Doing", BoardName: "Work Board"},
+ {ID: "c3", Name: "Backlog Task", ListName: "Backlog", BoardName: "Work Board"},
+ {ID: "c4", Name: "Todo Task", ListName: "To Do", BoardName: "Work Board"},
+ },
+ },
}
- // Create Handler
- h := NewTabsHandler(db, nil, "../../web/templates")
+ tasks := filterAndSortTrelloTasks(boards)
- // Skip if templates are not loaded
- if h.templates == nil {
- t.Skip("Templates not available in test environment")
+ // Should have 3 tasks: c1 (has due date), c2 (Doing list), c4 (To Do list)
+ if len(tasks) != 3 {
+ t.Errorf("Expected 3 tasks, got %d", len(tasks))
}
- req := httptest.NewRequest("GET", "/tabs/tasks", nil)
- w := httptest.NewRecorder()
-
- // Execute
- h.HandleTasks(w, req)
+ // Verify c3 (Backlog without due date) is not included
+ for _, task := range tasks {
+ if task.ID == "c3" {
+ t.Error("Backlog task without due date should not be included")
+ }
+ }
- // Verify
- resp := w.Body.String()
+ // Verify expected tasks are present
+ found := map[string]bool{}
+ for _, task := range tasks {
+ found[task.ID] = true
+ }
- // Check for presence of expected tasks
- if !strings.Contains(resp, "Due Task") {
- t.Errorf("Expected 'Due Task' to be present")
+ if !found["c1"] {
+ t.Error("Expected c1 (Due Task) to be present")
+ }
+ if !found["c2"] {
+ t.Error("Expected c2 (Doing Task) to be present")
}
- if !strings.Contains(resp, "Doing Task") {
- t.Errorf("Expected 'Doing Task' to be present")
+ if !found["c4"] {
+ t.Error("Expected c4 (Todo Task) to be present")
}
- if !strings.Contains(resp, "Todo Task") {
- t.Errorf("Expected 'Todo Task' to be present")
+}
+
+func TestAtomUrgencyTier(t *testing.T) {
+ now := time.Now()
+ yesterday := now.AddDate(0, 0, -1)
+ tomorrow := now.AddDate(0, 0, 1)
+ todayWithTime := time.Date(now.Year(), now.Month(), now.Day(), 14, 30, 0, 0, now.Location())
+ todayMidnight := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
+
+ tests := []struct {
+ name string
+ atom models.Atom
+ want int
+ }{
+ {"no due date", models.Atom{DueDate: nil}, 4},
+ {"overdue", models.Atom{DueDate: &yesterday, IsOverdue: true}, 0},
+ {"future", models.Atom{DueDate: &tomorrow, IsFuture: true}, 3},
+ {"today with time", models.Atom{DueDate: &todayWithTime, HasSetTime: true}, 1},
+ {"today all-day", models.Atom{DueDate: &todayMidnight, HasSetTime: false}, 2},
}
- // Check for absence of non-expected tasks
- if strings.Contains(resp, "Backlog Task") {
- t.Errorf("Expected 'Backlog Task' to be ABSENT")
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if got := atomUrgencyTier(tt.atom); got != tt.want {
+ t.Errorf("atomUrgencyTier() = %v, want %v", got, tt.want)
+ }
+ })
}
}