package services import ( "github.com/google/uuid" "os" "path/filepath" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap/zaptest" "veza-backend-api/internal/models" ) func TestNewPlaybackExportService(t *testing.T) { logger := zaptest.NewLogger(t) service := NewPlaybackExportService(logger) assert.NotNil(t, service) assert.NotNil(t, service.logger) } func TestNewPlaybackExportService_NilLogger(t *testing.T) { service := NewPlaybackExportService(nil) assert.NotNil(t, service) assert.NotNil(t, service.logger) } func TestPlaybackExportService_ExportCSV_Success(t *testing.T) { service := NewPlaybackExportService(zaptest.NewLogger(t)) // Créer un répertoire temporaire tmpDir := t.TempDir() filename := filepath.Join(tmpDir, "test.csv") // Créer des données de test now := time.Now() id1 := uuid.New() trackID := uuid.New() userID1 := uuid.New() id2 := uuid.New() userID2 := uuid.New() analytics := []models.PlaybackAnalytics{ { ID: id1, TrackID: trackID, UserID: userID1, PlayTime: 120, PauseCount: 2, SeekCount: 3, CompletionRate: 75.0, StartedAt: now, CreatedAt: now, }, { ID: id2, TrackID: trackID, UserID: userID2, PlayTime: 150, PauseCount: 1, SeekCount: 2, CompletionRate: 90.0, StartedAt: now, EndedAt: &now, CreatedAt: now, }, } err := service.ExportCSV(analytics, filename) require.NoError(t, err) // Vérifier que le fichier existe _, err = os.Stat(filename) assert.NoError(t, err) // Vérifier le contenu du fichier data, err := os.ReadFile(filename) require.NoError(t, err) assert.Contains(t, string(data), "ID") assert.Contains(t, string(data), "Track ID") assert.Contains(t, string(data), id1.String()) assert.Contains(t, string(data), "120") } func TestPlaybackExportService_ExportCSV_EmptyData(t *testing.T) { service := NewPlaybackExportService(zaptest.NewLogger(t)) tmpDir := t.TempDir() filename := filepath.Join(tmpDir, "test.csv") err := service.ExportCSV([]models.PlaybackAnalytics{}, filename) assert.Error(t, err) assert.Contains(t, err.Error(), "no analytics data") } func TestPlaybackExportService_ExportJSON_Success(t *testing.T) { service := NewPlaybackExportService(zaptest.NewLogger(t)) tmpDir := t.TempDir() filename := filepath.Join(tmpDir, "test.json") now := time.Now() id := uuid.New() trackID := uuid.New() userID := uuid.New() analytics := []models.PlaybackAnalytics{ { ID: id, TrackID: trackID, UserID: userID, PlayTime: 120, PauseCount: 2, SeekCount: 3, CompletionRate: 75.0, StartedAt: now, CreatedAt: now, }, } err := service.ExportJSON(analytics, filename) require.NoError(t, err) // Vérifier que le fichier existe _, err = os.Stat(filename) assert.NoError(t, err) // Vérifier que c'est du JSON valide data, err := os.ReadFile(filename) require.NoError(t, err) // Le JSON est indenté, donc les valeurs peuvent avoir des espaces assert.Contains(t, string(data), `"id": "`+id.String()+`"`) assert.Contains(t, string(data), `"track_id": "`+trackID.String()+`"`) assert.Contains(t, string(data), `"play_time": 120`) } func TestPlaybackExportService_ExportJSON_EmptyData(t *testing.T) { service := NewPlaybackExportService(zaptest.NewLogger(t)) tmpDir := t.TempDir() filename := filepath.Join(tmpDir, "test.json") err := service.ExportJSON([]models.PlaybackAnalytics{}, filename) assert.Error(t, err) assert.Contains(t, err.Error(), "no analytics data") } func TestPlaybackExportService_ExportReport_CSV(t *testing.T) { service := NewPlaybackExportService(zaptest.NewLogger(t)) tmpDir := t.TempDir() filename := filepath.Join(tmpDir, "report.csv") now := time.Now() analytics := []models.PlaybackAnalytics{ { ID: uuid.New(), TrackID: uuid.New(), UserID: uuid.New(), PlayTime: 120, PauseCount: 2, SeekCount: 3, CompletionRate: 75.0, StartedAt: now, CreatedAt: now, }, { ID: uuid.New(), TrackID: uuid.New(), UserID: uuid.New(), PlayTime: 171, // 95% de 180 PauseCount: 1, SeekCount: 2, CompletionRate: 95.0, StartedAt: now, EndedAt: &now, CreatedAt: now, }, } err := service.ExportReport(analytics, filename, FormatCSV) require.NoError(t, err) // Vérifier que le fichier existe _, err = os.Stat(filename) assert.NoError(t, err) // Vérifier le contenu data, err := os.ReadFile(filename) require.NoError(t, err) assert.Contains(t, string(data), "Total Sessions") assert.Contains(t, string(data), "Average Play Time") assert.Contains(t, string(data), "Completion Rate") } func TestPlaybackExportService_ExportReport_JSON(t *testing.T) { service := NewPlaybackExportService(zaptest.NewLogger(t)) tmpDir := t.TempDir() filename := filepath.Join(tmpDir, "report.json") now := time.Now() analytics := []models.PlaybackAnalytics{ { ID: uuid.New(), TrackID: uuid.New(), UserID: uuid.New(), PlayTime: 120, PauseCount: 2, SeekCount: 3, CompletionRate: 75.0, StartedAt: now, CreatedAt: now, }, } err := service.ExportReport(analytics, filename, FormatJSON) require.NoError(t, err) // Vérifier que le fichier existe _, err = os.Stat(filename) assert.NoError(t, err) // Vérifier que c'est du JSON valide avec statistiques data, err := os.ReadFile(filename) require.NoError(t, err) assert.Contains(t, string(data), `"statistics"`) assert.Contains(t, string(data), `"analytics"`) assert.Contains(t, string(data), `"total_sessions"`) } func TestPlaybackExportService_ExportReport_InvalidFormat(t *testing.T) { service := NewPlaybackExportService(zaptest.NewLogger(t)) tmpDir := t.TempDir() filename := filepath.Join(tmpDir, "report.txt") now := time.Now() analytics := []models.PlaybackAnalytics{ { ID: uuid.New(), TrackID: uuid.New(), UserID: uuid.New(), PlayTime: 120, CompletionRate: 75.0, StartedAt: now, CreatedAt: now, }, } err := service.ExportReport(analytics, filename, ExportFormat("invalid")) assert.Error(t, err) assert.Contains(t, err.Error(), "unsupported export format") } func TestPlaybackExportService_ExportReport_EmptyData(t *testing.T) { service := NewPlaybackExportService(zaptest.NewLogger(t)) tmpDir := t.TempDir() filename := filepath.Join(tmpDir, "report.csv") err := service.ExportReport([]models.PlaybackAnalytics{}, filename, FormatCSV) assert.Error(t, err) assert.Contains(t, err.Error(), "no analytics data") } func TestPlaybackExportService_calculateReportStats(t *testing.T) { service := NewPlaybackExportService(zaptest.NewLogger(t)) now := time.Now() analytics := []models.PlaybackAnalytics{ { ID: uuid.New(), TrackID: uuid.New(), UserID: uuid.New(), PlayTime: 120, PauseCount: 2, SeekCount: 3, CompletionRate: 75.0, StartedAt: now, CreatedAt: now, }, { ID: uuid.New(), TrackID: uuid.New(), UserID: uuid.New(), PlayTime: 150, PauseCount: 1, SeekCount: 2, CompletionRate: 95.0, // ≥95% StartedAt: now, EndedAt: &now, CreatedAt: now, }, { ID: uuid.New(), TrackID: uuid.New(), UserID: uuid.New(), PlayTime: 100, PauseCount: 0, SeekCount: 1, CompletionRate: 92.0, // ≥90% mais <95% StartedAt: now, CreatedAt: now, }, } stats := service.calculateReportStats(analytics) assert.Equal(t, 3, stats.TotalSessions) assert.Equal(t, int64(370), stats.TotalPlayTime) assert.InDelta(t, 123.33, stats.AveragePlayTime, 0.1) assert.Equal(t, int64(3), stats.TotalPauses) assert.InDelta(t, 1.0, stats.AveragePauses, 0.1) assert.Equal(t, int64(6), stats.TotalSeeks) assert.InDelta(t, 2.0, stats.AverageSeeks, 0.1) assert.InDelta(t, 87.33, stats.AverageCompletion, 0.1) // 2 sessions avec ≥90% completion (95% et 92%) assert.InDelta(t, 66.67, stats.CompletionRate, 0.1) // 1 session avec ≥95% completion assert.Equal(t, 1, stats.CompletedSessions) } func TestPlaybackExportService_calculateReportStats_Empty(t *testing.T) { service := NewPlaybackExportService(zaptest.NewLogger(t)) stats := service.calculateReportStats([]models.PlaybackAnalytics{}) assert.Equal(t, 0, stats.TotalSessions) assert.Equal(t, int64(0), stats.TotalPlayTime) assert.Equal(t, 0.0, stats.AveragePlayTime) } func TestPlaybackExportService_ExportCSV_WithEndedAt(t *testing.T) { service := NewPlaybackExportService(zaptest.NewLogger(t)) tmpDir := t.TempDir() filename := filepath.Join(tmpDir, "test.csv") now := time.Now() endedAt := now.Add(5 * time.Minute) analytics := []models.PlaybackAnalytics{ { ID: uuid.New(), TrackID: uuid.New(), UserID: uuid.New(), PlayTime: 120, CompletionRate: 75.0, StartedAt: now, EndedAt: &endedAt, CreatedAt: now, }, } err := service.ExportCSV(analytics, filename) require.NoError(t, err) // Vérifier que EndedAt est dans le fichier data, err := os.ReadFile(filename) require.NoError(t, err) assert.Contains(t, string(data), endedAt.Format(time.RFC3339)) } func TestPlaybackExportService_ExportCSV_WithoutEndedAt(t *testing.T) { service := NewPlaybackExportService(zaptest.NewLogger(t)) tmpDir := t.TempDir() filename := filepath.Join(tmpDir, "test.csv") now := time.Now() analytics := []models.PlaybackAnalytics{ { ID: uuid.New(), TrackID: uuid.New(), UserID: uuid.New(), PlayTime: 120, CompletionRate: 75.0, StartedAt: now, EndedAt: nil, CreatedAt: now, }, } err := service.ExportCSV(analytics, filename) require.NoError(t, err) // Vérifier que le fichier contient une ligne avec EndedAt vide data, err := os.ReadFile(filename) require.NoError(t, err) // La ligne devrait avoir une colonne vide pour EndedAt assert.Contains(t, string(data), ",120,0,0,75.00") // Part of the CSV line we can match safely } func TestPlaybackExportService_ExportToWriter_CSV(t *testing.T) { service := NewPlaybackExportService(zaptest.NewLogger(t)) tmpDir := t.TempDir() filename := filepath.Join(tmpDir, "test.csv") file, err := os.Create(filename) require.NoError(t, err) defer file.Close() now := time.Now() id := uuid.New() analytics := []models.PlaybackAnalytics{ { ID: id, TrackID: uuid.New(), UserID: uuid.New(), PlayTime: 120, CompletionRate: 75.0, StartedAt: now, CreatedAt: now, }, } err = service.ExportToWriter(analytics, FormatCSV, file) require.NoError(t, err) file.Close() // Vérifier le contenu data, err := os.ReadFile(filename) require.NoError(t, err) assert.Contains(t, string(data), "ID") assert.Contains(t, string(data), id.String()) } func TestPlaybackExportService_ExportToWriter_JSON(t *testing.T) { service := NewPlaybackExportService(zaptest.NewLogger(t)) tmpDir := t.TempDir() filename := filepath.Join(tmpDir, "test.json") file, err := os.Create(filename) require.NoError(t, err) defer file.Close() now := time.Now() id := uuid.New() analytics := []models.PlaybackAnalytics{ { ID: id, TrackID: uuid.New(), UserID: uuid.New(), PlayTime: 120, CompletionRate: 75.0, StartedAt: now, CreatedAt: now, }, } err = service.ExportToWriter(analytics, FormatJSON, file) require.NoError(t, err) file.Close() // Vérifier le contenu data, err := os.ReadFile(filename) require.NoError(t, err) // Le JSON est indenté, donc les valeurs peuvent avoir des espaces assert.Contains(t, string(data), `"id": "`+id.String()+`"`) } func TestPlaybackExportService_ExportToWriter_InvalidFormat(t *testing.T) { service := NewPlaybackExportService(zaptest.NewLogger(t)) tmpDir := t.TempDir() filename := filepath.Join(tmpDir, "test.txt") file, err := os.Create(filename) require.NoError(t, err) defer file.Close() now := time.Now() analytics := []models.PlaybackAnalytics{ { ID: uuid.New(), TrackID: uuid.New(), UserID: uuid.New(), PlayTime: 120, CompletionRate: 75.0, StartedAt: now, CreatedAt: now, }, } err = service.ExportToWriter(analytics, ExportFormat("invalid"), file) assert.Error(t, err) assert.Contains(t, err.Error(), "unsupported export format") } func TestPlaybackExportService_ExportToWriter_InvalidWriter(t *testing.T) { service := NewPlaybackExportService(zaptest.NewLogger(t)) now := time.Now() analytics := []models.PlaybackAnalytics{ { ID: uuid.New(), TrackID: uuid.New(), UserID: uuid.New(), PlayTime: 120, CompletionRate: 75.0, StartedAt: now, CreatedAt: now, }, } // Passer un writer invalide err := service.ExportToWriter(analytics, FormatCSV, "invalid") assert.Error(t, err) assert.Contains(t, err.Error(), "writer must be") }