package handlers import ( "bytes" "context" "encoding/json" "github.com/google/uuid" "net/http" "net/http/httptest" "testing" "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "go.uber.org/zap/zaptest" "gorm.io/driver/sqlite" "gorm.io/gorm" "veza-backend-api/internal/models" "veza-backend-api/internal/services" ) // MockBitrateAdaptationService est un mock du service d'adaptation de bitrate type MockBitrateAdaptationService struct { mock.Mock } func (m *MockBitrateAdaptationService) AdaptBitrate(ctx context.Context, trackID uuid.UUID, userID uuid.UUID, currentBitrate int, bandwidth int64, bufferLevel float64) (int, error) { args := m.Called(ctx, trackID, userID, currentBitrate, bandwidth, bufferLevel) return args.Int(0), args.Error(1) } func setupTestBitrateHandlerRouter(adaptationService *services.BitrateAdaptationService) *gin.Engine { gin.SetMode(gin.TestMode) router := gin.New() handler := NewBitrateHandler(adaptationService) // Route protégée (nécessite authentification) protected := router.Group("/api/v1/tracks") protected.Use(func(c *gin.Context) { // Simuler le middleware d'authentification // Use a fixed UUID for testing consistency if needed, or random uid := uuid.New() c.Set("user_id", uid) c.Next() }) { protected.POST("/:id/bitrate/adapt", handler.AdaptBitrate) } return router } func TestNewBitrateHandler(t *testing.T) { db, _ := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) logger := zaptest.NewLogger(t) bandwidthService := services.NewBandwidthDetectionService(logger) adaptationService := services.NewBitrateAdaptationService(db, bandwidthService, logger) handler := NewBitrateHandler(adaptationService) assert.NotNil(t, handler) assert.Equal(t, adaptationService, handler.adaptationService) } func TestBitrateHandler_AdaptBitrate_Success(t *testing.T) { db, _ := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) db.Exec("PRAGMA foreign_keys = ON") db.AutoMigrate(&models.User{}, &models.Track{}, &models.BitrateAdaptationLog{}) userID := uuid.New() trackID := uuid.New() // Create test user and track user := &models.User{ID: userID, Username: "testuser", Email: "test@example.com", IsActive: true} db.Create(user) track := &models.Track{ID: trackID, UserID: userID, Title: "Test Track", FilePath: "/test.mp3", FileSize: 1024, Format: "MP3", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted} db.Create(track) logger := zaptest.NewLogger(t) bandwidthService := services.NewBandwidthDetectionService(logger) adaptationService := services.NewBitrateAdaptationService(db, bandwidthService, logger) // Custom router setup to inject the specific user ID gin.SetMode(gin.TestMode) router := gin.New() handler := NewBitrateHandler(adaptationService) protected := router.Group("/api/v1/tracks") protected.Use(func(c *gin.Context) { c.Set("user_id", userID) c.Next() }) protected.POST("/:id/bitrate/adapt", handler.AdaptBitrate) // Créer la requête reqBody := AdaptBitrateRequest{ CurrentBitrate: 128, Bandwidth: 10485760, // 10 Mbps BufferLevel: 0.5, } jsonBody, _ := json.Marshal(reqBody) req, _ := http.NewRequest("POST", "/api/v1/tracks/"+trackID.String()+"/bitrate/adapt", bytes.NewBuffer(jsonBody)) req.Header.Set("Content-Type", "application/json") w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) var response map[string]interface{} json.Unmarshal(w.Body.Bytes(), &response) assert.Contains(t, response, "recommended_bitrate") assert.Equal(t, float64(320), response["recommended_bitrate"]) } func TestBitrateHandler_AdaptBitrate_InvalidTrackID(t *testing.T) { db, _ := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) logger := zaptest.NewLogger(t) bandwidthService := services.NewBandwidthDetectionService(logger) adaptationService := services.NewBitrateAdaptationService(db, bandwidthService, logger) router := setupTestBitrateHandlerRouter(adaptationService) reqBody := AdaptBitrateRequest{ CurrentBitrate: 128, Bandwidth: 10485760, BufferLevel: 0.5, } jsonBody, _ := json.Marshal(reqBody) req, _ := http.NewRequest("POST", "/api/v1/tracks/invalid/bitrate/adapt", bytes.NewBuffer(jsonBody)) req.Header.Set("Content-Type", "application/json") w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) var response map[string]interface{} json.Unmarshal(w.Body.Bytes(), &response) assert.Contains(t, response["error"], "invalid track id") } func TestBitrateHandler_AdaptBitrate_Unauthorized(t *testing.T) { db, _ := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) logger := zaptest.NewLogger(t) bandwidthService := services.NewBandwidthDetectionService(logger) adaptationService := services.NewBitrateAdaptationService(db, bandwidthService, logger) gin.SetMode(gin.TestMode) router := gin.New() handler := NewBitrateHandler(adaptationService) // Route sans middleware d'authentification router.POST("/api/v1/tracks/:id/bitrate/adapt", handler.AdaptBitrate) reqBody := AdaptBitrateRequest{ CurrentBitrate: 128, Bandwidth: 10485760, BufferLevel: 0.5, } jsonBody, _ := json.Marshal(reqBody) trackID := uuid.New() req, _ := http.NewRequest("POST", "/api/v1/tracks/"+trackID.String()+"/bitrate/adapt", bytes.NewBuffer(jsonBody)) req.Header.Set("Content-Type", "application/json") w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusUnauthorized, w.Code) var response map[string]interface{} json.Unmarshal(w.Body.Bytes(), &response) assert.Equal(t, "unauthorized", response["error"]) } func TestBitrateHandler_AdaptBitrate_InvalidJSON(t *testing.T) { db, _ := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) logger := zaptest.NewLogger(t) bandwidthService := services.NewBandwidthDetectionService(logger) adaptationService := services.NewBitrateAdaptationService(db, bandwidthService, logger) router := setupTestBitrateHandlerRouter(adaptationService) trackID := uuid.New() // JSON invalide req, _ := http.NewRequest("POST", "/api/v1/tracks/"+trackID.String()+"/bitrate/adapt", bytes.NewBuffer([]byte("invalid json"))) req.Header.Set("Content-Type", "application/json") w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) } func TestBitrateHandler_AdaptBitrate_MissingFields(t *testing.T) { db, _ := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) logger := zaptest.NewLogger(t) bandwidthService := services.NewBandwidthDetectionService(logger) adaptationService := services.NewBitrateAdaptationService(db, bandwidthService, logger) router := setupTestBitrateHandlerRouter(adaptationService) // Requête avec champs manquants reqBody := map[string]interface{}{ "current_bitrate": 128, // bandwidth manquant "buffer_level": 0.5, } jsonBody, _ := json.Marshal(reqBody) trackID := uuid.New() req, _ := http.NewRequest("POST", "/api/v1/tracks/"+trackID.String()+"/bitrate/adapt", bytes.NewBuffer(jsonBody)) req.Header.Set("Content-Type", "application/json") w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) } func TestBitrateHandler_AdaptBitrate_InvalidBufferLevel(t *testing.T) { db, _ := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) db.Exec("PRAGMA foreign_keys = ON") db.AutoMigrate(&models.User{}, &models.Track{}, &models.BitrateAdaptationLog{}) userID := uuid.New() user := &models.User{ID: userID, Username: "testuser", Email: "test@example.com", IsActive: true} db.Create(user) trackID := uuid.New() track := &models.Track{ID: trackID, UserID: userID, Title: "Test Track", FilePath: "/test.mp3", FileSize: 1024, Format: "MP3", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted} db.Create(track) logger := zaptest.NewLogger(t) bandwidthService := services.NewBandwidthDetectionService(logger) adaptationService := services.NewBitrateAdaptationService(db, bandwidthService, logger) // Custom router gin.SetMode(gin.TestMode) router := gin.New() handler := NewBitrateHandler(adaptationService) protected := router.Group("/api/v1/tracks") protected.Use(func(c *gin.Context) { c.Set("user_id", userID) c.Next() }) protected.POST("/:id/bitrate/adapt", handler.AdaptBitrate) // Buffer level invalide (> 1.0) reqBody := AdaptBitrateRequest{ CurrentBitrate: 128, Bandwidth: 10485760, BufferLevel: 1.5, // Invalide } jsonBody, _ := json.Marshal(reqBody) req, _ := http.NewRequest("POST", "/api/v1/tracks/"+trackID.String()+"/bitrate/adapt", bytes.NewBuffer(jsonBody)) req.Header.Set("Content-Type", "application/json") w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) var response map[string]interface{} json.Unmarshal(w.Body.Bytes(), &response) assert.Contains(t, response["error"], "invalid buffer level") } func TestBitrateHandler_AdaptBitrate_DecreaseBitrate(t *testing.T) { db, _ := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) db.Exec("PRAGMA foreign_keys = ON") db.AutoMigrate(&models.User{}, &models.Track{}, &models.BitrateAdaptationLog{}) userID := uuid.New() user := &models.User{ID: userID, Username: "testuser", Email: "test@example.com", IsActive: true} db.Create(user) trackID := uuid.New() track := &models.Track{ID: trackID, UserID: userID, Title: "Test Track", FilePath: "/test.mp3", FileSize: 1024, Format: "MP3", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted} db.Create(track) logger := zaptest.NewLogger(t) bandwidthService := services.NewBandwidthDetectionService(logger) adaptationService := services.NewBitrateAdaptationService(db, bandwidthService, logger) // Custom router gin.SetMode(gin.TestMode) router := gin.New() handler := NewBitrateHandler(adaptationService) protected := router.Group("/api/v1/tracks") protected.Use(func(c *gin.Context) { c.Set("user_id", userID) c.Next() }) protected.POST("/:id/bitrate/adapt", handler.AdaptBitrate) // Bande passante faible qui devrait réduire le bitrate reqBody := AdaptBitrateRequest{ CurrentBitrate: 320, Bandwidth: 307200, // 300 kbps BufferLevel: 0.5, } jsonBody, _ := json.Marshal(reqBody) req, _ := http.NewRequest("POST", "/api/v1/tracks/"+trackID.String()+"/bitrate/adapt", bytes.NewBuffer(jsonBody)) req.Header.Set("Content-Type", "application/json") w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) var response map[string]interface{} json.Unmarshal(w.Body.Bytes(), &response) assert.Contains(t, response, "recommended_bitrate") assert.Equal(t, float64(192), response["recommended_bitrate"]) } func TestBitrateHandler_AdaptBitrate_LowBuffer(t *testing.T) { db, _ := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) db.Exec("PRAGMA foreign_keys = ON") db.AutoMigrate(&models.User{}, &models.Track{}, &models.BitrateAdaptationLog{}) userID := uuid.New() user := &models.User{ID: userID, Username: "testuser", Email: "test@example.com", IsActive: true} db.Create(user) trackID := uuid.New() track := &models.Track{ID: trackID, UserID: userID, Title: "Test Track", FilePath: "/test.mp3", FileSize: 1024, Format: "MP3", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted} db.Create(track) logger := zaptest.NewLogger(t) bandwidthService := services.NewBandwidthDetectionService(logger) adaptationService := services.NewBitrateAdaptationService(db, bandwidthService, logger) // Custom router gin.SetMode(gin.TestMode) router := gin.New() handler := NewBitrateHandler(adaptationService) protected := router.Group("/api/v1/tracks") protected.Use(func(c *gin.Context) { c.Set("user_id", userID) c.Next() }) protected.POST("/:id/bitrate/adapt", handler.AdaptBitrate) // Buffer faible qui devrait empêcher l'augmentation reqBody := AdaptBitrateRequest{ CurrentBitrate: 128, Bandwidth: 10485760, // 10 Mbps (recommandation: 320) BufferLevel: 0.15, // < 20%, devrait empêcher l'augmentation } jsonBody, _ := json.Marshal(reqBody) req, _ := http.NewRequest("POST", "/api/v1/tracks/"+trackID.String()+"/bitrate/adapt", bytes.NewBuffer(jsonBody)) req.Header.Set("Content-Type", "application/json") w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) var response map[string]interface{} json.Unmarshal(w.Body.Bytes(), &response) assert.Contains(t, response, "recommended_bitrate") // Le bitrate devrait rester à 128 car le buffer est faible assert.Equal(t, float64(128), response["recommended_bitrate"]) } func setupTestBitrateHandlerRouterWithAnalytics(adaptationService *services.BitrateAdaptationService) *gin.Engine { gin.SetMode(gin.TestMode) router := gin.New() handler := NewBitrateHandler(adaptationService) // Route pour analytics (pas besoin d'authentification pour analytics) router.GET("/api/v1/tracks/:id/bitrate/analytics", handler.GetAnalytics) return router } func TestBitrateHandler_GetAnalytics_Success(t *testing.T) { db, _ := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) db.Exec("PRAGMA foreign_keys = ON") db.AutoMigrate(&models.User{}, &models.Track{}, &models.BitrateAdaptationLog{}) userID := uuid.New() trackID := uuid.New() // Créer test user et track user := &models.User{ID: userID, Username: "testuser", Email: "test@example.com", IsActive: true} db.Create(user) track := &models.Track{ID: trackID, UserID: userID, Title: "Test Track", FilePath: "/test.mp3", FileSize: 1024, Format: "MP3", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted} db.Create(track) // Créer quelques logs d'adaptation log1 := &models.BitrateAdaptationLog{ TrackID: trackID, UserID: userID, OldBitrate: 128, NewBitrate: 192, Reason: models.BitrateReasonNetworkFast, NetworkBandwidth: intPtr(1048576), } db.Create(log1) log2 := &models.BitrateAdaptationLog{ TrackID: trackID, UserID: userID, OldBitrate: 192, NewBitrate: 128, Reason: models.BitrateReasonNetworkSlow, NetworkBandwidth: intPtr(307200), } db.Create(log2) log3 := &models.BitrateAdaptationLog{ TrackID: trackID, UserID: userID, OldBitrate: 128, NewBitrate: 192, Reason: models.BitrateReasonBufferLow, NetworkBandwidth: nil, } db.Create(log3) logger := zaptest.NewLogger(t) bandwidthService := services.NewBandwidthDetectionService(logger) adaptationService := services.NewBitrateAdaptationService(db, bandwidthService, logger) router := setupTestBitrateHandlerRouterWithAnalytics(adaptationService) req, _ := http.NewRequest("GET", "/api/v1/tracks/"+trackID.String()+"/bitrate/analytics", nil) w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) var response map[string]interface{} json.Unmarshal(w.Body.Bytes(), &response) assert.Contains(t, response, "analytics") analytics := response["analytics"].(map[string]interface{}) assert.Equal(t, float64(3), analytics["total_adaptations"]) reasons := analytics["reasons"].(map[string]interface{}) assert.Equal(t, float64(1), reasons[string(models.BitrateReasonNetworkFast)]) assert.Equal(t, float64(1), reasons[string(models.BitrateReasonNetworkSlow)]) assert.Equal(t, float64(1), reasons[string(models.BitrateReasonBufferLow)]) // Vérifier que adaptations_over_time existe assert.Contains(t, analytics, "adaptations_over_time") } func TestBitrateHandler_GetAnalytics_InvalidTrackID(t *testing.T) { db, _ := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) logger := zaptest.NewLogger(t) bandwidthService := services.NewBandwidthDetectionService(logger) adaptationService := services.NewBitrateAdaptationService(db, bandwidthService, logger) router := setupTestBitrateHandlerRouterWithAnalytics(adaptationService) req, _ := http.NewRequest("GET", "/api/v1/tracks/invalid/bitrate/analytics", nil) w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) var response map[string]interface{} json.Unmarshal(w.Body.Bytes(), &response) assert.Contains(t, response["error"], "invalid track id") } func TestBitrateHandler_GetAnalytics_NoAdaptations(t *testing.T) { db, _ := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) db.Exec("PRAGMA foreign_keys = ON") db.AutoMigrate(&models.User{}, &models.Track{}, &models.BitrateAdaptationLog{}) userID := uuid.New() trackID := uuid.New() user := &models.User{ID: userID, Username: "testuser", Email: "test@example.com", IsActive: true} db.Create(user) track := &models.Track{ID: trackID, UserID: userID, Title: "Test Track", FilePath: "/test.mp3", FileSize: 1024, Format: "MP3", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted} db.Create(track) logger := zaptest.NewLogger(t) bandwidthService := services.NewBandwidthDetectionService(logger) adaptationService := services.NewBitrateAdaptationService(db, bandwidthService, logger) router := setupTestBitrateHandlerRouterWithAnalytics(adaptationService) req, _ := http.NewRequest("GET", "/api/v1/tracks/"+trackID.String()+"/bitrate/analytics", nil) w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) var response map[string]interface{} json.Unmarshal(w.Body.Bytes(), &response) analytics := response["analytics"].(map[string]interface{}) assert.Equal(t, float64(0), analytics["total_adaptations"]) reasons := analytics["reasons"].(map[string]interface{}) assert.Empty(t, reasons) } func TestBitrateHandler_GetAnalytics_ZeroTrackID(t *testing.T) { db, _ := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) logger := zaptest.NewLogger(t) bandwidthService := services.NewBandwidthDetectionService(logger) adaptationService := services.NewBitrateAdaptationService(db, bandwidthService, logger) router := setupTestBitrateHandlerRouterWithAnalytics(adaptationService) // Using a Nil UUID to simulate "zero" or invalid specific UUID req, _ := http.NewRequest("GET", "/api/v1/tracks/"+uuid.Nil.String()+"/bitrate/analytics", nil) w := httptest.NewRecorder() router.ServeHTTP(w, req) // It might be 400 or 404 or 500 depending on handler implementation, but here likely 400 // In original test it was testing "0" which was invalid parse. uuid.Nil is valid UUID but might be rejected by logic. // But here the handler parses it. If it parses successfully, it goes to logic. // Let's check the original test expectation: 400. // If I pass uuid.Nil, it parses. // I should probably pass "00000000-0000-0000-0000-000000000000" (Nil). // The handler checks: if err != nil ... // If I pass "0", uuid.Parse returns error, so 400. // So I can keep passing "0" string if I want to test parse error. // Or use uuid.Nil if I want to test logic error. // The original test used "0" which fails parsing for UUID. // So I will use "0" string which causes uuid.Parse to fail. req, _ = http.NewRequest("GET", "/api/v1/tracks/0/bitrate/analytics", nil) w = httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) var response map[string]interface{} json.Unmarshal(w.Body.Bytes(), &response) assert.Contains(t, response["error"], "invalid track id") } func intPtr(i int) *int { return &i }