veza/veza-backend-api/internal/services/playback_segmentation_service_test.go
2026-03-05 23:03:43 +01:00

480 lines
14 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 setupTestPlaybackSegmentationServiceDB(t *testing.T) (*gorm.DB, *PlaybackSegmentationService) {
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 := NewPlaybackSegmentationService(db, logger)
return db, service
}
func TestNewPlaybackSegmentationService(t *testing.T) {
db, _ := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
logger := zaptest.NewLogger(t)
service := NewPlaybackSegmentationService(db, logger)
assert.NotNil(t, service)
assert.Equal(t, db, service.db)
assert.NotNil(t, service.logger)
}
func TestNewPlaybackSegmentationService_NilLogger(t *testing.T) {
db, _ := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
service := NewPlaybackSegmentationService(db, nil)
assert.NotNil(t, service)
assert.NotNil(t, service.logger)
}
func TestPlaybackSegmentationService_SegmentUsers_NoSessions(t *testing.T) {
db, service := setupTestPlaybackSegmentationServiceDB(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)
result, err := service.SegmentUsers(ctx, trackID)
require.NoError(t, err)
assert.NotNil(t, result)
assert.Equal(t, trackID, result.TrackID)
assert.Equal(t, int64(0), result.TotalUsers)
assert.NotNil(t, result.Segments)
assert.NotNil(t, result.UserMetrics)
}
func TestPlaybackSegmentationService_SegmentUsers_InvalidTrackID(t *testing.T) {
_, service := setupTestPlaybackSegmentationServiceDB(t)
ctx := context.Background()
result, err := service.SegmentUsers(ctx, uuid.Nil)
assert.Error(t, err)
assert.Contains(t, err.Error(), "invalid track ID")
assert.Nil(t, result)
}
func TestPlaybackSegmentationService_SegmentUsers_TrackNotFound(t *testing.T) {
_, service := setupTestPlaybackSegmentationServiceDB(t)
ctx := context.Background()
result, err := service.SegmentUsers(ctx, uuid.New())
assert.Error(t, err)
assert.Contains(t, err.Error(), "track not found")
assert.Nil(t, result)
}
func TestPlaybackSegmentationService_SegmentUsers_WithSessions(t *testing.T) {
db, service := setupTestPlaybackSegmentationServiceDB(t)
ctx := context.Background()
// Créer users et track
user1ID := uuid.New()
user2ID := uuid.New()
trackID := uuid.New()
user1 := &models.User{ID: user1ID, Username: "user1", Slug: "user1", Email: "user1@example.com", IsActive: true}
user2 := &models.User{ID: user2ID, Username: "user2", Slug: "user2", Email: "user2@example.com", IsActive: true}
db.Create(user1)
db.Create(user2)
track := &models.Track{
ID: trackID,
UserID: user1ID,
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 différents niveaux d'engagement
now := time.Now()
// User 1: High engagement (completion élevé, peu de pauses/seeks)
analytics1 := &models.PlaybackAnalytics{
TrackID: trackID,
UserID: user1ID,
PlayTime: 180,
PauseCount: 0,
SeekCount: 0,
CompletionRate: 100.0,
StartedAt: now,
CreatedAt: now,
}
analytics2 := &models.PlaybackAnalytics{
TrackID: trackID,
UserID: user1ID,
PlayTime: 180,
PauseCount: 1,
SeekCount: 0,
CompletionRate: 95.0,
StartedAt: now,
CreatedAt: now,
}
// User 2: Low engagement (completion faible, beaucoup de pauses/seeks)
analytics3 := &models.PlaybackAnalytics{
TrackID: trackID,
UserID: user2ID,
PlayTime: 45,
PauseCount: 5,
SeekCount: 3,
CompletionRate: 25.0,
StartedAt: now,
CreatedAt: now,
}
db.Create(analytics1)
db.Create(analytics2)
db.Create(analytics3)
result, err := service.SegmentUsers(ctx, trackID)
require.NoError(t, err)
assert.NotNil(t, result)
assert.Equal(t, trackID, result.TrackID)
assert.Equal(t, int64(2), result.TotalUsers)
assert.NotNil(t, result.Segments)
assert.Greater(t, len(result.Segments), 0)
// Vérifier que les segments sont créés
assert.Contains(t, result.Segments, SegmentHighEngagement)
assert.Contains(t, result.Segments, SegmentLowEngagement)
}
func TestPlaybackSegmentationService_SegmentByEngagement(t *testing.T) {
_, service := setupTestPlaybackSegmentationServiceDB(t)
user1ID := uuid.New()
user2ID := uuid.New()
user3ID := uuid.New()
userMetrics := map[uuid.UUID]*UserMetrics{
user1ID: {UserID: user1ID, EngagementScore: 85.0}, // High
user2ID: {UserID: user2ID, EngagementScore: 60.0}, // Medium
user3ID: {UserID: user3ID, EngagementScore: 30.0}, // Low
}
segments := service.segmentByEngagement(userMetrics)
assert.Contains(t, segments, SegmentHighEngagement)
assert.Contains(t, segments, SegmentMediumEngagement)
assert.Contains(t, segments, SegmentLowEngagement)
assert.Contains(t, segments[SegmentHighEngagement], user1ID)
assert.Contains(t, segments[SegmentMediumEngagement], user2ID)
assert.Contains(t, segments[SegmentLowEngagement], user3ID)
}
func TestPlaybackSegmentationService_SegmentByCompletionRate(t *testing.T) {
_, service := setupTestPlaybackSegmentationServiceDB(t)
user1ID := uuid.New()
user2ID := uuid.New()
user3ID := uuid.New()
userMetrics := map[uuid.UUID]*UserMetrics{
user1ID: {UserID: user1ID, AverageCompletion: 90.0}, // High
user2ID: {UserID: user2ID, AverageCompletion: 60.0}, // Medium
user3ID: {UserID: user3ID, AverageCompletion: 30.0}, // Low
}
segments := service.segmentByCompletionRate(userMetrics)
assert.Contains(t, segments, SegmentHighCompletion)
assert.Contains(t, segments, SegmentMediumCompletion)
assert.Contains(t, segments, SegmentLowCompletion)
assert.Contains(t, segments[SegmentHighCompletion], user1ID)
assert.Contains(t, segments[SegmentMediumCompletion], user2ID)
assert.Contains(t, segments[SegmentLowCompletion], user3ID)
}
func TestPlaybackSegmentationService_SegmentByBehavior(t *testing.T) {
_, service := setupTestPlaybackSegmentationServiceDB(t)
user1ID := uuid.New()
user2ID := uuid.New()
user3ID := uuid.New()
user4ID := uuid.New()
userMetrics := map[uuid.UUID]*UserMetrics{
user1ID: {UserID: user1ID, SessionCount: 10, AverageSeeks: 0.5, AverageCompletion: 80.0}, // Active + Focused
user2ID: {UserID: user2ID, SessionCount: 1, AverageSeeks: 0.2, AverageCompletion: 75.0}, // Casual + Focused
user3ID: {UserID: user3ID, SessionCount: 5, AverageSeeks: 5.0, AverageCompletion: 50.0}, // Frequent skipper
user4ID: {UserID: user4ID, SessionCount: 2, AverageSeeks: 0.1, AverageCompletion: 60.0}, // Casual
}
segments := service.segmentByBehavior(userMetrics)
assert.Contains(t, segments, SegmentActiveListener)
assert.Contains(t, segments, SegmentCasualListener)
assert.Contains(t, segments, SegmentFrequentSkipper)
assert.Contains(t, segments, SegmentFocusedListener)
}
func TestPlaybackSegmentationService_CalculateUserMetrics(t *testing.T) {
_, service := setupTestPlaybackSegmentationServiceDB(t)
user1ID := uuid.New()
user2ID := uuid.New()
analytics := []models.PlaybackAnalytics{
{UserID: user1ID, PlayTime: 180, PauseCount: 0, SeekCount: 0, CompletionRate: 100.0},
{UserID: user1ID, PlayTime: 180, PauseCount: 1, SeekCount: 0, CompletionRate: 95.0},
{UserID: user2ID, PlayTime: 45, PauseCount: 5, SeekCount: 3, CompletionRate: 25.0},
}
userMetrics := service.calculateUserMetrics(analytics)
assert.Equal(t, 2, len(userMetrics))
assert.Contains(t, userMetrics, user1ID)
assert.Contains(t, userMetrics, user2ID)
// Vérifier les métriques de l'utilisateur 1
metrics1 := userMetrics[user1ID]
assert.Equal(t, int64(2), metrics1.SessionCount)
assert.InDelta(t, 97.5, metrics1.AverageCompletion, 0.1) // (100 + 95) / 2
assert.InDelta(t, 180.0, metrics1.AveragePlayTime, 0.1)
assert.InDelta(t, 0.5, metrics1.AveragePauses, 0.1) // (0 + 1) / 2 = 0.5
assert.Equal(t, 0.0, metrics1.AverageSeeks)
assert.Greater(t, metrics1.EngagementScore, 75.0) // High engagement
// Vérifier les métriques de l'utilisateur 2
metrics2 := userMetrics[user2ID]
assert.Equal(t, int64(1), metrics2.SessionCount)
assert.Equal(t, 25.0, metrics2.AverageCompletion)
assert.Equal(t, 5.0, metrics2.AveragePauses)
assert.Equal(t, 3.0, metrics2.AverageSeeks)
assert.Less(t, metrics2.EngagementScore, 50.0) // Low engagement
}
func TestPlaybackSegmentationService_GetUserSegment(t *testing.T) {
db, service := setupTestPlaybackSegmentationServiceDB(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 analytics avec high engagement
now := time.Now()
analytics := &models.PlaybackAnalytics{
TrackID: trackID,
UserID: userID,
PlayTime: 180,
PauseCount: 0,
SeekCount: 0,
CompletionRate: 100.0,
StartedAt: now,
CreatedAt: now,
}
db.Create(analytics)
segment, err := service.GetUserSegment(ctx, trackID, userID)
require.NoError(t, err)
assert.Equal(t, SegmentHighEngagement, segment)
}
func TestPlaybackSegmentationService_GetUserSegment_InvalidIDs(t *testing.T) {
_, service := setupTestPlaybackSegmentationServiceDB(t)
ctx := context.Background()
segment, err := service.GetUserSegment(ctx, uuid.Nil, uuid.New())
assert.Error(t, err)
assert.Contains(t, err.Error(), "invalid track ID or user ID")
assert.Equal(t, UserSegment(""), segment)
segment, err = service.GetUserSegment(ctx, uuid.New(), uuid.Nil)
assert.Error(t, err)
assert.Contains(t, err.Error(), "invalid track ID or user ID")
assert.Equal(t, UserSegment(""), segment)
}
func TestPlaybackSegmentationService_GetUserSegment_UserNotFound(t *testing.T) {
db, service := setupTestPlaybackSegmentationServiceDB(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)
unknownUserID := uuid.New()
segment, err := service.GetUserSegment(ctx, trackID, unknownUserID)
assert.Error(t, err)
assert.Contains(t, err.Error(), "user "+unknownUserID.String()+" not found")
assert.Equal(t, UserSegment(""), segment)
}
func TestPlaybackSegmentationService_SegmentUsers_AllSegments(t *testing.T) {
db, service := setupTestPlaybackSegmentationServiceDB(t)
ctx := context.Background()
// Créer plusieurs users avec différents comportements
user1ID := uuid.New()
user2ID := uuid.New()
user3ID := uuid.New()
user4ID := uuid.New()
users := []*models.User{
{ID: user1ID, Username: "user1", Slug: "user1", Email: "user1@example.com", IsActive: true},
{ID: user2ID, Username: "user2", Slug: "user2", Email: "user2@example.com", IsActive: true},
{ID: user3ID, Username: "user3", Slug: "user3", Email: "user3@example.com", IsActive: true},
{ID: user4ID, Username: "user4", Slug: "user4", Email: "user4@example.com", IsActive: true},
}
for _, u := range users {
db.Create(u)
}
trackID := uuid.New()
track := &models.Track{
ID: trackID,
UserID: user1ID,
Title: "Test Track",
FilePath: "/test.mp3",
FileSize: 1024,
Format: "MP3",
Duration: 180,
IsPublic: true,
Status: models.TrackStatusCompleted,
}
db.Create(track)
now := time.Now()
// User 1: High engagement, high completion, active, focused
for i := 0; i < 5; i++ {
db.Create(&models.PlaybackAnalytics{
TrackID: trackID,
UserID: user1ID,
PlayTime: 180,
PauseCount: 0,
SeekCount: 0,
CompletionRate: 100.0,
StartedAt: now,
CreatedAt: now,
})
}
// User 2: Medium engagement, medium completion, casual
db.Create(&models.PlaybackAnalytics{
TrackID: trackID,
UserID: user2ID,
PlayTime: 90,
PauseCount: 2,
SeekCount: 1,
CompletionRate: 50.0,
StartedAt: now,
CreatedAt: now,
})
// User 3: Low engagement, low completion, frequent skipper
for i := 0; i < 3; i++ {
db.Create(&models.PlaybackAnalytics{
TrackID: trackID,
UserID: user3ID,
PlayTime: 30,
PauseCount: 5,
SeekCount: 5,
CompletionRate: 15.0,
StartedAt: now,
CreatedAt: now,
})
}
// User 4: High engagement, high completion, casual
db.Create(&models.PlaybackAnalytics{
TrackID: trackID,
UserID: user4ID,
PlayTime: 180,
PauseCount: 0,
SeekCount: 0,
CompletionRate: 100.0,
StartedAt: now,
CreatedAt: now,
})
result, err := service.SegmentUsers(ctx, trackID)
require.NoError(t, err)
assert.NotNil(t, result)
assert.Equal(t, int64(4), result.TotalUsers)
// Vérifier que tous les segments sont présents
assert.Contains(t, result.Segments, SegmentHighEngagement)
assert.Contains(t, result.Segments, SegmentMediumEngagement)
assert.Contains(t, result.Segments, SegmentLowEngagement)
assert.Contains(t, result.Segments, SegmentHighCompletion)
assert.Contains(t, result.Segments, SegmentMediumCompletion)
assert.Contains(t, result.Segments, SegmentLowCompletion)
assert.Contains(t, result.Segments, SegmentActiveListener)
assert.Contains(t, result.Segments, SegmentCasualListener)
assert.Contains(t, result.Segments, SegmentFrequentSkipper)
assert.Contains(t, result.Segments, SegmentFocusedListener)
// Vérifier les compteurs
assert.Greater(t, result.SegmentCounts[SegmentHighEngagement], int64(0))
assert.Greater(t, result.SegmentCounts[SegmentLowEngagement], int64(0))
}