structured tests

This commit is contained in:
John Lancaster
2026-06-20 20:36:44 -05:00
parent 82b50fb63b
commit c653c7024b
3 changed files with 369 additions and 0 deletions
@@ -0,0 +1,131 @@
from __future__ import annotations
from pathlib import Path
import pytest
from personal_mcp.catalog.server import (
build_prompt_detail_payload,
build_prompts_index_payload,
)
from personal_mcp.skills import document_loader
def _write_skill(root: Path, *, skill_id: str) -> None:
skill_dir = root / "docs" / "skills" / skill_id
skill_dir.mkdir(parents=True, exist_ok=True)
skill_doc = "\n".join(
[
"---",
f"name: {skill_id}",
"description: Example skill",
"x-personal-mcp:",
f" id: {skill_id}",
" version: 1.0.0",
" tags: [example]",
" capabilities:",
f" - resource://skills/{skill_id}/document",
"---",
"",
f"# {skill_id}",
"",
]
)
(skill_dir / "SKILL.md").write_text(skill_doc, encoding="utf-8")
def _write_frontmatter_prompt(root: Path, *, prompt_id: str) -> None:
prompt_dir = root / "docs" / "prompts" / prompt_id
prompt_dir.mkdir(parents=True, exist_ok=True)
prompt_doc = "\n".join(
[
"---",
f"name: {prompt_id}",
"description: Scaffold initial tests",
"x-personal-mcp:",
f" id: {prompt_id}",
" version: 1.0.0",
" tags: [pytest, testing]",
" capabilities:",
f" - resource://prompts/{prompt_id}/document",
" arguments:",
" target_scope:",
" type: string",
" description: The test scope",
" required: true",
"---",
"",
"Create tests for {{target_scope}}.",
"",
]
)
(prompt_dir / "PROMPT.md").write_text(prompt_doc, encoding="utf-8")
def _write_legacy_prompt(root: Path) -> None:
legacy = root / "docs" / "prompts" / "testing"
legacy.mkdir(parents=True, exist_ok=True)
(legacy / "inital_test_structure.md").write_text(
"# Prompt For Creating Initial Test Structure\n",
encoding="utf-8",
)
def _load_registry(monkeypatch: pytest.MonkeyPatch, root: Path):
monkeypatch.setattr(document_loader, "files", lambda _: root)
return document_loader.load_docs_registry(package_anchor="unused")
def test_loads_frontmatter_prompt_and_arguments(monkeypatch, tmp_path: Path) -> None:
_write_skill(tmp_path, skill_id="demo-skill")
_write_frontmatter_prompt(tmp_path, prompt_id="initial-test-structure")
registry = _load_registry(monkeypatch, tmp_path)
assert "initial-test-structure" in registry.prompts_by_id
prompt = registry.prompts_by_id["initial-test-structure"]
assert prompt.arguments["target_scope"].type == "string"
assert prompt.arguments["target_scope"].required is True
def test_loads_legacy_prompt_markdown(monkeypatch, tmp_path: Path) -> None:
_write_skill(tmp_path, skill_id="demo-skill")
_write_legacy_prompt(tmp_path)
registry = _load_registry(monkeypatch, tmp_path)
assert "inital-test-structure" in registry.prompts_by_id
prompt = registry.prompts_by_id["inital-test-structure"]
assert prompt.document_relpath == "prompts/testing/inital_test_structure.md"
assert "Legacy prompt loaded" in prompt.description
def test_rejects_prompt_skill_id_collision(monkeypatch, tmp_path: Path) -> None:
_write_skill(tmp_path, skill_id="shared-id")
_write_frontmatter_prompt(tmp_path, prompt_id="shared-id")
with pytest.raises(document_loader.DocsRegistryValidationError) as exc:
_load_registry(monkeypatch, tmp_path)
codes = [issue.code for issue in exc.value.errors]
assert "prompt_skill_id_collision" in codes
def test_prompt_catalog_index_and_detail(monkeypatch, tmp_path: Path) -> None:
_write_skill(tmp_path, skill_id="demo-skill")
_write_frontmatter_prompt(tmp_path, prompt_id="initial-test-structure")
_write_frontmatter_prompt(tmp_path, prompt_id="route-tests")
registry = _load_registry(monkeypatch, tmp_path)
index = build_prompts_index_payload(
registry,
query="initial-test-structure",
limit=10,
)
assert index["total"] == 1
assert index["prompts"][0]["id"] == "initial-test-structure"
detail = build_prompt_detail_payload(registry, "initial-test-structure")
assert "target_scope" in detail["arguments"]
assert detail["arguments"]["target_scope"]["required"] is True
@@ -0,0 +1,94 @@
from __future__ import annotations
import asyncio
import pytest
from personal_mcp import mcp as mcp_module
def _listed_tool_names() -> set[str]:
tools = asyncio.run(mcp_module.mcp.list_tools())
return {tool.name for tool in tools}
@pytest.fixture
def listed_tool_names() -> set[str]:
return _listed_tool_names()
def test_canonical_step6_tools_are_listed(listed_tool_names: set[str]) -> None:
assert {
"list_resources",
"read_resource",
"search_patterns",
"get_pattern_by_id",
"get_skill_document_by_id",
"search_prompts",
"get_prompt_by_id",
}.issubset(listed_tool_names)
def test_catalog_compatibility_alias_tools_are_listed(
listed_tool_names: set[str],
) -> None:
assert {
"catalog_search_patterns",
"catalog_get_pattern_by_id",
"catalog_get_skill_document_by_id",
"catalog_search_prompts",
"catalog_get_prompt_by_id",
}.issubset(listed_tool_names)
@pytest.mark.parametrize(
"skill_id",
[mcp_module.REGISTRY.skills_in_load_order[0], "missing-step6-skill"],
)
def test_skill_document_alias_matches_canonical_payload(skill_id: str) -> None:
canonical = mcp_module.get_skill_document_by_id(skill_id)
alias = mcp_module.catalog_get_skill_document_by_id(skill_id)
assert alias == canonical
def test_search_and_pattern_aliases_match_canonical_payloads() -> None:
search_canonical = mcp_module.search_patterns(
query="mcp",
tags=[],
skip=0,
limit=5,
)
search_alias = mcp_module.catalog_search_patterns(
query="mcp",
tags=[],
skip=0,
limit=5,
)
assert search_alias == search_canonical
sample_skill_id = mcp_module.REGISTRY.skills_in_load_order[0]
pattern_canonical = mcp_module.get_pattern_by_id(sample_skill_id)
pattern_alias = mcp_module.catalog_get_pattern_by_id(sample_skill_id)
assert pattern_alias == pattern_canonical
def test_prompt_aliases_match_canonical_payloads() -> None:
search_canonical = mcp_module.search_prompts(
query="test",
tags=[],
skip=0,
limit=5,
)
search_alias = mcp_module.catalog_search_prompts(
query="test",
tags=[],
skip=0,
limit=5,
)
assert search_alias == search_canonical
sample_prompt_id = mcp_module.REGISTRY.prompts_in_load_order[0]
prompt_canonical = mcp_module.get_prompt_by_id(sample_prompt_id)
prompt_alias = mcp_module.catalog_get_prompt_by_id(sample_prompt_id)
assert prompt_alias == prompt_canonical
@@ -0,0 +1,144 @@
from __future__ import annotations
from pathlib import Path
from textwrap import dedent
import pytest
from personal_mcp.skills import document_loader
def _write_skill(
root: Path,
*,
skill_id: str,
references_block: str = "",
) -> Path:
skill_dir = root / "docs" / "skills" / skill_id
skill_dir.mkdir(parents=True, exist_ok=True)
frontmatter_lines = [
"---",
f"name: {skill_id}",
"description: Example skill",
"x-personal-mcp:",
f" id: {skill_id}",
" version: 1.0.0",
" tags: [example]",
" capabilities:",
f" - resource://skills/{skill_id}/document",
]
if references_block:
frontmatter_lines.extend(f" {line}" for line in references_block.splitlines())
frontmatter_lines.append("---")
skill_doc = "\n".join(frontmatter_lines) + f"\n\n# {skill_id}\n"
(skill_dir / "SKILL.md").write_text(skill_doc, encoding="utf-8")
return skill_dir
def _load_registry(monkeypatch: pytest.MonkeyPatch, root: Path):
monkeypatch.setattr(document_loader, "files", lambda _: root)
return document_loader.load_docs_registry(package_anchor="unused")
def test_auto_discovers_top_level_references(monkeypatch, tmp_path: Path) -> None:
skill_dir = _write_skill(tmp_path, skill_id="demo-skill")
refs = skill_dir / "references"
refs.mkdir()
(refs / "index.md").write_text("# Source Map\n", encoding="utf-8")
(refs / "feature-catalog.md").write_text("# Feature Catalog\n", encoding="utf-8")
registry = _load_registry(monkeypatch, tmp_path)
skill = registry.skills_by_id["demo-skill"]
assert set(skill.references) == {"index", "feature-catalog"}
assert skill.references["index"].title == "Index"
assert skill.references["feature-catalog"].title == "Feature Catalog"
def test_explicit_reference_overrides_discovered(monkeypatch, tmp_path: Path) -> None:
references_block = dedent(
"""\
references:
index:
path: references/index.md
mime_type: text/plain
title: Explicit Title
"""
).rstrip()
skill_dir = _write_skill(
tmp_path,
skill_id="override-skill",
references_block=references_block,
)
refs = skill_dir / "references"
refs.mkdir()
(refs / "index.md").write_text("# Source Map\n", encoding="utf-8")
registry = _load_registry(monkeypatch, tmp_path)
record = registry.skills_by_id["override-skill"].references["index"]
assert record.mime_type == "text/plain"
assert record.title == "Explicit Title"
def test_nested_references_are_not_auto_discovered(monkeypatch, tmp_path: Path) -> None:
references_block = dedent(
"""\
references:
architecture-overview:
path: references/nested/architecture-overview.md
title: Architecture Overview
"""
).rstrip()
skill_dir = _write_skill(
tmp_path,
skill_id="nested-skill",
references_block=references_block,
)
refs = skill_dir / "references"
(refs / "nested").mkdir(parents=True)
(refs / "nested" / "architecture-overview.md").write_text(
"# Architecture\n",
encoding="utf-8",
)
registry = _load_registry(monkeypatch, tmp_path)
skill = registry.skills_by_id["nested-skill"]
assert set(skill.references) == {"architecture-overview"}
@pytest.mark.parametrize(
("skill_id", "files", "expected_refs"),
[
(
"non-markdown-skill",
{"guide.txt": "plain text", "guide.md": "# Guide\n"},
{"guide"},
),
(
"normalized-id-skill",
{"implicit_io.md": "# Implicit IO\n"},
{"implicit-io"},
),
],
)
def test_auto_discovery_file_filtering_and_ref_id_normalization(
monkeypatch,
tmp_path: Path,
skill_id: str,
files: dict[str, str],
expected_refs: set[str],
) -> None:
skill_dir = _write_skill(tmp_path, skill_id=skill_id)
refs = skill_dir / "references"
refs.mkdir()
for filename, content in files.items():
(refs / filename).write_text(content, encoding="utf-8")
registry = _load_registry(monkeypatch, tmp_path)
skill = registry.skills_by_id[skill_id]
assert set(skill.references) == expected_refs