Files
markitect-tool/tests/test_template_generation.py

168 lines
5.4 KiB
Python

from pathlib import Path
import pytest
from click.testing import CliRunner
from markitect_tool.cli import main
from markitect_tool.contract import load_contract_file
from markitect_tool.generation import (
GenerationHookRequest,
GenerationHookResult,
generate_stub_from_contract,
generate_with_hook,
load_generation_plan_file,
run_generation_plan,
)
from markitect_tool.template import (
MissingTemplateVariable,
analyze_template,
render_template,
)
def test_analyze_template_extracts_nested_and_unicode_variables():
analysis = analyze_template("Hello {{customer.name}}, café {{café.price}}")
assert analysis.valid
assert analysis.variables == ["customer.name", "café.price"]
assert analysis.root_variables == ["customer", "café"]
assert analysis.nested_variables == ["customer.name", "café.price"]
assert analysis.max_nesting_depth == 2
def test_analyze_template_reports_invalid_syntax():
analysis = analyze_template("Valid {{name}} but invalid {{1bad}} and {{missing")
assert not analysis.valid
assert "name" in analysis.variables
assert len(analysis.syntax_errors) == 2
def test_render_template_strict_and_lenient_modes():
template = "# {{title}}\n\nOwner: {{owner.name}}\n\n{{items}}"
data = {"title": "Plan", "owner": {"name": "Ada"}, "items": ["one", "two"]}
result = render_template(template, data)
assert result.complete
assert "# Plan" in result.markdown
assert "Owner: Ada" in result.markdown
assert "- one\n- two" in result.markdown
with pytest.raises(MissingTemplateVariable):
render_template("{{missing}}", {}, strict=True)
lenient = render_template("{{missing}}", {}, strict=False)
assert lenient.markdown == "{{missing}}"
assert lenient.missing_variables == ["missing"]
def test_generate_stub_from_contract_uses_sections_and_guidance():
contract = load_contract_file("examples/contracts/adr.contract.md")
result = generate_stub_from_contract(contract, data={"status": "proposed"})
assert "document_type: adr" in result.markdown
assert "status: proposed" in result.markdown
assert "# Architecture Decision Record" in result.markdown
assert "## Context" in result.markdown
assert "TODO: Explain why the decision exists." in result.markdown
assert "Deprecated Approach" not in result.markdown
def test_generation_plan_renders_and_writes_outputs(tmp_path: Path):
template = tmp_path / "letter.md"
data = tmp_path / "data.yaml"
rules = tmp_path / "rules.md"
template.write_text("# Hello {{person.name}}\n\n{{message}}", encoding="utf-8")
data.write_text("person:\n name: Ada\nmessage: Welcome.\n", encoding="utf-8")
rules.write_text(
"""# Generation Rules
```yaml generation
template: letter.md
data_file: data.yaml
output: out/letter.md
```
""",
encoding="utf-8",
)
plan = load_generation_plan_file(rules)
result = run_generation_plan(plan, base_dir=tmp_path, output_dir=tmp_path)
assert result.documents[0].markdown == "# Hello Ada\n\nWelcome."
assert (tmp_path / "out" / "letter.md").read_text(encoding="utf-8") == "# Hello Ada\n\nWelcome."
def test_generation_hook_boundary_accepts_external_provider():
class FakeHook:
def generate(self, request: GenerationHookRequest) -> GenerationHookResult:
return GenerationHookResult(
markdown=f"# {request.data['title']}\n\n{request.prompt}",
provider="fake",
)
result = generate_with_hook(
GenerationHookRequest(prompt="Draft this deterministically in the test.", data={"title": "Hook"}),
FakeHook(),
)
assert result.provider == "fake"
assert result.markdown.startswith("# Hook")
def test_mkt_template_render_outputs_markdown(tmp_path: Path):
template = tmp_path / "template.md"
data = tmp_path / "data.json"
template.write_text("# {{title}}\n", encoding="utf-8")
data.write_text('{"title": "Rendered"}', encoding="utf-8")
result = CliRunner().invoke(main, ["template", "render", str(template), "--data", str(data)])
assert result.exit_code == 0
assert result.output == "# Rendered\n"
def test_mkt_template_inspect_outputs_text(tmp_path: Path):
template = tmp_path / "template.md"
template.write_text("# {{title}}\n\n{{owner.name}}", encoding="utf-8")
result = CliRunner().invoke(main, ["template", "inspect", str(template)])
assert result.exit_code == 0
assert "variables: 2" in result.output
assert "owner.name" in result.output
def test_mkt_generate_stub_outputs_contract_stub():
result = CliRunner().invoke(
main,
["generate", "stub", "--contract", "examples/contracts/adr.contract.md", "--set", "status=accepted"],
)
assert result.exit_code == 0
assert "status: accepted" in result.output
assert "## Decision" in result.output
def test_mkt_generate_rules_writes_file(tmp_path: Path):
template = tmp_path / "template.md"
rules = tmp_path / "rules.md"
template.write_text("# {{title}}\n", encoding="utf-8")
rules.write_text(
"""```yaml generation
template: template.md
data:
title: From Rules
output: generated.md
```""",
encoding="utf-8",
)
result = CliRunner().invoke(main, ["generate", "rules", str(rules), "--output-dir", str(tmp_path)])
assert result.exit_code == 0
assert '"count": 1' in result.output
assert (tmp_path / "generated.md").read_text(encoding="utf-8") == "# From Rules\n"