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 setupTestCommentService(t *testing.T) (*CommentService, *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.TrackComment{}) require.NoError(t, err) // Setup logger logger := zap.NewNop() // Setup service service := NewCommentService(db, logger) // Cleanup function cleanup := func() { // Database will be closed automatically } return service, db, cleanup } func TestCommentService_CreateComment_Success(t *testing.T) { service, db, cleanup := setupTestCommentService(t) defer cleanup() ctx := context.Background() userID := uuid.New() // Create test user user := &models.User{ ID: userID, Username: "testuser", Email: "test@example.com", IsActive: true, } err := db.Create(user).Error require.NoError(t, err) // Create test track track := &models.Track{ UserID: userID, Title: "Test Track", FilePath: "/test/track.mp3", FileSize: 5 * 1024 * 1024, Format: "MP3", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted, } err = db.Create(track).Error require.NoError(t, err) // Create comment comment, err := service.CreateComment(ctx, track.ID, userID, "Great track!", 0.0, nil) assert.NoError(t, err) assert.NotNil(t, comment) assert.Equal(t, track.ID, comment.TrackID) assert.Equal(t, userID, comment.UserID) assert.Equal(t, "Great track!", comment.Content) assert.Nil(t, comment.ParentID) assert.False(t, comment.IsEdited) assert.NotNil(t, comment.User) assert.Equal(t, "testuser", comment.User.Username) } func TestCommentService_CreateComment_TrackNotFound(t *testing.T) { service, _, cleanup := setupTestCommentService(t) defer cleanup() ctx := context.Background() userID := uuid.New() // Try to create comment on non-existent track comment, err := service.CreateComment(ctx, uuid.New(), userID, "Great track!", 0.0, nil) assert.Error(t, err) assert.Nil(t, comment) assert.ErrorIs(t, err, ErrTrackNotFound) } func TestCommentService_CreateComment_WithParent(t *testing.T) { service, db, cleanup := setupTestCommentService(t) defer cleanup() ctx := context.Background() userID := uuid.New() // Create test user user := &models.User{ ID: userID, Username: "testuser", Email: "test@example.com", IsActive: true, } err := db.Create(user).Error require.NoError(t, err) // Create test track track := &models.Track{ UserID: userID, Title: "Test Track", FilePath: "/test/track.mp3", FileSize: 5 * 1024 * 1024, Format: "MP3", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted, } err = db.Create(track).Error require.NoError(t, err) // Create parent comment parentComment, err := service.CreateComment(ctx, track.ID, userID, "Parent comment", 0.0, nil) require.NoError(t, err) // Create reply reply, err := service.CreateComment(ctx, track.ID, userID, "Reply to parent", 0.0, &parentComment.ID) assert.NoError(t, err) assert.NotNil(t, reply) assert.NotNil(t, reply.ParentID) assert.Equal(t, parentComment.ID, *reply.ParentID) assert.Equal(t, "Reply to parent", reply.Content) } func TestCommentService_CreateComment_ParentNotFound(t *testing.T) { service, db, cleanup := setupTestCommentService(t) defer cleanup() ctx := context.Background() userID := uuid.New() // Create test user user := &models.User{ ID: userID, Username: "testuser", Email: "test@example.com", IsActive: true, } err := db.Create(user).Error require.NoError(t, err) // Create test track track := &models.Track{ UserID: userID, Title: "Test Track", FilePath: "/test/track.mp3", FileSize: 5 * 1024 * 1024, Format: "MP3", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted, } err = db.Create(track).Error require.NoError(t, err) // Try to create reply with non-existent parent parentID := uuid.New() reply, err := service.CreateComment(ctx, track.ID, userID, "Reply", 0.0, &parentID) assert.Error(t, err) assert.Nil(t, reply) assert.ErrorIs(t, err, ErrParentCommentNotFound) } func TestCommentService_GetComments_Success(t *testing.T) { service, db, cleanup := setupTestCommentService(t) defer cleanup() ctx := context.Background() userID := uuid.New() // Create test user user := &models.User{ ID: userID, Username: "testuser", Email: "test@example.com", IsActive: true, } err := db.Create(user).Error require.NoError(t, err) // Create test track track := &models.Track{ UserID: userID, Title: "Test Track", FilePath: "/test/track.mp3", FileSize: 5 * 1024 * 1024, Format: "MP3", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted, } err = db.Create(track).Error require.NoError(t, err) // Create multiple comments for i := 0; i < 5; i++ { _, err := service.CreateComment(ctx, track.ID, userID, "Comment "+string(rune('0'+i)), 0.0, nil) require.NoError(t, err) } // Get comments comments, total, err := service.GetComments(ctx, track.ID, 1, 10) assert.NoError(t, err) assert.Equal(t, int64(5), total) assert.Len(t, comments, 5) assert.NotNil(t, comments[0].User) } func TestCommentService_GetComments_Pagination(t *testing.T) { service, db, cleanup := setupTestCommentService(t) defer cleanup() ctx := context.Background() userID := uuid.New() // Create test user user := &models.User{ ID: userID, Username: "testuser", Email: "test@example.com", IsActive: true, } err := db.Create(user).Error require.NoError(t, err) // Create test track track := &models.Track{ UserID: userID, Title: "Test Track", FilePath: "/test/track.mp3", FileSize: 5 * 1024 * 1024, Format: "MP3", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted, } err = db.Create(track).Error require.NoError(t, err) // Create multiple comments for i := 0; i < 10; i++ { _, err := service.CreateComment(ctx, track.ID, userID, "Comment", 0.0, nil) require.NoError(t, err) } // Get first page comments, total, err := service.GetComments(ctx, track.ID, 1, 3) assert.NoError(t, err) assert.Equal(t, int64(10), total) assert.Len(t, comments, 3) // Get second page comments2, total2, err := service.GetComments(ctx, track.ID, 2, 3) assert.NoError(t, err) assert.Equal(t, int64(10), total2) assert.Len(t, comments2, 3) } func TestCommentService_GetComments_OnlyRootComments(t *testing.T) { service, db, cleanup := setupTestCommentService(t) defer cleanup() ctx := context.Background() userID := uuid.New() // Create test user user := &models.User{ ID: userID, Username: "testuser", Email: "test@example.com", IsActive: true, } err := db.Create(user).Error require.NoError(t, err) // Create test track track := &models.Track{ UserID: userID, Title: "Test Track", FilePath: "/test/track.mp3", FileSize: 5 * 1024 * 1024, Format: "MP3", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted, } err = db.Create(track).Error require.NoError(t, err) // Create root comment rootComment, err := service.CreateComment(ctx, track.ID, userID, "Root comment", 0.0, nil) require.NoError(t, err) // Create reply (should not appear in GetComments) _, err = service.CreateComment(ctx, track.ID, userID, "Reply", 0.0, &rootComment.ID) require.NoError(t, err) // Get comments (should only return root comment) comments, total, err := service.GetComments(ctx, track.ID, 1, 10) assert.NoError(t, err) assert.Equal(t, int64(1), total) assert.Len(t, comments, 1) assert.Equal(t, rootComment.ID, comments[0].ID) } func TestCommentService_UpdateComment_Success(t *testing.T) { service, db, cleanup := setupTestCommentService(t) defer cleanup() ctx := context.Background() userID := uuid.New() // Create test user user := &models.User{ ID: userID, Username: "testuser", Email: "test@example.com", IsActive: true, } err := db.Create(user).Error require.NoError(t, err) // Create test track track := &models.Track{ UserID: userID, Title: "Test Track", FilePath: "/test/track.mp3", FileSize: 5 * 1024 * 1024, Format: "MP3", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted, } err = db.Create(track).Error require.NoError(t, err) // Create comment comment, err := service.CreateComment(ctx, track.ID, userID, "Original content", 0.0, nil) require.NoError(t, err) // Update comment updatedComment, err := service.UpdateComment(ctx, comment.ID, userID, "Updated content") assert.NoError(t, err) assert.NotNil(t, updatedComment) assert.Equal(t, "Updated content", updatedComment.Content) assert.True(t, updatedComment.IsEdited) } func TestCommentService_UpdateComment_NotFound(t *testing.T) { service, _, cleanup := setupTestCommentService(t) defer cleanup() ctx := context.Background() userID := uuid.New() // Try to update non-existent comment comment, err := service.UpdateComment(ctx, uuid.New(), userID, "Updated content") assert.Error(t, err) assert.Nil(t, comment) assert.ErrorIs(t, err, ErrCommentNotFound) } func TestCommentService_UpdateComment_Unauthorized(t *testing.T) { service, db, cleanup := setupTestCommentService(t) defer cleanup() ctx := context.Background() user1ID := uuid.New() // Create test users user1 := &models.User{ ID: user1ID, Username: "user1", Email: "user1@example.com", IsActive: true, } err := db.Create(user1).Error require.NoError(t, err) user2ID := uuid.New() user2 := &models.User{ ID: user2ID, Username: "user2", Email: "user2@example.com", IsActive: true, } err = db.Create(user2).Error require.NoError(t, err) // Create test track track := &models.Track{ UserID: user1ID, Title: "Test Track", FilePath: "/test/track.mp3", FileSize: 5 * 1024 * 1024, Format: "MP3", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted, } err = db.Create(track).Error require.NoError(t, err) // Create comment with user1 comment, err := service.CreateComment(ctx, track.ID, user1ID, "Original content", 0.0, nil) require.NoError(t, err) // Try to update with user2 (should fail) updatedComment, err := service.UpdateComment(ctx, comment.ID, user2ID, "Updated content") assert.Error(t, err) assert.Nil(t, updatedComment) assert.ErrorIs(t, err, ErrForbidden) } func TestCommentService_DeleteComment_Success(t *testing.T) { service, db, cleanup := setupTestCommentService(t) defer cleanup() ctx := context.Background() userID := uuid.New() // Create test user user := &models.User{ ID: userID, Username: "testuser", Email: "test@example.com", IsActive: true, } err := db.Create(user).Error require.NoError(t, err) // Create test track track := &models.Track{ UserID: userID, Title: "Test Track", FilePath: "/test/track.mp3", FileSize: 5 * 1024 * 1024, Format: "MP3", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted, } err = db.Create(track).Error require.NoError(t, err) // Create comment comment, err := service.CreateComment(ctx, track.ID, userID, "Comment to delete", 0.0, nil) require.NoError(t, err) // Delete comment err = service.DeleteComment(ctx, comment.ID, userID, false) assert.NoError(t, err) // Verify comment is soft deleted var deletedComment models.TrackComment err = db.First(&deletedComment, comment.ID).Error assert.Error(t, err) assert.Equal(t, gorm.ErrRecordNotFound, err) } func TestCommentService_DeleteComment_NotFound(t *testing.T) { service, _, cleanup := setupTestCommentService(t) defer cleanup() ctx := context.Background() userID := uuid.New() // Try to delete non-existent comment err := service.DeleteComment(ctx, uuid.New(), userID, false) assert.Error(t, err) assert.ErrorIs(t, err, ErrCommentNotFound) } func TestCommentService_DeleteComment_Unauthorized(t *testing.T) { service, db, cleanup := setupTestCommentService(t) defer cleanup() ctx := context.Background() user1ID := uuid.New() // Create test users user1 := &models.User{ ID: user1ID, Username: "user1", Email: "user1@example.com", IsActive: true, } err := db.Create(user1).Error require.NoError(t, err) user2ID := uuid.New() user2 := &models.User{ ID: user2ID, Username: "user2", Email: "user2@example.com", IsActive: true, } err = db.Create(user2).Error require.NoError(t, err) // Create test track track := &models.Track{ UserID: user1ID, Title: "Test Track", FilePath: "/test/track.mp3", FileSize: 5 * 1024 * 1024, Format: "MP3", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted, } err = db.Create(track).Error require.NoError(t, err) // Create comment with user1 comment, err := service.CreateComment(ctx, track.ID, user1ID, "Comment", 0.0, nil) require.NoError(t, err) // Try to delete with user2 (should fail) err = service.DeleteComment(ctx, comment.ID, user2ID, false) assert.Error(t, err) assert.ErrorIs(t, err, ErrForbidden) } func TestCommentService_GetReplies_Success(t *testing.T) { service, db, cleanup := setupTestCommentService(t) defer cleanup() ctx := context.Background() userID := uuid.New() // Create test user user := &models.User{ ID: userID, Username: "testuser", Email: "test@example.com", IsActive: true, } err := db.Create(user).Error require.NoError(t, err) // Create test track track := &models.Track{ UserID: userID, Title: "Test Track", FilePath: "/test/track.mp3", FileSize: 5 * 1024 * 1024, Format: "MP3", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted, } err = db.Create(track).Error require.NoError(t, err) // Create parent comment parentComment, err := service.CreateComment(ctx, track.ID, userID, "Parent comment", 0.0, nil) require.NoError(t, err) // Create multiple replies for i := 0; i < 5; i++ { _, err := service.CreateComment(ctx, track.ID, userID, "Reply", 0.0, &parentComment.ID) require.NoError(t, err) } // Get replies replies, total, err := service.GetReplies(ctx, parentComment.ID, 1, 10) assert.NoError(t, err) assert.Equal(t, int64(5), total) assert.Len(t, replies, 5) assert.NotNil(t, replies[0].User) } func TestCommentService_GetReplies_ParentNotFound(t *testing.T) { service, _, cleanup := setupTestCommentService(t) defer cleanup() ctx := context.Background() // Try to get replies for non-existent parent replies, total, err := service.GetReplies(ctx, uuid.New(), 1, 10) assert.Error(t, err) assert.Nil(t, replies) assert.Equal(t, int64(0), total) assert.ErrorIs(t, err, ErrParentCommentNotFound) } func TestCommentService_GetReplies_Pagination(t *testing.T) { service, db, cleanup := setupTestCommentService(t) defer cleanup() ctx := context.Background() userID := uuid.New() // Create test user user := &models.User{ ID: userID, Username: "testuser", Email: "test@example.com", IsActive: true, } err := db.Create(user).Error require.NoError(t, err) // Create test track track := &models.Track{ UserID: userID, Title: "Test Track", FilePath: "/test/track.mp3", FileSize: 5 * 1024 * 1024, Format: "MP3", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted, } err = db.Create(track).Error require.NoError(t, err) // Create parent comment parentComment, err := service.CreateComment(ctx, track.ID, userID, "Parent comment", 0.0, nil) require.NoError(t, err) // Create multiple replies for i := 0; i < 10; i++ { _, err := service.CreateComment(ctx, track.ID, userID, "Reply", 0.0, &parentComment.ID) require.NoError(t, err) } // Get first page replies, total, err := service.GetReplies(ctx, parentComment.ID, 1, 3) assert.NoError(t, err) assert.Equal(t, int64(10), total) assert.Len(t, replies, 3) // Get second page replies2, total2, err := service.GetReplies(ctx, parentComment.ID, 2, 3) assert.NoError(t, err) assert.Equal(t, int64(10), total2) assert.Len(t, replies2, 3) }