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:
374
backend/internal/api/exercises_test.go
Normal file
374
backend/internal/api/exercises_test.go
Normal file
@@ -0,0 +1,374 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"training-tracker/internal/models"
|
||||
)
|
||||
|
||||
func TestExerciseHandler_List(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
mock *mockExerciseRepo
|
||||
wantStatus int
|
||||
wantLen int
|
||||
}{
|
||||
{
|
||||
name: "empty list",
|
||||
mock: &mockExerciseRepo{exercises: nil},
|
||||
wantStatus: http.StatusOK,
|
||||
wantLen: 0,
|
||||
},
|
||||
{
|
||||
name: "multiple exercises",
|
||||
mock: &mockExerciseRepo{
|
||||
exercises: []models.Exercise{
|
||||
{ID: 1, Name: "Bench Press", Type: models.ExerciseTypeStrength},
|
||||
{ID: 2, Name: "Running", Type: models.ExerciseTypeCardio},
|
||||
},
|
||||
},
|
||||
wantStatus: http.StatusOK,
|
||||
wantLen: 2,
|
||||
},
|
||||
{
|
||||
name: "repository error",
|
||||
mock: &mockExerciseRepo{err: errors.New("db error")},
|
||||
wantStatus: http.StatusInternalServerError,
|
||||
wantLen: 0,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
handler := NewExerciseHandler(tt.mock)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/exercises", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
handler.ServeHTTP(rec, req)
|
||||
|
||||
if rec.Code != tt.wantStatus {
|
||||
t.Errorf("status = %d, want %d", rec.Code, tt.wantStatus)
|
||||
}
|
||||
|
||||
if tt.wantStatus == http.StatusOK {
|
||||
var exercises []models.Exercise
|
||||
if err := json.NewDecoder(rec.Body).Decode(&exercises); err != nil {
|
||||
t.Fatalf("failed to decode response: %v", err)
|
||||
}
|
||||
if len(exercises) != tt.wantLen {
|
||||
t.Errorf("len = %d, want %d", len(exercises), tt.wantLen)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestExerciseHandler_GetByID(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
now := time.Now()
|
||||
tests := []struct {
|
||||
name string
|
||||
url string
|
||||
mock *mockExerciseRepo
|
||||
wantStatus int
|
||||
wantName string
|
||||
}{
|
||||
{
|
||||
name: "found",
|
||||
url: "/api/exercises/1",
|
||||
mock: &mockExerciseRepo{
|
||||
exercise: &models.Exercise{
|
||||
ID: 1,
|
||||
Name: "Squat",
|
||||
Type: models.ExerciseTypeStrength,
|
||||
CreatedAt: now,
|
||||
},
|
||||
},
|
||||
wantStatus: http.StatusOK,
|
||||
wantName: "Squat",
|
||||
},
|
||||
{
|
||||
name: "not found",
|
||||
url: "/api/exercises/999",
|
||||
mock: &mockExerciseRepo{exercise: nil},
|
||||
wantStatus: http.StatusNotFound,
|
||||
},
|
||||
{
|
||||
name: "repository error",
|
||||
url: "/api/exercises/1",
|
||||
mock: &mockExerciseRepo{err: errors.New("db error")},
|
||||
wantStatus: http.StatusInternalServerError,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
handler := NewExerciseHandler(tt.mock)
|
||||
req := httptest.NewRequest(http.MethodGet, tt.url, nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
handler.ServeHTTP(rec, req)
|
||||
|
||||
if rec.Code != tt.wantStatus {
|
||||
t.Errorf("status = %d, want %d", rec.Code, tt.wantStatus)
|
||||
}
|
||||
|
||||
if tt.wantStatus == http.StatusOK {
|
||||
var exercise models.Exercise
|
||||
if err := json.NewDecoder(rec.Body).Decode(&exercise); err != nil {
|
||||
t.Fatalf("failed to decode response: %v", err)
|
||||
}
|
||||
if exercise.Name != tt.wantName {
|
||||
t.Errorf("name = %q, want %q", exercise.Name, tt.wantName)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestExerciseHandler_Create(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
now := time.Now()
|
||||
tests := []struct {
|
||||
name string
|
||||
body string
|
||||
mock *mockExerciseRepo
|
||||
wantStatus int
|
||||
}{
|
||||
{
|
||||
name: "valid strength exercise",
|
||||
body: `{"name":"Deadlift","type":"strength","muscle_group":"Back"}`,
|
||||
mock: &mockExerciseRepo{
|
||||
exercise: &models.Exercise{
|
||||
ID: 1,
|
||||
Name: "Deadlift",
|
||||
Type: models.ExerciseTypeStrength,
|
||||
MuscleGroup: "Back",
|
||||
CreatedAt: now,
|
||||
},
|
||||
},
|
||||
wantStatus: http.StatusCreated,
|
||||
},
|
||||
{
|
||||
name: "valid cardio exercise",
|
||||
body: `{"name":"Cycling","type":"cardio"}`,
|
||||
mock: &mockExerciseRepo{
|
||||
exercise: &models.Exercise{
|
||||
ID: 2,
|
||||
Name: "Cycling",
|
||||
Type: models.ExerciseTypeCardio,
|
||||
CreatedAt: now,
|
||||
},
|
||||
},
|
||||
wantStatus: http.StatusCreated,
|
||||
},
|
||||
{
|
||||
name: "missing name",
|
||||
body: `{"type":"strength"}`,
|
||||
mock: &mockExerciseRepo{},
|
||||
wantStatus: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
name: "missing type",
|
||||
body: `{"name":"Test"}`,
|
||||
mock: &mockExerciseRepo{},
|
||||
wantStatus: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
name: "invalid type",
|
||||
body: `{"name":"Test","type":"invalid"}`,
|
||||
mock: &mockExerciseRepo{},
|
||||
wantStatus: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
name: "invalid JSON",
|
||||
body: `{invalid}`,
|
||||
mock: &mockExerciseRepo{},
|
||||
wantStatus: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
name: "repository error",
|
||||
body: `{"name":"Test","type":"strength"}`,
|
||||
mock: &mockExerciseRepo{err: errors.New("db error")},
|
||||
wantStatus: http.StatusInternalServerError,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
handler := NewExerciseHandler(tt.mock)
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/exercises", bytes.NewBufferString(tt.body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
handler.ServeHTTP(rec, req)
|
||||
|
||||
if rec.Code != tt.wantStatus {
|
||||
t.Errorf("status = %d, want %d", rec.Code, tt.wantStatus)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestExerciseHandler_Update(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
now := time.Now()
|
||||
tests := []struct {
|
||||
name string
|
||||
url string
|
||||
body string
|
||||
mock *mockExerciseRepo
|
||||
wantStatus int
|
||||
}{
|
||||
{
|
||||
name: "valid update",
|
||||
url: "/api/exercises/1",
|
||||
body: `{"name":"Updated Squat","type":"strength"}`,
|
||||
mock: &mockExerciseRepo{
|
||||
exercise: &models.Exercise{
|
||||
ID: 1,
|
||||
Name: "Updated Squat",
|
||||
Type: models.ExerciseTypeStrength,
|
||||
CreatedAt: now,
|
||||
},
|
||||
},
|
||||
wantStatus: http.StatusOK,
|
||||
},
|
||||
{
|
||||
name: "not found",
|
||||
url: "/api/exercises/999",
|
||||
body: `{"name":"Test","type":"strength"}`,
|
||||
mock: &mockExerciseRepo{exercise: nil},
|
||||
wantStatus: http.StatusNotFound,
|
||||
},
|
||||
{
|
||||
name: "missing name",
|
||||
url: "/api/exercises/1",
|
||||
body: `{"type":"strength"}`,
|
||||
mock: &mockExerciseRepo{},
|
||||
wantStatus: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
name: "invalid type",
|
||||
url: "/api/exercises/1",
|
||||
body: `{"name":"Test","type":"invalid"}`,
|
||||
mock: &mockExerciseRepo{},
|
||||
wantStatus: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
name: "no ID in URL",
|
||||
url: "/api/exercises/",
|
||||
body: `{"name":"Test","type":"strength"}`,
|
||||
mock: &mockExerciseRepo{},
|
||||
wantStatus: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
name: "repository error",
|
||||
url: "/api/exercises/1",
|
||||
body: `{"name":"Test","type":"strength"}`,
|
||||
mock: &mockExerciseRepo{err: errors.New("db error")},
|
||||
wantStatus: http.StatusInternalServerError,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
handler := NewExerciseHandler(tt.mock)
|
||||
req := httptest.NewRequest(http.MethodPut, tt.url, bytes.NewBufferString(tt.body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
handler.ServeHTTP(rec, req)
|
||||
|
||||
if rec.Code != tt.wantStatus {
|
||||
t.Errorf("status = %d, want %d", rec.Code, tt.wantStatus)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestExerciseHandler_Delete(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
url string
|
||||
mock *mockExerciseRepo
|
||||
wantStatus int
|
||||
}{
|
||||
{
|
||||
name: "successful delete",
|
||||
url: "/api/exercises/1",
|
||||
mock: &mockExerciseRepo{deleteErr: nil},
|
||||
wantStatus: http.StatusNoContent,
|
||||
},
|
||||
{
|
||||
name: "not found",
|
||||
url: "/api/exercises/999",
|
||||
mock: &mockExerciseRepo{deleteErr: sql.ErrNoRows},
|
||||
wantStatus: http.StatusNotFound,
|
||||
},
|
||||
{
|
||||
name: "no ID in URL",
|
||||
url: "/api/exercises/",
|
||||
mock: &mockExerciseRepo{},
|
||||
wantStatus: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
name: "repository error",
|
||||
url: "/api/exercises/1",
|
||||
mock: &mockExerciseRepo{deleteErr: errors.New("db error")},
|
||||
wantStatus: http.StatusInternalServerError,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
handler := NewExerciseHandler(tt.mock)
|
||||
req := httptest.NewRequest(http.MethodDelete, tt.url, nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
handler.ServeHTTP(rec, req)
|
||||
|
||||
if rec.Code != tt.wantStatus {
|
||||
t.Errorf("status = %d, want %d", rec.Code, tt.wantStatus)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestExerciseHandler_MethodNotAllowed(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
handler := NewExerciseHandler(&mockExerciseRepo{})
|
||||
req := httptest.NewRequest(http.MethodPatch, "/api/exercises/1", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
handler.ServeHTTP(rec, req)
|
||||
|
||||
if rec.Code != http.StatusMethodNotAllowed {
|
||||
t.Errorf("status = %d, want %d", rec.Code, http.StatusMethodNotAllowed)
|
||||
}
|
||||
}
|
||||
97
backend/internal/api/mocks_test.go
Normal file
97
backend/internal/api/mocks_test.go
Normal file
@@ -0,0 +1,97 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"training-tracker/internal/models"
|
||||
)
|
||||
|
||||
// mockExerciseRepo is a mock implementation of ExerciseRepository for testing
|
||||
type mockExerciseRepo struct {
|
||||
exercises []models.Exercise
|
||||
exercise *models.Exercise
|
||||
err error
|
||||
deleteErr error
|
||||
}
|
||||
|
||||
func (m *mockExerciseRepo) List(ctx context.Context) ([]models.Exercise, error) {
|
||||
return m.exercises, m.err
|
||||
}
|
||||
|
||||
func (m *mockExerciseRepo) GetByID(ctx context.Context, id int64) (*models.Exercise, error) {
|
||||
return m.exercise, m.err
|
||||
}
|
||||
|
||||
func (m *mockExerciseRepo) Create(ctx context.Context, req *models.CreateExerciseRequest) (*models.Exercise, error) {
|
||||
return m.exercise, m.err
|
||||
}
|
||||
|
||||
func (m *mockExerciseRepo) Update(ctx context.Context, id int64, req *models.CreateExerciseRequest) (*models.Exercise, error) {
|
||||
return m.exercise, m.err
|
||||
}
|
||||
|
||||
func (m *mockExerciseRepo) Delete(ctx context.Context, id int64) error {
|
||||
return m.deleteErr
|
||||
}
|
||||
|
||||
// mockPlanRepo is a mock implementation of PlanRepository for testing
|
||||
type mockPlanRepo struct {
|
||||
plans []models.TrainingPlan
|
||||
plan *models.TrainingPlan
|
||||
err error
|
||||
deleteErr error
|
||||
}
|
||||
|
||||
func (m *mockPlanRepo) List(ctx context.Context) ([]models.TrainingPlan, error) {
|
||||
return m.plans, m.err
|
||||
}
|
||||
|
||||
func (m *mockPlanRepo) GetByID(ctx context.Context, id int64) (*models.TrainingPlan, error) {
|
||||
return m.plan, m.err
|
||||
}
|
||||
|
||||
func (m *mockPlanRepo) Create(ctx context.Context, req *models.CreatePlanRequest) (*models.TrainingPlan, error) {
|
||||
return m.plan, m.err
|
||||
}
|
||||
|
||||
func (m *mockPlanRepo) Update(ctx context.Context, id int64, req *models.CreatePlanRequest) (*models.TrainingPlan, error) {
|
||||
return m.plan, m.err
|
||||
}
|
||||
|
||||
func (m *mockPlanRepo) Delete(ctx context.Context, id int64) error {
|
||||
return m.deleteErr
|
||||
}
|
||||
|
||||
// mockSessionRepo is a mock implementation of SessionRepository for testing
|
||||
type mockSessionRepo struct {
|
||||
sessions []models.Session
|
||||
session *models.Session
|
||||
entry *models.SessionEntry
|
||||
err error
|
||||
deleteErr error
|
||||
entryErr error
|
||||
}
|
||||
|
||||
func (m *mockSessionRepo) List(ctx context.Context) ([]models.Session, error) {
|
||||
return m.sessions, m.err
|
||||
}
|
||||
|
||||
func (m *mockSessionRepo) GetByID(ctx context.Context, id int64) (*models.Session, error) {
|
||||
return m.session, m.err
|
||||
}
|
||||
|
||||
func (m *mockSessionRepo) Create(ctx context.Context, req *models.CreateSessionRequest) (*models.Session, error) {
|
||||
return m.session, m.err
|
||||
}
|
||||
|
||||
func (m *mockSessionRepo) Update(ctx context.Context, id int64, req *models.CreateSessionRequest) (*models.Session, error) {
|
||||
return m.session, m.err
|
||||
}
|
||||
|
||||
func (m *mockSessionRepo) Delete(ctx context.Context, id int64) error {
|
||||
return m.deleteErr
|
||||
}
|
||||
|
||||
func (m *mockSessionRepo) AddEntry(ctx context.Context, sessionID int64, req *models.CreateSessionEntryRequest) (*models.SessionEntry, error) {
|
||||
return m.entry, m.entryErr
|
||||
}
|
||||
396
backend/internal/api/plans_test.go
Normal file
396
backend/internal/api/plans_test.go
Normal file
@@ -0,0 +1,396 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"training-tracker/internal/models"
|
||||
)
|
||||
|
||||
func TestPlanHandler_List(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
mock *mockPlanRepo
|
||||
wantStatus int
|
||||
wantLen int
|
||||
}{
|
||||
{
|
||||
name: "empty list",
|
||||
mock: &mockPlanRepo{plans: nil},
|
||||
wantStatus: http.StatusOK,
|
||||
wantLen: 0,
|
||||
},
|
||||
{
|
||||
name: "multiple plans",
|
||||
mock: &mockPlanRepo{
|
||||
plans: []models.TrainingPlan{
|
||||
{ID: 1, Name: "Push Day"},
|
||||
{ID: 2, Name: "Pull Day"},
|
||||
{ID: 3, Name: "Leg Day"},
|
||||
},
|
||||
},
|
||||
wantStatus: http.StatusOK,
|
||||
wantLen: 3,
|
||||
},
|
||||
{
|
||||
name: "repository error",
|
||||
mock: &mockPlanRepo{err: errors.New("db error")},
|
||||
wantStatus: http.StatusInternalServerError,
|
||||
wantLen: 0,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
handler := NewPlanHandler(tt.mock)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/plans", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
handler.ServeHTTP(rec, req)
|
||||
|
||||
if rec.Code != tt.wantStatus {
|
||||
t.Errorf("status = %d, want %d", rec.Code, tt.wantStatus)
|
||||
}
|
||||
|
||||
if tt.wantStatus == http.StatusOK {
|
||||
var plans []models.TrainingPlan
|
||||
if err := json.NewDecoder(rec.Body).Decode(&plans); err != nil {
|
||||
t.Fatalf("failed to decode response: %v", err)
|
||||
}
|
||||
if len(plans) != tt.wantLen {
|
||||
t.Errorf("len = %d, want %d", len(plans), tt.wantLen)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPlanHandler_GetByID(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
now := time.Now()
|
||||
tests := []struct {
|
||||
name string
|
||||
url string
|
||||
mock *mockPlanRepo
|
||||
wantStatus int
|
||||
wantName string
|
||||
}{
|
||||
{
|
||||
name: "found",
|
||||
url: "/api/plans/1",
|
||||
mock: &mockPlanRepo{
|
||||
plan: &models.TrainingPlan{
|
||||
ID: 1,
|
||||
Name: "Full Body",
|
||||
Description: "Complete workout",
|
||||
CreatedAt: now,
|
||||
},
|
||||
},
|
||||
wantStatus: http.StatusOK,
|
||||
wantName: "Full Body",
|
||||
},
|
||||
{
|
||||
name: "found with exercises",
|
||||
url: "/api/plans/2",
|
||||
mock: &mockPlanRepo{
|
||||
plan: &models.TrainingPlan{
|
||||
ID: 2,
|
||||
Name: "Upper Body",
|
||||
Exercises: []models.PlanExercise{
|
||||
{ID: 1, PlanID: 2, ExerciseID: 1, Sets: 3, Reps: 10},
|
||||
{ID: 2, PlanID: 2, ExerciseID: 2, Sets: 4, Reps: 8},
|
||||
},
|
||||
CreatedAt: now,
|
||||
},
|
||||
},
|
||||
wantStatus: http.StatusOK,
|
||||
wantName: "Upper Body",
|
||||
},
|
||||
{
|
||||
name: "not found",
|
||||
url: "/api/plans/999",
|
||||
mock: &mockPlanRepo{plan: nil},
|
||||
wantStatus: http.StatusNotFound,
|
||||
},
|
||||
{
|
||||
name: "repository error",
|
||||
url: "/api/plans/1",
|
||||
mock: &mockPlanRepo{err: errors.New("db error")},
|
||||
wantStatus: http.StatusInternalServerError,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
handler := NewPlanHandler(tt.mock)
|
||||
req := httptest.NewRequest(http.MethodGet, tt.url, nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
handler.ServeHTTP(rec, req)
|
||||
|
||||
if rec.Code != tt.wantStatus {
|
||||
t.Errorf("status = %d, want %d", rec.Code, tt.wantStatus)
|
||||
}
|
||||
|
||||
if tt.wantStatus == http.StatusOK {
|
||||
var plan models.TrainingPlan
|
||||
if err := json.NewDecoder(rec.Body).Decode(&plan); err != nil {
|
||||
t.Fatalf("failed to decode response: %v", err)
|
||||
}
|
||||
if plan.Name != tt.wantName {
|
||||
t.Errorf("name = %q, want %q", plan.Name, tt.wantName)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPlanHandler_Create(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
now := time.Now()
|
||||
tests := []struct {
|
||||
name string
|
||||
body string
|
||||
mock *mockPlanRepo
|
||||
wantStatus int
|
||||
}{
|
||||
{
|
||||
name: "valid plan without exercises",
|
||||
body: `{"name":"Beginner Plan","description":"A simple plan"}`,
|
||||
mock: &mockPlanRepo{
|
||||
plan: &models.TrainingPlan{
|
||||
ID: 1,
|
||||
Name: "Beginner Plan",
|
||||
Description: "A simple plan",
|
||||
CreatedAt: now,
|
||||
},
|
||||
},
|
||||
wantStatus: http.StatusCreated,
|
||||
},
|
||||
{
|
||||
name: "valid plan with exercises",
|
||||
body: `{"name":"Advanced Plan","exercises":[{"exercise_id":1,"sets":3,"reps":10,"order":1}]}`,
|
||||
mock: &mockPlanRepo{
|
||||
plan: &models.TrainingPlan{
|
||||
ID: 2,
|
||||
Name: "Advanced Plan",
|
||||
Exercises: []models.PlanExercise{
|
||||
{ID: 1, PlanID: 2, ExerciseID: 1, Sets: 3, Reps: 10, Order: 1},
|
||||
},
|
||||
CreatedAt: now,
|
||||
},
|
||||
},
|
||||
wantStatus: http.StatusCreated,
|
||||
},
|
||||
{
|
||||
name: "missing name",
|
||||
body: `{"description":"No name"}`,
|
||||
mock: &mockPlanRepo{},
|
||||
wantStatus: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
name: "empty name",
|
||||
body: `{"name":""}`,
|
||||
mock: &mockPlanRepo{},
|
||||
wantStatus: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
name: "invalid JSON",
|
||||
body: `{invalid}`,
|
||||
mock: &mockPlanRepo{},
|
||||
wantStatus: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
name: "repository error",
|
||||
body: `{"name":"Test Plan"}`,
|
||||
mock: &mockPlanRepo{err: errors.New("db error")},
|
||||
wantStatus: http.StatusInternalServerError,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
handler := NewPlanHandler(tt.mock)
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/plans", bytes.NewBufferString(tt.body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
handler.ServeHTTP(rec, req)
|
||||
|
||||
if rec.Code != tt.wantStatus {
|
||||
t.Errorf("status = %d, want %d", rec.Code, tt.wantStatus)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPlanHandler_Update(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
now := time.Now()
|
||||
tests := []struct {
|
||||
name string
|
||||
url string
|
||||
body string
|
||||
mock *mockPlanRepo
|
||||
wantStatus int
|
||||
}{
|
||||
{
|
||||
name: "valid update",
|
||||
url: "/api/plans/1",
|
||||
body: `{"name":"Updated Plan","description":"New description"}`,
|
||||
mock: &mockPlanRepo{
|
||||
plan: &models.TrainingPlan{
|
||||
ID: 1,
|
||||
Name: "Updated Plan",
|
||||
Description: "New description",
|
||||
CreatedAt: now,
|
||||
},
|
||||
},
|
||||
wantStatus: http.StatusOK,
|
||||
},
|
||||
{
|
||||
name: "update with exercises",
|
||||
url: "/api/plans/1",
|
||||
body: `{"name":"Plan with Exercises","exercises":[{"exercise_id":1,"sets":4,"reps":12,"order":1}]}`,
|
||||
mock: &mockPlanRepo{
|
||||
plan: &models.TrainingPlan{
|
||||
ID: 1,
|
||||
Name: "Plan with Exercises",
|
||||
Exercises: []models.PlanExercise{
|
||||
{ID: 1, PlanID: 1, ExerciseID: 1, Sets: 4, Reps: 12, Order: 1},
|
||||
},
|
||||
CreatedAt: now,
|
||||
},
|
||||
},
|
||||
wantStatus: http.StatusOK,
|
||||
},
|
||||
{
|
||||
name: "not found",
|
||||
url: "/api/plans/999",
|
||||
body: `{"name":"Test"}`,
|
||||
mock: &mockPlanRepo{plan: nil},
|
||||
wantStatus: http.StatusNotFound,
|
||||
},
|
||||
{
|
||||
name: "missing name",
|
||||
url: "/api/plans/1",
|
||||
body: `{"description":"No name"}`,
|
||||
mock: &mockPlanRepo{},
|
||||
wantStatus: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
name: "no ID in URL",
|
||||
url: "/api/plans/",
|
||||
body: `{"name":"Test"}`,
|
||||
mock: &mockPlanRepo{},
|
||||
wantStatus: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
name: "repository error",
|
||||
url: "/api/plans/1",
|
||||
body: `{"name":"Test"}`,
|
||||
mock: &mockPlanRepo{err: errors.New("db error")},
|
||||
wantStatus: http.StatusInternalServerError,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
handler := NewPlanHandler(tt.mock)
|
||||
req := httptest.NewRequest(http.MethodPut, tt.url, bytes.NewBufferString(tt.body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
handler.ServeHTTP(rec, req)
|
||||
|
||||
if rec.Code != tt.wantStatus {
|
||||
t.Errorf("status = %d, want %d", rec.Code, tt.wantStatus)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPlanHandler_Delete(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
url string
|
||||
mock *mockPlanRepo
|
||||
wantStatus int
|
||||
}{
|
||||
{
|
||||
name: "successful delete",
|
||||
url: "/api/plans/1",
|
||||
mock: &mockPlanRepo{deleteErr: nil},
|
||||
wantStatus: http.StatusNoContent,
|
||||
},
|
||||
{
|
||||
name: "not found",
|
||||
url: "/api/plans/999",
|
||||
mock: &mockPlanRepo{deleteErr: sql.ErrNoRows},
|
||||
wantStatus: http.StatusNotFound,
|
||||
},
|
||||
{
|
||||
name: "no ID in URL",
|
||||
url: "/api/plans/",
|
||||
mock: &mockPlanRepo{},
|
||||
wantStatus: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
name: "repository error",
|
||||
url: "/api/plans/1",
|
||||
mock: &mockPlanRepo{deleteErr: errors.New("db error")},
|
||||
wantStatus: http.StatusInternalServerError,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
handler := NewPlanHandler(tt.mock)
|
||||
req := httptest.NewRequest(http.MethodDelete, tt.url, nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
handler.ServeHTTP(rec, req)
|
||||
|
||||
if rec.Code != tt.wantStatus {
|
||||
t.Errorf("status = %d, want %d", rec.Code, tt.wantStatus)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPlanHandler_MethodNotAllowed(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
handler := NewPlanHandler(&mockPlanRepo{})
|
||||
req := httptest.NewRequest(http.MethodPatch, "/api/plans/1", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
handler.ServeHTTP(rec, req)
|
||||
|
||||
if rec.Code != http.StatusMethodNotAllowed {
|
||||
t.Errorf("status = %d, want %d", rec.Code, http.StatusMethodNotAllowed)
|
||||
}
|
||||
}
|
||||
517
backend/internal/api/sessions_test.go
Normal file
517
backend/internal/api/sessions_test.go
Normal file
@@ -0,0 +1,517 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"training-tracker/internal/models"
|
||||
)
|
||||
|
||||
func TestSessionHandler_List(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
now := time.Now()
|
||||
tests := []struct {
|
||||
name string
|
||||
mock *mockSessionRepo
|
||||
wantStatus int
|
||||
wantLen int
|
||||
}{
|
||||
{
|
||||
name: "empty list",
|
||||
mock: &mockSessionRepo{sessions: nil},
|
||||
wantStatus: http.StatusOK,
|
||||
wantLen: 0,
|
||||
},
|
||||
{
|
||||
name: "multiple sessions",
|
||||
mock: &mockSessionRepo{
|
||||
sessions: []models.Session{
|
||||
{ID: 1, Date: now, Notes: "Morning workout"},
|
||||
{ID: 2, Date: now.AddDate(0, 0, -1), Notes: "Evening cardio"},
|
||||
},
|
||||
},
|
||||
wantStatus: http.StatusOK,
|
||||
wantLen: 2,
|
||||
},
|
||||
{
|
||||
name: "session with plan",
|
||||
mock: &mockSessionRepo{
|
||||
sessions: []models.Session{
|
||||
{
|
||||
ID: 1,
|
||||
Date: now,
|
||||
PlanID: ptr(int64(1)),
|
||||
Plan: &models.TrainingPlan{ID: 1, Name: "Push Day"},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantStatus: http.StatusOK,
|
||||
wantLen: 1,
|
||||
},
|
||||
{
|
||||
name: "repository error",
|
||||
mock: &mockSessionRepo{err: errors.New("db error")},
|
||||
wantStatus: http.StatusInternalServerError,
|
||||
wantLen: 0,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
handler := NewSessionHandler(tt.mock)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/sessions", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
handler.ServeHTTP(rec, req)
|
||||
|
||||
if rec.Code != tt.wantStatus {
|
||||
t.Errorf("status = %d, want %d", rec.Code, tt.wantStatus)
|
||||
}
|
||||
|
||||
if tt.wantStatus == http.StatusOK {
|
||||
var sessions []models.Session
|
||||
if err := json.NewDecoder(rec.Body).Decode(&sessions); err != nil {
|
||||
t.Fatalf("failed to decode response: %v", err)
|
||||
}
|
||||
if len(sessions) != tt.wantLen {
|
||||
t.Errorf("len = %d, want %d", len(sessions), tt.wantLen)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSessionHandler_GetByID(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
now := time.Now()
|
||||
tests := []struct {
|
||||
name string
|
||||
url string
|
||||
mock *mockSessionRepo
|
||||
wantStatus int
|
||||
wantNotes string
|
||||
}{
|
||||
{
|
||||
name: "found",
|
||||
url: "/api/sessions/1",
|
||||
mock: &mockSessionRepo{
|
||||
session: &models.Session{
|
||||
ID: 1,
|
||||
Date: now,
|
||||
Notes: "Great workout",
|
||||
CreatedAt: now,
|
||||
},
|
||||
},
|
||||
wantStatus: http.StatusOK,
|
||||
wantNotes: "Great workout",
|
||||
},
|
||||
{
|
||||
name: "found with entries",
|
||||
url: "/api/sessions/1",
|
||||
mock: &mockSessionRepo{
|
||||
session: &models.Session{
|
||||
ID: 1,
|
||||
Date: now,
|
||||
Notes: "Complete session",
|
||||
Entries: []models.SessionEntry{
|
||||
{ID: 1, SessionID: 1, ExerciseID: 1, Weight: 100, Reps: 10},
|
||||
{ID: 2, SessionID: 1, ExerciseID: 2, Duration: 1800},
|
||||
},
|
||||
CreatedAt: now,
|
||||
},
|
||||
},
|
||||
wantStatus: http.StatusOK,
|
||||
wantNotes: "Complete session",
|
||||
},
|
||||
{
|
||||
name: "not found",
|
||||
url: "/api/sessions/999",
|
||||
mock: &mockSessionRepo{session: nil},
|
||||
wantStatus: http.StatusNotFound,
|
||||
},
|
||||
{
|
||||
name: "repository error",
|
||||
url: "/api/sessions/1",
|
||||
mock: &mockSessionRepo{err: errors.New("db error")},
|
||||
wantStatus: http.StatusInternalServerError,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
handler := NewSessionHandler(tt.mock)
|
||||
req := httptest.NewRequest(http.MethodGet, tt.url, nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
handler.ServeHTTP(rec, req)
|
||||
|
||||
if rec.Code != tt.wantStatus {
|
||||
t.Errorf("status = %d, want %d", rec.Code, tt.wantStatus)
|
||||
}
|
||||
|
||||
if tt.wantStatus == http.StatusOK {
|
||||
var session models.Session
|
||||
if err := json.NewDecoder(rec.Body).Decode(&session); err != nil {
|
||||
t.Fatalf("failed to decode response: %v", err)
|
||||
}
|
||||
if session.Notes != tt.wantNotes {
|
||||
t.Errorf("notes = %q, want %q", session.Notes, tt.wantNotes)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSessionHandler_Create(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
now := time.Now()
|
||||
dateStr := now.Format(time.RFC3339)
|
||||
tests := []struct {
|
||||
name string
|
||||
body string
|
||||
mock *mockSessionRepo
|
||||
wantStatus int
|
||||
}{
|
||||
{
|
||||
name: "valid session without plan",
|
||||
body: `{"date":"` + dateStr + `","notes":"Morning run"}`,
|
||||
mock: &mockSessionRepo{
|
||||
session: &models.Session{
|
||||
ID: 1,
|
||||
Date: now,
|
||||
Notes: "Morning run",
|
||||
CreatedAt: now,
|
||||
},
|
||||
},
|
||||
wantStatus: http.StatusCreated,
|
||||
},
|
||||
{
|
||||
name: "valid session with plan",
|
||||
body: `{"date":"` + dateStr + `","plan_id":1,"notes":"Following plan"}`,
|
||||
mock: &mockSessionRepo{
|
||||
session: &models.Session{
|
||||
ID: 2,
|
||||
Date: now,
|
||||
PlanID: ptr(int64(1)),
|
||||
Notes: "Following plan",
|
||||
CreatedAt: now,
|
||||
},
|
||||
},
|
||||
wantStatus: http.StatusCreated,
|
||||
},
|
||||
{
|
||||
name: "missing date",
|
||||
body: `{"notes":"No date"}`,
|
||||
mock: &mockSessionRepo{},
|
||||
wantStatus: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
name: "invalid JSON",
|
||||
body: `{invalid}`,
|
||||
mock: &mockSessionRepo{},
|
||||
wantStatus: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
name: "repository error",
|
||||
body: `{"date":"` + dateStr + `"}`,
|
||||
mock: &mockSessionRepo{err: errors.New("db error")},
|
||||
wantStatus: http.StatusInternalServerError,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
handler := NewSessionHandler(tt.mock)
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/sessions", bytes.NewBufferString(tt.body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
handler.ServeHTTP(rec, req)
|
||||
|
||||
if rec.Code != tt.wantStatus {
|
||||
t.Errorf("status = %d, want %d", rec.Code, tt.wantStatus)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSessionHandler_Update(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
now := time.Now()
|
||||
dateStr := now.Format(time.RFC3339)
|
||||
tests := []struct {
|
||||
name string
|
||||
url string
|
||||
body string
|
||||
mock *mockSessionRepo
|
||||
wantStatus int
|
||||
}{
|
||||
{
|
||||
name: "valid update",
|
||||
url: "/api/sessions/1",
|
||||
body: `{"date":"` + dateStr + `","notes":"Updated notes"}`,
|
||||
mock: &mockSessionRepo{
|
||||
session: &models.Session{
|
||||
ID: 1,
|
||||
Date: now,
|
||||
Notes: "Updated notes",
|
||||
CreatedAt: now,
|
||||
},
|
||||
},
|
||||
wantStatus: http.StatusOK,
|
||||
},
|
||||
{
|
||||
name: "update with plan",
|
||||
url: "/api/sessions/1",
|
||||
body: `{"date":"` + dateStr + `","plan_id":2}`,
|
||||
mock: &mockSessionRepo{
|
||||
session: &models.Session{
|
||||
ID: 1,
|
||||
Date: now,
|
||||
PlanID: ptr(int64(2)),
|
||||
CreatedAt: now,
|
||||
},
|
||||
},
|
||||
wantStatus: http.StatusOK,
|
||||
},
|
||||
{
|
||||
name: "not found",
|
||||
url: "/api/sessions/999",
|
||||
body: `{"date":"` + dateStr + `"}`,
|
||||
mock: &mockSessionRepo{session: nil},
|
||||
wantStatus: http.StatusNotFound,
|
||||
},
|
||||
{
|
||||
name: "missing date",
|
||||
url: "/api/sessions/1",
|
||||
body: `{"notes":"No date"}`,
|
||||
mock: &mockSessionRepo{},
|
||||
wantStatus: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
name: "no ID in URL",
|
||||
url: "/api/sessions/",
|
||||
body: `{"date":"` + dateStr + `"}`,
|
||||
mock: &mockSessionRepo{},
|
||||
wantStatus: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
name: "repository error",
|
||||
url: "/api/sessions/1",
|
||||
body: `{"date":"` + dateStr + `"}`,
|
||||
mock: &mockSessionRepo{err: errors.New("db error")},
|
||||
wantStatus: http.StatusInternalServerError,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
handler := NewSessionHandler(tt.mock)
|
||||
req := httptest.NewRequest(http.MethodPut, tt.url, bytes.NewBufferString(tt.body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
handler.ServeHTTP(rec, req)
|
||||
|
||||
if rec.Code != tt.wantStatus {
|
||||
t.Errorf("status = %d, want %d", rec.Code, tt.wantStatus)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSessionHandler_Delete(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
url string
|
||||
mock *mockSessionRepo
|
||||
wantStatus int
|
||||
}{
|
||||
{
|
||||
name: "successful delete",
|
||||
url: "/api/sessions/1",
|
||||
mock: &mockSessionRepo{deleteErr: nil},
|
||||
wantStatus: http.StatusNoContent,
|
||||
},
|
||||
{
|
||||
name: "not found",
|
||||
url: "/api/sessions/999",
|
||||
mock: &mockSessionRepo{deleteErr: sql.ErrNoRows},
|
||||
wantStatus: http.StatusNotFound,
|
||||
},
|
||||
{
|
||||
name: "no ID in URL",
|
||||
url: "/api/sessions/",
|
||||
mock: &mockSessionRepo{},
|
||||
wantStatus: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
name: "repository error",
|
||||
url: "/api/sessions/1",
|
||||
mock: &mockSessionRepo{deleteErr: errors.New("db error")},
|
||||
wantStatus: http.StatusInternalServerError,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
handler := NewSessionHandler(tt.mock)
|
||||
req := httptest.NewRequest(http.MethodDelete, tt.url, nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
handler.ServeHTTP(rec, req)
|
||||
|
||||
if rec.Code != tt.wantStatus {
|
||||
t.Errorf("status = %d, want %d", rec.Code, tt.wantStatus)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSessionHandler_AddEntry(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
now := time.Now()
|
||||
tests := []struct {
|
||||
name string
|
||||
url string
|
||||
body string
|
||||
mock *mockSessionRepo
|
||||
wantStatus int
|
||||
}{
|
||||
{
|
||||
name: "valid strength entry",
|
||||
url: "/api/sessions/1/entries",
|
||||
body: `{"exercise_id":1,"weight":100,"reps":10,"sets_completed":3}`,
|
||||
mock: &mockSessionRepo{
|
||||
entry: &models.SessionEntry{
|
||||
ID: 1,
|
||||
SessionID: 1,
|
||||
ExerciseID: 1,
|
||||
Weight: 100,
|
||||
Reps: 10,
|
||||
SetsCompleted: 3,
|
||||
CreatedAt: now,
|
||||
},
|
||||
},
|
||||
wantStatus: http.StatusCreated,
|
||||
},
|
||||
{
|
||||
name: "valid cardio entry",
|
||||
url: "/api/sessions/1/entries",
|
||||
body: `{"exercise_id":2,"duration":1800,"distance":5.5,"heart_rate":145}`,
|
||||
mock: &mockSessionRepo{
|
||||
entry: &models.SessionEntry{
|
||||
ID: 2,
|
||||
SessionID: 1,
|
||||
ExerciseID: 2,
|
||||
Duration: 1800,
|
||||
Distance: 5.5,
|
||||
HeartRate: 145,
|
||||
CreatedAt: now,
|
||||
},
|
||||
},
|
||||
wantStatus: http.StatusCreated,
|
||||
},
|
||||
{
|
||||
name: "missing exercise_id",
|
||||
url: "/api/sessions/1/entries",
|
||||
body: `{"weight":100,"reps":10}`,
|
||||
mock: &mockSessionRepo{},
|
||||
wantStatus: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
name: "invalid JSON",
|
||||
url: "/api/sessions/1/entries",
|
||||
body: `{invalid}`,
|
||||
mock: &mockSessionRepo{},
|
||||
wantStatus: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
name: "no session ID",
|
||||
url: "/api/sessions//entries",
|
||||
body: `{"exercise_id":1}`,
|
||||
mock: &mockSessionRepo{},
|
||||
wantStatus: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
name: "repository error",
|
||||
url: "/api/sessions/1/entries",
|
||||
body: `{"exercise_id":1}`,
|
||||
mock: &mockSessionRepo{entryErr: errors.New("db error")},
|
||||
wantStatus: http.StatusInternalServerError,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
handler := NewSessionHandler(tt.mock)
|
||||
req := httptest.NewRequest(http.MethodPost, tt.url, bytes.NewBufferString(tt.body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
handler.ServeHTTP(rec, req)
|
||||
|
||||
if rec.Code != tt.wantStatus {
|
||||
t.Errorf("status = %d, want %d", rec.Code, tt.wantStatus)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSessionHandler_MethodNotAllowed(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
handler := NewSessionHandler(&mockSessionRepo{})
|
||||
req := httptest.NewRequest(http.MethodPatch, "/api/sessions/1", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
handler.ServeHTTP(rec, req)
|
||||
|
||||
if rec.Code != http.StatusMethodNotAllowed {
|
||||
t.Errorf("status = %d, want %d", rec.Code, http.StatusMethodNotAllowed)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSessionHandler_EntriesMethodNotAllowed(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
handler := NewSessionHandler(&mockSessionRepo{})
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/sessions/1/entries", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
handler.ServeHTTP(rec, req)
|
||||
|
||||
if rec.Code != http.StatusMethodNotAllowed {
|
||||
t.Errorf("status = %d, want %d", rec.Code, http.StatusMethodNotAllowed)
|
||||
}
|
||||
}
|
||||
|
||||
// ptr is a helper to create a pointer to a value
|
||||
func ptr[T any](v T) *T {
|
||||
return &v
|
||||
}
|
||||
Reference in New Issue
Block a user