generated from coulomb/repo-seed
168 lines
5.4 KiB
Python
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"
|