veza/veza-backend-api/internal/services/playback_heatmap_service_test.go

491 lines
13 KiB
Go

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 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)
}
}