generated from coulomb/repo-seed
Deterministic templating and generation support
This commit is contained in:
167
tests/test_template_generation.py
Normal file
167
tests/test_template_generation.py
Normal file
@@ -0,0 +1,167 @@
|
||||
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"
|
||||
Reference in New Issue
Block a user