- Conflit SQLx résolu (alignement sur version 0.7) - build.rs configurés pour protoc dans chat/stream servers - API Prometheus migrée vers HistogramOpts - Traits Display/Debug corrigés (String au lieu de &dyn Display) - API TOTP corrigée (totp-rs 5.4 avec Secret::Encoded) - Layers tracing-subscriber corrigés (types conditionnels) - VezaError/VezaResult exportés dans lib.rs - TransactionProvider simplifié (retour void au lieu de Box<dyn>) - VezaConfig contraint Serialize pour to_json() Files: veza-common/Cargo.toml, veza-common/src/*.rs, veza-chat-server/Cargo.toml, veza-chat-server/build.rs, veza-stream-server/Cargo.toml, veza-stream-server/build.rs, VEZA_ROADMAP.json Hours: 8 estimated, 3 actual
394 lines
11 KiB
Go
394 lines
11 KiB
Go
package middleware
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/google/uuid"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/mock"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// MockRoleService est un mock du RoleService pour les tests RBAC
|
|
// Implémente l'interface RoleChecker pour être compatible avec RequireRole
|
|
type MockRoleService struct {
|
|
mock.Mock
|
|
}
|
|
|
|
func (m *MockRoleService) HasRole(ctx context.Context, userID uuid.UUID, roleName string) (bool, error) {
|
|
args := m.Called(ctx, userID, roleName)
|
|
return args.Bool(0), args.Error(1)
|
|
}
|
|
|
|
func (m *MockRoleService) HasPermission(ctx context.Context, userID uuid.UUID, resource, action string) (bool, error) {
|
|
args := m.Called(ctx, userID, resource, action)
|
|
return args.Bool(0), args.Error(1)
|
|
}
|
|
|
|
func TestRequireRole_WithValidRole(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
|
|
mockRoleService := new(MockRoleService)
|
|
userID := uuid.New()
|
|
mockRoleService.On("HasRole", mock.Anything, userID, "admin").Return(true, nil)
|
|
|
|
router := gin.New()
|
|
router.Use(func(c *gin.Context) {
|
|
c.Set("user_id", userID)
|
|
c.Next()
|
|
})
|
|
router.Use(RequireRole(mockRoleService, "admin"))
|
|
router.GET("/test", func(c *gin.Context) {
|
|
c.JSON(http.StatusOK, gin.H{"message": "success"})
|
|
})
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/test", nil)
|
|
w := httptest.NewRecorder()
|
|
|
|
router.ServeHTTP(w, req)
|
|
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
|
mockRoleService.AssertExpectations(t)
|
|
}
|
|
|
|
func TestRequireRole_WithInvalidRole(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
|
|
mockRoleService := new(MockRoleService)
|
|
userID := uuid.New()
|
|
mockRoleService.On("HasRole", mock.Anything, userID, "admin").Return(false, nil)
|
|
|
|
handlerCalled := false
|
|
router := gin.New()
|
|
router.Use(func(c *gin.Context) {
|
|
c.Set("user_id", userID)
|
|
c.Next()
|
|
})
|
|
router.Use(RequireRole(mockRoleService, "admin"))
|
|
router.GET("/test", func(c *gin.Context) {
|
|
handlerCalled = true
|
|
c.JSON(http.StatusOK, gin.H{"message": "success"})
|
|
})
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/test", nil)
|
|
w := httptest.NewRecorder()
|
|
|
|
router.ServeHTTP(w, req)
|
|
|
|
assert.Equal(t, http.StatusForbidden, w.Code)
|
|
assert.False(t, handlerCalled, "Handler should not be called")
|
|
|
|
var response map[string]interface{}
|
|
err := json.Unmarshal(w.Body.Bytes(), &response)
|
|
assert.NoError(t, err)
|
|
// P0: Nouveau format AppError
|
|
errorObj, ok := response["error"].(map[string]interface{})
|
|
require.True(t, ok, "Error should be a map")
|
|
assert.Equal(t, "insufficient permissions", errorObj["message"])
|
|
|
|
mockRoleService.AssertExpectations(t)
|
|
}
|
|
|
|
func TestRequireRole_WithoutUserID(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
|
|
mockRoleService := new(MockRoleService)
|
|
handlerCalled := false
|
|
router := gin.New()
|
|
router.Use(RequireRole(mockRoleService, "admin"))
|
|
router.GET("/test", func(c *gin.Context) {
|
|
handlerCalled = true
|
|
c.JSON(http.StatusOK, gin.H{"message": "success"})
|
|
})
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/test", nil)
|
|
w := httptest.NewRecorder()
|
|
// user_id not set
|
|
|
|
router.ServeHTTP(w, req)
|
|
|
|
assert.Equal(t, http.StatusUnauthorized, w.Code)
|
|
assert.False(t, handlerCalled, "Handler should not be called")
|
|
|
|
var response map[string]interface{}
|
|
err := json.Unmarshal(w.Body.Bytes(), &response)
|
|
assert.NoError(t, err)
|
|
// P0: Nouveau format AppError
|
|
errorObj, ok := response["error"].(map[string]interface{})
|
|
require.True(t, ok, "Error should be a map")
|
|
assert.Equal(t, "unauthorized", errorObj["message"])
|
|
|
|
mockRoleService.AssertNotCalled(t, "HasRole")
|
|
}
|
|
|
|
func TestRequireRole_WithServiceError(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
|
|
mockRoleService := new(MockRoleService)
|
|
userID := uuid.New()
|
|
mockRoleService.On("HasRole", mock.Anything, userID, "admin").Return(false, assert.AnError)
|
|
|
|
handlerCalled := false
|
|
router := gin.New()
|
|
router.Use(func(c *gin.Context) {
|
|
c.Set("user_id", userID)
|
|
c.Next()
|
|
})
|
|
router.Use(RequireRole(mockRoleService, "admin"))
|
|
router.GET("/test", func(c *gin.Context) {
|
|
handlerCalled = true
|
|
c.JSON(http.StatusOK, gin.H{"message": "success"})
|
|
})
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/test", nil)
|
|
w := httptest.NewRecorder()
|
|
|
|
router.ServeHTTP(w, req)
|
|
|
|
assert.Equal(t, http.StatusInternalServerError, w.Code)
|
|
assert.False(t, handlerCalled, "Handler should not be called")
|
|
|
|
var response map[string]interface{}
|
|
err := json.Unmarshal(w.Body.Bytes(), &response)
|
|
assert.NoError(t, err)
|
|
assert.Contains(t, response, "error")
|
|
|
|
mockRoleService.AssertExpectations(t)
|
|
}
|
|
|
|
func TestRequireRole_WithIntUserIDType(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
|
|
mockRoleService := new(MockRoleService)
|
|
// No expectations - should fail before calling service
|
|
|
|
router := gin.New()
|
|
router.Use(func(c *gin.Context) {
|
|
c.Set("user_id", 123) // int instead of uuid.UUID
|
|
c.Next()
|
|
})
|
|
router.Use(RequireRole(mockRoleService, "admin"))
|
|
router.GET("/test", func(c *gin.Context) {
|
|
c.JSON(http.StatusOK, gin.H{"message": "success"})
|
|
})
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/test", nil)
|
|
w := httptest.NewRecorder()
|
|
|
|
router.ServeHTTP(w, req)
|
|
|
|
assert.Equal(t, http.StatusUnauthorized, w.Code)
|
|
mockRoleService.AssertNotCalled(t, "HasRole")
|
|
}
|
|
|
|
func TestRequirePermission_WithValidPermission(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
|
|
mockRoleService := new(MockRoleService)
|
|
userID := uuid.New()
|
|
mockRoleService.On("HasPermission", mock.Anything, userID, "tracks", "create").Return(true, nil)
|
|
|
|
router := gin.New()
|
|
router.Use(func(c *gin.Context) {
|
|
c.Set("user_id", userID)
|
|
c.Next()
|
|
})
|
|
router.Use(RequirePermission(mockRoleService, "tracks", "create"))
|
|
router.POST("/test", func(c *gin.Context) {
|
|
c.JSON(http.StatusOK, gin.H{"message": "success"})
|
|
})
|
|
|
|
req := httptest.NewRequest(http.MethodPost, "/test", nil)
|
|
w := httptest.NewRecorder()
|
|
|
|
router.ServeHTTP(w, req)
|
|
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
|
mockRoleService.AssertExpectations(t)
|
|
}
|
|
|
|
func TestRequirePermission_WithInvalidPermission(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
|
|
mockRoleService := new(MockRoleService)
|
|
userID := uuid.New()
|
|
mockRoleService.On("HasPermission", mock.Anything, userID, "tracks", "delete").Return(false, nil)
|
|
|
|
handlerCalled := false
|
|
router := gin.New()
|
|
router.Use(func(c *gin.Context) {
|
|
c.Set("user_id", userID)
|
|
c.Next()
|
|
})
|
|
router.Use(RequirePermission(mockRoleService, "tracks", "delete"))
|
|
router.DELETE("/test", func(c *gin.Context) {
|
|
handlerCalled = true
|
|
c.JSON(http.StatusOK, gin.H{"message": "success"})
|
|
})
|
|
|
|
req := httptest.NewRequest(http.MethodDelete, "/test", nil)
|
|
w := httptest.NewRecorder()
|
|
|
|
router.ServeHTTP(w, req)
|
|
|
|
assert.Equal(t, http.StatusForbidden, w.Code)
|
|
assert.False(t, handlerCalled, "Handler should not be called")
|
|
|
|
var response map[string]interface{}
|
|
err := json.Unmarshal(w.Body.Bytes(), &response)
|
|
assert.NoError(t, err)
|
|
// P0: Nouveau format AppError
|
|
errorObj, ok := response["error"].(map[string]interface{})
|
|
require.True(t, ok, "Error should be a map")
|
|
assert.Equal(t, "insufficient permissions", errorObj["message"])
|
|
|
|
mockRoleService.AssertExpectations(t)
|
|
}
|
|
|
|
func TestRequirePermission_WithoutUserID(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
|
|
mockRoleService := new(MockRoleService)
|
|
handlerCalled := false
|
|
router := gin.New()
|
|
router.Use(RequirePermission(mockRoleService, "tracks", "create"))
|
|
router.POST("/test", func(c *gin.Context) {
|
|
handlerCalled = true
|
|
c.JSON(http.StatusOK, gin.H{"message": "success"})
|
|
})
|
|
|
|
req := httptest.NewRequest(http.MethodPost, "/test", nil)
|
|
w := httptest.NewRecorder()
|
|
// user_id not set
|
|
|
|
router.ServeHTTP(w, req)
|
|
|
|
assert.Equal(t, http.StatusUnauthorized, w.Code)
|
|
assert.False(t, handlerCalled, "Handler should not be called")
|
|
|
|
var response map[string]interface{}
|
|
err := json.Unmarshal(w.Body.Bytes(), &response)
|
|
assert.NoError(t, err)
|
|
// P0: Nouveau format AppError
|
|
errorObj, ok := response["error"].(map[string]interface{})
|
|
require.True(t, ok, "Error should be a map")
|
|
assert.Equal(t, "unauthorized", errorObj["message"])
|
|
|
|
mockRoleService.AssertNotCalled(t, "HasPermission")
|
|
}
|
|
|
|
func TestRequirePermission_WithServiceError(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
|
|
mockRoleService := new(MockRoleService)
|
|
userID := uuid.New()
|
|
mockRoleService.On("HasPermission", mock.Anything, userID, "tracks", "create").Return(false, assert.AnError)
|
|
|
|
handlerCalled := false
|
|
router := gin.New()
|
|
router.Use(func(c *gin.Context) {
|
|
c.Set("user_id", userID)
|
|
c.Next()
|
|
})
|
|
router.Use(RequirePermission(mockRoleService, "tracks", "create"))
|
|
router.POST("/test", func(c *gin.Context) {
|
|
handlerCalled = true
|
|
c.JSON(http.StatusOK, gin.H{"message": "success"})
|
|
})
|
|
|
|
req := httptest.NewRequest(http.MethodPost, "/test", nil)
|
|
w := httptest.NewRecorder()
|
|
|
|
router.ServeHTTP(w, req)
|
|
|
|
assert.Equal(t, http.StatusInternalServerError, w.Code)
|
|
assert.False(t, handlerCalled, "Handler should not be called")
|
|
|
|
var response map[string]interface{}
|
|
err := json.Unmarshal(w.Body.Bytes(), &response)
|
|
assert.NoError(t, err)
|
|
assert.Contains(t, response, "error")
|
|
|
|
mockRoleService.AssertExpectations(t)
|
|
}
|
|
|
|
func TestRequirePermission_WithInvalidUserIDType(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
|
|
mockRoleService := new(MockRoleService)
|
|
handlerCalled := false
|
|
router := gin.New()
|
|
router.Use(func(c *gin.Context) {
|
|
c.Set("user_id", "invalid") // Invalid type
|
|
c.Next()
|
|
})
|
|
router.Use(RequirePermission(mockRoleService, "tracks", "create"))
|
|
router.POST("/test", func(c *gin.Context) {
|
|
handlerCalled = true
|
|
c.JSON(http.StatusOK, gin.H{"message": "success"})
|
|
})
|
|
|
|
req := httptest.NewRequest(http.MethodPost, "/test", nil)
|
|
w := httptest.NewRecorder()
|
|
|
|
router.ServeHTTP(w, req)
|
|
|
|
assert.Equal(t, http.StatusUnauthorized, w.Code)
|
|
assert.False(t, handlerCalled, "Handler should not be called")
|
|
|
|
var response map[string]interface{}
|
|
err := json.Unmarshal(w.Body.Bytes(), &response)
|
|
assert.NoError(t, err)
|
|
assert.Contains(t, response, "error")
|
|
// P0: Nouveau format AppError
|
|
errorObj, ok := response["error"].(map[string]interface{})
|
|
require.True(t, ok, "Error should be a map")
|
|
// "invalid" est une string, donc le code essaie de la parser en UUID et échoue
|
|
// Le message d'erreur est donc "invalid user id format" et non "invalid user id type"
|
|
assert.Equal(t, "invalid user id format", errorObj["message"])
|
|
|
|
mockRoleService.AssertNotCalled(t, "HasPermission")
|
|
}
|
|
|
|
func TestRequireRole_WithInvalidUserIDType(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
|
|
mockRoleService := new(MockRoleService)
|
|
handlerCalled := false
|
|
router := gin.New()
|
|
router.Use(func(c *gin.Context) {
|
|
c.Set("user_id", "invalid") // Invalid type
|
|
c.Next()
|
|
})
|
|
router.Use(RequireRole(mockRoleService, "admin"))
|
|
router.GET("/test", func(c *gin.Context) {
|
|
handlerCalled = true
|
|
c.JSON(http.StatusOK, gin.H{"message": "success"})
|
|
})
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/test", nil)
|
|
w := httptest.NewRecorder()
|
|
|
|
router.ServeHTTP(w, req)
|
|
|
|
assert.Equal(t, http.StatusUnauthorized, w.Code)
|
|
assert.False(t, handlerCalled, "Handler should not be called")
|
|
|
|
var response map[string]interface{}
|
|
err := json.Unmarshal(w.Body.Bytes(), &response)
|
|
assert.NoError(t, err)
|
|
assert.Contains(t, response, "error")
|
|
// P0: Nouveau format AppError
|
|
errorObj, ok := response["error"].(map[string]interface{})
|
|
require.True(t, ok, "Error should be a map")
|
|
// "invalid" est une string, donc le code essaie de la parser en UUID et échoue
|
|
// Le message d'erreur est donc "invalid user id format" et non "invalid user id type"
|
|
assert.Equal(t, "invalid user id format", errorObj["message"])
|
|
|
|
mockRoleService.AssertNotCalled(t, "HasRole")
|
|
}
|