internal/handler/workout_template_handler_test.go
Rama main · réplica de lectura del repositorio original.
package handler
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/johnzastrow/actalog/internal/service"
)
func TestWorkoutTemplateHandler_CreateTemplate_Unauthorized(t *testing.T) {
handler := &WorkoutTemplateHandler{}
req := createTestRequest(http.MethodPost, "/api/templates", `{"name": "Test Template"}`)
rr := httptest.NewRecorder()
handler.CreateTemplate(rr, req)
assertStatusCode(t, rr, http.StatusUnauthorized)
assertBodyContains(t, rr, "Unauthorized")
}
func TestWorkoutTemplateHandler_CreateTemplate_InvalidJSON(t *testing.T) {
handler := &WorkoutTemplateHandler{}
req := createAuthenticatedRequest(http.MethodPost, "/api/templates", "{bad json", 1, "test@example.com", "user")
rr := httptest.NewRecorder()
handler.CreateTemplate(rr, req)
assertStatusCode(t, rr, http.StatusBadRequest)
assertBodyContains(t, rr, "Invalid request body")
}
func TestWorkoutTemplateHandler_CreateTemplate_MissingName(t *testing.T) {
handler := &WorkoutTemplateHandler{}
req := createAuthenticatedRequest(http.MethodPost, "/api/templates", `{"description": "Test"}`, 1, "test@example.com", "user")
rr := httptest.NewRecorder()
handler.CreateTemplate(rr, req)
assertStatusCode(t, rr, http.StatusBadRequest)
assertBodyContains(t, rr, "Template name is required")
}
func TestWorkoutTemplateHandler_GetTemplate_InvalidID(t *testing.T) {
handler := &WorkoutTemplateHandler{}
// chi.URLParam returns empty string without router context
req := createTestRequest(http.MethodGet, "/api/templates/abc", "")
rr := httptest.NewRecorder()
handler.GetTemplate(rr, req)
assertStatusCode(t, rr, http.StatusBadRequest)
assertBodyContains(t, rr, "Invalid template ID")
}
func TestWorkoutTemplateHandler_ListMyTemplates_Unauthorized(t *testing.T) {
handler := &WorkoutTemplateHandler{}
req := createTestRequest(http.MethodGet, "/api/workouts/my-templates", "")
rr := httptest.NewRecorder()
handler.ListMyTemplates(rr, req)
assertStatusCode(t, rr, http.StatusUnauthorized)
assertBodyContains(t, rr, "Unauthorized")
}
func TestWorkoutTemplateHandler_UpdateTemplate_Unauthorized(t *testing.T) {
handler := &WorkoutTemplateHandler{}
req := createTestRequest(http.MethodPut, "/api/templates/1", `{"name": "Updated"}`)
rr := httptest.NewRecorder()
handler.UpdateTemplate(rr, req)
assertStatusCode(t, rr, http.StatusUnauthorized)
assertBodyContains(t, rr, "Unauthorized")
}
func TestWorkoutTemplateHandler_UpdateTemplate_InvalidID(t *testing.T) {
handler := &WorkoutTemplateHandler{}
// chi.URLParam returns empty string without router context
req := createAuthenticatedRequest(http.MethodPut, "/api/templates/abc", `{"name": "Updated"}`, 1, "test@example.com", "user")
rr := httptest.NewRecorder()
handler.UpdateTemplate(rr, req)
assertStatusCode(t, rr, http.StatusBadRequest)
assertBodyContains(t, rr, "Invalid template ID")
}
func TestWorkoutTemplateHandler_UpdateTemplate_InvalidJSON(t *testing.T) {
handler := &WorkoutTemplateHandler{}
req := createAuthenticatedRequest(http.MethodPut, "/api/templates/1", "{bad json", 1, "test@example.com", "user")
rr := httptest.NewRecorder()
handler.UpdateTemplate(rr, req)
// Without chi router context, URL param is empty -> Invalid template ID
assertStatusCode(t, rr, http.StatusBadRequest)
}
func TestWorkoutTemplateHandler_DeleteTemplate_Unauthorized(t *testing.T) {
handler := &WorkoutTemplateHandler{}
req := createTestRequest(http.MethodDelete, "/api/templates/1", "")
rr := httptest.NewRecorder()
handler.DeleteTemplate(rr, req)
assertStatusCode(t, rr, http.StatusUnauthorized)
assertBodyContains(t, rr, "Unauthorized")
}
func TestWorkoutTemplateHandler_DeleteTemplate_InvalidID(t *testing.T) {
handler := &WorkoutTemplateHandler{}
// chi.URLParam returns empty string without router context
req := createAuthenticatedRequest(http.MethodDelete, "/api/templates/abc", "", 1, "test@example.com", "user")
rr := httptest.NewRecorder()
handler.DeleteTemplate(rr, req)
assertStatusCode(t, rr, http.StatusBadRequest)
assertBodyContains(t, rr, "Invalid template ID")
}
// Test NewWorkoutTemplateHandler
func TestNewWorkoutTemplateHandler(t *testing.T) {
handler := NewWorkoutTemplateHandler(nil)
if handler == nil {
t.Error("NewWorkoutTemplateHandler should return a non-nil handler")
}
}
// Test UpdateTemplate with valid ID and JSON
func TestWorkoutTemplateHandler_UpdateTemplate_ValidIDInvalidJSON(t *testing.T) {
handler := &WorkoutTemplateHandler{}
req := createAuthenticatedRequest(http.MethodPut, "/api/templates/1", "{bad json", 1, "test@example.com", "user")
req = addChiURLParam(req, "id", "1")
rr := httptest.NewRecorder()
handler.UpdateTemplate(rr, req)
assertStatusCode(t, rr, http.StatusBadRequest)
assertBodyContains(t, rr, "Invalid request body")
}
func TestWorkoutTemplateHandler_UpdateTemplate_MissingName(t *testing.T) {
handler := &WorkoutTemplateHandler{}
req := createAuthenticatedRequest(http.MethodPut, "/api/templates/1", `{"description": "Test"}`, 1, "test@example.com", "user")
req = addChiURLParam(req, "id", "1")
rr := httptest.NewRecorder()
handler.UpdateTemplate(rr, req)
assertStatusCode(t, rr, http.StatusBadRequest)
assertBodyContains(t, rr, "Template name is required")
}
// Removed 9 panic-expectation tests:
// - TestWorkoutTemplateHandler_ListStandardTemplates_NilService
// - TestWorkoutTemplateHandler_ListMyTemplates_NilService
// - TestWorkoutTemplateHandler_GetTemplate_ValidIDNilService
// - TestWorkoutTemplateHandler_DeleteTemplate_ValidIDNilService
// - TestWorkoutTemplateHandler_CreateTemplate_ValidInputNilService
// - TestWorkoutTemplateHandler_UpdateTemplate_ValidInputNilService
// - TestWorkoutTemplateHandler_ListStandardTemplates_WithPagination (4 subtests)
// - TestWorkoutTemplateHandler_ListMyTemplates_WithPagination (3 subtests)
// - TestWorkoutTemplateHandler_GetTemplate_DifferentIDs (3 subtests)
// These tests verified nil pointer panics, not business logic.
// ===== Tests with real service for WorkoutTemplateHandler =====
func createWorkoutTemplateTestService() *service.WorkoutTemplateService {
mockWorkoutRepo := NewMockWorkoutRepository()
mockAuditLogRepo := NewMockAuditLogRepository()
return service.NewWorkoutTemplateService(mockWorkoutRepo, nil, nil, mockAuditLogRepo)
}
func TestWorkoutTemplateHandler_ListStandardTemplates_Success(t *testing.T) {
svc := createWorkoutTemplateTestService()
handler := NewWorkoutTemplateHandler(svc)
req := createAuthenticatedRequest(http.MethodGet, "/api/templates", "", 1, "test@example.com", "user")
rr := httptest.NewRecorder()
handler.ListStandardTemplates(rr, req)
assertStatusCode(t, rr, http.StatusOK)
assertContentType(t, rr, "application/json")
assertBodyContains(t, rr, "workouts")
}
func TestWorkoutTemplateHandler_ListStandardTemplates_WithPagination(t *testing.T) {
svc := createWorkoutTemplateTestService()
handler := NewWorkoutTemplateHandler(svc)
tests := []struct {
name string
url string
}{
{"with limit", "/api/templates?limit=10"},
{"with offset", "/api/templates?offset=5"},
{"with both", "/api/templates?limit=10&offset=5"},
{"with invalid limit", "/api/templates?limit=abc"},
{"with invalid offset", "/api/templates?offset=abc"},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
req := createAuthenticatedRequest(http.MethodGet, tc.url, "", 1, "test@example.com", "user")
rr := httptest.NewRecorder()
handler.ListStandardTemplates(rr, req)
assertStatusCode(t, rr, http.StatusOK)
assertBodyContains(t, rr, "workouts")
})
}
}
func TestWorkoutTemplateHandler_ListStandardTemplates_ServiceError(t *testing.T) {
mockWorkoutRepo := NewMockWorkoutRepository()
mockWorkoutRepo.SetError(ErrMockInternalError)
mockAuditLogRepo := NewMockAuditLogRepository()
svc := service.NewWorkoutTemplateService(mockWorkoutRepo, nil, nil, mockAuditLogRepo)
handler := NewWorkoutTemplateHandler(svc)
req := createAuthenticatedRequest(http.MethodGet, "/api/templates", "", 1, "test@example.com", "user")
rr := httptest.NewRecorder()
handler.ListStandardTemplates(rr, req)
assertStatusCode(t, rr, http.StatusInternalServerError)
}
func TestWorkoutTemplateHandler_ListMyTemplates_Success(t *testing.T) {
svc := createWorkoutTemplateTestService()
handler := NewWorkoutTemplateHandler(svc)
req := createAuthenticatedRequest(http.MethodGet, "/api/workouts/my-templates", "", 1, "test@example.com", "user")
rr := httptest.NewRecorder()
handler.ListMyTemplates(rr, req)
assertStatusCode(t, rr, http.StatusOK)
assertContentType(t, rr, "application/json")
assertBodyContains(t, rr, "workouts")
}
func TestWorkoutTemplateHandler_ListMyTemplates_WithPagination(t *testing.T) {
svc := createWorkoutTemplateTestService()
handler := NewWorkoutTemplateHandler(svc)
tests := []struct {
name string
url string
}{
{"with limit", "/api/workouts/my-templates?limit=10"},
{"with offset", "/api/workouts/my-templates?offset=5"},
{"with both", "/api/workouts/my-templates?limit=10&offset=5"},
{"with invalid limit", "/api/workouts/my-templates?limit=abc"},
{"with invalid offset", "/api/workouts/my-templates?offset=abc"},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
req := createAuthenticatedRequest(http.MethodGet, tc.url, "", 1, "test@example.com", "user")
rr := httptest.NewRecorder()
handler.ListMyTemplates(rr, req)
assertStatusCode(t, rr, http.StatusOK)
assertBodyContains(t, rr, "workouts")
})
}
}
func TestWorkoutTemplateHandler_ListMyTemplates_ServiceError(t *testing.T) {
mockWorkoutRepo := NewMockWorkoutRepository()
mockWorkoutRepo.SetError(ErrMockInternalError)
mockAuditLogRepo := NewMockAuditLogRepository()
svc := service.NewWorkoutTemplateService(mockWorkoutRepo, nil, nil, mockAuditLogRepo)
handler := NewWorkoutTemplateHandler(svc)
req := createAuthenticatedRequest(http.MethodGet, "/api/workouts/my-templates", "", 1, "test@example.com", "user")
rr := httptest.NewRecorder()
handler.ListMyTemplates(rr, req)
assertStatusCode(t, rr, http.StatusInternalServerError)
}
func TestWorkoutTemplateHandler_GetTemplate_Success(t *testing.T) {
svc := createWorkoutTemplateTestService()
handler := NewWorkoutTemplateHandler(svc)
// ID 3 exists in the mock - "My Custom Workout"
req := createAuthenticatedRequest(http.MethodGet, "/api/templates/3", "", 1, "test@example.com", "user")
req = addChiURLParam(req, "id", "3")
rr := httptest.NewRecorder()
handler.GetTemplate(rr, req)
assertStatusCode(t, rr, http.StatusOK)
assertContentType(t, rr, "application/json")
}
func TestWorkoutTemplateHandler_GetTemplate_NotFound(t *testing.T) {
svc := createWorkoutTemplateTestService()
handler := NewWorkoutTemplateHandler(svc)
// ID 999 doesn't exist
req := createAuthenticatedRequest(http.MethodGet, "/api/templates/999", "", 1, "test@example.com", "user")
req = addChiURLParam(req, "id", "999")
rr := httptest.NewRecorder()
handler.GetTemplate(rr, req)
// Returns 404 with error message when not found
assertStatusCode(t, rr, http.StatusNotFound)
assertBodyContains(t, rr, "not found")
}
func TestWorkoutTemplateHandler_CreateTemplate_Success(t *testing.T) {
svc := createWorkoutTemplateTestService()
handler := NewWorkoutTemplateHandler(svc)
body := `{"name": "New Template", "intro_warmup": "Warmup instructions", "notes": "Test notes"}`
req := createAuthenticatedRequest(http.MethodPost, "/api/templates", body, 1, "test@example.com", "user")
rr := httptest.NewRecorder()
handler.CreateTemplate(rr, req)
assertStatusCode(t, rr, http.StatusCreated)
assertContentType(t, rr, "application/json")
}
func TestWorkoutTemplateHandler_CreateTemplate_ServiceError(t *testing.T) {
mockWorkoutRepo := NewMockWorkoutRepository()
mockWorkoutRepo.SetError(ErrMockInternalError)
mockAuditLogRepo := NewMockAuditLogRepository()
svc := service.NewWorkoutTemplateService(mockWorkoutRepo, nil, nil, mockAuditLogRepo)
handler := NewWorkoutTemplateHandler(svc)
body := `{"name": "New Template", "intro_warmup": "Warmup instructions", "notes": "Test notes"}`
req := createAuthenticatedRequest(http.MethodPost, "/api/templates", body, 1, "test@example.com", "user")
rr := httptest.NewRecorder()
handler.CreateTemplate(rr, req)
assertStatusCode(t, rr, http.StatusInternalServerError)
}