"""Section 16 — MCP Mist Tools: 7-layer test suite. Covers the mist MCP executors: Write tools (write_tools/mists.py): execute_create_mist, execute_update_mist, execute_fork_mist, execute_delete_mist Read tools (services/musehub_mcp_executor.py): execute_read_mist, execute_list_mists, execute_read_mist_embed Resource handlers (mcp/resources.py): _read_mist, _read_owner_mists (via musehub://mists/... URIs) Seven layers: Layer 1 Unit: - muse_mist_* tool names appear in correct catalogue sets - _mist_data serialises MistResponse to correct dict keys - execute_create_mist: empty actor → forbidden - execute_create_mist: empty filename → missing_args - execute_create_mist: empty content → missing_args - execute_update_mist: empty actor → forbidden - execute_fork_mist: empty actor → forbidden - execute_delete_mist: empty actor → forbidden Layer 2 Integration: - execute_create_mist: happy path returns mist_id, artifact_type, content - execute_create_mist: duplicate content → already_exists - execute_update_mist: title change persisted - execute_update_mist: non-owner → not_found - execute_update_mist: visibility change to secret - execute_delete_mist: happy path returns deleted=True - execute_delete_mist: non-owner → not_found - execute_delete_mist: unknown mist_id → not_found - execute_fork_mist: happy path returns new mist_id and fork_parent_id - execute_fork_mist: unknown source → not_found - execute_read_mist: public mist readable by anon - execute_read_mist: secret mist readable by owner - execute_read_mist: secret mist blocked for non-owner - execute_read_mist: unknown → not_found - execute_list_mists: explore mode returns public mists - execute_list_mists: owner mode returns owner's mists - execute_list_mists: secret excluded for anon, included for owner - execute_read_mist_embed: returns iframe, javascript, badge strings - execute_read_mist_embed: secret mist → forbidden - execute_read_mist_embed: unknown mist → not_found Layer 3 E2E (HTTP tools/call): - Anonymous muse_mist_create → 401 - Anonymous muse_mist_update → 401 - Anonymous muse_mist_fork → 401 - Anonymous muse_mist_delete → 401 - Authenticated muse_mist_create → isError=False, mist_id present - Authenticated muse_mist_list (read tool) → isError=False, mists list Layer 4 Stress: - 10 sequential creates under 1000 ms Layer 5 Data Integrity: - Created mist retrievable via execute_read_mist - Created mist appears in execute_list_mists(owner=...) - Updated title persisted after execute_update_mist - Deleted mist not found via execute_read_mist - Fork parent_id correct + source fork_count incremented Layer 6 Security: - muse_mist_create/update/fork/delete in MUSEHUB_WRITE_TOOL_NAMES - muse_mist_read/list/embed in read set (not in MUSEHUB_WRITE_TOOL_NAMES) - Secret mist inaccessible via read executor to non-owner - Secret mist excluded from explore listing - Content returned as-is (no XSS transformation) Layer 7 Performance: - 10 sequential creates under 1000 ms """ from __future__ import annotations import json import secrets import time from datetime import datetime, timezone, timedelta import pytest import pytest_asyncio from httpx import AsyncClient, ASGITransport from sqlalchemy.ext.asyncio import AsyncSession from musehub.core.genesis import compute_identity_id, compute_repo_id from musehub.db.musehub_repo_models import MusehubRepo from musehub.main import app from musehub.mcp.tools.musehub import MUSEHUB_WRITE_TOOL_NAMES, MUSEHUB_TOOL_NAMES from musehub.types.json_types import JSONObject, StrDict from musehub.mcp.write_tools.mists import ( _mist_data, execute_create_mist, execute_delete_mist, execute_fork_mist, execute_update_mist, ) from musehub.services.musehub_mcp_executor import ( execute_list_mists, execute_list_mist_forks, execute_read_mist, execute_read_mist_embed, execute_read_mist_raw, ) from musehub.mcp.resources import read_resource # ── Fixtures ────────────────────────────────────────────────────────────────── @pytest.fixture def anyio_backend() -> str: return "asyncio" @pytest_asyncio.fixture async def http_client(db_session: AsyncSession) -> AsyncClient: async with AsyncClient( transport=ASGITransport(app=app), base_url="http://localhost", ) as c: yield c # ── Helpers ─────────────────────────────────────────────────────────────────── _OWNER = "alice" _PY_CONTENT = "def validate(x: str) -> bool:\n return bool(x)\n" _PY_FILENAME = "validate.py" def _uid() -> str: return secrets.token_hex(16) def _unique_content() -> str: """Return content unique enough that its mist_id won't collide.""" return f"{_PY_CONTENT}# salt={secrets.token_hex(16)}" def _tools_call(name: str, arguments: JSONObject) -> JSONObject: return { "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": name, "arguments": arguments}, } def _unwrap_tool_text(text: str) -> str: text = text.strip() if text.startswith(""): text = text[len(""):].strip() if text.endswith(""): text = text[: -len("")].strip() return text async def _create( content: str | None = None, filename: str = _PY_FILENAME, visibility: str = "public", actor: str = _OWNER, title: str = "", ) -> "MusehubToolResult": # type: ignore[name-defined] return await execute_create_mist( filename=filename, content=content or _unique_content(), actor=actor, title=title, visibility=visibility, ) # ── Layer 1 — Unit ──────────────────────────────────────────────────────────── class TestUnitToolCatalogue: def test_mist_write_tools_in_write_set(self) -> None: expected = {"muse_mist_create", "muse_mist_update", "muse_mist_fork", "muse_mist_delete"} missing = expected - MUSEHUB_WRITE_TOOL_NAMES assert not missing, f"Missing from write set: {missing}" def test_mist_read_tools_NOT_in_write_set(self) -> None: read_tools = {"muse_mist_read", "muse_mist_list", "muse_mist_embed"} in_write = read_tools & MUSEHUB_WRITE_TOOL_NAMES assert not in_write, f"Read tools incorrectly in write set: {in_write}" def test_all_mist_tools_in_tool_names(self) -> None: expected = { "muse_mist_create", "muse_mist_update", "muse_mist_fork", "muse_mist_delete", "muse_mist_read", "muse_mist_list", "muse_mist_embed", } missing = expected - MUSEHUB_TOOL_NAMES assert not missing, f"Missing from MUSEHUB_TOOL_NAMES: {missing}" class TestUnitMistDataHelper: async def test_mist_data_keys(self, db_session: AsyncSession) -> None: result = await _create() assert result.ok is True data = result.data for key in ("mist_id", "owner", "artifact_type", "language", "filename", "content", "size_bytes", "version", "visibility", "tags", "symbol_anchors", "created_at", "updated_at"): assert key in data, f"Missing key: {key}" class TestUnitInputValidation: async def test_create_empty_actor_returns_forbidden(self) -> None: result = await execute_create_mist(filename="f.py", content="x", actor="") assert result.ok is False assert result.error_code == "forbidden" async def test_create_empty_filename_returns_missing_args(self) -> None: result = await execute_create_mist(filename="", content="x", actor=_OWNER) assert result.ok is False assert result.error_code == "missing_args" async def test_create_empty_content_returns_missing_args(self) -> None: result = await execute_create_mist(filename="f.py", content="", actor=_OWNER) assert result.ok is False assert result.error_code == "missing_args" async def test_update_empty_actor_returns_forbidden(self) -> None: result = await execute_update_mist(mist_id="aB3xKq9dPwNm", actor="") assert result.ok is False assert result.error_code == "forbidden" async def test_fork_empty_actor_returns_forbidden(self) -> None: result = await execute_fork_mist(mist_id="aB3xKq9dPwNm", actor="") assert result.ok is False assert result.error_code == "forbidden" async def test_delete_empty_actor_returns_forbidden(self) -> None: result = await execute_delete_mist(mist_id="aB3xKq9dPwNm", actor="") assert result.ok is False assert result.error_code == "forbidden" # ── Layer 2 — Integration ───────────────────────────────────────────────────── class TestIntegrationCreate: async def test_create_happy_path(self, db_session: AsyncSession) -> None: result = await _create() assert result.ok is True data = result.data assert len(data["mist_id"]) == 12 assert data["artifact_type"] == "code" assert data["language"] == "python" assert data["owner"] == _OWNER assert data["visibility"] == "public" assert data["version"] == 1 async def test_create_duplicate_content_returns_already_exists( self, db_session: AsyncSession ) -> None: content = _unique_content() r1 = await execute_create_mist(filename=_PY_FILENAME, content=content, actor=_OWNER) assert r1.ok is True r2 = await execute_create_mist(filename=_PY_FILENAME, content=content, actor=_OWNER) assert r2.ok is False assert r2.error_code == "already_exists" async def test_create_with_title_and_tags(self, db_session: AsyncSession) -> None: result = await execute_create_mist( filename=_PY_FILENAME, content=_unique_content(), actor=_OWNER, title="My helper", tags=["utils", "security"], ) assert result.ok is True assert result.data["title"] == "My helper" assert result.data["tags"] == ["utils", "security"] async def test_create_secret_mist(self, db_session: AsyncSession) -> None: result = await execute_create_mist( filename=_PY_FILENAME, content=_unique_content(), actor=_OWNER, visibility="secret", ) assert result.ok is True assert result.data["visibility"] == "secret" class TestIntegrationUpdate: async def test_update_title(self, db_session: AsyncSession) -> None: created = await _create() mid = created.data["mist_id"] result = await execute_update_mist(mist_id=mid, actor=_OWNER, title="New title") assert result.ok is True assert result.data["title"] == "New title" async def test_update_visibility_to_secret(self, db_session: AsyncSession) -> None: created = await _create() mid = created.data["mist_id"] result = await execute_update_mist(mist_id=mid, actor=_OWNER, visibility="secret") assert result.ok is True assert result.data["visibility"] == "secret" async def test_update_content_increments_version(self, db_session: AsyncSession) -> None: created = await _create() mid = created.data["mist_id"] result = await execute_update_mist( mist_id=mid, actor=_OWNER, content="# new content\n" ) assert result.ok is True assert result.data["version"] == 2 async def test_update_non_owner_returns_not_found(self, db_session: AsyncSession) -> None: created = await _create() mid = created.data["mist_id"] result = await execute_update_mist(mist_id=mid, actor="bob", title="Stolen") assert result.ok is False assert result.error_code == "not_found" async def test_update_unknown_mist_returns_not_found(self, db_session: AsyncSession) -> None: result = await execute_update_mist(mist_id="unknown12345", actor=_OWNER, title="X") assert result.ok is False assert result.error_code == "not_found" class TestIntegrationDelete: async def test_delete_happy_path(self, db_session: AsyncSession) -> None: created = await _create() mid = created.data["mist_id"] result = await execute_delete_mist(mist_id=mid, actor=_OWNER) assert result.ok is True assert result.data["deleted"] is True assert result.data["mist_id"] == mid async def test_delete_non_owner_returns_not_found(self, db_session: AsyncSession) -> None: created = await _create() mid = created.data["mist_id"] result = await execute_delete_mist(mist_id=mid, actor="bob") assert result.ok is False assert result.error_code == "not_found" async def test_delete_unknown_returns_not_found(self, db_session: AsyncSession) -> None: result = await execute_delete_mist(mist_id="unknown12345", actor=_OWNER) assert result.ok is False assert result.error_code == "not_found" class TestIntegrationFork: async def test_fork_happy_path(self, db_session: AsyncSession) -> None: source = await _create(actor=_OWNER) mid = source.data["mist_id"] result = await execute_fork_mist(mist_id=mid, actor="bob") assert result.ok is True assert result.data["fork_parent_id"] == mid assert result.data["owner"] == "bob" assert result.data["mist_id"] != mid async def test_fork_unknown_returns_not_found(self, db_session: AsyncSession) -> None: result = await execute_fork_mist(mist_id="unknown12345", actor="bob") assert result.ok is False assert result.error_code == "not_found" class TestIntegrationReadMist: async def test_read_public_mist_anon(self, db_session: AsyncSession) -> None: created = await _create() mid = created.data["mist_id"] result = await execute_read_mist(mid, actor="") assert result.ok is True assert result.data["mist_id"] == mid assert "content" in result.data async def test_read_secret_mist_as_owner(self, db_session: AsyncSession) -> None: created = await _create(visibility="secret") mid = created.data["mist_id"] result = await execute_read_mist(mid, actor=_OWNER) assert result.ok is True async def test_read_secret_mist_as_non_owner_returns_forbidden( self, db_session: AsyncSession ) -> None: created = await _create(visibility="secret") mid = created.data["mist_id"] result = await execute_read_mist(mid, actor="bob") assert result.ok is False assert result.error_code == "forbidden" async def test_read_unknown_returns_not_found(self, db_session: AsyncSession) -> None: result = await execute_read_mist("unknown12345") assert result.ok is False assert result.error_code == "not_found" class TestIntegrationListMists: async def test_explore_returns_public(self, db_session: AsyncSession) -> None: created = await _create(actor=_OWNER) mid = created.data["mist_id"] result = await execute_list_mists(owner=None) assert result.ok is True ids = {m["mist_id"] for m in result.data["mists"]} assert mid in ids async def test_explore_excludes_secret(self, db_session: AsyncSession) -> None: created = await _create(visibility="secret") mid = created.data["mist_id"] result = await execute_list_mists(owner=None) assert result.ok is True ids = {m["mist_id"] for m in result.data["mists"]} assert mid not in ids async def test_owner_mode_includes_public(self, db_session: AsyncSession) -> None: created = await _create(actor=_OWNER) mid = created.data["mist_id"] result = await execute_list_mists(owner=_OWNER) assert result.ok is True ids = {m["mist_id"] for m in result.data["mists"]} assert mid in ids async def test_owner_mode_excludes_secret_for_anon(self, db_session: AsyncSession) -> None: created = await _create(visibility="secret") mid = created.data["mist_id"] result = await execute_list_mists(owner=_OWNER, include_secret=True, actor="bob") assert result.ok is True ids = {m["mist_id"] for m in result.data["mists"]} assert mid not in ids async def test_owner_mode_includes_secret_for_owner(self, db_session: AsyncSession) -> None: created = await _create(visibility="secret", actor=_OWNER) mid = created.data["mist_id"] result = await execute_list_mists(owner=_OWNER, include_secret=True, actor=_OWNER) assert result.ok is True ids = {m["mist_id"] for m in result.data["mists"]} assert mid in ids class TestIntegrationEmbed: async def test_embed_public_mist(self, db_session: AsyncSession) -> None: created = await _create() mid = created.data["mist_id"] result = await execute_read_mist_embed(mid, owner=_OWNER) assert result.ok is True data = result.data assert "iframe" in data assert "javascript" in data assert "badge" in data assert mid in data["iframe"] async def test_embed_secret_mist_returns_forbidden(self, db_session: AsyncSession) -> None: created = await _create(visibility="secret") mid = created.data["mist_id"] result = await execute_read_mist_embed(mid, owner=_OWNER) assert result.ok is False assert result.error_code == "forbidden" async def test_embed_unknown_mist_returns_not_found(self, db_session: AsyncSession) -> None: result = await execute_read_mist_embed("unknown12345", owner="nobody") assert result.ok is False assert result.error_code == "not_found" class TestIntegrationResource: async def test_read_resource_single_mist(self, db_session: AsyncSession) -> None: created = await _create() mid = created.data["mist_id"] data = await read_resource(f"musehub://mists/{_OWNER}/{mid}") assert "error" not in data assert data["mist_id"] == mid assert "content" in data async def test_read_resource_owner_mists(self, db_session: AsyncSession) -> None: created = await _create(actor=_OWNER) mid = created.data["mist_id"] data = await read_resource(f"musehub://mists/{_OWNER}") assert "error" not in data ids = {m["mist_id"] for m in data["mists"]} assert mid in ids async def test_read_resource_unknown_mist(self, db_session: AsyncSession) -> None: data = await read_resource("musehub://mists/nobody/unknown12345") assert "error" in data async def test_read_resource_secret_mist_blocked_for_anon( self, db_session: AsyncSession ) -> None: created = await _create(visibility="secret") mid = created.data["mist_id"] data = await read_resource(f"musehub://mists/{_OWNER}/{mid}", user_id=None) assert "error" in data # ── Layer 3 — End-to-End ────────────────────────────────────────────────────── class TestE2EAuthGate: """Write tool calls without auth must return 401.""" async def test_create_mist_no_auth(self, http_client: AsyncClient) -> None: resp = await http_client.post( "/mcp", json=_tools_call("muse_mist_create", {"filename": "f.py", "content": "x"}), headers={"Content-Type": "application/json"}, ) assert resp.status_code == 401 async def test_update_mist_no_auth(self, http_client: AsyncClient) -> None: resp = await http_client.post( "/mcp", json=_tools_call("muse_mist_update", {"mist_id": "aB3xKq9dPwNm"}), headers={"Content-Type": "application/json"}, ) assert resp.status_code == 401 async def test_fork_mist_no_auth(self, http_client: AsyncClient) -> None: resp = await http_client.post( "/mcp", json=_tools_call("muse_mist_fork", {"mist_id": "aB3xKq9dPwNm"}), headers={"Content-Type": "application/json"}, ) assert resp.status_code == 401 async def test_delete_mist_no_auth(self, http_client: AsyncClient) -> None: resp = await http_client.post( "/mcp", json=_tools_call("muse_mist_delete", {"mist_id": "aB3xKq9dPwNm"}), headers={"Content-Type": "application/json"}, ) assert resp.status_code == 401 async def test_create_mist_with_auth( self, http_client: AsyncClient, db_session: AsyncSession, auth_headers: StrDict ) -> None: content = _unique_content() resp = await http_client.post( "/mcp", json=_tools_call("muse_mist_create", { "filename": _PY_FILENAME, "content": content, "title": "E2E mist", }), headers=auth_headers, ) assert resp.status_code == 200 result = resp.json()["result"] assert result["isError"] is False payload = json.loads(_unwrap_tool_text(result["content"][0]["text"])) assert "mist_id" in payload assert payload["title"] == "E2E mist" async def test_list_mists_read_tool_no_auth(self, http_client: AsyncClient) -> None: """muse_mist_list is a read tool — accessible without auth.""" resp = await http_client.post( "/mcp", json=_tools_call("muse_mist_list", {}), headers={"Content-Type": "application/json"}, ) # Read tools don't require auth at the HTTP layer assert resp.status_code in (200, 401) if resp.status_code == 200: result = resp.json()["result"] assert result["isError"] is False # ── Layer 4 — Stress ────────────────────────────────────────────────────────── class TestStressMistTools: async def test_10_sequential_creates(self, db_session: AsyncSession) -> None: start = time.monotonic() ids: list[str] = [] for _ in range(10): result = await execute_create_mist( filename=_PY_FILENAME, content=_unique_content(), actor=_OWNER, ) assert result.ok is True ids.append(result.data["mist_id"]) elapsed = time.monotonic() - start assert elapsed < 1.0, f"10 creates took {elapsed:.2f}s (> 1s)" assert len(set(ids)) == 10, "All mist IDs must be unique" # ── Layer 5 — Data Integrity ────────────────────────────────────────────────── class TestDataIntegrity: async def test_created_mist_retrievable(self, db_session: AsyncSession) -> None: created = await _create(title="Persistent") mid = created.data["mist_id"] read = await execute_read_mist(mid, actor=_OWNER) assert read.ok is True assert read.data["mist_id"] == mid assert read.data["title"] == "Persistent" async def test_created_mist_in_owner_list(self, db_session: AsyncSession) -> None: created = await _create(actor=_OWNER) mid = created.data["mist_id"] result = await execute_list_mists(owner=_OWNER) assert result.ok is True ids = {m["mist_id"] for m in result.data["mists"]} assert mid in ids async def test_update_title_persisted(self, db_session: AsyncSession) -> None: created = await _create() mid = created.data["mist_id"] await execute_update_mist(mist_id=mid, actor=_OWNER, title="Persisted title") read = await execute_read_mist(mid) assert read.ok is True assert read.data["title"] == "Persisted title" async def test_deleted_mist_not_found(self, db_session: AsyncSession) -> None: created = await _create() mid = created.data["mist_id"] del_result = await execute_delete_mist(mist_id=mid, actor=_OWNER) assert del_result.ok is True read = await execute_read_mist(mid) assert read.ok is False assert read.error_code == "not_found" async def test_fork_parent_id_and_source_fork_count(self, db_session: AsyncSession) -> None: source = await _create(actor=_OWNER) mid = source.data["mist_id"] fork = await execute_fork_mist(mist_id=mid, actor="bob") assert fork.ok is True assert fork.data["fork_parent_id"] == mid # Source fork_count incremented — verify via read read = await execute_read_mist(mid, actor=_OWNER) assert read.ok is True assert read.data["fork_count"] >= 1 async def test_view_count_increments_on_read(self, db_session: AsyncSession) -> None: created = await _create() mid = created.data["mist_id"] r1 = await execute_read_mist(mid) r2 = await execute_read_mist(mid) assert r2.data["view_count"] > r1.data["view_count"] async def test_embed_count_increments_on_embed(self, db_session: AsyncSession) -> None: created = await _create() mid = created.data["mist_id"] r1 = await execute_read_mist(mid) await execute_read_mist_embed(mid, owner=_OWNER) r2 = await execute_read_mist(mid) assert r2.data["embed_count"] > r1.data["embed_count"] # ── Layer 6 — Security ──────────────────────────────────────────────────────── class TestSecurity: def test_write_tools_in_auth_gate_set(self) -> None: write_tools = {"muse_mist_create", "muse_mist_update", "muse_mist_fork", "muse_mist_delete"} missing = write_tools - MUSEHUB_WRITE_TOOL_NAMES assert not missing, f"Write tools missing from auth gate: {missing}" def test_read_tools_not_in_write_set(self) -> None: read_tools = {"muse_mist_read", "muse_mist_list", "muse_mist_embed"} in_write = read_tools & MUSEHUB_WRITE_TOOL_NAMES assert not in_write, f"Read tools in write auth gate (shouldn't be): {in_write}" async def test_secret_mist_not_in_explore(self, db_session: AsyncSession) -> None: created = await _create(visibility="secret") mid = created.data["mist_id"] result = await execute_list_mists(owner=None, actor="") ids = {m["mist_id"] for m in result.data["mists"]} assert mid not in ids, "Secret mist must not appear in explore feed" async def test_secret_mist_blocked_for_non_owner_read(self, db_session: AsyncSession) -> None: created = await _create(visibility="secret") mid = created.data["mist_id"] result = await execute_read_mist(mid, actor="bob") assert result.ok is False assert result.error_code == "forbidden" async def test_content_returned_verbatim_no_xss_transform( self, db_session: AsyncSession ) -> None: """Content is returned verbatim — XSS prevention is a renderer concern.""" xss_payload = '' created = await execute_create_mist( filename="test.html", content=xss_payload, actor=_OWNER, ) assert created.ok is True mid = created.data["mist_id"] read = await execute_read_mist(mid, actor=_OWNER) assert read.ok is True assert read.data["content"] == xss_payload async def test_agent_id_stored_verbatim(self, db_session: AsyncSession) -> None: """agent_id is stored as opaque string — no injection risk in storage.""" agent = "agentception-worker-42; DROP TABLE mists;--" created = await execute_create_mist( filename=_PY_FILENAME, content=_unique_content(), actor=_OWNER, agent_id=agent, ) assert created.ok is True assert created.data["agent_id"] == agent # ── Layer 7 — Performance ───────────────────────────────────────────────────── class TestPerformance: async def test_10_creates_under_500ms(self, db_session: AsyncSession) -> None: start = time.monotonic() for _ in range(10): result = await execute_create_mist( filename=_PY_FILENAME, content=_unique_content(), actor=_OWNER, ) assert result.ok is True elapsed = time.monotonic() - start assert elapsed < 0.5, f"10 creates took {elapsed:.2f}s (> 500ms)" async def test_list_100_mists_under_200ms(self, db_session: AsyncSession) -> None: # noqa: E501 from muse.plugins.mist.plugin import compute_mist_id base_time = datetime.now(tz=timezone.utc) unique_type = f"perf_{secrets.token_hex(4)}" owner_id = compute_identity_id(_OWNER.encode()) for i in range(20): content = f"perf_{i}_{secrets.token_hex(16)}" mid = compute_mist_id(content.encode()) from musehub.db.musehub_repo_models import MusehubMist slug = secrets.token_hex(6) created_at = base_time repo_id = compute_repo_id(owner_id, slug, "code", created_at.isoformat()) repo = MusehubRepo( repo_id=repo_id, name=slug, owner=_OWNER, slug=slug, visibility="public", owner_user_id=owner_id, created_at=created_at, updated_at=created_at, ) db_session.add(repo) await db_session.flush() await db_session.refresh(repo) row = MusehubMist( mist_id=mid, repo_id=str(repo.repo_id), owner=_OWNER, filename="p.py", content=content, artifact_type=unique_type, language="python", visibility="public", tags=[], symbol_anchors=[], created_at=base_time + timedelta(seconds=i), updated_at=base_time + timedelta(seconds=i), ) db_session.add(row) await db_session.commit() start = time.monotonic() result = await execute_list_mists( artifact_type=unique_type, limit=20, ) elapsed = time.monotonic() - start assert result.ok is True assert elapsed < 0.2, f"list 20 mists took {elapsed:.2f}s (> 200ms)" # ── execute_list_mist_forks tests ───────────────────────────────────────────── @pytest.mark.anyio class TestListMistForks: """Tests for execute_list_mist_forks — all 8 tiers. Covers: empty mist_id guard, not_found, forbidden (secret parent, non-owner actor), happy path with zero forks, happy path with forks, limit clamping, and performance (<200ms for 10 forks). """ async def test_empty_mist_id_returns_missing_args( self, db_session: AsyncSession ) -> None: """Empty mist_id returns missing_args immediately without a DB hit.""" result = await execute_list_mist_forks("") assert result.ok is False assert result.error_code == "missing_args" async def test_unknown_mist_returns_not_found( self, db_session: AsyncSession ) -> None: """Non-existent parent mist returns not_found.""" result = await execute_list_mist_forks("NoSuchMistXX") assert result.ok is False assert result.error_code == "not_found" async def test_secret_parent_anon_returns_forbidden( self, db_session: AsyncSession ) -> None: """Secret parent mist with anonymous actor returns forbidden.""" created = await execute_create_mist( filename=_PY_FILENAME, content=_unique_content(), actor=_OWNER, visibility="secret", ) assert created.ok is True mist_id = created.data["mist_id"] result = await execute_list_mist_forks(mist_id, actor="") assert result.ok is False assert result.error_code == "forbidden" async def test_public_parent_no_forks_returns_empty_list( self, db_session: AsyncSession ) -> None: """Public parent with no forks returns empty forks list, total=0.""" created = await execute_create_mist( filename=_PY_FILENAME, content=_unique_content(), actor=_OWNER, ) assert created.ok is True mist_id = created.data["mist_id"] result = await execute_list_mist_forks(mist_id) assert result.ok is True assert result.data["mist_id"] == mist_id assert result.data["total"] == 0 assert result.data["forks"] == [] async def test_forks_appear_after_fork_creation( self, db_session: AsyncSession ) -> None: """After forking a mist, execute_list_mist_forks returns the fork.""" parent = await execute_create_mist( filename=_PY_FILENAME, content=_unique_content(), actor=_OWNER, ) assert parent.ok is True parent_id = parent.data["mist_id"] fork = await execute_fork_mist(mist_id=parent_id, actor="otheruser") assert fork.ok is True result = await execute_list_mist_forks(parent_id) assert result.ok is True assert result.data["total"] == 1 fork_entry = result.data["forks"][0] assert fork_entry["owner"] == "otheruser" assert fork_entry["mist_id"] == fork.data["mist_id"] async def test_limit_clamped_to_100( self, db_session: AsyncSession ) -> None: """Passing limit=200 is silently clamped to 100 (no error).""" created = await execute_create_mist( filename=_PY_FILENAME, content=_unique_content(), actor=_OWNER, ) assert created.ok is True result = await execute_list_mist_forks( created.data["mist_id"], limit=200 ) assert result.ok is True async def test_secret_parent_owner_can_list_forks( self, db_session: AsyncSession ) -> None: """Owner of a secret parent can list its forks.""" created = await execute_create_mist( filename=_PY_FILENAME, content=_unique_content(), actor=_OWNER, visibility="secret", ) assert created.ok is True result = await execute_list_mist_forks( created.data["mist_id"], actor=_OWNER ) assert result.ok is True assert result.data["total"] == 0 async def test_fork_entry_has_required_keys( self, db_session: AsyncSession ) -> None: """Each fork entry contains the required schema keys.""" parent = await execute_create_mist( filename=_PY_FILENAME, content=_unique_content(), actor=_OWNER, ) assert parent.ok is True await execute_fork_mist(mist_id=parent.data["mist_id"], actor="otheruser") result = await execute_list_mist_forks(parent.data["mist_id"]) assert result.ok is True entry = result.data["forks"][0] for key in ("mist_id", "owner", "filename", "artifact_type", "fork_depth", "fork_count", "visibility", "tags", "created_at"): assert key in entry, f"Missing key '{key}' in fork entry" async def test_muse_mist_list_forks_in_tool_catalogue(self) -> None: """muse_mist_list_forks appears in MUSEHUB_TOOL_NAMES.""" assert "muse_mist_list_forks" in MUSEHUB_TOOL_NAMES async def test_muse_mist_list_forks_not_in_write_tools(self) -> None: """muse_mist_list_forks is a read tool — must not appear in write set.""" assert "muse_mist_list_forks" not in MUSEHUB_WRITE_TOOL_NAMES async def test_10_forks_listed_under_200ms( self, db_session: AsyncSession ) -> None: """Listing 10 forks completes in under 200ms.""" parent = await execute_create_mist( filename=_PY_FILENAME, content=_unique_content(), actor=_OWNER, ) assert parent.ok is True parent_id = parent.data["mist_id"] for i in range(10): fork = await execute_fork_mist(mist_id=parent_id, actor=f"user{i}") assert fork.ok is True start = time.monotonic() result = await execute_list_mist_forks(parent_id, limit=10) elapsed = time.monotonic() - start assert result.ok is True assert result.data["total"] == 10 assert elapsed < 0.2, f"listing 10 forks took {elapsed:.2f}s (> 200ms)" # ── execute_read_mist_raw tests ─────────────────────────────────────────────── @pytest.mark.anyio class TestReadMistRaw: """Tests for execute_read_mist_raw — all 8 tiers. Covers: empty mist_id guard, not_found, forbidden (secret mist, non-owner), happy path content/keys, view counter increment, performance (<50ms), and tool catalogue membership. """ async def test_empty_mist_id_returns_missing_args( self, db_session: AsyncSession ) -> None: """Empty mist_id returns missing_args without a DB hit.""" result = await execute_read_mist_raw("") assert result.ok is False assert result.error_code == "missing_args" async def test_unknown_mist_returns_not_found( self, db_session: AsyncSession ) -> None: """Non-existent mist_id returns not_found.""" result = await execute_read_mist_raw("NoSuchMistXX") assert result.ok is False assert result.error_code == "not_found" async def test_secret_mist_anon_returns_forbidden( self, db_session: AsyncSession ) -> None: """Anonymous actor cannot read a secret mist.""" created = await execute_create_mist( filename=_PY_FILENAME, content=_unique_content(), actor=_OWNER, visibility="secret", ) assert created.ok is True result = await execute_read_mist_raw(created.data["mist_id"], actor="") assert result.ok is False assert result.error_code == "forbidden" async def test_secret_mist_non_owner_returns_forbidden( self, db_session: AsyncSession ) -> None: """Non-owner actor cannot read a secret mist.""" created = await execute_create_mist( filename=_PY_FILENAME, content=_unique_content(), actor=_OWNER, visibility="secret", ) assert created.ok is True result = await execute_read_mist_raw( created.data["mist_id"], actor="intruder" ) assert result.ok is False assert result.error_code == "forbidden" async def test_secret_mist_owner_can_read_raw( self, db_session: AsyncSession ) -> None: """Owner can read a secret mist's raw content.""" content = _unique_content() created = await execute_create_mist( filename=_PY_FILENAME, content=content, actor=_OWNER, visibility="secret", ) assert created.ok is True result = await execute_read_mist_raw( created.data["mist_id"], actor=_OWNER ) assert result.ok is True assert result.data["content"] == content async def test_public_mist_readable_by_anon( self, db_session: AsyncSession ) -> None: """Public mist is readable by anonymous caller.""" content = _unique_content() created = await execute_create_mist( filename=_PY_FILENAME, content=content, actor=_OWNER, ) assert created.ok is True result = await execute_read_mist_raw(created.data["mist_id"]) assert result.ok is True assert result.data["content"] == content async def test_data_has_required_keys( self, db_session: AsyncSession ) -> None: """Successful result contains all expected data keys.""" created = await execute_create_mist( filename=_PY_FILENAME, content=_unique_content(), actor=_OWNER, ) assert created.ok is True result = await execute_read_mist_raw(created.data["mist_id"]) assert result.ok is True for key in ("mist_id", "filename", "artifact_type", "language", "size_bytes", "content"): assert key in result.data, f"Missing key '{key}' in raw result" async def test_size_bytes_matches_content_length( self, db_session: AsyncSession ) -> None: """size_bytes in the result equals the UTF-8 byte length of content.""" content = _unique_content() created = await execute_create_mist( filename=_PY_FILENAME, content=content, actor=_OWNER, ) assert created.ok is True result = await execute_read_mist_raw(created.data["mist_id"]) assert result.ok is True assert result.data["size_bytes"] == len(content.encode("utf-8")) async def test_muse_mist_raw_in_tool_catalogue(self) -> None: """muse_mist_raw appears in MUSEHUB_TOOL_NAMES.""" assert "muse_mist_raw" in MUSEHUB_TOOL_NAMES async def test_muse_mist_raw_not_in_write_tools(self) -> None: """muse_mist_raw is a read tool — must not appear in the write set.""" assert "muse_mist_raw" not in MUSEHUB_WRITE_TOOL_NAMES async def test_raw_under_50ms(self, db_session: AsyncSession) -> None: """Raw read of a 1 KiB mist completes in under 50ms.""" content = f"x = 1\n# {'a' * 500}\n" created = await execute_create_mist( filename=_PY_FILENAME, content=content, actor=_OWNER, ) assert created.ok is True start = time.monotonic() result = await execute_read_mist_raw(created.data["mist_id"]) elapsed = time.monotonic() - start assert result.ok is True assert elapsed < 0.05, f"raw read took {elapsed:.3f}s (> 50ms)"