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) }