from __future__ import annotations
from pathlib import Path
import pytest
from scribe_mcp.doc_management.manager import apply_doc_change
from scribe_mcp.utils.frontmatter import parse_frontmatter
async def _setup_project(tmp_path: Path) -> dict:
project_root = tmp_path / "frontmatter_repo"
docs_dir = project_root / ".scribe" / "docs" / "dev_plans" / "test_project"
docs_dir.mkdir(parents=True, exist_ok=True)
architecture_path = docs_dir / "ARCHITECTURE_GUIDE.md"
architecture_path.write_text(
"# Title\n\nBody\n",
encoding="utf-8",
)
(docs_dir / "PHASE_PLAN.md").write_text("# Phase\n", encoding="utf-8")
(docs_dir / "CHECKLIST.md").write_text("# Checklist\n", encoding="utf-8")
(docs_dir / "PROGRESS_LOG.md").write_text("# Log\n", encoding="utf-8")
return {
"name": "Frontmatter Project",
"root": str(project_root),
"docs_dir": str(docs_dir),
"progress_log": str(docs_dir / "PROGRESS_LOG.md"),
"docs": {
"architecture": str(architecture_path),
"phase_plan": str(docs_dir / "PHASE_PLAN.md"),
"checklist": str(docs_dir / "CHECKLIST.md"),
"progress_log": str(docs_dir / "PROGRESS_LOG.md"),
},
"defaults": {"agent": "QA Bot"},
}
@pytest.mark.asyncio
async def test_frontmatter_created_and_updated(tmp_path: Path) -> None:
project = await _setup_project(tmp_path)
path = Path(project["docs"]["architecture"])
change = await apply_doc_change(
project,
doc="architecture",
action="replace_range",
section=None,
content="# Title\n\nBody updated\n",
patch=None,
patch_source_hash=None,
start_line=1,
end_line=3,
template=None,
metadata={},
dry_run=False,
)
assert change.success
text = path.read_text(encoding="utf-8")
parsed = parse_frontmatter(text)
assert parsed.has_frontmatter
assert parsed.frontmatter_data.get("title") == "Title"
assert parsed.frontmatter_data.get("doc_type") == "architecture"
assert parsed.frontmatter_data.get("last_updated")
@pytest.mark.asyncio
async def test_frontmatter_preserves_custom_fields(tmp_path: Path) -> None:
project = await _setup_project(tmp_path)
path = Path(project["docs"]["architecture"])
path.write_text(
"\n".join(
[
"---",
"id: custom-id",
"title: \"Custom Title\"",
"doc_type: architecture",
"custom_field: 123",
"---",
"# Custom Title",
"",
"Body",
"",
]
),
encoding="utf-8",
)
change = await apply_doc_change(
project,
doc="architecture",
action="replace_range",
section=None,
content="# Custom Title\n\nBody updated\n",
patch=None,
patch_source_hash=None,
start_line=1,
end_line=3,
template=None,
metadata={},
dry_run=False,
)
assert change.success
parsed = parse_frontmatter(path.read_text(encoding="utf-8"))
assert parsed.frontmatter_data.get("id") == "custom-id"
assert parsed.frontmatter_data.get("custom_field") == 123
assert parsed.frontmatter_data.get("last_updated")
@pytest.mark.asyncio
async def test_frontmatter_explicit_updates(tmp_path: Path) -> None:
project = await _setup_project(tmp_path)
path = Path(project["docs"]["architecture"])
change = await apply_doc_change(
project,
doc="architecture",
action="replace_range",
section=None,
content="# Title\n\nBody updated\n",
patch=None,
patch_source_hash=None,
start_line=1,
end_line=3,
template=None,
metadata={"frontmatter": {"status": "authoritative"}},
dry_run=False,
)
assert change.success
parsed = parse_frontmatter(path.read_text(encoding="utf-8"))
assert parsed.frontmatter_data.get("status") == "authoritative"
@pytest.mark.asyncio
async def test_replace_range_uses_body_relative_lines(tmp_path: Path) -> None:
project = await _setup_project(tmp_path)
path = Path(project["docs"]["architecture"])
path.write_text(
"\n".join(
[
"---",
"id: body-relative-test",
"title: \"Body Relative\"",
"doc_type: architecture",
"---",
"# Body Relative",
"",
"Line A",
"Line B",
"",
]
),
encoding="utf-8",
)
change = await apply_doc_change(
project,
doc="architecture",
action="replace_range",
section=None,
content="# Body Relative\n\nLine A Updated\nLine B\n",
patch=None,
patch_source_hash=None,
start_line=1,
end_line=4,
template=None,
metadata={},
dry_run=False,
)
assert change.success
parsed = parse_frontmatter(path.read_text(encoding="utf-8"))
assert parsed.frontmatter_data.get("id") == "body-relative-test"
assert "Line A Updated" in parsed.body