started model tests

This commit is contained in:
John Lancaster
2026-06-21 17:09:03 -05:00
parent 37fa9b6c6f
commit c189677717
7 changed files with 582 additions and 11 deletions
@@ -0,0 +1,171 @@
from __future__ import annotations
from pathlib import PurePosixPath
from typing import Any
import pytest
import yaml
from pydantic import ValidationError
from personal_mcp.registry.ingest.document import MarkdownDocument
from personal_mcp.registry.load import _parse_frontmatter
from personal_mcp.registry.models.common import _normalize_docs_path
from personal_mcp.registry.models.registry import DocsRegistry
pytestmark = pytest.mark.unit
def make_markdown_document(
relpath: str,
*,
content: str = "# body\n",
frontmatter: str | None = None,
) -> MarkdownDocument:
"""Builds a markdown ingest document with deterministic defaults."""
return MarkdownDocument(
relpath=PurePosixPath(relpath),
content=content,
frontmatter=frontmatter,
)
def make_skill_frontmatter_payload(
*,
skill_id: str,
name: str | None = None,
version: str = "1.0.0",
description: str = "demo skill",
tags: tuple[str, ...] = ("testing",),
capabilities: tuple[str, ...] | None = None,
depends_on: tuple[str, ...] = (),
references: dict[str, dict[str, Any]] | None = None,
) -> str:
"""Builds frontmatter payload YAML for skill conversion tests."""
canonical_name = name or skill_id
payload: dict[str, Any] = {
"name": canonical_name,
"description": description,
"x-personal-mcp": {
"id": skill_id,
"version": version,
"tags": list(tags),
"capabilities": list(capabilities or (f"resource://skills/{canonical_name}/document",)),
"depends_on": list(depends_on),
},
}
if references is not None:
payload["x-personal-mcp"]["references"] = references
return yaml.safe_dump(payload, sort_keys=False)
def make_prompt_frontmatter_payload(
*,
prompt_id: str,
name: str | None = None,
version: str = "1.0.0",
description: str = "demo prompt",
tags: tuple[str, ...] = ("testing",),
capabilities: tuple[str, ...] | None = None,
arguments: dict[str, dict[str, Any]] | None = None,
) -> str:
"""Builds frontmatter payload YAML for prompt conversion tests."""
canonical_name = name or prompt_id
payload: dict[str, Any] = {
"name": canonical_name,
"description": description,
"x-personal-mcp": {
"id": prompt_id,
"version": version,
"tags": list(tags),
"capabilities": list(capabilities or (f"resource://prompts/{canonical_name}/document",)),
"arguments": arguments or {},
},
}
return yaml.safe_dump(payload, sort_keys=False)
def as_markdown(frontmatter_yaml: str, *, body: str = "# body\n") -> str:
"""Wraps frontmatter YAML in markdown fence delimiters."""
return f"---\n{frontmatter_yaml.strip()}\n---\n{body}"
def assert_model_is_frozen(instance: Any, *, attr: str, value: Any) -> None:
"""Asserts pydantic frozen model semantics."""
with pytest.raises(ValidationError, match="Instance is frozen"):
setattr(instance, attr, value)
class TestGate1LayoutValidation:
"""Gate 1: source layout and parse shape constraints."""
def test_parse_frontmatter_requires_payload(self) -> None:
"""Ensures missing frontmatter fails before metadata validation."""
with pytest.raises(ValueError, match="missing YAML frontmatter"):
_parse_frontmatter(None, path=PurePosixPath("skills/alpha/SKILL.md"))
def test_parse_frontmatter_requires_mapping(self) -> None:
"""Ensures non-object YAML payloads are rejected at parse time."""
with pytest.raises(TypeError, match="frontmatter must parse to an object"):
_parse_frontmatter("- one\n- two\n", path=PurePosixPath("skills/alpha/SKILL.md"))
def test_parse_frontmatter_accepts_mapping(self) -> None:
"""Ensures valid mapping payload is returned for downstream validation."""
parsed = _parse_frontmatter("name: alpha\n", path=PurePosixPath("skills/alpha/SKILL.md"))
assert parsed == {"name": "alpha"}
class TestGate5ContractValidation:
"""Gate 5: canonical resource-path contract normalization."""
def test_normalize_docs_path_keeps_posix_relative_paths(self) -> None:
"""Ensures docs paths remain normalized before registry publication."""
assert _normalize_docs_path("skills/demo/SKILL.md") == "skills/demo/SKILL.md"
def test_normalize_docs_path_rejects_parent_traversal(self) -> None:
"""Ensures traversal attempts fail contract validation."""
with pytest.raises(ValueError, match="normalized docs-relative path"):
_normalize_docs_path("../outside.md")
class TestGate6FreezeValidation:
"""Gate 6: immutable in-memory registry snapshot semantics."""
def test_docs_registry_copies_mapping_inputs(self) -> None:
"""Ensures registry snapshots are isolated from caller-owned mapping mutations."""
source_docs = {"index.md": "# index\n"}
registry = DocsRegistry(
skills_by_id={},
skills_in_load_order=(),
skills_summary_in_load_order=(),
docs_markdown_by_path=source_docs,
docs_markdown_path_index=("index.md",),
tag_to_skill_ids={},
capability_to_skill_ids={},
prompts_by_id={},
prompts_in_load_order=(),
prompts_summary_in_load_order=(),
tag_to_prompt_ids={},
)
source_docs["other.md"] = "# other\n"
assert "other.md" not in registry.docs_markdown_by_path
def test_docs_registry_instance_is_frozen(self) -> None:
"""Ensures frozen model prevents attribute reassignment."""
registry = DocsRegistry(
skills_by_id={},
skills_in_load_order=(),
skills_summary_in_load_order=(),
docs_markdown_by_path={},
docs_markdown_path_index=(),
tag_to_skill_ids={},
capability_to_skill_ids={},
prompts_by_id={},
prompts_in_load_order=(),
prompts_summary_in_load_order=(),
tag_to_prompt_ids={},
)
assert_model_is_frozen(registry, attr="docs_markdown_path_index", value=("index.md",))