package services import ( "context" "testing" "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap/zaptest" "gorm.io/driver/sqlite" "gorm.io/gorm" "veza-backend-api/internal/models" ) func setupTestBitrateAdaptationServiceDB(t *testing.T) (*gorm.DB, uuid.UUID, uuid.UUID) { db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) require.NoError(t, err) // Enable foreign keys for SQLite db.Exec("PRAGMA foreign_keys = ON") // Auto-migrate err = db.AutoMigrate(&models.User{}, &models.Track{}, &models.BitrateAdaptationLog{}) require.NoError(t, err) userID := uuid.New() // Create test user user := &models.User{ ID: userID, Username: "testuser", Email: "test@example.com", IsActive: true, } err = db.Create(user).Error require.NoError(t, err) // Create test track trackID := uuid.New() track := &models.Track{ ID: trackID, UserID: userID, Title: "Test Track", FilePath: "/test/track.mp3", FileSize: 5 * 1024 * 1024, Format: "MP3", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted, } err = db.Create(track).Error require.NoError(t, err) return db, userID, trackID } func TestNewBitrateAdaptationService(t *testing.T) { db, _, _ := setupTestBitrateAdaptationServiceDB(t) logger := zaptest.NewLogger(t) bandwidthService := NewBandwidthDetectionService(logger) service := NewBitrateAdaptationService(db, bandwidthService, logger) assert.NotNil(t, service) assert.Equal(t, db, service.db) assert.Equal(t, bandwidthService, service.bandwidthService) assert.NotNil(t, service.logger) } func TestNewBitrateAdaptationService_NilLogger(t *testing.T) { db, _, _ := setupTestBitrateAdaptationServiceDB(t) bandwidthService := NewBandwidthDetectionService(nil) service := NewBitrateAdaptationService(db, bandwidthService, nil) assert.NotNil(t, service) assert.NotNil(t, service.logger) } func TestBitrateAdaptationService_AdaptBitrate_NoChange(t *testing.T) { db, userID, trackID := setupTestBitrateAdaptationServiceDB(t) logger := zaptest.NewLogger(t) bandwidthService := NewBandwidthDetectionService(logger) service := NewBitrateAdaptationService(db, bandwidthService, logger) ctx := context.Background() // Test avec bitrate qui ne change pas // Bande passante de 2 Mbps = 2097152 bps, avec buffer 20% = 1677 kbps disponible // Recommandation: 320 kbps // Current: 320 kbps, donc pas de changement newBitrate, err := service.AdaptBitrate(ctx, trackID, userID, 320, 2097152, 0.5) require.NoError(t, err) assert.Equal(t, 320, newBitrate) // Vérifier qu'aucun log n'a été créé var count int64 db.Model(&models.BitrateAdaptationLog{}).Count(&count) assert.Equal(t, int64(0), count) } func TestBitrateAdaptationService_AdaptBitrate_Increase(t *testing.T) { db, userID, trackID := setupTestBitrateAdaptationServiceDB(t) logger := zaptest.NewLogger(t) bandwidthService := NewBandwidthDetectionService(logger) service := NewBitrateAdaptationService(db, bandwidthService, logger) ctx := context.Background() // Test avec augmentation de bitrate // Bande passante de 10 Mbps = 10485760 bps, avec buffer 20% = 8388 kbps disponible // Recommandation: 320 kbps // Current: 128 kbps, buffer: 0.5 (50%) newBitrate, err := service.AdaptBitrate(ctx, trackID, userID, 128, 10485760, 0.5) require.NoError(t, err) assert.Equal(t, 320, newBitrate) // Vérifier qu'un log a été créé var log models.BitrateAdaptationLog err = db.First(&log).Error require.NoError(t, err) assert.Equal(t, trackID, log.TrackID) assert.Equal(t, userID, log.UserID) assert.Equal(t, 128, log.OldBitrate) assert.Equal(t, 320, log.NewBitrate) assert.Equal(t, models.BitrateReasonNetworkFast, log.Reason) assert.NotNil(t, log.NetworkBandwidth) } func TestBitrateAdaptationService_AdaptBitrate_Decrease(t *testing.T) { db, userID, trackID := setupTestBitrateAdaptationServiceDB(t) logger := zaptest.NewLogger(t) bandwidthService := NewBandwidthDetectionService(logger) service := NewBitrateAdaptationService(db, bandwidthService, logger) ctx := context.Background() // Test avec diminution de bitrate // Bande passante de 300 kbps = 307200 bps, avec buffer 20% = 245 kbps disponible // Recommandation: 192 kbps // Current: 320 kbps, buffer: 0.5 (50%) newBitrate, err := service.AdaptBitrate(ctx, trackID, userID, 320, 307200, 0.5) require.NoError(t, err) assert.Equal(t, 192, newBitrate) // Vérifier qu'un log a été créé var log models.BitrateAdaptationLog err = db.First(&log).Error require.NoError(t, err) assert.Equal(t, 320, log.OldBitrate) assert.Equal(t, 192, log.NewBitrate) assert.Equal(t, models.BitrateReasonNetworkSlow, log.Reason) } func TestBitrateAdaptationService_AdaptBitrate_LowBuffer_PreventIncrease(t *testing.T) { db, userID, trackID := setupTestBitrateAdaptationServiceDB(t) logger := zaptest.NewLogger(t) bandwidthService := NewBandwidthDetectionService(logger) service := NewBitrateAdaptationService(db, bandwidthService, logger) ctx := context.Background() // Test avec buffer faible qui empêche l'augmentation // Bande passante de 10 Mbps = 10485760 bps, recommandation: 320 kbps // Current: 128 kbps, buffer: 0.15 (15% < 20%) // L'augmentation devrait être bloquée newBitrate, err := service.AdaptBitrate(ctx, trackID, userID, 128, 10485760, 0.15) require.NoError(t, err) assert.Equal(t, 128, newBitrate) // Pas d'augmentation // Vérifier qu'aucun log n'a été créé (pas de changement) var count int64 db.Model(&models.BitrateAdaptationLog{}).Count(&count) assert.Equal(t, int64(0), count) } func TestBitrateAdaptationService_AdaptBitrate_VeryLowBuffer_ForceDecrease(t *testing.T) { db, userID, trackID := setupTestBitrateAdaptationServiceDB(t) logger := zaptest.NewLogger(t) bandwidthService := NewBandwidthDetectionService(logger) service := NewBitrateAdaptationService(db, bandwidthService, logger) ctx := context.Background() // Test avec buffer très faible qui force la diminution // Bande passante de 10 Mbps = 10485760 bps, recommandation: 320 kbps // Current: 320 kbps, buffer: 0.05 (5% < 10%) // La diminution devrait être forcée newBitrate, err := service.AdaptBitrate(ctx, trackID, userID, 320, 10485760, 0.05) require.NoError(t, err) assert.Equal(t, 192, newBitrate) // Diminution forcée // Vérifier qu'un log a été créé var log models.BitrateAdaptationLog err = db.First(&log).Error require.NoError(t, err) assert.Equal(t, 320, log.OldBitrate) assert.Equal(t, 192, log.NewBitrate) assert.Equal(t, models.BitrateReasonBufferLow, log.Reason) } func TestBitrateAdaptationService_AdaptBitrate_VeryLowBuffer_192to128(t *testing.T) { db, userID, trackID := setupTestBitrateAdaptationServiceDB(t) logger := zaptest.NewLogger(t) bandwidthService := NewBandwidthDetectionService(logger) service := NewBitrateAdaptationService(db, bandwidthService, logger) ctx := context.Background() // Test avec buffer très faible, passage de 192 à 128 newBitrate, err := service.AdaptBitrate(ctx, trackID, userID, 192, 10485760, 0.05) require.NoError(t, err) assert.Equal(t, 128, newBitrate) // Vérifier qu'un log a été créé var log models.BitrateAdaptationLog err = db.First(&log).Error require.NoError(t, err) assert.Equal(t, 192, log.OldBitrate) assert.Equal(t, 128, log.NewBitrate) } func TestBitrateAdaptationService_AdaptBitrate_InvalidParameters(t *testing.T) { db, userID, trackID := setupTestBitrateAdaptationServiceDB(t) logger := zaptest.NewLogger(t) bandwidthService := NewBandwidthDetectionService(logger) service := NewBitrateAdaptationService(db, bandwidthService, logger) ctx := context.Background() // Test avec trackID invalide _, err := service.AdaptBitrate(ctx, uuid.Nil, userID, 128, 10485760, 0.5) assert.Error(t, err) assert.Contains(t, err.Error(), "invalid track ID") // Test avec userID invalide _, err = service.AdaptBitrate(ctx, trackID, uuid.Nil, 128, 10485760, 0.5) assert.Error(t, err) assert.Contains(t, err.Error(), "invalid user ID") // Test avec currentBitrate invalide _, err = service.AdaptBitrate(ctx, trackID, userID, 0, 10485760, 0.5) assert.Error(t, err) assert.Contains(t, err.Error(), "invalid current bitrate") // Test avec bufferLevel invalide (négatif) _, err = service.AdaptBitrate(ctx, trackID, userID, 128, 10485760, -0.1) assert.Error(t, err) assert.Contains(t, err.Error(), "invalid buffer level") // Test avec bufferLevel invalide (> 1.0) _, err = service.AdaptBitrate(ctx, trackID, userID, 128, 10485760, 1.5) assert.Error(t, err) assert.Contains(t, err.Error(), "invalid buffer level") } func TestBitrateAdaptationService_DetermineReason(t *testing.T) { db, _, _ := setupTestBitrateAdaptationServiceDB(t) logger := zaptest.NewLogger(t) bandwidthService := NewBandwidthDetectionService(logger) service := NewBitrateAdaptationService(db, bandwidthService, logger) // Test avec buffer faible reason := service.determineReason(128, 320, 0.15) assert.Equal(t, models.BitrateReasonBufferLow, reason) // Test avec augmentation (buffer normal) reason = service.determineReason(128, 320, 0.5) assert.Equal(t, models.BitrateReasonNetworkFast, reason) // Test avec diminution (buffer normal) reason = service.determineReason(320, 192, 0.5) assert.Equal(t, models.BitrateReasonNetworkSlow, reason) // Test avec buffer faible mais augmentation reason = service.determineReason(128, 192, 0.15) assert.Equal(t, models.BitrateReasonBufferLow, reason) } func TestBitrateAdaptationService_AdaptBitrate_MultipleAdaptations(t *testing.T) { db, userID, trackID := setupTestBitrateAdaptationServiceDB(t) logger := zaptest.NewLogger(t) bandwidthService := NewBandwidthDetectionService(logger) service := NewBitrateAdaptationService(db, bandwidthService, logger) ctx := context.Background() // Première adaptation: 128 -> 192 // Bande passante de 300 kbps = 307200 bps, avec buffer 20% = 245 kbps disponible // Recommandation: 192 kbps newBitrate, err := service.AdaptBitrate(ctx, trackID, userID, 128, 307200, 0.5) require.NoError(t, err) assert.Equal(t, 192, newBitrate) // Deuxième adaptation: 192 -> 320 // Bande passante de 10 Mbps = 10485760 bps, avec buffer 20% = 8388 kbps disponible // Recommandation: 320 kbps newBitrate, err = service.AdaptBitrate(ctx, trackID, userID, 192, 10485760, 0.5) require.NoError(t, err) assert.Equal(t, 320, newBitrate) // Vérifier qu'il y a 2 logs var count int64 db.Model(&models.BitrateAdaptationLog{}).Count(&count) assert.Equal(t, int64(2), count) } func TestBitrateAdaptationService_AdaptBitrate_EdgeCases(t *testing.T) { db, userID, trackID := setupTestBitrateAdaptationServiceDB(t) logger := zaptest.NewLogger(t) bandwidthService := NewBandwidthDetectionService(logger) service := NewBitrateAdaptationService(db, bandwidthService, logger) ctx := context.Background() // Test avec buffer exactement à 20% newBitrate, err := service.AdaptBitrate(ctx, trackID, userID, 128, 10485760, 0.2) require.NoError(t, err) // À 20%, l'augmentation devrait être permise assert.Equal(t, 320, newBitrate) // Nettoyer les logs précédents db.Exec("DELETE FROM bitrate_adaptation_logs") // Test avec buffer exactement à 10% newBitrate, err = service.AdaptBitrate(ctx, trackID, userID, 320, 10485760, 0.1) require.NoError(t, err) // À 10%, la diminution devrait être forcée assert.Equal(t, 192, newBitrate) // Nettoyer les logs précédents db.Exec("DELETE FROM bitrate_adaptation_logs") // Test avec buffer à 0% newBitrate, err = service.AdaptBitrate(ctx, trackID, userID, 320, 10485760, 0.0) require.NoError(t, err) assert.Equal(t, 192, newBitrate) // Nettoyer les logs précédents db.Exec("DELETE FROM bitrate_adaptation_logs") // Test avec buffer à 100% newBitrate, err = service.AdaptBitrate(ctx, trackID, userID, 128, 10485760, 1.0) require.NoError(t, err) assert.Equal(t, 320, newBitrate) } func TestBitrateAdaptationService_AdaptBitrate_LogCreationFailure(t *testing.T) { // Créer une DB qui va échouer lors de la création // On utilise une table qui n'existe pas pour simuler l'erreur db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) require.NoError(t, err) // Ne pas créer la table bitrate_adaptation_logs pour simuler une erreur // Mais on doit créer User et Track pour que les foreign keys fonctionnent err = db.AutoMigrate(&models.User{}, &models.Track{}) require.NoError(t, err) userID := uuid.New() // Create test user user := &models.User{ ID: userID, Username: "testuser", Email: "test@example.com", IsActive: true, } require.NoError(t, db.Create(user).Error) // Create test track track := &models.Track{ UserID: userID, Title: "Test Track", FilePath: "/test/track.mp3", FileSize: 5 * 1024 * 1024, Format: "MP3", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted, } require.NoError(t, db.Create(track).Error) logger := zaptest.NewLogger(t) bandwidthService := NewBandwidthDetectionService(logger) service := NewBitrateAdaptationService(db, bandwidthService, logger) ctx := context.Background() // L'adaptation devrait quand même fonctionner même si le log échoue newBitrate, err := service.AdaptBitrate(ctx, track.ID, userID, 128, 10485760, 0.5) // L'adaptation ne devrait pas retourner d'erreur même si le log échoue require.NoError(t, err) assert.Equal(t, 320, newBitrate) }