internal/handler/export_handler_test.go
Rama main · réplica de lectura del repositorio original.
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)
}