generated from coulomb/repo-seed
196 lines
7.1 KiB
Python
196 lines
7.1 KiB
Python
from pathlib import Path
|
|
|
|
import pytest
|
|
from click.testing import CliRunner
|
|
|
|
from markitect_tool.cli import main
|
|
from markitect_tool.core import parse_markdown
|
|
from markitect_tool.reference import (
|
|
ReferenceContext,
|
|
ReferenceResolutionError,
|
|
load_namespaces,
|
|
parse_reference,
|
|
resolve_reference,
|
|
)
|
|
|
|
|
|
def test_parse_reference_splits_namespace_fragment_and_selector():
|
|
address = parse_reference("std:clauses/payment.md#section:fees::blocks[type=code]")
|
|
|
|
assert address.namespace == "std"
|
|
assert address.address == "clauses/payment.md"
|
|
assert address.fragment == "section:fees"
|
|
assert address.selector == "blocks[type=code]"
|
|
|
|
|
|
def test_load_namespaces_accepts_optional_colon_suffix():
|
|
namespaces = load_namespaces({"namespaces": {"std:": "./standard", "src": "../src"}})
|
|
|
|
assert namespaces == {"std": "./standard", "src": "../src"}
|
|
|
|
|
|
def test_resolve_path_reference_returns_document_unit(tmp_path: Path):
|
|
context_file = tmp_path / "context.md"
|
|
target_file = tmp_path / "target.md"
|
|
context_file.write_text("# Context\n", encoding="utf-8")
|
|
target_file.write_text("---\nid: target-doc\ntitle: Target\n---\n\n# Target\n\nBody.", encoding="utf-8")
|
|
context = ReferenceContext(root=tmp_path, current_path=context_file)
|
|
|
|
resolution = resolve_reference("target.md", context=context)
|
|
|
|
assert resolution.target_path == str(target_file.resolve())
|
|
assert len(resolution.units) == 1
|
|
assert resolution.units[0].kind == "document"
|
|
assert resolution.units[0].unit_id == "target-doc"
|
|
assert "# Target" in resolution.units[0].text
|
|
|
|
|
|
def test_resolve_namespace_reference_and_explicit_section_id(tmp_path: Path):
|
|
standard = tmp_path / "standard"
|
|
standard.mkdir()
|
|
context_file = tmp_path / "context.md"
|
|
clause_file = standard / "clauses.md"
|
|
context_file.write_text(
|
|
"---\nnamespaces:\n std: ./standard\n---\n\n# Context\n",
|
|
encoding="utf-8",
|
|
)
|
|
clause_file.write_text(
|
|
"# Clauses\n\n## Payment Terms {#payment-terms}\n\nPay within 30 days.\n",
|
|
encoding="utf-8",
|
|
)
|
|
document = parse_markdown(context_file.read_text(encoding="utf-8"), source_path=str(context_file))
|
|
context = ReferenceContext.from_document(document, root=tmp_path)
|
|
|
|
resolution = resolve_reference("std:clauses.md#section:payment-terms", context=context)
|
|
|
|
assert resolution.units[0].kind == "section"
|
|
assert resolution.units[0].unit_id == "payment-terms"
|
|
assert resolution.units[0].name == "Payment Terms"
|
|
assert "Pay within 30 days" in resolution.units[0].text
|
|
|
|
|
|
def test_resolve_selector_reference_uses_existing_query_engine(tmp_path: Path):
|
|
standard = tmp_path / "standard"
|
|
standard.mkdir()
|
|
context_file = tmp_path / "context.md"
|
|
source_file = standard / "clauses.md"
|
|
context_file.write_text(
|
|
"---\nnamespaces:\n std: ./standard\n---\n\n# Context\n",
|
|
encoding="utf-8",
|
|
)
|
|
source_file.write_text(
|
|
"# Clauses\n\n## Warranty\n\nWarranty text.\n\n## Liability\n\nLiability text.\n",
|
|
encoding="utf-8",
|
|
)
|
|
context = ReferenceContext.from_document(parse_markdown(context_file.read_text(encoding="utf-8"), str(context_file)), root=tmp_path)
|
|
|
|
resolution = resolve_reference("std:clauses.md::sections[heading=Warranty]", context=context)
|
|
|
|
assert [unit.kind for unit in resolution.units] == ["section"]
|
|
assert resolution.units[0].name == "Warranty"
|
|
assert "Liability" not in resolution.units[0].text
|
|
|
|
|
|
def test_resolve_pathless_fragment_uses_current_document(tmp_path: Path):
|
|
context_file = tmp_path / "context.md"
|
|
context_file.write_text("# Context\n\n## Overview\n\nUseful local context.\n", encoding="utf-8")
|
|
context = ReferenceContext(root=tmp_path, current_path=context_file)
|
|
|
|
resolution = resolve_reference("#overview", context=context)
|
|
|
|
assert resolution.target_path == str(context_file.resolve())
|
|
assert resolution.units[0].kind == "section"
|
|
assert resolution.units[0].unit_id == "overview"
|
|
assert "Useful local context" in resolution.units[0].text
|
|
|
|
|
|
def test_resolve_named_region_by_id_and_tag(tmp_path: Path):
|
|
context_file = tmp_path / "context.md"
|
|
context_file.write_text(
|
|
"""# Context
|
|
|
|
<!-- mkt:region id="overview" tags="reuse summary" -->
|
|
Reusable region text.
|
|
<!-- /mkt:region -->
|
|
""",
|
|
encoding="utf-8",
|
|
)
|
|
context = ReferenceContext(root=tmp_path, current_path=context_file)
|
|
|
|
by_id = resolve_reference("#region:overview", context=context)
|
|
by_tag = resolve_reference("#tag:summary", context=context)
|
|
|
|
assert by_id.units[0].kind == "region"
|
|
assert by_id.units[0].text == "Reusable region text."
|
|
assert by_tag.units[0].unit_id == "overview"
|
|
|
|
|
|
def test_resolve_fenced_block_by_id(tmp_path: Path):
|
|
context_file = tmp_path / "context.md"
|
|
context_file.write_text(
|
|
"""# Context
|
|
|
|
```python {#load-config tags="code setup" tangle="src/config.py"}
|
|
def load_config():
|
|
return {}
|
|
```
|
|
""",
|
|
encoding="utf-8",
|
|
)
|
|
context = ReferenceContext(root=tmp_path, current_path=context_file)
|
|
|
|
resolution = resolve_reference("#fence:load-config", context=context)
|
|
|
|
assert resolution.units[0].kind == "fenced_block"
|
|
assert resolution.units[0].unit_id == "load-config"
|
|
assert resolution.units[0].metadata["language"] == "python"
|
|
assert resolution.units[0].metadata["attrs"]["tangle"] == "src/config.py"
|
|
assert "def load_config" in resolution.units[0].text
|
|
|
|
|
|
def test_resolve_line_range_fragment(tmp_path: Path):
|
|
context_file = tmp_path / "context.md"
|
|
context_file.write_text("# Context\n\nLine A\nLine B\nLine C\n", encoding="utf-8")
|
|
context = ReferenceContext(root=tmp_path, current_path=context_file)
|
|
|
|
resolution = resolve_reference("#line:3-4", context=context)
|
|
|
|
assert resolution.units[0].kind == "line_range"
|
|
assert resolution.units[0].span.line_start == 3
|
|
assert resolution.units[0].text == "Line A\nLine B"
|
|
|
|
|
|
def test_resolve_rejects_unknown_namespace(tmp_path: Path):
|
|
context_file = tmp_path / "context.md"
|
|
context_file.write_text("# Context\n", encoding="utf-8")
|
|
context = ReferenceContext(root=tmp_path, current_path=context_file)
|
|
|
|
with pytest.raises(ReferenceResolutionError, match="Unknown namespace"):
|
|
resolve_reference("missing:doc.md", context=context)
|
|
|
|
|
|
def test_resolve_rejects_paths_outside_root(tmp_path: Path):
|
|
context_file = tmp_path / "context.md"
|
|
context_file.write_text("# Context\n", encoding="utf-8")
|
|
context = ReferenceContext(root=tmp_path, current_path=context_file)
|
|
|
|
with pytest.raises(ReferenceResolutionError, match="escapes root"):
|
|
resolve_reference("../outside.md", context=context)
|
|
|
|
|
|
def test_mkt_ref_resolve_outputs_text(tmp_path: Path):
|
|
context_file = tmp_path / "context.md"
|
|
target_file = tmp_path / "target.md"
|
|
context_file.write_text("# Context\n", encoding="utf-8")
|
|
target_file.write_text("# Target\n\n## Decision\n\nChosen.", encoding="utf-8")
|
|
|
|
result = CliRunner().invoke(
|
|
main,
|
|
["ref", "resolve", str(context_file), "target.md#decision", "--root", str(tmp_path)],
|
|
)
|
|
|
|
assert result.exit_code == 0
|
|
assert "1 unit(s)" in result.output
|
|
assert "section decision" in result.output
|
|
assert "Decision" in result.output
|