package services import ( "context" "github.com/google/uuid" "testing" "time" "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 setupTestPlaybackSegmentationServiceDB(t *testing.T) (*gorm.DB, *PlaybackSegmentationService) { db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) require.NoError(t, err) db.Exec("PRAGMA foreign_keys = ON") err = db.AutoMigrate(&models.User{}, &models.Track{}, &models.PlaybackAnalytics{}) require.NoError(t, err) logger := zaptest.NewLogger(t) service := NewPlaybackSegmentationService(db, logger) return db, service } func TestNewPlaybackSegmentationService(t *testing.T) { db, _ := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) logger := zaptest.NewLogger(t) service := NewPlaybackSegmentationService(db, logger) assert.NotNil(t, service) assert.Equal(t, db, service.db) assert.NotNil(t, service.logger) } func TestNewPlaybackSegmentationService_NilLogger(t *testing.T) { db, _ := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) service := NewPlaybackSegmentationService(db, nil) assert.NotNil(t, service) assert.NotNil(t, service.logger) } func TestPlaybackSegmentationService_SegmentUsers_NoSessions(t *testing.T) { db, service := setupTestPlaybackSegmentationServiceDB(t) ctx := context.Background() // Créer user et track 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) result, err := service.SegmentUsers(ctx, trackID) require.NoError(t, err) assert.NotNil(t, result) assert.Equal(t, trackID, result.TrackID) assert.Equal(t, int64(0), result.TotalUsers) assert.NotNil(t, result.Segments) assert.NotNil(t, result.UserMetrics) } func TestPlaybackSegmentationService_SegmentUsers_InvalidTrackID(t *testing.T) { _, service := setupTestPlaybackSegmentationServiceDB(t) ctx := context.Background() result, err := service.SegmentUsers(ctx, uuid.Nil) assert.Error(t, err) assert.Contains(t, err.Error(), "invalid track ID") assert.Nil(t, result) } func TestPlaybackSegmentationService_SegmentUsers_TrackNotFound(t *testing.T) { _, service := setupTestPlaybackSegmentationServiceDB(t) ctx := context.Background() result, err := service.SegmentUsers(ctx, uuid.New()) assert.Error(t, err) assert.Contains(t, err.Error(), "track not found") assert.Nil(t, result) } func TestPlaybackSegmentationService_SegmentUsers_WithSessions(t *testing.T) { db, service := setupTestPlaybackSegmentationServiceDB(t) ctx := context.Background() // Créer users et track user1ID := uuid.New() user2ID := uuid.New() trackID := uuid.New() user1 := &models.User{ID: user1ID, Username: "user1", Slug: "user1", Email: "user1@example.com", IsActive: true} user2 := &models.User{ID: user2ID, Username: "user2", Slug: "user2", Email: "user2@example.com", IsActive: true} db.Create(user1) db.Create(user2) track := &models.Track{ ID: trackID, UserID: user1ID, Title: "Test Track", FilePath: "/test.mp3", FileSize: 1024, Format: "MP3", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted, } db.Create(track) // Créer des analytics avec différents niveaux d'engagement now := time.Now() // User 1: High engagement (completion élevé, peu de pauses/seeks) analytics1 := &models.PlaybackAnalytics{ TrackID: trackID, UserID: user1ID, PlayTime: 180, PauseCount: 0, SeekCount: 0, CompletionRate: 100.0, StartedAt: now, CreatedAt: now, } analytics2 := &models.PlaybackAnalytics{ TrackID: trackID, UserID: user1ID, PlayTime: 180, PauseCount: 1, SeekCount: 0, CompletionRate: 95.0, StartedAt: now, CreatedAt: now, } // User 2: Low engagement (completion faible, beaucoup de pauses/seeks) analytics3 := &models.PlaybackAnalytics{ TrackID: trackID, UserID: user2ID, PlayTime: 45, PauseCount: 5, SeekCount: 3, CompletionRate: 25.0, StartedAt: now, CreatedAt: now, } db.Create(analytics1) db.Create(analytics2) db.Create(analytics3) result, err := service.SegmentUsers(ctx, trackID) require.NoError(t, err) assert.NotNil(t, result) assert.Equal(t, trackID, result.TrackID) assert.Equal(t, int64(2), result.TotalUsers) assert.NotNil(t, result.Segments) assert.Greater(t, len(result.Segments), 0) // Vérifier que les segments sont créés assert.Contains(t, result.Segments, SegmentHighEngagement) assert.Contains(t, result.Segments, SegmentLowEngagement) } func TestPlaybackSegmentationService_SegmentByEngagement(t *testing.T) { _, service := setupTestPlaybackSegmentationServiceDB(t) user1ID := uuid.New() user2ID := uuid.New() user3ID := uuid.New() userMetrics := map[uuid.UUID]*UserMetrics{ user1ID: {UserID: user1ID, EngagementScore: 85.0}, // High user2ID: {UserID: user2ID, EngagementScore: 60.0}, // Medium user3ID: {UserID: user3ID, EngagementScore: 30.0}, // Low } segments := service.segmentByEngagement(userMetrics) assert.Contains(t, segments, SegmentHighEngagement) assert.Contains(t, segments, SegmentMediumEngagement) assert.Contains(t, segments, SegmentLowEngagement) assert.Contains(t, segments[SegmentHighEngagement], user1ID) assert.Contains(t, segments[SegmentMediumEngagement], user2ID) assert.Contains(t, segments[SegmentLowEngagement], user3ID) } func TestPlaybackSegmentationService_SegmentByCompletionRate(t *testing.T) { _, service := setupTestPlaybackSegmentationServiceDB(t) user1ID := uuid.New() user2ID := uuid.New() user3ID := uuid.New() userMetrics := map[uuid.UUID]*UserMetrics{ user1ID: {UserID: user1ID, AverageCompletion: 90.0}, // High user2ID: {UserID: user2ID, AverageCompletion: 60.0}, // Medium user3ID: {UserID: user3ID, AverageCompletion: 30.0}, // Low } segments := service.segmentByCompletionRate(userMetrics) assert.Contains(t, segments, SegmentHighCompletion) assert.Contains(t, segments, SegmentMediumCompletion) assert.Contains(t, segments, SegmentLowCompletion) assert.Contains(t, segments[SegmentHighCompletion], user1ID) assert.Contains(t, segments[SegmentMediumCompletion], user2ID) assert.Contains(t, segments[SegmentLowCompletion], user3ID) } func TestPlaybackSegmentationService_SegmentByBehavior(t *testing.T) { _, service := setupTestPlaybackSegmentationServiceDB(t) user1ID := uuid.New() user2ID := uuid.New() user3ID := uuid.New() user4ID := uuid.New() userMetrics := map[uuid.UUID]*UserMetrics{ user1ID: {UserID: user1ID, SessionCount: 10, AverageSeeks: 0.5, AverageCompletion: 80.0}, // Active + Focused user2ID: {UserID: user2ID, SessionCount: 1, AverageSeeks: 0.2, AverageCompletion: 75.0}, // Casual + Focused user3ID: {UserID: user3ID, SessionCount: 5, AverageSeeks: 5.0, AverageCompletion: 50.0}, // Frequent skipper user4ID: {UserID: user4ID, SessionCount: 2, AverageSeeks: 0.1, AverageCompletion: 60.0}, // Casual } segments := service.segmentByBehavior(userMetrics) assert.Contains(t, segments, SegmentActiveListener) assert.Contains(t, segments, SegmentCasualListener) assert.Contains(t, segments, SegmentFrequentSkipper) assert.Contains(t, segments, SegmentFocusedListener) } func TestPlaybackSegmentationService_CalculateUserMetrics(t *testing.T) { _, service := setupTestPlaybackSegmentationServiceDB(t) user1ID := uuid.New() user2ID := uuid.New() analytics := []models.PlaybackAnalytics{ {UserID: user1ID, PlayTime: 180, PauseCount: 0, SeekCount: 0, CompletionRate: 100.0}, {UserID: user1ID, PlayTime: 180, PauseCount: 1, SeekCount: 0, CompletionRate: 95.0}, {UserID: user2ID, PlayTime: 45, PauseCount: 5, SeekCount: 3, CompletionRate: 25.0}, } userMetrics := service.calculateUserMetrics(analytics) assert.Equal(t, 2, len(userMetrics)) assert.Contains(t, userMetrics, user1ID) assert.Contains(t, userMetrics, user2ID) // Vérifier les métriques de l'utilisateur 1 metrics1 := userMetrics[user1ID] assert.Equal(t, int64(2), metrics1.SessionCount) assert.InDelta(t, 97.5, metrics1.AverageCompletion, 0.1) // (100 + 95) / 2 assert.InDelta(t, 180.0, metrics1.AveragePlayTime, 0.1) assert.InDelta(t, 0.5, metrics1.AveragePauses, 0.1) // (0 + 1) / 2 = 0.5 assert.Equal(t, 0.0, metrics1.AverageSeeks) assert.Greater(t, metrics1.EngagementScore, 75.0) // High engagement // Vérifier les métriques de l'utilisateur 2 metrics2 := userMetrics[user2ID] assert.Equal(t, int64(1), metrics2.SessionCount) assert.Equal(t, 25.0, metrics2.AverageCompletion) assert.Equal(t, 5.0, metrics2.AveragePauses) assert.Equal(t, 3.0, metrics2.AverageSeeks) assert.Less(t, metrics2.EngagementScore, 50.0) // Low engagement } func TestPlaybackSegmentationService_GetUserSegment(t *testing.T) { db, service := setupTestPlaybackSegmentationServiceDB(t) ctx := context.Background() // Créer user et track 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) // Créer analytics avec high engagement now := time.Now() analytics := &models.PlaybackAnalytics{ TrackID: trackID, UserID: userID, PlayTime: 180, PauseCount: 0, SeekCount: 0, CompletionRate: 100.0, StartedAt: now, CreatedAt: now, } db.Create(analytics) segment, err := service.GetUserSegment(ctx, trackID, userID) require.NoError(t, err) assert.Equal(t, SegmentHighEngagement, segment) } func TestPlaybackSegmentationService_GetUserSegment_InvalidIDs(t *testing.T) { _, service := setupTestPlaybackSegmentationServiceDB(t) ctx := context.Background() segment, err := service.GetUserSegment(ctx, uuid.Nil, uuid.New()) assert.Error(t, err) assert.Contains(t, err.Error(), "invalid track ID or user ID") assert.Equal(t, UserSegment(""), segment) segment, err = service.GetUserSegment(ctx, uuid.New(), uuid.Nil) assert.Error(t, err) assert.Contains(t, err.Error(), "invalid track ID or user ID") assert.Equal(t, UserSegment(""), segment) } func TestPlaybackSegmentationService_GetUserSegment_UserNotFound(t *testing.T) { db, service := setupTestPlaybackSegmentationServiceDB(t) ctx := context.Background() // Créer user et track 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) unknownUserID := uuid.New() segment, err := service.GetUserSegment(ctx, trackID, unknownUserID) assert.Error(t, err) assert.Contains(t, err.Error(), "user "+unknownUserID.String()+" not found") assert.Equal(t, UserSegment(""), segment) } func TestPlaybackSegmentationService_SegmentUsers_AllSegments(t *testing.T) { db, service := setupTestPlaybackSegmentationServiceDB(t) ctx := context.Background() // Créer plusieurs users avec différents comportements user1ID := uuid.New() user2ID := uuid.New() user3ID := uuid.New() user4ID := uuid.New() users := []*models.User{ {ID: user1ID, Username: "user1", Slug: "user1", Email: "user1@example.com", IsActive: true}, {ID: user2ID, Username: "user2", Slug: "user2", Email: "user2@example.com", IsActive: true}, {ID: user3ID, Username: "user3", Slug: "user3", Email: "user3@example.com", IsActive: true}, {ID: user4ID, Username: "user4", Slug: "user4", Email: "user4@example.com", IsActive: true}, } for _, u := range users { db.Create(u) } trackID := uuid.New() track := &models.Track{ ID: trackID, UserID: user1ID, Title: "Test Track", FilePath: "/test.mp3", FileSize: 1024, Format: "MP3", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted, } db.Create(track) now := time.Now() // User 1: High engagement, high completion, active, focused for i := 0; i < 5; i++ { db.Create(&models.PlaybackAnalytics{ TrackID: trackID, UserID: user1ID, PlayTime: 180, PauseCount: 0, SeekCount: 0, CompletionRate: 100.0, StartedAt: now, CreatedAt: now, }) } // User 2: Medium engagement, medium completion, casual db.Create(&models.PlaybackAnalytics{ TrackID: trackID, UserID: user2ID, PlayTime: 90, PauseCount: 2, SeekCount: 1, CompletionRate: 50.0, StartedAt: now, CreatedAt: now, }) // User 3: Low engagement, low completion, frequent skipper for i := 0; i < 3; i++ { db.Create(&models.PlaybackAnalytics{ TrackID: trackID, UserID: user3ID, PlayTime: 30, PauseCount: 5, SeekCount: 5, CompletionRate: 15.0, StartedAt: now, CreatedAt: now, }) } // User 4: High engagement, high completion, casual db.Create(&models.PlaybackAnalytics{ TrackID: trackID, UserID: user4ID, PlayTime: 180, PauseCount: 0, SeekCount: 0, CompletionRate: 100.0, StartedAt: now, CreatedAt: now, }) result, err := service.SegmentUsers(ctx, trackID) require.NoError(t, err) assert.NotNil(t, result) assert.Equal(t, int64(4), result.TotalUsers) // Vérifier que tous les segments sont présents assert.Contains(t, result.Segments, SegmentHighEngagement) assert.Contains(t, result.Segments, SegmentMediumEngagement) assert.Contains(t, result.Segments, SegmentLowEngagement) assert.Contains(t, result.Segments, SegmentHighCompletion) assert.Contains(t, result.Segments, SegmentMediumCompletion) assert.Contains(t, result.Segments, SegmentLowCompletion) assert.Contains(t, result.Segments, SegmentActiveListener) assert.Contains(t, result.Segments, SegmentCasualListener) assert.Contains(t, result.Segments, SegmentFrequentSkipper) assert.Contains(t, result.Segments, SegmentFocusedListener) // Vérifier les compteurs assert.Greater(t, result.SegmentCounts[SegmentHighEngagement], int64(0)) assert.Greater(t, result.SegmentCounts[SegmentLowEngagement], int64(0)) }