veza/veza-backend-api/internal/handlers/comment_handler_test.go
senke b103a09a25 chore: consolidate CI, E2E, backend and frontend updates
- CI: workflows updates (cd, ci), remove playwright.yml
- E2E: global-setup, auth/playlists/profile specs
- Remove playwright-report and test-results artifacts from tracking
- Backend: auth, handlers, services, workers, migrations
- Frontend: components, features, vite config
- Add e2e-results.json to gitignore
- Docs: REMEDIATION_PROGRESS, audit archive
- Rust: chat-server, stream-server updates
2026-02-17 16:43:21 +01:00

437 lines
12 KiB
Go

package handlers
import (
"bytes"
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"veza-backend-api/internal/common"
"veza-backend-api/internal/models"
"veza-backend-api/internal/services"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"go.uber.org/zap"
)
// MockCommentService implements CommentService interface for testing
type MockCommentService struct {
mock.Mock
}
func (m *MockCommentService) CreateComment(ctx context.Context, trackID uuid.UUID, userID uuid.UUID, content string, timestamp float64, parentID *uuid.UUID) (*models.TrackComment, error) {
args := m.Called(ctx, trackID, userID, content, timestamp, parentID)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*models.TrackComment), args.Error(1)
}
func (m *MockCommentService) GetComments(ctx context.Context, trackID uuid.UUID, page, limit int) ([]models.TrackComment, int64, error) {
args := m.Called(ctx, trackID, page, limit)
if args.Get(0) == nil {
return nil, args.Get(1).(int64), args.Error(2)
}
return args.Get(0).([]models.TrackComment), args.Get(1).(int64), args.Error(2)
}
func (m *MockCommentService) UpdateComment(ctx context.Context, commentID uuid.UUID, userID uuid.UUID, content string) (*models.TrackComment, error) {
args := m.Called(ctx, commentID, userID, content)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*models.TrackComment), args.Error(1)
}
func (m *MockCommentService) DeleteComment(ctx context.Context, commentID uuid.UUID, userID uuid.UUID, isAdmin bool) error {
args := m.Called(ctx, commentID, userID, isAdmin)
return args.Error(0)
}
func (m *MockCommentService) GetReplies(ctx context.Context, parentID uuid.UUID, page, limit int) ([]models.TrackComment, int64, error) {
args := m.Called(ctx, parentID, page, limit)
if args.Get(0) == nil {
return nil, args.Get(1).(int64), args.Error(2)
}
return args.Get(0).([]models.TrackComment), args.Get(1).(int64), args.Error(2)
}
func (m *MockCommentService) GetTrackCreatorID(ctx context.Context, trackID uuid.UUID) (uuid.UUID, error) {
args := m.Called(ctx, trackID)
return args.Get(0).(uuid.UUID), args.Error(1)
}
func setupTestCommentRouter(mockService *MockCommentService) *gin.Engine {
gin.SetMode(gin.TestMode)
router := gin.New()
logger := zap.NewNop()
handler := NewCommentHandlerWithInterface(mockService, logger)
api := router.Group("/api/v1")
api.Use(func(c *gin.Context) {
// Mock auth middleware - set user_id from header if present
userIDStr := c.GetHeader("X-User-ID")
if userIDStr != "" {
uid, err := uuid.Parse(userIDStr)
if err == nil {
common.SetUserIDInContext(c, uid)
}
}
c.Next()
})
{
tracks := api.Group("/tracks")
{
tracks.POST("/:id/comments", handler.CreateComment)
tracks.GET("/:id/comments", handler.GetComments)
}
// Comments routes - UpdateComment and DeleteComment use /comments/:id
comments := api.Group("/comments")
{
comments.PUT("/:id", handler.UpdateComment)
comments.DELETE("/:id", handler.DeleteComment)
}
}
return router
}
func TestCommentHandler_CreateComment_Success(t *testing.T) {
// Setup
mockService := new(MockCommentService)
router := setupTestCommentRouter(mockService)
userID := uuid.New()
trackID := uuid.New()
expectedComment := &models.TrackComment{
ID: uuid.New(),
TrackID: trackID,
UserID: userID,
Content: "Great track!",
}
mockService.On("CreateComment", mock.Anything, trackID, userID, "Great track!", 0.0, (*uuid.UUID)(nil)).Return(expectedComment, nil)
reqBody := CreateCommentRequest{
Content: "Great track!",
}
body, _ := json.Marshal(reqBody)
// Execute
req, _ := http.NewRequest("POST", "/api/v1/tracks/"+trackID.String()+"/comments", bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-User-ID", userID.String())
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
// Assert
assert.Equal(t, http.StatusCreated, w.Code)
mockService.AssertExpectations(t)
}
func TestCommentHandler_CreateComment_WithParentID(t *testing.T) {
// Setup
mockService := new(MockCommentService)
router := setupTestCommentRouter(mockService)
userID := uuid.New()
trackID := uuid.New()
parentID := uuid.New()
expectedComment := &models.TrackComment{
ID: uuid.New(),
TrackID: trackID,
UserID: userID,
Content: "Reply comment",
ParentID: &parentID,
}
mockService.On("CreateComment", mock.Anything, trackID, userID, "Reply comment", 0.0, &parentID).Return(expectedComment, nil)
reqBody := CreateCommentRequest{
Content: "Reply comment",
ParentID: &parentID,
}
body, _ := json.Marshal(reqBody)
// Execute
req, _ := http.NewRequest("POST", "/api/v1/tracks/"+trackID.String()+"/comments", bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-User-ID", userID.String())
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
// Assert
assert.Equal(t, http.StatusCreated, w.Code)
mockService.AssertExpectations(t)
}
func TestCommentHandler_CreateComment_Unauthorized(t *testing.T) {
// Setup
mockService := new(MockCommentService)
router := setupTestCommentRouter(mockService)
trackID := uuid.New()
reqBody := CreateCommentRequest{
Content: "Great track!",
}
body, _ := json.Marshal(reqBody)
// Execute
req, _ := http.NewRequest("POST", "/api/v1/tracks/"+trackID.String()+"/comments", bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
// Assert - GetUserIDUUID returns 401 via RespondWithAppError
assert.True(t, w.Code == http.StatusUnauthorized || w.Code == http.StatusForbidden)
mockService.AssertNotCalled(t, "CreateComment")
}
func TestCommentHandler_CreateComment_InvalidTrackID(t *testing.T) {
// Setup
mockService := new(MockCommentService)
router := setupTestCommentRouter(mockService)
userID := uuid.New()
reqBody := CreateCommentRequest{
Content: "Great track!",
}
body, _ := json.Marshal(reqBody)
// Execute
req, _ := http.NewRequest("POST", "/api/v1/tracks/invalid/comments", bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-User-ID", userID.String())
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
// Assert
assert.Equal(t, http.StatusBadRequest, w.Code)
mockService.AssertNotCalled(t, "CreateComment")
}
func TestCommentHandler_CreateComment_TrackNotFound(t *testing.T) {
// Setup
mockService := new(MockCommentService)
router := setupTestCommentRouter(mockService)
userID := uuid.New()
trackID := uuid.New()
mockService.On("CreateComment", mock.Anything, trackID, userID, "Great track!", 0.0, (*uuid.UUID)(nil)).Return(nil, services.ErrTrackNotFound)
reqBody := CreateCommentRequest{
Content: "Great track!",
}
body, _ := json.Marshal(reqBody)
// Execute
req, _ := http.NewRequest("POST", "/api/v1/tracks/"+trackID.String()+"/comments", bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-User-ID", userID.String())
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
// Assert
assert.Equal(t, http.StatusNotFound, w.Code)
mockService.AssertExpectations(t)
}
func TestCommentHandler_GetComments_Success(t *testing.T) {
// Setup
mockService := new(MockCommentService)
router := setupTestCommentRouter(mockService)
trackID := uuid.New()
userID := uuid.New()
expectedComments := []models.TrackComment{
{
ID: uuid.New(),
TrackID: trackID,
UserID: userID,
Content: "First comment",
},
{
ID: uuid.New(),
TrackID: trackID,
UserID: userID,
Content: "Second comment",
},
}
mockService.On("GetComments", mock.Anything, trackID, 1, 20).Return(expectedComments, int64(2), nil)
// Execute
req, _ := http.NewRequest("GET", "/api/v1/tracks/"+trackID.String()+"/comments?page=1&limit=20", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
// Assert
assert.Equal(t, http.StatusOK, w.Code)
mockService.AssertExpectations(t)
}
func TestCommentHandler_GetComments_InvalidTrackID(t *testing.T) {
// Setup
mockService := new(MockCommentService)
router := setupTestCommentRouter(mockService)
// Execute
req, _ := http.NewRequest("GET", "/api/v1/tracks/invalid/comments", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
// Assert
assert.Equal(t, http.StatusBadRequest, w.Code)
mockService.AssertNotCalled(t, "GetComments")
}
func TestCommentHandler_UpdateComment_Success(t *testing.T) {
// Setup
mockService := new(MockCommentService)
router := setupTestCommentRouter(mockService)
userID := uuid.New()
trackID := uuid.New()
commentID := uuid.New()
expectedComment := &models.TrackComment{
ID: commentID,
TrackID: trackID,
UserID: userID,
Content: "Updated comment",
}
mockService.On("UpdateComment", mock.Anything, commentID, userID, "Updated comment").Return(expectedComment, nil)
reqBody := UpdateCommentRequest{
Content: "Updated comment",
}
body, _ := json.Marshal(reqBody)
// Execute - UpdateComment uses /comments/:id route
req, _ := http.NewRequest("PUT", "/api/v1/comments/"+commentID.String(), bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-User-ID", userID.String())
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
// Assert
assert.Equal(t, http.StatusOK, w.Code)
mockService.AssertExpectations(t)
}
func TestCommentHandler_UpdateComment_Unauthorized(t *testing.T) {
// Setup
mockService := new(MockCommentService)
router := setupTestCommentRouter(mockService)
commentID := uuid.New()
reqBody := UpdateCommentRequest{
Content: "Updated comment",
}
body, _ := json.Marshal(reqBody)
// Execute
req, _ := http.NewRequest("PUT", "/api/v1/comments/"+commentID.String(), bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
// Assert
assert.True(t, w.Code == http.StatusUnauthorized || w.Code == http.StatusForbidden)
mockService.AssertNotCalled(t, "UpdateComment")
}
func TestCommentHandler_UpdateComment_CommentNotFound(t *testing.T) {
// Setup
mockService := new(MockCommentService)
router := setupTestCommentRouter(mockService)
userID := uuid.New()
commentID := uuid.New()
mockService.On("UpdateComment", mock.Anything, commentID, userID, "Updated comment").Return(nil, services.ErrCommentNotFound)
reqBody := UpdateCommentRequest{
Content: "Updated comment",
}
body, _ := json.Marshal(reqBody)
// Execute
req, _ := http.NewRequest("PUT", "/api/v1/comments/"+commentID.String(), bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-User-ID", userID.String())
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
// Assert
assert.Equal(t, http.StatusNotFound, w.Code)
mockService.AssertExpectations(t)
}
func TestCommentHandler_DeleteComment_Success(t *testing.T) {
// Setup
mockService := new(MockCommentService)
router := setupTestCommentRouter(mockService)
userID := uuid.New()
commentID := uuid.New()
mockService.On("DeleteComment", mock.Anything, commentID, userID, false).Return(nil)
// Execute - DeleteComment uses /comments/:id route
req, _ := http.NewRequest("DELETE", "/api/v1/comments/"+commentID.String(), nil)
req.Header.Set("X-User-ID", userID.String())
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
// Assert
assert.Equal(t, http.StatusOK, w.Code)
mockService.AssertExpectations(t)
}
func TestCommentHandler_DeleteComment_Unauthorized(t *testing.T) {
// Setup
mockService := new(MockCommentService)
router := setupTestCommentRouter(mockService)
commentID := uuid.New()
// Execute
req, _ := http.NewRequest("DELETE", "/api/v1/comments/"+commentID.String(), nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
// Assert
assert.True(t, w.Code == http.StatusUnauthorized || w.Code == http.StatusForbidden)
mockService.AssertNotCalled(t, "DeleteComment")
}
func TestCommentHandler_DeleteComment_InvalidCommentID(t *testing.T) {
// Setup
mockService := new(MockCommentService)
router := setupTestCommentRouter(mockService)
userID := uuid.New()
// Execute
req, _ := http.NewRequest("DELETE", "/api/v1/comments/invalid", nil)
req.Header.Set("X-User-ID", userID.String())
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
// Assert
assert.Equal(t, http.StatusBadRequest, w.Code)
mockService.AssertNotCalled(t, "DeleteComment")
}