Files
cv-site/internal/middleware/preferences_test.go
T
juanatsap 00e28906e6 fix: Resolve CSS bundling in production and lint errors
- Fix golangci-lint errcheck errors by using t.Setenv() instead of os.Setenv()
- Add CSS bundle build step to deploy workflow for production
- Add graceful fallback to modular CSS if bundle doesn't exist
- Remove unused os import from preferences_test.go
2025-11-30 12:38:31 +00:00

444 lines
11 KiB
Go

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)
}
})
}
}