package services import ( "context" "github.com/google/uuid" "testing" "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 setupTestPlaylistFollowService(t *testing.T) (*PlaylistFollowService, *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.Playlist{}, &models.PlaylistFollow{}) require.NoError(t, err) logger := zap.NewNop() service := NewPlaylistFollowService(db, logger) cleanup := func() { // Database will be closed automatically } return service, db, cleanup } func TestPlaylistFollowService_FollowPlaylist(t *testing.T) { service, db, cleanup := setupTestPlaylistFollowService(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, } require.NoError(t, db.Create(user1).Error) require.NoError(t, db.Create(user2).Error) // Create test playlist playlist := &models.Playlist{ UserID: user1.ID, Title: "Test Playlist", Description: "A test playlist", IsPublic: true, FollowerCount: 0, } require.NoError(t, db.Create(playlist).Error) // Test follow err := service.FollowPlaylist(ctx, user2.ID, playlist.ID) assert.NoError(t, err) // Verify follow was created var follow models.PlaylistFollow err = db.Where("user_id = ? AND playlist_id = ?", user2.ID, playlist.ID).First(&follow).Error assert.NoError(t, err) assert.Equal(t, user2.ID, follow.UserID) assert.Equal(t, playlist.ID, follow.PlaylistID) // Verify follower count was updated var updatedPlaylist models.Playlist db.First(&updatedPlaylist, playlist.ID) assert.Equal(t, 1, updatedPlaylist.FollowerCount) } func TestPlaylistFollowService_FollowPlaylist_OwnPlaylist(t *testing.T) { service, db, cleanup := setupTestPlaylistFollowService(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, FollowerCount: 0, } require.NoError(t, db.Create(playlist).Error) // Test follow own playlist (should fail) err := service.FollowPlaylist(ctx, user.ID, playlist.ID) assert.Error(t, err) assert.Equal(t, "cannot follow own playlist", err.Error()) } func TestPlaylistFollowService_FollowPlaylist_NotFound(t *testing.T) { service, db, cleanup := setupTestPlaylistFollowService(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) // Test follow non-existent playlist err := service.FollowPlaylist(ctx, user.ID, uuid.New()) assert.Error(t, err) assert.Equal(t, "playlist not found", err.Error()) } func TestPlaylistFollowService_FollowPlaylist_Idempotent(t *testing.T) { service, db, cleanup := setupTestPlaylistFollowService(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, } require.NoError(t, db.Create(user1).Error) require.NoError(t, db.Create(user2).Error) // Create test playlist playlist := &models.Playlist{ UserID: user1.ID, Title: "Test Playlist", Description: "A test playlist", IsPublic: true, FollowerCount: 0, } require.NoError(t, db.Create(playlist).Error) // Follow twice err := service.FollowPlaylist(ctx, user2.ID, playlist.ID) assert.NoError(t, err) err = service.FollowPlaylist(ctx, user2.ID, playlist.ID) assert.NoError(t, err) // Should be idempotent // Verify only one follow exists var count int64 db.Model(&models.PlaylistFollow{}).Where("user_id = ? AND playlist_id = ?", user2.ID, playlist.ID).Count(&count) assert.Equal(t, int64(1), count) } func TestPlaylistFollowService_UnfollowPlaylist(t *testing.T) { service, db, cleanup := setupTestPlaylistFollowService(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, } require.NoError(t, db.Create(user1).Error) require.NoError(t, db.Create(user2).Error) // Create test playlist playlist := &models.Playlist{ UserID: user1.ID, Title: "Test Playlist", Description: "A test playlist", IsPublic: true, FollowerCount: 0, } require.NoError(t, db.Create(playlist).Error) // Follow first err := service.FollowPlaylist(ctx, user2.ID, playlist.ID) require.NoError(t, err) // Unfollow err = service.UnfollowPlaylist(ctx, user2.ID, playlist.ID) assert.NoError(t, err) // Verify follow was deleted var count int64 db.Model(&models.PlaylistFollow{}).Where("user_id = ? AND playlist_id = ? AND deleted_at IS NULL", user2.ID, playlist.ID).Count(&count) assert.Equal(t, int64(0), count) // Verify follower count was updated var updatedPlaylist models.Playlist db.First(&updatedPlaylist, playlist.ID) assert.Equal(t, 0, updatedPlaylist.FollowerCount) } func TestPlaylistFollowService_UnfollowPlaylist_Idempotent(t *testing.T) { service, db, cleanup := setupTestPlaylistFollowService(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, } require.NoError(t, db.Create(user1).Error) require.NoError(t, db.Create(user2).Error) // Create test playlist playlist := &models.Playlist{ UserID: user1.ID, Title: "Test Playlist", Description: "A test playlist", IsPublic: true, FollowerCount: 0, } require.NoError(t, db.Create(playlist).Error) // Unfollow without following first (should be idempotent) err := service.UnfollowPlaylist(ctx, user2.ID, playlist.ID) assert.NoError(t, err) } func TestPlaylistFollowService_IsFollowing(t *testing.T) { service, db, cleanup := setupTestPlaylistFollowService(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, } require.NoError(t, db.Create(user1).Error) require.NoError(t, db.Create(user2).Error) // Create test playlist playlist := &models.Playlist{ UserID: user1.ID, Title: "Test Playlist", Description: "A test playlist", IsPublic: true, FollowerCount: 0, } require.NoError(t, db.Create(playlist).Error) // Check before following isFollowing, err := service.IsFollowing(ctx, user2.ID, playlist.ID) assert.NoError(t, err) assert.False(t, isFollowing) // Follow err = service.FollowPlaylist(ctx, user2.ID, playlist.ID) require.NoError(t, err) // Check after following isFollowing, err = service.IsFollowing(ctx, user2.ID, playlist.ID) assert.NoError(t, err) assert.True(t, isFollowing) } func TestPlaylistFollowService_GetPlaylistFollowersCount(t *testing.T) { service, db, cleanup := setupTestPlaylistFollowService(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, FollowerCount: 0, } require.NoError(t, db.Create(playlist).Error) // Check count before following count, err := service.GetPlaylistFollowersCount(ctx, playlist.ID) assert.NoError(t, err) assert.Equal(t, int64(0), count) // Follow by user2 err = service.FollowPlaylist(ctx, user2.ID, playlist.ID) require.NoError(t, err) // Follow by user3 err = service.FollowPlaylist(ctx, user3.ID, playlist.ID) require.NoError(t, err) // Check count after following count, err = service.GetPlaylistFollowersCount(ctx, playlist.ID) assert.NoError(t, err) assert.Equal(t, int64(2), count) }