//go:build integration // +build integration package integration import ( "context" "encoding/json" "net/http" "net/http/httptest" "testing" "github.com/gin-gonic/gin" "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap/zaptest" "gorm.io/driver/postgres" "gorm.io/gorm" "veza-backend-api/internal/config" "veza-backend-api/internal/database" "veza-backend-api/internal/handlers" "veza-backend-api/internal/models" "veza-backend-api/internal/repositories" "veza-backend-api/internal/services" "veza-backend-api/internal/testutils" ) // setupOAuthGoogleTestRouter creates a test router with OAuth Google flow (mock provider) func setupOAuthGoogleTestRouter(t *testing.T) (*gin.Engine, *services.OAuthService, *services.JWTService, *gorm.DB, *httptest.Server, func()) { ctx := context.Background() gin.SetMode(gin.TestMode) logger := zaptest.NewLogger(t) dsn, err := testutils.GetTestContainerDB(ctx) if err != nil { t.Skipf("Skipping test: PostgreSQL testcontainer not available: %v", err) return nil, nil, nil, nil, nil, func() {} } db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{}) require.NoError(t, err) sqlDB, err := db.DB() require.NoError(t, err) dbWrapper := &database.Database{ DB: sqlDB, GormDB: db, Logger: logger, } // Mock OAuth provider: token exchange + userinfo mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path == "/token" && r.Method == http.MethodPost { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]interface{}{ "access_token": "mock_access_token_123", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "mock_refresh_token", }) return } if r.URL.Path == "/userinfo" && r.Method == http.MethodGet { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]interface{}{ "id": "google123", "email": "oauth-google@test.com", "name": "OAuth Google User", }) return } http.NotFound(w, r) })) jwtService, err := services.NewJWTService("", "", "test-secret-key-must-be-32-chars-long", "test-issuer", "test-audience") require.NoError(t, err) sessionService := services.NewSessionService(dbWrapper, logger) userRepo := repositories.NewGormUserRepository(db) userService := services.NewUserServiceWithDB(userRepo, db) oauthCfg := &services.OAuthServiceConfig{ TestTokenURL: mockServer.URL + "/token", TestUserInfoURL: mockServer.URL + "/userinfo", FrontendURL: "http://localhost:5173", AllowedDomains: []string{"*"}, } oauthService := services.NewOAuthService(dbWrapper, logger, jwtService, sessionService, userService, oauthCfg) oauthService.InitializeConfigs("test-client", "test-secret", "", "", "", "", "", "", "http://localhost:8080") cfg := &config.Config{ CookiePath: "/", CookieDomain: "", CookieHttpOnly: true, CookieSecure: false, CookieSameSite: "lax", JWTService: jwtService, } oauthHandler := handlers.NewOAuthHandler(oauthService, logger, nil, "http://localhost:5173", cfg) router := gin.New() authGroup := router.Group("/auth") oauthGroup := authGroup.Group("/oauth") { oauthGroup.GET("/:provider", oauthHandler.InitiateOAuth) oauthGroup.GET("/:provider/callback", oauthHandler.OAuthCallback) } cleanup := func() { mockServer.Close() } return router, oauthService, jwtService, db, mockServer, cleanup } // TestOAuthGoogleFlow tests the full OAuth Google flow with mocked provider func TestOAuthGoogleFlow(t *testing.T) { router, oauthService, jwtService, db, _, cleanup := setupOAuthGoogleTestRouter(t) defer cleanup() if router == nil { return } // Generate state token and insert into DB (simulate InitiateOAuth step) stateToken, codeVerifier, err := oauthService.GenerateStateToken("google", "http://localhost:5173") require.NoError(t, err) require.NotEmpty(t, stateToken) require.NotEmpty(t, codeVerifier) // Simulate OAuth callback with mock code callbackURL := "/auth/oauth/google/callback?code=mock_code&state=" + stateToken req := httptest.NewRequest(http.MethodGet, callbackURL, nil) w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusTemporaryRedirect, w.Code, "Expected redirect after OAuth callback") // Verify cookies are set cookies := w.Result().Cookies() var accessTokenCookie, refreshTokenCookie *http.Cookie for _, c := range cookies { if c.Name == "access_token" { accessTokenCookie = c } if c.Name == "refresh_token" { refreshTokenCookie = c } } require.NotNil(t, accessTokenCookie, "access_token cookie should be set") require.NotNil(t, refreshTokenCookie, "refresh_token cookie should be set") assert.NotEmpty(t, accessTokenCookie.Value, "access_token should not be empty") assert.NotEmpty(t, refreshTokenCookie.Value, "refresh_token should not be empty") assert.True(t, accessTokenCookie.HttpOnly, "access_token should be httpOnly") assert.True(t, refreshTokenCookie.HttpOnly, "refresh_token should be httpOnly") // Validate JWT with JWTService claims, err := jwtService.ValidateToken(accessTokenCookie.Value) require.NoError(t, err) assert.NotEqual(t, claims.UserID, uuid.Nil) // Verify session exists in DB var sessionCount int64 err = db.Table("sessions").Count(&sessionCount).Error require.NoError(t, err) assert.GreaterOrEqual(t, sessionCount, int64(1), "Session should be created") // Verify user was created var user models.User err = db.Where("email = ?", "oauth-google@test.com").First(&user).Error require.NoError(t, err) assert.Equal(t, "oauth-google@test.com", user.Email) }