package services import ( "context" "fmt" "os" "path/filepath" "testing" "time" "github.com/google/uuid" "veza-backend-api/internal/models" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap/zaptest" ) func setupTestHLSDir(t *testing.T) (string, func()) { testDir := filepath.Join(os.TempDir(), fmt.Sprintf("hls_test_%d", time.Now().UnixNano())) err := os.MkdirAll(testDir, 0755) require.NoError(t, err) cleanup := func() { os.RemoveAll(testDir) } return testDir, cleanup } func createTestTrack(t *testing.T, filePath string) *models.Track { // Créer un fichier audio de test minimal err := os.WriteFile(filePath, []byte("fake audio content"), 0644) require.NoError(t, err) // GO-004: Utiliser UUID au lieu de int trackID := uuid.New() userID := uuid.New() return &models.Track{ ID: trackID, UserID: userID, Title: "Test Track", FilePath: filePath, FileSize: 1024, Format: "mp3", Duration: 180, Status: models.TrackStatusCompleted, } } func TestNewHLSTranscodeService(t *testing.T) { logger := zaptest.NewLogger(t) service := NewHLSTranscodeService("/tmp/hls", logger) assert.NotNil(t, service) assert.Equal(t, "/tmp/hls", service.outputDir) assert.Equal(t, []int{128, 192, 320}, service.bitrates) assert.NotNil(t, service.logger) } func TestNewHLSTranscodeService_NilLogger(t *testing.T) { service := NewHLSTranscodeService("/tmp/hls", nil) assert.NotNil(t, service) assert.NotNil(t, service.logger) // Devrait créer un logger Nop } func TestHLSTranscodeService_SetBitrates(t *testing.T) { logger := zaptest.NewLogger(t) service := NewHLSTranscodeService("/tmp/hls", logger) customBitrates := []int{64, 128, 256} service.SetBitrates(customBitrates) assert.Equal(t, customBitrates, service.bitrates) } func TestHLSTranscodeService_TranscodeTrack_NilTrack(t *testing.T) { logger := zaptest.NewLogger(t) service := NewHLSTranscodeService("/tmp/hls", logger) ctx := context.Background() result, err := service.TranscodeTrack(ctx, nil) assert.Error(t, err) assert.Nil(t, result) assert.Contains(t, err.Error(), "track cannot be nil") } func TestHLSTranscodeService_TranscodeTrack_EmptyFilePath(t *testing.T) { logger := zaptest.NewLogger(t) service := NewHLSTranscodeService("/tmp/hls", logger) // GO-004: Utiliser UUID au lieu de int trackID := uuid.New() track := &models.Track{ ID: trackID, FilePath: "", } ctx := context.Background() result, err := service.TranscodeTrack(ctx, track) assert.Error(t, err) assert.Nil(t, result) assert.Contains(t, err.Error(), "file path is empty") } func TestHLSTranscodeService_TranscodeTrack_FileNotExists(t *testing.T) { logger := zaptest.NewLogger(t) testDir, cleanup := setupTestHLSDir(t) defer cleanup() service := NewHLSTranscodeService(testDir, logger) // GO-004: Utiliser UUID au lieu de int trackID := uuid.New() track := &models.Track{ ID: trackID, FilePath: "/nonexistent/file.mp3", } ctx := context.Background() result, err := service.TranscodeTrack(ctx, track) assert.Error(t, err) assert.Nil(t, result) assert.Contains(t, err.Error(), "file does not exist") } func TestHLSTranscodeService_TranscodeTrack_CreatesDirectory(t *testing.T) { logger := zaptest.NewLogger(t) testDir, cleanup := setupTestHLSDir(t) defer cleanup() service := NewHLSTranscodeService(testDir, logger) // Créer un fichier audio de test testAudioFile := filepath.Join(testDir, "test.mp3") track := createTestTrack(t, testAudioFile) ctx := context.Background() // Note: Ce test échouera si ffmpeg n'est pas installé // C'est acceptable car c'est un test d'intégration result, err := service.TranscodeTrack(ctx, track) // Si ffmpeg n'est pas disponible, on s'attend à une erreur if err != nil { // Vérifier que le répertoire a été créé même en cas d'erreur trackDir := filepath.Join(testDir, fmt.Sprintf("track_%d", track.ID)) // Le répertoire peut ne pas exister si l'erreur survient avant sa création // ou peut exister si l'erreur survient après _ = trackDir assert.Error(t, err) assert.Nil(t, result) } else { // Si ffmpeg est disponible, vérifier que tout a été créé assert.NoError(t, err) assert.NotNil(t, result) assert.Equal(t, track.ID, result.TrackID) assert.Contains(t, result.PlaylistURL, "master.m3u8") assert.Greater(t, result.SegmentsCount, 0) assert.Equal(t, models.HLSStatusReady, result.Status) } } func TestHLSTranscodeService_CountSegments(t *testing.T) { logger := zaptest.NewLogger(t) testDir, cleanup := setupTestHLSDir(t) defer cleanup() service := NewHLSTranscodeService(testDir, logger) // Créer une structure de test trackDir := filepath.Join(testDir, "track_123") qualityDir1 := filepath.Join(trackDir, "128k") qualityDir2 := filepath.Join(trackDir, "192k") require.NoError(t, os.MkdirAll(qualityDir1, 0755)) require.NoError(t, os.MkdirAll(qualityDir2, 0755)) // Créer des segments de test for i := 0; i < 3; i++ { segmentPath := filepath.Join(qualityDir1, fmt.Sprintf("segment_%03d.ts", i)) require.NoError(t, os.WriteFile(segmentPath, []byte("test"), 0644)) } for i := 0; i < 2; i++ { segmentPath := filepath.Join(qualityDir2, fmt.Sprintf("segment_%03d.ts", i)) require.NoError(t, os.WriteFile(segmentPath, []byte("test"), 0644)) } count, err := service.countSegments(trackDir) assert.NoError(t, err) // Devrait retourner le maximum (3 segments dans 128k) assert.Equal(t, 3, count) } func TestHLSTranscodeService_CountSegments_EmptyDir(t *testing.T) { logger := zaptest.NewLogger(t) testDir, cleanup := setupTestHLSDir(t) defer cleanup() service := NewHLSTranscodeService(testDir, logger) trackDir := filepath.Join(testDir, "track_123") require.NoError(t, os.MkdirAll(trackDir, 0755)) // Créer les répertoires de qualité vides for _, bitrate := range service.bitrates { qualityDir := filepath.Join(trackDir, fmt.Sprintf("%dk", bitrate)) require.NoError(t, os.MkdirAll(qualityDir, 0755)) } count, err := service.countSegments(trackDir) assert.NoError(t, err) assert.Equal(t, 0, count) } func TestHLSTranscodeService_CountSegments_NonexistentDir(t *testing.T) { logger := zaptest.NewLogger(t) testDir, cleanup := setupTestHLSDir(t) defer cleanup() service := NewHLSTranscodeService(testDir, logger) count, err := service.countSegments("/nonexistent/dir") assert.Error(t, err) assert.Equal(t, 0, count) } func TestHLSTranscodeService_CountSegments_MultipleBitrates(t *testing.T) { logger := zaptest.NewLogger(t) testDir, cleanup := setupTestHLSDir(t) defer cleanup() service := NewHLSTranscodeService(testDir, logger) // Créer un répertoire de track avec des segments trackDir := filepath.Join(testDir, "track_123") require.NoError(t, os.MkdirAll(trackDir, 0755)) // Créer des répertoires de qualité avec différents nombres de segments qualityDir128 := filepath.Join(trackDir, "128k") require.NoError(t, os.MkdirAll(qualityDir128, 0755)) require.NoError(t, os.WriteFile(filepath.Join(qualityDir128, "segment_000.ts"), []byte("data"), 0644)) require.NoError(t, os.WriteFile(filepath.Join(qualityDir128, "segment_001.ts"), []byte("data"), 0644)) qualityDir192 := filepath.Join(trackDir, "192k") require.NoError(t, os.MkdirAll(qualityDir192, 0755)) require.NoError(t, os.WriteFile(filepath.Join(qualityDir192, "segment_000.ts"), []byte("data"), 0644)) require.NoError(t, os.WriteFile(filepath.Join(qualityDir192, "segment_001.ts"), []byte("data"), 0644)) require.NoError(t, os.WriteFile(filepath.Join(qualityDir192, "segment_002.ts"), []byte("data"), 0644)) require.NoError(t, os.WriteFile(filepath.Join(qualityDir192, "segment_003.ts"), []byte("data"), 0644)) qualityDir320 := filepath.Join(trackDir, "320k") require.NoError(t, os.MkdirAll(qualityDir320, 0755)) require.NoError(t, os.WriteFile(filepath.Join(qualityDir320, "segment_000.ts"), []byte("data"), 0644)) count, err := service.countSegments(trackDir) assert.NoError(t, err) // Devrait retourner le maximum (4 segments dans 192k) assert.Equal(t, 4, count) } func TestHLSTranscodeService_CountSegments_OnlySegmentFiles(t *testing.T) { logger := zaptest.NewLogger(t) testDir, cleanup := setupTestHLSDir(t) defer cleanup() service := NewHLSTranscodeService(testDir, logger) // Créer un répertoire de track avec des segments trackDir := filepath.Join(testDir, "track_123") require.NoError(t, os.MkdirAll(trackDir, 0755)) qualityDir := filepath.Join(trackDir, "128k") require.NoError(t, os.MkdirAll(qualityDir, 0755)) // Créer des fichiers segment_*.ts require.NoError(t, os.WriteFile(filepath.Join(qualityDir, "segment_000.ts"), []byte("data"), 0644)) require.NoError(t, os.WriteFile(filepath.Join(qualityDir, "segment_001.ts"), []byte("data"), 0644)) // Créer d'autres fichiers qui ne doivent pas être comptés require.NoError(t, os.WriteFile(filepath.Join(qualityDir, "playlist.m3u8"), []byte("data"), 0644)) require.NoError(t, os.WriteFile(filepath.Join(qualityDir, "other.ts"), []byte("data"), 0644)) require.NoError(t, os.WriteFile(filepath.Join(qualityDir, "segment_other.txt"), []byte("data"), 0644)) count, err := service.countSegments(trackDir) assert.NoError(t, err) // Devrait compter uniquement les fichiers segment_*.ts (2 fichiers) assert.Equal(t, 2, count) } func TestHLSTranscodeService_GetPlaylistDuration(t *testing.T) { logger := zaptest.NewLogger(t) testDir, cleanup := setupTestHLSDir(t) defer cleanup() service := NewHLSTranscodeService(testDir, logger) // Créer une playlist de test playlistContent := `#EXTM3U #EXT-X-VERSION:3 #EXTINF:10.0, segment_000.ts #EXTINF:10.5, segment_001.ts #EXTINF:9.5, segment_002.ts #EXT-X-ENDLIST ` playlistPath := filepath.Join(testDir, "playlist.m3u8") require.NoError(t, os.WriteFile(playlistPath, []byte(playlistContent), 0644)) duration := service.getPlaylistDuration(playlistPath) assert.Equal(t, 30.0, duration) } func TestHLSTranscodeService_GetPlaylistDuration_NonexistentFile(t *testing.T) { logger := zaptest.NewLogger(t) testDir, cleanup := setupTestHLSDir(t) defer cleanup() service := NewHLSTranscodeService(testDir, logger) duration := service.getPlaylistDuration("/nonexistent/playlist.m3u8") assert.Equal(t, 0.0, duration) } func TestHLSTranscodeService_GenerateMasterPlaylist(t *testing.T) { logger := zaptest.NewLogger(t) testDir, cleanup := setupTestHLSDir(t) defer cleanup() service := NewHLSTranscodeService(testDir, logger) // Créer les répertoires et playlists de qualité bitrates := []int{128, 192, 320} for _, bitrate := range bitrates { qualityDir := filepath.Join(testDir, fmt.Sprintf("%dk", bitrate)) require.NoError(t, os.MkdirAll(qualityDir, 0755)) playlistPath := filepath.Join(qualityDir, "playlist.m3u8") require.NoError(t, os.WriteFile(playlistPath, []byte("#EXTM3U\n"), 0644)) } err := service.generateMasterPlaylist(testDir, bitrates) assert.NoError(t, err) // Vérifier que le fichier master.m3u8 a été créé masterPlaylistPath := filepath.Join(testDir, "master.m3u8") assert.FileExists(t, masterPlaylistPath) // Vérifier le contenu content, err := os.ReadFile(masterPlaylistPath) require.NoError(t, err) contentStr := string(content) assert.Contains(t, contentStr, "#EXTM3U") assert.Contains(t, contentStr, "#EXT-X-VERSION:3") assert.Contains(t, contentStr, "128k/playlist.m3u8") assert.Contains(t, contentStr, "192k/playlist.m3u8") assert.Contains(t, contentStr, "320k/playlist.m3u8") } func TestHLSTranscodeService_CleanupTrackDir(t *testing.T) { logger := zaptest.NewLogger(t) testDir, cleanup := setupTestHLSDir(t) defer cleanup() service := NewHLSTranscodeService(testDir, logger) // Créer un répertoire de track // GO-004: Utiliser UUID au lieu de int // Note: CleanupTrackDir uses format "track_", so we need to match that trackID := uuid.New() trackDir := filepath.Join(testDir, fmt.Sprintf("track_%s", trackID.String())) require.NoError(t, os.MkdirAll(trackDir, 0755)) require.NoError(t, os.WriteFile(filepath.Join(trackDir, "test.txt"), []byte("test"), 0644)) // Nettoyer err := service.CleanupTrackDir(trackID) assert.NoError(t, err) assert.NoDirExists(t, trackDir) } func TestHLSTranscodeService_CleanupTrackDir_Nonexistent(t *testing.T) { logger := zaptest.NewLogger(t) testDir, cleanup := setupTestHLSDir(t) defer cleanup() service := NewHLSTranscodeService(testDir, logger) // Nettoyer un répertoire qui n'existe pas (ne devrait pas retourner d'erreur) // GO-004: Utiliser UUID au lieu de int nonexistentTrackID := uuid.New() err := service.CleanupTrackDir(nonexistentTrackID) assert.NoError(t, err) } func TestHLSTranscodeService_TranscodeTrack_WithCustomBitrates(t *testing.T) { logger := zaptest.NewLogger(t) testDir, cleanup := setupTestHLSDir(t) defer cleanup() service := NewHLSTranscodeService(testDir, logger) service.SetBitrates([]int{64, 128}) testAudioFile := filepath.Join(testDir, "test.mp3") track := createTestTrack(t, testAudioFile) ctx := context.Background() result, err := service.TranscodeTrack(ctx, track) // Si ffmpeg n'est pas disponible, on s'attend à une erreur if err != nil { assert.Error(t, err) assert.Nil(t, result) } else { assert.NoError(t, err) assert.NotNil(t, result) assert.Len(t, result.Bitrates, 2) assert.Contains(t, result.Bitrates, 64) assert.Contains(t, result.Bitrates, 128) } } func TestHLSTranscodeService_GetPlaylistDuration_InvalidFormat(t *testing.T) { logger := zaptest.NewLogger(t) testDir, cleanup := setupTestHLSDir(t) defer cleanup() service := NewHLSTranscodeService(testDir, logger) // Créer une playlist avec format invalide playlistContent := `#EXTM3U #EXTINF:invalid, segment_000.ts ` playlistPath := filepath.Join(testDir, "playlist.m3u8") require.NoError(t, os.WriteFile(playlistPath, []byte(playlistContent), 0644)) duration := service.getPlaylistDuration(playlistPath) // Devrait retourner 0 pour format invalide assert.Equal(t, 0.0, duration) } func TestHLSTranscodeService_GetPlaylistDuration_EmptyFile(t *testing.T) { logger := zaptest.NewLogger(t) testDir, cleanup := setupTestHLSDir(t) defer cleanup() service := NewHLSTranscodeService(testDir, logger) playlistPath := filepath.Join(testDir, "empty.m3u8") require.NoError(t, os.WriteFile(playlistPath, []byte(""), 0644)) duration := service.getPlaylistDuration(playlistPath) assert.Equal(t, 0.0, duration) } func TestHLSTranscodeService_GenerateMasterPlaylist_EmptyBitrates(t *testing.T) { logger := zaptest.NewLogger(t) testDir, cleanup := setupTestHLSDir(t) defer cleanup() service := NewHLSTranscodeService(testDir, logger) err := service.generateMasterPlaylist(testDir, []int{}) assert.NoError(t, err) // Vérifier que le fichier master.m3u8 a été créé masterPlaylistPath := filepath.Join(testDir, "master.m3u8") assert.FileExists(t, masterPlaylistPath) // Vérifier le contenu (devrait contenir seulement le header) content, err := os.ReadFile(masterPlaylistPath) require.NoError(t, err) contentStr := string(content) assert.Contains(t, contentStr, "#EXTM3U") assert.Contains(t, contentStr, "#EXT-X-VERSION:3") }