- Tests complets pour frontend_log_handler.go (12 tests)
- Tests couvrent NewFrontendLogHandler et ReceiveLog
- Tests pour tous les niveaux de log (DEBUG, INFO, WARN, ERROR)
- Tests pour gestion des erreurs et validation JSON
- Couverture actuelle: 30.6% (objectif: 80%)
Files: veza-backend-api/internal/handlers/frontend_log_handler_test.go
VEZA_ROADMAP.json
Hours: 16 estimated, 23 actual
338 lines
11 KiB
Go
338 lines
11 KiB
Go
package handlers
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
|
|
"veza-backend-api/internal/models"
|
|
|
|
"veza-backend-api/internal/services" // Needed for search params
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/google/uuid"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/mock"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
// MockPlaylistService is a mock implementation of PlaylistServiceInterface
|
|
type MockPlaylistService struct {
|
|
mock.Mock
|
|
}
|
|
|
|
func (m *MockPlaylistService) CreatePlaylist(ctx context.Context, userID uuid.UUID, title, description string, isPublic bool) (*models.Playlist, error) {
|
|
args := m.Called(ctx, userID, title, description, isPublic)
|
|
if args.Get(0) == nil {
|
|
return nil, args.Error(1)
|
|
}
|
|
return args.Get(0).(*models.Playlist), args.Error(1)
|
|
}
|
|
|
|
func (m *MockPlaylistService) GetPlaylists(ctx context.Context, currentUserID *uuid.UUID, filterUserID *uuid.UUID, page, limit int) ([]*models.Playlist, int64, error) {
|
|
args := m.Called(ctx, currentUserID, filterUserID, page, limit)
|
|
if args.Get(0) == nil {
|
|
return nil, 0, args.Error(2)
|
|
}
|
|
return args.Get(0).([]*models.Playlist), args.Get(1).(int64), args.Error(2)
|
|
}
|
|
|
|
func (m *MockPlaylistService) GetPlaylist(ctx context.Context, id uuid.UUID, currentUserID *uuid.UUID) (*models.Playlist, error) {
|
|
args := m.Called(ctx, id, currentUserID)
|
|
if args.Get(0) == nil {
|
|
return nil, args.Error(1)
|
|
}
|
|
return args.Get(0).(*models.Playlist), args.Error(1)
|
|
}
|
|
|
|
func (m *MockPlaylistService) UpdatePlaylist(ctx context.Context, id uuid.UUID, userID uuid.UUID, title, description *string, isPublic *bool) (*models.Playlist, error) {
|
|
args := m.Called(ctx, id, userID, title, description, isPublic)
|
|
if args.Get(0) == nil {
|
|
return nil, args.Error(1)
|
|
}
|
|
return args.Get(0).(*models.Playlist), args.Error(1)
|
|
}
|
|
|
|
func (m *MockPlaylistService) DeletePlaylist(ctx context.Context, id uuid.UUID, userID uuid.UUID) error {
|
|
args := m.Called(ctx, id, userID)
|
|
return args.Error(0)
|
|
}
|
|
|
|
func (m *MockPlaylistService) AddTrack(ctx context.Context, playlistID, trackID, userID uuid.UUID) error {
|
|
args := m.Called(ctx, playlistID, trackID, userID)
|
|
return args.Error(0)
|
|
}
|
|
|
|
func (m *MockPlaylistService) RemoveTrack(ctx context.Context, playlistID, trackID, userID uuid.UUID) error {
|
|
args := m.Called(ctx, playlistID, trackID, userID)
|
|
return args.Error(0)
|
|
}
|
|
|
|
func (m *MockPlaylistService) ReorderTracks(ctx context.Context, playlistID, userID uuid.UUID, trackIDs []uuid.UUID) error {
|
|
args := m.Called(ctx, playlistID, userID, trackIDs)
|
|
return args.Error(0)
|
|
}
|
|
|
|
func (m *MockPlaylistService) AddCollaborator(ctx context.Context, playlistID, userID, collaboratorUserID uuid.UUID, permission models.PlaylistPermission) (*models.PlaylistCollaborator, error) {
|
|
args := m.Called(ctx, playlistID, userID, collaboratorUserID, permission)
|
|
if args.Get(0) == nil {
|
|
return nil, args.Error(1)
|
|
}
|
|
return args.Get(0).(*models.PlaylistCollaborator), args.Error(1)
|
|
}
|
|
|
|
func (m *MockPlaylistService) RemoveCollaborator(ctx context.Context, playlistID, userID, collaboratorUserID uuid.UUID) error {
|
|
args := m.Called(ctx, playlistID, userID, collaboratorUserID)
|
|
return args.Error(0)
|
|
}
|
|
|
|
func (m *MockPlaylistService) UpdateCollaboratorPermission(ctx context.Context, playlistID, userID, collaboratorUserID uuid.UUID, permission models.PlaylistPermission) error {
|
|
args := m.Called(ctx, playlistID, userID, collaboratorUserID, permission)
|
|
return args.Error(0)
|
|
}
|
|
|
|
func (m *MockPlaylistService) GetCollaborators(ctx context.Context, playlistID, userID uuid.UUID) ([]*models.PlaylistCollaborator, error) {
|
|
args := m.Called(ctx, playlistID, userID)
|
|
if args.Get(0) == nil {
|
|
return nil, args.Error(1)
|
|
}
|
|
return args.Get(0).([]*models.PlaylistCollaborator), args.Error(1)
|
|
}
|
|
|
|
func (m *MockPlaylistService) CreateShareLink(ctx context.Context, playlistID, userID uuid.UUID, expiresAt *time.Time) (*models.PlaylistShareLink, error) {
|
|
args := m.Called(ctx, playlistID, userID, expiresAt)
|
|
if args.Get(0) == nil {
|
|
return nil, args.Error(1)
|
|
}
|
|
return args.Get(0).(*models.PlaylistShareLink), args.Error(1)
|
|
}
|
|
|
|
func (m *MockPlaylistService) FollowPlaylist(ctx context.Context, playlistID, userID uuid.UUID) error {
|
|
args := m.Called(ctx, playlistID, userID)
|
|
return args.Error(0)
|
|
}
|
|
|
|
func (m *MockPlaylistService) UnfollowPlaylist(ctx context.Context, playlistID, userID uuid.UUID) error {
|
|
args := m.Called(ctx, playlistID, userID)
|
|
return args.Error(0)
|
|
}
|
|
|
|
func (m *MockPlaylistService) CheckPermission(ctx context.Context, playlistID, userID uuid.UUID, permission models.PlaylistPermission) (bool, error) {
|
|
args := m.Called(ctx, playlistID, userID, permission)
|
|
return args.Bool(0), args.Error(1)
|
|
}
|
|
|
|
func (m *MockPlaylistService) SearchPlaylists(ctx context.Context, params services.SearchPlaylistsParams) ([]*models.Playlist, int64, error) {
|
|
args := m.Called(ctx, params)
|
|
if args.Get(0) == nil {
|
|
return nil, 0, args.Error(2)
|
|
}
|
|
return args.Get(0).([]*models.Playlist), args.Get(1).(int64), args.Error(2)
|
|
}
|
|
|
|
func setupPlaylistTestRouter(mockService *MockPlaylistService) *gin.Engine {
|
|
gin.SetMode(gin.TestMode)
|
|
router := gin.New()
|
|
|
|
logger := zap.NewNop()
|
|
// Use the generic new handler with interface
|
|
handler := NewPlaylistHandlerWithInterface(mockService, nil, logger) // db is nil as we use mock service
|
|
|
|
api := router.Group("/api/v1")
|
|
api.Use(func(c *gin.Context) {
|
|
// Mock auth middleware manually for simplicity
|
|
userIDStr := c.GetHeader("X-User-ID")
|
|
if userIDStr != "" {
|
|
uid, err := uuid.Parse(userIDStr)
|
|
if err == nil {
|
|
// Inject user_id into context as middleware would
|
|
c.Set("user_id", uid)
|
|
}
|
|
}
|
|
c.Next()
|
|
})
|
|
{
|
|
api.GET("/playlists", handler.GetPlaylists)
|
|
api.POST("/playlists", handler.CreatePlaylist)
|
|
api.GET("/playlists/:id", handler.GetPlaylist)
|
|
api.PUT("/playlists/:id", handler.UpdatePlaylist)
|
|
api.DELETE("/playlists/:id", handler.DeletePlaylist)
|
|
}
|
|
|
|
return router
|
|
}
|
|
|
|
func TestPlaylistHandler_GetPlaylists_Success(t *testing.T) {
|
|
mockService := new(MockPlaylistService)
|
|
router := setupPlaylistTestRouter(mockService)
|
|
|
|
userID := uuid.New()
|
|
|
|
expectedPlaylists := []*models.Playlist{
|
|
{ID: uuid.New(), Title: "List 1", UserID: userID},
|
|
{ID: uuid.New(), Title: "List 2", UserID: userID},
|
|
}
|
|
|
|
// Expect GetPlaylists call
|
|
mockService.On("GetPlaylists", mock.Anything, mock.MatchedBy(func(u *uuid.UUID) bool {
|
|
return u != nil && *u == userID
|
|
}), mock.Anything, 1, 20).Return(expectedPlaylists, int64(2), nil)
|
|
|
|
req, _ := http.NewRequest("GET", "/api/v1/playlists", nil)
|
|
req.Header.Set("X-User-ID", userID.String())
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
|
|
|
var response map[string]interface{}
|
|
json.Unmarshal(w.Body.Bytes(), &response)
|
|
|
|
data := response["data"].(map[string]interface{})
|
|
assert.Equal(t, float64(2), data["total"])
|
|
|
|
mockService.AssertExpectations(t)
|
|
}
|
|
|
|
func TestPlaylistHandler_CreatePlaylist_Success(t *testing.T) {
|
|
mockService := new(MockPlaylistService)
|
|
router := setupPlaylistTestRouter(mockService)
|
|
|
|
userID := uuid.New()
|
|
reqBody := CreatePlaylistRequest{
|
|
Title: "New Playlist",
|
|
Description: "Desc",
|
|
IsPublic: true,
|
|
}
|
|
|
|
createdPlaylist := &models.Playlist{
|
|
ID: uuid.New(),
|
|
UserID: userID,
|
|
Title: reqBody.Title,
|
|
Description: reqBody.Description,
|
|
IsPublic: reqBody.IsPublic,
|
|
}
|
|
|
|
mockService.On("CreatePlaylist", mock.Anything, userID, reqBody.Title, reqBody.Description, reqBody.IsPublic).Return(createdPlaylist, nil)
|
|
|
|
body, _ := json.Marshal(reqBody)
|
|
req, _ := http.NewRequest("POST", "/api/v1/playlists", 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.Equal(t, http.StatusCreated, w.Code)
|
|
mockService.AssertExpectations(t)
|
|
}
|
|
|
|
func TestPlaylistHandler_GetPlaylist_NotFound(t *testing.T) {
|
|
mockService := new(MockPlaylistService)
|
|
router := setupPlaylistTestRouter(mockService)
|
|
|
|
userID := uuid.New() // Authenticated user
|
|
playlistID := uuid.New()
|
|
|
|
// Error returned by service when not found or access denied
|
|
mockService.On("GetPlaylist", mock.Anything, playlistID, mock.MatchedBy(func(u *uuid.UUID) bool {
|
|
return u != nil && *u == userID
|
|
})).Return(nil, services.ErrPlaylistNotFound)
|
|
|
|
req, _ := http.NewRequest("GET", "/api/v1/playlists/"+playlistID.String(), nil)
|
|
req.Header.Set("X-User-ID", userID.String())
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
assert.Equal(t, http.StatusNotFound, w.Code)
|
|
mockService.AssertExpectations(t)
|
|
}
|
|
|
|
func TestPlaylistHandler_DeletePlaylist_Success(t *testing.T) {
|
|
mockService := new(MockPlaylistService)
|
|
router := setupPlaylistTestRouter(mockService)
|
|
|
|
userID := uuid.New()
|
|
playlistID := uuid.New()
|
|
|
|
mockService.On("DeletePlaylist", mock.Anything, playlistID, userID).Return(nil)
|
|
|
|
req, _ := http.NewRequest("DELETE", "/api/v1/playlists/"+playlistID.String(), nil)
|
|
req.Header.Set("X-User-ID", userID.String())
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
|
mockService.AssertExpectations(t)
|
|
}
|
|
|
|
func TestPlaylistHandler_DeletePlaylist_Forbidden(t *testing.T) {
|
|
mockService := new(MockPlaylistService)
|
|
router := setupPlaylistTestRouter(mockService)
|
|
|
|
userID := uuid.New()
|
|
playlistID := uuid.New()
|
|
|
|
mockService.On("DeletePlaylist", mock.Anything, playlistID, userID).Return(services.ErrAccessDenied)
|
|
|
|
req, _ := http.NewRequest("DELETE", "/api/v1/playlists/"+playlistID.String(), nil)
|
|
req.Header.Set("X-User-ID", userID.String())
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
assert.Equal(t, http.StatusForbidden, w.Code)
|
|
mockService.AssertExpectations(t)
|
|
}
|
|
|
|
func TestPlaylistHandler_UpdatePlaylist_Success(t *testing.T) {
|
|
mockService := new(MockPlaylistService)
|
|
router := setupPlaylistTestRouter(mockService)
|
|
|
|
userID := uuid.New()
|
|
playlistID := uuid.New()
|
|
|
|
newTitle := "Updated Title"
|
|
reqBody := UpdatePlaylistRequest{
|
|
Title: &newTitle,
|
|
}
|
|
|
|
updatedPlaylist := &models.Playlist{
|
|
ID: playlistID,
|
|
Title: newTitle,
|
|
}
|
|
|
|
mockService.On("UpdatePlaylist", mock.Anything, playlistID, userID,
|
|
mock.MatchedBy(func(s *string) bool { return s != nil && *s == "Updated Title" }),
|
|
(*string)(nil), (*bool)(nil)).Return(updatedPlaylist, nil)
|
|
|
|
body, _ := json.Marshal(reqBody)
|
|
req, _ := http.NewRequest("PUT", "/api/v1/playlists/"+playlistID.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.Equal(t, http.StatusOK, w.Code)
|
|
mockService.AssertExpectations(t)
|
|
}
|
|
|
|
func TestPlaylistHandler_UpdatePlaylist_ValidationError(t *testing.T) {
|
|
mockService := new(MockPlaylistService)
|
|
router := setupPlaylistTestRouter(mockService)
|
|
|
|
userID := uuid.New()
|
|
playlistID := uuid.New()
|
|
|
|
// Title too long or empty if it was required, but here just malformed request maybe?
|
|
// Let's send invalid json
|
|
req, _ := http.NewRequest("PUT", "/api/v1/playlists/"+playlistID.String(), bytes.NewBuffer([]byte("{invalid")))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req.Header.Set("X-User-ID", userID.String())
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
assert.Equal(t, http.StatusBadRequest, w.Code)
|
|
}
|