generated from coulomb/repo-seed
extension for ref resolve, explode, implode, weave, tangle
This commit is contained in:
195
tests/test_reference_resolution.py
Normal file
195
tests/test_reference_resolution.py
Normal file
@@ -0,0 +1,195 @@
|
||||
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
|
||||
Reference in New Issue
Block a user