//go:build performance // +build performance package performance import ( "bytes" "encoding/json" "fmt" "net/http" "net/http/httptest" "testing" "time" "github.com/gin-gonic/gin" "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap" "go.uber.org/zap/zaptest" "gorm.io/driver/sqlite" "gorm.io/gorm" "veza-backend-api/internal/core/auth" "veza-backend-api/internal/core/track" "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/validators" ) // PerformanceThresholds définit les seuils de performance acceptables var PerformanceThresholds = struct { HealthCheck time.Duration // Health check should be very fast AuthLogin time.Duration // Authentication should be fast AuthRegister time.Duration // Registration can be slightly slower TrackList time.Duration // List operations should be fast TrackGet time.Duration // Get single item should be fast TrackCreate time.Duration // Create operations can be slower PlaylistList time.Duration // List operations should be fast PlaylistCreate time.Duration // Create operations can be slower UserList time.Duration // List operations should be fast UserGet time.Duration // Get single item should be fast }{ HealthCheck: 10 * time.Millisecond, AuthLogin: 100 * time.Millisecond, AuthRegister: 200 * time.Millisecond, TrackList: 50 * time.Millisecond, TrackGet: 30 * time.Millisecond, TrackCreate: 500 * time.Millisecond, PlaylistList: 50 * time.Millisecond, PlaylistCreate: 200 * time.Millisecond, UserList: 50 * time.Millisecond, UserGet: 30 * time.Millisecond, } // setupPerformanceTestRouter crée un router de test pour les tests de performance func setupPerformanceTestRouter(t *testing.T) (*gin.Engine, *gorm.DB, func()) { gin.SetMode(gin.TestMode) logger := zaptest.NewLogger(t) // Setup in-memory SQLite database db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) require.NoError(t, err) db.Exec("PRAGMA foreign_keys = ON") // Auto-migrate models err = db.AutoMigrate( &models.User{}, &models.Track{}, &models.Playlist{}, &models.PlaylistTrack{}, &models.RefreshToken{}, &models.Session{}, &models.Role{}, &models.UserRole{}, &models.Permission{}, ) require.NoError(t, err) dbWrapper := &database.Database{GormDB: db} // Setup services emailValidator := validators.NewEmailValidator(db) passwordValidator := validators.NewPasswordValidator() passwordService := services.NewPasswordService(dbWrapper, logger) jwtService, err := services.NewJWTService("", "", "test-secret-key-must-be-32-chars-long", "test-issuer", "test-audience") require.NoError(t, err) refreshTokenService := services.NewRefreshTokenService(db) emailVerificationService := services.NewEmailVerificationService(dbWrapper, logger) passwordResetService := services.NewPasswordResetService(dbWrapper, logger) emailService := services.NewEmailService(dbWrapper, logger) authService := auth.NewAuthService( db, emailValidator, passwordValidator, passwordService, jwtService, refreshTokenService, emailVerificationService, passwordResetService, emailService, nil, nil, logger, ) // Setup track services uploadDir := t.TempDir() trackService := track.NewTrackService(db, logger, uploadDir) trackUploadService := services.NewTrackUploadService(db, logger) likeService := services.NewTrackLikeService(db, logger) streamService := services.NewStreamService("http://localhost:8082", logger) trackHandler := track.NewTrackHandler(trackService, trackUploadService, nil, likeService, streamService) // Setup playlist services playlistRepo := repositories.NewPlaylistRepository(db) playlistTrackRepo := repositories.NewPlaylistTrackRepository(db) playlistCollaboratorRepo := repositories.NewPlaylistCollaboratorRepository(db) userRepo := repositories.NewGormUserRepository(db) playlistService := services.NewPlaylistService(playlistRepo, playlistTrackRepo, playlistCollaboratorRepo, userRepo, logger) playlistHandler := handlers.NewPlaylistHandler(playlistService, db, logger) // Setup user services userService := services.NewUserServiceWithDB(userRepo, db) profileHandler := handlers.NewProfileHandler(userService, logger) // Create router router := gin.New() // Health check endpoint router.GET("/health", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"status": "ok"}) }) router.GET("/readyz", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"status": "ready"}) }) // Auth routes authGroup := router.Group("/api/v1/auth") { authGroup.POST("/login", handlers.Login(authService, services.NewSessionService(dbWrapper, logger), services.NewTwoFactorService(dbWrapper, logger), logger)) authGroup.POST("/register", handlers.Register(authService, logger)) } // Track routes tracksGroup := router.Group("/api/v1/tracks") { tracksGroup.GET("", trackHandler.ListTracks) tracksGroup.GET("/:id", trackHandler.GetTrack) tracksGroup.POST("", func(c *gin.Context) { // Mock upload for performance testing c.JSON(http.StatusCreated, gin.H{"id": uuid.New().String()}) }) } // Playlist routes playlistsGroup := router.Group("/api/v1/playlists") { playlistsGroup.GET("", playlistHandler.GetPlaylists) playlistsGroup.POST("", func(c *gin.Context) { // Mock create for performance testing c.JSON(http.StatusCreated, gin.H{"id": uuid.New().String()}) }) } // User routes usersGroup := router.Group("/api/v1/users") { usersGroup.GET("", profileHandler.ListUsers) usersGroup.GET("/:id", profileHandler.GetProfile) } cleanup := func() { // Cleanup handled by t.TempDir() } return router, db, cleanup } // measureResponseTime mesure le temps de réponse d'une requête HTTP func measureResponseTime(router *gin.Engine, method, path string, body []byte, headers map[string]string) time.Duration { req := httptest.NewRequest(method, path, bytes.NewBuffer(body)) for k, v := range headers { req.Header.Set(k, v) } req.Header.Set("Content-Type", "application/json") start := time.Now() w := httptest.NewRecorder() router.ServeHTTP(w, req) duration := time.Since(start) return duration } // TestPerformance_HealthCheck teste les performances de l'endpoint health check func TestPerformance_HealthCheck(t *testing.T) { if testing.Short() { t.Skip("Skipping performance test in short mode") } router, _, cleanup := setupPerformanceTestRouter(t) defer cleanup() // Run multiple requests to get average var totalDuration time.Duration iterations := 100 for i := 0; i < iterations; i++ { duration := measureResponseTime(router, "GET", "/health", nil, nil) totalDuration += duration } avgDuration := totalDuration / time.Duration(iterations) t.Logf("Health check average response time: %v (threshold: %v)", avgDuration, PerformanceThresholds.HealthCheck) assert.Less(t, avgDuration, PerformanceThresholds.HealthCheck, "Health check should respond within threshold") } // TestPerformance_ReadinessCheck teste les performances de l'endpoint readiness check func TestPerformance_ReadinessCheck(t *testing.T) { if testing.Short() { t.Skip("Skipping performance test in short mode") } router, _, cleanup := setupPerformanceTestRouter(t) defer cleanup() var totalDuration time.Duration iterations := 100 for i := 0; i < iterations; i++ { duration := measureResponseTime(router, "GET", "/readyz", nil, nil) totalDuration += duration } avgDuration := totalDuration / time.Duration(iterations) t.Logf("Readiness check average response time: %v (threshold: %v)", avgDuration, PerformanceThresholds.HealthCheck) assert.Less(t, avgDuration, PerformanceThresholds.HealthCheck, "Readiness check should respond within threshold") } // TestPerformance_AuthLogin teste les performances de l'endpoint de login func TestPerformance_AuthLogin(t *testing.T) { if testing.Short() { t.Skip("Skipping performance test in short mode") } router, db, cleanup := setupPerformanceTestRouter(t) defer cleanup() // Create test user userID := uuid.New() user := &models.User{ ID: userID, Email: "test@example.com", Username: "testuser", PasswordHash: "$2a$10$abcdefghijklmnopqrstuvwxyz1234567890", // Mock hashed password IsVerified: true, } err := db.Create(user).Error require.NoError(t, err) loginReq := map[string]interface{}{ "email": "test@example.com", "password": "password123", } loginBody, _ := json.Marshal(loginReq) var totalDuration time.Duration iterations := 50 for i := 0; i < iterations; i++ { duration := measureResponseTime(router, "POST", "/api/v1/auth/login", loginBody, nil) totalDuration += duration } avgDuration := totalDuration / time.Duration(iterations) t.Logf("Auth login average response time: %v (threshold: %v)", avgDuration, PerformanceThresholds.AuthLogin) assert.Less(t, avgDuration, PerformanceThresholds.AuthLogin, "Auth login should respond within threshold") } // TestPerformance_AuthRegister teste les performances de l'endpoint d'inscription func TestPerformance_AuthRegister(t *testing.T) { if testing.Short() { t.Skip("Skipping performance test in short mode") } router, _, cleanup := setupPerformanceTestRouter(t) defer cleanup() registerReq := map[string]interface{}{ "email": fmt.Sprintf("test%d@example.com", time.Now().UnixNano()), "username": fmt.Sprintf("testuser%d", time.Now().UnixNano()), "password": "SecurePassword123!", "password_confirm": "SecurePassword123!", } registerBody, _ := json.Marshal(registerReq) var totalDuration time.Duration iterations := 20 for i := 0; i < iterations; i++ { // Use unique email for each iteration registerReq["email"] = fmt.Sprintf("test%d@example.com", time.Now().UnixNano()+int64(i)) registerReq["username"] = fmt.Sprintf("testuser%d", time.Now().UnixNano()+int64(i)) registerBody, _ = json.Marshal(registerReq) duration := measureResponseTime(router, "POST", "/api/v1/auth/register", registerBody, nil) totalDuration += duration } avgDuration := totalDuration / time.Duration(iterations) t.Logf("Auth register average response time: %v (threshold: %v)", avgDuration, PerformanceThresholds.AuthRegister) assert.Less(t, avgDuration, PerformanceThresholds.AuthRegister, "Auth register should respond within threshold") } // TestPerformance_TrackList teste les performances de l'endpoint de liste de tracks func TestPerformance_TrackList(t *testing.T) { if testing.Short() { t.Skip("Skipping performance test in short mode") } router, db, cleanup := setupPerformanceTestRouter(t) defer cleanup() // Create test tracks userID := uuid.New() for i := 0; i < 10; i++ { track := &models.Track{ ID: uuid.New(), UserID: userID, Title: fmt.Sprintf("Test Track %d", i), } db.Create(track) } var totalDuration time.Duration iterations := 100 for i := 0; i < iterations; i++ { duration := measureResponseTime(router, "GET", "/api/v1/tracks", nil, nil) totalDuration += duration } avgDuration := totalDuration / time.Duration(iterations) t.Logf("Track list average response time: %v (threshold: %v)", avgDuration, PerformanceThresholds.TrackList) assert.Less(t, avgDuration, PerformanceThresholds.TrackList, "Track list should respond within threshold") } // TestPerformance_TrackGet teste les performances de l'endpoint de récupération d'un track func TestPerformance_TrackGet(t *testing.T) { if testing.Short() { t.Skip("Skipping performance test in short mode") } router, db, cleanup := setupPerformanceTestRouter(t) defer cleanup() // Create test track userID := uuid.New() trackID := uuid.New() track := &models.Track{ ID: trackID, UserID: userID, Title: "Test Track", } err := db.Create(track).Error require.NoError(t, err) var totalDuration time.Duration iterations := 100 for i := 0; i < iterations; i++ { duration := measureResponseTime(router, "GET", fmt.Sprintf("/api/v1/tracks/%s", trackID), nil, nil) totalDuration += duration } avgDuration := totalDuration / time.Duration(iterations) t.Logf("Track get average response time: %v (threshold: %v)", avgDuration, PerformanceThresholds.TrackGet) assert.Less(t, avgDuration, PerformanceThresholds.TrackGet, "Track get should respond within threshold") } // TestPerformance_TrackCreate teste les performances de l'endpoint de création de track func TestPerformance_TrackCreate(t *testing.T) { if testing.Short() { t.Skip("Skipping performance test in short mode") } router, _, cleanup := setupPerformanceTestRouter(t) defer cleanup() createReq := map[string]interface{}{ "title": "Test Track", } createBody, _ := json.Marshal(createReq) var totalDuration time.Duration iterations := 50 for i := 0; i < iterations; i++ { duration := measureResponseTime(router, "POST", "/api/v1/tracks", createBody, nil) totalDuration += duration } avgDuration := totalDuration / time.Duration(iterations) t.Logf("Track create average response time: %v (threshold: %v)", avgDuration, PerformanceThresholds.TrackCreate) assert.Less(t, avgDuration, PerformanceThresholds.TrackCreate, "Track create should respond within threshold") } // TestPerformance_PlaylistList teste les performances de l'endpoint de liste de playlists func TestPerformance_PlaylistList(t *testing.T) { if testing.Short() { t.Skip("Skipping performance test in short mode") } router, db, cleanup := setupPerformanceTestRouter(t) defer cleanup() // Create test playlists userID := uuid.New() for i := 0; i < 10; i++ { playlist := &models.Playlist{ ID: uuid.New(), UserID: userID, Title: fmt.Sprintf("Test Playlist %d", i), } db.Create(playlist) } var totalDuration time.Duration iterations := 100 for i := 0; i < iterations; i++ { duration := measureResponseTime(router, "GET", "/api/v1/playlists", nil, nil) totalDuration += duration } avgDuration := totalDuration / time.Duration(iterations) t.Logf("Playlist list average response time: %v (threshold: %v)", avgDuration, PerformanceThresholds.PlaylistList) assert.Less(t, avgDuration, PerformanceThresholds.PlaylistList, "Playlist list should respond within threshold") } // TestPerformance_PlaylistCreate teste les performances de l'endpoint de création de playlist func TestPerformance_PlaylistCreate(t *testing.T) { if testing.Short() { t.Skip("Skipping performance test in short mode") } router, _, cleanup := setupPerformanceTestRouter(t) defer cleanup() createReq := map[string]interface{}{ "name": "Test Playlist", } createBody, _ := json.Marshal(createReq) var totalDuration time.Duration iterations := 50 for i := 0; i < iterations; i++ { duration := measureResponseTime(router, "POST", "/api/v1/playlists", createBody, nil) totalDuration += duration } avgDuration := totalDuration / time.Duration(iterations) t.Logf("Playlist create average response time: %v (threshold: %v)", avgDuration, PerformanceThresholds.PlaylistCreate) assert.Less(t, avgDuration, PerformanceThresholds.PlaylistCreate, "Playlist create should respond within threshold") } // TestPerformance_UserList teste les performances de l'endpoint de liste d'utilisateurs func TestPerformance_UserList(t *testing.T) { if testing.Short() { t.Skip("Skipping performance test in short mode") } router, db, cleanup := setupPerformanceTestRouter(t) defer cleanup() // Create test users for i := 0; i < 10; i++ { user := &models.User{ ID: uuid.New(), Email: fmt.Sprintf("user%d@example.com", i), Username: fmt.Sprintf("user%d", i), IsVerified: true, } db.Create(user) } var totalDuration time.Duration iterations := 100 for i := 0; i < iterations; i++ { duration := measureResponseTime(router, "GET", "/api/v1/users", nil, nil) totalDuration += duration } avgDuration := totalDuration / time.Duration(iterations) t.Logf("User list average response time: %v (threshold: %v)", avgDuration, PerformanceThresholds.UserList) assert.Less(t, avgDuration, PerformanceThresholds.UserList, "User list should respond within threshold") } // TestPerformance_UserGet teste les performances de l'endpoint de récupération d'un utilisateur func TestPerformance_UserGet(t *testing.T) { if testing.Short() { t.Skip("Skipping performance test in short mode") } router, db, cleanup := setupPerformanceTestRouter(t) defer cleanup() // Create test user userID := uuid.New() user := &models.User{ ID: userID, Email: "test@example.com", Username: "testuser", IsVerified: true, } err := db.Create(user).Error require.NoError(t, err) var totalDuration time.Duration iterations := 100 for i := 0; i < iterations; i++ { duration := measureResponseTime(router, "GET", fmt.Sprintf("/api/v1/users/%s", userID), nil, nil) totalDuration += duration } avgDuration := totalDuration / time.Duration(iterations) t.Logf("User get average response time: %v (threshold: %v)", avgDuration, PerformanceThresholds.UserGet) assert.Less(t, avgDuration, PerformanceThresholds.UserGet, "User get should respond within threshold") } // BenchmarkHealthCheck benchmark pour l'endpoint health check func BenchmarkHealthCheck(b *testing.B) { gin.SetMode(gin.TestMode) router := gin.New() router.GET("/health", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"status": "ok"}) }) b.ResetTimer() for i := 0; i < b.N; i++ { req := httptest.NewRequest("GET", "/health", nil) w := httptest.NewRecorder() router.ServeHTTP(w, req) } } // BenchmarkTrackList benchmark pour l'endpoint de liste de tracks func BenchmarkTrackList(b *testing.B) { gin.SetMode(gin.TestMode) logger := zap.NewNop() db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) if err != nil { b.Fatalf("Failed to open database: %v", err) } db.AutoMigrate(&models.Track{}) // Create test tracks userID := uuid.New() for i := 0; i < 100; i++ { track := &models.Track{ ID: uuid.New(), UserID: userID, Title: fmt.Sprintf("Test Track %d", i), } db.Create(track) } uploadDir := b.TempDir() trackService := track.NewTrackService(db, logger, uploadDir) trackUploadService := services.NewTrackUploadService(db, logger) likeService := services.NewTrackLikeService(db, logger) streamService := services.NewStreamService("http://localhost:8082", logger) trackHandler := track.NewTrackHandler(trackService, trackUploadService, nil, likeService, streamService) router := gin.New() router.GET("/api/v1/tracks", trackHandler.ListTracks) b.ResetTimer() for i := 0; i < b.N; i++ { req := httptest.NewRequest("GET", "/api/v1/tracks", nil) w := httptest.NewRecorder() router.ServeHTTP(w, req) } } // BenchmarkTrackGet benchmark pour l'endpoint de récupération d'un track func BenchmarkTrackGet(b *testing.B) { gin.SetMode(gin.TestMode) logger := zap.NewNop() db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) if err != nil { b.Fatalf("Failed to open database: %v", err) } db.AutoMigrate(&models.Track{}) // Create test track userID := uuid.New() trackID := uuid.New() testTrack := &models.Track{ ID: trackID, UserID: userID, Title: "Test Track", } db.Create(testTrack) uploadDir := b.TempDir() trackService := track.NewTrackService(db, logger, uploadDir) trackUploadService := services.NewTrackUploadService(db, logger) likeService := services.NewTrackLikeService(db, logger) streamService := services.NewStreamService("http://localhost:8082", logger) trackHandler := track.NewTrackHandler(trackService, trackUploadService, nil, likeService, streamService) router := gin.New() router.GET("/api/v1/tracks/:id", trackHandler.GetTrack) b.ResetTimer() for i := 0; i < b.N; i++ { req := httptest.NewRequest("GET", fmt.Sprintf("/api/v1/tracks/%s", trackID), nil) w := httptest.NewRecorder() router.ServeHTTP(w, req) } }