Ctrl K
ComunidadGuías…

internal/handler/export_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"
	"strings"
	"testing"

	"github.com/johnzastrow/actalog/internal/service"
)

func createTestExportService() *service.ExportService {
	mockWODRepo := NewMockWODRepository()
	mockMovementRepo := NewMockMovementRepository()
	mockUserRepo := NewMockUserRepository()
	mockUserWorkoutRepo := NewMockUserWorkoutRepository()
	return service.NewExportService(mockWODRepo, mockMovementRepo, mockUserRepo, mockUserWorkoutRepo)
}

func TestExportHandler_ExportWODs_Unauthorized(t *testing.T) {
	handler := &ExportHandler{}

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

	handler.ExportWODs(rr, req)

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

func TestExportHandler_ExportMovements_Unauthorized(t *testing.T) {
	handler := &ExportHandler{}

	req := createTestRequest(http.MethodGet, "/api/export/movements", "")
	rr := httptest.NewRecorder()

	handler.ExportMovements(rr, req)

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

func TestExportHandler_ExportUserWorkouts_Unauthorized(t *testing.T) {
	handler := &ExportHandler{}

	req := createTestRequest(http.MethodGet, "/api/export/user-workouts", "")
	rr := httptest.NewRecorder()

	handler.ExportUserWorkouts(rr, req)

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

func TestExportHandler_ExportUserWorkouts_InvalidStartDate(t *testing.T) {
	handler := &ExportHandler{}

	req := createAuthenticatedRequest(http.MethodGet, "/api/export/user-workouts?start_date=invalid&end_date=2024-12-31", "", 1, "test@example.com", "user")
	rr := httptest.NewRecorder()

	handler.ExportUserWorkouts(rr, req)

	assertStatusCode(t, rr, http.StatusBadRequest)
	assertBodyContains(t, rr, "Invalid start_date format")
}

func TestExportHandler_ExportUserWorkouts_InvalidEndDate(t *testing.T) {
	handler := &ExportHandler{}

	req := createAuthenticatedRequest(http.MethodGet, "/api/export/user-workouts?start_date=2024-01-01&end_date=invalid", "", 1, "test@example.com", "user")
	rr := httptest.NewRecorder()

	handler.ExportUserWorkouts(rr, req)

	assertStatusCode(t, rr, http.StatusBadRequest)
	assertBodyContains(t, rr, "Invalid end_date format")
}

func TestExportHandler_ExportUserWorkouts_MissingOneDateParam(t *testing.T) {
	handler := &ExportHandler{}

	tests := []struct {
		name      string
		query     string
		wantError string
	}{
		{
			name:      "only start_date",
			query:     "?start_date=2024-01-01",
			wantError: "Both start_date and end_date must be provided together",
		},
		{
			name:      "only end_date",
			query:     "?end_date=2024-12-31",
			wantError: "Both start_date and end_date must be provided together",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			req := createAuthenticatedRequest(http.MethodGet, "/api/export/user-workouts"+tt.query, "", 1, "test@example.com", "user")
			rr := httptest.NewRecorder()

			handler.ExportUserWorkouts(rr, req)

			assertStatusCode(t, rr, http.StatusBadRequest)
			assertBodyContains(t, rr, tt.wantError)
		})
	}
}

func TestParseBoolParam(t *testing.T) {
	tests := []struct {
		name         string
		value        string
		defaultValue bool
		want         bool
	}{
		{name: "empty with true default", value: "", defaultValue: true, want: true},
		{name: "empty with false default", value: "", defaultValue: false, want: false},
		{name: "true", value: "true", defaultValue: false, want: true},
		{name: "false", value: "false", defaultValue: true, want: false},
		{name: "1", value: "1", defaultValue: false, want: true},
		{name: "0", value: "0", defaultValue: true, want: false},
		{name: "invalid", value: "invalid", defaultValue: true, want: true},
		{name: "invalid with false default", value: "invalid", defaultValue: false, want: false},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := parseBoolParam(tt.value, tt.defaultValue)
			if got != tt.want {
				t.Errorf("parseBoolParam(%q, %v) = %v, want %v", tt.value, tt.defaultValue, got, tt.want)
			}
		})
	}
}

func TestNewExportHandler(t *testing.T) {
	handler := NewExportHandler(nil)
	if handler == nil {
		t.Error("NewExportHandler should return a non-nil handler")
	}
}

func TestExportHandler_ExportWODs_MissingRole(t *testing.T) {
	handler := &ExportHandler{}

	// Create request with userID but no role context
	req := createUserIDOnlyRequest(http.MethodGet, "/api/export/wods", "", 1)
	rr := httptest.NewRecorder()

	handler.ExportWODs(rr, req)

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

func TestExportHandler_ExportMovements_MissingRole(t *testing.T) {
	handler := &ExportHandler{}

	// Create request with userID but no role context
	req := createUserIDOnlyRequest(http.MethodGet, "/api/export/movements", "", 1)
	rr := httptest.NewRecorder()

	handler.ExportMovements(rr, req)

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

// Removed 5 panic-expectation tests:
// - TestExportHandler_ExportWODs_WithQueryParams (6 subtests)
// - TestExportHandler_ExportWODs_AsAdmin
// - TestExportHandler_ExportMovements_WithQueryParams (6 subtests)
// - TestExportHandler_ExportMovements_AsAdmin
// - TestExportHandler_ExportUserWorkouts_WithFormatParams (5 subtests)
// These tests verified nil pointer panics, not business logic.

// ===== Success path tests with real service =====

func TestExportHandler_ExportWODs_Success_CSV(t *testing.T) {
	svc := createTestExportService()
	handler := NewExportHandler(svc)

	req := createAuthenticatedRequest(http.MethodGet, "/api/export/wods", "", 1, "test@example.com", "user")
	rr := httptest.NewRecorder()

	handler.ExportWODs(rr, req)

	assertStatusCode(t, rr, http.StatusOK)
	// Check for CSV content type
	contentType := rr.Header().Get("Content-Type")
	if !strings.Contains(contentType, "text/csv") {
		t.Errorf("Expected Content-Type text/csv, got %s", contentType)
	}
	// Check for Content-Disposition header
	disposition := rr.Header().Get("Content-Disposition")
	if !strings.Contains(disposition, "wods_export.csv") {
		t.Errorf("Expected Content-Disposition to contain wods_export.csv, got %s", disposition)
	}
}

func TestExportHandler_ExportWODs_Success_JSON(t *testing.T) {
	svc := createTestExportService()
	handler := NewExportHandler(svc)

	req := createAuthenticatedRequest(http.MethodGet, "/api/export/wods?format=json", "", 1, "test@example.com", "user")
	rr := httptest.NewRecorder()

	handler.ExportWODs(rr, req)

	assertStatusCode(t, rr, http.StatusOK)
	// Check for JSON content type
	contentType := rr.Header().Get("Content-Type")
	if !strings.Contains(contentType, "application/json") {
		t.Errorf("Expected Content-Type application/json, got %s", contentType)
	}
}

func TestExportHandler_ExportWODs_WithQueryParams(t *testing.T) {
	svc := createTestExportService()
	handler := NewExportHandler(svc)

	tests := []struct {
		name  string
		query string
	}{
		{"include_standard_true", "?include_standard=true"},
		{"include_standard_false", "?include_standard=false"},
		{"include_custom_true", "?include_custom=true"},
		{"include_custom_false", "?include_custom=false"},
		{"all_params", "?include_standard=true&include_custom=true&format=csv"},
		{"json_format", "?format=json"},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			req := createAuthenticatedRequest(http.MethodGet, "/api/export/wods"+tc.query, "", 1, "test@example.com", "user")
			rr := httptest.NewRecorder()

			handler.ExportWODs(rr, req)

			assertStatusCode(t, rr, http.StatusOK)
		})
	}
}

func TestExportHandler_ExportWODs_AsAdmin(t *testing.T) {
	svc := createTestExportService()
	handler := NewExportHandler(svc)

	req := createAuthenticatedRequest(http.MethodGet, "/api/export/wods", "", 1, "admin@example.com", "admin")
	rr := httptest.NewRecorder()

	handler.ExportWODs(rr, req)

	assertStatusCode(t, rr, http.StatusOK)
}

func TestExportHandler_ExportMovements_Success_CSV(t *testing.T) {
	svc := createTestExportService()
	handler := NewExportHandler(svc)

	req := createAuthenticatedRequest(http.MethodGet, "/api/export/movements", "", 1, "test@example.com", "user")
	rr := httptest.NewRecorder()

	handler.ExportMovements(rr, req)

	assertStatusCode(t, rr, http.StatusOK)
	// Check for CSV content type
	contentType := rr.Header().Get("Content-Type")
	if !strings.Contains(contentType, "text/csv") {
		t.Errorf("Expected Content-Type text/csv, got %s", contentType)
	}
	// Check for Content-Disposition header
	disposition := rr.Header().Get("Content-Disposition")
	if !strings.Contains(disposition, "movements_export.csv") {
		t.Errorf("Expected Content-Disposition to contain movements_export.csv, got %s", disposition)
	}
}

func TestExportHandler_ExportMovements_Success_JSON(t *testing.T) {
	svc := createTestExportService()
	handler := NewExportHandler(svc)

	req := createAuthenticatedRequest(http.MethodGet, "/api/export/movements?format=json", "", 1, "test@example.com", "user")
	rr := httptest.NewRecorder()

	handler.ExportMovements(rr, req)

	assertStatusCode(t, rr, http.StatusOK)
	// Check for JSON content type
	contentType := rr.Header().Get("Content-Type")
	if !strings.Contains(contentType, "application/json") {
		t.Errorf("Expected Content-Type application/json, got %s", contentType)
	}
}

func TestExportHandler_ExportMovements_WithQueryParams(t *testing.T) {
	svc := createTestExportService()
	handler := NewExportHandler(svc)

	tests := []struct {
		name  string
		query string
	}{
		{"include_standard_true", "?include_standard=true"},
		{"include_standard_false", "?include_standard=false"},
		{"include_custom_true", "?include_custom=true"},
		{"include_custom_false", "?include_custom=false"},
		{"all_params", "?include_standard=true&include_custom=true&format=csv"},
		{"json_format", "?format=json"},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			req := createAuthenticatedRequest(http.MethodGet, "/api/export/movements"+tc.query, "", 1, "test@example.com", "user")
			rr := httptest.NewRecorder()

			handler.ExportMovements(rr, req)

			assertStatusCode(t, rr, http.StatusOK)
		})
	}
}

func TestExportHandler_ExportMovements_AsAdmin(t *testing.T) {
	svc := createTestExportService()
	handler := NewExportHandler(svc)

	req := createAuthenticatedRequest(http.MethodGet, "/api/export/movements", "", 1, "admin@example.com", "admin")
	rr := httptest.NewRecorder()

	handler.ExportMovements(rr, req)

	assertStatusCode(t, rr, http.StatusOK)
}

func TestExportHandler_ExportUserWorkouts_Success_JSON(t *testing.T) {
	svc := createTestExportService()
	handler := NewExportHandler(svc)

	req := createAuthenticatedRequest(http.MethodGet, "/api/export/user-workouts", "", 1, "test@example.com", "user")
	rr := httptest.NewRecorder()

	handler.ExportUserWorkouts(rr, req)

	assertStatusCode(t, rr, http.StatusOK)
	// Check for JSON content type (default format)
	contentType := rr.Header().Get("Content-Type")
	if !strings.Contains(contentType, "application/json") {
		t.Errorf("Expected Content-Type application/json, got %s", contentType)
	}
}

func TestExportHandler_ExportUserWorkouts_Success_CSV(t *testing.T) {
	svc := createTestExportService()
	handler := NewExportHandler(svc)

	req := createAuthenticatedRequest(http.MethodGet, "/api/export/user-workouts?format=csv", "", 1, "test@example.com", "user")
	rr := httptest.NewRecorder()

	handler.ExportUserWorkouts(rr, req)

	assertStatusCode(t, rr, http.StatusOK)
	// Check for CSV content type
	contentType := rr.Header().Get("Content-Type")
	if !strings.Contains(contentType, "text/csv") {
		t.Errorf("Expected Content-Type text/csv, got %s", contentType)
	}
}

func TestExportHandler_ExportUserWorkouts_WithDateRange(t *testing.T) {
	svc := createTestExportService()
	handler := NewExportHandler(svc)

	req := createAuthenticatedRequest(http.MethodGet, "/api/export/user-workouts?start_date=2024-01-01&end_date=2024-12-31", "", 1, "test@example.com", "user")
	rr := httptest.NewRecorder()

	handler.ExportUserWorkouts(rr, req)

	assertStatusCode(t, rr, http.StatusOK)
}

func TestExportHandler_ExportUserWorkouts_WithAllParams(t *testing.T) {
	svc := createTestExportService()
	handler := NewExportHandler(svc)

	tests := []struct {
		name  string
		query string
	}{
		{"csv_format", "?format=csv"},
		{"json_format", "?format=json"},
		{"with_date_range_csv", "?start_date=2024-01-01&end_date=2024-12-31&format=csv"},
		{"with_date_range_json", "?start_date=2024-01-01&end_date=2024-12-31&format=json"},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			req := createAuthenticatedRequest(http.MethodGet, "/api/export/user-workouts"+tc.query, "", 1, "test@example.com", "user")
			rr := httptest.NewRecorder()

			handler.ExportUserWorkouts(rr, req)

			assertStatusCode(t, rr, http.StatusOK)
		})
	}
}

func TestExportHandler_ExportWODs_ServiceError(t *testing.T) {
	mockWODRepo := NewMockWODRepository()
	mockWODRepo.SetError(ErrMockInternalError)
	mockMovementRepo := NewMockMovementRepository()
	mockUserRepo := NewMockUserRepository()
	mockUserWorkoutRepo := NewMockUserWorkoutRepository()
	svc := service.NewExportService(mockWODRepo, mockMovementRepo, mockUserRepo, mockUserWorkoutRepo)
	handler := NewExportHandler(svc)

	req := createAuthenticatedRequest(http.MethodGet, "/api/export/wods", "", 1, "test@example.com", "user")
	rr := httptest.NewRecorder()

	handler.ExportWODs(rr, req)

	assertStatusCode(t, rr, http.StatusInternalServerError)
}

func TestExportHandler_ExportMovements_ServiceError(t *testing.T) {
	mockWODRepo := NewMockWODRepository()
	mockMovementRepo := NewMockMovementRepository()
	mockMovementRepo.SetError(ErrMockInternalError)
	mockUserRepo := NewMockUserRepository()
	mockUserWorkoutRepo := NewMockUserWorkoutRepository()
	svc := service.NewExportService(mockWODRepo, mockMovementRepo, mockUserRepo, mockUserWorkoutRepo)
	handler := NewExportHandler(svc)

	req := createAuthenticatedRequest(http.MethodGet, "/api/export/movements", "", 1, "test@example.com", "user")
	rr := httptest.NewRecorder()

	handler.ExportMovements(rr, req)

	assertStatusCode(t, rr, http.StatusInternalServerError)
}