"""KD-6b vault UI — seven-tier test matrix (§KD-6a.F).""" from __future__ import annotations import asyncio import pathlib import secrets from datetime import datetime, timezone from unittest.mock import patch import pytest from httpx import AsyncClient from sqlalchemy.ext.asyncio import AsyncSession from musehub.services.knowtation_view import ( KnowtationViewAdapter, KnowtationViewResult, VaultUIState, compute_harmony_diff, frontmatter_panel_html, harmony_diff_html, is_vault_domain_active, map_mcp_reason, render_note_html, render_wikilinks, split_frontmatter, structured_delta_to_view_model, validate_note_path, ) from musehub.services.knowtation_view.path_grammar import PathGrammarError SMOKE_ROOT = pathlib.Path("/Users/aaronrenecarvajal/MUSE_HUB/knowtation-vault-smoke") SMOKE_NOTE = "projects/kd-smoke/overview.md" # --------------------------------------------------------------------------- # Fixtures # --------------------------------------------------------------------------- @pytest.fixture def smoke_root() -> pathlib.Path: if not SMOKE_ROOT.is_dir(): pytest.skip("knowtation-vault-smoke fixture not present") return SMOKE_ROOT async def _seed_knowtation_repo( db: AsyncSession, *, owner: str = "kd6owner", slug: str = "kd6-vault", ): from musehub.core.genesis import compute_identity_id, compute_repo_id from musehub.db.musehub_domain_models import MusehubDomain from musehub.db.musehub_repo_models import MusehubRepo domain = MusehubDomain( domain_id=f"sha256:{secrets.token_hex(32)}", author_slug="aaronrene", slug="knowtation", display_name="Knowtation", description="Test domain", version="0.1.0", viewer_type="generic", capabilities={}, ) db.add(domain) await db.flush() created = datetime.now(tz=timezone.utc) owner_id = compute_identity_id(owner.encode()) repo = MusehubRepo( repo_id=compute_repo_id(owner_id, slug, "knowtation", created.isoformat()), name=slug, owner=owner, slug=slug, visibility="public", owner_user_id=owner_id, marketplace_domain_id=domain.domain_id, domain_id="knowtation", created_at=created, updated_at=created, ) db.add(repo) await db.commit() await db.refresh(repo) await db.refresh(domain) return repo, domain # --------------------------------------------------------------------------- # Tier 1 — Unit # --------------------------------------------------------------------------- @pytest.mark.tier1 class TestVaultUnit: def test_gate_active_for_knowtation_scoped_id(self) -> None: assert is_vault_domain_active({"scoped_id": "@aaronrene/knowtation"}) is True def test_gate_inactive_for_code_domain(self) -> None: assert is_vault_domain_active({"scoped_id": "@gabriel/muse"}) is False assert is_vault_domain_active(None) is False def test_path_grammar_rejects_traversal(self) -> None: with pytest.raises(PathGrammarError) as exc: validate_note_path("../secret.md") assert exc.value.reason == "PATH_INVALID" def test_path_grammar_rejects_non_note(self) -> None: with pytest.raises(PathGrammarError): validate_note_path("data/index.sqlite") def test_wikilink_resolves_and_broken_class(self) -> None: raw = "See [[linked-target]] and [[missing-note]]" out = render_wikilinks( raw, owner="o", repo_slug="r", note_paths=frozenset({"linked-target.md"}), ) assert "vault-wikilink" in out assert "broken" in out def test_frontmatter_split_and_panel_no_script(self) -> None: raw = "---\ntitle: T\ntags: [a]\n---\n# Body\n" fm, body = split_frontmatter(raw) assert fm["title"] == "T" assert "Body" in body panel = frontmatter_panel_html(fm) assert " None: _, html = render_note_html("\n**bold**", owner="o", repo_slug="r") assert "" in html def test_degraded_banner_logic(self) -> None: r = KnowtationViewResult(ok=True, data={}, degraded=True) assert r.ui_state == VaultUIState.SEARCH_DEGRADED def test_mcp_reason_mapping(self) -> None: assert map_mcp_reason("SCOPE_UNAUTHORIZED") == VaultUIState.PROPOSAL_SCOPE_UNAUTHORIZED def test_harmony_view_model_from_delta(self) -> None: delta = { "domain": "knowtation", "summary": "tags changed", "ops": [ {"op": "insert", "address": "frontmatter:tags", "content_summary": "smoke"}, {"op": "replace", "address": "section:2:Summary#1", "content_summary": "updated"}, ], } vm = structured_delta_to_view_model(delta) assert vm["section_count"] == 2 html = harmony_diff_html(vm) assert "vault-harmony-diff" in html assert " None: adapter = KnowtationViewAdapter(smoke_root) if not adapter.mounted: pytest.skip("knowtation MCP plugin not importable") def test_adapter_vault_resource(self, smoke_root: pathlib.Path) -> None: adapter = KnowtationViewAdapter(smoke_root) assert adapter.mounted r = adapter.read_resource("knowtation://vault") assert r.ok assert "notes" in r.data or "stats" in r.data def test_adapter_search_lexical(self, smoke_root: pathlib.Path) -> None: adapter = KnowtationViewAdapter(smoke_root) r = adapter.search("overview", mode="lexical", top_k=5) assert r.ok assert isinstance(r.data.get("results"), list) def test_adapter_get_note_and_impact(self, smoke_root: pathlib.Path) -> None: adapter = KnowtationViewAdapter(smoke_root) note = adapter.get_note(SMOKE_NOTE) assert note.ok impact = adapter.impact(SMOKE_NOTE) assert impact.ok def test_intel_strip_loader(self, smoke_root: pathlib.Path) -> None: adapter = KnowtationViewAdapter(smoke_root) strip = adapter.load_intel_strip() assert "dead" in strip assert "prime" in strip def test_propose_stub(self, smoke_root: pathlib.Path) -> None: try: import muse.plugins.knowtation.mcp_helpers as mcp_helpers except ImportError: pytest.skip("knowtation mcp_helpers not importable") class _Resp: def read(self, n: int = -1) -> bytes: return b'{"id":"p1","status":"created","proposal_id":"p1"}' def __enter__(self): return self def __exit__(self, *a: object) -> None: return None with patch.object(mcp_helpers.urllib.request, "urlopen", return_value=_Resp()): adapter = KnowtationViewAdapter(smoke_root) r = adapter.propose(SMOKE_NOTE, title="T", scope="personal") assert r.ok or r.ui_state is not None # --------------------------------------------------------------------------- # Tier 3 — E2E (HTTP + Playwright browser) # --------------------------------------------------------------------------- @pytest.mark.tier3 async def test_vault_routes_gate_non_vault_404( client: AsyncClient, db_session: AsyncSession, ) -> None: from datetime import datetime, timezone from musehub.core.genesis import compute_identity_id, compute_repo_id from musehub.db.musehub_repo_models import MusehubRepo created = datetime.now(tz=timezone.utc) owner = "codeonly" slug = "code-repo" owner_id = compute_identity_id(owner.encode()) repo = MusehubRepo( repo_id=compute_repo_id(owner_id, slug, "code", created.isoformat()), name=slug, owner=owner, slug=slug, visibility="public", owner_user_id=owner_id, created_at=created, updated_at=created, ) db_session.add(repo) await db_session.commit() resp = await client.get(f"/{owner}/{slug}/vault/search") assert resp.status_code == 404 @pytest.mark.tier3 async def test_vault_search_json_shape( client: AsyncClient, db_session: AsyncSession, smoke_root: pathlib.Path, monkeypatch: pytest.MonkeyPatch, tmp_path: pathlib.Path, ) -> None: repo, _ = await _seed_knowtation_repo(db_session) repo_dir = tmp_path / repo.owner / repo.slug repo_dir.mkdir(parents=True) # Symlink .muse from smoke vault for MCP mount (repo_dir / ".muse").symlink_to(smoke_root / ".muse") for md in smoke_root.rglob("*.md"): rel = md.relative_to(smoke_root) dest = repo_dir / rel dest.parent.mkdir(parents=True, exist_ok=True) dest.write_text(md.read_text(encoding="utf-8"), encoding="utf-8") (repo_dir / ".museattributes").write_text( (smoke_root / ".museattributes").read_text(encoding="utf-8"), encoding="utf-8", ) monkeypatch.setattr( "musehub.services.knowtation_view.repo_root.resolve_repo_root", lambda o, s: repo_dir, ) resp = await client.get( f"/{repo.owner}/{repo.slug}/vault/search", params={"q": "overview", "format": "json"}, ) assert resp.status_code == 200 data = resp.json() assert "results" in data assert "mode" in data @pytest.mark.tier3 def test_playwright_vault_landing_and_note( smoke_root: pathlib.Path, tmp_path: pathlib.Path, ) -> None: pytest.importorskip("playwright.sync_api") from playwright.sync_api import sync_playwright note_raw = (smoke_root / SMOKE_NOTE).read_text(encoding="utf-8") fm, body_html = render_note_html( note_raw, owner="play", repo_slug="vault", note_paths=frozenset({SMOKE_NOTE, "projects/kd-smoke/linked-target.md"}), ) landing_html = f"""
Resume here
Dead notes
  • {SMOKE_NOTE}
""" note_html = f"""
{body_html}
""" search_html = """ """ diff_html = harmony_diff_html( structured_delta_to_view_model( {"domain": "knowtation", "summary": "s", "ops": [{"op": "insert", "address": "section:2:Summary#1", "content_summary": "x"}]} ) ) proposal_html = f'
{diff_html}
' with sync_playwright() as p: browser = p.chromium.launch(headless=True) page = browser.new_page() page.set_content(landing_html) assert page.locator(".vault-prime-card").is_visible() assert page.locator(".vault-intel-strip").is_visible() page.set_content(note_html) assert page.locator(".vault-note-body").is_visible() assert "Summary" in page.locator(".vault-note-body").inner_text() page.set_content(search_html) assert page.locator(".vault-search-card a").is_visible() page.set_content(proposal_html) assert page.locator(".vault-harmony-diff").is_visible() browser.close() # --------------------------------------------------------------------------- # Tier 4 — Stress # --------------------------------------------------------------------------- @pytest.mark.tier4 def test_search_top_k_clamp(smoke_root: pathlib.Path) -> None: adapter = KnowtationViewAdapter(smoke_root) if not adapter.mounted: pytest.skip("knowtation MCP plugin not importable") r = adapter.search("note", mode="lexical", top_k=100) assert r.ok results = r.data.get("results") or [] assert len(results) <= 100 # --------------------------------------------------------------------------- # Tier 5 — Data integrity # --------------------------------------------------------------------------- @pytest.mark.tier5 def test_search_scores_bounded(smoke_root: pathlib.Path) -> None: adapter = KnowtationViewAdapter(smoke_root) if not adapter.mounted: pytest.skip("knowtation MCP plugin not importable") r = adapter.search("overview", mode="lexical", top_k=10) assert r.ok for hit in r.data.get("results") or []: if "score" in hit: assert 0.0 <= float(hit["score"]) <= 1.0 @pytest.mark.tier5 def test_get_note_hash_matches_bytes(smoke_root: pathlib.Path) -> None: adapter = KnowtationViewAdapter(smoke_root) if not adapter.mounted: pytest.skip("knowtation MCP plugin not importable") r = adapter.get_note(SMOKE_NOTE) assert r.ok on_disk = (smoke_root / SMOKE_NOTE).read_bytes() import hashlib assert r.data.get("content_hash") == hashlib.sha256(on_disk).hexdigest() # --------------------------------------------------------------------------- # Tier 6 — Performance # --------------------------------------------------------------------------- @pytest.mark.tier6 def test_search_lexical_under_2s(smoke_root: pathlib.Path) -> None: adapter = KnowtationViewAdapter(smoke_root) if not adapter.mounted: pytest.skip("knowtation MCP plugin not importable") import time t0 = time.monotonic() adapter.search("overview", mode="lexical", top_k=10) assert time.monotonic() - t0 < 2.0 # --------------------------------------------------------------------------- # Tier 7 — Security # --------------------------------------------------------------------------- @pytest.mark.tier7 async def test_note_path_traversal_400( client: AsyncClient, db_session: AsyncSession, tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch, smoke_root: pathlib.Path, ) -> None: repo, _ = await _seed_knowtation_repo(db_session, owner="sec", slug="vault-sec") repo_dir = tmp_path / repo.owner / repo.slug repo_dir.mkdir(parents=True) monkeypatch.setattr( "musehub.services.knowtation_view.repo_root.resolve_repo_root", lambda o, s: repo_dir, ) resp = await client.get(f"/{repo.owner}/{repo.slug}/note/../../etc/passwd") assert resp.status_code in (400, 404) @pytest.mark.tier7 def test_html_never_contains_hub_port(smoke_root: pathlib.Path) -> None: adapter = KnowtationViewAdapter(smoke_root) if not adapter.mounted: pytest.skip("knowtation MCP plugin not importable") r = adapter.search("x", mode="lexical") text = str(r.data) assert "KNOWTATION_HUB_PORT" not in text assert "MUSE_ICP_IDENTITY_PEM" not in text