[T0-006] test(backend): Ajout tests pour role_handler

- Tests complets pour role_handler.go (22 tests)
- Interface RoleServiceInterface créée pour permettre le mock
- Tests couvrent GetRoles, GetRole, CreateRole, UpdateRole, DeleteRole, AssignRole, RevokeRole, GetUserRoles
- Couverture actuelle: 30.3% (objectif: 80%)

Files: veza-backend-api/internal/handlers/role_handler.go
       veza-backend-api/internal/handlers/role_handler_test.go
       VEZA_ROADMAP.json
Hours: 16 estimated, 19 actual
This commit is contained in:
senke 2025-12-28 22:30:38 +01:00
parent af134bb6a5
commit fb25465e21
7 changed files with 731 additions and 19 deletions

View file

@ -235,7 +235,7 @@
"priority": "P0",
"status": "in_progress",
"estimated_hours": 16,
"actual_hours": 17,
"actual_hours": 19,
"started_at": "2025-12-28T15:13:09Z",
"completed_at": null,
"dependencies": ["T0-001", "T0-005"],
@ -269,21 +269,23 @@
"veza-backend-api/internal/handlers/search_handlers_test.go",
"veza-backend-api/internal/handlers/comment_handler_test.go",
"veza-backend-api/internal/handlers/avatar_handler_test.go",
"veza-backend-api/internal/handlers/notification_handlers_test.go"
"veza-backend-api/internal/handlers/notification_handlers_test.go",
"veza-backend-api/internal/handlers/role_handler_test.go"
],
"to_modify": [
"veza-backend-api/internal/models/role.go",
"veza-backend-api/internal/handlers/search_handlers.go",
"veza-backend-api/internal/handlers/comment_handler.go",
"veza-backend-api/internal/handlers/avatar_handler.go",
"veza-backend-api/internal/handlers/notification_handlers.go"
"veza-backend-api/internal/handlers/notification_handlers.go",
"veza-backend-api/internal/handlers/role_handler.go"
]
},
"commands": {
"verify": ["cd veza-backend-api && ./scripts/test_coverage_one_by_one.sh"],
"test": ["cd veza-backend-api && go test ./internal/api/handlers -run TestRBACHandlers -v", "cd veza-backend-api && go test ./internal/api/user -run TestUserHandler -v", "cd veza-backend-api && go test ./internal/services -run TestSocialService -v", "cd veza-backend-api && go test ./internal/services -run TestCacheService -v", "cd veza-backend-api && go test ./internal/services -run TestNotificationService -v", "cd veza-backend-api && go test ./internal/services -run TestPasswordService_ -v", "cd veza-backend-api && go test ./internal/services -run TestMetadataService -v", "cd veza-backend-api && go test ./internal/services -run TestBackupService -v", "cd veza-backend-api && go test ./internal/services -run TestJobService -v", "cd veza-backend-api && go test ./internal/services -run TestAuditService -v", "cd veza-backend-api && go test ./internal/services -run TestAccountLockoutService -v", "cd veza-backend-api && go test ./internal/services -run TestEmailService_ -v", "cd veza-backend-api && go test ./internal/services -run TestRoleService_ -v"]
},
"implementation_notes": "Progrès réalisés: 1) Scripts créés pour exécuter les tests par groupes/packages individuels (évite les crashes RAM), 2) Tests complets pour handlers RBAC (16 tests, tous passent), 3) Tests complets pour handlers user (16 tests, tous passent), 4) Tests complets pour service social (18 tests, tous passent), 5) Tests complets pour service cache (20 tests, tous passent), 6) Tests complets pour service notification (15 tests, tous passent), 7) Tests complets pour service password (15 tests, tous passent, certains skip car nécessitent PostgreSQL NOW()), 8) Tests complets pour service metadata (14 tests, tous passent), 9) Tests complets pour service backup (15 tests, tous passent, 1 skip car nécessite PostgreSQL pg_dump), 10) Tests complets pour service job (14 tests, tous passent), 11) Tests complets pour service audit (20 tests, tous passent, 2 skip car bug dans service avec UserID nil), 12) Tests complets pour service account_lockout (18 tests, tous passent), 13) Tests complets pour service email (28 tests, tous passent, 1 skip car nécessite DB réelle), 14) Tests complets pour service role (24 tests, tous passent), 15) Hook GORM ajouté dans UserRole.BeforeCreate pour remplir automatiquement RoleName depuis RoleID, 16) Interfaces créées (RBACServiceInterface, UserServiceInterface, DataExportServiceInterface) pour permettre le mock dans les tests, 17) Mock créé pour JobEnqueuer interface, 18) Tests complets pour search_handlers.go (6 tests, tous passent) - interface SearchServiceInterface créée, 19) Tests complets pour comment_handler.go (12 tests, tous passent) - interface CommentServiceInterface créée avec toutes les méthodes nécessaires, 20) Tests complets pour avatar_handler.go (15 tests, tous passent) - interfaces ImageServiceInterface et UserServiceInterfaceForAvatar créées, 21) Tests complets pour notification_handlers.go (14 tests, tous passent) - interface NotificationServiceInterface créée, 22) Couverture actuelle: 30.3% (objectif: 80%). Prochaines étapes: Créer des tests pour les autres handlers critiques (track, playlist, room, message, auth, oauth, password_reset, etc.) et services manquants pour atteindre 80%. Cette tâche nécessite encore environ 4-6 heures de travail pour créer suffisamment de tests.",
"implementation_notes": "Progrès réalisés: 1) Scripts créés pour exécuter les tests par groupes/packages individuels (évite les crashes RAM), 2) Tests complets pour handlers RBAC (16 tests, tous passent), 3) Tests complets pour handlers user (16 tests, tous passent), 4) Tests complets pour service social (18 tests, tous passent), 5) Tests complets pour service cache (20 tests, tous passent), 6) Tests complets pour service notification (15 tests, tous passent), 7) Tests complets pour service password (15 tests, tous passent, certains skip car nécessitent PostgreSQL NOW()), 8) Tests complets pour service metadata (14 tests, tous passent), 9) Tests complets pour service backup (15 tests, tous passent, 1 skip car nécessite PostgreSQL pg_dump), 10) Tests complets pour service job (14 tests, tous passent), 11) Tests complets pour service audit (20 tests, tous passent, 2 skip car bug dans service avec UserID nil), 12) Tests complets pour service account_lockout (18 tests, tous passent), 13) Tests complets pour service email (28 tests, tous passent, 1 skip car nécessite DB réelle), 14) Tests complets pour service role (24 tests, tous passent), 15) Hook GORM ajouté dans UserRole.BeforeCreate pour remplir automatiquement RoleName depuis RoleID, 16) Interfaces créées (RBACServiceInterface, UserServiceInterface, DataExportServiceInterface) pour permettre le mock dans les tests, 17) Mock créé pour JobEnqueuer interface, 18) Tests complets pour search_handlers.go (6 tests, tous passent) - interface SearchServiceInterface créée, 19) Tests complets pour comment_handler.go (12 tests, tous passent) - interface CommentServiceInterface créée avec toutes les méthodes nécessaires, 20) Tests complets pour avatar_handler.go (15 tests, tous passent) - interfaces ImageServiceInterface et UserServiceInterfaceForAvatar créées, 21) Tests complets pour notification_handlers.go (14 tests, tous passent) - interface NotificationServiceInterface créée, 22) Tests complets pour role_handler.go (22 tests, tous passent) - interface RoleServiceInterface créée, 23) Couverture actuelle: 30.3% (objectif: 80%). Prochaines étapes: Créer des tests pour les autres handlers critiques (track, playlist, room, message, auth, oauth, password_reset, settings, etc.) et services manquants pour atteindre 80%. Cette tâche nécessite encore environ 4-6 heures de travail pour créer suffisamment de tests.",
"blockers": []
},
{

View file

@ -445,8 +445,8 @@ veza-backend-api/internal/config/watcher.go:91.4,94.29 2 0
veza-backend-api/internal/config/watcher.go:94.29,97.50 3 0
veza-backend-api/internal/config/watcher.go:97.50,99.6 1 0
veza-backend-api/internal/config/watcher.go:99.11,101.6 1 0
veza-backend-api/internal/config/watcher.go:104.38,105.11 1 0
veza-backend-api/internal/config/watcher.go:105.11,107.5 1 0
veza-backend-api/internal/config/watcher.go:104.38,105.11 1 1
veza-backend-api/internal/config/watcher.go:105.11,107.5 1 1
veza-backend-api/internal/config/watcher.go:108.4,108.51 1 0
veza-backend-api/internal/config/watcher.go:110.21,112.28 1 1
veza-backend-api/internal/config/watcher.go:112.28,114.5 1 0

View file

@ -56,7 +56,7 @@ veza-backend-api/internal/logging/logger.go:340.24,342.3 1 1
veza-backend-api/internal/logging/logger.go:344.2,345.25 2 1
veza-backend-api/internal/logging/logger.go:349.44,353.6 2 1
veza-backend-api/internal/logging/logger.go:353.6,354.10 1 1
veza-backend-api/internal/logging/logger.go:355.28,356.40 1 1
veza-backend-api/internal/logging/logger.go:355.28,356.40 1 0
veza-backend-api/internal/logging/logger.go:357.11,359.60 2 1
veza-backend-api/internal/logging/logger.go:359.60,361.5 1 1
veza-backend-api/internal/logging/logger.go:362.4,362.14 1 1

View file

@ -77,17 +77,17 @@ veza-backend-api/internal/workers/job_worker.go:94.2,96.32 1 1
veza-backend-api/internal/workers/job_worker.go:100.48,107.43 3 1
veza-backend-api/internal/workers/job_worker.go:107.43,109.3 1 1
veza-backend-api/internal/workers/job_worker.go:113.63,118.45 3 1
veza-backend-api/internal/workers/job_worker.go:118.45,120.3 1 0
veza-backend-api/internal/workers/job_worker.go:118.45,120.3 1 1
veza-backend-api/internal/workers/job_worker.go:122.2,122.6 1 1
veza-backend-api/internal/workers/job_worker.go:122.6,123.10 1 1
veza-backend-api/internal/workers/job_worker.go:124.21,125.10 1 1
veza-backend-api/internal/workers/job_worker.go:126.19,127.47 1 0
veza-backend-api/internal/workers/job_worker.go:127.47,129.5 1 0
veza-backend-api/internal/workers/job_worker.go:135.46,150.25 3 1
veza-backend-api/internal/workers/job_worker.go:150.25,152.3 1 0
veza-backend-api/internal/workers/job_worker.go:154.2,154.29 1 1
veza-backend-api/internal/workers/job_worker.go:150.25,152.3 1 1
veza-backend-api/internal/workers/job_worker.go:154.2,154.29 1 0
veza-backend-api/internal/workers/job_worker.go:154.29,156.3 1 0
veza-backend-api/internal/workers/job_worker.go:157.2,157.12 1 1
veza-backend-api/internal/workers/job_worker.go:157.2,157.12 1 0
veza-backend-api/internal/workers/job_worker.go:161.70,167.6 4 1
veza-backend-api/internal/workers/job_worker.go:167.6,168.10 1 1
veza-backend-api/internal/workers/job_worker.go:169.21,171.10 2 1

View file

@ -1322,8 +1322,8 @@ veza-backend-api/internal/config/watcher.go:91.4,94.29 2 0
veza-backend-api/internal/config/watcher.go:94.29,97.50 3 0
veza-backend-api/internal/config/watcher.go:97.50,99.6 1 0
veza-backend-api/internal/config/watcher.go:99.11,101.6 1 0
veza-backend-api/internal/config/watcher.go:104.38,105.11 1 0
veza-backend-api/internal/config/watcher.go:105.11,107.5 1 0
veza-backend-api/internal/config/watcher.go:104.38,105.11 1 1
veza-backend-api/internal/config/watcher.go:105.11,107.5 1 1
veza-backend-api/internal/config/watcher.go:108.4,108.51 1 0
veza-backend-api/internal/config/watcher.go:110.21,112.28 1 1
veza-backend-api/internal/config/watcher.go:112.28,114.5 1 0
@ -3283,7 +3283,7 @@ veza-backend-api/internal/logging/logger.go:340.24,342.3 1 1
veza-backend-api/internal/logging/logger.go:344.2,345.25 2 1
veza-backend-api/internal/logging/logger.go:349.44,353.6 2 1
veza-backend-api/internal/logging/logger.go:353.6,354.10 1 1
veza-backend-api/internal/logging/logger.go:355.28,356.40 1 1
veza-backend-api/internal/logging/logger.go:355.28,356.40 1 0
veza-backend-api/internal/logging/logger.go:357.11,359.60 2 1
veza-backend-api/internal/logging/logger.go:359.60,361.5 1 1
veza-backend-api/internal/logging/logger.go:362.4,362.14 1 1
@ -5693,17 +5693,17 @@ veza-backend-api/internal/workers/job_worker.go:94.2,96.32 1 1
veza-backend-api/internal/workers/job_worker.go:100.48,107.43 3 1
veza-backend-api/internal/workers/job_worker.go:107.43,109.3 1 1
veza-backend-api/internal/workers/job_worker.go:113.63,118.45 3 1
veza-backend-api/internal/workers/job_worker.go:118.45,120.3 1 0
veza-backend-api/internal/workers/job_worker.go:118.45,120.3 1 1
veza-backend-api/internal/workers/job_worker.go:122.2,122.6 1 1
veza-backend-api/internal/workers/job_worker.go:122.6,123.10 1 1
veza-backend-api/internal/workers/job_worker.go:124.21,125.10 1 1
veza-backend-api/internal/workers/job_worker.go:126.19,127.47 1 0
veza-backend-api/internal/workers/job_worker.go:127.47,129.5 1 0
veza-backend-api/internal/workers/job_worker.go:135.46,150.25 3 1
veza-backend-api/internal/workers/job_worker.go:150.25,152.3 1 0
veza-backend-api/internal/workers/job_worker.go:154.2,154.29 1 1
veza-backend-api/internal/workers/job_worker.go:150.25,152.3 1 1
veza-backend-api/internal/workers/job_worker.go:154.2,154.29 1 0
veza-backend-api/internal/workers/job_worker.go:154.29,156.3 1 0
veza-backend-api/internal/workers/job_worker.go:157.2,157.12 1 1
veza-backend-api/internal/workers/job_worker.go:157.2,157.12 1 0
veza-backend-api/internal/workers/job_worker.go:161.70,167.6 4 1
veza-backend-api/internal/workers/job_worker.go:167.6,168.10 1 1
veza-backend-api/internal/workers/job_worker.go:169.21,171.10 2 1

View file

@ -1,6 +1,7 @@
package handlers
import (
"context"
"net/http"
"time"
@ -13,9 +14,22 @@ import (
"go.uber.org/zap"
)
// RoleServiceInterface defines the interface for role operations
// This allows for easier testing with mocks
type RoleServiceInterface interface {
GetRoles(ctx context.Context) ([]models.Role, error)
GetRole(ctx context.Context, roleID uuid.UUID) (*models.Role, error)
CreateRole(ctx context.Context, role *models.Role) error
UpdateRole(ctx context.Context, roleID uuid.UUID, updates *models.Role) error
DeleteRole(ctx context.Context, roleID uuid.UUID) error
AssignRoleToUser(ctx context.Context, userID uuid.UUID, roleID uuid.UUID, assignedBy uuid.UUID, expiresAt *time.Time) error
RevokeRoleFromUser(ctx context.Context, userID uuid.UUID, roleID uuid.UUID) error
GetUserRoles(ctx context.Context, userID uuid.UUID) ([]models.Role, error)
}
// RoleHandler gère les endpoints de gestion des rôles
type RoleHandler struct {
roleService *services.RoleService
roleService RoleServiceInterface
commonHandler *CommonHandler
}
@ -27,6 +41,14 @@ func NewRoleHandler(roleService *services.RoleService, logger *zap.Logger) *Role
}
}
// NewRoleHandlerWithInterface crée un nouveau RoleHandler avec une interface (pour les tests)
func NewRoleHandlerWithInterface(roleService RoleServiceInterface, logger *zap.Logger) *RoleHandler {
return &RoleHandler{
roleService: roleService,
commonHandler: NewCommonHandler(logger),
}
}
// GetRoles récupère tous les rôles
// BE-API-007: Implement roles management endpoints
func (h *RoleHandler) GetRoles(c *gin.Context) {

View file

@ -0,0 +1,688 @@
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)
}