//go:build integration package storage import ( "context" "database/sql" "testing" "training-tracker/internal/models" ) func TestExerciseRepository_CRUD(t *testing.T) { if testing.Short() { t.Skip("skipping integration test") } tdb := setupTestDB(t) defer tdb.cleanup(t) repo := NewExerciseRepository(tdb.DB) ctx := context.Background() t.Run("List_empty", func(t *testing.T) { tdb.truncateTables(t) exercises, err := repo.List(ctx) if err != nil { t.Fatalf("List failed: %v", err) } if exercises != nil && len(exercises) != 0 { t.Errorf("expected empty list, got %d items", len(exercises)) } }) t.Run("Create_strength", func(t *testing.T) { tdb.truncateTables(t) req := &models.CreateExerciseRequest{ Name: "Bench Press", Type: models.ExerciseTypeStrength, MuscleGroup: "Chest", Description: "Barbell bench press", } exercise, err := repo.Create(ctx, req) if err != nil { t.Fatalf("Create failed: %v", err) } if exercise.ID == 0 { t.Error("expected non-zero ID") } if exercise.Name != req.Name { t.Errorf("name = %q, want %q", exercise.Name, req.Name) } if exercise.Type != req.Type { t.Errorf("type = %q, want %q", exercise.Type, req.Type) } if exercise.MuscleGroup != req.MuscleGroup { t.Errorf("muscle_group = %q, want %q", exercise.MuscleGroup, req.MuscleGroup) } if exercise.Description != req.Description { t.Errorf("description = %q, want %q", exercise.Description, req.Description) } if exercise.CreatedAt.IsZero() { t.Error("expected non-zero created_at") } }) t.Run("Create_cardio", func(t *testing.T) { tdb.truncateTables(t) req := &models.CreateExerciseRequest{ Name: "Running", Type: models.ExerciseTypeCardio, } exercise, err := repo.Create(ctx, req) if err != nil { t.Fatalf("Create failed: %v", err) } if exercise.Type != models.ExerciseTypeCardio { t.Errorf("type = %q, want %q", exercise.Type, models.ExerciseTypeCardio) } if exercise.MuscleGroup != "" { t.Errorf("expected empty muscle_group, got %q", exercise.MuscleGroup) } }) t.Run("GetByID_found", func(t *testing.T) { tdb.truncateTables(t) created, _ := repo.Create(ctx, &models.CreateExerciseRequest{ Name: "Squat", Type: models.ExerciseTypeStrength, }) exercise, err := repo.GetByID(ctx, created.ID) if err != nil { t.Fatalf("GetByID failed: %v", err) } if exercise == nil { t.Fatal("expected exercise, got nil") } if exercise.ID != created.ID { t.Errorf("ID = %d, want %d", exercise.ID, created.ID) } if exercise.Name != "Squat" { t.Errorf("name = %q, want %q", exercise.Name, "Squat") } }) t.Run("GetByID_not_found", func(t *testing.T) { tdb.truncateTables(t) exercise, err := repo.GetByID(ctx, 99999) if err != nil { t.Fatalf("GetByID failed: %v", err) } if exercise != nil { t.Errorf("expected nil, got %+v", exercise) } }) t.Run("Update_found", func(t *testing.T) { tdb.truncateTables(t) created, _ := repo.Create(ctx, &models.CreateExerciseRequest{ Name: "Old Name", Type: models.ExerciseTypeStrength, }) updated, err := repo.Update(ctx, created.ID, &models.CreateExerciseRequest{ Name: "New Name", Type: models.ExerciseTypeCardio, MuscleGroup: "Legs", Description: "Updated description", }) if err != nil { t.Fatalf("Update failed: %v", err) } if updated == nil { t.Fatal("expected updated exercise, got nil") } if updated.Name != "New Name" { t.Errorf("name = %q, want %q", updated.Name, "New Name") } if updated.Type != models.ExerciseTypeCardio { t.Errorf("type = %q, want %q", updated.Type, models.ExerciseTypeCardio) } if updated.MuscleGroup != "Legs" { t.Errorf("muscle_group = %q, want %q", updated.MuscleGroup, "Legs") } }) t.Run("Update_not_found", func(t *testing.T) { tdb.truncateTables(t) updated, err := repo.Update(ctx, 99999, &models.CreateExerciseRequest{ Name: "Test", Type: models.ExerciseTypeStrength, }) if err != nil { t.Fatalf("Update failed: %v", err) } if updated != nil { t.Errorf("expected nil, got %+v", updated) } }) t.Run("Delete_found", func(t *testing.T) { tdb.truncateTables(t) created, _ := repo.Create(ctx, &models.CreateExerciseRequest{ Name: "To Delete", Type: models.ExerciseTypeStrength, }) err := repo.Delete(ctx, created.ID) if err != nil { t.Fatalf("Delete failed: %v", err) } exercise, _ := repo.GetByID(ctx, created.ID) if exercise != nil { t.Error("expected exercise to be deleted") } }) t.Run("Delete_not_found", func(t *testing.T) { tdb.truncateTables(t) err := repo.Delete(ctx, 99999) if err != sql.ErrNoRows { t.Errorf("expected sql.ErrNoRows, got %v", err) } }) t.Run("List_multiple", func(t *testing.T) { tdb.truncateTables(t) repo.Create(ctx, &models.CreateExerciseRequest{Name: "Alpha", Type: models.ExerciseTypeStrength}) repo.Create(ctx, &models.CreateExerciseRequest{Name: "Beta", Type: models.ExerciseTypeCardio}) repo.Create(ctx, &models.CreateExerciseRequest{Name: "Gamma", Type: models.ExerciseTypeStrength}) exercises, err := repo.List(ctx) if err != nil { t.Fatalf("List failed: %v", err) } if len(exercises) != 3 { t.Errorf("len = %d, want 3", len(exercises)) } // Results are ordered by name if exercises[0].Name != "Alpha" { t.Errorf("first exercise = %q, want Alpha", exercises[0].Name) } }) t.Run("Update_clear_optional_fields", func(t *testing.T) { tdb.truncateTables(t) created, _ := repo.Create(ctx, &models.CreateExerciseRequest{ Name: "With Optional", Type: models.ExerciseTypeStrength, MuscleGroup: "Arms", Description: "Has description", }) updated, err := repo.Update(ctx, created.ID, &models.CreateExerciseRequest{ Name: "Without Optional", Type: models.ExerciseTypeStrength, }) if err != nil { t.Fatalf("Update failed: %v", err) } if updated.MuscleGroup != "" { t.Errorf("expected empty muscle_group, got %q", updated.MuscleGroup) } if updated.Description != "" { t.Errorf("expected empty description, got %q", updated.Description) } }) }