fix: Mobile hamburger menu and iPad sidebar visibility

Mobile fixes:
- Add click toggle handler for hamburger menu (was hover-only)
- Menu now opens/closes on tap and closes when clicking outside
- Keep hover support for desktop

iPad fixes:
- Sidebar content now visible on touch devices (901-1280px)
- Added (hover: hover) media query to prevent hide-on-hover on tablets

Security improvements:
- Replace exec.CommandContext with go-git library for git operations
- Add path traversal and command injection prevention
- Fix race condition in template hot reload
- Add environment-based cookie Secure flag

Code quality:
- Add constants.go for magic numbers
- Remove unused code (ParsePreferenceToggleRequest, DomainError)
- Add FOUC prevention with inline critical CSS
- Add Makefile dev/run/clean targets
- Fix README git clone URL
- Add doc/DECISIONS.md for architectural decisions

Tests:
- Add hamburger menu click toggle tests
- Add iPad sidebar visibility tests
- Update security tests for go-git implementation
- Add cookie Secure flag tests
This commit is contained in:
juanatsap
2025-11-30 09:29:35 +00:00
parent 60c1b5ac2b
commit eb92f64e93
18 changed files with 874 additions and 183 deletions
+108
View File
@@ -3,6 +3,7 @@ package middleware
import (
"net/http"
"net/http/httptest"
"os"
"testing"
)
@@ -342,3 +343,110 @@ func TestMultipleMigrations(t *testing.T) {
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) {
// Save original environment
originalEnv := os.Getenv("GO_ENV")
defer os.Setenv("GO_ENV", originalEnv)
// Set test environment
os.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) {
// Save original environment
originalEnv := os.Getenv("GO_ENV")
defer os.Setenv("GO_ENV", originalEnv)
// Set test environment
os.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)
}
})
}
}