veza/veza-backend-api/internal/api/handlers/rbac_handlers_test.go

488 lines
14 KiB
Go

package handlers
import (
"bytes"
"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"
"go.uber.org/zap/zaptest"
"veza-backend-api/internal/services"
)
// MockRBACService is a mock implementation of RBACService for testing
type MockRBACService struct {
mock.Mock
}
func (m *MockRBACService) CreateRole(ctx context.Context, name, description string, permissions []uuid.UUID) (*services.Role, error) {
args := m.Called(ctx, name, description, permissions)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*services.Role), args.Error(1)
}
func (m *MockRBACService) GetRoleByID(ctx context.Context, roleID uuid.UUID) (*services.Role, error) {
args := m.Called(ctx, roleID)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*services.Role), args.Error(1)
}
func (m *MockRBACService) GetAllRoles(ctx context.Context) ([]*services.Role, error) {
args := m.Called(ctx)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).([]*services.Role), args.Error(1)
}
func (m *MockRBACService) AssignRoleToUser(ctx context.Context, userID, roleID uuid.UUID) error {
args := m.Called(ctx, userID, roleID)
return args.Error(0)
}
func (m *MockRBACService) RemoveRoleFromUser(ctx context.Context, userID, roleID uuid.UUID) error {
args := m.Called(ctx, userID, roleID)
return args.Error(0)
}
func (m *MockRBACService) GetUserRoles(ctx context.Context, userID uuid.UUID) ([]*services.Role, error) {
args := m.Called(ctx, userID)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).([]*services.Role), args.Error(1)
}
func (m *MockRBACService) GetUserPermissions(ctx context.Context, userID uuid.UUID) ([]services.Permission, error) {
args := m.Called(ctx, userID)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).([]services.Permission), args.Error(1)
}
func (m *MockRBACService) CheckPermission(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 (m *MockRBACService) CreatePermission(ctx context.Context, name, description, resource, action string) (*services.Permission, error) {
args := m.Called(ctx, name, description, resource, action)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*services.Permission), args.Error(1)
}
// setupTestRBACRouter creates a test router with RBAC handlers
func setupTestRBACRouter(mockService *MockRBACService) *gin.Engine {
gin.SetMode(gin.TestMode)
router := gin.New()
logger := zaptest.NewLogger(&testing.T{})
handler := NewRBACHandlersWithInterface(mockService, logger)
api := router.Group("/api/v1")
{
roles := api.Group("/roles")
{
roles.POST("", handler.CreateRole)
roles.GET("", handler.GetAllRoles)
roles.GET("/:id", handler.GetRole)
}
permissions := api.Group("/permissions")
{
permissions.POST("", handler.CreatePermission)
}
users := api.Group("/users")
{
users.POST("/:user_id/roles", handler.AssignRoleToUser)
users.DELETE("/:user_id/roles/:role_id", handler.RemoveRoleFromUser)
users.GET("/:user_id/roles", handler.GetUserRoles)
users.GET("/:user_id/permissions", handler.GetUserPermissions)
users.GET("/:user_id/permissions/check", handler.CheckPermission)
}
}
return router
}
func TestRBACHandlers_CreateRole_Success(t *testing.T) {
mockService := new(MockRBACService)
router := setupTestRBACRouter(mockService)
roleID := uuid.New()
expectedRole := &services.Role{
ID: roleID,
Name: "test-role",
Description: "Test role description",
Permissions: []services.Permission{},
IsSystem: false,
}
mockService.On("CreateRole", mock.Anything, "test-role", "Test role description", []uuid.UUID{}).Return(expectedRole, nil)
reqBody := map[string]interface{}{
"name": "test-role",
"description": "Test role description",
"permissions": []uuid.UUID{},
}
body, _ := json.Marshal(reqBody)
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/api/v1/roles", bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusCreated, w.Code)
mockService.AssertExpectations(t)
var response map[string]interface{}
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
assert.True(t, response["success"].(bool))
}
func TestRBACHandlers_CreateRole_InvalidJSON(t *testing.T) {
mockService := new(MockRBACService)
router := setupTestRBACRouter(mockService)
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/api/v1/roles", bytes.NewBuffer([]byte("invalid json")))
req.Header.Set("Content-Type", "application/json")
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusBadRequest, w.Code)
}
func TestRBACHandlers_CreateRole_MissingName(t *testing.T) {
mockService := new(MockRBACService)
router := setupTestRBACRouter(mockService)
reqBody := map[string]interface{}{
"description": "Test role description",
}
body, _ := json.Marshal(reqBody)
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/api/v1/roles", bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusBadRequest, w.Code)
}
func TestRBACHandlers_GetRole_Success(t *testing.T) {
mockService := new(MockRBACService)
router := setupTestRBACRouter(mockService)
roleID := uuid.New()
expectedRole := &services.Role{
ID: roleID,
Name: "test-role",
Description: "Test role description",
Permissions: []services.Permission{},
IsSystem: false,
}
mockService.On("GetRoleByID", mock.Anything, roleID).Return(expectedRole, nil)
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/v1/roles/"+roleID.String(), nil)
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
mockService.AssertExpectations(t)
var response map[string]interface{}
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
assert.True(t, response["success"].(bool))
}
func TestRBACHandlers_GetRole_InvalidID(t *testing.T) {
mockService := new(MockRBACService)
router := setupTestRBACRouter(mockService)
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/v1/roles/invalid-id", nil)
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusBadRequest, w.Code)
}
func TestRBACHandlers_GetRole_NotFound(t *testing.T) {
mockService := new(MockRBACService)
router := setupTestRBACRouter(mockService)
roleID := uuid.New()
mockService.On("GetRoleByID", mock.Anything, roleID).Return(nil, assert.AnError)
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/v1/roles/"+roleID.String(), nil)
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusNotFound, w.Code)
mockService.AssertExpectations(t)
}
func TestRBACHandlers_GetAllRoles_Success(t *testing.T) {
mockService := new(MockRBACService)
router := setupTestRBACRouter(mockService)
expectedRoles := []*services.Role{
{
ID: uuid.New(),
Name: "role1",
Description: "Role 1",
Permissions: []services.Permission{},
IsSystem: false,
},
{
ID: uuid.New(),
Name: "role2",
Description: "Role 2",
Permissions: []services.Permission{},
IsSystem: false,
},
}
mockService.On("GetAllRoles", mock.Anything).Return(expectedRoles, nil)
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/v1/roles", nil)
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
mockService.AssertExpectations(t)
var response map[string]interface{}
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
assert.True(t, response["success"].(bool))
}
func TestRBACHandlers_AssignRoleToUser_Success(t *testing.T) {
mockService := new(MockRBACService)
router := setupTestRBACRouter(mockService)
userID := uuid.New()
roleID := uuid.New()
mockService.On("AssignRoleToUser", mock.Anything, userID, roleID).Return(nil)
reqBody := map[string]interface{}{
"role_id": roleID.String(),
}
body, _ := json.Marshal(reqBody)
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/api/v1/users/"+userID.String()+"/roles", bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
mockService.AssertExpectations(t)
var response map[string]interface{}
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
assert.True(t, response["success"].(bool))
}
func TestRBACHandlers_AssignRoleToUser_InvalidUserID(t *testing.T) {
mockService := new(MockRBACService)
router := setupTestRBACRouter(mockService)
roleID := uuid.New()
reqBody := map[string]interface{}{
"role_id": roleID.String(),
}
body, _ := json.Marshal(reqBody)
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/api/v1/users/invalid-id/roles", bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusBadRequest, w.Code)
}
func TestRBACHandlers_RemoveRoleFromUser_Success(t *testing.T) {
mockService := new(MockRBACService)
router := setupTestRBACRouter(mockService)
userID := uuid.New()
roleID := uuid.New()
mockService.On("RemoveRoleFromUser", mock.Anything, userID, roleID).Return(nil)
w := httptest.NewRecorder()
req, _ := http.NewRequest("DELETE", "/api/v1/users/"+userID.String()+"/roles/"+roleID.String(), nil)
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
mockService.AssertExpectations(t)
}
func TestRBACHandlers_GetUserRoles_Success(t *testing.T) {
mockService := new(MockRBACService)
router := setupTestRBACRouter(mockService)
userID := uuid.New()
expectedRoles := []*services.Role{
{
ID: uuid.New(),
Name: "admin",
Description: "Admin role",
Permissions: []services.Permission{},
IsSystem: true,
},
}
mockService.On("GetUserRoles", mock.Anything, userID).Return(expectedRoles, nil)
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/v1/users/"+userID.String()+"/roles", nil)
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
mockService.AssertExpectations(t)
var response map[string]interface{}
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
assert.True(t, response["success"].(bool))
}
func TestRBACHandlers_GetUserPermissions_Success(t *testing.T) {
mockService := new(MockRBACService)
router := setupTestRBACRouter(mockService)
userID := uuid.New()
expectedPermissions := []services.Permission{
{
ID: uuid.New(),
Name: "read:tracks",
Description: "Read tracks",
Resource: "tracks",
Action: "read",
},
}
mockService.On("GetUserPermissions", mock.Anything, userID).Return(expectedPermissions, nil)
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/v1/users/"+userID.String()+"/permissions", nil)
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
mockService.AssertExpectations(t)
var response map[string]interface{}
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
assert.True(t, response["success"].(bool))
}
func TestRBACHandlers_CheckPermission_Success(t *testing.T) {
mockService := new(MockRBACService)
router := setupTestRBACRouter(mockService)
userID := uuid.New()
mockService.On("CheckPermission", mock.Anything, userID, "tracks", "read").Return(true, nil)
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/v1/users/"+userID.String()+"/permissions/check?resource=tracks&action=read", nil)
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
mockService.AssertExpectations(t)
var response map[string]interface{}
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
assert.True(t, response["success"].(bool))
assert.True(t, response["has_permission"].(bool))
}
func TestRBACHandlers_CheckPermission_MissingParams(t *testing.T) {
mockService := new(MockRBACService)
router := setupTestRBACRouter(mockService)
userID := uuid.New()
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/v1/users/"+userID.String()+"/permissions/check?resource=tracks", nil)
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusBadRequest, w.Code)
}
func TestRBACHandlers_CreatePermission_Success(t *testing.T) {
mockService := new(MockRBACService)
router := setupTestRBACRouter(mockService)
permissionID := uuid.New()
expectedPermission := &services.Permission{
ID: permissionID,
Name: "read:tracks",
Description: "Read tracks",
Resource: "tracks",
Action: "read",
}
mockService.On("CreatePermission", mock.Anything, "read:tracks", "Read tracks", "tracks", "read").Return(expectedPermission, nil)
reqBody := map[string]interface{}{
"name": "read:tracks",
"description": "Read tracks",
"resource": "tracks",
"action": "read",
}
body, _ := json.Marshal(reqBody)
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/api/v1/permissions", bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusCreated, w.Code)
mockService.AssertExpectations(t)
var response map[string]interface{}
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
assert.True(t, response["success"].(bool))
}
func TestRBACHandlers_CreatePermission_MissingFields(t *testing.T) {
mockService := new(MockRBACService)
router := setupTestRBACRouter(mockService)
reqBody := map[string]interface{}{
"name": "read:tracks",
// Missing required fields: resource, action
}
body, _ := json.Marshal(reqBody)
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/api/v1/permissions", bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusBadRequest, w.Code)
}