package repositories import ( "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/treytartt/casera-api/internal/models" "github.com/treytartt/casera-api/internal/testutil" ) func TestTaskRepository_Create(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewTaskRepository(db) user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password") residence := testutil.CreateTestResidence(t, db, user.ID, "Test House") task := &models.Task{ ResidenceID: residence.ID, CreatedByID: user.ID, Title: "Fix leaky faucet", Description: "Kitchen faucet is dripping", } err := repo.Create(task) require.NoError(t, err) assert.NotZero(t, task.ID) } func TestTaskRepository_FindByID(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewTaskRepository(db) user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password") residence := testutil.CreateTestResidence(t, db, user.ID, "Test House") task := testutil.CreateTestTask(t, db, residence.ID, user.ID, "Test Task") found, err := repo.FindByID(task.ID) require.NoError(t, err) assert.Equal(t, task.ID, found.ID) assert.Equal(t, "Test Task", found.Title) } func TestTaskRepository_FindByID_NotFound(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewTaskRepository(db) _, err := repo.FindByID(9999) assert.Error(t, err) } func TestTaskRepository_FindByResidence(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewTaskRepository(db) user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password") residence1 := testutil.CreateTestResidence(t, db, user.ID, "House 1") residence2 := testutil.CreateTestResidence(t, db, user.ID, "House 2") testutil.CreateTestTask(t, db, residence1.ID, user.ID, "Task 1") testutil.CreateTestTask(t, db, residence1.ID, user.ID, "Task 2") testutil.CreateTestTask(t, db, residence2.ID, user.ID, "Task 3") tasks, err := repo.FindByResidence(residence1.ID) require.NoError(t, err) assert.Len(t, tasks, 2) } func TestTaskRepository_Update(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewTaskRepository(db) user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password") residence := testutil.CreateTestResidence(t, db, user.ID, "Test House") task := testutil.CreateTestTask(t, db, residence.ID, user.ID, "Original Title") task.Title = "Updated Title" task.Description = "Updated description" err := repo.Update(task) require.NoError(t, err) found, err := repo.FindByID(task.ID) require.NoError(t, err) assert.Equal(t, "Updated Title", found.Title) assert.Equal(t, "Updated description", found.Description) } func TestTaskRepository_Delete(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewTaskRepository(db) user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password") residence := testutil.CreateTestResidence(t, db, user.ID, "Test House") task := testutil.CreateTestTask(t, db, residence.ID, user.ID, "Test Task") err := repo.Delete(task.ID) require.NoError(t, err) _, err = repo.FindByID(task.ID) assert.Error(t, err) // Should not be found } func TestTaskRepository_Cancel(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewTaskRepository(db) user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password") residence := testutil.CreateTestResidence(t, db, user.ID, "Test House") task := testutil.CreateTestTask(t, db, residence.ID, user.ID, "Test Task") assert.False(t, task.IsCancelled) err := repo.Cancel(task.ID) require.NoError(t, err) found, err := repo.FindByID(task.ID) require.NoError(t, err) assert.True(t, found.IsCancelled) } func TestTaskRepository_Uncancel(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewTaskRepository(db) user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password") residence := testutil.CreateTestResidence(t, db, user.ID, "Test House") task := testutil.CreateTestTask(t, db, residence.ID, user.ID, "Test Task") repo.Cancel(task.ID) err := repo.Uncancel(task.ID) require.NoError(t, err) found, err := repo.FindByID(task.ID) require.NoError(t, err) assert.False(t, found.IsCancelled) } func TestTaskRepository_Archive(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewTaskRepository(db) user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password") residence := testutil.CreateTestResidence(t, db, user.ID, "Test House") task := testutil.CreateTestTask(t, db, residence.ID, user.ID, "Test Task") err := repo.Archive(task.ID) require.NoError(t, err) found, err := repo.FindByID(task.ID) require.NoError(t, err) assert.True(t, found.IsArchived) } func TestTaskRepository_Unarchive(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewTaskRepository(db) user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password") residence := testutil.CreateTestResidence(t, db, user.ID, "Test House") task := testutil.CreateTestTask(t, db, residence.ID, user.ID, "Test Task") repo.Archive(task.ID) err := repo.Unarchive(task.ID) require.NoError(t, err) found, err := repo.FindByID(task.ID) require.NoError(t, err) assert.False(t, found.IsArchived) } func TestTaskRepository_CreateCompletion(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewTaskRepository(db) user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password") residence := testutil.CreateTestResidence(t, db, user.ID, "Test House") task := testutil.CreateTestTask(t, db, residence.ID, user.ID, "Test Task") completion := &models.TaskCompletion{ TaskID: task.ID, CompletedByID: user.ID, CompletedAt: time.Now().UTC(), Notes: "Completed successfully", } err := repo.CreateCompletion(completion) require.NoError(t, err) assert.NotZero(t, completion.ID) } func TestTaskRepository_FindCompletionByID(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewTaskRepository(db) user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password") residence := testutil.CreateTestResidence(t, db, user.ID, "Test House") task := testutil.CreateTestTask(t, db, residence.ID, user.ID, "Test Task") completion := &models.TaskCompletion{ TaskID: task.ID, CompletedByID: user.ID, CompletedAt: time.Now().UTC(), Notes: "Test notes", } db.Create(completion) found, err := repo.FindCompletionByID(completion.ID) require.NoError(t, err) assert.Equal(t, completion.ID, found.ID) assert.Equal(t, "Test notes", found.Notes) } func TestTaskRepository_FindCompletionsByTask(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewTaskRepository(db) user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password") residence := testutil.CreateTestResidence(t, db, user.ID, "Test House") task := testutil.CreateTestTask(t, db, residence.ID, user.ID, "Test Task") // Create multiple completions for i := 0; i < 3; i++ { db.Create(&models.TaskCompletion{ TaskID: task.ID, CompletedByID: user.ID, CompletedAt: time.Now().UTC(), }) } completions, err := repo.FindCompletionsByTask(task.ID) require.NoError(t, err) assert.Len(t, completions, 3) } func TestTaskRepository_DeleteCompletion(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewTaskRepository(db) user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password") residence := testutil.CreateTestResidence(t, db, user.ID, "Test House") task := testutil.CreateTestTask(t, db, residence.ID, user.ID, "Test Task") completion := &models.TaskCompletion{ TaskID: task.ID, CompletedByID: user.ID, CompletedAt: time.Now().UTC(), } db.Create(completion) err := repo.DeleteCompletion(completion.ID) require.NoError(t, err) _, err = repo.FindCompletionByID(completion.ID) assert.Error(t, err) } func TestTaskRepository_GetAllCategories(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewTaskRepository(db) testutil.SeedLookupData(t, db) categories, err := repo.GetAllCategories() require.NoError(t, err) assert.Greater(t, len(categories), 0) } func TestTaskRepository_GetAllPriorities(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewTaskRepository(db) testutil.SeedLookupData(t, db) priorities, err := repo.GetAllPriorities() require.NoError(t, err) assert.Greater(t, len(priorities), 0) } func TestTaskRepository_GetAllFrequencies(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewTaskRepository(db) testutil.SeedLookupData(t, db) frequencies, err := repo.GetAllFrequencies() require.NoError(t, err) assert.Greater(t, len(frequencies), 0) } func TestTaskRepository_CountByResidence(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewTaskRepository(db) user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password") residence := testutil.CreateTestResidence(t, db, user.ID, "Test House") testutil.CreateTestTask(t, db, residence.ID, user.ID, "Task 1") testutil.CreateTestTask(t, db, residence.ID, user.ID, "Task 2") testutil.CreateTestTask(t, db, residence.ID, user.ID, "Task 3") count, err := repo.CountByResidence(residence.ID) require.NoError(t, err) assert.Equal(t, int64(3), count) } // === Kanban Board Categorization Tests === func TestKanbanBoard_CancelledTasksGoToCancelledColumn(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewTaskRepository(db) testutil.SeedLookupData(t, db) user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password") residence := testutil.CreateTestResidence(t, db, user.ID, "Test House") // Create a cancelled task task := testutil.CreateTestTask(t, db, residence.ID, user.ID, "Cancelled Task") repo.Cancel(task.ID) board, err := repo.GetKanbanData(residence.ID, 30) require.NoError(t, err) // Find cancelled column var cancelledColumn *models.KanbanColumn for i := range board.Columns { if board.Columns[i].Name == "cancelled_tasks" { cancelledColumn = &board.Columns[i] break } } require.NotNil(t, cancelledColumn, "cancelled_tasks column should exist") assert.Equal(t, 1, cancelledColumn.Count) assert.Len(t, cancelledColumn.Tasks, 1) assert.Equal(t, "Cancelled Task", cancelledColumn.Tasks[0].Title) // Verify button types for cancelled column assert.ElementsMatch(t, []string{"uncancel", "delete"}, cancelledColumn.ButtonTypes) } func TestKanbanBoard_CompletedTasksGoToCompletedColumn(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewTaskRepository(db) testutil.SeedLookupData(t, db) user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password") residence := testutil.CreateTestResidence(t, db, user.ID, "Test House") // Create a task with a completion task := testutil.CreateTestTask(t, db, residence.ID, user.ID, "Completed Task") completion := &models.TaskCompletion{ TaskID: task.ID, CompletedByID: user.ID, CompletedAt: time.Now().UTC(), Notes: "Done!", } err := repo.CreateCompletion(completion) require.NoError(t, err) board, err := repo.GetKanbanData(residence.ID, 30) require.NoError(t, err) // Find completed column var completedColumn *models.KanbanColumn for i := range board.Columns { if board.Columns[i].Name == "completed_tasks" { completedColumn = &board.Columns[i] break } } require.NotNil(t, completedColumn, "completed_tasks column should exist") assert.Equal(t, 1, completedColumn.Count) assert.Len(t, completedColumn.Tasks, 1) assert.Equal(t, "Completed Task", completedColumn.Tasks[0].Title) // Verify button types for completed column (read-only, no buttons) assert.ElementsMatch(t, []string{}, completedColumn.ButtonTypes) } func TestKanbanBoard_InProgressTasksGoToInProgressColumn(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewTaskRepository(db) testutil.SeedLookupData(t, db) user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password") residence := testutil.CreateTestResidence(t, db, user.ID, "Test House") // Create a task with InProgress = true task := &models.Task{ ResidenceID: residence.ID, CreatedByID: user.ID, Title: "In Progress Task", InProgress: true, } err := db.Create(task).Error require.NoError(t, err) board, err := repo.GetKanbanData(residence.ID, 30) require.NoError(t, err) // Find in_progress column var inProgressColumn *models.KanbanColumn for i := range board.Columns { if board.Columns[i].Name == "in_progress_tasks" { inProgressColumn = &board.Columns[i] break } } require.NotNil(t, inProgressColumn, "in_progress_tasks column should exist") assert.Equal(t, 1, inProgressColumn.Count) assert.Len(t, inProgressColumn.Tasks, 1) assert.Equal(t, "In Progress Task", inProgressColumn.Tasks[0].Title) // Verify button types for in-progress column (no mark_in_progress since already in progress) assert.ElementsMatch(t, []string{"edit", "complete", "cancel"}, inProgressColumn.ButtonTypes) } func TestKanbanBoard_OverdueTasksGoToOverdueColumn(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewTaskRepository(db) testutil.SeedLookupData(t, db) user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password") residence := testutil.CreateTestResidence(t, db, user.ID, "Test House") // Create a task with due date in the past pastDue := time.Now().UTC().AddDate(0, 0, -5) // 5 days ago task := &models.Task{ ResidenceID: residence.ID, CreatedByID: user.ID, Title: "Overdue Task", DueDate: &pastDue, } err := db.Create(task).Error require.NoError(t, err) board, err := repo.GetKanbanData(residence.ID, 30) require.NoError(t, err) // Find overdue column var overdueColumn *models.KanbanColumn for i := range board.Columns { if board.Columns[i].Name == "overdue_tasks" { overdueColumn = &board.Columns[i] break } } require.NotNil(t, overdueColumn, "overdue_tasks column should exist") assert.Equal(t, 1, overdueColumn.Count) assert.Len(t, overdueColumn.Tasks, 1) assert.Equal(t, "Overdue Task", overdueColumn.Tasks[0].Title) // Verify button types for overdue column assert.ElementsMatch(t, []string{"edit", "complete", "cancel", "mark_in_progress"}, overdueColumn.ButtonTypes) } func TestKanbanBoard_DueSoonTasksGoToDueSoonColumn(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewTaskRepository(db) testutil.SeedLookupData(t, db) user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password") residence := testutil.CreateTestResidence(t, db, user.ID, "Test House") // Create a task with due date within threshold (default 30 days) dueSoon := time.Now().UTC().AddDate(0, 0, 15) // 15 days from now task := &models.Task{ ResidenceID: residence.ID, CreatedByID: user.ID, Title: "Due Soon Task", DueDate: &dueSoon, } err := db.Create(task).Error require.NoError(t, err) board, err := repo.GetKanbanData(residence.ID, 30) require.NoError(t, err) // Find due_soon column var dueSoonColumn *models.KanbanColumn for i := range board.Columns { if board.Columns[i].Name == "due_soon_tasks" { dueSoonColumn = &board.Columns[i] break } } require.NotNil(t, dueSoonColumn, "due_soon_tasks column should exist") assert.Equal(t, 1, dueSoonColumn.Count) assert.Len(t, dueSoonColumn.Tasks, 1) assert.Equal(t, "Due Soon Task", dueSoonColumn.Tasks[0].Title) // Verify button types for due_soon column assert.ElementsMatch(t, []string{"edit", "complete", "cancel", "mark_in_progress"}, dueSoonColumn.ButtonTypes) } func TestKanbanBoard_UpcomingTasksGoToUpcomingColumn(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewTaskRepository(db) testutil.SeedLookupData(t, db) user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password") residence := testutil.CreateTestResidence(t, db, user.ID, "Test House") // Create a task with due date beyond threshold (default 30 days) upcoming := time.Now().UTC().AddDate(0, 0, 45) // 45 days from now task := &models.Task{ ResidenceID: residence.ID, CreatedByID: user.ID, Title: "Upcoming Task", DueDate: &upcoming, } err := db.Create(task).Error require.NoError(t, err) board, err := repo.GetKanbanData(residence.ID, 30) require.NoError(t, err) // Find upcoming column var upcomingColumn *models.KanbanColumn for i := range board.Columns { if board.Columns[i].Name == "upcoming_tasks" { upcomingColumn = &board.Columns[i] break } } require.NotNil(t, upcomingColumn, "upcoming_tasks column should exist") assert.Equal(t, 1, upcomingColumn.Count) assert.Len(t, upcomingColumn.Tasks, 1) assert.Equal(t, "Upcoming Task", upcomingColumn.Tasks[0].Title) // Verify button types for upcoming column assert.ElementsMatch(t, []string{"edit", "complete", "cancel", "mark_in_progress"}, upcomingColumn.ButtonTypes) } func TestKanbanBoard_TasksWithNoDueDateGoToUpcomingColumn(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewTaskRepository(db) testutil.SeedLookupData(t, db) user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password") residence := testutil.CreateTestResidence(t, db, user.ID, "Test House") // Create a task with no due date task := testutil.CreateTestTask(t, db, residence.ID, user.ID, "No Due Date Task") board, err := repo.GetKanbanData(residence.ID, 30) require.NoError(t, err) // Find upcoming column var upcomingColumn *models.KanbanColumn for i := range board.Columns { if board.Columns[i].Name == "upcoming_tasks" { upcomingColumn = &board.Columns[i] break } } require.NotNil(t, upcomingColumn, "upcoming_tasks column should exist") assert.Equal(t, 1, upcomingColumn.Count) assert.Equal(t, task.ID, upcomingColumn.Tasks[0].ID) } func TestKanbanBoard_ArchivedTasksAreExcluded(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewTaskRepository(db) testutil.SeedLookupData(t, db) user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password") residence := testutil.CreateTestResidence(t, db, user.ID, "Test House") // Create a regular task and an archived task testutil.CreateTestTask(t, db, residence.ID, user.ID, "Regular Task") archivedTask := testutil.CreateTestTask(t, db, residence.ID, user.ID, "Archived Task") repo.Archive(archivedTask.ID) board, err := repo.GetKanbanData(residence.ID, 30) require.NoError(t, err) // Count total tasks across all columns totalTasks := 0 for _, col := range board.Columns { totalTasks += col.Count } // Only the non-archived task should be in the board assert.Equal(t, 1, totalTasks) } func TestKanbanBoard_CategoryPriority_CancelledTakesPrecedence(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewTaskRepository(db) testutil.SeedLookupData(t, db) user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password") residence := testutil.CreateTestResidence(t, db, user.ID, "Test House") // Create a task that is BOTH overdue AND cancelled // Cancelled should take precedence pastDue := time.Now().UTC().AddDate(0, 0, -5) task := &models.Task{ ResidenceID: residence.ID, CreatedByID: user.ID, Title: "Overdue and Cancelled Task", DueDate: &pastDue, IsCancelled: true, } err := db.Create(task).Error require.NoError(t, err) board, err := repo.GetKanbanData(residence.ID, 30) require.NoError(t, err) // Find cancelled column var cancelledColumn *models.KanbanColumn var overdueColumn *models.KanbanColumn for i := range board.Columns { if board.Columns[i].Name == "cancelled_tasks" { cancelledColumn = &board.Columns[i] } if board.Columns[i].Name == "overdue_tasks" { overdueColumn = &board.Columns[i] } } // Task should be in cancelled, not overdue assert.Equal(t, 1, cancelledColumn.Count, "Task should be in cancelled column") assert.Equal(t, 0, overdueColumn.Count, "Task should NOT be in overdue column") } func TestKanbanBoard_CategoryPriority_CompletedTakesPrecedenceOverInProgress(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewTaskRepository(db) testutil.SeedLookupData(t, db) user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password") residence := testutil.CreateTestResidence(t, db, user.ID, "Test House") // Create a task that has InProgress = true AND a completion // Completed should take precedence task := &models.Task{ ResidenceID: residence.ID, CreatedByID: user.ID, Title: "In Progress with Completion", InProgress: true, } err := db.Create(task).Error require.NoError(t, err) // Add a completion completion := &models.TaskCompletion{ TaskID: task.ID, CompletedByID: user.ID, CompletedAt: time.Now().UTC(), } err = repo.CreateCompletion(completion) require.NoError(t, err) board, err := repo.GetKanbanData(residence.ID, 30) require.NoError(t, err) // Find columns var completedColumn, inProgressColumn *models.KanbanColumn for i := range board.Columns { if board.Columns[i].Name == "completed_tasks" { completedColumn = &board.Columns[i] } if board.Columns[i].Name == "in_progress_tasks" { inProgressColumn = &board.Columns[i] } } // Task should be in completed, not in_progress assert.Equal(t, 1, completedColumn.Count, "Task should be in completed column") assert.Equal(t, 0, inProgressColumn.Count, "Task should NOT be in in_progress column") } func TestKanbanBoard_DaysThresholdAffectsCategorization(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewTaskRepository(db) testutil.SeedLookupData(t, db) user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password") residence := testutil.CreateTestResidence(t, db, user.ID, "Test House") // Create a task due in 10 days dueIn10Days := time.Now().UTC().AddDate(0, 0, 10) task := &models.Task{ ResidenceID: residence.ID, CreatedByID: user.ID, Title: "Task Due in 10 Days", DueDate: &dueIn10Days, } err := db.Create(task).Error require.NoError(t, err) // With 30-day threshold, should be in "due_soon" board30, err := repo.GetKanbanData(residence.ID, 30) require.NoError(t, err) var dueSoon30, upcoming30 *models.KanbanColumn for i := range board30.Columns { if board30.Columns[i].Name == "due_soon_tasks" { dueSoon30 = &board30.Columns[i] } if board30.Columns[i].Name == "upcoming_tasks" { upcoming30 = &board30.Columns[i] } } assert.Equal(t, 1, dueSoon30.Count, "With 30-day threshold, task should be in due_soon") assert.Equal(t, 0, upcoming30.Count, "With 30-day threshold, task should NOT be in upcoming") // With 5-day threshold, should be in "upcoming" board5, err := repo.GetKanbanData(residence.ID, 5) require.NoError(t, err) var dueSoon5, upcoming5 *models.KanbanColumn for i := range board5.Columns { if board5.Columns[i].Name == "due_soon_tasks" { dueSoon5 = &board5.Columns[i] } if board5.Columns[i].Name == "upcoming_tasks" { upcoming5 = &board5.Columns[i] } } assert.Equal(t, 0, dueSoon5.Count, "With 5-day threshold, task should NOT be in due_soon") assert.Equal(t, 1, upcoming5.Count, "With 5-day threshold, task should be in upcoming") } func TestKanbanBoard_ColumnMetadata(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewTaskRepository(db) testutil.SeedLookupData(t, db) user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password") residence := testutil.CreateTestResidence(t, db, user.ID, "Test House") board, err := repo.GetKanbanData(residence.ID, 30) require.NoError(t, err) // Verify all 6 columns exist with correct metadata expectedColumns := []struct { name string displayName string color string buttonTypes []string iosIcon string androidIcon string }{ {"overdue_tasks", "Overdue", "#FF3B30", []string{"edit", "complete", "cancel", "mark_in_progress"}, "exclamationmark.triangle", "Warning"}, {"in_progress_tasks", "In Progress", "#5856D6", []string{"edit", "complete", "cancel"}, "hammer", "Build"}, {"due_soon_tasks", "Due Soon", "#FF9500", []string{"edit", "complete", "cancel", "mark_in_progress"}, "clock", "Schedule"}, {"upcoming_tasks", "Upcoming", "#007AFF", []string{"edit", "complete", "cancel", "mark_in_progress"}, "calendar", "Event"}, {"completed_tasks", "Completed", "#34C759", []string{}, "checkmark.circle", "CheckCircle"}, // Completed tasks are read-only (no buttons) {"cancelled_tasks", "Cancelled", "#8E8E93", []string{"uncancel", "delete"}, "xmark.circle", "Cancel"}, } assert.Len(t, board.Columns, 6, "Board should have 6 columns") for i, expected := range expectedColumns { col := board.Columns[i] assert.Equal(t, expected.name, col.Name, "Column %d name mismatch", i) assert.Equal(t, expected.displayName, col.DisplayName, "Column %d display name mismatch", i) assert.Equal(t, expected.color, col.Color, "Column %d color mismatch", i) assert.ElementsMatch(t, expected.buttonTypes, col.ButtonTypes, "Column %d button types mismatch", i) assert.Equal(t, expected.iosIcon, col.Icons["ios"], "Column %d iOS icon mismatch", i) assert.Equal(t, expected.androidIcon, col.Icons["android"], "Column %d Android icon mismatch", i) } // Verify days threshold is returned assert.Equal(t, 30, board.DaysThreshold) } func TestKanbanBoard_MultipleResidences(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewTaskRepository(db) testutil.SeedLookupData(t, db) user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password") residence1 := testutil.CreateTestResidence(t, db, user.ID, "House 1") residence2 := testutil.CreateTestResidence(t, db, user.ID, "House 2") // Create tasks in both residences testutil.CreateTestTask(t, db, residence1.ID, user.ID, "Task in House 1") testutil.CreateTestTask(t, db, residence2.ID, user.ID, "Task in House 2") // Create a cancelled task in house 1 cancelledTask := testutil.CreateTestTask(t, db, residence1.ID, user.ID, "Cancelled in House 1") repo.Cancel(cancelledTask.ID) board, err := repo.GetKanbanDataForMultipleResidences([]uint{residence1.ID, residence2.ID}, 30) require.NoError(t, err) // Count total tasks totalTasks := 0 for _, col := range board.Columns { totalTasks += col.Count } assert.Equal(t, 3, totalTasks, "Should have 3 tasks total across both residences") // Find upcoming and cancelled columns var upcomingColumn, cancelledColumn *models.KanbanColumn for i := range board.Columns { if board.Columns[i].Name == "upcoming_tasks" { upcomingColumn = &board.Columns[i] } if board.Columns[i].Name == "cancelled_tasks" { cancelledColumn = &board.Columns[i] } } assert.Equal(t, 2, upcomingColumn.Count, "Should have 2 upcoming tasks") assert.Equal(t, 1, cancelledColumn.Count, "Should have 1 cancelled task") }