Ctrl K
ComunidadGuías…

internal/handler/workout_wod_handler_test.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package handler

import (
	"net/http"
	"net/http/httptest"
	"testing"
)

func TestWorkoutWODHandler_AddWODToWorkout_Unauthorized(t *testing.T) {
	handler := &WorkoutWODHandler{}

	// Request without user context
	req := createTestRequest(http.MethodPost, "/api/workouts/1/wods", `{"wod_id": 1}`)
	rr := httptest.NewRecorder()

	handler.AddWODToWorkout(rr, req)

	assertStatusCode(t, rr, http.StatusUnauthorized)
	assertBodyContains(t, rr, "Unauthorized")
}

func TestWorkoutWODHandler_AddWODToWorkout_InvalidWorkoutID(t *testing.T) {
	handler := &WorkoutWODHandler{}

	// Without chi router context, URLParam returns empty string
	req := createAuthenticatedRequest(http.MethodPost, "/api/workouts/abc/wods", `{"wod_id": 1}`, 1, "test@example.com", "user")
	rr := httptest.NewRecorder()

	handler.AddWODToWorkout(rr, req)

	assertStatusCode(t, rr, http.StatusBadRequest)
	assertBodyContains(t, rr, "Invalid workout ID")
}

func TestWorkoutWODHandler_AddWODToWorkout_InvalidJSON(t *testing.T) {
	handler := &WorkoutWODHandler{}

	req := createAuthenticatedRequest(http.MethodPost, "/api/workouts/1/wods", "{invalid json", 1, "test@example.com", "user")
	// Without chi router context for workout_id
	rr := httptest.NewRecorder()

	handler.AddWODToWorkout(rr, req)

	// Without chi context, it will fail on URL param parsing first
	assertStatusCode(t, rr, http.StatusBadRequest)
}

func TestWorkoutWODHandler_AddWODToWorkout_MissingWODID(t *testing.T) {
	handler := &WorkoutWODHandler{}

	// Valid JSON but missing wod_id - however, without chi context this will fail on URL param
	req := createAuthenticatedRequest(http.MethodPost, "/api/workouts/1/wods", `{"order_index": 1}`, 1, "test@example.com", "user")
	rr := httptest.NewRecorder()

	handler.AddWODToWorkout(rr, req)

	assertStatusCode(t, rr, http.StatusBadRequest)
}

func TestWorkoutWODHandler_RemoveWODFromWorkout_Unauthorized(t *testing.T) {
	handler := &WorkoutWODHandler{}

	req := createTestRequest(http.MethodDelete, "/api/workout-wods/1", "")
	rr := httptest.NewRecorder()

	handler.RemoveWODFromWorkout(rr, req)

	assertStatusCode(t, rr, http.StatusUnauthorized)
	assertBodyContains(t, rr, "Unauthorized")
}

func TestWorkoutWODHandler_RemoveWODFromWorkout_InvalidID(t *testing.T) {
	handler := &WorkoutWODHandler{}

	req := createAuthenticatedRequest(http.MethodDelete, "/api/workout-wods/abc", "", 1, "test@example.com", "user")
	rr := httptest.NewRecorder()

	handler.RemoveWODFromWorkout(rr, req)

	assertStatusCode(t, rr, http.StatusBadRequest)
	assertBodyContains(t, rr, "Invalid workout WOD ID")
}

func TestWorkoutWODHandler_UpdateWorkoutWOD_Unauthorized(t *testing.T) {
	handler := &WorkoutWODHandler{}

	req := createTestRequest(http.MethodPut, "/api/workout-wods/1", `{"score_value": "10:30"}`)
	rr := httptest.NewRecorder()

	handler.UpdateWorkoutWOD(rr, req)

	assertStatusCode(t, rr, http.StatusUnauthorized)
	assertBodyContains(t, rr, "Unauthorized")
}

func TestWorkoutWODHandler_UpdateWorkoutWOD_InvalidID(t *testing.T) {
	handler := &WorkoutWODHandler{}

	req := createAuthenticatedRequest(http.MethodPut, "/api/workout-wods/abc", `{"score_value": "10:30"}`, 1, "test@example.com", "user")
	rr := httptest.NewRecorder()

	handler.UpdateWorkoutWOD(rr, req)

	assertStatusCode(t, rr, http.StatusBadRequest)
	assertBodyContains(t, rr, "Invalid workout WOD ID")
}

func TestWorkoutWODHandler_UpdateWorkoutWOD_InvalidJSON(t *testing.T) {
	handler := &WorkoutWODHandler{}

	req := createAuthenticatedRequest(http.MethodPut, "/api/workout-wods/1", "{invalid", 1, "test@example.com", "user")
	rr := httptest.NewRecorder()

	handler.UpdateWorkoutWOD(rr, req)

	// Without chi context, fails on URL param first
	assertStatusCode(t, rr, http.StatusBadRequest)
}

func TestWorkoutWODHandler_ToggleWODPR_Unauthorized(t *testing.T) {
	handler := &WorkoutWODHandler{}

	req := createTestRequest(http.MethodPost, "/api/workout-wods/1/toggle-pr", "")
	rr := httptest.NewRecorder()

	handler.ToggleWODPR(rr, req)

	assertStatusCode(t, rr, http.StatusUnauthorized)
	assertBodyContains(t, rr, "Unauthorized")
}

func TestWorkoutWODHandler_ToggleWODPR_InvalidID(t *testing.T) {
	handler := &WorkoutWODHandler{}

	req := createAuthenticatedRequest(http.MethodPost, "/api/workout-wods/abc/toggle-pr", "", 1, "test@example.com", "user")
	rr := httptest.NewRecorder()

	handler.ToggleWODPR(rr, req)

	assertStatusCode(t, rr, http.StatusBadRequest)
	assertBodyContains(t, rr, "Invalid workout WOD ID")
}

func TestWorkoutWODHandler_ListWODsForWorkout_InvalidID(t *testing.T) {
	handler := &WorkoutWODHandler{}

	req := createTestRequest(http.MethodGet, "/api/workouts/abc/wods", "")
	rr := httptest.NewRecorder()

	handler.ListWODsForWorkout(rr, req)

	assertStatusCode(t, rr, http.StatusBadRequest)
	assertBodyContains(t, rr, "Invalid workout ID")
}

func TestNewWorkoutWODHandler(t *testing.T) {
	// Test constructor with nil service (basic construction test)
	handler := NewWorkoutWODHandler(nil)

	if handler == nil {
		t.Error("NewWorkoutWODHandler() should not return nil")
	}

	if handler.workoutWODService != nil {
		t.Error("workoutWODService should be nil when passed nil")
	}
}

func TestAddWODToWorkoutRequest(t *testing.T) {
	// Test struct initialization
	req := AddWODToWorkoutRequest{
		WODID:      123,
		OrderIndex: 1,
		Division:   nil,
	}

	if req.WODID != 123 {
		t.Errorf("WODID = %d, want 123", req.WODID)
	}

	if req.OrderIndex != 1 {
		t.Errorf("OrderIndex = %d, want 1", req.OrderIndex)
	}

	if req.Division != nil {
		t.Error("Division should be nil")
	}

	// With division
	div := "Rx"
	req2 := AddWODToWorkoutRequest{
		WODID:      456,
		OrderIndex: 2,
		Division:   &div,
	}

	if req2.Division == nil || *req2.Division != "Rx" {
		t.Error("Division should be 'Rx'")
	}
}

func TestUpdateWorkoutWODRequest(t *testing.T) {
	// Test struct initialization
	req := UpdateWorkoutWODRequest{
		ScoreValue: nil,
		Division:   nil,
	}

	if req.ScoreValue != nil {
		t.Error("ScoreValue should be nil")
	}

	if req.Division != nil {
		t.Error("Division should be nil")
	}

	// With values
	scoreVal := "10:30"
	div := "Scaled"
	req2 := UpdateWorkoutWODRequest{
		ScoreValue: &scoreVal,
		Division:   &div,
	}

	if req2.ScoreValue == nil || *req2.ScoreValue != "10:30" {
		t.Error("ScoreValue should be '10:30'")
	}

	if req2.Division == nil || *req2.Division != "Scaled" {
		t.Error("Division should be 'Scaled'")
	}
}

// Tests with valid IDs using chi URL params

func TestWorkoutWODHandler_AddWODToWorkout_ValidIDInvalidJSON(t *testing.T) {
	handler := &WorkoutWODHandler{}

	req := createAuthenticatedRequest(http.MethodPost, "/api/workouts/1/wods", "{invalid json", 1, "test@example.com", "user")
	req = addChiURLParam(req, "workout_id", "1")
	rr := httptest.NewRecorder()

	handler.AddWODToWorkout(rr, req)

	assertStatusCode(t, rr, http.StatusBadRequest)
	assertBodyContains(t, rr, "Invalid request body")
}

func TestWorkoutWODHandler_AddWODToWorkout_MissingWODIDWithValidWorkoutID(t *testing.T) {
	handler := &WorkoutWODHandler{}

	req := createAuthenticatedRequest(http.MethodPost, "/api/workouts/1/wods", `{"order_index": 1}`, 1, "test@example.com", "user")
	req = addChiURLParam(req, "workout_id", "1")
	rr := httptest.NewRecorder()

	handler.AddWODToWorkout(rr, req)

	assertStatusCode(t, rr, http.StatusBadRequest)
	assertBodyContains(t, rr, "WOD ID is required")
}

func TestWorkoutWODHandler_UpdateWorkoutWOD_ValidIDInvalidJSON(t *testing.T) {
	handler := &WorkoutWODHandler{}

	req := createAuthenticatedRequest(http.MethodPut, "/api/workout-wods/1", "{invalid json", 1, "test@example.com", "user")
	req = addChiURLParam(req, "workout_wod_id", "1")
	rr := httptest.NewRecorder()

	handler.UpdateWorkoutWOD(rr, req)

	assertStatusCode(t, rr, http.StatusBadRequest)
	assertBodyContains(t, rr, "Invalid request body")
}

// Removed 11 panic-expectation tests:
// - TestWorkoutWODHandler_AddWODToWorkout_ValidInputNilService
// - TestWorkoutWODHandler_AddWODToWorkout_WithDivision
// - TestWorkoutWODHandler_RemoveWODFromWorkout_ValidIDNilService
// - TestWorkoutWODHandler_UpdateWorkoutWOD_ValidInputNilService
// - TestWorkoutWODHandler_ToggleWODPR_ValidIDNilService
// - TestWorkoutWODHandler_ListWODsForWorkout_ValidIDNilService
// - TestWorkoutWODHandler_AddWODToWorkout_DifferentIDs (3 subtests)
// - TestWorkoutWODHandler_RemoveWODFromWorkout_DifferentIDs (3 subtests)
// - TestWorkoutWODHandler_UpdateWorkoutWOD_DifferentIDs (3 subtests)
// - TestWorkoutWODHandler_ToggleWODPR_DifferentIDs (3 subtests)
// - TestWorkoutWODHandler_ListWODsForWorkout_DifferentIDs (3 subtests)
// These tests verified nil pointer panics, not business logic.

// =============================================================================
// Tests with Real Service (exercises code paths)
// =============================================================================

func TestWorkoutWODHandler_AddWODToWorkout_WithService(t *testing.T) {
	svc := createTestWorkoutWODService()
	handler := NewWorkoutWODHandler(svc)

	req := createAuthenticatedRequest(http.MethodPost, "/api/workouts/1/wods", `{"wod_id": 1, "order_index": 0}`, 1, "test@example.com", "user")
	req = addChiURLParam(req, "workout_id", "1")
	rr := httptest.NewRecorder()

	handler.AddWODToWorkout(rr, req)

	// This exercises the service call path - result depends on mock state
	// May return 201 (success), 403 (auth failure), or 500 (other error)
	if rr.Code != http.StatusCreated && rr.Code != http.StatusForbidden && rr.Code != http.StatusInternalServerError {
		t.Errorf("Expected StatusCreated, StatusForbidden, or StatusInternalServerError, got %d", rr.Code)
	}
}

func TestWorkoutWODHandler_AddWODToWorkout_WithDivision(t *testing.T) {
	svc := createTestWorkoutWODService()
	handler := NewWorkoutWODHandler(svc)

	req := createAuthenticatedRequest(http.MethodPost, "/api/workouts/1/wods", `{"wod_id": 1, "order_index": 0, "division": "Rx"}`, 1, "test@example.com", "user")
	req = addChiURLParam(req, "workout_id", "1")
	rr := httptest.NewRecorder()

	handler.AddWODToWorkout(rr, req)

	// Exercises path with division field - result depends on mock state
	if rr.Code != http.StatusCreated && rr.Code != http.StatusForbidden && rr.Code != http.StatusInternalServerError {
		t.Errorf("Expected StatusCreated, StatusForbidden, or StatusInternalServerError, got %d", rr.Code)
	}
}

func TestWorkoutWODHandler_AddWODToWorkout_WorkoutNotFound(t *testing.T) {
	svc := createTestWorkoutWODService()
	handler := NewWorkoutWODHandler(svc)

	req := createAuthenticatedRequest(http.MethodPost, "/api/workouts/999/wods", `{"wod_id": 1, "order_index": 0}`, 1, "test@example.com", "user")
	req = addChiURLParam(req, "workout_id", "999")
	rr := httptest.NewRecorder()

	handler.AddWODToWorkout(rr, req)

	// Should fail since workout doesn't exist
	if rr.Code != http.StatusNotFound && rr.Code != http.StatusInternalServerError {
		t.Errorf("Expected StatusNotFound or StatusInternalServerError, got %d", rr.Code)
	}
}

func TestWorkoutWODHandler_AddWODToWorkout_WODNotFound(t *testing.T) {
	svc := createTestWorkoutWODService()
	handler := NewWorkoutWODHandler(svc)

	req := createAuthenticatedRequest(http.MethodPost, "/api/workouts/1/wods", `{"wod_id": 999, "order_index": 0}`, 1, "test@example.com", "user")
	req = addChiURLParam(req, "workout_id", "1")
	rr := httptest.NewRecorder()

	handler.AddWODToWorkout(rr, req)

	// Should fail since WOD doesn't exist - service returns error wrapped, so handler returns 500 or 403
	if rr.Code != http.StatusNotFound && rr.Code != http.StatusInternalServerError && rr.Code != http.StatusForbidden {
		t.Errorf("Expected StatusNotFound, StatusInternalServerError, or StatusForbidden, got %d", rr.Code)
	}
}

func TestWorkoutWODHandler_ListWODsForWorkout_Success(t *testing.T) {
	svc := createTestWorkoutWODService()
	handler := NewWorkoutWODHandler(svc)

	req := createTestRequest(http.MethodGet, "/api/workouts/1/wods", "")
	req = addChiURLParam(req, "workout_id", "1")
	rr := httptest.NewRecorder()

	handler.ListWODsForWorkout(rr, req)

	assertStatusCode(t, rr, http.StatusOK)
	assertBodyContains(t, rr, "wods")
}

func TestWorkoutWODHandler_RemoveWODFromWorkout_NotFound(t *testing.T) {
	svc := createTestWorkoutWODService()
	handler := NewWorkoutWODHandler(svc)

	req := createAuthenticatedRequest(http.MethodDelete, "/api/workout-wods/999", "", 1, "test@example.com", "user")
	req = addChiURLParam(req, "workout_wod_id", "999")
	rr := httptest.NewRecorder()

	handler.RemoveWODFromWorkout(rr, req)

	// Service wraps "not found" errors, so handler returns 500
	assertStatusCode(t, rr, http.StatusInternalServerError)
	assertBodyContains(t, rr, "not found")
}

func TestWorkoutWODHandler_UpdateWorkoutWOD_NotFound(t *testing.T) {
	svc := createTestWorkoutWODService()
	handler := NewWorkoutWODHandler(svc)

	req := createAuthenticatedRequest(http.MethodPut, "/api/workout-wods/999", `{"score_value": "10:30"}`, 1, "test@example.com", "user")
	req = addChiURLParam(req, "workout_wod_id", "999")
	rr := httptest.NewRecorder()

	handler.UpdateWorkoutWOD(rr, req)

	// Service wraps "not found" errors, so handler returns 500
	assertStatusCode(t, rr, http.StatusInternalServerError)
	assertBodyContains(t, rr, "not found")
}

func TestWorkoutWODHandler_ToggleWODPR_NotFound(t *testing.T) {
	svc := createTestWorkoutWODService()
	handler := NewWorkoutWODHandler(svc)

	req := createAuthenticatedRequest(http.MethodPost, "/api/workout-wods/999/toggle-pr", "", 1, "test@example.com", "user")
	req = addChiURLParam(req, "workout_wod_id", "999")
	rr := httptest.NewRecorder()

	handler.ToggleWODPR(rr, req)

	// Service wraps "not found" errors, so handler returns 500
	assertStatusCode(t, rr, http.StatusInternalServerError)
	assertBodyContains(t, rr, "not found")
}