""" Tests for the friendly naming service. """ import re import pytest from pathlib import Path # Copy the pure functions from naming_service for testing # This avoids import issues with the app module CROCKFORD_ALPHABET = "0123456789abcdefghjkmnpqrstvwxyz" def normalize_name(name: str) -> str: """Copy of normalize_name for testing.""" name = name.lower() name = re.sub(r"[\s_]+", "-", name) name = re.sub(r"[^a-z0-9-]", "", name) name = re.sub(r"-+", "-", name) name = name.strip("-") return name or "unnamed" def parse_friendly_name(friendly_name: str): """Copy of parse_friendly_name for testing.""" parts = friendly_name.strip().split(" ", 1) base_name = parts[0] version_id = parts[1] if len(parts) > 1 else None return base_name, version_id def format_friendly_name(base_name: str, version_id: str) -> str: """Copy of format_friendly_name for testing.""" return f"{base_name} {version_id}" def format_l2_name(actor_id: str, base_name: str, version_id: str) -> str: """Copy of format_l2_name for testing.""" return f"{actor_id} {base_name} {version_id}" class TestNameNormalization: """Tests for name normalization.""" def test_normalize_simple_name(self) -> None: """Simple names should be lowercased.""" assert normalize_name("Brightness") == "brightness" def test_normalize_spaces_to_dashes(self) -> None: """Spaces should be converted to dashes.""" assert normalize_name("My Cool Effect") == "my-cool-effect" def test_normalize_underscores_to_dashes(self) -> None: """Underscores should be converted to dashes.""" assert normalize_name("brightness_v2") == "brightness-v2" def test_normalize_removes_special_chars(self) -> None: """Special characters should be removed.""" # Special chars are removed (not replaced with dashes) assert normalize_name("Test!!!Effect") == "testeffect" assert normalize_name("cool@effect#1") == "cooleffect1" # But spaces/underscores become dashes first assert normalize_name("Test Effect!") == "test-effect" def test_normalize_collapses_dashes(self) -> None: """Multiple dashes should be collapsed.""" assert normalize_name("test--effect") == "test-effect" assert normalize_name("test___effect") == "test-effect" def test_normalize_strips_edge_dashes(self) -> None: """Leading/trailing dashes should be stripped.""" assert normalize_name("-test-effect-") == "test-effect" def test_normalize_empty_returns_unnamed(self) -> None: """Empty names should return 'unnamed'.""" assert normalize_name("") == "unnamed" assert normalize_name("---") == "unnamed" assert normalize_name("!!!") == "unnamed" class TestFriendlyNameParsing: """Tests for friendly name parsing.""" def test_parse_base_name_only(self) -> None: """Parsing base name only returns None for version.""" base, version = parse_friendly_name("my-effect") assert base == "my-effect" assert version is None def test_parse_with_version(self) -> None: """Parsing with version returns both parts.""" base, version = parse_friendly_name("my-effect 01hw3x9k") assert base == "my-effect" assert version == "01hw3x9k" def test_parse_strips_whitespace(self) -> None: """Parsing should strip leading/trailing whitespace.""" base, version = parse_friendly_name(" my-effect ") assert base == "my-effect" assert version is None class TestFriendlyNameFormatting: """Tests for friendly name formatting.""" def test_format_friendly_name(self) -> None: """Format combines base and version with space.""" assert format_friendly_name("my-effect", "01hw3x9k") == "my-effect 01hw3x9k" def test_format_l2_name(self) -> None: """L2 format includes actor ID.""" result = format_l2_name("@alice@example.com", "my-effect", "01hw3x9k") assert result == "@alice@example.com my-effect 01hw3x9k" class TestDatabaseSchemaExists: """Tests that verify database schema includes friendly_names table.""" def test_schema_has_friendly_names_table(self) -> None: """Database schema should include friendly_names table.""" path = Path(__file__).parent.parent / "database.py" content = path.read_text() assert "CREATE TABLE IF NOT EXISTS friendly_names" in content def test_schema_has_required_columns(self) -> None: """Friendly names table should have required columns.""" path = Path(__file__).parent.parent / "database.py" content = path.read_text() assert "actor_id" in content assert "base_name" in content assert "version_id" in content assert "item_type" in content assert "display_name" in content def test_schema_has_unique_constraints(self) -> None: """Friendly names table should have unique constraints.""" path = Path(__file__).parent.parent / "database.py" content = path.read_text() # Unique on (actor_id, base_name, version_id) assert "UNIQUE(actor_id, base_name, version_id)" in content # Unique on (actor_id, cid) assert "UNIQUE(actor_id, cid)" in content class TestDatabaseFunctionsExist: """Tests that verify database functions exist.""" def test_create_friendly_name_exists(self) -> None: """create_friendly_name function should exist.""" path = Path(__file__).parent.parent / "database.py" content = path.read_text() assert "async def create_friendly_name(" in content def test_get_friendly_name_by_cid_exists(self) -> None: """get_friendly_name_by_cid function should exist.""" path = Path(__file__).parent.parent / "database.py" content = path.read_text() assert "async def get_friendly_name_by_cid(" in content def test_resolve_friendly_name_exists(self) -> None: """resolve_friendly_name function should exist.""" path = Path(__file__).parent.parent / "database.py" content = path.read_text() assert "async def resolve_friendly_name(" in content def test_list_friendly_names_exists(self) -> None: """list_friendly_names function should exist.""" path = Path(__file__).parent.parent / "database.py" content = path.read_text() assert "async def list_friendly_names(" in content def test_delete_friendly_name_exists(self) -> None: """delete_friendly_name function should exist.""" path = Path(__file__).parent.parent / "database.py" content = path.read_text() assert "async def delete_friendly_name(" in content class TestNamingServiceModuleExists: """Tests that verify naming service module structure.""" def test_module_file_exists(self) -> None: """Naming service module file should exist.""" path = Path(__file__).parent.parent / "app" / "services" / "naming_service.py" assert path.exists() def test_module_has_normalize_name(self) -> None: """Module should have normalize_name function.""" path = Path(__file__).parent.parent / "app" / "services" / "naming_service.py" content = path.read_text() assert "def normalize_name(" in content def test_module_has_generate_version_id(self) -> None: """Module should have generate_version_id function.""" path = Path(__file__).parent.parent / "app" / "services" / "naming_service.py" content = path.read_text() assert "def generate_version_id(" in content def test_module_has_naming_service_class(self) -> None: """Module should have NamingService class.""" path = Path(__file__).parent.parent / "app" / "services" / "naming_service.py" content = path.read_text() assert "class NamingService:" in content def test_naming_service_has_assign_name(self) -> None: """NamingService should have assign_name method.""" path = Path(__file__).parent.parent / "app" / "services" / "naming_service.py" content = path.read_text() assert "async def assign_name(" in content def test_naming_service_has_resolve(self) -> None: """NamingService should have resolve method.""" path = Path(__file__).parent.parent / "app" / "services" / "naming_service.py" content = path.read_text() assert "async def resolve(" in content def test_naming_service_has_get_by_cid(self) -> None: """NamingService should have get_by_cid method.""" path = Path(__file__).parent.parent / "app" / "services" / "naming_service.py" content = path.read_text() assert "async def get_by_cid(" in content class TestVersionIdProperties: """Tests for version ID format properties (using actual function).""" def test_version_id_format(self) -> None: """Version ID should use base32-crockford alphabet.""" # Read the naming service to verify it uses the right alphabet path = Path(__file__).parent.parent / "app" / "services" / "naming_service.py" content = path.read_text() assert 'CROCKFORD_ALPHABET = "0123456789abcdefghjkmnpqrstvwxyz"' in content def test_version_id_uses_hmac(self) -> None: """Version ID generation should use HMAC for server verification.""" path = Path(__file__).parent.parent / "app" / "services" / "naming_service.py" content = path.read_text() assert "hmac.new(" in content def test_version_id_uses_timestamp(self) -> None: """Version ID generation should be timestamp-based.""" path = Path(__file__).parent.parent / "app" / "services" / "naming_service.py" content = path.read_text() assert "time.time()" in content