347 lines
8.7 KiB
Go
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)
|
|
}
|
|
})
|
|
}
|