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