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 Reusable region text. """, 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