Files
ss-tools/backend/tests/services/test_profile_utils.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

374 lines
14 KiB
Python

# #region Test.ProfileUtils [C:3] [TYPE Module] [SEMANTICS test,profile,utils,pure-functions]
# @BRIEF Tests for profile_utils.py — sanitize, normalize, mask, validate payload, build default preference, owner tokens.
# @RELATION BINDS_TO -> [ProfileUtils]
from pathlib import Path
import sys
sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
import pytest
class TestSanitizeText:
"""sanitize_text — normalize optional text into trimmed form or None."""
def test_none_returns_none(self):
from src.services.profile_utils import sanitize_text
assert sanitize_text(None) is None
def test_empty_returns_none(self):
from src.services.profile_utils import sanitize_text
assert sanitize_text("") is None
def test_whitespace_returns_none(self):
from src.services.profile_utils import sanitize_text
assert sanitize_text(" ") is None
def test_trimmed_text(self):
from src.services.profile_utils import sanitize_text
assert sanitize_text(" hello ") == "hello"
def test_already_trimmed(self):
from src.services.profile_utils import sanitize_text
assert sanitize_text("world") == "world"
class TestSanitizeSecret:
"""sanitize_secret — normalize secret value into trimmed form or None."""
def test_none_returns_none(self):
from src.services.profile_utils import sanitize_secret
assert sanitize_secret(None) is None
def test_empty_returns_none(self):
from src.services.profile_utils import sanitize_secret
assert sanitize_secret("") is None
def test_whitespace_returns_none(self):
from src.services.profile_utils import sanitize_secret
assert sanitize_secret(" ") is None
def test_trimmed_secret(self):
from src.services.profile_utils import sanitize_secret
assert sanitize_secret(" my-token-abc ") == "my-token-abc"
class TestSanitizeUsername:
"""sanitize_username — delegates to sanitize_text."""
def test_none_returns_none(self):
from src.services.profile_utils import sanitize_username
assert sanitize_username(None) is None
def test_valid_username(self):
from src.services.profile_utils import sanitize_username
assert sanitize_username(" admin ") == "admin"
class TestNormalizeUsername:
"""normalize_username — trim + lower."""
def test_none_returns_none(self):
from src.services.profile_utils import normalize_username
assert normalize_username(None) is None
def test_empty_returns_none(self):
from src.services.profile_utils import normalize_username
assert normalize_username("") is None
def test_trim_and_lower(self):
from src.services.profile_utils import normalize_username
assert normalize_username(" AdminUser ") == "adminuser"
def test_already_normalized(self):
from src.services.profile_utils import normalize_username
assert normalize_username("guest") == "guest"
class TestNormalizeStartPage:
"""normalize_start_page — map aliases to canonical values."""
def test_reports_logs_maps_to_reports(self):
from src.services.profile_utils import normalize_start_page
assert normalize_start_page("reports-logs") == "reports"
def test_dashboards_passes_through(self):
from src.services.profile_utils import normalize_start_page
assert normalize_start_page("dashboards") == "dashboards"
def test_datasets_passes_through(self):
from src.services.profile_utils import normalize_start_page
assert normalize_start_page("datasets") == "datasets"
def test_reports_passes_through(self):
from src.services.profile_utils import normalize_start_page
assert normalize_start_page("reports") == "reports"
def test_unknown_defaults_to_dashboards(self):
from src.services.profile_utils import normalize_start_page
assert normalize_start_page("settings") == "dashboards"
def test_none_defaults_to_dashboards(self):
from src.services.profile_utils import normalize_start_page
assert normalize_start_page(None) == "dashboards"
def test_case_insensitive(self):
from src.services.profile_utils import normalize_start_page
assert normalize_start_page("DATASETS") == "datasets"
class TestNormalizeDensity:
"""normalize_density — map aliases to canonical values."""
def test_free_maps_to_comfortable(self):
from src.services.profile_utils import normalize_density
assert normalize_density("free") == "comfortable"
def test_compact_passes_through(self):
from src.services.profile_utils import normalize_density
assert normalize_density("compact") == "compact"
def test_comfortable_passes_through(self):
from src.services.profile_utils import normalize_density
assert normalize_density("comfortable") == "comfortable"
def test_unknown_defaults_to_comfortable(self):
from src.services.profile_utils import normalize_density
assert normalize_density("spacious") == "comfortable"
def test_none_defaults_to_comfortable(self):
from src.services.profile_utils import normalize_density
assert normalize_density(None) == "comfortable"
class TestMaskSecretValue:
"""mask_secret_value — safe display value for secrets."""
def test_none_returns_none(self):
from src.services.profile_utils import mask_secret_value
assert mask_secret_value(None) is None
def test_empty_returns_none(self):
from src.services.profile_utils import mask_secret_value
assert mask_secret_value("") is None
def test_short_secret_returns_asterisks(self):
from src.services.profile_utils import mask_secret_value
assert mask_secret_value("ab") == "***"
def test_exactly_four_chars_returns_asterisks(self):
from src.services.profile_utils import mask_secret_value
assert mask_secret_value("abcd") == "***"
def test_longer_secret_masked(self):
from src.services.profile_utils import mask_secret_value
assert mask_secret_value("my-token-value") == "my***ue"
def test_whitespace_stripped_before_masking(self):
from src.services.profile_utils import mask_secret_value
assert mask_secret_value(" ab ") == "***"
class TestValidateUpdatePayload:
"""validate_update_payload — field-level validation for preference mutation."""
def test_valid_payload_returns_empty(self):
from src.services.profile_utils import validate_update_payload
errors = validate_update_payload(
superset_username="admin",
show_only_my_dashboards=False,
git_email="admin@example.com",
start_page="dashboards",
dashboards_table_density="comfortable",
)
assert errors == []
def test_username_with_spaces(self):
from src.services.profile_utils import validate_update_payload
errors = validate_update_payload(
superset_username="admin user",
show_only_my_dashboards=False,
git_email=None,
start_page="dashboards",
dashboards_table_density="comfortable",
)
assert any("space" in e.lower() for e in errors)
def test_show_only_requires_username_when_true(self):
from src.services.profile_utils import validate_update_payload
errors = validate_update_payload(
superset_username=None,
show_only_my_dashboards=True,
git_email=None,
start_page="dashboards",
dashboards_table_density="comfortable",
)
assert any("username" in e.lower() and "required" in e.lower() for e in errors)
def test_invalid_git_email(self):
from src.services.profile_utils import validate_update_payload
errors = validate_update_payload(
superset_username="admin",
show_only_my_dashboards=False,
git_email="not-an-email",
start_page="dashboards",
dashboards_table_density="comfortable",
)
assert any("email" in e.lower() for e in errors)
def test_git_email_with_spaces(self):
from src.services.profile_utils import validate_update_payload
errors = validate_update_payload(
superset_username="admin",
show_only_my_dashboards=False,
git_email="admin @x.com",
start_page="dashboards",
dashboards_table_density="comfortable",
)
assert any("email" in e.lower() for e in errors)
def test_git_email_starts_with_at(self):
from src.services.profile_utils import validate_update_payload
errors = validate_update_payload(
superset_username="admin",
show_only_my_dashboards=False,
git_email="@example.com",
start_page="dashboards",
dashboards_table_density="comfortable",
)
assert any("email" in e.lower() for e in errors)
def test_invalid_start_page(self):
from src.services.profile_utils import validate_update_payload
errors = validate_update_payload(
superset_username="admin",
show_only_my_dashboards=False,
git_email=None,
start_page="settings",
dashboards_table_density="comfortable",
)
assert any("start page" in e.lower() for e in errors)
def test_invalid_density(self):
from src.services.profile_utils import validate_update_payload
errors = validate_update_payload(
superset_username="admin",
show_only_my_dashboards=False,
git_email=None,
start_page="dashboards",
dashboards_table_density="spacious",
)
assert any("density" in e.lower() for e in errors)
def test_invalid_notification_email(self):
from src.services.profile_utils import validate_update_payload
errors = validate_update_payload(
superset_username="admin",
show_only_my_dashboards=False,
git_email=None,
start_page="dashboards",
dashboards_table_density="comfortable",
email_address="bad-email",
)
assert any("notification email" in e.lower() for e in errors)
def test_valid_notification_email(self):
from src.services.profile_utils import validate_update_payload
errors = validate_update_payload(
superset_username="admin",
show_only_my_dashboards=False,
git_email=None,
start_page="dashboards",
dashboards_table_density="comfortable",
email_address="user@example.com",
)
assert not any("notification email" in e.lower() for e in errors)
def test_multiple_errors(self):
from src.services.profile_utils import validate_update_payload
errors = validate_update_payload(
superset_username="admin user",
show_only_my_dashboards=True,
git_email="bad",
start_page="unknown",
dashboards_table_density="spacious",
)
assert len(errors) >= 4
class TestBuildDefaultPreference:
"""build_default_preference — default ProfilePreference for unconfigured users."""
def test_returns_profile_preference(self):
from src.services.profile_utils import build_default_preference
result = build_default_preference("user-1")
assert result.user_id == "user-1"
assert result.superset_username is None
assert result.show_only_my_dashboards is False
assert result.show_only_slug_dashboards is True
assert result.start_page == "dashboards"
assert result.dashboards_table_density == "comfortable"
assert result.has_git_personal_access_token is False
assert result.notify_on_fail is True
class TestNormalizeOwnerTokens:
"""normalize_owner_tokens — deduplicated lower-cased tokens from owners."""
def test_none_returns_empty(self):
from src.services.profile_utils import normalize_owner_tokens
assert normalize_owner_tokens(None) == []
def test_empty_list(self):
from src.services.profile_utils import normalize_owner_tokens
assert normalize_owner_tokens([]) == []
def test_dict_owner_with_username(self):
from src.services.profile_utils import normalize_owner_tokens
owners = [{"username": "AdminUser"}]
tokens = normalize_owner_tokens(owners)
assert "adminuser" in tokens
def test_dict_owner_with_full_name(self):
from src.services.profile_utils import normalize_owner_tokens
owners = [{"first_name": "John", "last_name": "Doe"}]
tokens = normalize_owner_tokens(owners)
assert "john" in tokens
assert "doe" in tokens
assert "john_doe" or "john doe" in tokens
def test_string_owner(self):
from src.services.profile_utils import normalize_owner_tokens
owners = [" Admin "]
tokens = normalize_owner_tokens(owners)
assert "admin" in tokens
def test_dedup(self):
from src.services.profile_utils import normalize_owner_tokens
owners = ["admin", " Admin "]
tokens = normalize_owner_tokens(owners)
assert tokens == ["admin"]
def test_skip_empty(self):
from src.services.profile_utils import normalize_owner_tokens
owners = ["", " ", None]
tokens = normalize_owner_tokens(owners)
assert tokens == []
def test_dict_owner_with_email(self):
from src.services.profile_utils import normalize_owner_tokens
owners = [{"email": "User@Example.com"}]
tokens = normalize_owner_tokens(owners)
assert "user@example.com" in tokens
def test_dict_owner_multiple_candidates(self):
from src.services.profile_utils import normalize_owner_tokens
owners = [{"username": "jdoe", "first_name": "John", "last_name": "Doe", "email": "john@example.com"}]
tokens = normalize_owner_tokens(owners)
assert "jdoe" in tokens
assert "john" in tokens
assert "doe" in tokens
assert "john@example.com" in tokens
# #endregion Test.ProfileUtils