package services import ( "context" "testing" "time" "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 setupTestPlaybackAggregationServiceDB(t *testing.T) *gorm.DB { 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) return db } func TestNewPlaybackAggregationService(t *testing.T) { db := setupTestPlaybackAggregationServiceDB(t) logger := zaptest.NewLogger(t) service := NewPlaybackAggregationService(db, logger) assert.NotNil(t, service) assert.Equal(t, db, service.db) } func TestPlaybackAggregationService_AggregateByPeriod_Day(t *testing.T) { db := setupTestPlaybackAggregationServiceDB(t) logger := zaptest.NewLogger(t) service := NewPlaybackAggregationService(db, logger) // Créer test user et track 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) // Créer des sessions sur différentes dates now := time.Now() sessions := []models.PlaybackAnalytics{ { TrackID: trackID, UserID: userID, PlayTime: 120, PauseCount: 2, SeekCount: 3, CompletionRate: 75.0, StartedAt: now.AddDate(0, 0, -2), CreatedAt: now.AddDate(0, 0, -2), }, { TrackID: trackID, UserID: userID, PlayTime: 150, PauseCount: 1, SeekCount: 2, CompletionRate: 90.0, StartedAt: now.AddDate(0, 0, -2), CreatedAt: now.AddDate(0, 0, -2), }, { TrackID: trackID, UserID: userID, PlayTime: 100, PauseCount: 3, SeekCount: 1, CompletionRate: 60.0, StartedAt: now.AddDate(0, 0, -1), CreatedAt: now.AddDate(0, 0, -1), }, } for _, session := range sessions { db.Create(&session) } startDate := now.AddDate(0, 0, -3) endDate := now result, err := service.AggregateByPeriod(context.Background(), trackID, PeriodDay, startDate, endDate) require.NoError(t, err) assert.NotNil(t, result) assert.Equal(t, int64(3), result.TotalSessions) assert.Equal(t, int64(370), result.TotalPlayTime) assert.InDelta(t, 123.33, result.AveragePlayTime, 0.1) // Vérifier qu'il y a 2 périodes (2 jours différents) assert.Len(t, result.Periods, 2) // Vérifier la première période (jour -2) period1 := result.Periods[0] assert.Equal(t, int64(2), period1.Sessions) assert.Equal(t, int64(270), period1.TotalPlayTime) assert.InDelta(t, 135.0, period1.AveragePlayTime, 0.1) } func TestPlaybackAggregationService_AggregateByPeriod_Week(t *testing.T) { db := setupTestPlaybackAggregationServiceDB(t) logger := zaptest.NewLogger(t) service := NewPlaybackAggregationService(db, logger) user := &models.User{ID: uuid.New(), Username: "testuser", Email: "test@example.com", IsActive: true} db.Create(user) track := &models.Track{ ID: uuid.New(), UserID: user.ID, Title: "Test Track", FilePath: "/test.mp3", FileSize: 1024, Format: "MP3", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted, } db.Create(track) now := time.Now() startDate := now.AddDate(0, 0, -14) endDate := now // Créer des sessions dans différentes semaines sessions := []models.PlaybackAnalytics{ { TrackID: track.ID, UserID: user.ID, PlayTime: 120, PauseCount: 2, SeekCount: 3, CompletionRate: 75.0, StartedAt: startDate.AddDate(0, 0, 1), CreatedAt: startDate.AddDate(0, 0, 1), }, { TrackID: track.ID, UserID: user.ID, PlayTime: 150, PauseCount: 1, SeekCount: 2, CompletionRate: 90.0, StartedAt: startDate.AddDate(0, 0, 8), CreatedAt: startDate.AddDate(0, 0, 8), }, } for _, session := range sessions { db.Create(&session) } result, err := service.AggregateByPeriod(context.Background(), track.ID, PeriodWeek, startDate, endDate) require.NoError(t, err) assert.NotNil(t, result) assert.Equal(t, int64(2), result.TotalSessions) } func TestPlaybackAggregationService_AggregateByPeriod_Month(t *testing.T) { db := setupTestPlaybackAggregationServiceDB(t) logger := zaptest.NewLogger(t) service := NewPlaybackAggregationService(db, logger) user := &models.User{ID: uuid.New(), Username: "testuser", Email: "test@example.com", IsActive: true} db.Create(user) track := &models.Track{ ID: uuid.New(), UserID: user.ID, Title: "Test Track", FilePath: "/test.mp3", FileSize: 1024, Format: "MP3", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted, } db.Create(track) now := time.Now() startDate := now.AddDate(0, -2, 0) endDate := now // Créer des sessions dans différents mois sessions := []models.PlaybackAnalytics{ { TrackID: track.ID, UserID: user.ID, PlayTime: 120, PauseCount: 2, SeekCount: 3, CompletionRate: 75.0, StartedAt: startDate.AddDate(0, 0, 1), CreatedAt: startDate.AddDate(0, 0, 1), }, { TrackID: track.ID, UserID: user.ID, PlayTime: 150, PauseCount: 1, SeekCount: 2, CompletionRate: 90.0, StartedAt: startDate.AddDate(0, 1, 0), CreatedAt: startDate.AddDate(0, 1, 0), }, } for _, session := range sessions { db.Create(&session) } result, err := service.AggregateByPeriod(context.Background(), track.ID, PeriodMonth, startDate, endDate) require.NoError(t, err) assert.NotNil(t, result) assert.Equal(t, int64(2), result.TotalSessions) } func TestPlaybackAggregationService_AggregateByPeriod_InvalidTrackID(t *testing.T) { db := setupTestPlaybackAggregationServiceDB(t) logger := zaptest.NewLogger(t) service := NewPlaybackAggregationService(db, logger) now := time.Now() startDate := now.AddDate(0, 0, -7) endDate := now _, err := service.AggregateByPeriod(context.Background(), uuid.Nil, PeriodDay, startDate, endDate) assert.Error(t, err) assert.Contains(t, err.Error(), "invalid track ID") } func TestPlaybackAggregationService_AggregateByPeriod_TrackNotFound(t *testing.T) { db := setupTestPlaybackAggregationServiceDB(t) logger := zaptest.NewLogger(t) service := NewPlaybackAggregationService(db, logger) now := time.Now() startDate := now.AddDate(0, 0, -7) endDate := now _, err := service.AggregateByPeriod(context.Background(), uuid.New(), PeriodDay, startDate, endDate) assert.Error(t, err) assert.Contains(t, err.Error(), "track not found") } func TestPlaybackAggregationService_AggregateByPeriod_InvalidPeriod(t *testing.T) { db := setupTestPlaybackAggregationServiceDB(t) logger := zaptest.NewLogger(t) service := NewPlaybackAggregationService(db, logger) user := &models.User{ID: uuid.New(), Username: "testuser", Email: "test@example.com", IsActive: true} db.Create(user) track := &models.Track{ ID: uuid.New(), UserID: user.ID, Title: "Test Track", FilePath: "/test.mp3", FileSize: 1024, Format: "MP3", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted, } db.Create(track) now := time.Now() startDate := now.AddDate(0, 0, -7) endDate := now _, err := service.AggregateByPeriod(context.Background(), track.ID, PeriodType("invalid"), startDate, endDate) assert.Error(t, err) assert.Contains(t, err.Error(), "invalid period type") } func TestPlaybackAggregationService_AggregateByPeriod_NoData(t *testing.T) { db := setupTestPlaybackAggregationServiceDB(t) logger := zaptest.NewLogger(t) service := NewPlaybackAggregationService(db, logger) user := &models.User{ID: uuid.New(), Username: "testuser", Email: "test@example.com", IsActive: true} db.Create(user) track := &models.Track{ ID: uuid.New(), UserID: user.ID, Title: "Test Track", FilePath: "/test.mp3", FileSize: 1024, Format: "MP3", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted, } db.Create(track) now := time.Now() startDate := now.AddDate(0, 0, -7) endDate := now result, err := service.AggregateByPeriod(context.Background(), track.ID, PeriodDay, startDate, endDate) require.NoError(t, err) assert.NotNil(t, result) assert.Equal(t, int64(0), result.TotalSessions) assert.Len(t, result.Periods, 0) } func TestPlaybackAggregationService_AggregateByPeriod_Trends(t *testing.T) { db := setupTestPlaybackAggregationServiceDB(t) logger := zaptest.NewLogger(t) service := NewPlaybackAggregationService(db, logger) user := &models.User{ID: uuid.New(), Username: "testuser", Email: "test@example.com", IsActive: true} db.Create(user) track := &models.Track{ ID: uuid.New(), UserID: user.ID, Title: "Test Track", FilePath: "/test.mp3", FileSize: 1024, Format: "MP3", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted, } db.Create(track) now := time.Now() // Créer des sessions avec des valeurs croissantes pour tester les tendances // Important: créer dans des jours différents pour avoir plusieurs périodes sessions := []models.PlaybackAnalytics{ { TrackID: track.ID, UserID: user.ID, PlayTime: 100, PauseCount: 1, SeekCount: 1, CompletionRate: 50.0, StartedAt: now.AddDate(0, 0, -3), CreatedAt: now.AddDate(0, 0, -3), }, { TrackID: track.ID, UserID: user.ID, PlayTime: 200, PauseCount: 2, SeekCount: 2, CompletionRate: 100.0, StartedAt: now.AddDate(0, 0, -1), CreatedAt: now.AddDate(0, 0, -1), }, } for _, session := range sessions { db.Create(&session) } startDate := now.AddDate(0, 0, -4) endDate := now result, err := service.AggregateByPeriod(context.Background(), track.ID, PeriodDay, startDate, endDate) require.NoError(t, err) assert.NotNil(t, result) // Les tendances ne sont calculées que s'il y a au moins 2 périodes if len(result.Periods) >= 2 { assert.NotNil(t, result.Trends) // Vérifier que les tendances sont calculées (croissance attendue) if result.Trends != nil { // Les tendances peuvent être positives (croissance) ou négatives (décroissance) // On vérifie juste qu'elles sont calculées (non nulles si les valeurs changent) assert.NotNil(t, result.Trends.SessionsTrend) assert.NotNil(t, result.Trends.PlayTimeTrend) assert.NotNil(t, result.Trends.CompletionTrend) } } else { // Si moins de 2 périodes, les tendances ne sont pas calculées assert.Nil(t, result.Trends) } } func TestPlaybackAggregationService_AggregateByDateRange(t *testing.T) { db := setupTestPlaybackAggregationServiceDB(t) logger := zaptest.NewLogger(t) service := NewPlaybackAggregationService(db, logger) user := &models.User{ID: uuid.New(), Username: "testuser", Email: "test@example.com", IsActive: true} db.Create(user) track := &models.Track{ ID: uuid.New(), UserID: user.ID, Title: "Test Track", FilePath: "/test.mp3", FileSize: 1024, Format: "MP3", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted, } db.Create(track) now := time.Now() sessions := []models.PlaybackAnalytics{ { TrackID: track.ID, UserID: user.ID, PlayTime: 120, PauseCount: 2, SeekCount: 3, CompletionRate: 75.0, StartedAt: now.AddDate(0, 0, -2), CreatedAt: now.AddDate(0, 0, -2), }, { TrackID: track.ID, UserID: user.ID, PlayTime: 150, PauseCount: 1, SeekCount: 2, CompletionRate: 90.0, StartedAt: now.AddDate(0, 0, -1), CreatedAt: now.AddDate(0, 0, -1), }, } for _, session := range sessions { db.Create(&session) } startDate := now.AddDate(0, 0, -3) endDate := now result, err := service.AggregateByDateRange(context.Background(), track.ID, startDate, endDate) require.NoError(t, err) assert.NotNil(t, result) assert.Equal(t, int64(2), result.Sessions) assert.Equal(t, int64(270), result.TotalPlayTime) assert.InDelta(t, 135.0, result.AveragePlayTime, 0.1) assert.InDelta(t, 82.5, result.AverageCompletion, 0.1) } func TestPlaybackAggregationService_GetTopTracksByPlayback(t *testing.T) { db := setupTestPlaybackAggregationServiceDB(t) logger := zaptest.NewLogger(t) service := NewPlaybackAggregationService(db, logger) userID := uuid.New() user := &models.User{ID: userID, Username: "testuser", Email: "test@example.com", IsActive: true} db.Create(user) // Créer plusieurs tracks track1ID := uuid.New() track2ID := uuid.New() tracks := []models.Track{ {ID: track1ID, UserID: userID, Title: "Track 1", FilePath: "/1.mp3", FileSize: 1024, Format: "MP3", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted}, {ID: track2ID, UserID: userID, Title: "Track 2", FilePath: "/2.mp3", FileSize: 1024, Format: "MP3", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted}, } for _, track := range tracks { db.Create(&track) } now := time.Now() // Créer plus de sessions pour le track 1 sessions := []models.PlaybackAnalytics{ {TrackID: track1ID, UserID: userID, PlayTime: 120, CompletionRate: 75.0, StartedAt: now, CreatedAt: now}, {TrackID: track1ID, UserID: userID, PlayTime: 150, CompletionRate: 90.0, StartedAt: now, CreatedAt: now}, {TrackID: track2ID, UserID: userID, PlayTime: 100, CompletionRate: 60.0, StartedAt: now, CreatedAt: now}, } for _, session := range sessions { db.Create(&session) } result, err := service.GetTopTracksByPlayback(context.Background(), 10, nil, nil) require.NoError(t, err) assert.NotNil(t, result) assert.Len(t, result, 2) // Vérifier que le track 1 est en premier (plus de sessions) // track_id is now uuid.UUID, not int64 trackID, ok := result[0]["track_id"].(uuid.UUID) require.True(t, ok, "track_id should be uuid.UUID") assert.Equal(t, track1ID, trackID) assert.Equal(t, int64(2), result[0]["sessions"]) } func TestPlaybackAggregationService_GetTopTracksByPlayback_WithDateRange(t *testing.T) { db := setupTestPlaybackAggregationServiceDB(t) logger := zaptest.NewLogger(t) service := NewPlaybackAggregationService(db, logger) user := &models.User{ID: uuid.New(), Username: "testuser", Email: "test@example.com", IsActive: true} db.Create(user) track := &models.Track{ ID: uuid.New(), UserID: user.ID, Title: "Test Track", FilePath: "/test.mp3", FileSize: 1024, Format: "MP3", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted, } db.Create(track) now := time.Now() startDate := now.AddDate(0, 0, -7) endDate := now // Créer une session dans la plage session := models.PlaybackAnalytics{ TrackID: track.ID, UserID: user.ID, PlayTime: 120, CompletionRate: 75.0, StartedAt: now.AddDate(0, 0, -3), CreatedAt: now.AddDate(0, 0, -3), } db.Create(&session) result, err := service.GetTopTracksByPlayback(context.Background(), 10, &startDate, &endDate) require.NoError(t, err) assert.NotNil(t, result) assert.Len(t, result, 1) assert.Equal(t, track.ID, result[0]["track_id"]) } func TestPlaybackAggregationService_GetTopTracksByPlayback_DefaultLimit(t *testing.T) { db := setupTestPlaybackAggregationServiceDB(t) logger := zaptest.NewLogger(t) service := NewPlaybackAggregationService(db, logger) user := &models.User{ID: uuid.New(), Username: "testuser", Email: "test@example.com", IsActive: true} db.Create(user) // Créer plusieurs tracks for i := 1; i <= 15; i++ { trackID := uuid.New() track := models.Track{ ID: trackID, UserID: user.ID, Title: "Track " + string(rune(i)), FilePath: "/test.mp3", FileSize: 1024, Format: "MP3", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted, } db.Create(&track) session := models.PlaybackAnalytics{ TrackID: trackID, UserID: user.ID, PlayTime: 120, CompletionRate: 75.0, StartedAt: time.Now(), CreatedAt: time.Now(), } db.Create(&session) } result, err := service.GetTopTracksByPlayback(context.Background(), 0, nil, nil) require.NoError(t, err) assert.NotNil(t, result) // Devrait utiliser la limite par défaut de 10 assert.LessOrEqual(t, len(result), 10) }