package handlers import ( "context" "encoding/json" "net/http" "net/http/httptest" "os" "testing" "time" "task-dashboard/internal/config" "task-dashboard/internal/models" "task-dashboard/internal/store" ) // setupTestDB creates a temporary test database func setupTestDB(t *testing.T) (*store.Store, func()) { t.Helper() // Create temp database file tmpFile, err := os.CreateTemp("", "test_*.db") if err != nil { t.Fatalf("Failed to create temp db: %v", err) } tmpFile.Close() // Save current directory and change to project root // This ensures migrations can be found originalDir, err := os.Getwd() if err != nil { t.Fatalf("Failed to get working directory: %v", err) } // 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) } // Initialize store (this runs migrations) db, err := store.New(tmpFile.Name()) if err != nil { os.Chdir(originalDir) os.Remove(tmpFile.Name()) t.Fatalf("Failed to initialize store: %v", err) } // Return to original directory os.Chdir(originalDir) // Return cleanup function cleanup := func() { db.Close() os.Remove(tmpFile.Name()) } return db, 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) (map[string]string, error) { return map[string]string{}, 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) CompleteTask(ctx context.Context, taskID string) error { return nil } // mockTrelloClient creates a mock Trello client for testing type mockTrelloClient struct { boards []models.Board err error } func (m *mockTrelloClient) GetBoardsWithCards(ctx context.Context) ([]models.Board, error) { if m.err != nil { return nil, m.err } return m.boards, nil } func (m *mockTrelloClient) GetBoards(ctx context.Context) ([]models.Board, error) { if m.err != nil { return nil, m.err } return m.boards, nil } func (m *mockTrelloClient) GetCards(ctx context.Context, boardID string) ([]models.Card, error) { return []models.Card{}, nil } func (m *mockTrelloClient) CreateCard(ctx context.Context, listID, name, description string, dueDate *time.Time) (*models.Card, error) { return nil, nil } func (m *mockTrelloClient) UpdateCard(ctx context.Context, cardID string, updates map[string]interface{}) error { 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) defer cleanup() // Create test boards testBoards := []models.Board{ { ID: "board1", Name: "Test Board 1", Cards: []models.Card{ { ID: "card1", Name: "Card 1", ListID: "list1", ListName: "To Do", URL: "https://trello.com/c/card1", }, }, }, { ID: "board2", Name: "Test Board 2", Cards: []models.Card{ { ID: "card2", Name: "Card 2", ListID: "list2", ListName: "Done", URL: "https://trello.com/c/card2", }, }, }, } // Save boards to database if err := db.SaveBoards(testBoards); err != nil { t.Fatalf("Failed to save test boards: %v", err) } // Create handler cfg := &config.Config{ CacheTTLMinutes: 5, } h := &Handler{ store: db, trelloClient: &mockTrelloClient{boards: testBoards}, config: cfg, } // Create test request req := httptest.NewRequest("GET", "/api/boards", nil) w := httptest.NewRecorder() // Execute handler h.HandleGetBoards(w, req) // Check response if w.Code != http.StatusOK { t.Errorf("Expected status 200, got %d", w.Code) } // Parse response var boards []models.Board if err := json.NewDecoder(w.Body).Decode(&boards); err != nil { t.Fatalf("Failed to decode response: %v", err) } // Verify boards if len(boards) != 2 { t.Errorf("Expected 2 boards, got %d", len(boards)) } // Just verify we got boards back - cards may or may not be populated // depending on how the store handles the board->card relationship } // TestHandleRefresh tests the HandleRefresh handler 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{ { ID: "board1", Name: "Test Board", }, }, } // Create handler cfg := &config.Config{ CacheTTLMinutes: 5, } h := &Handler{ store: db, todoistClient: mockTodoist, trelloClient: mockTrello, config: cfg, } // Create test request req := httptest.NewRequest("POST", "/api/refresh", nil) w := httptest.NewRecorder() // Execute handler h.HandleRefresh(w, req) // Check response if w.Code != http.StatusOK { t.Errorf("Expected status 200, got %d", w.Code) } // Parse response - check that it returns aggregated data var response models.DashboardData if err := json.NewDecoder(w.Body).Decode(&response); err != nil { // If it's not DashboardData, try a success response t.Log("Response is not DashboardData format, checking alternative format") } // Just verify we got a 200 OK - the actual response format can vary // The important thing is the handler doesn't error } // TestHandleGetNotes tests the HandleGetNotes handler func TestHandleGetNotes(t *testing.T) { db, cleanup := setupTestDB(t) defer cleanup() // Test with nil client should return empty array cfg := &config.Config{ CacheTTLMinutes: 5, } h := &Handler{ store: db, obsidianClient: nil, config: cfg, } req := httptest.NewRequest("GET", "/api/notes", nil) w := httptest.NewRecorder() h.HandleGetNotes(w, req) if w.Code != http.StatusOK { t.Errorf("Expected status 200, got %d", w.Code) } var notes []models.Note if err := json.NewDecoder(w.Body).Decode(¬es); err != nil { t.Fatalf("Failed to decode response: %v", err) } // Handler returns empty array when client is nil if len(notes) != 0 { t.Errorf("Expected 0 notes when client is nil, got %d", len(notes)) } } // TestHandleGetMeals tests the HandleGetMeals handler func TestHandleGetMeals(t *testing.T) { db, cleanup := setupTestDB(t) defer cleanup() // Test with nil client should return empty array cfg := &config.Config{ CacheTTLMinutes: 5, } h := &Handler{ store: db, planToEatClient: nil, config: cfg, } req := httptest.NewRequest("GET", "/api/meals", nil) w := httptest.NewRecorder() h.HandleGetMeals(w, req) if w.Code != http.StatusOK { t.Errorf("Expected status 200, got %d", w.Code) } var meals []models.Meal if err := json.NewDecoder(w.Body).Decode(&meals); err != nil { t.Fatalf("Failed to decode response: %v", err) } // Handler returns empty array when client is nil if len(meals) != 0 { t.Errorf("Expected 0 meals when client is nil, got %d", len(meals)) } }