- 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
437 lines
12 KiB
Go
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")
|
|
}
|