""" RBAC Unit Tests =============== Tests for the Role-Based Access Control system: - SystemRole hierarchy - has_role() method - set_role() with is_admin sync - Permission helper methods (can_access_admin_panel, can_manage_users, etc.) - role_required decorator """ import os import sys from unittest.mock import MagicMock, patch import pytest # Add project root to path sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..')) from database import SystemRole, CompanyRole, User # ============================================================ # SystemRole Hierarchy Tests # ============================================================ class TestSystemRoleHierarchy: """Test that SystemRole enum values enforce correct hierarchy.""" def test_role_ordering(self): assert SystemRole.UNAFFILIATED < SystemRole.MEMBER assert SystemRole.MEMBER < SystemRole.EMPLOYEE assert SystemRole.EMPLOYEE < SystemRole.MANAGER assert SystemRole.MANAGER < SystemRole.OFFICE_MANAGER assert SystemRole.OFFICE_MANAGER < SystemRole.ADMIN def test_role_values(self): assert SystemRole.UNAFFILIATED == 10 assert SystemRole.MEMBER == 20 assert SystemRole.EMPLOYEE == 30 assert SystemRole.MANAGER == 40 assert SystemRole.OFFICE_MANAGER == 50 assert SystemRole.ADMIN == 100 def test_from_string_valid(self): assert SystemRole.from_string('ADMIN') == SystemRole.ADMIN assert SystemRole.from_string('OFFICE_MANAGER') == SystemRole.OFFICE_MANAGER assert SystemRole.from_string('MEMBER') == SystemRole.MEMBER def test_from_string_case_insensitive(self): assert SystemRole.from_string('admin') == SystemRole.ADMIN assert SystemRole.from_string('member') == SystemRole.MEMBER def test_from_string_invalid_defaults_to_unaffiliated(self): assert SystemRole.from_string('INVALID') == SystemRole.UNAFFILIATED assert SystemRole.from_string('') == SystemRole.UNAFFILIATED def test_enum_by_name(self): assert SystemRole['ADMIN'] == SystemRole.ADMIN assert SystemRole['MEMBER'] == SystemRole.MEMBER with pytest.raises(KeyError): SystemRole['INVALID'] # ============================================================ # User.has_role() Tests # ============================================================ def _make_user(role_name='MEMBER', is_admin=False): """Create a fake user object with User methods for testing RBAC logic. Cannot use User() directly since SA instrumented attributes require a mapped session. Instead, use a plain object with User's methods bound. """ class FakeUser: pass user = FakeUser() user.role = role_name user.is_admin = is_admin user.company_role = 'NONE' user.company_id = None # Bind User's property and methods user.system_role = User.system_role.fget(user) user.has_role = lambda required_role: User.has_role(user, required_role) user.can_access_admin_panel = lambda: User.can_access_admin_panel(user) user.can_manage_users = lambda: User.can_manage_users(user) user.can_moderate_forum = lambda: User.can_moderate_forum(user) return user class TestHasRole: """Test User.has_role() hierarchical check.""" def test_admin_has_all_roles(self): user = _make_user('ADMIN') assert user.has_role(SystemRole.ADMIN) assert user.has_role(SystemRole.OFFICE_MANAGER) assert user.has_role(SystemRole.MANAGER) assert user.has_role(SystemRole.EMPLOYEE) assert user.has_role(SystemRole.MEMBER) assert user.has_role(SystemRole.UNAFFILIATED) def test_office_manager_cannot_access_admin(self): user = _make_user('OFFICE_MANAGER') assert not user.has_role(SystemRole.ADMIN) assert user.has_role(SystemRole.OFFICE_MANAGER) assert user.has_role(SystemRole.MANAGER) assert user.has_role(SystemRole.MEMBER) def test_member_minimal_access(self): user = _make_user('MEMBER') assert not user.has_role(SystemRole.ADMIN) assert not user.has_role(SystemRole.OFFICE_MANAGER) assert not user.has_role(SystemRole.MANAGER) assert not user.has_role(SystemRole.EMPLOYEE) assert user.has_role(SystemRole.MEMBER) assert user.has_role(SystemRole.UNAFFILIATED) def test_unaffiliated_lowest_access(self): user = _make_user('UNAFFILIATED') assert not user.has_role(SystemRole.MEMBER) assert user.has_role(SystemRole.UNAFFILIATED) def test_none_role_defaults_to_unaffiliated(self): user = _make_user(None) assert user.has_role(SystemRole.UNAFFILIATED) assert not user.has_role(SystemRole.MEMBER) # ============================================================ # User.set_role() Tests # ============================================================ class TestSetRole: """Test User.set_role() with is_admin sync. Uses a simple namespace object to test set_role logic directly, since SQLAlchemy instrumented attributes don't work outside a session. """ def _make_settable_user(self, role_name='MEMBER', is_admin=False): """Create an object that can receive set_role() assignments.""" class FakeUser: pass user = FakeUser() user.role = role_name user.is_admin = is_admin # Bind set_role method from User class user.set_role = lambda new_role, sync_is_admin=True: User.set_role(user, new_role, sync_is_admin) return user def test_set_admin_syncs_is_admin_true(self): user = self._make_settable_user('MEMBER', is_admin=False) user.set_role(SystemRole.ADMIN) assert user.role == 'ADMIN' assert user.is_admin is True def test_set_member_syncs_is_admin_false(self): user = self._make_settable_user('ADMIN', is_admin=True) user.set_role(SystemRole.MEMBER) assert user.role == 'MEMBER' assert user.is_admin is False def test_set_office_manager_is_admin_false(self): user = self._make_settable_user('ADMIN', is_admin=True) user.set_role(SystemRole.OFFICE_MANAGER) assert user.role == 'OFFICE_MANAGER' assert user.is_admin is False def test_set_role_without_sync(self): user = self._make_settable_user('MEMBER', is_admin=False) user.set_role(SystemRole.ADMIN, sync_is_admin=False) assert user.role == 'ADMIN' assert user.is_admin is False # Not synced # ============================================================ # Permission Helper Methods Tests # ============================================================ class TestPermissionHelpers: """Test can_access_admin_panel, can_manage_users, can_moderate_forum.""" def test_admin_can_access_admin_panel(self): assert _make_user('ADMIN').can_access_admin_panel() def test_office_manager_can_access_admin_panel(self): assert _make_user('OFFICE_MANAGER').can_access_admin_panel() def test_manager_cannot_access_admin_panel(self): assert not _make_user('MANAGER').can_access_admin_panel() def test_member_cannot_access_admin_panel(self): assert not _make_user('MEMBER').can_access_admin_panel() def test_only_admin_can_manage_users(self): assert _make_user('ADMIN').can_manage_users() assert not _make_user('OFFICE_MANAGER').can_manage_users() assert not _make_user('MANAGER').can_manage_users() def test_office_manager_can_moderate_forum(self): assert _make_user('ADMIN').can_moderate_forum() assert _make_user('OFFICE_MANAGER').can_moderate_forum() assert not _make_user('MANAGER').can_moderate_forum() # ============================================================ # CompanyRole Tests # ============================================================ class TestCompanyRole: """Test CompanyRole enum and hierarchy.""" def test_role_ordering(self): assert CompanyRole.NONE < CompanyRole.VIEWER assert CompanyRole.VIEWER < CompanyRole.EMPLOYEE assert CompanyRole.EMPLOYEE < CompanyRole.MANAGER def test_from_string(self): assert CompanyRole.from_string('MANAGER') == CompanyRole.MANAGER assert CompanyRole.from_string('EMPLOYEE') == CompanyRole.EMPLOYEE assert CompanyRole.from_string('INVALID') == CompanyRole.NONE