492 lines
13 KiB
Go
492 lines
13 KiB
Go
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 setupTestPlaybackHeatmapServiceDB(t *testing.T) (*gorm.DB, *PlaybackHeatmapService) {
|
|
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 := NewPlaybackHeatmapService(db, logger)
|
|
|
|
return db, service
|
|
}
|
|
|
|
func TestNewPlaybackHeatmapService(t *testing.T) {
|
|
db, _ := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
|
logger := zaptest.NewLogger(t)
|
|
|
|
service := NewPlaybackHeatmapService(db, logger)
|
|
|
|
assert.NotNil(t, service)
|
|
assert.Equal(t, db, service.db)
|
|
assert.NotNil(t, service.logger)
|
|
}
|
|
|
|
func TestNewPlaybackHeatmapService_NilLogger(t *testing.T) {
|
|
db, _ := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
|
|
|
service := NewPlaybackHeatmapService(db, nil)
|
|
|
|
assert.NotNil(t, service)
|
|
assert.NotNil(t, service.logger)
|
|
}
|
|
|
|
func TestPlaybackHeatmapService_GenerateHeatmap_NoSessions(t *testing.T) {
|
|
db, service := setupTestPlaybackHeatmapServiceDB(t)
|
|
ctx := context.Background()
|
|
|
|
// Créer user et track
|
|
userID := uuid.New()
|
|
trackID := uuid.New()
|
|
user := &models.User{ID: userID, Username: "testuser", Email: "test@example.com", IsActive: true}
|
|
db.Create(user)
|
|
track := &models.Track{
|
|
ID: trackID,
|
|
UserID: userID,
|
|
Title: "Test Track",
|
|
FilePath: "/test.mp3",
|
|
FileSize: 1024,
|
|
Format: "MP3",
|
|
Duration: 180, // 3 minutes
|
|
IsPublic: true,
|
|
Status: models.TrackStatusCompleted,
|
|
}
|
|
db.Create(track)
|
|
|
|
result, err := service.GenerateHeatmap(ctx, trackID, 5)
|
|
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, result)
|
|
assert.Equal(t, trackID, result.TrackID)
|
|
assert.Equal(t, 180, result.TrackDuration)
|
|
assert.Equal(t, 5, result.SegmentSize)
|
|
assert.Equal(t, int64(0), result.TotalSessions)
|
|
assert.NotNil(t, result.Segments)
|
|
}
|
|
|
|
func TestPlaybackHeatmapService_GenerateHeatmap_InvalidTrackID(t *testing.T) {
|
|
_, service := setupTestPlaybackHeatmapServiceDB(t)
|
|
ctx := context.Background()
|
|
|
|
result, err := service.GenerateHeatmap(ctx, uuid.Nil, 5)
|
|
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "invalid track ID")
|
|
assert.Nil(t, result)
|
|
}
|
|
|
|
func TestPlaybackHeatmapService_GenerateHeatmap_TrackNotFound(t *testing.T) {
|
|
_, service := setupTestPlaybackHeatmapServiceDB(t)
|
|
ctx := context.Background()
|
|
|
|
result, err := service.GenerateHeatmap(ctx, uuid.New(), 5)
|
|
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "track not found")
|
|
assert.Nil(t, result)
|
|
}
|
|
|
|
func TestPlaybackHeatmapService_GenerateHeatmap_WithSessions(t *testing.T) {
|
|
db, service := setupTestPlaybackHeatmapServiceDB(t)
|
|
ctx := context.Background()
|
|
|
|
// Créer user et track
|
|
userID := uuid.New()
|
|
trackID := uuid.New()
|
|
user := &models.User{ID: userID, Username: "testuser", Email: "test@example.com", IsActive: true}
|
|
db.Create(user)
|
|
track := &models.Track{
|
|
ID: trackID,
|
|
UserID: userID,
|
|
Title: "Test Track",
|
|
FilePath: "/test.mp3",
|
|
FileSize: 1024,
|
|
Format: "MP3",
|
|
Duration: 180, // 3 minutes
|
|
IsPublic: true,
|
|
Status: models.TrackStatusCompleted,
|
|
}
|
|
db.Create(track)
|
|
|
|
// Créer des analytics avec différents temps de lecture
|
|
now := time.Now()
|
|
analytics1 := &models.PlaybackAnalytics{
|
|
TrackID: trackID,
|
|
UserID: userID,
|
|
PlayTime: 90, // 50% de 180
|
|
PauseCount: 2,
|
|
SeekCount: 1,
|
|
CompletionRate: 50.0,
|
|
StartedAt: now,
|
|
CreatedAt: now,
|
|
}
|
|
analytics2 := &models.PlaybackAnalytics{
|
|
TrackID: trackID,
|
|
UserID: userID,
|
|
PlayTime: 180, // 100% de 180
|
|
PauseCount: 0,
|
|
SeekCount: 0,
|
|
CompletionRate: 100.0,
|
|
StartedAt: now,
|
|
CreatedAt: now,
|
|
}
|
|
db.Create(analytics1)
|
|
db.Create(analytics2)
|
|
|
|
result, err := service.GenerateHeatmap(ctx, trackID, 10) // Segments de 10 secondes
|
|
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, result)
|
|
assert.Equal(t, trackID, result.TrackID)
|
|
assert.Equal(t, 180, result.TrackDuration)
|
|
assert.Equal(t, 10, result.SegmentSize)
|
|
assert.Equal(t, int64(2), result.TotalSessions)
|
|
assert.Greater(t, len(result.Segments), 0)
|
|
|
|
// Vérifier que les premiers segments ont été écoutés
|
|
if len(result.Segments) > 0 {
|
|
assert.Greater(t, result.Segments[0].ListenCount, int64(0))
|
|
}
|
|
}
|
|
|
|
func TestPlaybackHeatmapService_GenerateHeatmap_DefaultSegmentSize(t *testing.T) {
|
|
db, service := setupTestPlaybackHeatmapServiceDB(t)
|
|
ctx := context.Background()
|
|
|
|
// Créer user et track
|
|
userID := uuid.New()
|
|
trackID := uuid.New()
|
|
user := &models.User{ID: userID, Username: "testuser", Email: "test@example.com", IsActive: true}
|
|
db.Create(user)
|
|
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)
|
|
|
|
// Utiliser 0 pour le segmentSize (devrait utiliser la valeur par défaut de 5)
|
|
result, err := service.GenerateHeatmap(ctx, trackID, 0)
|
|
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, result)
|
|
assert.Equal(t, 5, result.SegmentSize) // Valeur par défaut
|
|
}
|
|
|
|
func TestPlaybackHeatmapService_GenerateHeatmap_MaxSegmentSize(t *testing.T) {
|
|
db, service := setupTestPlaybackHeatmapServiceDB(t)
|
|
ctx := context.Background()
|
|
|
|
// Créer user et track
|
|
userID := uuid.New()
|
|
trackID := uuid.New()
|
|
user := &models.User{ID: userID, Username: "testuser", Email: "test@example.com", IsActive: true}
|
|
db.Create(user)
|
|
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)
|
|
|
|
// Utiliser un nombre très élevé (devrait être limité à 60)
|
|
result, err := service.GenerateHeatmap(ctx, trackID, 200)
|
|
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, result)
|
|
assert.Equal(t, 60, result.SegmentSize) // Maximum
|
|
}
|
|
|
|
func TestPlaybackHeatmapService_GenerateHeatmap_InvalidDuration(t *testing.T) {
|
|
db, service := setupTestPlaybackHeatmapServiceDB(t)
|
|
ctx := context.Background()
|
|
|
|
// Créer user et track avec durée invalide
|
|
userID := uuid.New()
|
|
trackID := uuid.New()
|
|
user := &models.User{ID: userID, Username: "testuser", Email: "test@example.com", IsActive: true}
|
|
db.Create(user)
|
|
track := &models.Track{
|
|
ID: trackID,
|
|
UserID: userID,
|
|
Title: "Test Track",
|
|
FilePath: "/test.mp3",
|
|
FileSize: 1024,
|
|
Format: "MP3",
|
|
Duration: 0, // Durée invalide
|
|
IsPublic: true,
|
|
Status: models.TrackStatusCompleted,
|
|
}
|
|
db.Create(track)
|
|
|
|
result, err := service.GenerateHeatmap(ctx, trackID, 5)
|
|
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "invalid duration")
|
|
assert.Nil(t, result)
|
|
}
|
|
|
|
func TestPlaybackHeatmapService_CalculateListenedZones(t *testing.T) {
|
|
_, service := setupTestPlaybackHeatmapServiceDB(t)
|
|
|
|
// Créer des analytics avec différents temps de lecture
|
|
analytics := []models.PlaybackAnalytics{
|
|
{PlayTime: 30, CompletionRate: 16.67}, // 30 secondes sur 180
|
|
{PlayTime: 60, CompletionRate: 33.33}, // 60 secondes sur 180
|
|
{PlayTime: 180, CompletionRate: 100.0}, // 180 secondes (complet)
|
|
}
|
|
|
|
zones := service.calculateListenedZones(analytics, 180, 10) // Segments de 10 secondes
|
|
|
|
assert.NotNil(t, zones)
|
|
assert.Greater(t, len(zones), 0)
|
|
|
|
// Vérifier que les premiers segments ont été écoutés
|
|
if zones[0] != nil {
|
|
assert.Greater(t, zones[0].ListenCount, int64(0))
|
|
}
|
|
|
|
// Vérifier que le segment 0-10 a été écouté par toutes les sessions
|
|
if zones[0] != nil {
|
|
assert.Equal(t, int64(3), zones[0].ListenCount) // 3 sessions ont atteint le premier segment
|
|
}
|
|
}
|
|
|
|
func TestPlaybackHeatmapService_CalculateSkipZones(t *testing.T) {
|
|
_, service := setupTestPlaybackHeatmapServiceDB(t)
|
|
|
|
// Créer des analytics avec des seeks (indiquant des skips)
|
|
analytics := []models.PlaybackAnalytics{
|
|
{PlayTime: 30, SeekCount: 2, CompletionRate: 16.67}, // 2 seeks, lecture courte
|
|
{PlayTime: 60, SeekCount: 1, CompletionRate: 33.33}, // 1 seek
|
|
{PlayTime: 180, SeekCount: 0, CompletionRate: 100.0}, // Pas de seeks
|
|
}
|
|
|
|
zones := service.calculateSkipZones(analytics, 180, 10)
|
|
|
|
assert.NotNil(t, zones)
|
|
assert.Greater(t, len(zones), 0)
|
|
}
|
|
|
|
func TestPlaybackHeatmapService_GenerateHeatmapSegments(t *testing.T) {
|
|
_, service := setupTestPlaybackHeatmapServiceDB(t)
|
|
|
|
// Créer des zones écoutées et skip
|
|
listenedZones := make(map[int]*ListenedZone)
|
|
listenedZones[0] = &ListenedZone{
|
|
StartTime: 0.0,
|
|
EndTime: 10.0,
|
|
ListenCount: 3,
|
|
TotalPlayTime: 30.0,
|
|
SessionCount: 3,
|
|
}
|
|
listenedZones[1] = &ListenedZone{
|
|
StartTime: 10.0,
|
|
EndTime: 20.0,
|
|
ListenCount: 2,
|
|
TotalPlayTime: 20.0,
|
|
SessionCount: 2,
|
|
}
|
|
|
|
skipZones := make(map[int]*SkipZone)
|
|
skipZones[0] = &SkipZone{
|
|
StartTime: 0.0,
|
|
EndTime: 10.0,
|
|
SkipCount: 0,
|
|
}
|
|
skipZones[1] = &SkipZone{
|
|
StartTime: 10.0,
|
|
EndTime: 20.0,
|
|
SkipCount: 1,
|
|
}
|
|
|
|
segments := service.generateHeatmapSegments(listenedZones, skipZones, 180, 10)
|
|
|
|
assert.NotNil(t, segments)
|
|
assert.Greater(t, len(segments), 0)
|
|
|
|
// Vérifier que les segments ont des intensités calculées
|
|
if len(segments) > 0 {
|
|
assert.GreaterOrEqual(t, segments[0].Intensity, 0.0)
|
|
assert.Greater(t, segments[0].ListenCount, int64(0))
|
|
}
|
|
}
|
|
|
|
func TestPlaybackHeatmapService_GetHeatmapIntensityArray(t *testing.T) {
|
|
db, service := setupTestPlaybackHeatmapServiceDB(t)
|
|
ctx := context.Background()
|
|
|
|
// Créer user et track
|
|
userID := uuid.New()
|
|
trackID := uuid.New()
|
|
user := &models.User{ID: userID, Username: "testuser", Email: "test@example.com", IsActive: true}
|
|
db.Create(user)
|
|
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 analytics
|
|
now := time.Now()
|
|
analytics := &models.PlaybackAnalytics{
|
|
TrackID: trackID,
|
|
UserID: userID,
|
|
PlayTime: 90,
|
|
PauseCount: 1,
|
|
SeekCount: 0,
|
|
CompletionRate: 50.0,
|
|
StartedAt: now,
|
|
CreatedAt: now,
|
|
}
|
|
db.Create(analytics)
|
|
|
|
intensities, err := service.GetHeatmapIntensityArray(ctx, trackID, 10)
|
|
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, intensities)
|
|
assert.Greater(t, len(intensities), 0)
|
|
|
|
// Vérifier que les intensités sont normalisées (0-1)
|
|
for _, intensity := range intensities {
|
|
assert.GreaterOrEqual(t, intensity, 0.0)
|
|
assert.LessOrEqual(t, intensity, 1.0)
|
|
}
|
|
}
|
|
|
|
func TestPlaybackHeatmapService_GenerateHeatmap_WithSkips(t *testing.T) {
|
|
db, service := setupTestPlaybackHeatmapServiceDB(t)
|
|
ctx := context.Background()
|
|
|
|
// Créer user et track
|
|
userID := uuid.New()
|
|
trackID := uuid.New()
|
|
user := &models.User{ID: userID, Username: "testuser", Email: "test@example.com", IsActive: true}
|
|
db.Create(user)
|
|
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 analytics avec des seeks (skips)
|
|
now := time.Now()
|
|
analytics := &models.PlaybackAnalytics{
|
|
TrackID: trackID,
|
|
UserID: userID,
|
|
PlayTime: 60,
|
|
PauseCount: 0,
|
|
SeekCount: 3, // 3 seeks = skips
|
|
CompletionRate: 33.33,
|
|
StartedAt: now,
|
|
CreatedAt: now,
|
|
}
|
|
db.Create(analytics)
|
|
|
|
result, err := service.GenerateHeatmap(ctx, trackID, 10)
|
|
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, result)
|
|
assert.Greater(t, len(result.Segments), 0)
|
|
|
|
// Vérifier qu'il y a des skips détectés (ou pas, selon le seuil)
|
|
// Note: Les skips peuvent ne pas être détectés si le ratio est trop faible
|
|
// C'est un comportement attendu basé sur le seuil de 0.1
|
|
_ = result.Segments // Utilisé pour vérifier la structure
|
|
}
|
|
|
|
func TestPlaybackHeatmapService_GenerateHeatmap_IntensityNormalization(t *testing.T) {
|
|
db, service := setupTestPlaybackHeatmapServiceDB(t)
|
|
ctx := context.Background()
|
|
|
|
// Créer user et track
|
|
userID := uuid.New()
|
|
trackID := uuid.New()
|
|
user := &models.User{ID: userID, Username: "testuser", Email: "test@example.com", IsActive: true}
|
|
db.Create(user)
|
|
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 plusieurs analytics pour avoir des intensités variées
|
|
now := time.Now()
|
|
for i := 0; i < 5; i++ {
|
|
analytics := &models.PlaybackAnalytics{
|
|
TrackID: trackID,
|
|
UserID: userID,
|
|
PlayTime: 90 + (i * 10),
|
|
PauseCount: 0,
|
|
SeekCount: 0,
|
|
CompletionRate: float64(50 + i*5),
|
|
StartedAt: now,
|
|
CreatedAt: now,
|
|
}
|
|
db.Create(analytics)
|
|
}
|
|
|
|
result, err := service.GenerateHeatmap(ctx, trackID, 10)
|
|
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, result)
|
|
|
|
// Vérifier que les intensités sont normalisées (0-1)
|
|
for _, seg := range result.Segments {
|
|
assert.GreaterOrEqual(t, seg.Intensity, 0.0)
|
|
assert.LessOrEqual(t, seg.Intensity, 1.0)
|
|
}
|
|
}
|