All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 2m33s
Merges full history from art-dag/mono.git into the monorepo under the artdag/ directory. Contains: core (DAG engine), l1 (Celery rendering server), l2 (ActivityPub registry), common (shared templates/middleware), client (CLI), test (e2e). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> git-subtree-dir: artdag git-subtree-mainline:1a179de547git-subtree-split:4c2e716558
247 lines
9.7 KiB
Python
247 lines
9.7 KiB
Python
"""
|
|
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
|