Files
rose-ash/artdag/l1/tests/test_naming_service.py
giles 1a74d811f7
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 2m33s
Incorporate art-dag-mono repo into artdag/ subfolder
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: 1a179de547
git-subtree-split: 4c2e716558
2026-02-27 09:07:23 +00:00

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