package sqlite import ( "context" "testing" "time" "apocapoc-api/internal/domain/entities" "apocapoc-api/internal/domain/value_objects" "apocapoc-api/internal/shared/errors" "apocapoc-api/internal/shared/pagination" ) func TestHabitRepositoryCreate(t *testing.T) { db := setupTestDB(t) defer db.Close() repo := NewHabitRepository(db) ctx := context.Background() habit := entities.NewHabit( "user-123", "Morning Exercise", value_objects.HabitTypeBoolean, value_objects.FrequencyDaily, false, false, ) habit.Description = "Exercise every morning" err := repo.Create(ctx, habit) if err != nil { t.Fatalf("Create failed: %v", err) } if habit.ID == "" { t.Error("Expected habit ID to be generated, got empty string") } } func TestHabitRepositoryCreateWithSpecificDays(t *testing.T) { db := setupTestDB(t) defer db.Close() repo := NewHabitRepository(db) ctx := context.Background() habit := entities.NewHabit( "user-123", "Weekly Workout", value_objects.HabitTypeBoolean, value_objects.FrequencyWeekly, false, false, ) habit.SpecificDays = []int{1, 3, 5} err := repo.Create(ctx, habit) if err != nil { t.Fatalf("Create failed: %v", err) } found, err := repo.FindByID(ctx, habit.ID) if err != nil { t.Fatalf("FindByID failed: %v", err) } if len(found.SpecificDays) != 3 { t.Errorf("Expected 3 specific days, got %d", len(found.SpecificDays)) } } func TestHabitRepositoryFindByID(t *testing.T) { db := setupTestDB(t) defer db.Close() repo := NewHabitRepository(db) ctx := context.Background() habit := entities.NewHabit( "user-456", "Read Books", value_objects.HabitTypeCounter, value_objects.FrequencyDaily, true, false, ) targetValue := 30.0 habit.TargetValue = &targetValue err := repo.Create(ctx, habit) if err != nil { t.Fatalf("Create failed: %v", err) } found, err := repo.FindByID(ctx, habit.ID) if err != nil { t.Fatalf("FindByID failed: %v", err) } if found.ID != habit.ID { t.Errorf("Expected ID %s, got %s", habit.ID, found.ID) } if found.Name != habit.Name { t.Errorf("Expected name %s, got %s", habit.Name, found.Name) } if found.CarryOver != habit.CarryOver { t.Errorf("Expected carry_over %v, got %v", habit.CarryOver, found.CarryOver) } if found.TargetValue == nil || *found.TargetValue != 30.0 { t.Errorf("Expected target_value 30.0, got %v", found.TargetValue) } } func TestHabitRepositoryFindByIDNotFound(t *testing.T) { db := setupTestDB(t) defer db.Close() repo := NewHabitRepository(db) ctx := context.Background() _, err := repo.FindByID(ctx, "non-existent-id") if err != errors.ErrNotFound { t.Errorf("Expected ErrNotFound, got %v", err) } } func TestHabitRepositoryFindActiveByUserID(t *testing.T) { db := setupTestDB(t) defer db.Close() repo := NewHabitRepository(db) ctx := context.Background() userID := "user-789" habit1 := entities.NewHabit(userID, "Habit 1", value_objects.HabitTypeBoolean, value_objects.FrequencyDaily, false, false) habit2 := entities.NewHabit(userID, "Habit 2", value_objects.HabitTypeBoolean, value_objects.FrequencyDaily, false, false) habit3 := entities.NewHabit(userID, "Habit 3", value_objects.HabitTypeBoolean, value_objects.FrequencyDaily, false, false) repo.Create(ctx, habit1) repo.Create(ctx, habit2) repo.Create(ctx, habit3) now := time.Now() habit2.ArchivedAt = &now repo.Update(ctx, habit2) habits, err := repo.FindActiveByUserID(ctx, userID) if err != nil { t.Fatalf("FindActiveByUserID failed: %v", err) } if len(habits) != 2 { t.Errorf("Expected 2 active habits, got %d", len(habits)) } } func TestHabitRepositoryUpdate(t *testing.T) { db := setupTestDB(t) defer db.Close() repo := NewHabitRepository(db) ctx := context.Background() habit := entities.NewHabit( "user-999", "Original Name", value_objects.HabitTypeBoolean, value_objects.FrequencyDaily, false, false, ) err := repo.Create(ctx, habit) if err != nil { t.Fatalf("Create failed: %v", err) } habit.Name = "Updated Name" habit.Description = "Updated description" habit.CarryOver = true err = repo.Update(ctx, habit) if err != nil { t.Fatalf("Update failed: %v", err) } found, err := repo.FindByID(ctx, habit.ID) if err != nil { t.Fatalf("FindByID failed: %v", err) } if found.Name != "Updated Name" { t.Errorf("Expected name 'Updated Name', got %s", found.Name) } if found.Description != "Updated description" { t.Errorf("Expected description 'Updated description', got %s", found.Description) } if !found.CarryOver { t.Error("Expected carry_over to be true") } } func TestHabitRepositoryUpdateNotFound(t *testing.T) { db := setupTestDB(t) defer db.Close() repo := NewHabitRepository(db) ctx := context.Background() habit := entities.NewHabit( "user-123", "Test", value_objects.HabitTypeBoolean, value_objects.FrequencyDaily, false, false, ) habit.ID = "non-existent" err := repo.Update(ctx, habit) if err != errors.ErrNotFound { t.Errorf("Expected ErrNotFound, got %v", err) } } func TestHabitRepositoryArchive(t *testing.T) { db := setupTestDB(t) defer db.Close() repo := NewHabitRepository(db) ctx := context.Background() habit := entities.NewHabit( "user-archive", "To Archive", value_objects.HabitTypeBoolean, value_objects.FrequencyDaily, false, false, ) err := repo.Create(ctx, habit) if err != nil { t.Fatalf("Create failed: %v", err) } now := time.Now() habit.ArchivedAt = &now err = repo.Update(ctx, habit) if err != nil { t.Fatalf("Update failed: %v", err) } found, err := repo.FindByID(ctx, habit.ID) if err != nil { t.Fatalf("FindByID failed: %v", err) } if found.ArchivedAt == nil { t.Error("Expected habit to be archived") } } func TestHabitRepositoryFindActiveByUserIDWithPagination(t *testing.T) { db := setupTestDB(t) defer db.Close() repo := NewHabitRepository(db) ctx := context.Background() userID := "user-pagination-test" for i := 1; i <= 10; i++ { habit := entities.NewHabit( userID, "Habit "+string(rune(i+'0')), value_objects.HabitTypeBoolean, value_objects.FrequencyDaily, false, false, ) err := repo.Create(ctx, habit) if err != nil { t.Fatalf("Create failed: %v", err) } time.Sleep(1 * time.Millisecond) } now := time.Now() allHabits, _ := repo.FindActiveByUserID(ctx, userID) allHabits[0].ArchivedAt = &now repo.Update(ctx, allHabits[0]) t.Run("FirstPage", func(t *testing.T) { params := pagination.NewParams(1, 5) habits, err := repo.FindActiveByUserIDWithPagination(ctx, userID, params) if err != nil { t.Fatalf("FindActiveByUserIDWithPagination failed: %v", err) } if len(habits) != 5 { t.Errorf("Expected 5 habits on first page, got %d", len(habits)) } }) t.Run("SecondPage", func(t *testing.T) { params := pagination.NewParams(2, 5) habits, err := repo.FindActiveByUserIDWithPagination(ctx, userID, params) if err != nil { t.Fatalf("FindActiveByUserIDWithPagination failed: %v", err) } if len(habits) != 4 { t.Errorf("Expected 4 habits on second page (9 total active), got %d", len(habits)) } }) t.Run("PageBeyondTotal", func(t *testing.T) { params := pagination.NewParams(10, 5) habits, err := repo.FindActiveByUserIDWithPagination(ctx, userID, params) if err != nil { t.Fatalf("FindActiveByUserIDWithPagination failed: %v", err) } if len(habits) != 0 { t.Errorf("Expected 0 habits beyond total pages, got %d", len(habits)) } }) t.Run("CustomPageSize", func(t *testing.T) { params := pagination.NewParams(1, 3) habits, err := repo.FindActiveByUserIDWithPagination(ctx, userID, params) if err != nil { t.Fatalf("FindActiveByUserIDWithPagination failed: %v", err) } if len(habits) != 3 { t.Errorf("Expected 3 habits with page_size=3, got %d", len(habits)) } }) t.Run("ExcludesArchived", func(t *testing.T) { params := pagination.NewParams(1, 20) habits, err := repo.FindActiveByUserIDWithPagination(ctx, userID, params) if err != nil { t.Fatalf("FindActiveByUserIDWithPagination failed: %v", err) } if len(habits) != 9 { t.Errorf("Expected 9 active habits (1 archived), got %d", len(habits)) } for _, habit := range habits { if habit.ArchivedAt != nil { t.Error("Expected no archived habits in results") } } }) } func TestHabitRepositoryCountActiveByUserID(t *testing.T) { db := setupTestDB(t) defer db.Close() repo := NewHabitRepository(db) ctx := context.Background() userID := "user-count-test" t.Run("NoHabits", func(t *testing.T) { count, err := repo.CountActiveByUserID(ctx, "non-existent-user") if err != nil { t.Fatalf("CountActiveByUserID failed: %v", err) } if count != 0 { t.Errorf("Expected count 0 for non-existent user, got %d", count) } }) for i := 1; i <= 7; i++ { habit := entities.NewHabit( userID, "Habit "+string(rune(i+'0')), value_objects.HabitTypeBoolean, value_objects.FrequencyDaily, false, false, ) repo.Create(ctx, habit) } t.Run("AllActive", func(t *testing.T) { count, err := repo.CountActiveByUserID(ctx, userID) if err != nil { t.Fatalf("CountActiveByUserID failed: %v", err) } if count != 7 { t.Errorf("Expected count 7, got %d", count) } }) t.Run("WithArchived", func(t *testing.T) { habits, _ := repo.FindActiveByUserID(ctx, userID) now := time.Now() habits[0].ArchivedAt = &now habits[1].ArchivedAt = &now repo.Update(ctx, habits[0]) repo.Update(ctx, habits[1]) count, err := repo.CountActiveByUserID(ctx, userID) if err != nil { t.Fatalf("CountActiveByUserID failed: %v", err) } if count != 5 { t.Errorf("Expected count 5 (7 total - 2 archived), got %d", count) } }) }