Subagents delivered tests across all uncovered backend modules: Schemas (100%): agent, auth, health, profile, settings, validation Services (98-100%): auth, profile, health, llm, mapping, resource, security, git, superset_lookup, sql_table_extractor, rbac API routes (new): auth, admin, health, environments, plugins, dashboards (helpers, projection, actions, listing), git (config, deps, env, helpers) Clean Release (100%): DTO, facade, policy_engine, stages, repos, preparation, source_isolation, compliance Git services: base, remote_providers Agent module: app, run, middleware, langgraph_setup Core: trace, cleanup, ws_log_handler, timezone, auth (config/oauth/security), matching Reports: normalizer, report_service, type_profiles Notifications: service, providers Also: - .gitignore: add .coverage, *.cover, coverage-* dirs - src/schemas/auth.py: fix AD group DN regex (comma in CN=...) - Remove co-located src/services/__tests__/ (caused pytest module collision)
156 lines
6.4 KiB
Python
156 lines
6.4 KiB
Python
# #region Test.ProfileService [C:3] [TYPE Module] [SEMANTICS test,profile,facade,delegate]
|
|
# @BRIEF Tests for services/profile_service.py — ProfileService facade that delegates to
|
|
# ProfilePreferenceService, SupersetLookupService, and SecurityBadgeService.
|
|
# @RELATION BINDS_TO -> [ProfileService]
|
|
# @TEST_EDGE: missing_field -> empty/none username returns False from matches_dashboard_actor
|
|
# @TEST_EDGE: invalid_type -> mixed owners payload handled correctly
|
|
# @TEST_EDGE: delegation -> facade methods call sub-services and return their results
|
|
|
|
from pathlib import Path
|
|
import sys
|
|
sys.path.insert(0, str(Path(__file__).parent.parent.parent / "src"))
|
|
|
|
from unittest.mock import AsyncMock, MagicMock, patch
|
|
import pytest
|
|
|
|
from src.schemas.profile import ProfilePreferenceUpdateRequest, SupersetAccountLookupRequest
|
|
|
|
|
|
class _MockUser:
|
|
def __init__(self, **kwargs):
|
|
for k, v in kwargs.items():
|
|
setattr(self, k, v)
|
|
|
|
|
|
class TestProfileServiceInit:
|
|
"""ProfileService __init__ — creates sub-services."""
|
|
|
|
@patch("src.services.profile_service.ProfilePreferenceService")
|
|
@patch("src.services.profile_service.SupersetLookupService")
|
|
@patch("src.services.profile_service.SecurityBadgeService")
|
|
def test_init_creates_sub_services(self, MockBadge, MockLookup, MockPref):
|
|
from src.services.profile_service import ProfileService
|
|
|
|
db = MagicMock()
|
|
config = MagicMock()
|
|
loader = MagicMock()
|
|
svc = ProfileService(db, config, loader)
|
|
|
|
MockPref.assert_called_once_with(db, config, loader)
|
|
MockLookup.assert_called_once_with(config)
|
|
MockBadge.assert_called_once_with(loader)
|
|
assert svc.db is db
|
|
assert svc.config_manager is config
|
|
assert svc.plugin_loader is loader
|
|
|
|
@patch("src.services.profile_service.ProfilePreferenceService")
|
|
@patch("src.services.profile_service.SupersetLookupService")
|
|
@patch("src.services.profile_service.SecurityBadgeService")
|
|
def test_init_without_plugin_loader(self, MockBadge, MockLookup, MockPref):
|
|
from src.services.profile_service import ProfileService
|
|
|
|
db = MagicMock()
|
|
config = MagicMock()
|
|
svc = ProfileService(db, config)
|
|
|
|
MockBadge.assert_called_once_with(None)
|
|
|
|
|
|
class TestProfileServiceDelegation:
|
|
"""ProfileService delegates to sub-services."""
|
|
|
|
@pytest.fixture
|
|
def service(self):
|
|
from src.services.profile_service import ProfileService
|
|
svc = ProfileService.__new__(ProfileService)
|
|
svc.preference_service = MagicMock()
|
|
svc.lookup_service = AsyncMock()
|
|
svc.security_badge_service = MagicMock()
|
|
svc.db = MagicMock()
|
|
svc.config_manager = MagicMock()
|
|
svc.plugin_loader = MagicMock()
|
|
return svc
|
|
|
|
def test_get_my_preference_delegates(self, service):
|
|
user = _MockUser(id="u-1")
|
|
expected = MagicMock()
|
|
service.preference_service.get_my_preference.return_value = expected
|
|
|
|
result = service.get_my_preference(user)
|
|
assert result is expected
|
|
service.preference_service.get_my_preference.assert_called_once_with(user)
|
|
|
|
def test_get_dashboard_filter_binding_delegates(self, service):
|
|
user = _MockUser(id="u-1")
|
|
expected = {"show_only_my_dashboards": True}
|
|
service.preference_service.get_dashboard_filter_binding.return_value = expected
|
|
|
|
result = service.get_dashboard_filter_binding(user)
|
|
assert result == expected
|
|
service.preference_service.get_dashboard_filter_binding.assert_called_once_with(user)
|
|
|
|
def test_update_my_preference_delegates(self, service):
|
|
user = _MockUser(id="u-1")
|
|
payload = ProfilePreferenceUpdateRequest(superset_username="jdoe")
|
|
expected = MagicMock()
|
|
service.preference_service.update_my_preference.return_value = expected
|
|
|
|
result = service.update_my_preference(user, payload)
|
|
assert result is expected
|
|
service.preference_service.update_my_preference.assert_called_once_with(user, payload, None)
|
|
|
|
def test_update_my_preference_with_target_user(self, service):
|
|
user = _MockUser(id="u-1")
|
|
payload = ProfilePreferenceUpdateRequest(superset_username="jdoe")
|
|
service.update_my_preference(user, payload, target_user_id="u-2")
|
|
service.preference_service.update_my_preference.assert_called_once_with(user, payload, "u-2")
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_lookup_superset_accounts_delegates(self, service):
|
|
user = _MockUser(id="u-1")
|
|
request = SupersetAccountLookupRequest(environment_id="env-1")
|
|
expected = MagicMock()
|
|
service.lookup_service.lookup_superset_accounts.return_value = expected
|
|
|
|
result = await service.lookup_superset_accounts(user, request)
|
|
assert result is expected
|
|
service.lookup_service.lookup_superset_accounts.assert_called_once_with(user, request)
|
|
|
|
|
|
class TestMatchesDashboardActor:
|
|
"""ProfileService.matches_dashboard_actor — normalize + match logic."""
|
|
|
|
@pytest.fixture
|
|
def service(self):
|
|
from src.services.profile_service import ProfileService
|
|
return ProfileService.__new__(ProfileService)
|
|
|
|
def test_none_username_returns_false(self, service):
|
|
assert service.matches_dashboard_actor(None, [], None) is False
|
|
|
|
def test_empty_username_returns_false(self, service):
|
|
assert service.matches_dashboard_actor("", [], None) is False
|
|
|
|
def test_matches_owner_username(self, service):
|
|
owners = [{"username": "AdminUser"}]
|
|
assert service.matches_dashboard_actor("adminuser", owners, None) is True
|
|
|
|
def test_matches_modified_by(self, service):
|
|
assert service.matches_dashboard_actor("admin", None, "Admin") is True
|
|
|
|
def test_no_match_returns_false(self, service):
|
|
owners = [{"username": "OtherUser"}]
|
|
assert service.matches_dashboard_actor("admin", owners, "Other") is False
|
|
|
|
def test_matches_with_case_insensitive(self, service):
|
|
owners = [{"username": "John.Doe"}]
|
|
assert service.matches_dashboard_actor("john.doe", owners, None) is True
|
|
|
|
def test_owner_tokens_with_full_name(self, service):
|
|
owners = [{"first_name": "John", "last_name": "Doe"}]
|
|
assert service.matches_dashboard_actor("john_doe", owners, None) is True
|
|
|
|
def test_empty_owners_list(self, service):
|
|
assert service.matches_dashboard_actor("admin", [], None) is False
|
|
# #endregion Test.ProfileService
|