package middleware import ( "net/http" "net/http/httptest" "testing" "github.com/labstack/echo/v4" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/treytartt/honeydue-api/internal/config" "github.com/treytartt/honeydue-api/internal/models" ) func TestTokenAuth_BearerScheme_Accepted(t *testing.T) { db := setupTestDB(t) _, token := createTestUserAndToken(t, db, "bearer_user", 10) m := NewAuthMiddleware(db, nil) e := echo.New() req := httptest.NewRequest(http.MethodGet, "/api/test/", nil) req.Header.Set("Authorization", "Bearer "+token.Key) rec := httptest.NewRecorder() c := e.NewContext(req, rec) handler := m.TokenAuth()(func(c echo.Context) error { return c.String(http.StatusOK, "ok") }) err := handler(c) require.NoError(t, err) assert.Equal(t, http.StatusOK, rec.Code) user := GetAuthUser(c) require.NotNil(t, user) assert.Equal(t, "bearer_user", user.Username) } func TestTokenAuth_InvalidScheme_Rejected(t *testing.T) { db := setupTestDB(t) _, token := createTestUserAndToken(t, db, "scheme_user", 10) m := NewAuthMiddleware(db, nil) e := echo.New() req := httptest.NewRequest(http.MethodGet, "/api/test/", nil) req.Header.Set("Authorization", "Basic "+token.Key) rec := httptest.NewRecorder() c := e.NewContext(req, rec) handler := m.TokenAuth()(func(c echo.Context) error { return c.String(http.StatusOK, "ok") }) err := handler(c) require.Error(t, err) assert.Contains(t, err.Error(), "error.not_authenticated") } func TestTokenAuth_MalformedHeader_Rejected(t *testing.T) { db := setupTestDB(t) m := NewAuthMiddleware(db, nil) e := echo.New() req := httptest.NewRequest(http.MethodGet, "/api/test/", nil) req.Header.Set("Authorization", "JustATokenWithNoScheme") rec := httptest.NewRecorder() c := e.NewContext(req, rec) handler := m.TokenAuth()(func(c echo.Context) error { return c.String(http.StatusOK, "ok") }) err := handler(c) require.Error(t, err) assert.Contains(t, err.Error(), "error.not_authenticated") } func TestTokenAuth_EmptyToken_Rejected(t *testing.T) { db := setupTestDB(t) m := NewAuthMiddleware(db, nil) e := echo.New() req := httptest.NewRequest(http.MethodGet, "/api/test/", nil) req.Header.Set("Authorization", "Token ") rec := httptest.NewRecorder() c := e.NewContext(req, rec) handler := m.TokenAuth()(func(c echo.Context) error { return c.String(http.StatusOK, "ok") }) err := handler(c) require.Error(t, err) assert.Contains(t, err.Error(), "error.not_authenticated") } func TestTokenAuth_InactiveUser_Rejected(t *testing.T) { db := setupTestDB(t) user, token := createTestUserAndToken(t, db, "inactive_user", 10) // Deactivate the user require.NoError(t, db.Model(user).Update("is_active", false).Error) m := NewAuthMiddleware(db, nil) e := echo.New() req := httptest.NewRequest(http.MethodGet, "/api/test/", nil) req.Header.Set("Authorization", "Token "+token.Key) rec := httptest.NewRecorder() c := e.NewContext(req, rec) handler := m.TokenAuth()(func(c echo.Context) error { return c.String(http.StatusOK, "ok") }) err := handler(c) require.Error(t, err) assert.Contains(t, err.Error(), "error.invalid_token") } func TestOptionalTokenAuth_NoToken_PassesThrough(t *testing.T) { db := setupTestDB(t) m := NewAuthMiddleware(db, nil) e := echo.New() req := httptest.NewRequest(http.MethodGet, "/api/test/", nil) // No Authorization header rec := httptest.NewRecorder() c := e.NewContext(req, rec) handler := m.OptionalTokenAuth()(func(c echo.Context) error { user := GetAuthUser(c) if user == nil { return c.String(http.StatusOK, "no-user") } return c.String(http.StatusOK, user.Username) }) err := handler(c) require.NoError(t, err) assert.Equal(t, http.StatusOK, rec.Code) assert.Equal(t, "no-user", rec.Body.String()) } func TestOptionalTokenAuth_ValidToken_SetsUser(t *testing.T) { db := setupTestDB(t) _, token := createTestUserAndToken(t, db, "opt_user", 10) m := NewAuthMiddleware(db, nil) e := echo.New() req := httptest.NewRequest(http.MethodGet, "/api/test/", nil) req.Header.Set("Authorization", "Token "+token.Key) rec := httptest.NewRecorder() c := e.NewContext(req, rec) handler := m.OptionalTokenAuth()(func(c echo.Context) error { user := GetAuthUser(c) if user == nil { return c.String(http.StatusOK, "no-user") } return c.String(http.StatusOK, user.Username) }) err := handler(c) require.NoError(t, err) assert.Equal(t, http.StatusOK, rec.Code) assert.Equal(t, "opt_user", rec.Body.String()) } func TestOptionalTokenAuth_ExpiredToken_IgnoresUser(t *testing.T) { db := setupTestDB(t) _, token := createTestUserAndToken(t, db, "expired_opt_user", 91) m := NewAuthMiddleware(db, nil) e := echo.New() req := httptest.NewRequest(http.MethodGet, "/api/test/", nil) req.Header.Set("Authorization", "Token "+token.Key) rec := httptest.NewRecorder() c := e.NewContext(req, rec) handler := m.OptionalTokenAuth()(func(c echo.Context) error { user := GetAuthUser(c) if user == nil { return c.String(http.StatusOK, "no-user") } return c.String(http.StatusOK, user.Username) }) err := handler(c) require.NoError(t, err) assert.Equal(t, http.StatusOK, rec.Code) assert.Equal(t, "no-user", rec.Body.String()) } func TestOptionalTokenAuth_InvalidToken_IgnoresUser(t *testing.T) { db := setupTestDB(t) m := NewAuthMiddleware(db, nil) e := echo.New() req := httptest.NewRequest(http.MethodGet, "/api/test/", nil) req.Header.Set("Authorization", "Token nonexistent-token") rec := httptest.NewRecorder() c := e.NewContext(req, rec) handler := m.OptionalTokenAuth()(func(c echo.Context) error { user := GetAuthUser(c) if user == nil { return c.String(http.StatusOK, "no-user") } return c.String(http.StatusOK, user.Username) }) err := handler(c) require.NoError(t, err) assert.Equal(t, http.StatusOK, rec.Code) assert.Equal(t, "no-user", rec.Body.String()) } func TestNewAuthMiddlewareWithConfig_CustomExpiryDays(t *testing.T) { db := setupTestDB(t) cfg := &config.Config{ Security: config.SecurityConfig{ TokenExpiryDays: 30, }, } m := NewAuthMiddlewareWithConfig(db, nil, cfg) assert.NotNil(t, m) assert.Equal(t, 30, m.tokenExpiryDays) // Token at 29 days should be valid _, token := createTestUserAndToken(t, db, "short_expiry_user", 29) e := echo.New() req := httptest.NewRequest(http.MethodGet, "/api/test/", nil) req.Header.Set("Authorization", "Token "+token.Key) rec := httptest.NewRecorder() c := e.NewContext(req, rec) handler := m.TokenAuth()(func(c echo.Context) error { return c.String(http.StatusOK, "ok") }) err := handler(c) require.NoError(t, err) assert.Equal(t, http.StatusOK, rec.Code) } func TestNewAuthMiddlewareWithConfig_ExpiredWithCustomExpiry(t *testing.T) { db := setupTestDB(t) cfg := &config.Config{ Security: config.SecurityConfig{ TokenExpiryDays: 30, }, } m := NewAuthMiddlewareWithConfig(db, nil, cfg) // Token at 31 days should be expired with 30-day config _, token := createTestUserAndToken(t, db, "custom_expired_user", 31) e := echo.New() req := httptest.NewRequest(http.MethodGet, "/api/test/", nil) req.Header.Set("Authorization", "Token "+token.Key) rec := httptest.NewRecorder() c := e.NewContext(req, rec) handler := m.TokenAuth()(func(c echo.Context) error { return c.String(http.StatusOK, "ok") }) err := handler(c) require.Error(t, err) assert.Contains(t, err.Error(), "error.token_expired") } func TestNewAuthMiddlewareWithConfig_NilConfig_UsesDefault(t *testing.T) { db := setupTestDB(t) m := NewAuthMiddlewareWithConfig(db, nil, nil) assert.Equal(t, DefaultTokenExpiryDays, m.tokenExpiryDays) } func TestGetAuthToken_ReturnsToken(t *testing.T) { e := echo.New() req := httptest.NewRequest(http.MethodGet, "/", nil) rec := httptest.NewRecorder() c := e.NewContext(req, rec) c.Set(AuthTokenKey, "test-token-value") assert.Equal(t, "test-token-value", GetAuthToken(c)) } func TestGetAuthToken_NilContext_ReturnsEmpty(t *testing.T) { e := echo.New() req := httptest.NewRequest(http.MethodGet, "/", nil) rec := httptest.NewRecorder() c := e.NewContext(req, rec) // No token set assert.Equal(t, "", GetAuthToken(c)) } func TestGetAuthToken_WrongType_ReturnsEmpty(t *testing.T) { e := echo.New() req := httptest.NewRequest(http.MethodGet, "/", nil) rec := httptest.NewRecorder() c := e.NewContext(req, rec) c.Set(AuthTokenKey, 12345) // Wrong type assert.Equal(t, "", GetAuthToken(c)) } func TestIsTokenExpired_ZeroTime_NotExpired(t *testing.T) { db := setupTestDB(t) m := NewAuthMiddleware(db, nil) // Legacy tokens without created time should not be expired assert.False(t, m.isTokenExpired(models.AuthToken{}.Created)) } func TestInvalidateToken_NilCache_NoError(t *testing.T) { db := setupTestDB(t) m := NewAuthMiddleware(db, nil) // nil cache err := m.InvalidateToken(nil, "some-token") assert.NoError(t, err) }