veza/veza-backend-api/internal/services/playback_segmentation_service_test.go
2025-12-03 20:29:37 +01:00

452 lines
14 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 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
user := &models.User{ID: 1, Username: "testuser", Email: "test@example.com", IsActive: true}
db.Create(user)
track := &models.Track{
ID: 1,
UserID: 1,
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, 1)
require.NoError(t, err)
assert.NotNil(t, result)
assert.Equal(t, int64(1), 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, 0)
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, 999)
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
user1 := &models.User{ID: 1, Username: "user1", Slug: "user1", Email: "user1@example.com", IsActive: true}
user2 := &models.User{ID: 2, Username: "user2", Slug: "user2", Email: "user2@example.com", IsActive: true}
db.Create(user1)
db.Create(user2)
track := &models.Track{
ID: 1,
UserID: 1,
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: 1,
UserID: 1,
PlayTime: 180,
PauseCount: 0,
SeekCount: 0,
CompletionRate: 100.0,
StartedAt: now,
CreatedAt: now,
}
analytics2 := &models.PlaybackAnalytics{
TrackID: 1,
UserID: 1,
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: 1,
UserID: 2,
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, 1)
require.NoError(t, err)
assert.NotNil(t, result)
assert.Equal(t, int64(1), 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)
userMetrics := map[int64]*UserMetrics{
1: {UserID: 1, EngagementScore: 85.0}, // High
2: {UserID: 2, EngagementScore: 60.0}, // Medium
3: {UserID: 3, 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], int64(1))
assert.Contains(t, segments[SegmentMediumEngagement], int64(2))
assert.Contains(t, segments[SegmentLowEngagement], int64(3))
}
func TestPlaybackSegmentationService_SegmentByCompletionRate(t *testing.T) {
_, service := setupTestPlaybackSegmentationServiceDB(t)
userMetrics := map[int64]*UserMetrics{
1: {UserID: 1, AverageCompletion: 90.0}, // High
2: {UserID: 2, AverageCompletion: 60.0}, // Medium
3: {UserID: 3, 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], int64(1))
assert.Contains(t, segments[SegmentMediumCompletion], int64(2))
assert.Contains(t, segments[SegmentLowCompletion], int64(3))
}
func TestPlaybackSegmentationService_SegmentByBehavior(t *testing.T) {
_, service := setupTestPlaybackSegmentationServiceDB(t)
userMetrics := map[int64]*UserMetrics{
1: {UserID: 1, SessionCount: 10, AverageSeeks: 0.5, AverageCompletion: 80.0}, // Active + Focused
2: {UserID: 2, SessionCount: 1, AverageSeeks: 0.2, AverageCompletion: 75.0}, // Casual + Focused
3: {UserID: 3, SessionCount: 5, AverageSeeks: 5.0, AverageCompletion: 50.0}, // Frequent skipper
4: {UserID: 4, 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)
analytics := []models.PlaybackAnalytics{
{UserID: 1, PlayTime: 180, PauseCount: 0, SeekCount: 0, CompletionRate: 100.0},
{UserID: 1, PlayTime: 180, PauseCount: 1, SeekCount: 0, CompletionRate: 95.0},
{UserID: 2, PlayTime: 45, PauseCount: 5, SeekCount: 3, CompletionRate: 25.0},
}
userMetrics := service.calculateUserMetrics(analytics)
assert.Equal(t, 2, len(userMetrics))
assert.Contains(t, userMetrics, int64(1))
assert.Contains(t, userMetrics, int64(2))
// Vérifier les métriques de l'utilisateur 1
metrics1 := userMetrics[1]
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[2]
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
user := &models.User{ID: 1, Username: "testuser", Email: "test@example.com", IsActive: true}
db.Create(user)
track := &models.Track{
ID: 1,
UserID: 1,
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: 1,
UserID: 1,
PlayTime: 180,
PauseCount: 0,
SeekCount: 0,
CompletionRate: 100.0,
StartedAt: now,
CreatedAt: now,
}
db.Create(analytics)
segment, err := service.GetUserSegment(ctx, 1, 1)
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, 0, 1)
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, 1, 0)
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
user := &models.User{ID: 1, Username: "testuser", Email: "test@example.com", IsActive: true}
db.Create(user)
track := &models.Track{
ID: 1,
UserID: 1,
Title: "Test Track",
FilePath: "/test.mp3",
FileSize: 1024,
Format: "MP3",
Duration: 180,
IsPublic: true,
Status: models.TrackStatusCompleted,
}
db.Create(track)
segment, err := service.GetUserSegment(ctx, 1, 999)
assert.Error(t, err)
assert.Contains(t, err.Error(), "user 999 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
users := []*models.User{
{ID: 1, Username: "user1", Slug: "user1", Email: "user1@example.com", IsActive: true},
{ID: 2, Username: "user2", Slug: "user2", Email: "user2@example.com", IsActive: true},
{ID: 3, Username: "user3", Slug: "user3", Email: "user3@example.com", IsActive: true},
{ID: 4, Username: "user4", Slug: "user4", Email: "user4@example.com", IsActive: true},
}
for _, u := range users {
db.Create(u)
}
track := &models.Track{
ID: 1,
UserID: 1,
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: 1,
UserID: 1,
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: 1,
UserID: 2,
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: 1,
UserID: 3,
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: 1,
UserID: 4,
PlayTime: 180,
PauseCount: 0,
SeekCount: 0,
CompletionRate: 100.0,
StartedAt: now,
CreatedAt: now,
})
result, err := service.SegmentUsers(ctx, 1)
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))
}