package middleware import ( "net/http" "net/http/httptest" "testing" "time" "github.com/labstack/echo/v4" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestTimezoneMiddleware_IANATimezone(t *testing.T) { mw := TimezoneMiddleware() e := echo.New() handler := mw(func(c echo.Context) error { loc := GetUserTimezone(c) return c.String(http.StatusOK, loc.String()) }) req := httptest.NewRequest(http.MethodGet, "/api/test/", nil) req.Header.Set(TimezoneHeader, "America/New_York") rec := httptest.NewRecorder() c := e.NewContext(req, rec) err := handler(c) require.NoError(t, err) assert.Equal(t, "America/New_York", rec.Body.String()) } func TestTimezoneMiddleware_UTCOffset(t *testing.T) { mw := TimezoneMiddleware() e := echo.New() handler := mw(func(c echo.Context) error { loc := GetUserTimezone(c) // Just verify it's not UTC (if offset is non-zero) return c.String(http.StatusOK, loc.String()) }) req := httptest.NewRequest(http.MethodGet, "/api/test/", nil) req.Header.Set(TimezoneHeader, "-05:00") rec := httptest.NewRecorder() c := e.NewContext(req, rec) err := handler(c) require.NoError(t, err) assert.Equal(t, "-05:00", rec.Body.String()) } func TestTimezoneMiddleware_NoHeader_DefaultsToUTC(t *testing.T) { mw := TimezoneMiddleware() e := echo.New() handler := mw(func(c echo.Context) error { loc := GetUserTimezone(c) return c.String(http.StatusOK, loc.String()) }) req := httptest.NewRequest(http.MethodGet, "/api/test/", nil) // No X-Timezone header rec := httptest.NewRecorder() c := e.NewContext(req, rec) err := handler(c) require.NoError(t, err) assert.Equal(t, "UTC", rec.Body.String()) } func TestTimezoneMiddleware_InvalidTimezone_DefaultsToUTC(t *testing.T) { mw := TimezoneMiddleware() e := echo.New() handler := mw(func(c echo.Context) error { loc := GetUserTimezone(c) return c.String(http.StatusOK, loc.String()) }) req := httptest.NewRequest(http.MethodGet, "/api/test/", nil) req.Header.Set(TimezoneHeader, "Invalid/Timezone") rec := httptest.NewRecorder() c := e.NewContext(req, rec) err := handler(c) require.NoError(t, err) assert.Equal(t, "UTC", rec.Body.String()) } func TestTimezoneMiddleware_SetsUserNow(t *testing.T) { mw := TimezoneMiddleware() e := echo.New() var capturedNow time.Time handler := mw(func(c echo.Context) error { capturedNow = GetUserNow(c) return c.String(http.StatusOK, "ok") }) req := httptest.NewRequest(http.MethodGet, "/api/test/", nil) req.Header.Set(TimezoneHeader, "America/Chicago") rec := httptest.NewRecorder() c := e.NewContext(req, rec) err := handler(c) require.NoError(t, err) // The "now" should be the start of the day in the user's timezone assert.Equal(t, 0, capturedNow.Hour()) assert.Equal(t, 0, capturedNow.Minute()) assert.Equal(t, 0, capturedNow.Second()) } func TestTimezoneMiddleware_SetsTimezoneName(t *testing.T) { mw := TimezoneMiddleware() e := echo.New() handler := mw(func(c echo.Context) error { name := GetTimezoneName(c) return c.String(http.StatusOK, name) }) req := httptest.NewRequest(http.MethodGet, "/api/test/", nil) req.Header.Set(TimezoneHeader, "Europe/London") rec := httptest.NewRecorder() c := e.NewContext(req, rec) err := handler(c) require.NoError(t, err) assert.Equal(t, "Europe/London", rec.Body.String()) } func TestGetUserTimezone_NotSet_ReturnsUTC(t *testing.T) { e := echo.New() req := httptest.NewRequest(http.MethodGet, "/", nil) rec := httptest.NewRecorder() c := e.NewContext(req, rec) // No timezone set in context loc := GetUserTimezone(c) assert.Equal(t, time.UTC, loc) } func TestGetUserTimezone_WrongType_ReturnsUTC(t *testing.T) { e := echo.New() req := httptest.NewRequest(http.MethodGet, "/", nil) rec := httptest.NewRecorder() c := e.NewContext(req, rec) c.Set(TimezoneKey, "not-a-location") loc := GetUserTimezone(c) assert.Equal(t, time.UTC, loc) } func TestGetUserNow_NotSet_ReturnsUTCNow(t *testing.T) { e := echo.New() req := httptest.NewRequest(http.MethodGet, "/", nil) rec := httptest.NewRecorder() c := e.NewContext(req, rec) before := time.Now().UTC() now := GetUserNow(c) after := time.Now().UTC() assert.True(t, !now.Before(before.Add(-time.Second)), "now should be roughly after before") assert.True(t, !now.After(after.Add(time.Second)), "now should be roughly before after") } func TestGetUserNow_WrongType_ReturnsUTCNow(t *testing.T) { e := echo.New() req := httptest.NewRequest(http.MethodGet, "/", nil) rec := httptest.NewRecorder() c := e.NewContext(req, rec) c.Set(UserNowKey, "not-a-time") now := GetUserNow(c) assert.NotNil(t, now) } func TestIsTimezoneChanged_NoChange_ReturnsFalse(t *testing.T) { e := echo.New() req := httptest.NewRequest(http.MethodGet, "/", nil) rec := httptest.NewRecorder() c := e.NewContext(req, rec) c.Set(TimezoneChangedKey, false) assert.False(t, IsTimezoneChanged(c)) } func TestIsTimezoneChanged_Changed_ReturnsTrue(t *testing.T) { e := echo.New() req := httptest.NewRequest(http.MethodGet, "/", nil) rec := httptest.NewRecorder() c := e.NewContext(req, rec) c.Set(TimezoneChangedKey, true) assert.True(t, IsTimezoneChanged(c)) } func TestIsTimezoneChanged_NotSet_ReturnsFalse(t *testing.T) { e := echo.New() req := httptest.NewRequest(http.MethodGet, "/", nil) rec := httptest.NewRecorder() c := e.NewContext(req, rec) // Not set at all assert.False(t, IsTimezoneChanged(c)) } func TestParseTimezone_UTCOffsetWithoutColon(t *testing.T) { loc := parseTimezone("-0800") assert.NotEqual(t, time.UTC, loc) assert.Equal(t, "-0800", loc.String()) } func TestParseTimezone_PositiveOffset(t *testing.T) { loc := parseTimezone("+05:30") assert.NotEqual(t, time.UTC, loc) assert.Equal(t, "+05:30", loc.String()) } func TestParseTimezone_UTC(t *testing.T) { loc := parseTimezone("UTC") assert.Equal(t, time.UTC, loc) }