Files
2026-01-19 19:50:21 +01:00

347 lines
8.7 KiB
Go

//go:build integration
package storage
import (
"context"
"database/sql"
"testing"
"training-tracker/internal/models"
)
func TestPlanRepository_CRUD(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test")
}
tdb := setupTestDB(t)
defer tdb.cleanup(t)
planRepo := NewPlanRepository(tdb.DB)
exerciseRepo := NewExerciseRepository(tdb.DB)
ctx := context.Background()
t.Run("List_empty", func(t *testing.T) {
tdb.truncateTables(t)
plans, err := planRepo.List(ctx)
if err != nil {
t.Fatalf("List failed: %v", err)
}
if plans != nil && len(plans) != 0 {
t.Errorf("expected empty list, got %d items", len(plans))
}
})
t.Run("Create_without_exercises", func(t *testing.T) {
tdb.truncateTables(t)
req := &models.CreatePlanRequest{
Name: "Push Day",
Description: "Chest, shoulders, triceps",
}
plan, err := planRepo.Create(ctx, req)
if err != nil {
t.Fatalf("Create failed: %v", err)
}
if plan.ID == 0 {
t.Error("expected non-zero ID")
}
if plan.Name != req.Name {
t.Errorf("name = %q, want %q", plan.Name, req.Name)
}
if plan.Description != req.Description {
t.Errorf("description = %q, want %q", plan.Description, req.Description)
}
if plan.CreatedAt.IsZero() {
t.Error("expected non-zero created_at")
}
})
t.Run("Create_with_exercises", func(t *testing.T) {
tdb.truncateTables(t)
exercise1, _ := exerciseRepo.Create(ctx, &models.CreateExerciseRequest{
Name: "Bench Press",
Type: models.ExerciseTypeStrength,
})
exercise2, _ := exerciseRepo.Create(ctx, &models.CreateExerciseRequest{
Name: "Shoulder Press",
Type: models.ExerciseTypeStrength,
})
req := &models.CreatePlanRequest{
Name: "Push Day",
Exercises: []models.PlanExerciseInput{
{ExerciseID: exercise1.ID, Sets: 4, Reps: 8, Order: 1},
{ExerciseID: exercise2.ID, Sets: 3, Reps: 12, Order: 2},
},
}
plan, err := planRepo.Create(ctx, req)
if err != nil {
t.Fatalf("Create failed: %v", err)
}
if len(plan.Exercises) != 2 {
t.Fatalf("expected 2 exercises, got %d", len(plan.Exercises))
}
if plan.Exercises[0].Sets != 4 {
t.Errorf("sets = %d, want 4", plan.Exercises[0].Sets)
}
if plan.Exercises[0].Reps != 8 {
t.Errorf("reps = %d, want 8", plan.Exercises[0].Reps)
}
if plan.Exercises[0].Exercise.Name != "Bench Press" {
t.Errorf("exercise name = %q, want %q", plan.Exercises[0].Exercise.Name, "Bench Press")
}
})
t.Run("Create_with_duration_exercise", func(t *testing.T) {
tdb.truncateTables(t)
exercise, _ := exerciseRepo.Create(ctx, &models.CreateExerciseRequest{
Name: "Running",
Type: models.ExerciseTypeCardio,
})
req := &models.CreatePlanRequest{
Name: "Cardio Day",
Exercises: []models.PlanExerciseInput{
{ExerciseID: exercise.ID, Duration: 1800, Order: 1},
},
}
plan, err := planRepo.Create(ctx, req)
if err != nil {
t.Fatalf("Create failed: %v", err)
}
if plan.Exercises[0].Duration != 1800 {
t.Errorf("duration = %d, want 1800", plan.Exercises[0].Duration)
}
})
t.Run("GetByID_found", func(t *testing.T) {
tdb.truncateTables(t)
created, _ := planRepo.Create(ctx, &models.CreatePlanRequest{
Name: "Test Plan",
Description: "Test description",
})
plan, err := planRepo.GetByID(ctx, created.ID)
if err != nil {
t.Fatalf("GetByID failed: %v", err)
}
if plan == nil {
t.Fatal("expected plan, got nil")
}
if plan.ID != created.ID {
t.Errorf("ID = %d, want %d", plan.ID, created.ID)
}
})
t.Run("GetByID_with_exercises", func(t *testing.T) {
tdb.truncateTables(t)
exercise, _ := exerciseRepo.Create(ctx, &models.CreateExerciseRequest{
Name: "Squat",
Type: models.ExerciseTypeStrength,
})
created, _ := planRepo.Create(ctx, &models.CreatePlanRequest{
Name: "Leg Day",
Exercises: []models.PlanExerciseInput{
{ExerciseID: exercise.ID, Sets: 5, Reps: 5, Order: 1},
},
})
plan, err := planRepo.GetByID(ctx, created.ID)
if err != nil {
t.Fatalf("GetByID failed: %v", err)
}
if len(plan.Exercises) != 1 {
t.Fatalf("expected 1 exercise, got %d", len(plan.Exercises))
}
if plan.Exercises[0].Exercise.Name != "Squat" {
t.Errorf("exercise name = %q, want Squat", plan.Exercises[0].Exercise.Name)
}
})
t.Run("GetByID_not_found", func(t *testing.T) {
tdb.truncateTables(t)
plan, err := planRepo.GetByID(ctx, 99999)
if err != nil {
t.Fatalf("GetByID failed: %v", err)
}
if plan != nil {
t.Errorf("expected nil, got %+v", plan)
}
})
t.Run("Update_found", func(t *testing.T) {
tdb.truncateTables(t)
created, _ := planRepo.Create(ctx, &models.CreatePlanRequest{
Name: "Old Name",
Description: "Old description",
})
updated, err := planRepo.Update(ctx, created.ID, &models.CreatePlanRequest{
Name: "New Name",
Description: "New description",
})
if err != nil {
t.Fatalf("Update failed: %v", err)
}
if updated == nil {
t.Fatal("expected updated plan, got nil")
}
if updated.Name != "New Name" {
t.Errorf("name = %q, want New Name", updated.Name)
}
if updated.Description != "New description" {
t.Errorf("description = %q, want New description", updated.Description)
}
})
t.Run("Update_replaces_exercises", func(t *testing.T) {
tdb.truncateTables(t)
exercise1, _ := exerciseRepo.Create(ctx, &models.CreateExerciseRequest{
Name: "Old Exercise",
Type: models.ExerciseTypeStrength,
})
exercise2, _ := exerciseRepo.Create(ctx, &models.CreateExerciseRequest{
Name: "New Exercise",
Type: models.ExerciseTypeStrength,
})
created, _ := planRepo.Create(ctx, &models.CreatePlanRequest{
Name: "Test Plan",
Exercises: []models.PlanExerciseInput{
{ExerciseID: exercise1.ID, Sets: 3, Reps: 10, Order: 1},
},
})
updated, err := planRepo.Update(ctx, created.ID, &models.CreatePlanRequest{
Name: "Test Plan",
Exercises: []models.PlanExerciseInput{
{ExerciseID: exercise2.ID, Sets: 4, Reps: 12, Order: 1},
},
})
if err != nil {
t.Fatalf("Update failed: %v", err)
}
if len(updated.Exercises) != 1 {
t.Fatalf("expected 1 exercise, got %d", len(updated.Exercises))
}
if updated.Exercises[0].Exercise.Name != "New Exercise" {
t.Errorf("exercise name = %q, want New Exercise", updated.Exercises[0].Exercise.Name)
}
if updated.Exercises[0].Sets != 4 {
t.Errorf("sets = %d, want 4", updated.Exercises[0].Sets)
}
})
t.Run("Update_not_found", func(t *testing.T) {
tdb.truncateTables(t)
updated, err := planRepo.Update(ctx, 99999, &models.CreatePlanRequest{
Name: "Test",
})
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, _ := planRepo.Create(ctx, &models.CreatePlanRequest{
Name: "To Delete",
})
err := planRepo.Delete(ctx, created.ID)
if err != nil {
t.Fatalf("Delete failed: %v", err)
}
plan, _ := planRepo.GetByID(ctx, created.ID)
if plan != nil {
t.Error("expected plan to be deleted")
}
})
t.Run("Delete_cascades_exercises", func(t *testing.T) {
tdb.truncateTables(t)
exercise, _ := exerciseRepo.Create(ctx, &models.CreateExerciseRequest{
Name: "Test Exercise",
Type: models.ExerciseTypeStrength,
})
created, _ := planRepo.Create(ctx, &models.CreatePlanRequest{
Name: "Plan with Exercises",
Exercises: []models.PlanExerciseInput{
{ExerciseID: exercise.ID, Sets: 3, Reps: 10, Order: 1},
},
})
err := planRepo.Delete(ctx, created.ID)
if err != nil {
t.Fatalf("Delete failed: %v", err)
}
// Verify plan exercises are deleted
var count int
tdb.QueryRowContext(ctx, "SELECT COUNT(*) FROM plan_exercises WHERE plan_id = $1", created.ID).Scan(&count)
if count != 0 {
t.Errorf("expected 0 plan_exercises, got %d", count)
}
// But the exercise itself should still exist
existingExercise, _ := exerciseRepo.GetByID(ctx, exercise.ID)
if existingExercise == nil {
t.Error("expected exercise to still exist")
}
})
t.Run("Delete_not_found", func(t *testing.T) {
tdb.truncateTables(t)
err := planRepo.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)
planRepo.Create(ctx, &models.CreatePlanRequest{Name: "Alpha Plan"})
planRepo.Create(ctx, &models.CreatePlanRequest{Name: "Beta Plan"})
planRepo.Create(ctx, &models.CreatePlanRequest{Name: "Gamma Plan"})
plans, err := planRepo.List(ctx)
if err != nil {
t.Fatalf("List failed: %v", err)
}
if len(plans) != 3 {
t.Errorf("len = %d, want 3", len(plans))
}
// Results are ordered by name
if plans[0].Name != "Alpha Plan" {
t.Errorf("first plan = %q, want Alpha Plan", plans[0].Name)
}
})
}