"""Tier 1 — unit tests for knowtation MCP schemas and helpers.""" from __future__ import annotations import pathlib import pytest from muse.mcp.errors import MCPApplicationError from muse.mcp.server import MuseMCPServer, expected_prompt_count, expected_tool_count from muse.plugins.knowtation.mcp_helpers import validate_note_path from muse.plugins.knowtation.search import hybrid_search, lexical_search class TestPathValidation: def test_rejects_traversal(self) -> None: with pytest.raises(Exception) as exc: validate_note_path("../etc/passwd.md") assert exc.value.reason == "PATH_INVALID" def test_rejects_non_note(self) -> None: with pytest.raises(Exception) as exc: validate_note_path("data/index.sqlite") assert exc.value.reason == "NOT_A_NOTE" def test_accepts_note(self) -> None: assert validate_note_path("notes/foo.md") == "notes/foo.md" class TestSearchUnit: def test_lexical_empty_query(self, tmp_path: pathlib.Path) -> None: assert lexical_search(tmp_path, " ") == [] def test_hybrid_shape(self, tmp_path: pathlib.Path) -> None: note = tmp_path / "a.md" note.write_text("---\ntitle: Alpha\n---\n# Alpha\nkeyword-here\n", encoding="utf-8") out = hybrid_search(tmp_path, "keyword-here", top_k=5, mode="lexical") assert out["mode"] == "lexical" assert "backends" in out assert "degraded" in out assert isinstance(out["results"], list) class TestActivationGate: def test_non_knowtation_domain_empty_tools(self, tmp_path: pathlib.Path) -> None: muse = tmp_path / ".muse" muse.mkdir() (muse / "repo.json").write_text('{"domain":"code","repo_id":"x"}', encoding="utf-8") server = MuseMCPServer(tmp_path) resp = server.handle_request({"jsonrpc": "2.0", "id": 1, "method": "tools/list"}) assert resp is not None assert resp["result"]["tools"] == [] assert expected_tool_count("code") == 0 assert expected_prompt_count("knowtation") == 4 def test_knowtation_domain_lists_nine_tools(self, smoke_root: pathlib.Path) -> None: server = MuseMCPServer(smoke_root) resp = server.handle_request({"jsonrpc": "2.0", "id": 1, "method": "tools/list"}) assert resp is not None names = {t["name"] for t in resp["result"]["tools"]} assert len(names) == 9 assert "knowtation/search" in names @pytest.fixture def smoke_root() -> pathlib.Path: return pathlib.Path("/Users/aaronrenecarvajal/MUSE_HUB/knowtation-vault-smoke")