package middleware import ( "context" "encoding/json" "fmt" "net/http" "net/http/httptest" "testing" "veza-backend-api/internal/models" "github.com/gin-gonic/gin" "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) // MockPlaylistService est un mock du PlaylistService pour les tests type MockPlaylistService struct { mock.Mock } func (m *MockPlaylistService) CheckPermission(ctx context.Context, playlistID, userID uuid.UUID, requiredPermission models.PlaylistPermission) (bool, error) { args := m.Called(ctx, playlistID, userID, requiredPermission) return args.Bool(0), args.Error(1) } // setupPlaylistPermissionTestRouter crée un router de test avec le middleware de permissions func setupPlaylistPermissionTestRouter(t *testing.T) (*gin.Engine, *MockPlaylistService, func()) { gin.SetMode(gin.TestMode) // Setup mock service mockService := new(MockPlaylistService) // Setup router router := gin.New() router.Use(func(c *gin.Context) { // Mock authentication middleware - set user_id from query param if userIDStr := c.Query("user_id"); userIDStr != "" { if uid, err := uuid.Parse(userIDStr); err == nil { c.Set("user_id", uid) } } c.Next() }) // Test endpoint router.GET("/test/:id", CheckPlaylistPermission(mockService, models.PlaylistPermissionRead), func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"message": "success"}) }) cleanup := func() { // Nothing to cleanup } return router, mockService, cleanup } func TestCheckPlaylistPermission_Owner(t *testing.T) { router, mockService, cleanup := setupPlaylistPermissionTestRouter(t) defer cleanup() playlistID := uuid.New() userID := uuid.New() mockService.On("CheckPermission", mock.Anything, playlistID, userID, models.PlaylistPermissionRead).Return(true, nil) w := httptest.NewRecorder() req := httptest.NewRequest("GET", fmt.Sprintf("/test/%s?user_id=%s", playlistID, userID), nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) var response map[string]string json.Unmarshal(w.Body.Bytes(), &response) assert.Equal(t, "success", response["message"]) mockService.AssertExpectations(t) } func TestCheckPlaylistPermission_PublicRead(t *testing.T) { router, mockService, cleanup := setupPlaylistPermissionTestRouter(t) defer cleanup() playlistID := uuid.New() userID := uuid.New() mockService.On("CheckPermission", mock.Anything, playlistID, userID, models.PlaylistPermissionRead).Return(true, nil) w := httptest.NewRecorder() req := httptest.NewRequest("GET", fmt.Sprintf("/test/%s?user_id=%s", playlistID, userID), nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) var response map[string]string json.Unmarshal(w.Body.Bytes(), &response) assert.Equal(t, "success", response["message"]) mockService.AssertExpectations(t) } func TestCheckPlaylistPermission_PrivateForbidden(t *testing.T) { router, mockService, cleanup := setupPlaylistPermissionTestRouter(t) defer cleanup() playlistID := uuid.New() userID := uuid.New() mockService.On("CheckPermission", mock.Anything, playlistID, userID, models.PlaylistPermissionRead).Return(false, nil) w := httptest.NewRecorder() req := httptest.NewRequest("GET", fmt.Sprintf("/test/%s?user_id=%s", playlistID, userID), nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusForbidden, w.Code) var response map[string]interface{} err := json.Unmarshal(w.Body.Bytes(), &response) require.NoError(t, err) // P0: Nouveau format AppError errorObj, ok := response["error"].(map[string]interface{}) require.True(t, ok, "Error should be a map") assert.Contains(t, errorObj["message"].(string), "forbidden") mockService.AssertExpectations(t) } func TestCheckPlaylistPermission_CollaboratorRead(t *testing.T) { router, mockService, cleanup := setupPlaylistPermissionTestRouter(t) defer cleanup() playlistID := uuid.New() userID := uuid.New() mockService.On("CheckPermission", mock.Anything, playlistID, userID, models.PlaylistPermissionRead).Return(true, nil) w := httptest.NewRecorder() req := httptest.NewRequest("GET", fmt.Sprintf("/test/%s?user_id=%s", playlistID, userID), nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) var response map[string]string json.Unmarshal(w.Body.Bytes(), &response) assert.Equal(t, "success", response["message"]) mockService.AssertExpectations(t) } func TestCheckPlaylistPermission_CollaboratorWrite(t *testing.T) { gin.SetMode(gin.TestMode) mockService := new(MockPlaylistService) playlistID := uuid.New() userID := uuid.New() mockService.On("CheckPermission", mock.Anything, playlistID, userID, models.PlaylistPermissionWrite).Return(true, nil) routerWrite := gin.New() routerWrite.Use(func(c *gin.Context) { if userIDStr := c.Query("user_id"); userIDStr != "" { if uid, err := uuid.Parse(userIDStr); err == nil { c.Set("user_id", uid) } } c.Next() }) routerWrite.GET("/test/:id", RequirePlaylistWrite(mockService), func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"message": "success"}) }) w := httptest.NewRecorder() req := httptest.NewRequest("GET", fmt.Sprintf("/test/%s?user_id=%s", playlistID, userID), nil) routerWrite.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) mockService.AssertExpectations(t) } func TestCheckPlaylistPermission_CollaboratorReadCannotWrite(t *testing.T) { gin.SetMode(gin.TestMode) mockService := new(MockPlaylistService) playlistID := uuid.New() userID := uuid.New() mockService.On("CheckPermission", mock.Anything, playlistID, userID, models.PlaylistPermissionWrite).Return(false, nil) routerWrite := gin.New() routerWrite.Use(func(c *gin.Context) { if userIDStr := c.Query("user_id"); userIDStr != "" { if uid, err := uuid.Parse(userIDStr); err == nil { c.Set("user_id", uid) } } c.Next() }) routerWrite.GET("/test/:id", RequirePlaylistWrite(mockService), func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"message": "success"}) }) w := httptest.NewRecorder() req := httptest.NewRequest("GET", fmt.Sprintf("/test/%s?user_id=%s", playlistID, userID), nil) routerWrite.ServeHTTP(w, req) assert.Equal(t, http.StatusForbidden, w.Code) mockService.AssertExpectations(t) } func TestCheckPlaylistPermission_NotFound(t *testing.T) { router, mockService, cleanup := setupPlaylistPermissionTestRouter(t) defer cleanup() playlistID := uuid.New() userID := uuid.New() mockService.On("CheckPermission", mock.Anything, playlistID, userID, models.PlaylistPermissionRead).Return(false, fmt.Errorf("playlist not found")) w := httptest.NewRecorder() req := httptest.NewRequest("GET", fmt.Sprintf("/test/%s?user_id=%s", playlistID, userID), nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusNotFound, w.Code) var response map[string]interface{} err := json.Unmarshal(w.Body.Bytes(), &response) require.NoError(t, err) // P0: Nouveau format AppError errorObj, ok := response["error"].(map[string]interface{}) require.True(t, ok, "Error should be a map") assert.Contains(t, errorObj["message"].(string), "playlist not found") mockService.AssertExpectations(t) } func TestCheckPlaylistPermission_Unauthorized(t *testing.T) { router, mockService, cleanup := setupPlaylistPermissionTestRouter(t) defer cleanup() playlistID := uuid.New() w := httptest.NewRecorder() req := httptest.NewRequest("GET", fmt.Sprintf("/test/%s", playlistID), nil) // Pas de user_id router.ServeHTTP(w, req) assert.Equal(t, http.StatusUnauthorized, w.Code) var response map[string]interface{} err := json.Unmarshal(w.Body.Bytes(), &response) require.NoError(t, err) // P0: Nouveau format AppError errorObj, ok := response["error"].(map[string]interface{}) require.True(t, ok, "Error should be a map") assert.Contains(t, errorObj["message"].(string), "unauthorized") mockService.AssertNotCalled(t, "CheckPermission") } func TestCheckPlaylistPermission_InvalidPlaylistID(t *testing.T) { router, mockService, cleanup := setupPlaylistPermissionTestRouter(t) defer cleanup() userID := uuid.New() w := httptest.NewRecorder() req := httptest.NewRequest("GET", fmt.Sprintf("/test/invalid?user_id=%s", userID), nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) var response map[string]interface{} err := json.Unmarshal(w.Body.Bytes(), &response) require.NoError(t, err) // P0: Nouveau format AppError errorObj, ok := response["error"].(map[string]interface{}) require.True(t, ok, "Error should be a map") assert.Contains(t, errorObj["message"].(string), "invalid playlist id") mockService.AssertNotCalled(t, "CheckPermission") } func TestRequirePlaylistOwner(t *testing.T) { gin.SetMode(gin.TestMode) mockService := new(MockPlaylistService) playlistID := uuid.New() ownerID := uuid.New() otherID := uuid.New() mockService.On("CheckPermission", mock.Anything, playlistID, ownerID, models.PlaylistPermissionAdmin).Return(true, nil) mockService.On("CheckPermission", mock.Anything, playlistID, otherID, models.PlaylistPermissionAdmin).Return(false, nil) routerOwner := gin.New() routerOwner.Use(func(c *gin.Context) { if userIDStr := c.Query("user_id"); userIDStr != "" { if uid, err := uuid.Parse(userIDStr); err == nil { c.Set("user_id", uid) } } c.Next() }) routerOwner.GET("/test/:id", RequirePlaylistOwner(mockService), func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"message": "success"}) }) // Owner peut accéder w := httptest.NewRecorder() req := httptest.NewRequest("GET", fmt.Sprintf("/test/%s?user_id=%s", playlistID, ownerID), nil) routerOwner.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) // Autre utilisateur ne peut pas accéder w2 := httptest.NewRecorder() req2 := httptest.NewRequest("GET", fmt.Sprintf("/test/%s?user_id=%s", playlistID, otherID), nil) routerOwner.ServeHTTP(w2, req2) assert.Equal(t, http.StatusForbidden, w2.Code) mockService.AssertExpectations(t) }