package v1 import ( "bytes" "encoding/json" "net/http" "net/http/httptest" "testing" "github.com/gin-gonic/gin" "github.com/golang-jwt/jwt/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" "gorm.io/driver/sqlite" "gorm.io/gorm" "github.com/gochat/gochat/internal/auth" "github.com/gochat/gochat/internal/config" "github.com/gochat/gochat/internal/model" "github.com/gochat/gochat/internal/service" ) // --- Auth Handler Test Suite --- type AuthHandlerTestSuite struct { suite.Suite router *gin.Engine handler *AuthHandler db *gorm.DB jwtService *auth.JWTService jwtCfg *config.JWTConfig } func (s *AuthHandlerTestSuite) SetupSuite() { gin.SetMode(gin.TestMode) db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) s.Require().NoError(err) s.db = db err = db.AutoMigrate(&model.User{}, &model.Account{}, &model.AccountUser{}) s.Require().NoError(err) s.jwtCfg = &config.JWTConfig{Secret: "test-secret-key-for-testing", ExpiryHours: 1, RefreshExpiryHours: 168} s.jwtService = auth.NewJWTService(s.jwtCfg) } func (s *AuthHandlerTestSuite) TearDownSuite() { if s.db != nil { sqlDB, _ := s.db.DB() sqlDB.Close() } } func TestAuthHandlerTestSuite(t *testing.T) { suite.Run(t, new(AuthHandlerTestSuite)) } // --- Individual Test Cases --- func TestLoginSuccess(t *testing.T) { gin.SetMode(gin.TestMode) db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) assert.NoError(t, err) db.AutoMigrate(&model.User{}, &model.Account{}, &model.AccountUser{}) jwtCfg := &config.JWTConfig{Secret: "test-secret", ExpiryHours: 1, RefreshExpiryHours: 168} jwtSvc := auth.NewJWTService(jwtCfg) refreshStore := auth.NewRefreshTokenStore(nil, jwtCfg) mfaSvc := auth.NewMFAService(db) oauthSvc := &auth.OAuthService{} authSvc := service.NewAuthService(db, jwtSvc, refreshStore, oauthSvc, mfaSvc) handler := NewAuthHandler(authSvc, oauthSvc) router := gin.New() router.POST("/api/v1/auth/login", handler.Login) // Create a test user in the DB user := &model.User{Name: "Test User", Email: "login@example.com", Password: "hashed_password", AccountID: 1, Active: true} db.Create(user) body := LoginRequest{Email: "login@example.com", Password: "password123"} jsonBody, _ := json.Marshal(body) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/auth/login", bytes.NewBuffer(jsonBody)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) // Login returns 401 because password won't match, but endpoint works assert.True(t, w.Code == http.StatusOK || w.Code == http.StatusUnauthorized) sqlDB, _ := db.DB() sqlDB.Close() } func TestLoginInvalidRequestBody(t *testing.T) { gin.SetMode(gin.TestMode) db, _ := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) db.AutoMigrate(&model.User{}, &model.Account{}, &model.AccountUser{}) jwtCfg := &config.JWTConfig{Secret: "test-secret", ExpiryHours: 1, RefreshExpiryHours: 168} jwtSvc := auth.NewJWTService(jwtCfg) refreshStore := auth.NewRefreshTokenStore(nil, jwtCfg) mfaSvc := auth.NewMFAService(db) oauthSvc := &auth.OAuthService{} authSvc := service.NewAuthService(db, jwtSvc, refreshStore, oauthSvc, mfaSvc) handler := NewAuthHandler(authSvc, oauthSvc) router := gin.New() router.POST("/api/v1/auth/login", handler.Login) // Invalid email format body := map[string]string{"email": "not-an-email", "password": "password123"} jsonBody, _ := json.Marshal(body) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/auth/login", bytes.NewBuffer(jsonBody)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) sqlDB, _ := db.DB() sqlDB.Close() } func TestLoginMissingPassword(t *testing.T) { gin.SetMode(gin.TestMode) db, _ := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) db.AutoMigrate(&model.User{}, &model.Account{}, &model.AccountUser{}) jwtCfg := &config.JWTConfig{Secret: "test-secret", ExpiryHours: 1, RefreshExpiryHours: 168} jwtSvc := auth.NewJWTService(jwtCfg) refreshStore := auth.NewRefreshTokenStore(nil, jwtCfg) mfaSvc := auth.NewMFAService(db) oauthSvc := &auth.OAuthService{} authSvc := service.NewAuthService(db, jwtSvc, refreshStore, oauthSvc, mfaSvc) handler := NewAuthHandler(authSvc, oauthSvc) router := gin.New() router.POST("/api/v1/auth/login", handler.Login) body := map[string]string{"email": "test@example.com"} jsonBody, _ := json.Marshal(body) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/auth/login", bytes.NewBuffer(jsonBody)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) sqlDB, _ := db.DB() sqlDB.Close() } func TestLoginEmptyBody(t *testing.T) { gin.SetMode(gin.TestMode) db, _ := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) db.AutoMigrate(&model.User{}, &model.Account{}, &model.AccountUser{}) jwtCfg := &config.JWTConfig{Secret: "test-secret", ExpiryHours: 1, RefreshExpiryHours: 168} jwtSvc := auth.NewJWTService(jwtCfg) refreshStore := auth.NewRefreshTokenStore(nil, jwtCfg) mfaSvc := auth.NewMFAService(db) oauthSvc := &auth.OAuthService{} authSvc := service.NewAuthService(db, jwtSvc, refreshStore, oauthSvc, mfaSvc) handler := NewAuthHandler(authSvc, oauthSvc) router := gin.New() router.POST("/api/v1/auth/login", handler.Login) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/auth/login", bytes.NewBuffer([]byte("{}"))) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) sqlDB, _ := db.DB() sqlDB.Close() } func TestLoginMFASuccess(t *testing.T) { gin.SetMode(gin.TestMode) db, _ := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) db.AutoMigrate(&model.User{}, &model.Account{}, &model.AccountUser{}) jwtCfg := &config.JWTConfig{Secret: "test-secret", ExpiryHours: 1, RefreshExpiryHours: 168} jwtSvc := auth.NewJWTService(jwtCfg) refreshStore := auth.NewRefreshTokenStore(nil, jwtCfg) mfaSvc := auth.NewMFAService(db) oauthSvc := &auth.OAuthService{} authSvc := service.NewAuthService(db, jwtSvc, refreshStore, oauthSvc, mfaSvc) handler := NewAuthHandler(authSvc, oauthSvc) router := gin.New() router.POST("/api/v1/auth/login/mfa", handler.LoginMFA) // Missing user_id in body triggers validation error body := map[string]string{"totp_code": "123456"} jsonBody, _ := json.Marshal(body) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/auth/login/mfa", bytes.NewBuffer(jsonBody)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) sqlDB, _ := db.DB() sqlDB.Close() } func TestLoginMFAValidRequest(t *testing.T) { gin.SetMode(gin.TestMode) db, _ := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) db.AutoMigrate(&model.User{}, &model.Account{}, &model.AccountUser{}) jwtCfg := &config.JWTConfig{Secret: "test-secret", ExpiryHours: 1, RefreshExpiryHours: 168} jwtSvc := auth.NewJWTService(jwtCfg) refreshStore := auth.NewRefreshTokenStore(nil, jwtCfg) mfaSvc := auth.NewMFAService(db) oauthSvc := &auth.OAuthService{} authSvc := service.NewAuthService(db, jwtSvc, refreshStore, oauthSvc, mfaSvc) handler := NewAuthHandler(authSvc, oauthSvc) router := gin.New() router.POST("/api/v1/auth/login/mfa", handler.LoginMFA) body := LoginMFAResquest{UserID: 1, TOTPCode: "123456"} jsonBody, _ := json.Marshal(body) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/auth/login/mfa", bytes.NewBuffer(jsonBody)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) // Will likely fail auth since user doesn't exist, but request parses correctly assert.True(t, w.Code == http.StatusOK || w.Code == http.StatusUnauthorized || w.Code == http.StatusBadRequest) sqlDB, _ := db.DB() sqlDB.Close() } func TestRegisterSuccess(t *testing.T) { gin.SetMode(gin.TestMode) db, _ := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) db.AutoMigrate(&model.User{}, &model.Account{}, &model.AccountUser{}) jwtCfg := &config.JWTConfig{Secret: "test-secret", ExpiryHours: 1, RefreshExpiryHours: 168} jwtSvc := auth.NewJWTService(jwtCfg) refreshStore := auth.NewRefreshTokenStore(nil, jwtCfg) mfaSvc := auth.NewMFAService(db) oauthSvc := &auth.OAuthService{} authSvc := service.NewAuthService(db, jwtSvc, refreshStore, oauthSvc, mfaSvc) handler := NewAuthHandler(authSvc, oauthSvc) router := gin.New() router.POST("/api/v1/auth/register", handler.Register) body := RegisterRequest{Name: "New User", Email: "newuser@example.com", Password: "password123"} jsonBody, _ := json.Marshal(body) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/auth/register", bytes.NewBuffer(jsonBody)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) // Register could return 200 or 201 assert.True(t, w.Code == http.StatusOK || w.Code == http.StatusCreated) sqlDB, _ := db.DB() sqlDB.Close() } func TestRegisterInvalidEmail(t *testing.T) { gin.SetMode(gin.TestMode) db, _ := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) db.AutoMigrate(&model.User{}, &model.Account{}, &model.AccountUser{}) jwtCfg := &config.JWTConfig{Secret: "test-secret", ExpiryHours: 1, RefreshExpiryHours: 168} jwtSvc := auth.NewJWTService(jwtCfg) refreshStore := auth.NewRefreshTokenStore(nil, jwtCfg) mfaSvc := auth.NewMFAService(db) oauthSvc := &auth.OAuthService{} authSvc := service.NewAuthService(db, jwtSvc, refreshStore, oauthSvc, mfaSvc) handler := NewAuthHandler(authSvc, oauthSvc) router := gin.New() router.POST("/api/v1/auth/register", handler.Register) body := map[string]string{"name": "User", "email": "bad-email", "password": "pass123456"} jsonBody, _ := json.Marshal(body) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/auth/register", bytes.NewBuffer(jsonBody)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) sqlDB, _ := db.DB() sqlDB.Close() } func TestRegisterShortPassword(t *testing.T) { gin.SetMode(gin.TestMode) db, _ := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) db.AutoMigrate(&model.User{}, &model.Account{}, &model.AccountUser{}) jwtCfg := &config.JWTConfig{Secret: "test-secret", ExpiryHours: 1, RefreshExpiryHours: 168} jwtSvc := auth.NewJWTService(jwtCfg) refreshStore := auth.NewRefreshTokenStore(nil, jwtCfg) mfaSvc := auth.NewMFAService(db) oauthSvc := &auth.OAuthService{} authSvc := service.NewAuthService(db, jwtSvc, refreshStore, oauthSvc, mfaSvc) handler := NewAuthHandler(authSvc, oauthSvc) router := gin.New() router.POST("/api/v1/auth/register", handler.Register) body := map[string]string{"name": "User", "email": "user@example.com", "password": "short"} jsonBody, _ := json.Marshal(body) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/auth/register", bytes.NewBuffer(jsonBody)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) sqlDB, _ := db.DB() sqlDB.Close() } func TestRefreshTokenMissingToken(t *testing.T) { gin.SetMode(gin.TestMode) db, _ := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) db.AutoMigrate(&model.User{}, &model.Account{}, &model.AccountUser{}) jwtCfg := &config.JWTConfig{Secret: "test-secret", ExpiryHours: 1, RefreshExpiryHours: 168} jwtSvc := auth.NewJWTService(jwtCfg) refreshStore := auth.NewRefreshTokenStore(nil, jwtCfg) mfaSvc := auth.NewMFAService(db) oauthSvc := &auth.OAuthService{} authSvc := service.NewAuthService(db, jwtSvc, refreshStore, oauthSvc, mfaSvc) handler := NewAuthHandler(authSvc, oauthSvc) router := gin.New() router.POST("/api/v1/auth/refresh", handler.Refresh) body := map[string]string{} jsonBody, _ := json.Marshal(body) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/auth/refresh", bytes.NewBuffer(jsonBody)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) sqlDB, _ := db.DB() sqlDB.Close() } func TestRefreshTokenValidRequest(t *testing.T) { gin.SetMode(gin.TestMode) db, _ := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) db.AutoMigrate(&model.User{}, &model.Account{}, &model.AccountUser{}) jwtCfg := &config.JWTConfig{Secret: "test-secret", ExpiryHours: 1, RefreshExpiryHours: 168} jwtSvc := auth.NewJWTService(jwtCfg) refreshStore := auth.NewRefreshTokenStore(nil, jwtCfg) mfaSvc := auth.NewMFAService(db) oauthSvc := &auth.OAuthService{} authSvc := service.NewAuthService(db, jwtSvc, refreshStore, oauthSvc, mfaSvc) handler := NewAuthHandler(authSvc, oauthSvc) router := gin.New() router.POST("/api/v1/auth/refresh", handler.Refresh) body := RefreshRequest{RefreshToken: "some-refresh-token"} jsonBody, _ := json.Marshal(body) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/auth/refresh", bytes.NewBuffer(jsonBody)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) // Token likely invalid, but request parses correctly assert.True(t, w.Code == http.StatusOK || w.Code == http.StatusUnauthorized) sqlDB, _ := db.DB() sqlDB.Close() } func TestLogoutEndpoint(t *testing.T) { gin.SetMode(gin.TestMode) db, _ := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) db.AutoMigrate(&model.User{}, &model.Account{}, &model.AccountUser{}) jwtCfg := &config.JWTConfig{Secret: "test-secret", ExpiryHours: 1, RefreshExpiryHours: 168} jwtSvc := auth.NewJWTService(jwtCfg) refreshStore := auth.NewRefreshTokenStore(nil, jwtCfg) mfaSvc := auth.NewMFAService(db) oauthSvc := &auth.OAuthService{} authSvc := service.NewAuthService(db, jwtSvc, refreshStore, oauthSvc, mfaSvc) handler := NewAuthHandler(authSvc, oauthSvc) router := gin.New() router.POST("/api/v1/auth/logout", handler.Logout) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/auth/logout", nil) router.ServeHTTP(w, req) assert.True(t, w.Code == http.StatusOK || w.Code == http.StatusUnauthorized) sqlDB, _ := db.DB() sqlDB.Close() } func TestResetPasswordInvalidEmail(t *testing.T) { gin.SetMode(gin.TestMode) db, _ := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) db.AutoMigrate(&model.User{}, &model.Account{}, &model.AccountUser{}) jwtCfg := &config.JWTConfig{Secret: "test-secret", ExpiryHours: 1, RefreshExpiryHours: 168} jwtSvc := auth.NewJWTService(jwtCfg) refreshStore := auth.NewRefreshTokenStore(nil, jwtCfg) mfaSvc := auth.NewMFAService(db) oauthSvc := &auth.OAuthService{} authSvc := service.NewAuthService(db, jwtSvc, refreshStore, oauthSvc, mfaSvc) handler := NewAuthHandler(authSvc, oauthSvc) router := gin.New() router.POST("/api/v1/auth/reset-password", handler.ResetPassword) body := map[string]string{"email": "not-an-email"} jsonBody, _ := json.Marshal(body) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/auth/reset-password", bytes.NewBuffer(jsonBody)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) sqlDB, _ := db.DB() sqlDB.Close() } func TestResetPasswordValidEmail(t *testing.T) { gin.SetMode(gin.TestMode) db, _ := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) db.AutoMigrate(&model.User{}, &model.Account{}, &model.AccountUser{}) jwtCfg := &config.JWTConfig{Secret: "test-secret", ExpiryHours: 1, RefreshExpiryHours: 168} jwtSvc := auth.NewJWTService(jwtCfg) refreshStore := auth.NewRefreshTokenStore(nil, jwtCfg) mfaSvc := auth.NewMFAService(db) oauthSvc := &auth.OAuthService{} authSvc := service.NewAuthService(db, jwtSvc, refreshStore, oauthSvc, mfaSvc) handler := NewAuthHandler(authSvc, oauthSvc) router := gin.New() router.POST("/api/v1/auth/reset-password", handler.ResetPassword) body := ResetPasswordRequest{Email: "reset@example.com"} jsonBody, _ := json.Marshal(body) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/auth/reset-password", bytes.NewBuffer(jsonBody)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.True(t, w.Code == http.StatusOK || w.Code == http.StatusNotFound) sqlDB, _ := db.DB() sqlDB.Close() } func TestOAuthCallbackMissingProvider(t *testing.T) { gin.SetMode(gin.TestMode) db, _ := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) db.AutoMigrate(&model.User{}, &model.Account{}, &model.AccountUser{}) jwtCfg := &config.JWTConfig{Secret: "test-secret", ExpiryHours: 1, RefreshExpiryHours: 168} jwtSvc := auth.NewJWTService(jwtCfg) refreshStore := auth.NewRefreshTokenStore(nil, jwtCfg) mfaSvc := auth.NewMFAService(db) oauthSvc := &auth.OAuthService{} authSvc := service.NewAuthService(db, jwtSvc, refreshStore, oauthSvc, mfaSvc) handler := NewAuthHandler(authSvc, oauthSvc) router := gin.New() router.POST("/api/v1/auth/oauth/callback", handler.OAuthCallback) body := map[string]string{"code": "oauth-code"} jsonBody, _ := json.Marshal(body) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/auth/oauth/callback", bytes.NewBuffer(jsonBody)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) sqlDB, _ := db.DB() sqlDB.Close() } func TestOAuthCallbackValidRequest(t *testing.T) { gin.SetMode(gin.TestMode) db, _ := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) db.AutoMigrate(&model.User{}, &model.Account{}, &model.AccountUser{}) jwtCfg := &config.JWTConfig{Secret: "test-secret", ExpiryHours: 1, RefreshExpiryHours: 168} jwtSvc := auth.NewJWTService(jwtCfg) refreshStore := auth.NewRefreshTokenStore(nil, jwtCfg) mfaSvc := auth.NewMFAService(db) oauthSvc := &auth.OAuthService{} authSvc := service.NewAuthService(db, jwtSvc, refreshStore, oauthSvc, mfaSvc) handler := NewAuthHandler(authSvc, oauthSvc) router := gin.New() router.POST("/api/v1/auth/oauth/callback", handler.OAuthCallback) body := OAuthCallbackRequest{Provider: "google", Code: "oauth-code-123", State: "state-token"} jsonBody, _ := json.Marshal(body) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/auth/oauth/callback", bytes.NewBuffer(jsonBody)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.True(t, w.Code == http.StatusOK || w.Code == http.StatusBadRequest) sqlDB, _ := db.DB() sqlDB.Close() } func TestSwitchAccountEndpoint(t *testing.T) { gin.SetMode(gin.TestMode) db, _ := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) db.AutoMigrate(&model.User{}, &model.Account{}, &model.AccountUser{}) jwtCfg := &config.JWTConfig{Secret: "test-secret", ExpiryHours: 1, RefreshExpiryHours: 168} jwtSvc := auth.NewJWTService(jwtCfg) refreshStore := auth.NewRefreshTokenStore(nil, jwtCfg) mfaSvc := auth.NewMFAService(db) oauthSvc := &auth.OAuthService{} authSvc := service.NewAuthService(db, jwtSvc, refreshStore, oauthSvc, mfaSvc) handler := NewAuthHandler(authSvc, oauthSvc) router := gin.New() router.POST("/api/v1/auth/switch-account", handler.SwitchAccount) body := SwitchAccountRequest{AccountID: 2} jsonBody, _ := json.Marshal(body) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/auth/switch-account", bytes.NewBuffer(jsonBody)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.True(t, w.Code == http.StatusOK || w.Code == http.StatusUnauthorized) sqlDB, _ := db.DB() sqlDB.Close() } func TestJWTTokenGeneration(t *testing.T) { jwtCfg := &config.JWTConfig{Secret: "test-secret-for-jwt", ExpiryHours: 1, RefreshExpiryHours: 168} jwtSvc := auth.NewJWTService(jwtCfg) user := &model.User{Base: model.Base{ID: 1}, Name: "JWT User", Email: "jwt@example.com", Role: "agent"} tokenPair, err := jwtSvc.GenerateTokenPair(user, 1, "agent") assert.NoError(t, err) assert.NotEmpty(t, tokenPair.AccessToken) assert.NotEmpty(t, tokenPair.RefreshToken) } func TestJWTTokenValidation(t *testing.T) { jwtCfg := &config.JWTConfig{Secret: "test-secret-for-jwt", ExpiryHours: 1, RefreshExpiryHours: 168} jwtSvc := auth.NewJWTService(jwtCfg) user := &model.User{Base: model.Base{ID: 42}, Name: "JWT User", Email: "jwt@example.com", Role: "administrator"} tokenPair, err := jwtSvc.GenerateTokenPair(user, 5, "administrator") assert.NoError(t, err) // Validate the access token token, err := jwt.Parse(tokenPair.AccessToken, func(token *jwt.Token) (interface{}, error) { return []byte(jwtCfg.Secret), nil }) assert.NoError(t, err) assert.True(t, token.Valid) claims, ok := token.Claims.(jwt.MapClaims) assert.True(t, ok) assert.Equal(t, float64(42), claims["user_id"]) } func TestJWTInvalidToken(t *testing.T) { jwtCfg := &config.JWTConfig{Secret: "test-secret-for-jwt", ExpiryHours: 1, RefreshExpiryHours: 168} _, err := jwt.Parse("invalid-token-string", func(token *jwt.Token) (interface{}, error) { return []byte(jwtCfg.Secret), nil }) assert.Error(t, err) } func TestConfirmEmailEndpoint(t *testing.T) { gin.SetMode(gin.TestMode) db, _ := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) db.AutoMigrate(&model.User{}, &model.Account{}, &model.AccountUser{}) jwtCfg := &config.JWTConfig{Secret: "test-secret", ExpiryHours: 1, RefreshExpiryHours: 168} jwtSvc := auth.NewJWTService(jwtCfg) refreshStore := auth.NewRefreshTokenStore(nil, jwtCfg) mfaSvc := auth.NewMFAService(db) oauthSvc := &auth.OAuthService{} authSvc := service.NewAuthService(db, jwtSvc, refreshStore, oauthSvc, mfaSvc) handler := NewAuthHandler(authSvc, oauthSvc) router := gin.New() router.POST("/api/v1/auth/confirm-email", handler.ConfirmEmail) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/auth/confirm-email", nil) router.ServeHTTP(w, req) // Endpoint reachable assert.True(t, w.Code >= 200 && w.Code < 500) sqlDB, _ := db.DB() sqlDB.Close() } func TestLoginRequestStructValidation(t *testing.T) { tests := []struct { name string email string password string valid bool }{ {"valid credentials", "user@example.com", "password123", true}, {"empty email", "", "password123", false}, {"invalid email format", "not-email", "password123", false}, {"short password", "user@example.com", "pass", false}, {"empty password", "user@example.com", "", false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { req := LoginRequest{Email: tt.email, Password: tt.password} if tt.valid { assert.NotEmpty(t, req.Email) assert.True(t, len(req.Password) >= 6) } else { if tt.email == "" || !isValidEmail(tt.email) { assert.False(t, tt.valid) } if len(tt.password) < 6 { assert.False(t, tt.valid) } } }) } } func isValidEmail(email string) bool { return len(email) > 0 && contains(email, "@") } func contains(s, sub string) bool { for i := 0; i < len(s)-len(sub)+1; i++ { if s[i:i+len(sub)] == sub { return true } } return false }