package middleware import ( "net/http" "net/http/httptest" "testing" ) // TestPreferencesMiddleware tests that middleware reads cookies and stores in context func TestPreferencesMiddleware(t *testing.T) { tests := []struct { name string cookies map[string]string expectedPrefs *Preferences description string }{ { name: "Default preferences (no cookies)", cookies: map[string]string{}, expectedPrefs: &Preferences{ CVLength: "short", CVIcons: "show", CVLanguage: "en", CVTheme: "default", ColorTheme: "light", }, description: "Should use default values when no cookies present", }, { name: "All preferences set", cookies: map[string]string{ "cv-length": "long", "cv-icons": "hide", "cv-language": "es", "cv-theme": "clean", "color-theme": "dark", }, expectedPrefs: &Preferences{ CVLength: "long", CVIcons: "hide", CVLanguage: "es", CVTheme: "clean", ColorTheme: "dark", }, description: "Should read all preference cookies correctly", }, { name: "Migration: extended → long", cookies: map[string]string{ "cv-length": "extended", }, expectedPrefs: &Preferences{ CVLength: "long", // Migrated from "extended" CVIcons: "show", CVLanguage: "en", CVTheme: "default", ColorTheme: "light", }, description: "Should migrate 'extended' to 'long'", }, { name: "Migration: true → show", cookies: map[string]string{ "cv-icons": "true", }, expectedPrefs: &Preferences{ CVLength: "short", CVIcons: "show", // Migrated from "true" CVLanguage: "en", CVTheme: "default", ColorTheme: "light", }, description: "Should migrate 'true' to 'show'", }, { name: "Migration: false → hide", cookies: map[string]string{ "cv-icons": "false", }, expectedPrefs: &Preferences{ CVLength: "short", CVIcons: "hide", // Migrated from "false" CVLanguage: "en", CVTheme: "default", ColorTheme: "light", }, description: "Should migrate 'false' to 'hide'", }, { name: "Partial preferences", cookies: map[string]string{ "cv-length": "long", "cv-language": "es", }, expectedPrefs: &Preferences{ CVLength: "long", CVIcons: "show", // Default CVLanguage: "es", CVTheme: "default", // Default ColorTheme: "light", // Default }, description: "Should combine cookies with defaults", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Create request with cookies req := httptest.NewRequest(http.MethodGet, "/", nil) for name, value := range tt.cookies { req.AddCookie(&http.Cookie{ Name: name, Value: value, }) } // Create test handler that checks context var capturedPrefs *Preferences testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { capturedPrefs = GetPreferences(r) w.WriteHeader(http.StatusOK) }) // Wrap with middleware wrappedHandler := PreferencesMiddleware(testHandler) // Execute request w := httptest.NewRecorder() wrappedHandler.ServeHTTP(w, req) // Verify preferences if capturedPrefs == nil { t.Fatal("Preferences not found in context") } if capturedPrefs.CVLength != tt.expectedPrefs.CVLength { t.Errorf("CVLength: got %q, want %q", capturedPrefs.CVLength, tt.expectedPrefs.CVLength) } if capturedPrefs.CVIcons != tt.expectedPrefs.CVIcons { t.Errorf("CVIcons: got %q, want %q", capturedPrefs.CVIcons, tt.expectedPrefs.CVIcons) } if capturedPrefs.CVLanguage != tt.expectedPrefs.CVLanguage { t.Errorf("CVLanguage: got %q, want %q", capturedPrefs.CVLanguage, tt.expectedPrefs.CVLanguage) } if capturedPrefs.CVTheme != tt.expectedPrefs.CVTheme { t.Errorf("CVTheme: got %q, want %q", capturedPrefs.CVTheme, tt.expectedPrefs.CVTheme) } if capturedPrefs.ColorTheme != tt.expectedPrefs.ColorTheme { t.Errorf("ColorTheme: got %q, want %q", capturedPrefs.ColorTheme, tt.expectedPrefs.ColorTheme) } }) } } // TestGetPreferencesWithoutMiddleware tests fallback behavior func TestGetPreferencesWithoutMiddleware(t *testing.T) { // Create request without middleware req := httptest.NewRequest(http.MethodGet, "/", nil) // GetPreferences should return defaults prefs := GetPreferences(req) if prefs.CVLength != "short" { t.Errorf("Expected default CVLength 'short', got %q", prefs.CVLength) } if prefs.CVIcons != "show" { t.Errorf("Expected default CVIcons 'show', got %q", prefs.CVIcons) } if prefs.CVLanguage != "en" { t.Errorf("Expected default CVLanguage 'en', got %q", prefs.CVLanguage) } if prefs.CVTheme != "default" { t.Errorf("Expected default CVTheme 'default', got %q", prefs.CVTheme) } if prefs.ColorTheme != "light" { t.Errorf("Expected default ColorTheme 'light', got %q", prefs.ColorTheme) } } // TestSetPreferenceCookie tests cookie setting func TestSetPreferenceCookie(t *testing.T) { tests := []struct { name string cookieName string cookieValue string }{ { name: "Set cv-length cookie", cookieName: "cv-length", cookieValue: "long", }, { name: "Set cv-icons cookie", cookieName: "cv-icons", cookieValue: "hide", }, { name: "Set cv-language cookie", cookieName: "cv-language", cookieValue: "es", }, { name: "Set cv-theme cookie", cookieName: "cv-theme", cookieValue: "clean", }, { name: "Set color-theme cookie", cookieName: "color-theme", cookieValue: "dark", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { w := httptest.NewRecorder() SetPreferenceCookie(w, tt.cookieName, tt.cookieValue) // Get cookies from response cookies := w.Result().Cookies() if len(cookies) != 1 { t.Fatalf("Expected 1 cookie, got %d", len(cookies)) } cookie := cookies[0] // Verify cookie attributes if cookie.Name != tt.cookieName { t.Errorf("Cookie name: got %q, want %q", cookie.Name, tt.cookieName) } if cookie.Value != tt.cookieValue { t.Errorf("Cookie value: got %q, want %q", cookie.Value, tt.cookieValue) } if cookie.Path != "/" { t.Errorf("Cookie path: got %q, want %q", cookie.Path, "/") } if cookie.MaxAge != 365*24*60*60 { t.Errorf("Cookie MaxAge: got %d, want %d (1 year)", cookie.MaxAge, 365*24*60*60) } if !cookie.HttpOnly { t.Error("Cookie should be HttpOnly") } if cookie.SameSite != http.SameSiteStrictMode { t.Errorf("Cookie SameSite: got %v, want %v", cookie.SameSite, http.SameSiteStrictMode) } }) } } // TestGetPreferenceCookie tests cookie reading helper func TestGetPreferenceCookie(t *testing.T) { tests := []struct { name string cookieName string cookieValue string defaultValue string expectedValue string }{ { name: "Cookie exists", cookieName: "cv-length", cookieValue: "long", defaultValue: "short", expectedValue: "long", }, { name: "Cookie doesn't exist", cookieName: "cv-length", cookieValue: "", defaultValue: "short", expectedValue: "short", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "/", nil) if tt.cookieValue != "" { req.AddCookie(&http.Cookie{ Name: tt.cookieName, Value: tt.cookieValue, }) } value := getPreferenceCookie(req, tt.cookieName, tt.defaultValue) if value != tt.expectedValue { t.Errorf("Expected %q, got %q", tt.expectedValue, value) } }) } } // TestMiddlewareChain tests middleware can be chained func TestMiddlewareChain(t *testing.T) { // Create multiple handlers in chain finalHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { prefs := GetPreferences(r) w.Header().Set("X-CV-Length", prefs.CVLength) w.WriteHeader(http.StatusOK) }) // Wrap with middleware wrappedHandler := PreferencesMiddleware(finalHandler) // Create request with cookie req := httptest.NewRequest(http.MethodGet, "/", nil) req.AddCookie(&http.Cookie{Name: "cv-length", Value: "long"}) w := httptest.NewRecorder() wrappedHandler.ServeHTTP(w, req) // Verify preferences were passed through chain if w.Header().Get("X-CV-Length") != "long" { t.Errorf("Expected X-CV-Length header 'long', got %q", w.Header().Get("X-CV-Length")) } } // TestMultipleMigrations tests multiple migrations in one request func TestMultipleMigrations(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "/", nil) req.AddCookie(&http.Cookie{Name: "cv-length", Value: "extended"}) req.AddCookie(&http.Cookie{Name: "cv-icons", Value: "true"}) var capturedPrefs *Preferences testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { capturedPrefs = GetPreferences(r) }) wrappedHandler := PreferencesMiddleware(testHandler) w := httptest.NewRecorder() wrappedHandler.ServeHTTP(w, req) // Both migrations should occur if capturedPrefs.CVLength != "long" { t.Errorf("CVLength: expected 'long' (migrated from 'extended'), got %q", capturedPrefs.CVLength) } if capturedPrefs.CVIcons != "show" { t.Errorf("CVIcons: expected 'show' (migrated from 'true'), got %q", capturedPrefs.CVIcons) } } // TestIsProductionMode tests the production mode detection function func TestIsProductionMode(t *testing.T) { tests := []struct { name string envValue string expected bool }{ { name: "Production environment", envValue: "production", expected: true, }, { name: "Prod shorthand", envValue: "prod", expected: true, }, { name: "Development environment", envValue: "development", expected: false, }, { name: "Empty environment", envValue: "", expected: false, }, { name: "Staging environment", envValue: "staging", expected: false, }, { name: "Case sensitivity - PRODUCTION", envValue: "PRODUCTION", expected: false, // Case sensitive }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // t.Setenv automatically restores the original value after the test t.Setenv("GO_ENV", tt.envValue) result := isProductionMode() if result != tt.expected { t.Errorf("isProductionMode() with GO_ENV=%q: got %v, want %v", tt.envValue, result, tt.expected) } }) } } // TestSetPreferenceCookieSecureFlag tests that Secure flag is set correctly based on environment func TestSetPreferenceCookieSecureFlag(t *testing.T) { tests := []struct { name string envValue string expectedSecure bool }{ { name: "Production mode sets Secure=true", envValue: "production", expectedSecure: true, }, { name: "Development mode sets Secure=false", envValue: "development", expectedSecure: false, }, { name: "Empty env sets Secure=false", envValue: "", expectedSecure: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // t.Setenv automatically restores the original value after the test t.Setenv("GO_ENV", tt.envValue) w := httptest.NewRecorder() SetPreferenceCookie(w, "test-cookie", "test-value") cookies := w.Result().Cookies() if len(cookies) != 1 { t.Fatalf("Expected 1 cookie, got %d", len(cookies)) } if cookies[0].Secure != tt.expectedSecure { t.Errorf("Cookie Secure flag with GO_ENV=%q: got %v, want %v", tt.envValue, cookies[0].Secure, tt.expectedSecure) } }) } }