# #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