"""TDD tests for Spectral Sigil — content-addressed SVG avatar generation. Route: GET /avatars/{identity_id}.svg Generator: musehub.services.spectral_sigil Tests are RED-first. Each assertion drives one concrete implementation decision. """ from __future__ import annotations import hashlib import re import xml.etree.ElementTree as ET from unittest.mock import AsyncMock, MagicMock, patch import pytest from httpx import ASGITransport, AsyncClient from musehub.main import app # --------------------------------------------------------------------------- # Fixtures and helpers # --------------------------------------------------------------------------- # A valid sha256-style identity_id (no "sha256:" prefix — bare hex) _HUMAN_ID = "a" * 64 _AGENT_ID = "b" * 64 _ORG_ID = "c" * 64 # identity_id whose bytes[0:4] set hue to a known value for assertions _KNOWN_HUE_ID = "00000000" + "a" * 56 # bytes 0-3 = 0x00000000 → hue 0 (red) def _parse_svg(content: bytes) -> ET.Element: return ET.fromstring(content) def _make_identity_mock( identity_id: str, handle: str, identity_type: str, domain_counts: dict[str, int] | None = None, ) -> MagicMock: mock = MagicMock() mock.identity_id = identity_id mock.handle = handle mock.identity_type = identity_type mock.domain_counts = domain_counts or {} return mock # --------------------------------------------------------------------------- # Module import — must exist # --------------------------------------------------------------------------- def test_spectral_sigil_module_importable() -> None: from musehub.services import spectral_sigil # noqa: F401 def test_spectral_sigil_generator_callable() -> None: from musehub.services.spectral_sigil import generate_sigil assert callable(generate_sigil) # --------------------------------------------------------------------------- # Core generation contract # --------------------------------------------------------------------------- def test_generate_sigil_returns_bytes() -> None: from musehub.services.spectral_sigil import generate_sigil result = generate_sigil(_HUMAN_ID, "gabriel", "human", {}) assert isinstance(result, bytes) def test_generate_sigil_is_valid_svg() -> None: from musehub.services.spectral_sigil import generate_sigil svg_bytes = generate_sigil(_HUMAN_ID, "gabriel", "human", {}) root = _parse_svg(svg_bytes) # Root element must be assert root.tag in {"svg", "{http://www.w3.org/2000/svg}svg"} def test_generate_sigil_has_viewbox() -> None: from musehub.services.spectral_sigil import generate_sigil svg_bytes = generate_sigil(_HUMAN_ID, "gabriel", "human", {}) root = _parse_svg(svg_bytes) assert "viewBox" in root.attrib def test_generate_sigil_deterministic_same_id() -> None: from musehub.services.spectral_sigil import generate_sigil a = generate_sigil(_HUMAN_ID, "gabriel", "human", {}) b = generate_sigil(_HUMAN_ID, "gabriel", "human", {}) assert a == b def test_generate_sigil_deterministic_same_domains() -> None: from musehub.services.spectral_sigil import generate_sigil domains = {"code": 10, "music": 5} a = generate_sigil(_HUMAN_ID, "gabriel", "human", domains) b = generate_sigil(_HUMAN_ID, "gabriel", "human", domains) assert a == b def test_generate_sigil_differs_by_identity_id() -> None: from musehub.services.spectral_sigil import generate_sigil a = generate_sigil(_HUMAN_ID, "gabriel", "human", {}) b = generate_sigil(_AGENT_ID, "gabriel", "human", {}) assert a != b # --------------------------------------------------------------------------- # Archetype — distinct SVG structure # --------------------------------------------------------------------------- def test_human_sigil_contains_path_element() -> None: """Human archetype uses organic bezier blob → at least one .""" from musehub.services.spectral_sigil import generate_sigil svg_bytes = generate_sigil(_HUMAN_ID, "gabriel", "human", {}) root = _parse_svg(svg_bytes) # ET uses Clark notation {ns}tag for namespaced elements paths = ( root.findall(".//{http://www.w3.org/2000/svg}path") or root.findall(".//path") ) assert len(paths) >= 1, "human sigil must contain at least one (bezier blob)" def test_agent_sigil_contains_polygon_or_path() -> None: """Agent archetype uses hexagonal circuit trace → polygon or path.""" from musehub.services.spectral_sigil import generate_sigil svg_bytes = generate_sigil(_AGENT_ID, "aria", "agent", {}) root = _parse_svg(svg_bytes) polygons = root.findall(".//polygon") or root.findall( ".//{http://www.w3.org/2000/svg}polygon" ) paths = root.findall(".//path") or root.findall(".//{http://www.w3.org/2000/svg}path") assert len(polygons) + len(paths) >= 1, "agent sigil must contain polygon or path (hex)" def test_org_sigil_contains_polygon_or_path() -> None: """Org archetype uses quorum polygon → polygon or path.""" from musehub.services.spectral_sigil import generate_sigil svg_bytes = generate_sigil(_ORG_ID, "tellurstori", "org", {}) root = _parse_svg(svg_bytes) polygons = root.findall(".//polygon") or root.findall( ".//{http://www.w3.org/2000/svg}polygon" ) paths = root.findall(".//path") or root.findall(".//{http://www.w3.org/2000/svg}path") assert len(polygons) + len(paths) >= 1, "org sigil must contain polygon or path" def test_archetypes_produce_distinct_svgs() -> None: """Same identity_id bytes but different archetype → different output.""" from musehub.services.spectral_sigil import generate_sigil human_svg = generate_sigil(_HUMAN_ID, "gabriel", "human", {}) agent_svg = generate_sigil(_HUMAN_ID, "gabriel", "agent", {}) org_svg = generate_sigil(_HUMAN_ID, "gabriel", "org", {}) # All three must differ assert human_svg != agent_svg assert human_svg != org_svg assert agent_svg != org_svg # --------------------------------------------------------------------------- # Hue derivation from identity_id bytes # --------------------------------------------------------------------------- def test_hue_derives_from_identity_id_bytes() -> None: """bytes[0:4] of identity_id → hue in 0-360. Verify formula matches implementation.""" from musehub.services.spectral_sigil import derive_hue # 0x00000000 → hue 0 assert derive_hue("00000000" + "a" * 56) == 0 # 0xffffffff = 4294967295; 4294967295 % 360 = 255 assert derive_hue("ffffffff" + "a" * 56) == 4294967295 % 360 def test_hue_function_importable() -> None: from musehub.services.spectral_sigil import derive_hue # noqa: F401 assert callable(derive_hue) # --------------------------------------------------------------------------- # Orbital rings — derived from bytes 8-15 # --------------------------------------------------------------------------- def test_sigil_contains_ellipse_elements() -> None: """Orbital rings are rendered as or elements.""" from musehub.services.spectral_sigil import generate_sigil svg_bytes = generate_sigil(_HUMAN_ID, "gabriel", "human", {}) root = _parse_svg(svg_bytes) ellipses = root.findall(".//ellipse") or root.findall( ".//{http://www.w3.org/2000/svg}ellipse" ) circles = root.findall(".//circle") or root.findall( ".//{http://www.w3.org/2000/svg}circle" ) assert len(ellipses) + len(circles) >= 2, "at least 2 orbital rings expected" # --------------------------------------------------------------------------- # Domain dots — colored by domain activity # --------------------------------------------------------------------------- def test_domain_dots_absent_when_no_activity() -> None: """With no domain counts, domain dots should be grayscale / muted.""" from musehub.services.spectral_sigil import generate_sigil svg_bytes = generate_sigil(_HUMAN_ID, "gabriel", "human", {}) svg_text = svg_bytes.decode() # Domain-specific colors must NOT appear when there's no activity domain_colors = ["#388bfd", "#bc8cff", "#3fb950", "#f0883e", "#d29922"] for color in domain_colors: assert color.lower() not in svg_text.lower(), ( f"domain color {color} should not appear with no activity" ) def test_domain_dot_code_color_present_when_active() -> None: """With code activity, the code domain color #388bfd must appear in SVG.""" from musehub.services.spectral_sigil import generate_sigil svg_bytes = generate_sigil(_HUMAN_ID, "gabriel", "human", {"code": 42}) svg_text = svg_bytes.decode() assert "#388bfd" in svg_text.lower() or "388bfd" in svg_text.lower(), ( "code domain color #388bfd expected when code activity > 0" ) def test_domain_dot_music_color_present_when_active() -> None: from musehub.services.spectral_sigil import generate_sigil svg_bytes = generate_sigil(_HUMAN_ID, "gabriel", "human", {"music": 7}) svg_text = svg_bytes.decode() assert "#bc8cff" in svg_text.lower() or "bc8cff" in svg_text.lower() def test_domain_dot_midi_color_present_when_active() -> None: from musehub.services.spectral_sigil import generate_sigil svg_bytes = generate_sigil(_HUMAN_ID, "gabriel", "human", {"midi": 3}) svg_text = svg_bytes.decode() assert "#3fb950" in svg_text.lower() or "3fb950" in svg_text.lower() def test_domain_dot_mpay_color_present_when_active() -> None: from musehub.services.spectral_sigil import generate_sigil svg_bytes = generate_sigil(_HUMAN_ID, "gabriel", "human", {"mpay": 1}) svg_text = svg_bytes.decode() assert "#d29922" in svg_text.lower() or "d29922" in svg_text.lower() # --------------------------------------------------------------------------- # Handle initial — center text # --------------------------------------------------------------------------- def test_sigil_contains_text_with_first_char() -> None: """The SVG must contain a element with the first char of the handle.""" from musehub.services.spectral_sigil import generate_sigil svg_bytes = generate_sigil(_HUMAN_ID, "gabriel", "human", {}) root = _parse_svg(svg_bytes) texts = root.findall(".//text") or root.findall( ".//{http://www.w3.org/2000/svg}text" ) assert len(texts) >= 1, "sigil must contain at least one element" found = any("g" in (t.text or "").lower() for t in texts) assert found, "first char 'g' of handle 'gabriel' must appear in a element" def test_sigil_text_initial_matches_handle() -> None: from musehub.services.spectral_sigil import generate_sigil svg_bytes = generate_sigil(_AGENT_ID, "aria", "agent", {}) root = _parse_svg(svg_bytes) texts = root.findall(".//text") or root.findall( ".//{http://www.w3.org/2000/svg}text" ) found = any("a" in (t.text or "").lower() for t in texts) assert found, "first char 'a' of handle 'aria' must appear in a element" # --------------------------------------------------------------------------- # HTTP route — GET /avatars/{algo}/{hex}.svg # --------------------------------------------------------------------------- _SHA256_URL = f"/avatars/sha256/{_HUMAN_ID}.svg" @pytest.mark.asyncio async def test_avatar_route_returns_200_for_valid_id(db_session: AsyncMock) -> None: """GET /avatars/sha256/{hex}.svg → 200 image/svg+xml.""" mock_identity = _make_identity_mock(_HUMAN_ID, "gabriel", "human", {}) with patch( "musehub.api.routes.musehub.ui_avatars._fetch_identity", new=AsyncMock(return_value=mock_identity), ): async with AsyncClient( transport=ASGITransport(app=app), base_url="http://test" ) as client: response = await client.get(_SHA256_URL) assert response.status_code == 200 assert "image/svg+xml" in response.headers["content-type"] @pytest.mark.asyncio async def test_avatar_route_svg_body_is_valid(db_session: AsyncMock) -> None: mock_identity = _make_identity_mock(_HUMAN_ID, "gabriel", "human", {}) with patch( "musehub.api.routes.musehub.ui_avatars._fetch_identity", new=AsyncMock(return_value=mock_identity), ): async with AsyncClient( transport=ASGITransport(app=app), base_url="http://test" ) as client: response = await client.get(_SHA256_URL) root = _parse_svg(response.content) assert root.tag in {"svg", "{http://www.w3.org/2000/svg}svg"} @pytest.mark.asyncio async def test_avatar_route_has_immutable_cache_headers(db_session: AsyncMock) -> None: """Content-addressed: same id → same bytes forever → immutable cache.""" mock_identity = _make_identity_mock(_HUMAN_ID, "gabriel", "human", {}) with patch( "musehub.api.routes.musehub.ui_avatars._fetch_identity", new=AsyncMock(return_value=mock_identity), ): async with AsyncClient( transport=ASGITransport(app=app), base_url="http://test" ) as client: response = await client.get(_SHA256_URL) cache = response.headers.get("cache-control", "") assert "immutable" in cache or "max-age=31536000" in cache @pytest.mark.asyncio async def test_avatar_route_returns_404_for_unknown_id(db_session: AsyncMock) -> None: """Unknown identity_id → 404.""" with patch( "musehub.api.routes.musehub.ui_avatars._fetch_identity", new=AsyncMock(return_value=None), ): async with AsyncClient( transport=ASGITransport(app=app), base_url="http://test" ) as client: response = await client.get(_SHA256_URL) assert response.status_code == 404 @pytest.mark.asyncio async def test_avatar_route_returns_400_for_invalid_hex(db_session: AsyncMock) -> None: """Non-hex or wrong-length digest → 400.""" async with AsyncClient( transport=ASGITransport(app=app), base_url="http://test" ) as client: response = await client.get("/avatars/sha256/not-a-valid-hex.svg") assert response.status_code == 400 @pytest.mark.asyncio async def test_avatar_route_returns_400_for_unknown_algo(db_session: AsyncMock) -> None: """Unknown algo → 400.""" async with AsyncClient( transport=ASGITransport(app=app), base_url="http://test" ) as client: response = await client.get(f"/avatars/md5/{_HUMAN_ID}.svg") assert response.status_code == 400 @pytest.mark.asyncio async def test_avatar_route_deterministic_across_requests(db_session: AsyncMock) -> None: """Two identical requests must return identical bodies.""" mock_identity = _make_identity_mock(_HUMAN_ID, "gabriel", "human", {"code": 5}) with patch( "musehub.api.routes.musehub.ui_avatars._fetch_identity", new=AsyncMock(return_value=mock_identity), ): async with AsyncClient( transport=ASGITransport(app=app), base_url="http://test" ) as client: r1 = await client.get(_SHA256_URL) r2 = await client.get(_SHA256_URL) assert r1.content == r2.content