567 lines
16 KiB
Go
567 lines
16 KiB
Go
package services
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/google/uuid"
|
|
|
|
"veza-backend-api/internal/models"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"go.uber.org/zap/zaptest"
|
|
"gorm.io/driver/sqlite"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
func setupTestHLSService(t *testing.T) (*HLSService, *gorm.DB, string, func()) {
|
|
// Setup in-memory database
|
|
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
|
require.NoError(t, err)
|
|
|
|
// Enable foreign keys
|
|
db.Exec("PRAGMA foreign_keys = ON")
|
|
|
|
// Auto-migrate
|
|
err = db.AutoMigrate(&models.User{}, &models.Track{}, &models.HLSStream{})
|
|
require.NoError(t, err)
|
|
|
|
userID := uuid.New()
|
|
// Create test user
|
|
user := &models.User{
|
|
ID: userID,
|
|
Username: "testuser",
|
|
Email: "test@example.com",
|
|
IsActive: true,
|
|
}
|
|
err = db.Create(user).Error
|
|
require.NoError(t, err)
|
|
|
|
// Create test track
|
|
track := &models.Track{
|
|
UserID: userID,
|
|
Title: "Test Track",
|
|
FilePath: "/test/track.mp3",
|
|
FileSize: 5 * 1024 * 1024,
|
|
Format: "MP3",
|
|
Duration: 180,
|
|
IsPublic: true,
|
|
Status: models.TrackStatusCompleted,
|
|
}
|
|
err = db.Create(track).Error
|
|
require.NoError(t, err)
|
|
|
|
// Create test directory structure
|
|
testDir := filepath.Join(os.TempDir(), fmt.Sprintf("hls_service_test_%d", os.Getpid()))
|
|
require.NoError(t, os.MkdirAll(testDir, 0755))
|
|
|
|
trackDir := filepath.Join(testDir, fmt.Sprintf("track_%s", track.ID.String()))
|
|
require.NoError(t, os.MkdirAll(trackDir, 0755))
|
|
|
|
// Create master playlist
|
|
masterPlaylistPath := filepath.Join(trackDir, "master.m3u8")
|
|
masterPlaylistContent := `#EXTM3U
|
|
#EXT-X-VERSION:3
|
|
#EXT-X-STREAM-INF:BANDWIDTH=128000
|
|
128k/playlist.m3u8
|
|
`
|
|
require.NoError(t, os.WriteFile(masterPlaylistPath, []byte(masterPlaylistContent), 0644))
|
|
|
|
// Create quality playlist
|
|
qualityDir := filepath.Join(trackDir, "128k")
|
|
require.NoError(t, os.MkdirAll(qualityDir, 0755))
|
|
qualityPlaylistPath := filepath.Join(qualityDir, "playlist.m3u8")
|
|
qualityPlaylistContent := `#EXTM3U
|
|
#EXT-X-VERSION:3
|
|
#EXTINF:10.0,
|
|
segment_000.ts
|
|
`
|
|
require.NoError(t, os.WriteFile(qualityPlaylistPath, []byte(qualityPlaylistContent), 0644))
|
|
|
|
// Create test segment
|
|
segmentPath := filepath.Join(qualityDir, "segment_000.ts")
|
|
require.NoError(t, os.WriteFile(segmentPath, []byte("test segment data"), 0644))
|
|
|
|
// Create HLS stream
|
|
hlsStream := &models.HLSStream{
|
|
TrackID: track.ID,
|
|
PlaylistURL: filepath.Join(fmt.Sprintf("track_%s", track.ID.String()), "master.m3u8"),
|
|
SegmentsCount: 1,
|
|
Bitrates: models.BitrateList{128},
|
|
Status: models.HLSStatusReady,
|
|
}
|
|
err = db.Create(hlsStream).Error
|
|
require.NoError(t, err)
|
|
|
|
// Create service
|
|
logger := zaptest.NewLogger(t)
|
|
service := NewHLSService(db, testDir, logger)
|
|
|
|
cleanup := func() {
|
|
os.RemoveAll(testDir)
|
|
}
|
|
|
|
return service, db, testDir, cleanup
|
|
}
|
|
|
|
func TestNewHLSService(t *testing.T) {
|
|
logger := zaptest.NewLogger(t)
|
|
db, _ := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
|
service := NewHLSService(db, "/tmp", logger)
|
|
|
|
assert.NotNil(t, service)
|
|
assert.Equal(t, "/tmp", service.outputDir)
|
|
assert.NotNil(t, service.logger)
|
|
}
|
|
|
|
func TestNewHLSService_NilLogger(t *testing.T) {
|
|
db, _ := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
|
service := NewHLSService(db, "/tmp", nil)
|
|
|
|
assert.NotNil(t, service)
|
|
assert.NotNil(t, service.logger) // Devrait créer un logger Nop
|
|
}
|
|
|
|
func TestHLSService_GetMasterPlaylist(t *testing.T) {
|
|
service, _, _, cleanup := setupTestHLSService(t)
|
|
defer cleanup()
|
|
|
|
ctx := context.Background()
|
|
// We need to find the track ID created in setup
|
|
// Since we can't easily get it returned from setup without changing signature,
|
|
// let's query the DB or rely on the fact that setup creates one track.
|
|
// Actually, setupTestHLSService returns (service, db, testDir, cleanup).
|
|
// We can query the DB.
|
|
var track models.Track
|
|
result := service.db.First(&track)
|
|
require.NoError(t, result.Error)
|
|
|
|
playlist, err := service.GetMasterPlaylist(ctx, track.ID)
|
|
|
|
assert.NoError(t, err)
|
|
assert.Contains(t, playlist, "#EXTM3U")
|
|
assert.Contains(t, playlist, "128k/playlist.m3u8")
|
|
}
|
|
|
|
func TestHLSService_GetMasterPlaylist_NotFound(t *testing.T) {
|
|
service, _, _, cleanup := setupTestHLSService(t)
|
|
defer cleanup()
|
|
|
|
ctx := context.Background()
|
|
playlist, err := service.GetMasterPlaylist(ctx, uuid.New())
|
|
|
|
assert.Error(t, err)
|
|
assert.Empty(t, playlist)
|
|
assert.Contains(t, err.Error(), "not found")
|
|
}
|
|
|
|
func TestHLSService_GetQualityPlaylist(t *testing.T) {
|
|
service, _, _, cleanup := setupTestHLSService(t)
|
|
defer cleanup()
|
|
|
|
ctx := context.Background()
|
|
var track models.Track
|
|
service.db.First(&track)
|
|
|
|
playlist, err := service.GetQualityPlaylist(ctx, track.ID, "128k")
|
|
|
|
assert.NoError(t, err)
|
|
assert.Contains(t, playlist, "#EXTM3U")
|
|
assert.Contains(t, playlist, "segment_000.ts")
|
|
}
|
|
|
|
func TestHLSService_GetQualityPlaylist_NotFound(t *testing.T) {
|
|
service, _, _, cleanup := setupTestHLSService(t)
|
|
defer cleanup()
|
|
|
|
ctx := context.Background()
|
|
playlist, err := service.GetQualityPlaylist(ctx, uuid.New(), "128k")
|
|
|
|
assert.Error(t, err)
|
|
assert.Empty(t, playlist)
|
|
assert.Contains(t, err.Error(), "not found")
|
|
}
|
|
|
|
func TestHLSService_GetQualityPlaylist_InvalidBitrate(t *testing.T) {
|
|
service, _, _, cleanup := setupTestHLSService(t)
|
|
defer cleanup()
|
|
|
|
ctx := context.Background()
|
|
var track models.Track
|
|
service.db.First(&track)
|
|
|
|
playlist, err := service.GetQualityPlaylist(ctx, track.ID, "999k")
|
|
|
|
assert.Error(t, err)
|
|
assert.Empty(t, playlist)
|
|
assert.Contains(t, err.Error(), "not found")
|
|
}
|
|
|
|
func TestHLSService_GetSegmentPath(t *testing.T) {
|
|
service, _, _, cleanup := setupTestHLSService(t)
|
|
defer cleanup()
|
|
|
|
ctx := context.Background()
|
|
var track models.Track
|
|
service.db.First(&track)
|
|
|
|
segmentPath, err := service.GetSegmentPath(ctx, track.ID, "128k", "segment_000.ts")
|
|
|
|
assert.NoError(t, err)
|
|
assert.NotEmpty(t, segmentPath)
|
|
assert.FileExists(t, segmentPath)
|
|
}
|
|
|
|
func TestHLSService_GetSegmentPath_NotFound(t *testing.T) {
|
|
service, _, _, cleanup := setupTestHLSService(t)
|
|
defer cleanup()
|
|
|
|
ctx := context.Background()
|
|
segmentPath, err := service.GetSegmentPath(ctx, uuid.New(), "128k", "segment_000.ts")
|
|
|
|
assert.Error(t, err)
|
|
assert.Empty(t, segmentPath)
|
|
assert.Contains(t, err.Error(), "not found")
|
|
}
|
|
|
|
func TestHLSService_GetSegmentPath_InvalidSegment(t *testing.T) {
|
|
service, _, _, cleanup := setupTestHLSService(t)
|
|
defer cleanup()
|
|
|
|
ctx := context.Background()
|
|
var track models.Track
|
|
service.db.First(&track)
|
|
|
|
segmentPath, err := service.GetSegmentPath(ctx, track.ID, "128k", "nonexistent.ts")
|
|
|
|
assert.Error(t, err)
|
|
assert.Empty(t, segmentPath)
|
|
assert.Contains(t, err.Error(), "not found")
|
|
}
|
|
|
|
func TestHLSService_GetSegmentPath_DirectoryTraversal(t *testing.T) {
|
|
service, _, _, cleanup := setupTestHLSService(t)
|
|
defer cleanup()
|
|
|
|
ctx := context.Background()
|
|
var track models.Track
|
|
service.db.First(&track)
|
|
// Tentative de directory traversal
|
|
segmentPath, err := service.GetSegmentPath(ctx, track.ID, "128k", "../../../etc/passwd")
|
|
|
|
assert.Error(t, err)
|
|
assert.Empty(t, segmentPath)
|
|
// Le fichier n'existe pas, donc erreur "not found" ou "invalid path"
|
|
assert.True(t, err != nil)
|
|
}
|
|
|
|
func TestHLSService_GetStreamStatus(t *testing.T) {
|
|
service, _, _, cleanup := setupTestHLSService(t)
|
|
defer cleanup()
|
|
|
|
ctx := context.Background()
|
|
var track models.Track
|
|
service.db.First(&track)
|
|
|
|
status, err := service.GetStreamStatus(ctx, track.ID)
|
|
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, status)
|
|
assert.Equal(t, models.HLSStatusReady, status["status"])
|
|
assert.Equal(t, models.BitrateList{128}, status["bitrates"])
|
|
assert.Equal(t, 1, status["segments_count"])
|
|
assert.Contains(t, status["playlist_url"], "master.m3u8")
|
|
assert.Equal(t, track.ID, status["track_id"])
|
|
}
|
|
|
|
func TestHLSService_GetStreamStatus_NotFound(t *testing.T) {
|
|
service, _, _, cleanup := setupTestHLSService(t)
|
|
defer cleanup()
|
|
|
|
ctx := context.Background()
|
|
status, err := service.GetStreamStatus(ctx, uuid.New())
|
|
|
|
assert.Error(t, err)
|
|
assert.Nil(t, status)
|
|
assert.Contains(t, err.Error(), "not found")
|
|
}
|
|
|
|
func TestHLSService_GetStreamStatus_Processing(t *testing.T) {
|
|
logger := zaptest.NewLogger(t)
|
|
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.HLSStream{}, &models.HLSTranscodeQueue{})
|
|
require.NoError(t, err)
|
|
|
|
userID := uuid.New()
|
|
// Create test user
|
|
user := &models.User{
|
|
ID: userID,
|
|
Username: "testuser",
|
|
Email: "test@example.com",
|
|
IsActive: true,
|
|
}
|
|
err = db.Create(user).Error
|
|
require.NoError(t, err)
|
|
|
|
// Create test track
|
|
track := &models.Track{
|
|
UserID: userID,
|
|
Title: "Test Track",
|
|
FilePath: "/test/track.mp3",
|
|
FileSize: 5 * 1024 * 1024,
|
|
Format: "MP3",
|
|
Duration: 180,
|
|
IsPublic: true,
|
|
Status: models.TrackStatusProcessing,
|
|
}
|
|
err = db.Create(track).Error
|
|
require.NoError(t, err)
|
|
|
|
// Create HLS stream with processing status
|
|
hlsStream := &models.HLSStream{
|
|
TrackID: track.ID,
|
|
PlaylistURL: "track_1/master.m3u8",
|
|
SegmentsCount: 0,
|
|
Bitrates: models.BitrateList{},
|
|
Status: models.HLSStatusProcessing,
|
|
}
|
|
err = db.Create(hlsStream).Error
|
|
require.NoError(t, err)
|
|
|
|
// Create queue job
|
|
queueJob := &models.HLSTranscodeQueue{
|
|
TrackID: track.ID,
|
|
Priority: 5,
|
|
Status: models.QueueStatusProcessing,
|
|
RetryCount: 0,
|
|
MaxRetries: 3,
|
|
}
|
|
err = db.Create(queueJob).Error
|
|
require.NoError(t, err)
|
|
|
|
// Create service
|
|
testDir := filepath.Join(os.TempDir(), fmt.Sprintf("hls_service_test_%d", os.Getpid()))
|
|
service := NewHLSService(db, testDir, logger)
|
|
|
|
ctx := context.Background()
|
|
status, err := service.GetStreamStatus(ctx, track.ID)
|
|
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, status)
|
|
assert.Equal(t, models.HLSStatusProcessing, status["status"])
|
|
assert.Equal(t, queueJob.ID, status["queue_job_id"])
|
|
assert.Equal(t, queueJob.RetryCount, status["retry_count"])
|
|
}
|
|
|
|
func TestHLSService_TriggerTranscode(t *testing.T) {
|
|
// Setup
|
|
logger := zaptest.NewLogger(t)
|
|
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.HLSStream{})
|
|
require.NoError(t, err)
|
|
|
|
userID := uuid.New()
|
|
// Create test user
|
|
user := &models.User{
|
|
ID: userID,
|
|
Username: "testuser",
|
|
Email: "test@example.com",
|
|
IsActive: true,
|
|
}
|
|
err = db.Create(user).Error
|
|
require.NoError(t, err)
|
|
|
|
// Create test directory
|
|
testDir := filepath.Join(os.TempDir(), fmt.Sprintf("hls_trigger_test_%d", os.Getpid()))
|
|
require.NoError(t, os.MkdirAll(testDir, 0755))
|
|
defer os.RemoveAll(testDir)
|
|
|
|
// Create test track with audio file
|
|
testAudioFile := filepath.Join(testDir, "test.mp3")
|
|
require.NoError(t, os.WriteFile(testAudioFile, []byte("fake audio content"), 0644))
|
|
|
|
track := &models.Track{
|
|
UserID: userID,
|
|
Title: "Test Track",
|
|
FilePath: testAudioFile,
|
|
FileSize: 1024,
|
|
Format: "mp3",
|
|
Duration: 180,
|
|
Status: models.TrackStatusCompleted,
|
|
}
|
|
err = db.Create(track).Error
|
|
require.NoError(t, err)
|
|
|
|
// Create transcode service
|
|
transcodeService := NewHLSTranscodeService(testDir, logger)
|
|
hlsService := NewHLSServiceWithTranscode(db, testDir, transcodeService, logger)
|
|
|
|
ctx := context.Background()
|
|
|
|
// Note: Ce test échouera si ffmpeg n'est pas installé
|
|
// C'est acceptable car c'est un test d'intégration
|
|
err = hlsService.TriggerTranscode(ctx, track)
|
|
|
|
if err != nil {
|
|
// Si ffmpeg n'est pas disponible, vérifier que l'erreur est logique
|
|
assert.Error(t, err)
|
|
// Vérifier qu'un stream a été créé avec statut "failed"
|
|
var stream models.HLSStream
|
|
err = db.Where("track_id = ?", track.ID).First(&stream).Error
|
|
if err == nil {
|
|
assert.Equal(t, models.HLSStatusFailed, stream.Status)
|
|
}
|
|
} else {
|
|
// Si ffmpeg est disponible, vérifier que le stream a été créé avec succès
|
|
var stream models.HLSStream
|
|
err = db.Where("track_id = ?", track.ID).First(&stream).Error
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, models.HLSStatusReady, stream.Status)
|
|
assert.NotEmpty(t, stream.PlaylistURL)
|
|
assert.Greater(t, stream.SegmentsCount, 0)
|
|
}
|
|
}
|
|
|
|
func TestHLSService_TriggerTranscode_NilTrack(t *testing.T) {
|
|
logger := zaptest.NewLogger(t)
|
|
db, _ := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
|
transcodeService := NewHLSTranscodeService("/tmp", logger)
|
|
service := NewHLSServiceWithTranscode(db, "/tmp", transcodeService, logger)
|
|
|
|
ctx := context.Background()
|
|
err := service.TriggerTranscode(ctx, nil)
|
|
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "track cannot be nil")
|
|
}
|
|
|
|
func TestHLSService_TriggerTranscode_NoTranscodeService(t *testing.T) {
|
|
logger := zaptest.NewLogger(t)
|
|
db, _ := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
|
service := NewHLSService(db, "/tmp", logger)
|
|
|
|
track := &models.Track{
|
|
ID: uuid.New(),
|
|
Title: "Test Track",
|
|
FilePath: "/test/track.mp3",
|
|
}
|
|
|
|
ctx := context.Background()
|
|
err := service.TriggerTranscode(ctx, track)
|
|
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "transcode service not configured")
|
|
}
|
|
|
|
func TestHLSService_TriggerTranscode_AlreadyExists(t *testing.T) {
|
|
logger := zaptest.NewLogger(t)
|
|
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.HLSStream{})
|
|
require.NoError(t, err)
|
|
|
|
userID := uuid.New()
|
|
// Create test user
|
|
user := &models.User{
|
|
ID: userID,
|
|
Username: "testuser",
|
|
Email: "test@example.com",
|
|
IsActive: true,
|
|
}
|
|
err = db.Create(user).Error
|
|
require.NoError(t, err)
|
|
|
|
// Create test track
|
|
track := &models.Track{
|
|
UserID: userID,
|
|
Title: "Test Track",
|
|
FilePath: "/test/track.mp3",
|
|
FileSize: 1024,
|
|
Format: "mp3",
|
|
Duration: 180,
|
|
Status: models.TrackStatusCompleted,
|
|
}
|
|
err = db.Create(track).Error
|
|
require.NoError(t, err)
|
|
|
|
// Create existing HLS stream with ready status
|
|
hlsStream := &models.HLSStream{
|
|
TrackID: track.ID,
|
|
PlaylistURL: "/test/master.m3u8",
|
|
Status: models.HLSStatusReady,
|
|
}
|
|
err = db.Create(hlsStream).Error
|
|
require.NoError(t, err)
|
|
|
|
transcodeService := NewHLSTranscodeService("/tmp", logger)
|
|
service := NewHLSServiceWithTranscode(db, "/tmp", transcodeService, logger)
|
|
|
|
ctx := context.Background()
|
|
err = service.TriggerTranscode(ctx, track)
|
|
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "already exists and is ready")
|
|
}
|
|
|
|
func TestHLSService_TriggerTranscode_AlreadyProcessing(t *testing.T) {
|
|
logger := zaptest.NewLogger(t)
|
|
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.HLSStream{})
|
|
require.NoError(t, err)
|
|
|
|
userID := uuid.New()
|
|
// Create test user
|
|
user := &models.User{
|
|
ID: userID,
|
|
Username: "testuser",
|
|
Email: "test@example.com",
|
|
IsActive: true,
|
|
}
|
|
err = db.Create(user).Error
|
|
require.NoError(t, err)
|
|
|
|
// Create test track
|
|
track := &models.Track{
|
|
UserID: userID,
|
|
Title: "Test Track",
|
|
FilePath: "/test/track.mp3",
|
|
FileSize: 1024,
|
|
Format: "mp3",
|
|
Duration: 180,
|
|
Status: models.TrackStatusProcessing,
|
|
}
|
|
err = db.Create(track).Error
|
|
require.NoError(t, err)
|
|
|
|
// Create existing HLS stream with processing status
|
|
hlsStream := &models.HLSStream{
|
|
TrackID: track.ID,
|
|
Status: models.HLSStatusProcessing,
|
|
}
|
|
err = db.Create(hlsStream).Error
|
|
require.NoError(t, err)
|
|
|
|
transcodeService := NewHLSTranscodeService("/tmp", logger)
|
|
service := NewHLSServiceWithTranscode(db, "/tmp", transcodeService, logger)
|
|
|
|
ctx := context.Background()
|
|
err = service.TriggerTranscode(ctx, track)
|
|
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "already being processed")
|
|
}
|