test: Add unit and integration tests for API and storage
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
245
backend/internal/storage/exercises_test.go
Normal file
245
backend/internal/storage/exercises_test.go
Normal file
@@ -0,0 +1,245 @@
|
||||
//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)
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user