"""Section 25 — Domains: 7-layer test suite. Covers musehub/services/musehub_domains.py and musehub/api/routes/musehub/domains.py. Layer map --------- 1. Unit — compute_manifest_hash, _to_response, dataclasses 2. Integration — service functions against real PostgreSQL DB 3. E2E — HTTP client against full app 4. Stress — many domains, concurrent queries 5. Data Integrity — sort order, deprecated exclusion, hash correctness 6. Security — auth enforcement, duplicate scoped_id 7. Performance — timing budgets """ from __future__ import annotations import asyncio import hashlib import json import time import uuid import pytest from httpx import AsyncClient from sqlalchemy.ext.asyncio import AsyncSession from musehub.db.musehub_domain_models import MusehubDomain, MusehubDomainInstall from musehub.db.musehub_models import MusehubRepo from musehub.muse_contracts.json_types import JSONObject, StrDict from musehub.services.musehub_domains import ( DomainListResponse, DomainReposResponse, DomainResponse, _to_response, compute_manifest_hash, create_domain, get_domain_by_id, get_domain_by_scoped_id, list_domains, list_repos_for_domain, record_domain_install, ) # --------------------------------------------------------------------------- # DB helpers # --------------------------------------------------------------------------- def _uid() -> str: return str(uuid.uuid4()) async def _db_domain( session: AsyncSession, *, author_slug: str = "alice", slug: str | None = None, display_name: str = "Test Domain", description: str = "A test domain", capabilities: JSONObject | None = None, viewer_type: str = "generic", version: str = "1.0.0", install_count: int = 0, is_verified: bool = False, is_deprecated: bool = False, ) -> MusehubDomain: from datetime import datetime, timezone slug = slug or f"domain-{_uid()[:8]}" caps = capabilities or {"dimensions": [], "merge_semantics": "three_way"} manifest_hash = compute_manifest_hash(caps) domain = MusehubDomain( domain_id=_uid(), author_user_id=author_slug, author_slug=author_slug, slug=slug, display_name=display_name, description=description, version=version, manifest_hash=manifest_hash, capabilities=caps, viewer_type=viewer_type, install_count=install_count, is_verified=is_verified, is_deprecated=is_deprecated, created_at=datetime.now(timezone.utc), updated_at=datetime.now(timezone.utc), ) session.add(domain) await session.flush() return domain async def _db_repo( session: AsyncSession, owner: str = "alice", *, domain_id: str | None = None, visibility: str = "public", deleted: bool = False, ) -> MusehubRepo: from datetime import datetime, timezone slug = f"repo-{_uid()[:8]}" repo = MusehubRepo( repo_id=_uid(), name=slug, slug=slug, owner=owner, owner_user_id=owner, visibility=visibility, domain_id=domain_id, deleted_at=datetime.now(timezone.utc) if deleted else None, ) session.add(repo) await session.flush() return repo # =========================================================================== # Layer 1 — Unit # =========================================================================== class TestUnitComputeManifestHash: def test_returns_hex_string(self) -> None: h = compute_manifest_hash({"dimensions": []}) assert isinstance(h, str) assert len(h) == 64 # SHA-256 hex def test_deterministic(self) -> None: caps = {"dimensions": [{"name": "tempo"}], "merge_semantics": "ot"} assert compute_manifest_hash(caps) == compute_manifest_hash(caps) def test_sorted_keys_order_independent(self) -> None: caps_a = {"b": 1, "a": 2} caps_b = {"a": 2, "b": 1} assert compute_manifest_hash(caps_a) == compute_manifest_hash(caps_b) def test_different_capabilities_different_hash(self) -> None: h1 = compute_manifest_hash({"dimensions": []}) h2 = compute_manifest_hash({"dimensions": [{"name": "tempo"}]}) assert h1 != h2 def test_matches_manual_sha256(self) -> None: caps = {"x": 1} blob = json.dumps(caps, sort_keys=True, separators=(",", ":")).encode() expected = hashlib.sha256(blob).hexdigest() assert compute_manifest_hash(caps) == expected class TestUnitToResponse: """Unit tests for _to_response using an integration DB fixture to create real ORM rows.""" @pytest.mark.anyio async def test_scoped_id_format(self, db_session: AsyncSession) -> None: d = await _db_domain(db_session, author_slug="gabriel", slug="midi") resp = _to_response(d) assert resp.scoped_id == "@gabriel/midi" @pytest.mark.anyio async def test_install_count_preserved(self, db_session: AsyncSession) -> None: d = await _db_domain(db_session, slug="midi-count", install_count=5) resp = _to_response(d) assert resp.install_count == 5 @pytest.mark.anyio async def test_is_verified_preserved(self, db_session: AsyncSession) -> None: d = await _db_domain(db_session, slug="midi-verified", is_verified=True) resp = _to_response(d) assert resp.is_verified is True @pytest.mark.anyio async def test_capabilities_copied(self, db_session: AsyncSession) -> None: caps = {"dimensions": [{"name": "tempo"}], "merge_semantics": "ot"} d = await _db_domain(db_session, slug="midi-caps", capabilities=caps) resp = _to_response(d) assert resp.capabilities == caps @pytest.mark.anyio async def test_none_capabilities_become_empty_dict( self, db_session: AsyncSession ) -> None: d = await _db_domain(db_session, slug="midi-no-caps") d.capabilities = None resp = _to_response(d) assert resp.capabilities == {} class TestUnitDataclasses: def test_domain_response_fields(self) -> None: from datetime import datetime, timezone dr = DomainResponse( domain_id=_uid(), author_slug="alice", slug="midi", scoped_id="@alice/midi", display_name="MIDI", description="", version="1.0.0", manifest_hash="abc", capabilities={}, viewer_type="generic", install_count=0, is_verified=False, is_deprecated=False, created_at=datetime.now(timezone.utc), updated_at=datetime.now(timezone.utc), ) assert dr.scoped_id == "@alice/midi" def test_domain_list_response_fields(self) -> None: dlr = DomainListResponse(domains=[], total=0) assert dlr.total == 0 def test_domain_repos_response_fields(self) -> None: drr = DomainReposResponse( domain_id=_uid(), scoped_id="@a/b", repos=[], total=0 ) assert drr.repos == [] # =========================================================================== # Layer 2 — Integration # =========================================================================== class TestIntegrationListDomains: @pytest.mark.anyio async def test_returns_all_non_deprecated(self, db_session: AsyncSession) -> None: await _db_domain(db_session, slug="midi", display_name="MIDI") await _db_domain(db_session, slug="code", display_name="Code") await _db_domain(db_session, slug="old", is_deprecated=True) await db_session.flush() result = await list_domains(db_session) slugs = [d.slug for d in result.domains] assert "midi" in slugs assert "code" in slugs assert "old" not in slugs @pytest.mark.anyio async def test_query_filters_by_display_name(self, db_session: AsyncSession) -> None: await _db_domain(db_session, slug="piano", display_name="Piano Roll Domain") await _db_domain(db_session, slug="genome", display_name="Genomics Domain") await db_session.flush() result = await list_domains(db_session, query="Piano") assert len(result.domains) == 1 assert result.domains[0].slug == "piano" @pytest.mark.anyio async def test_verified_only_filter(self, db_session: AsyncSession) -> None: await _db_domain(db_session, slug="v", is_verified=True) await _db_domain(db_session, slug="u", is_verified=False) await db_session.flush() result = await list_domains(db_session, verified_only=True) slugs = [d.slug for d in result.domains] assert "v" in slugs assert "u" not in slugs @pytest.mark.anyio async def test_pagination_page_size(self, db_session: AsyncSession) -> None: for i in range(5): await _db_domain(db_session, slug=f"d{i}") await db_session.flush() result = await list_domains(db_session, page=1, page_size=2) assert len(result.domains) == 2 assert result.total == 5 class TestIntegrationGetDomain: @pytest.mark.anyio async def test_get_by_scoped_id_found(self, db_session: AsyncSession) -> None: await _db_domain(db_session, author_slug="alice", slug="midi") await db_session.flush() result = await get_domain_by_scoped_id(db_session, "alice", "midi") assert result is not None assert result.scoped_id == "@alice/midi" @pytest.mark.anyio async def test_get_by_scoped_id_not_found(self, db_session: AsyncSession) -> None: result = await get_domain_by_scoped_id(db_session, "nobody", "missing") assert result is None @pytest.mark.anyio async def test_get_by_id_found(self, db_session: AsyncSession) -> None: d = await _db_domain(db_session, slug="code") await db_session.flush() result = await get_domain_by_id(db_session, d.domain_id) assert result is not None assert result.domain_id == d.domain_id @pytest.mark.anyio async def test_get_by_id_not_found(self, db_session: AsyncSession) -> None: result = await get_domain_by_id(db_session, "nonexistent-id") assert result is None class TestIntegrationListReposForDomain: @pytest.mark.anyio async def test_returns_public_repos(self, db_session: AsyncSession) -> None: d = await _db_domain(db_session, slug="midi") await _db_repo(db_session, domain_id=d.domain_id, visibility="public") await _db_repo(db_session, domain_id=d.domain_id, visibility="public") await db_session.flush() result = await list_repos_for_domain(db_session, d.domain_id) assert result.total == 2 assert len(result.repos) == 2 @pytest.mark.anyio async def test_private_repos_excluded(self, db_session: AsyncSession) -> None: d = await _db_domain(db_session, slug="midi") await _db_repo(db_session, domain_id=d.domain_id, visibility="public") await _db_repo(db_session, domain_id=d.domain_id, visibility="private") await db_session.flush() result = await list_repos_for_domain(db_session, d.domain_id) assert result.total == 1 @pytest.mark.anyio async def test_deleted_repos_excluded(self, db_session: AsyncSession) -> None: d = await _db_domain(db_session, slug="midi") await _db_repo(db_session, domain_id=d.domain_id, visibility="public") await _db_repo(db_session, domain_id=d.domain_id, visibility="public", deleted=True) await db_session.flush() result = await list_repos_for_domain(db_session, d.domain_id) assert result.total == 1 @pytest.mark.anyio async def test_nonexistent_domain_returns_empty(self, db_session: AsyncSession) -> None: result = await list_repos_for_domain(db_session, "nonexistent-id") assert result.total == 0 assert result.repos == [] class TestIntegrationCreateDomain: @pytest.mark.anyio async def test_creates_domain_with_hash(self, db_session: AsyncSession) -> None: caps = {"dimensions": [{"name": "tempo"}], "merge_semantics": "ot"} result = await create_domain( db_session, author_user_id="alice", author_slug="alice", slug="midi", display_name="MIDI", description="MIDI domain", capabilities=caps, ) assert result.domain_id != "" assert result.manifest_hash == compute_manifest_hash(caps) @pytest.mark.anyio async def test_scoped_id_format(self, db_session: AsyncSession) -> None: result = await create_domain( db_session, author_user_id="bob", author_slug="bob", slug="code", display_name="Code", description="", capabilities={}, ) assert result.scoped_id == "@bob/code" @pytest.mark.anyio async def test_not_verified_by_default(self, db_session: AsyncSession) -> None: result = await create_domain( db_session, author_user_id="alice", author_slug="alice", slug="genome", display_name="Genome", description="", capabilities={}, ) assert result.is_verified is False assert result.is_deprecated is False class TestIntegrationRecordDomainInstall: @pytest.mark.anyio async def test_increments_install_count(self, db_session: AsyncSession) -> None: d = await _db_domain(db_session, slug="midi", install_count=0) await db_session.flush() await record_domain_install(db_session, "user1", d.domain_id) await db_session.flush() # Verify via get_domain domain = await get_domain_by_id(db_session, d.domain_id) assert domain is not None assert domain.install_count == 1 @pytest.mark.anyio async def test_idempotent_same_user(self, db_session: AsyncSession) -> None: d = await _db_domain(db_session, slug="midi", install_count=0) await db_session.flush() await record_domain_install(db_session, "user1", d.domain_id) await record_domain_install(db_session, "user1", d.domain_id) # duplicate await db_session.flush() domain = await get_domain_by_id(db_session, d.domain_id) assert domain is not None assert domain.install_count == 1 # not 2 @pytest.mark.anyio async def test_different_users_each_increment(self, db_session: AsyncSession) -> None: d = await _db_domain(db_session, slug="midi", install_count=0) await db_session.flush() await record_domain_install(db_session, "user1", d.domain_id) await record_domain_install(db_session, "user2", d.domain_id) await db_session.flush() domain = await get_domain_by_id(db_session, d.domain_id) assert domain is not None assert domain.install_count == 2 # =========================================================================== # Layer 3 — E2E # =========================================================================== class TestE2EListDomains: @pytest.mark.anyio async def test_list_returns_200( self, client: AsyncClient, db_session: AsyncSession, ) -> None: await _db_domain(db_session, slug="midi-api") await db_session.commit() r = await client.get("/api/domains") assert r.status_code == 200 body = r.json() assert "domains" in body assert "total" in body assert isinstance(body["domains"], list) @pytest.mark.anyio async def test_list_no_auth_required( self, client: AsyncClient, db_session: AsyncSession, ) -> None: await db_session.commit() r = await client.get("/api/domains") assert r.status_code == 200 @pytest.mark.anyio async def test_list_query_param_filters( self, client: AsyncClient, db_session: AsyncSession, ) -> None: await _db_domain(db_session, slug="piano-e2e", display_name="Piano Roll E2E") await _db_domain(db_session, slug="genome-e2e", display_name="Genome E2E") await db_session.commit() r = await client.get("/api/domains?q=Piano+Roll") assert r.status_code == 200 body = r.json() slugs = [d["slug"] for d in body["domains"]] assert "piano-e2e" in slugs assert "genome-e2e" not in slugs @pytest.mark.anyio async def test_list_page_size_param( self, client: AsyncClient, db_session: AsyncSession, ) -> None: for i in range(5): await _db_domain(db_session, slug=f"e2e-page-{i}") await db_session.commit() r = await client.get("/api/domains?page_size=2") assert r.status_code == 200 assert len(r.json()["domains"]) <= 2 class TestE2ERegisterDomain: @pytest.mark.anyio async def test_register_201( self, client: AsyncClient, auth_headers: StrDict, db_session: AsyncSession, ) -> None: await db_session.commit() body = { "author_slug": "testuser", "slug": "my-domain", "display_name": "My Domain", "description": "A domain for testing", "capabilities": {"dimensions": [], "merge_semantics": "three_way"}, "viewer_type": "generic", "version": "1.0.0", } r = await client.post("/api/domains", json=body, headers=auth_headers) assert r.status_code == 201 resp = r.json() assert "domain_id" in resp assert "scoped_id" in resp assert "manifest_hash" in resp @pytest.mark.anyio async def test_register_requires_auth( self, client: AsyncClient, db_session: AsyncSession, ) -> None: await db_session.commit() body = { "author_slug": "testuser", "slug": "unauthed", "display_name": "Unauthed", "description": "", "capabilities": {}, } r = await client.post("/api/domains", json=body) assert r.status_code == 401 @pytest.mark.anyio async def test_register_duplicate_409( self, client: AsyncClient, auth_headers: StrDict, db_session: AsyncSession, ) -> None: await db_session.commit() body = { "author_slug": "testuser", "slug": "dup-domain", "display_name": "Dup", "description": "", "capabilities": {}, } r1 = await client.post("/api/domains", json=body, headers=auth_headers) assert r1.status_code == 201 r2 = await client.post("/api/domains", json=body, headers=auth_headers) assert r2.status_code == 409 class TestE2EGetDomain: @pytest.mark.anyio async def test_get_domain_200( self, client: AsyncClient, db_session: AsyncSession, ) -> None: await _db_domain(db_session, author_slug="alice", slug="midi-detail") await db_session.commit() r = await client.get("/api/domains/@alice/midi-detail") assert r.status_code == 200 body = r.json() assert body["scoped_id"] == "@alice/midi-detail" assert "capabilities" in body assert "manifest_hash" in body @pytest.mark.anyio async def test_get_domain_404( self, client: AsyncClient, ) -> None: r = await client.get("/api/domains/@nobody/nonexistent") assert r.status_code == 404 @pytest.mark.anyio async def test_get_domain_repos_200( self, client: AsyncClient, db_session: AsyncSession, ) -> None: d = await _db_domain(db_session, author_slug="alice", slug="midi-repos") await _db_repo(db_session, domain_id=d.domain_id, visibility="public") await db_session.commit() r = await client.get("/api/domains/@alice/midi-repos/repos") assert r.status_code == 200 body = r.json() assert body["total"] == 1 assert len(body["repos"]) == 1 @pytest.mark.anyio async def test_get_domain_repos_404_unknown_domain( self, client: AsyncClient, ) -> None: r = await client.get("/api/domains/@nobody/missing/repos") assert r.status_code == 404 # =========================================================================== # Layer 4 — Stress # =========================================================================== class TestStress: @pytest.mark.anyio async def test_list_100_domains(self, db_session: AsyncSession) -> None: for i in range(100): await _db_domain(db_session, slug=f"domain-{i}") await db_session.flush() result = await list_domains(db_session, page=1, page_size=100) assert result.total == 100 @pytest.mark.anyio async def test_concurrent_list_domains(self, db_session: AsyncSession) -> None: for i in range(10): await _db_domain(db_session, slug=f"c{i}") await db_session.flush() results = await asyncio.gather( *[list_domains(db_session) for _ in range(5)] ) assert all(r.total == 10 for r in results) @pytest.mark.anyio async def test_list_repos_for_domain_50_repos( self, db_session: AsyncSession ) -> None: d = await _db_domain(db_session, slug="big-domain") for i in range(50): await _db_repo(db_session, domain_id=d.domain_id, visibility="public") await db_session.flush() result = await list_repos_for_domain(db_session, d.domain_id, page_size=50) assert result.total == 50 # =========================================================================== # Layer 5 — Data Integrity # =========================================================================== class TestDataIntegrity: @pytest.mark.anyio async def test_sorted_by_install_count_desc(self, db_session: AsyncSession) -> None: await _db_domain(db_session, slug="low", install_count=1) await _db_domain(db_session, slug="high", install_count=10) await _db_domain(db_session, slug="mid", install_count=5) await db_session.flush() result = await list_domains(db_session) counts = [d.install_count for d in result.domains] assert counts == sorted(counts, reverse=True) @pytest.mark.anyio async def test_deprecated_excluded_from_list(self, db_session: AsyncSession) -> None: await _db_domain(db_session, slug="active") await _db_domain(db_session, slug="old", is_deprecated=True) await db_session.flush() result = await list_domains(db_session) slugs = [d.slug for d in result.domains] assert "active" in slugs assert "old" not in slugs @pytest.mark.anyio async def test_manifest_hash_matches_capabilities( self, db_session: AsyncSession ) -> None: caps = {"dimensions": [{"name": "tempo"}], "merge_semantics": "ot"} result = await create_domain( db_session, author_user_id="alice", author_slug="alice", slug="verify-hash", display_name="Verify", description="", capabilities=caps, ) assert result.manifest_hash == compute_manifest_hash(caps) @pytest.mark.anyio async def test_capabilities_json_not_lossy(self, db_session: AsyncSession) -> None: caps = { "dimensions": [{"name": "tempo", "unit": "bpm"}], "artifact_types": ["audio/midi"], "merge_semantics": "ot", } created = await create_domain( db_session, author_user_id="alice", author_slug="alice", slug="caps-test", display_name="Caps", description="", capabilities=caps, ) await db_session.flush() retrieved = await get_domain_by_id(db_session, created.domain_id) assert retrieved is not None assert retrieved.capabilities["dimensions"][0]["name"] == "tempo" @pytest.mark.anyio async def test_total_count_reflects_query_filter( self, db_session: AsyncSession ) -> None: await _db_domain(db_session, slug="match-a", display_name="Match This") await _db_domain(db_session, slug="no-match", display_name="Something Else") await db_session.flush() result = await list_domains(db_session, query="Match This") assert result.total == 1 # =========================================================================== # Layer 6 — Security # =========================================================================== class TestSecurity: @pytest.mark.anyio async def test_post_without_auth_returns_401( self, client: AsyncClient, db_session: AsyncSession, ) -> None: await db_session.commit() r = await client.post( "/api/domains", json={ "author_slug": "hack", "slug": "hack-domain", "display_name": "Hack", "description": "", "capabilities": {}, }, ) assert r.status_code == 401 @pytest.mark.anyio async def test_duplicate_scoped_id_returns_409( self, client: AsyncClient, auth_headers: StrDict, db_session: AsyncSession, ) -> None: await db_session.commit() body = { "author_slug": "testuser", "slug": "conflict-test", "display_name": "Conflict", "description": "", "capabilities": {}, } r1 = await client.post("/api/domains", json=body, headers=auth_headers) assert r1.status_code == 201 r2 = await client.post("/api/domains", json=body, headers=auth_headers) assert r2.status_code == 409 assert "already registered" in r2.json()["detail"] @pytest.mark.anyio async def test_sql_injection_in_query_param_safe( self, client: AsyncClient, db_session: AsyncSession, ) -> None: await db_session.commit() r = await client.get("/api/domains?q='; DROP TABLE musehub_domains; --") assert r.status_code == 200 # parameterized query — safe @pytest.mark.anyio async def test_manifest_hash_tampering_detectable(self) -> None: """Different capabilities always produce different hashes.""" original_caps = {"dimensions": [{"name": "tempo"}]} tampered_caps = {"dimensions": [{"name": "tempo"}, {"name": "injected"}]} assert compute_manifest_hash(original_caps) != compute_manifest_hash(tampered_caps) # =========================================================================== # Layer 7 — Performance # =========================================================================== class TestPerformance: @pytest.mark.anyio async def test_list_50_domains_under_200ms(self, db_session: AsyncSession) -> None: for i in range(50): await _db_domain(db_session, slug=f"perf-{i}") await db_session.flush() start = time.perf_counter() result = await list_domains(db_session, page=1, page_size=50) elapsed = time.perf_counter() - start assert result.total == 50 assert elapsed < 0.2, f"list_domains took {elapsed:.3f}s" @pytest.mark.anyio async def test_compute_manifest_hash_fast(self) -> None: caps = {"dimensions": [{"name": f"dim_{i}"} for i in range(100)]} start = time.perf_counter() for _ in range(1000): compute_manifest_hash(caps) elapsed = time.perf_counter() - start assert elapsed < 0.5, f"1000 hash computations took {elapsed:.3f}s" @pytest.mark.anyio async def test_create_domain_under_100ms(self, db_session: AsyncSession) -> None: start = time.perf_counter() await create_domain( db_session, author_user_id="alice", author_slug="alice", slug="perf-create", display_name="Perf", description="", capabilities={"dimensions": [], "merge_semantics": "ot"}, ) elapsed = time.perf_counter() - start assert elapsed < 0.1, f"create_domain took {elapsed:.3f}s"