veza/veza-backend-api/tests/performance/critical_endpoints_performance_test.go
senke b805ddf9d9 [BE-TEST-014] test: Add performance tests for critical endpoints
- Added comprehensive performance tests for critical endpoints:
  * Health check endpoints (/health, /readyz) - threshold: 10ms
  * Authentication endpoints (login: 100ms, register: 200ms)
  * Track endpoints (list: 50ms, get: 30ms, create: 500ms)
  * Playlist endpoints (list: 50ms, create: 200ms)
  * User endpoints (list: 50ms, get: 30ms)
- Includes both performance tests (measuring response times against thresholds)
- Includes benchmarks using Go benchmark framework
- All tests tagged with performance build tag
- Tests use in-memory SQLite for fast execution
2025-12-25 01:48:38 +01:00

647 lines
20 KiB
Go

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