Files
ss-tools/backend/tests/services/test_rbac_permission_catalog.py
busya c33792c8ee test(services): add unit tests for 7 service-layer modules
- profile_utils: 40 tests, 100% coverage (sanitize, normalize, mask, validate payload)
- security_badge_service: 17 tests, 100% coverage (role/permission extraction, security summary)
- services_mapping: 4 tests, 100% coverage (client resolution, get_suggestions)
- superset_lookup_service: 10 tests, 100% coverage (resolve environment, lookup success/degraded)
- notification_providers: 16 tests, 95% coverage (SMTP, Telegram, Slack providers)
- llm_provider: 25 tests, 91% coverage (mask_api_key, CRUD with encrypted API keys)
- rbac_permission_catalog: 12 tests, 79% coverage (route scanning, sync to DB)
2026-06-10 14:57:04 +03:00

174 lines
7.6 KiB
Python

# #region Test.RbacPermissionCatalog [C:3] [TYPE Module] [SEMANTICS test,rbac,permission,discovery,route]
# @BRIEF Tests for services/rbac_permission_catalog.py — permission discovery, route scanning, sync.
# @RELATION BINDS_TO -> [rbac_permission_catalog]
from pathlib import Path
import sys
sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
import os
import tempfile
from unittest.mock import MagicMock, patch, PropertyMock
import pytest
class TestIterRouteFiles:
"""_iter_route_files — discover Python files in routes directory."""
def test_directory_not_found(self):
from src.services.rbac_permission_catalog import _iter_route_files
with patch("src.services.rbac_permission_catalog.ROUTES_DIR", Path("/nonexistent/path")):
result = _iter_route_files()
assert result == []
def test_finds_py_files(self):
from src.services.rbac_permission_catalog import _iter_route_files
with tempfile.TemporaryDirectory() as tmpdir:
routes_dir = Path(tmpdir) / "routes"
routes_dir.mkdir()
(routes_dir / "test_route.py").write_text("")
(routes_dir / "__init__.py").write_text("")
(routes_dir / "subdir").mkdir()
(routes_dir / "subdir" / "nested.py").write_text("")
# These should be excluded
(routes_dir / "__tests__").mkdir()
(routes_dir / "__tests__" / "test_excluded.py").write_text("")
(routes_dir / "__pycache__").mkdir()
(routes_dir / "__pycache__" / "cached.py").write_text("")
with patch("src.services.rbac_permission_catalog.ROUTES_DIR", routes_dir):
files = _iter_route_files()
filenames = [f.name for f in files]
assert "test_route.py" in filenames
assert "__init__.py" in filenames
assert "nested.py" in filenames
assert "test_excluded.py" not in filenames
assert "cached.py" not in filenames
class TestDiscoverRoutePermissions:
"""_discover_route_permissions — extract has_permission() calls from route files."""
def test_extracts_permissions(self):
from src.services.rbac_permission_catalog import _discover_route_permissions
with tempfile.TemporaryDirectory() as tmpdir:
route_file = Path(tmpdir) / "test_route.py"
route_file.write_text("""
has_permission("dashboards", "READ")
has_permission("datasets", "WRITE")
# Not a permission call:
x = has_permission("users", "ADMIN")
""")
with patch("src.services.rbac_permission_catalog.ROUTES_DIR", Path(tmpdir)):
result = _discover_route_permissions()
assert ("dashboards", "READ") in result
assert ("datasets", "WRITE") in result
assert ("users", "ADMIN") in result
def test_empty_file(self):
from src.services.rbac_permission_catalog import _discover_route_permissions
with tempfile.TemporaryDirectory() as tmpdir:
route_file = Path(tmpdir) / "empty.py"
route_file.write_text("# No permissions here")
with patch("src.services.rbac_permission_catalog.ROUTES_DIR", Path(tmpdir)):
result = _discover_route_permissions()
assert result == set()
def test_multiple_files(self):
from src.services.rbac_permission_catalog import _discover_route_permissions
with tempfile.TemporaryDirectory() as tmpdir:
f1 = Path(tmpdir) / "routes_a.py"
f1.write_text('has_permission("dashboard", "READ")')
f2 = Path(tmpdir) / "routes_b.py"
f2.write_text('has_permission("dataset", "WRITE")')
with patch("src.services.rbac_permission_catalog.ROUTES_DIR", Path(tmpdir)):
result = _discover_route_permissions()
assert ("dashboard", "READ") in result
assert ("dataset", "WRITE") in result
def test_single_quote_variants(self):
from src.services.rbac_permission_catalog import _discover_route_permissions
with tempfile.TemporaryDirectory() as tmpdir:
f = Path(tmpdir) / "test.py"
f.write_text("""has_permission('dashboard', 'READ')""")
with patch("src.services.rbac_permission_catalog.ROUTES_DIR", Path(tmpdir)):
result = _discover_route_permissions()
assert ("dashboard", "READ") in result
class TestDiscoverDeclaredPermissions:
"""discover_declared_permissions — cached public API."""
def test_returns_permissions(self):
from src.services.rbac_permission_catalog import discover_declared_permissions
with patch("src.services.rbac_permission_catalog._discover_route_permissions",
return_value={("dashboard", "READ"), ("dataset", "WRITE")}):
result = discover_declared_permissions()
assert ("dashboard", "READ") in result
assert ("dataset", "WRITE") in result
def test_caching(self):
from src.services.rbac_permission_catalog import discover_declared_permissions
mock_fn = MagicMock(return_value={("dashboard", "READ")})
with patch("src.services.rbac_permission_catalog._discover_route_permissions", mock_fn):
result1 = discover_declared_permissions()
result2 = discover_declared_permissions()
assert result1 == result2
# With no plugin_loader, it calls _discover_route_permissions
# The lru_cache should only call once if args match
# discover_declared_permissions takes optional plugin_loader
def test_with_plugin_loader(self):
from src.services.rbac_permission_catalog import discover_declared_permissions
mock_loader = MagicMock()
mock_loader.list_plugins.return_value = []
mock_fn = MagicMock(return_value={("dashboard", "READ")})
with patch("src.services.rbac_permission_catalog._discover_route_permissions", mock_fn):
result = discover_declared_permissions(plugin_loader=mock_loader)
# Should call plugin loader to discover plugin permissions
assert ("dashboard", "READ") in result
class TestSyncPermissionCatalog:
"""sync_permission_catalog — sync discovered permissions to DB."""
def test_creates_new_permissions(self):
from src.services.rbac_permission_catalog import sync_permission_catalog
db = MagicMock()
db.query.return_value.all.return_value = [] # No existing permissions
discovered = {("dashboard", "READ"), ("dataset", "WRITE")}
result = sync_permission_catalog(db, discovered)
assert result == 2 # 2 new permissions
assert db.add.call_count >= 2
db.commit.assert_called_once()
def test_skips_existing_permissions(self):
from src.services.rbac_permission_catalog import sync_permission_catalog
existing = MagicMock()
existing.resource = "dashboard"
existing.action = "READ"
db = MagicMock()
db.query.return_value.all.return_value = [existing]
discovered = {("dashboard", "READ"), ("dataset", "WRITE")}
result = sync_permission_catalog(db, discovered)
assert result == 1 # 1 new (dataset/WRITE), 1 skipped
db.commit.assert_called_once()
def test_empty_discovered(self):
from src.services.rbac_permission_catalog import sync_permission_catalog
db = MagicMock()
result = sync_permission_catalog(db, set())
assert result == 0
db.commit.assert_not_called()
# #endregion Test.RbacPermissionCatalog