- 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)
374 lines
14 KiB
Python
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
|