internal/handler/performance_handler_test.go
Rama main · réplica de lectura del repositorio original.
package handler
import (
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
"github.com/johnzastrow/actalog/internal/domain"
"github.com/johnzastrow/actalog/internal/repository"
)
// createTestPerformanceHandler creates a performance handler with test database and sample data
func createTestPerformanceHandler(t *testing.T) (*PerformanceHandler, func()) {
db, cleanup, err := repository.SetupTestDB()
if err != nil {
t.Fatalf("Failed to setup test database: %v", err)
}
// Create test user
userRepo := repository.NewSQLiteUserRepository(db)
now := time.Now()
testUser := &domain.User{
Email: "perftest@example.com",
PasswordHash: "hashedpassword",
Role: "athlete",
CreatedAt: now,
UpdatedAt: now,
}
if err := userRepo.Create(testUser); err != nil {
cleanup()
t.Fatalf("Failed to create test user: %v", err)
}
// Create repositories
movementRepo := repository.NewMovementRepository(db)
wodRepo := repository.NewWODRepository(db)
userWorkoutMovementRepo := repository.NewUserWorkoutMovementRepository(db)
userWorkoutWODRepo := repository.NewUserWorkoutWODRepository(db)
// Create test movement
movement := &domain.Movement{
Name: "Back Squat",
Type: "weightlifting",
Description: "Barbell back squat",
CreatedAt: now,
UpdatedAt: now,
}
if err := movementRepo.Create(movement); err != nil {
cleanup()
t.Fatalf("Failed to create movement: %v", err)
}
// Create test WOD
createdBy := int64(1)
wod := &domain.WOD{
Name: "Fran",
Type: "For Time",
ScoreType: "time",
Description: "21-15-9 Thrusters and Pull-ups",
CreatedBy: &createdBy,
CreatedAt: now,
UpdatedAt: now,
}
if err := wodRepo.Create(wod); err != nil {
cleanup()
t.Fatalf("Failed to create WOD: %v", err)
}
// Create a workout
_, err = db.Exec(`
INSERT INTO workouts (name, notes, created_by, created_at, updated_at)
VALUES ('Test Workout', 'Test notes', 1, ?, ?)
`, now, now)
if err != nil {
cleanup()
t.Fatalf("Failed to create workout: %v", err)
}
// Create a user workout
_, err = db.Exec(`
INSERT INTO user_workouts (user_id, workout_id, workout_date, created_at, updated_at)
VALUES (1, 1, ?, ?, ?)
`, now.Format("2006-01-02"), now, now)
if err != nil {
cleanup()
t.Fatalf("Failed to create user workout: %v", err)
}
// Create workout movement performance
_, err = db.Exec(`
INSERT INTO workout_movements (workout_id, movement_id, sets, reps, weight, is_pr, order_index, created_at, updated_at)
VALUES (1, 1, 5, 5, 315.0, 1, 0, ?, ?)
`, now, now)
if err != nil {
cleanup()
t.Fatalf("Failed to create workout movement: %v", err)
}
// Create workout WOD performance
_, err = db.Exec(`
INSERT INTO workout_wods (workout_id, wod_id, score_value, division, is_pr, order_index, created_at, updated_at)
VALUES (1, 1, '5:30', 'rx', 1, 0, ?, ?)
`, now, now)
if err != nil {
cleanup()
t.Fatalf("Failed to create workout WOD: %v", err)
}
handler := NewPerformanceHandler(movementRepo, wodRepo, userWorkoutMovementRepo, userWorkoutWODRepo, createTestLogger())
return handler, cleanup
}
func TestPerformanceHandler_UnifiedSearch_Unauthorized(t *testing.T) {
handler := &PerformanceHandler{}
req := createTestRequest(http.MethodGet, "/api/performance/search?q=squat", "")
rr := httptest.NewRecorder()
handler.UnifiedSearch(rr, req)
assertStatusCode(t, rr, http.StatusUnauthorized)
assertBodyContains(t, rr, "Unauthorized")
}
func TestPerformanceHandler_UnifiedSearch_MissingQuery(t *testing.T) {
handler := &PerformanceHandler{}
req := createAuthenticatedRequest(http.MethodGet, "/api/performance/search", "", 1, "test@example.com", "user")
rr := httptest.NewRecorder()
handler.UnifiedSearch(rr, req)
assertStatusCode(t, rr, http.StatusBadRequest)
assertBodyContains(t, rr, "Search query is required")
}
func TestPerformanceHandler_GetMovementPerformance_Unauthorized(t *testing.T) {
handler := &PerformanceHandler{}
req := createTestRequest(http.MethodGet, "/api/performance/movements/1", "")
rr := httptest.NewRecorder()
handler.GetMovementPerformance(rr, req)
assertStatusCode(t, rr, http.StatusUnauthorized)
assertBodyContains(t, rr, "Unauthorized")
}
func TestPerformanceHandler_GetMovementPerformance_InvalidID(t *testing.T) {
handler := &PerformanceHandler{}
// chi.URLParam returns empty string without router context
req := createAuthenticatedRequest(http.MethodGet, "/api/performance/movements/abc", "", 1, "test@example.com", "user")
rr := httptest.NewRecorder()
handler.GetMovementPerformance(rr, req)
assertStatusCode(t, rr, http.StatusBadRequest)
assertBodyContains(t, rr, "Invalid movement ID")
}
func TestPerformanceHandler_GetWODPerformance_Unauthorized(t *testing.T) {
handler := &PerformanceHandler{}
req := createTestRequest(http.MethodGet, "/api/performance/wods/1", "")
rr := httptest.NewRecorder()
handler.GetWODPerformance(rr, req)
assertStatusCode(t, rr, http.StatusUnauthorized)
assertBodyContains(t, rr, "Unauthorized")
}
func TestPerformanceHandler_GetWODPerformance_InvalidID(t *testing.T) {
handler := &PerformanceHandler{}
// chi.URLParam returns empty string without router context
req := createAuthenticatedRequest(http.MethodGet, "/api/performance/wods/abc", "", 1, "test@example.com", "user")
rr := httptest.NewRecorder()
handler.GetWODPerformance(rr, req)
assertStatusCode(t, rr, http.StatusBadRequest)
assertBodyContains(t, rr, "Invalid WOD ID")
}
func TestNewPerformanceHandler(t *testing.T) {
handler := NewPerformanceHandler(nil, nil, nil, nil, nil)
if handler == nil {
t.Error("NewPerformanceHandler should return a non-nil handler")
}
}
// Success path tests with real database
func TestPerformanceHandler_UnifiedSearch_Success(t *testing.T) {
handler, cleanup := createTestPerformanceHandler(t)
defer cleanup()
req := createAuthenticatedRequest(http.MethodGet, "/api/performance/search?q=squat", "", 1, "perftest@example.com", "user")
rr := httptest.NewRecorder()
handler.UnifiedSearch(rr, req)
assertStatusCode(t, rr, http.StatusOK)
assertContentType(t, rr, "application/json")
body := rr.Body.String()
if !strings.Contains(body, "results") {
t.Error("Response should contain 'results' field")
}
if !strings.Contains(body, "Back Squat") {
t.Error("Response should contain the movement 'Back Squat'")
}
}
func TestPerformanceHandler_UnifiedSearch_WithLimit(t *testing.T) {
handler, cleanup := createTestPerformanceHandler(t)
defer cleanup()
req := createAuthenticatedRequest(http.MethodGet, "/api/performance/search?q=squat&limit=5", "", 1, "perftest@example.com", "user")
rr := httptest.NewRecorder()
handler.UnifiedSearch(rr, req)
assertStatusCode(t, rr, http.StatusOK)
}
func TestPerformanceHandler_UnifiedSearch_InvalidLimit(t *testing.T) {
handler, cleanup := createTestPerformanceHandler(t)
defer cleanup()
// Invalid limit should use default
req := createAuthenticatedRequest(http.MethodGet, "/api/performance/search?q=squat&limit=abc", "", 1, "perftest@example.com", "user")
rr := httptest.NewRecorder()
handler.UnifiedSearch(rr, req)
assertStatusCode(t, rr, http.StatusOK)
}
func TestPerformanceHandler_UnifiedSearch_NoResults(t *testing.T) {
handler, cleanup := createTestPerformanceHandler(t)
defer cleanup()
req := createAuthenticatedRequest(http.MethodGet, "/api/performance/search?q=nonexistent", "", 1, "perftest@example.com", "user")
rr := httptest.NewRecorder()
handler.UnifiedSearch(rr, req)
assertStatusCode(t, rr, http.StatusOK)
// Should return empty results
body := rr.Body.String()
if !strings.Contains(body, "results") {
t.Error("Response should contain 'results' field")
}
}
func TestPerformanceHandler_GetMovementPerformance_Success(t *testing.T) {
handler, cleanup := createTestPerformanceHandler(t)
defer cleanup()
req := createAuthenticatedRequest(http.MethodGet, "/api/performance/movements/1", "", 1, "perftest@example.com", "user")
req = addChiURLParam(req, "id", "1")
rr := httptest.NewRecorder()
handler.GetMovementPerformance(rr, req)
assertStatusCode(t, rr, http.StatusOK)
assertContentType(t, rr, "application/json")
body := rr.Body.String()
if !strings.Contains(body, "performances") {
t.Error("Response should contain 'performances' field")
}
if !strings.Contains(body, "count") {
t.Error("Response should contain 'count' field")
}
}
func TestPerformanceHandler_GetMovementPerformance_EmptyResult(t *testing.T) {
handler, cleanup := createTestPerformanceHandler(t)
defer cleanup()
// Non-existent movement returns empty performances (not 404)
req := createAuthenticatedRequest(http.MethodGet, "/api/performance/movements/9999", "", 1, "perftest@example.com", "user")
req = addChiURLParam(req, "id", "9999")
rr := httptest.NewRecorder()
handler.GetMovementPerformance(rr, req)
assertStatusCode(t, rr, http.StatusOK)
body := rr.Body.String()
if !strings.Contains(body, `"count":0`) {
t.Error("Response should contain count of 0 for non-existent movement")
}
}
func TestPerformanceHandler_GetWODPerformance_Success(t *testing.T) {
handler, cleanup := createTestPerformanceHandler(t)
defer cleanup()
req := createAuthenticatedRequest(http.MethodGet, "/api/performance/wods/1", "", 1, "perftest@example.com", "user")
req = addChiURLParam(req, "id", "1")
rr := httptest.NewRecorder()
handler.GetWODPerformance(rr, req)
assertStatusCode(t, rr, http.StatusOK)
assertContentType(t, rr, "application/json")
body := rr.Body.String()
if !strings.Contains(body, "count") {
t.Error("Response should contain 'count' field")
}
}
func TestPerformanceHandler_GetWODPerformance_EmptyResult(t *testing.T) {
handler, cleanup := createTestPerformanceHandler(t)
defer cleanup()
// Non-existent WOD returns empty performances (not 404)
req := createAuthenticatedRequest(http.MethodGet, "/api/performance/wods/9999", "", 1, "perftest@example.com", "user")
req = addChiURLParam(req, "id", "9999")
rr := httptest.NewRecorder()
handler.GetWODPerformance(rr, req)
assertStatusCode(t, rr, http.StatusOK)
body := rr.Body.String()
if !strings.Contains(body, `"count":0`) {
t.Error("Response should contain count of 0 for non-existent WOD")
}
}