package services import ( "context" "testing" "github.com/google/uuid" "veza-backend-api/internal/models" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap" "gorm.io/driver/sqlite" "gorm.io/gorm" ) func setupTestPlaylistAnalyticsService(t *testing.T) (*PlaylistAnalyticsService, *gorm.DB, func()) { db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) require.NoError(t, err) // Enable foreign keys for SQLite db.Exec("PRAGMA foreign_keys = ON") // Auto-migrate err = db.AutoMigrate( &models.User{}, &models.Track{}, &models.Playlist{}, &models.PlaylistTrack{}, &models.PlaylistShareLink{}, &models.PlaylistFollow{}, &models.TrackPlay{}, ) require.NoError(t, err) logger := zap.NewNop() service := NewPlaylistAnalyticsService(db, logger) cleanup := func() { // Database will be closed automatically } return service, db, cleanup } func TestPlaylistAnalyticsService_GetPlaylistStats(t *testing.T) { service, db, cleanup := setupTestPlaylistAnalyticsService(t) defer cleanup() ctx := context.Background() // Create test user user := &models.User{ Username: "user1", Email: "user1@example.com", PasswordHash: "hash1", Slug: "user1", IsActive: true, } require.NoError(t, db.Create(user).Error) // Create test tracks track1 := &models.Track{ UserID: user.ID, Title: "Track 1", FilePath: "/path/to/track1.mp3", FileSize: 1024, Format: "MP3", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted, PlayCount: 0, LikeCount: 0, } track2 := &models.Track{ UserID: user.ID, Title: "Track 2", FilePath: "/path/to/track2.mp3", FileSize: 2048, Format: "MP3", Duration: 240, IsPublic: true, Status: models.TrackStatusCompleted, PlayCount: 0, LikeCount: 0, } require.NoError(t, db.Create(track1).Error) require.NoError(t, db.Create(track2).Error) // Create test playlist playlist := &models.Playlist{ UserID: user.ID, Title: "Test Playlist", Description: "A test playlist", IsPublic: true, TrackCount: 2, FollowerCount: 0, } require.NoError(t, db.Create(playlist).Error) // Add tracks to playlist playlistTrack1 := &models.PlaylistTrack{ PlaylistID: playlist.ID, TrackID: track1.ID, Position: 1, } playlistTrack2 := &models.PlaylistTrack{ PlaylistID: playlist.ID, TrackID: track2.ID, Position: 2, } require.NoError(t, db.Create(playlistTrack1).Error) require.NoError(t, db.Create(playlistTrack2).Error) // Create some track plays play1 := &models.TrackPlay{ TrackID: track1.ID, UserID: &user.ID, Duration: 180, } play2 := &models.TrackPlay{ TrackID: track1.ID, UserID: &user.ID, Duration: 150, } play3 := &models.TrackPlay{ TrackID: track2.ID, UserID: &user.ID, Duration: 240, } require.NoError(t, db.Create(play1).Error) require.NoError(t, db.Create(play2).Error) require.NoError(t, db.Create(play3).Error) // Create share link shareLink := &models.PlaylistShareLink{ PlaylistID: playlist.ID, UserID: user.ID, ShareToken: "test-token-123", AccessCount: 0, } require.NoError(t, db.Create(shareLink).Error) // Create follow follow := &models.PlaylistFollow{ PlaylistID: playlist.ID, UserID: user.ID, } require.NoError(t, db.Create(follow).Error) // Update follower count playlist.FollowerCount = 1 require.NoError(t, db.Save(playlist).Error) // Get stats stats, err := service.GetPlaylistStats(ctx, playlist.ID) require.NoError(t, err) assert.NotNil(t, stats) // Verify stats assert.Equal(t, int64(3), stats.Plays) // 3 plays total (2 for track1, 1 for track2) assert.Equal(t, int64(1), stats.Shares) // 1 share link assert.Equal(t, int64(1), stats.Likes) // 1 follow assert.Equal(t, int64(1), stats.Followers) // 1 follower assert.Equal(t, 2, stats.TrackCount) // 2 tracks } func TestPlaylistAnalyticsService_GetPlaylistStats_NotFound(t *testing.T) { service, _, cleanup := setupTestPlaylistAnalyticsService(t) defer cleanup() ctx := context.Background() // Get stats for non-existent playlist stats, err := service.GetPlaylistStats(ctx, uuid.New()) assert.Error(t, err) assert.Nil(t, stats) assert.Equal(t, "playlist not found", err.Error()) } func TestPlaylistAnalyticsService_GetPlaylistStats_EmptyPlaylist(t *testing.T) { service, db, cleanup := setupTestPlaylistAnalyticsService(t) defer cleanup() ctx := context.Background() // Create test user user := &models.User{ Username: "user1", Email: "user1@example.com", PasswordHash: "hash1", Slug: "user1", IsActive: true, } require.NoError(t, db.Create(user).Error) // Create empty playlist playlist := &models.Playlist{ UserID: user.ID, Title: "Empty Playlist", Description: "An empty playlist", IsPublic: true, TrackCount: 0, FollowerCount: 0, } require.NoError(t, db.Create(playlist).Error) // Get stats stats, err := service.GetPlaylistStats(ctx, playlist.ID) require.NoError(t, err) assert.NotNil(t, stats) // Verify stats for empty playlist assert.Equal(t, int64(0), stats.Plays) assert.Equal(t, int64(0), stats.Shares) assert.Equal(t, int64(0), stats.Likes) assert.Equal(t, int64(0), stats.Followers) assert.Equal(t, 0, stats.TrackCount) } func TestPlaylistAnalyticsService_GetPlaylistStats_MultipleShares(t *testing.T) { service, db, cleanup := setupTestPlaylistAnalyticsService(t) defer cleanup() ctx := context.Background() // Create test user user := &models.User{ Username: "user1", Email: "user1@example.com", PasswordHash: "hash1", Slug: "user1", IsActive: true, } require.NoError(t, db.Create(user).Error) // Create test playlist playlist := &models.Playlist{ UserID: user.ID, Title: "Test Playlist", Description: "A test playlist", IsPublic: true, TrackCount: 0, FollowerCount: 0, } require.NoError(t, db.Create(playlist).Error) // Create multiple share links shareLink1 := &models.PlaylistShareLink{ PlaylistID: playlist.ID, UserID: user.ID, ShareToken: "token-1", AccessCount: 0, } shareLink2 := &models.PlaylistShareLink{ PlaylistID: playlist.ID, UserID: user.ID, ShareToken: "token-2", AccessCount: 0, } require.NoError(t, db.Create(shareLink1).Error) require.NoError(t, db.Create(shareLink2).Error) // Get stats stats, err := service.GetPlaylistStats(ctx, playlist.ID) require.NoError(t, err) assert.NotNil(t, stats) // Verify shares count assert.Equal(t, int64(2), stats.Shares) } func TestPlaylistAnalyticsService_GetPlaylistStats_MultipleFollows(t *testing.T) { service, db, cleanup := setupTestPlaylistAnalyticsService(t) defer cleanup() ctx := context.Background() // Create test users user1 := &models.User{ Username: "user1", Email: "user1@example.com", PasswordHash: "hash1", Slug: "user1", IsActive: true, } user2 := &models.User{ Username: "user2", Email: "user2@example.com", PasswordHash: "hash2", Slug: "user2", IsActive: true, } user3 := &models.User{ Username: "user3", Email: "user3@example.com", PasswordHash: "hash3", Slug: "user3", IsActive: true, } require.NoError(t, db.Create(user1).Error) require.NoError(t, db.Create(user2).Error) require.NoError(t, db.Create(user3).Error) // Create test playlist playlist := &models.Playlist{ UserID: user1.ID, Title: "Test Playlist", Description: "A test playlist", IsPublic: true, TrackCount: 0, FollowerCount: 0, } require.NoError(t, db.Create(playlist).Error) // Create multiple follows follow1 := &models.PlaylistFollow{ PlaylistID: playlist.ID, UserID: user2.ID, } follow2 := &models.PlaylistFollow{ PlaylistID: playlist.ID, UserID: user3.ID, } require.NoError(t, db.Create(follow1).Error) require.NoError(t, db.Create(follow2).Error) // Update follower count playlist.FollowerCount = 2 require.NoError(t, db.Save(playlist).Error) // Get stats stats, err := service.GetPlaylistStats(ctx, playlist.ID) require.NoError(t, err) assert.NotNil(t, stats) // Verify follows count assert.Equal(t, int64(2), stats.Likes) assert.Equal(t, int64(2), stats.Followers) } func TestPlaylistAnalyticsService_IncrementPlaylistPlays(t *testing.T) { service, _, cleanup := setupTestPlaylistAnalyticsService(t) defer cleanup() ctx := context.Background() // Test increment (should not error, but doesn't do anything for now) err := service.IncrementPlaylistPlays(ctx, uuid.New()) assert.NoError(t, err) }