package services import ( "context" "github.com/google/uuid" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap" "gorm.io/driver/sqlite" "gorm.io/gorm" "veza-backend-api/internal/models" ) func setupTestTrackStatsDB(t *testing.T) (*TrackService, *gorm.DB, func()) { // Setup in-memory SQLite database db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) require.NoError(t, err) // Auto-migrate err = db.AutoMigrate( &models.User{}, &models.Track{}, &models.TrackLike{}, &models.TrackComment{}, &models.TrackPlay{}, &models.TrackShare{}, ) require.NoError(t, err) // Create test user user := &models.User{ ID: 1, Username: "testuser", Email: "test@example.com", IsActive: true, } err = db.Create(user).Error require.NoError(t, err) // Create logger logger := zap.NewNop() // Create service service := NewTrackService(db, logger, "test_uploads") // Cleanup function cleanup := func() { // SQLite in-memory database doesn't need explicit cleanup } return service, db, cleanup } func TestTrackService_GetTrackStats_Success(t *testing.T) { service, db, cleanup := setupTestTrackStatsDB(t) defer cleanup() ctx := context.Background() // Create a track track := &models.Track{ UserID: 1, Title: "Test Track", FilePath: "/path/to/track.mp3", FileSize: 1024, Format: "MP3", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted, } err := db.Create(track).Error require.NoError(t, err) // Create some likes like1 := &models.TrackLike{UserID: 1, TrackID: track.ID} like2 := &models.TrackLike{UserID: 2, TrackID: track.ID} err = db.Create(like1).Error require.NoError(t, err) err = db.Create(like2).Error require.NoError(t, err) // Create some comments comment1 := &models.TrackComment{ TrackID: track.ID, UserID: 1, Content: "Great track!", } comment2 := &models.TrackComment{ TrackID: track.ID, UserID: 2, Content: "Love it!", } err = db.Create(comment1).Error require.NoError(t, err) err = db.Create(comment2).Error require.NoError(t, err) // Create some plays play1 := &models.TrackPlay{ TrackID: track.ID, UserID: &[]int64{1}[0], Duration: 120, PlayedAt: time.Now(), } play2 := &models.TrackPlay{ TrackID: track.ID, UserID: &[]int64{2}[0], Duration: 150, PlayedAt: time.Now(), } play3 := &models.TrackPlay{ TrackID: track.ID, UserID: nil, // Anonymous play Duration: 60, PlayedAt: time.Now(), } err = db.Create(play1).Error require.NoError(t, err) err = db.Create(play2).Error require.NoError(t, err) err = db.Create(play3).Error require.NoError(t, err) // Create a share with download permission and access count share := &models.TrackShare{ TrackID: track.ID, UserID: 1, ShareToken: "test-token", Permissions: "read,download", AccessCount: 5, } err = db.Create(share).Error require.NoError(t, err) // Get stats stats, err := service.GetTrackStats(ctx, track.ID) require.NoError(t, err) require.NotNil(t, stats) // Verify stats assert.Equal(t, int64(3), stats.Views) // 3 plays assert.Equal(t, int64(2), stats.Likes) // 2 likes assert.Equal(t, int64(2), stats.Comments) // 2 comments assert.Equal(t, int64(330), stats.TotalPlayTime) // 120 + 150 + 60 = 330 seconds assert.Equal(t, int64(5), stats.Downloads) // 5 downloads from share } func TestTrackService_GetTrackStats_TrackNotFound(t *testing.T) { service, _, cleanup := setupTestTrackStatsDB(t) defer cleanup() ctx := context.Background() // Try to get stats for non-existent track stats, err := service.GetTrackStats(ctx, 999) assert.Error(t, err) assert.Nil(t, stats) assert.Equal(t, ErrTrackNotFound, err) } func TestTrackService_GetTrackStats_EmptyStats(t *testing.T) { service, db, cleanup := setupTestTrackStatsDB(t) defer cleanup() ctx := context.Background() // Create a track with no interactions track := &models.Track{ UserID: 1, Title: "Empty Track", FilePath: "/path/to/track.mp3", FileSize: 1024, Format: "MP3", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted, } err := db.Create(track).Error require.NoError(t, err) // Get stats stats, err := service.GetTrackStats(ctx, track.ID) require.NoError(t, err) require.NotNil(t, stats) // Verify all stats are zero assert.Equal(t, int64(0), stats.Views) assert.Equal(t, int64(0), stats.Likes) assert.Equal(t, int64(0), stats.Comments) assert.Equal(t, int64(0), stats.TotalPlayTime) assert.Equal(t, int64(0), stats.Downloads) } func TestTrackService_GetTrackStats_MultipleShares(t *testing.T) { service, db, cleanup := setupTestTrackStatsDB(t) defer cleanup() ctx := context.Background() // Create a track track := &models.Track{ UserID: 1, Title: "Shared Track", FilePath: "/path/to/track.mp3", FileSize: 1024, Format: "MP3", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted, } err := db.Create(track).Error require.NoError(t, err) // Create multiple shares with download permissions share1 := &models.TrackShare{ TrackID: track.ID, UserID: 1, ShareToken: "token1", Permissions: "read,download", AccessCount: 3, } share2 := &models.TrackShare{ TrackID: track.ID, UserID: 1, ShareToken: "token2", Permissions: "download", AccessCount: 2, } share3 := &models.TrackShare{ TrackID: track.ID, UserID: 1, ShareToken: "token3", Permissions: "read", // No download permission AccessCount: 10, } err = db.Create(share1).Error require.NoError(t, err) err = db.Create(share2).Error require.NoError(t, err) err = db.Create(share3).Error require.NoError(t, err) // Get stats stats, err := service.GetTrackStats(ctx, track.ID) require.NoError(t, err) require.NotNil(t, stats) // Verify downloads count only shares with download permission assert.Equal(t, int64(5), stats.Downloads) // 3 + 2 = 5 (share3 excluded) } func TestTrackService_GetTrackStats_SoftDeletedComments(t *testing.T) { service, db, cleanup := setupTestTrackStatsDB(t) defer cleanup() ctx := context.Background() // Create a track track := &models.Track{ UserID: 1, Title: "Track with Deleted Comments", FilePath: "/path/to/track.mp3", FileSize: 1024, Format: "MP3", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted, } err := db.Create(track).Error require.NoError(t, err) // Create comments comment1 := &models.TrackComment{ TrackID: track.ID, UserID: 1, Content: "Comment 1", } comment2 := &models.TrackComment{ TrackID: track.ID, UserID: 2, Content: "Comment 2", } err = db.Create(comment1).Error require.NoError(t, err) err = db.Create(comment2).Error require.NoError(t, err) // Soft delete one comment err = db.Delete(comment1).Error require.NoError(t, err) // Get stats stats, err := service.GetTrackStats(ctx, track.ID) require.NoError(t, err) require.NotNil(t, stats) // Verify only non-deleted comments are counted // Note: GORM's Count by default excludes soft-deleted records assert.Equal(t, int64(1), stats.Comments) }