Files
ss-tools/backend/tests/services/test_profile_service.py
busya ce0369ae5c test(backend): add 55+ test files to push coverage to 98%
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)
2026-06-15 13:55:57 +03:00

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