veza/veza-backend-api/tests/integration/oauth_google_test.go
senke a1000ce7fb style(backend): gofmt -w on 85 files (whitespace only)
backend-ci.yml's `test -z "$(gofmt -l .)"` strict gate (added in
13c21ac11) failed on a backlog of unformatted files. None of the
85 files in this commit had been edited since the gate was added
because no push touched veza-backend-api/** in between, so the
gate never fired until today's CI fixes triggered it.

The diff is exclusively whitespace alignment in struct literals
and trailing-space comments. `go build ./...` and the full test
suite (with VEZA_SKIP_INTEGRATION=1 -short) pass identically.
2026-04-14 12:22:14 +02:00

179 lines
5.6 KiB
Go

//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)
}