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

687 lines
18 KiB
Go

package handlers
import (
"bytes"
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"time"
"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"
)
// MockRoleService implements RoleService interface for testing
type MockRoleService struct {
mock.Mock
}
func (m *MockRoleService) GetRoles(ctx context.Context) ([]models.Role, error) {
args := m.Called(ctx)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).([]models.Role), args.Error(1)
}
func (m *MockRoleService) GetRole(ctx context.Context, roleID uuid.UUID) (*models.Role, error) {
args := m.Called(ctx, roleID)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*models.Role), args.Error(1)
}
func (m *MockRoleService) CreateRole(ctx context.Context, role *models.Role) error {
args := m.Called(ctx, role)
return args.Error(0)
}
func (m *MockRoleService) UpdateRole(ctx context.Context, roleID uuid.UUID, updates *models.Role) error {
args := m.Called(ctx, roleID, updates)
return args.Error(0)
}
func (m *MockRoleService) DeleteRole(ctx context.Context, roleID uuid.UUID) error {
args := m.Called(ctx, roleID)
return args.Error(0)
}
func (m *MockRoleService) AssignRoleToUser(ctx context.Context, userID uuid.UUID, roleID uuid.UUID, assignedBy uuid.UUID, expiresAt *time.Time) error {
args := m.Called(ctx, userID, roleID, assignedBy, expiresAt)
return args.Error(0)
}
func (m *MockRoleService) RevokeRoleFromUser(ctx context.Context, userID uuid.UUID, roleID uuid.UUID) error {
args := m.Called(ctx, userID, roleID)
return args.Error(0)
}
func (m *MockRoleService) GetUserRoles(ctx context.Context, userID uuid.UUID) ([]models.Role, error) {
args := m.Called(ctx, userID)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).([]models.Role), args.Error(1)
}
func setupTestRoleRouter(mockService *MockRoleService) *gin.Engine {
gin.SetMode(gin.TestMode)
router := gin.New()
logger := zap.NewNop()
handler := NewRoleHandlerWithInterface(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()
})
{
api.GET("/roles", handler.GetRoles)
api.GET("/roles/:id", handler.GetRole)
api.POST("/roles", handler.CreateRole)
api.PUT("/roles/:id", handler.UpdateRole)
api.DELETE("/roles/:id", handler.DeleteRole)
api.POST("/users/:userId/roles", handler.AssignRole)
api.DELETE("/users/:userId/roles/:roleId", handler.RevokeRole)
api.GET("/users/:id/roles", handler.GetUserRoles)
}
return router
}
func TestRoleHandler_GetRoles_Success(t *testing.T) {
// Setup
mockService := new(MockRoleService)
router := setupTestRoleRouter(mockService)
expectedRoles := []models.Role{
{
ID: uuid.New(),
Name: "admin",
Description: "Administrator role",
IsSystem: true,
},
{
ID: uuid.New(),
Name: "user",
Description: "Regular user role",
IsSystem: true,
},
}
mockService.On("GetRoles", mock.Anything).Return(expectedRoles, nil)
// Execute
req, _ := http.NewRequest("GET", "/api/v1/roles", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
// Assert
assert.Equal(t, http.StatusOK, w.Code)
mockService.AssertExpectations(t)
}
func TestRoleHandler_GetRoles_ServiceError(t *testing.T) {
// Setup
mockService := new(MockRoleService)
router := setupTestRoleRouter(mockService)
mockService.On("GetRoles", mock.Anything).Return(nil, assert.AnError)
// Execute
req, _ := http.NewRequest("GET", "/api/v1/roles", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
// Assert
assert.Equal(t, http.StatusInternalServerError, w.Code)
mockService.AssertExpectations(t)
}
func TestRoleHandler_GetRole_Success(t *testing.T) {
// Setup
mockService := new(MockRoleService)
router := setupTestRoleRouter(mockService)
roleID := uuid.New()
expectedRole := &models.Role{
ID: roleID,
Name: "admin",
Description: "Administrator role",
IsSystem: true,
}
mockService.On("GetRole", mock.Anything, roleID).Return(expectedRole, nil)
// Execute
req, _ := http.NewRequest("GET", "/api/v1/roles/"+roleID.String(), nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
// Assert
assert.Equal(t, http.StatusOK, w.Code)
mockService.AssertExpectations(t)
}
func TestRoleHandler_GetRole_InvalidID(t *testing.T) {
// Setup
mockService := new(MockRoleService)
router := setupTestRoleRouter(mockService)
// Execute
req, _ := http.NewRequest("GET", "/api/v1/roles/invalid", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
// Assert
assert.Equal(t, http.StatusBadRequest, w.Code)
mockService.AssertNotCalled(t, "GetRole")
}
func TestRoleHandler_GetRole_NotFound(t *testing.T) {
// Setup
mockService := new(MockRoleService)
router := setupTestRoleRouter(mockService)
roleID := uuid.New()
mockService.On("GetRole", mock.Anything, roleID).Return(nil, assert.AnError)
// Execute
req, _ := http.NewRequest("GET", "/api/v1/roles/"+roleID.String(), nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
// Assert
assert.Equal(t, http.StatusInternalServerError, w.Code)
mockService.AssertExpectations(t)
}
func TestRoleHandler_CreateRole_Success(t *testing.T) {
// Setup
mockService := new(MockRoleService)
router := setupTestRoleRouter(mockService)
role := models.Role{
Name: "moderator",
Description: "Moderator role",
IsSystem: false,
}
mockService.On("CreateRole", mock.Anything, &role).Return(nil)
body, _ := json.Marshal(role)
// Execute
req, _ := http.NewRequest("POST", "/api/v1/roles", bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
// Assert
assert.Equal(t, http.StatusCreated, w.Code)
mockService.AssertExpectations(t)
}
func TestRoleHandler_CreateRole_InvalidJSON(t *testing.T) {
// Setup
mockService := new(MockRoleService)
router := setupTestRoleRouter(mockService)
// Execute
req, _ := http.NewRequest("POST", "/api/v1/roles", bytes.NewBuffer([]byte("invalid json")))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
// Assert
assert.Equal(t, http.StatusBadRequest, w.Code)
mockService.AssertNotCalled(t, "CreateRole")
}
func TestRoleHandler_CreateRole_ServiceError(t *testing.T) {
// Setup
mockService := new(MockRoleService)
router := setupTestRoleRouter(mockService)
role := models.Role{
Name: "moderator",
Description: "Moderator role",
IsSystem: false,
}
mockService.On("CreateRole", mock.Anything, &role).Return(assert.AnError)
body, _ := json.Marshal(role)
// Execute
req, _ := http.NewRequest("POST", "/api/v1/roles", bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
// Assert
assert.Equal(t, http.StatusInternalServerError, w.Code)
mockService.AssertExpectations(t)
}
func TestRoleHandler_UpdateRole_Success(t *testing.T) {
// Setup
mockService := new(MockRoleService)
router := setupTestRoleRouter(mockService)
roleID := uuid.New()
updates := models.Role{
Name: "updated_moderator",
Description: "Updated moderator role",
}
mockService.On("UpdateRole", mock.Anything, roleID, &updates).Return(nil)
body, _ := json.Marshal(updates)
// Execute
req, _ := http.NewRequest("PUT", "/api/v1/roles/"+roleID.String(), bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
// Assert
assert.Equal(t, http.StatusOK, w.Code)
mockService.AssertExpectations(t)
}
func TestRoleHandler_UpdateRole_InvalidID(t *testing.T) {
// Setup
mockService := new(MockRoleService)
router := setupTestRoleRouter(mockService)
updates := models.Role{
Name: "updated_moderator",
}
body, _ := json.Marshal(updates)
// Execute
req, _ := http.NewRequest("PUT", "/api/v1/roles/invalid", bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
// Assert
assert.Equal(t, http.StatusBadRequest, w.Code)
mockService.AssertNotCalled(t, "UpdateRole")
}
func TestRoleHandler_UpdateRole_NotFound(t *testing.T) {
// Setup
mockService := new(MockRoleService)
router := setupTestRoleRouter(mockService)
roleID := uuid.New()
updates := models.Role{
Name: "updated_moderator",
}
mockService.On("UpdateRole", mock.Anything, roleID, &updates).Return(assert.AnError)
body, _ := json.Marshal(updates)
// Execute
req, _ := http.NewRequest("PUT", "/api/v1/roles/"+roleID.String(), bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
// Assert
assert.Equal(t, http.StatusInternalServerError, w.Code)
mockService.AssertExpectations(t)
}
func TestRoleHandler_DeleteRole_Success(t *testing.T) {
// Setup
mockService := new(MockRoleService)
router := setupTestRoleRouter(mockService)
roleID := uuid.New()
mockService.On("DeleteRole", mock.Anything, roleID).Return(nil)
// Execute
req, _ := http.NewRequest("DELETE", "/api/v1/roles/"+roleID.String(), nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
// Assert
assert.Equal(t, http.StatusOK, w.Code)
mockService.AssertExpectations(t)
}
func TestRoleHandler_DeleteRole_InvalidID(t *testing.T) {
// Setup
mockService := new(MockRoleService)
router := setupTestRoleRouter(mockService)
// Execute
req, _ := http.NewRequest("DELETE", "/api/v1/roles/invalid", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
// Assert
assert.Equal(t, http.StatusBadRequest, w.Code)
mockService.AssertNotCalled(t, "DeleteRole")
}
func TestRoleHandler_DeleteRole_NotFound(t *testing.T) {
// Setup
mockService := new(MockRoleService)
router := setupTestRoleRouter(mockService)
roleID := uuid.New()
// The handler returns 400 for "role not found" or "cannot delete system role", but 500 for other errors
mockService.On("DeleteRole", mock.Anything, roleID).Return(assert.AnError)
// Execute
req, _ := http.NewRequest("DELETE", "/api/v1/roles/"+roleID.String(), nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
// Assert - handler returns 500 for generic errors, 400 only for specific error messages
assert.Equal(t, http.StatusInternalServerError, w.Code)
mockService.AssertExpectations(t)
}
func TestRoleHandler_DeleteRole_RoleNotFoundMessage(t *testing.T) {
// Setup
mockService := new(MockRoleService)
router := setupTestRoleRouter(mockService)
roleID := uuid.New()
// Mock error with specific message that handler checks for 400
mockService.On("DeleteRole", mock.Anything, roleID).Return(&mockError{message: "role not found"})
// Execute
req, _ := http.NewRequest("DELETE", "/api/v1/roles/"+roleID.String(), nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
// Assert - handler checks error.Error() == "role not found" for 400
assert.Equal(t, http.StatusBadRequest, w.Code)
mockService.AssertExpectations(t)
}
func TestRoleHandler_DeleteRole_SystemRole(t *testing.T) {
// Setup
mockService := new(MockRoleService)
router := setupTestRoleRouter(mockService)
roleID := uuid.New()
// Mock error with specific message that handler checks for 400
mockService.On("DeleteRole", mock.Anything, roleID).Return(&mockError{message: "cannot delete system role"})
// Execute
req, _ := http.NewRequest("DELETE", "/api/v1/roles/"+roleID.String(), nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
// Assert - handler checks error.Error() == "cannot delete system role" for 400
assert.Equal(t, http.StatusBadRequest, w.Code)
mockService.AssertExpectations(t)
}
// mockError is a simple error type for testing
type mockError struct {
message string
}
func (e *mockError) Error() string {
return e.message
}
func TestRoleHandler_AssignRole_Success(t *testing.T) {
// Setup
mockService := new(MockRoleService)
router := setupTestRoleRouter(mockService)
userID := uuid.New()
roleID := uuid.New()
assignedBy := uuid.New()
reqBody := map[string]interface{}{
"role_id": roleID.String(),
}
body, _ := json.Marshal(reqBody)
mockService.On("AssignRoleToUser", mock.Anything, userID, roleID, assignedBy, (*time.Time)(nil)).Return(nil)
// Execute
req, _ := http.NewRequest("POST", "/api/v1/users/"+userID.String()+"/roles", bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-User-ID", assignedBy.String())
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
// Assert
assert.Equal(t, http.StatusOK, w.Code)
mockService.AssertExpectations(t)
}
func TestRoleHandler_AssignRole_Unauthorized(t *testing.T) {
// Setup
mockService := new(MockRoleService)
router := setupTestRoleRouter(mockService)
userID := uuid.New()
roleID := uuid.New()
reqBody := map[string]interface{}{
"role_id": roleID.String(),
}
body, _ := json.Marshal(reqBody)
// Execute
req, _ := http.NewRequest("POST", "/api/v1/users/"+userID.String()+"/roles", 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, "AssignRoleToUser")
}
func TestRoleHandler_AssignRole_InvalidUserID(t *testing.T) {
// Setup
mockService := new(MockRoleService)
router := setupTestRoleRouter(mockService)
roleID := uuid.New()
assignedBy := uuid.New()
reqBody := map[string]interface{}{
"role_id": roleID.String(),
}
body, _ := json.Marshal(reqBody)
// Execute
req, _ := http.NewRequest("POST", "/api/v1/users/invalid/roles", bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-User-ID", assignedBy.String())
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
// Assert
assert.Equal(t, http.StatusBadRequest, w.Code)
mockService.AssertNotCalled(t, "AssignRoleToUser")
}
func TestRoleHandler_RevokeRole_Success(t *testing.T) {
// Setup
mockService := new(MockRoleService)
router := setupTestRoleRouter(mockService)
userID := uuid.New()
roleID := uuid.New()
mockService.On("RevokeRoleFromUser", mock.Anything, userID, roleID).Return(nil)
// Execute
req, _ := http.NewRequest("DELETE", "/api/v1/users/"+userID.String()+"/roles/"+roleID.String(), nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
// Assert
assert.Equal(t, http.StatusOK, w.Code)
mockService.AssertExpectations(t)
}
func TestRoleHandler_RevokeRole_InvalidUserID(t *testing.T) {
// Setup
mockService := new(MockRoleService)
router := setupTestRoleRouter(mockService)
roleID := uuid.New()
// Execute
req, _ := http.NewRequest("DELETE", "/api/v1/users/invalid/roles/"+roleID.String(), nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
// Assert
assert.Equal(t, http.StatusBadRequest, w.Code)
mockService.AssertNotCalled(t, "RevokeRoleFromUser")
}
func TestRoleHandler_RevokeRole_InvalidRoleID(t *testing.T) {
// Setup
mockService := new(MockRoleService)
router := setupTestRoleRouter(mockService)
userID := uuid.New()
// Execute
req, _ := http.NewRequest("DELETE", "/api/v1/users/"+userID.String()+"/roles/invalid", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
// Assert
assert.Equal(t, http.StatusBadRequest, w.Code)
mockService.AssertNotCalled(t, "RevokeRoleFromUser")
}
func TestRoleHandler_RevokeRole_NotFound(t *testing.T) {
// Setup
mockService := new(MockRoleService)
router := setupTestRoleRouter(mockService)
userID := uuid.New()
roleID := uuid.New()
mockService.On("RevokeRoleFromUser", mock.Anything, userID, roleID).Return(assert.AnError)
// Execute
req, _ := http.NewRequest("DELETE", "/api/v1/users/"+userID.String()+"/roles/"+roleID.String(), nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
// Assert
assert.Equal(t, http.StatusInternalServerError, w.Code)
mockService.AssertExpectations(t)
}
func TestRoleHandler_GetUserRoles_Success(t *testing.T) {
// Setup
mockService := new(MockRoleService)
router := setupTestRoleRouter(mockService)
userID := uuid.New()
expectedRoles := []models.Role{
{
ID: uuid.New(),
Name: "admin",
},
{
ID: uuid.New(),
Name: "user",
},
}
mockService.On("GetUserRoles", mock.Anything, userID).Return(expectedRoles, nil)
// Execute
req, _ := http.NewRequest("GET", "/api/v1/users/"+userID.String()+"/roles", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
// Assert
assert.Equal(t, http.StatusOK, w.Code)
mockService.AssertExpectations(t)
}
func TestRoleHandler_GetUserRoles_InvalidID(t *testing.T) {
// Setup
mockService := new(MockRoleService)
router := setupTestRoleRouter(mockService)
// Execute
req, _ := http.NewRequest("GET", "/api/v1/users/invalid/roles", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
// Assert
assert.Equal(t, http.StatusBadRequest, w.Code)
mockService.AssertNotCalled(t, "GetUserRoles")
}
func TestRoleHandler_GetUserRoles_ServiceError(t *testing.T) {
// Setup
mockService := new(MockRoleService)
router := setupTestRoleRouter(mockService)
userID := uuid.New()
mockService.On("GetUserRoles", mock.Anything, userID).Return(nil, assert.AnError)
// Execute
req, _ := http.NewRequest("GET", "/api/v1/users/"+userID.String()+"/roles", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
// Assert
assert.Equal(t, http.StatusInternalServerError, w.Code)
mockService.AssertExpectations(t)
}
func TestNewRoleHandler(t *testing.T) {
// Setup
mockService := &services.RoleService{}
logger := zap.NewNop()
// Execute
handler := NewRoleHandler(mockService, logger)
// Assert
assert.NotNil(t, handler)
assert.NotNil(t, handler.roleService)
assert.NotNil(t, handler.commonHandler)
}