package handlers import ( "context" "os" "path/filepath" "testing" "time" "task-dashboard/internal/models" "task-dashboard/internal/store" _ "github.com/mattn/go-sqlite3" ) // MockCalendarClient implements GoogleCalendarAPI interface for testing type MockCalendarClient struct { Events []models.CalendarEvent Err error } func (m *MockCalendarClient) GetUpcomingEvents(ctx context.Context, maxResults int) ([]models.CalendarEvent, error) { return m.Events, m.Err } func (m *MockCalendarClient) GetEventsByDateRange(ctx context.Context, start, end time.Time) ([]models.CalendarEvent, error) { return m.Events, m.Err } func setupTestStore(t *testing.T) *store.Store { t.Helper() tempDir := t.TempDir() dbPath := filepath.Join(tempDir, "test.db") migrationDir := filepath.Join(tempDir, "migrations") if err := os.MkdirAll(migrationDir, 0755); err != nil { t.Fatalf("Failed to create migration dir: %v", err) } schema := ` CREATE TABLE IF NOT EXISTS tasks ( id TEXT PRIMARY KEY, content TEXT NOT NULL, description TEXT, project_id TEXT, project_name TEXT, due_date DATETIME, priority INTEGER DEFAULT 1, completed BOOLEAN DEFAULT FALSE, labels TEXT, url TEXT, created_at DATETIME, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS meals ( id TEXT PRIMARY KEY, recipe_name TEXT NOT NULL, date DATETIME, meal_type TEXT, recipe_url TEXT, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS boards ( id TEXT PRIMARY KEY, name TEXT NOT NULL, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS cards ( id TEXT PRIMARY KEY, name TEXT NOT NULL, board_id TEXT NOT NULL, list_id TEXT, list_name TEXT, due_date DATETIME, url TEXT, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ); ` if err := os.WriteFile(filepath.Join(migrationDir, "001_init.sql"), []byte(schema), 0644); err != nil { t.Fatalf("Failed to write migration file: %v", err) } // Initialize store (this creates tables) s, err := store.New(dbPath, migrationDir) if err != nil { t.Fatalf("Failed to create store: %v", err) } return s } func TestBuildTimeline(t *testing.T) { s := setupTestStore(t) // Fix a base time: 2023-01-01 08:00:00 baseTime := time.Date(2023, 1, 1, 8, 0, 0, 0, time.UTC) // Task: 10:00 taskDate := baseTime.Add(2 * time.Hour) s.SaveTasks([]models.Task{ {ID: "t1", Content: "Task 1", DueDate: &taskDate}, }) // Meal: Lunch (defaults to 12:00) mealDate := baseTime // Date part matters s.SaveMeals([]models.Meal{ {ID: "m1", RecipeName: "Lunch", Date: mealDate, MealType: "lunch"}, }) // Card: 14:00 cardDate := baseTime.Add(6 * time.Hour) s.SaveBoards([]models.Board{ { ID: "b1", Name: "Board 1", Cards: []models.Card{ {ID: "c1", Name: "Card 1", DueDate: &cardDate, ListID: "l1"}, }, }, }) // Calendar Event: 09:00 eventDate := baseTime.Add(1 * time.Hour) mockCal := &MockCalendarClient{ Events: []models.CalendarEvent{ {ID: "e1", Summary: "Event 1", Start: eventDate, End: eventDate.Add(1 * time.Hour)}, }, } // Test Range: Full Day start := time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC) end := time.Date(2023, 1, 2, 0, 0, 0, 0, time.UTC) items, err := BuildTimeline(context.Background(), s, mockCal, start, end) if err != nil { t.Fatalf("BuildTimeline failed: %v", err) } if len(items) != 4 { t.Errorf("Expected 4 items, got %d", len(items)) } // Expected Order: // 1. Event (09:00) // 2. Task (10:00) // 3. Meal (12:00) // 4. Card (14:00) if items[0].Type != models.TimelineItemTypeEvent { t.Errorf("Expected item 0 to be Event, got %s", items[0].Type) } if items[1].Type != models.TimelineItemTypeTask { t.Errorf("Expected item 1 to be Task, got %s", items[1].Type) } if items[2].Type != models.TimelineItemTypeMeal { t.Errorf("Expected item 2 to be Meal, got %s", items[2].Type) } if items[3].Type != models.TimelineItemTypeCard { t.Errorf("Expected item 3 to be Card, got %s", items[3].Type) } }