480 lines
14 KiB
Go
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))
|
|
}
|