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 setupTestPlaybackComparisonServiceDB(t *testing.T) (*gorm.DB, *PlaybackComparisonService) { 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 := NewPlaybackComparisonService(db, logger) return db, service } func TestNewPlaybackComparisonService(t *testing.T) { db, _ := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) logger := zaptest.NewLogger(t) service := NewPlaybackComparisonService(db, logger) assert.NotNil(t, service) assert.Equal(t, db, service.db) assert.NotNil(t, service.logger) } func TestNewPlaybackComparisonService_NilLogger(t *testing.T) { db, _ := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) service := NewPlaybackComparisonService(db, nil) assert.NotNil(t, service) assert.NotNil(t, service.logger) } func TestPlaybackComparisonService_ComparePeriods(t *testing.T) { db, service := setupTestPlaybackComparisonServiceDB(t) ctx := context.Background() // Créer user et track user := &models.User{ID: 1, Username: "testuser", Email: "test@example.com", IsActive: true} db.Create(user) track := &models.Track{ ID: 1, UserID: 1, Title: "Test Track", FilePath: "/test.mp3", FileSize: 1024, Format: "MP3", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted, } db.Create(track) // Créer des analytics pour la période 1 (il y a 2 semaines) now := time.Now() period1Start := now.AddDate(0, 0, -14) analytics1 := &models.PlaybackAnalytics{ TrackID: 1, UserID: 1, PlayTime: 120, PauseCount: 2, SeekCount: 3, CompletionRate: 66.67, StartedAt: period1Start.AddDate(0, 0, 1), CreatedAt: period1Start.AddDate(0, 0, 1), } analytics2 := &models.PlaybackAnalytics{ TrackID: 1, UserID: 1, PlayTime: 150, PauseCount: 1, SeekCount: 2, CompletionRate: 83.33, StartedAt: period1Start.AddDate(0, 0, 2), CreatedAt: period1Start.AddDate(0, 0, 2), } db.Create(analytics1) db.Create(analytics2) // Créer des analytics pour la période 2 (cette semaine) period2Start := now.AddDate(0, 0, -7) analytics3 := &models.PlaybackAnalytics{ TrackID: 1, UserID: 1, PlayTime: 180, PauseCount: 0, SeekCount: 1, CompletionRate: 100.0, StartedAt: period2Start.AddDate(0, 0, 1), CreatedAt: period2Start.AddDate(0, 0, 1), } analytics4 := &models.PlaybackAnalytics{ TrackID: 1, UserID: 1, PlayTime: 170, PauseCount: 1, SeekCount: 0, CompletionRate: 94.44, StartedAt: period2Start.AddDate(0, 0, 2), CreatedAt: period2Start.AddDate(0, 0, 2), } db.Create(analytics3) db.Create(analytics4) // Comparer les périodes result, err := service.ComparePeriods(ctx, 1, "week", "week") require.NoError(t, err) assert.NotNil(t, result) assert.NotNil(t, result.Period1) assert.NotNil(t, result.Period2) assert.NotNil(t, result.Difference) assert.NotNil(t, result.PercentageChange) // Vérifier que period2 a plus de sessions que period1 (car on compare deux semaines différentes) // Note: Les périodes "week" sont calculées depuis maintenant, donc on compare la même période // Pour un vrai test, on devrait utiliser des dates personnalisées, mais testons quand même la structure assert.GreaterOrEqual(t, result.Period2.TotalSessions, int64(0)) } func TestPlaybackComparisonService_ComparePeriods_InvalidTrackID(t *testing.T) { _, service := setupTestPlaybackComparisonServiceDB(t) ctx := context.Background() result, err := service.ComparePeriods(ctx, 0, "week", "month") assert.Error(t, err) assert.Contains(t, err.Error(), "invalid track ID") assert.Nil(t, result) } func TestPlaybackComparisonService_ComparePeriods_TrackNotFound(t *testing.T) { _, service := setupTestPlaybackComparisonServiceDB(t) ctx := context.Background() result, err := service.ComparePeriods(ctx, 999, "week", "month") assert.Error(t, err) assert.Contains(t, err.Error(), "track not found") assert.Nil(t, result) } func TestPlaybackComparisonService_ComparePeriods_InvalidPeriod(t *testing.T) { db, service := setupTestPlaybackComparisonServiceDB(t) ctx := context.Background() // Créer user et track user := &models.User{ID: 1, Username: "testuser", Email: "test@example.com", IsActive: true} db.Create(user) track := &models.Track{ ID: 1, UserID: 1, Title: "Test Track", FilePath: "/test.mp3", FileSize: 1024, Format: "MP3", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted, } db.Create(track) result, err := service.ComparePeriods(ctx, 1, "invalid", "week") assert.Error(t, err) assert.Contains(t, err.Error(), "invalid period") assert.Nil(t, result) } func TestPlaybackComparisonService_CompareTracks(t *testing.T) { db, service := setupTestPlaybackComparisonServiceDB(t) ctx := context.Background() // Créer user et tracks user := &models.User{ID: 1, Username: "testuser", Email: "test@example.com", IsActive: true} db.Create(user) track1 := &models.Track{ ID: 1, UserID: 1, Title: "Track 1", FilePath: "/track1.mp3", FileSize: 1024, Format: "MP3", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted, } track2 := &models.Track{ ID: 2, UserID: 1, Title: "Track 2", FilePath: "/track2.mp3", FileSize: 2048, Format: "MP3", Duration: 240, IsPublic: true, Status: models.TrackStatusCompleted, } db.Create(track1) db.Create(track2) // Créer des analytics pour track1 now := time.Now() startDate := now.AddDate(0, 0, -7) endDate := now analytics1 := &models.PlaybackAnalytics{ TrackID: 1, UserID: 1, PlayTime: 120, PauseCount: 2, SeekCount: 3, CompletionRate: 66.67, StartedAt: startDate.AddDate(0, 0, 1), CreatedAt: startDate.AddDate(0, 0, 1), } analytics2 := &models.PlaybackAnalytics{ TrackID: 1, UserID: 1, PlayTime: 150, PauseCount: 1, SeekCount: 2, CompletionRate: 83.33, StartedAt: startDate.AddDate(0, 0, 2), CreatedAt: startDate.AddDate(0, 0, 2), } db.Create(analytics1) db.Create(analytics2) // Créer des analytics pour track2 analytics3 := &models.PlaybackAnalytics{ TrackID: 2, UserID: 1, PlayTime: 200, PauseCount: 0, SeekCount: 1, CompletionRate: 83.33, StartedAt: startDate.AddDate(0, 0, 1), CreatedAt: startDate.AddDate(0, 0, 1), } db.Create(analytics3) // Comparer les tracks result, err := service.CompareTracks(ctx, 1, 2, startDate, endDate) require.NoError(t, err) assert.NotNil(t, result) assert.NotNil(t, result.Period1) assert.NotNil(t, result.Period2) assert.NotNil(t, result.Difference) assert.NotNil(t, result.PercentageChange) // Vérifier que track1 a 2 sessions et track2 a 1 session assert.Equal(t, int64(2), result.Period1.TotalSessions) assert.Equal(t, int64(1), result.Period2.TotalSessions) assert.Equal(t, int64(-1), result.Difference.TotalSessions) } func TestPlaybackComparisonService_CompareTracks_InvalidTrackID(t *testing.T) { _, service := setupTestPlaybackComparisonServiceDB(t) ctx := context.Background() now := time.Now() startDate := now.AddDate(0, 0, -7) endDate := now result, err := service.CompareTracks(ctx, 0, 2, startDate, endDate) assert.Error(t, err) assert.Contains(t, err.Error(), "invalid track ID 1") assert.Nil(t, result) } func TestPlaybackComparisonService_CompareTracks_TrackNotFound(t *testing.T) { _, service := setupTestPlaybackComparisonServiceDB(t) ctx := context.Background() now := time.Now() startDate := now.AddDate(0, 0, -7) endDate := now result, err := service.CompareTracks(ctx, 999, 1000, startDate, endDate) assert.Error(t, err) assert.Contains(t, err.Error(), "track not found") assert.Nil(t, result) } func TestPlaybackComparisonService_CompareUsers(t *testing.T) { db, service := setupTestPlaybackComparisonServiceDB(t) ctx := context.Background() // Créer users et track user1 := &models.User{ID: 1, Username: "user1", Slug: "user1", Email: "user1@example.com", IsActive: true} user2 := &models.User{ID: 2, Username: "user2", Slug: "user2", Email: "user2@example.com", IsActive: true} db.Create(user1) db.Create(user2) track := &models.Track{ ID: 1, UserID: 1, Title: "Test Track", FilePath: "/test.mp3", FileSize: 1024, Format: "MP3", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted, } db.Create(track) // Créer des analytics pour user1 now := time.Now() startDate := now.AddDate(0, 0, -7) endDate := now analytics1 := &models.PlaybackAnalytics{ TrackID: 1, UserID: 1, PlayTime: 120, PauseCount: 2, SeekCount: 3, CompletionRate: 66.67, StartedAt: startDate.AddDate(0, 0, 1), CreatedAt: startDate.AddDate(0, 0, 1), } analytics2 := &models.PlaybackAnalytics{ TrackID: 1, UserID: 1, PlayTime: 150, PauseCount: 1, SeekCount: 2, CompletionRate: 83.33, StartedAt: startDate.AddDate(0, 0, 2), CreatedAt: startDate.AddDate(0, 0, 2), } db.Create(analytics1) db.Create(analytics2) // Créer des analytics pour user2 analytics3 := &models.PlaybackAnalytics{ TrackID: 1, UserID: 2, PlayTime: 180, PauseCount: 0, SeekCount: 1, CompletionRate: 100.0, StartedAt: startDate.AddDate(0, 0, 1), CreatedAt: startDate.AddDate(0, 0, 1), } db.Create(analytics3) // Comparer les users result, err := service.CompareUsers(ctx, 1, 1, 2, startDate, endDate) require.NoError(t, err) assert.NotNil(t, result) assert.NotNil(t, result.Period1) assert.NotNil(t, result.Period2) assert.NotNil(t, result.Difference) assert.NotNil(t, result.PercentageChange) // Vérifier que user1 a 2 sessions et user2 a 1 session assert.Equal(t, int64(2), result.Period1.TotalSessions) assert.Equal(t, int64(1), result.Period2.TotalSessions) assert.Equal(t, int64(-1), result.Difference.TotalSessions) } func TestPlaybackComparisonService_CompareUsers_InvalidTrackID(t *testing.T) { _, service := setupTestPlaybackComparisonServiceDB(t) ctx := context.Background() now := time.Now() startDate := now.AddDate(0, 0, -7) endDate := now result, err := service.CompareUsers(ctx, 0, 1, 2, startDate, endDate) assert.Error(t, err) assert.Contains(t, err.Error(), "invalid track ID") assert.Nil(t, result) } func TestPlaybackComparisonService_CompareUsers_InvalidUserID(t *testing.T) { _, service := setupTestPlaybackComparisonServiceDB(t) ctx := context.Background() now := time.Now() startDate := now.AddDate(0, 0, -7) endDate := now result, err := service.CompareUsers(ctx, 1, 0, 2, startDate, endDate) assert.Error(t, err) assert.Contains(t, err.Error(), "invalid user ID 1") assert.Nil(t, result) } func TestPlaybackComparisonService_CompareUsers_TrackNotFound(t *testing.T) { _, service := setupTestPlaybackComparisonServiceDB(t) ctx := context.Background() now := time.Now() startDate := now.AddDate(0, 0, -7) endDate := now result, err := service.CompareUsers(ctx, 999, 1, 2, startDate, endDate) assert.Error(t, err) assert.Contains(t, err.Error(), "track not found") assert.Nil(t, result) } func TestPlaybackComparisonService_CompareUsers_UserNotFound(t *testing.T) { db, service := setupTestPlaybackComparisonServiceDB(t) ctx := context.Background() // Créer user et track user := &models.User{ID: 1, Username: "user1", Email: "user1@example.com", IsActive: true} db.Create(user) track := &models.Track{ ID: 1, UserID: 1, 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.CompareUsers(ctx, 1, 1, 999, startDate, endDate) assert.Error(t, err) assert.Contains(t, err.Error(), "user not found") assert.Nil(t, result) } func TestPlaybackComparisonService_CalculateDifference(t *testing.T) { _, service := setupTestPlaybackComparisonServiceDB(t) stats1 := &PlaybackStats{ TotalSessions: 10, TotalPlayTime: 1000, AveragePlayTime: 100.0, TotalPauses: 5, AveragePauses: 0.5, TotalSeeks: 8, AverageSeeks: 0.8, AverageCompletion: 75.0, CompletionRate: 60.0, } stats2 := &PlaybackStats{ TotalSessions: 15, TotalPlayTime: 1500, AveragePlayTime: 100.0, TotalPauses: 10, AveragePauses: 0.67, TotalSeeks: 12, AverageSeeks: 0.8, AverageCompletion: 80.0, CompletionRate: 70.0, } difference := service.calculateDifference(stats1, stats2) assert.NotNil(t, difference) assert.Equal(t, int64(5), difference.TotalSessions) assert.Equal(t, int64(500), difference.TotalPlayTime) assert.Equal(t, float64(0.0), difference.AveragePlayTime) assert.Equal(t, int64(5), difference.TotalPauses) assert.InDelta(t, 0.17, difference.AveragePauses, 0.01) assert.Equal(t, int64(4), difference.TotalSeeks) assert.Equal(t, float64(0.0), difference.AverageSeeks) assert.Equal(t, 5.0, difference.AverageCompletion) assert.Equal(t, 10.0, difference.CompletionRate) } func TestPlaybackComparisonService_CalculatePercentageChange(t *testing.T) { _, service := setupTestPlaybackComparisonServiceDB(t) stats1 := &PlaybackStats{ TotalSessions: 10, TotalPlayTime: 1000, AveragePlayTime: 100.0, TotalPauses: 5, AveragePauses: 0.5, TotalSeeks: 8, AverageSeeks: 0.8, AverageCompletion: 75.0, CompletionRate: 60.0, } stats2 := &PlaybackStats{ TotalSessions: 15, TotalPlayTime: 1500, AveragePlayTime: 100.0, TotalPauses: 10, AveragePauses: 0.67, TotalSeeks: 12, AverageSeeks: 0.8, AverageCompletion: 80.0, CompletionRate: 70.0, } change := service.calculatePercentageChange(stats1, stats2) assert.NotNil(t, change) assert.Equal(t, 50.0, change.TotalSessions) // (15-10)/10 * 100 = 50% assert.Equal(t, 50.0, change.TotalPlayTime) // (1500-1000)/1000 * 100 = 50% assert.Equal(t, 0.0, change.AveragePlayTime) // (100-100)/100 * 100 = 0% assert.Equal(t, 100.0, change.TotalPauses) // (10-5)/5 * 100 = 100% assert.InDelta(t, 34.0, change.AveragePauses, 1.0) // (0.67-0.5)/0.5 * 100 ≈ 34% assert.Equal(t, 50.0, change.TotalSeeks) // (12-8)/8 * 100 = 50% assert.Equal(t, 0.0, change.AverageSeeks) // (0.8-0.8)/0.8 * 100 = 0% assert.InDelta(t, 6.67, change.AverageCompletion, 0.1) // (80-75)/75 * 100 ≈ 6.67% assert.InDelta(t, 16.67, change.CompletionRate, 0.1) // (70-60)/60 * 100 ≈ 16.67% } func TestPlaybackComparisonService_CalculatePercentageChange_ZeroBase(t *testing.T) { _, service := setupTestPlaybackComparisonServiceDB(t) stats1 := &PlaybackStats{ TotalSessions: 0, TotalPlayTime: 0, } stats2 := &PlaybackStats{ TotalSessions: 10, TotalPlayTime: 1000, } change := service.calculatePercentageChange(stats1, stats2) assert.NotNil(t, change) assert.Equal(t, 100.0, change.TotalSessions) // 100% increase from 0 assert.Equal(t, 100.0, change.TotalPlayTime) // 100% increase from 0 } func TestPlaybackComparisonService_GetPeriodDates(t *testing.T) { _, service := setupTestPlaybackComparisonServiceDB(t) // Test "today" start, end, err := service.getPeriodDates("today") require.NoError(t, err) assert.True(t, start.Before(end) || start.Equal(end)) assert.True(t, end.Before(time.Now().Add(time.Minute)) || end.Equal(time.Now())) // Test "week" start, end, err = service.getPeriodDates("week") require.NoError(t, err) assert.True(t, start.Before(end)) duration := end.Sub(start) assert.True(t, duration >= 6*24*time.Hour && duration <= 7*24*time.Hour) // Test "month" start, end, err = service.getPeriodDates("month") require.NoError(t, err) assert.True(t, start.Before(end)) duration = end.Sub(start) // La durée peut varier légèrement selon le moment où le test est exécuté assert.True(t, duration >= 28*24*time.Hour && duration <= 31*24*time.Hour) // Test "year" start, end, err = service.getPeriodDates("year") require.NoError(t, err) assert.True(t, start.Before(end)) duration = end.Sub(start) // La durée peut varier légèrement selon le moment où le test est exécuté assert.True(t, duration >= 363*24*time.Hour && duration <= 366*24*time.Hour) // Test invalid period _, _, err = service.getPeriodDates("invalid") assert.Error(t, err) assert.Contains(t, err.Error(), "invalid period") }