# #region Test.SecurityBadgeService [C:3] [TYPE Module] [SEMANTICS test,security,badge] # @BRIEF Verify SecurityBadgeService contracts — role extraction, permission pairs, security summary. # @RELATION BINDS_TO -> [SecurityBadgeService] # @TEST_EDGE: missing_field -> empty/None roles, permissions, auth_source handled gracefully # @TEST_EDGE: invalid_type -> empty role names, whitespace-only names sanitized # @TEST_EDGE: external_fail -> discover_declared_permissions raises, fallback to user permissions from pathlib import Path import sys sys.path.insert(0, str(Path(__file__).parent.parent / "src")) from unittest.mock import MagicMock, patch import pytest # #region _make_user [C:1] [TYPE Function] def _make_user(**kwargs): from src.models.auth import User user = MagicMock(spec=User) for k, v in kwargs.items(): setattr(user, k, v) return user # #endregion _make_user # #region _make_role [C:1] [TYPE Function] def _make_role(name, permissions=None): role = MagicMock() role.name = name role.permissions = permissions or [] return role # #endregion _make_role # #region _make_permission [C:1] [TYPE Function] def _make_permission(resource, action): perm = MagicMock() perm.resource = resource perm.action = action return perm # #endregion _make_permission class TestCollectUserPermissionPairs: """_collect_user_permission_pairs — extract (resource, ACTION) tuples.""" # #region test_no_roles_returns_empty [C:2] [TYPE Function] # @BRIEF User with empty roles list yields no permission pairs. def test_no_roles_returns_empty(self): from src.services.security_badge_service import SecurityBadgeService svc = SecurityBadgeService() user = _make_user(roles=[]) result = svc._collect_user_permission_pairs(user) assert result == set() # #endregion test_no_roles_returns_empty # #region test_role_with_permissions [C:2] [TYPE Function] # @BRIEF Role permissions are normalized to (resource, ACTION) tuples. def test_role_with_permissions(self): from src.services.security_badge_service import SecurityBadgeService svc = SecurityBadgeService() perms = [_make_permission("dashboard", "read"), _make_permission("dataset", "write")] role = _make_role("Editor", perms) user = _make_user(roles=[role]) result = svc._collect_user_permission_pairs(user) assert ("dashboard", "READ") in result assert ("dataset", "WRITE") in result # #endregion test_role_with_permissions # #region test_permission_without_resource_skipped [C:2] [TYPE Function] # @BRIEF Permissions with empty or None resource are excluded from collection. def test_permission_without_resource_skipped(self): from src.services.security_badge_service import SecurityBadgeService svc = SecurityBadgeService() perms = [_make_permission("", "read"), _make_permission(None, "write")] role = _make_role("Viewer", perms) user = _make_user(roles=[role]) result = svc._collect_user_permission_pairs(user) assert result == set() # #endregion test_permission_without_resource_skipped # #region test_multiple_roles_dedup [C:2] [TYPE Function] # @BRIEF Duplicate permissions across roles are deduplicated in result set. def test_multiple_roles_dedup(self): from src.services.security_badge_service import SecurityBadgeService svc = SecurityBadgeService() p1 = _make_permission("dashboard", "read") role1 = _make_role("Admin", [p1]) role2 = _make_role("Editor", [p1]) user = _make_user(roles=[role1, role2]) result = svc._collect_user_permission_pairs(user) assert len(result) == 1 # #endregion test_multiple_roles_dedup class TestBuildSecuritySummary: """build_security_summary — full security snapshot.""" # #region test_no_roles [C:2] [TYPE Function] # @BRIEF User with no roles produces empty roles list and None current_role. def test_no_roles(self): from src.services.security_badge_service import SecurityBadgeService svc = SecurityBadgeService() user = _make_user(roles=[], auth_source="database") summary = svc.build_security_summary(user) assert summary.read_only is True assert summary.roles == [] assert summary.current_role is None assert summary.auth_source == "database" # #endregion test_no_roles # #region test_single_role [C:2] [TYPE Function] # @BRIEF Single non-admin role becomes current_role and appears in sorted roles list. def test_single_role(self): from src.services.security_badge_service import SecurityBadgeService svc = SecurityBadgeService() role = _make_role("Viewer") user = _make_user(roles=[role], auth_source="ldap") summary = svc.build_security_summary(user) assert summary.roles == ["Viewer"] assert summary.current_role == "Viewer" # #endregion test_single_role # #region test_admin_role_detected [C:2] [TYPE Function] # @BRIEF Admin role is detected and set as current_role. def test_admin_role_detected(self): from src.services.security_badge_service import SecurityBadgeService svc = SecurityBadgeService() role = _make_role("Admin") user = _make_user(roles=[role], auth_source="database") summary = svc.build_security_summary(user) assert "Admin" in summary.roles assert summary.current_role == "Admin" # #endregion test_admin_role_detected # #region test_roles_sorted [C:2] [TYPE Function] # @BRIEF Roles in summary are deterministically sorted alphabetically. def test_roles_sorted(self): from src.services.security_badge_service import SecurityBadgeService svc = SecurityBadgeService() role_z = _make_role("ZViewer") role_a = _make_role("AAdmin") user = _make_user(roles=[role_z, role_a]) summary = svc.build_security_summary(user) assert summary.roles == ["AAdmin", "ZViewer"] # #endregion test_roles_sorted # #region test_discover_permissions_fallback [C:2] [TYPE Function] # @BRIEF When discover_declared_permissions raises, fallback to user permission pairs. def test_discover_permissions_fallback(self): from src.services.security_badge_service import SecurityBadgeService perms = [_make_permission("dashboard", "read")] role = _make_role("Editor", perms) user = _make_user(roles=[role]) with patch("src.services.security_badge_service.discover_declared_permissions", side_effect=RuntimeError("Plugin load failed")): svc = SecurityBadgeService(plugin_loader=MagicMock()) summary = svc.build_security_summary(user) assert len(summary.permissions) >= 0 # #endregion test_discover_permissions_fallback # #region test_is_admin_allows_all [C:2] [TYPE Function] # @BRIEF Admin user sees all declared permissions as allowed=True. def test_is_admin_allows_all(self): from src.services.security_badge_service import SecurityBadgeService perms = [_make_permission("dashboard", "read")] role = _make_role("Admin", perms) user = _make_user(roles=[role]) with patch("src.services.security_badge_service.discover_declared_permissions", return_value={("dashboard", "READ"), ("dataset", "WRITE")}): svc = SecurityBadgeService() summary = svc.build_security_summary(user) admin_perm = next((p for p in summary.permissions if p.key == "dashboard"), None) assert admin_perm is not None assert admin_perm.allowed is True # #endregion test_is_admin_allows_all # #region test_non_admin_permission_allowed_and_denied [C:2] [TYPE Function] # @BRIEF Non-admin user: owned permissions allowed=True, others allowed=False. def test_non_admin_permission_allowed_and_denied(self): from src.services.security_badge_service import SecurityBadgeService perms = [_make_permission("dashboard", "read")] role = _make_role("Editor", perms) user = _make_user(roles=[role], auth_source="LOCAL") with patch("src.services.security_badge_service.discover_declared_permissions", return_value={("dashboard", "READ"), ("dataset", "WRITE")}): svc = SecurityBadgeService() summary = svc.build_security_summary(user) perm_map = {p.key: p.allowed for p in summary.permissions} assert perm_map["dashboard"] is True assert perm_map["dataset:write"] is False # #endregion test_non_admin_permission_allowed_and_denied # #region test_role_source_equals_auth_source [C:2] [TYPE Function] # @BRIEF role_source field mirrors auth_source from user. def test_role_source_equals_auth_source(self): from src.services.security_badge_service import SecurityBadgeService svc = SecurityBadgeService() user = _make_user(roles=[], auth_source="ADFS") summary = svc.build_security_summary(user) assert summary.role_source == "ADFS" # #endregion test_role_source_equals_auth_source # #region test_auth_source_none [C:2] [TYPE Function] # @BRIEF User with None auth_source produces None in summary. def test_auth_source_none(self): from src.services.security_badge_service import SecurityBadgeService svc = SecurityBadgeService() user = _make_user(roles=[], auth_source=None) summary = svc.build_security_summary(user) assert summary.auth_source is None # #endregion test_auth_source_none # #region test_user_roles_none [C:2] [TYPE Function] # @BRIEF User with roles=None is treated as empty roles list. def test_user_roles_none(self): from src.services.security_badge_service import SecurityBadgeService svc = SecurityBadgeService() user = _make_user(roles=None, auth_source="LOCAL") summary = svc.build_security_summary(user) assert summary.roles == [] assert summary.current_role is None # #endregion test_user_roles_none # #region test_permission_key_format_read [C:2] [TYPE Function] # @BRIEF READ action produces resource-only key (no action suffix). def test_permission_key_format_read(self): from src.services.security_badge_service import SecurityBadgeService svc = SecurityBadgeService() key = svc._format_permission_key("dashboard", "READ") assert key == "dashboard" # #endregion test_permission_key_format_read # #region test_permission_key_format_write [C:2] [TYPE Function] # @BRIEF Non-READ action produces resource:action key format. def test_permission_key_format_write(self): from src.services.security_badge_service import SecurityBadgeService svc = SecurityBadgeService() key = svc._format_permission_key("dataset", "WRITE") assert key == "dataset:write" # #endregion test_permission_key_format_write class TestFormatPermissionKey: """_format_permission_key — compact UI key format.""" # #region test_read_omits_action [C:2] [TYPE Function] # @BRIEF READ action yields bare resource string without action suffix. def test_read_omits_action(self): from src.services.security_badge_service import SecurityBadgeService svc = SecurityBadgeService() assert svc._format_permission_key("dashboard", "READ") == "dashboard" # #endregion test_read_omits_action # #region test_write_includes_action [C:2] [TYPE Function] # @BRIEF Non-READ action yields resource:lowercase_action compound key. def test_write_includes_action(self): from src.services.security_badge_service import SecurityBadgeService svc = SecurityBadgeService() assert svc._format_permission_key("dataset", "WRITE") == "dataset:write" # #endregion test_write_includes_action # #region test_empty_resource [C:2] [TYPE Function] # @BRIEF Empty resource produces a valid string key without raising. def test_empty_resource(self): from src.services.security_badge_service import SecurityBadgeService svc = SecurityBadgeService() result = svc._format_permission_key("", "READ") assert result is not None assert isinstance(result, str) # #endregion test_empty_resource class TestPermissionEdgeCases: """Edge cases for permission processing.""" # #region test_empty_role_name_skipped [C:2] [TYPE Function] # @BRIEF Roles with empty or None names are excluded from summary roles list. def test_empty_role_name_skipped(self): from src.services.security_badge_service import SecurityBadgeService svc = SecurityBadgeService() role_empty = _make_role("") role_none = _make_role(None) user = _make_user(roles=[role_empty, role_none]) summary = svc.build_security_summary(user) assert summary.roles == [] # #endregion test_empty_role_name_skipped # #region test_role_name_with_whitespace_sanitized [C:2] [TYPE Function] # @BRIEF Role names with surrounding whitespace are trimmed in summary. def test_role_name_with_whitespace_sanitized(self): from src.services.security_badge_service import SecurityBadgeService svc = SecurityBadgeService() role = _make_role(" Editor ") user = _make_user(roles=[role]) summary = svc.build_security_summary(user) assert "Editor" in summary.roles # #endregion test_role_name_with_whitespace_sanitized # #region test_collect_with_none_permissions [C:2] [TYPE Function] # @BRIEF Role with permissions=None yields empty permission set without error. def test_collect_with_none_permissions(self): from src.services.security_badge_service import SecurityBadgeService svc = SecurityBadgeService() role = _make_role("Viewer", permissions=None) user = _make_user(roles=[role]) result = svc._collect_user_permission_pairs(user) assert result == set() # #endregion test_collect_with_none_permissions # #endregion Test.SecurityBadgeService