Refactor handlers to be in their own classes for easier maintainability

This commit is contained in:
Miguel Jacq 2025-11-27 20:41:10 +11:00
parent d1ca60b779
commit 85f21e739d
Signed by: mig5
GPG key ID: 59B3F0C24135C6A9
19 changed files with 1826 additions and 1463 deletions

114
tests/test_toml_handler.py Normal file
View file

@ -0,0 +1,114 @@
from __future__ import annotations
from pathlib import Path
import pytest
import yaml
from jinjaturtle.core import (
parse_config,
flatten_config,
generate_defaults_yaml,
generate_template,
)
from jinjaturtle.handlers.toml import TomlHandler
import jinjaturtle.handlers.toml as toml_module
SAMPLES_DIR = Path(__file__).parent / "samples"
def test_toml_sample_roundtrip():
toml_path = SAMPLES_DIR / "tom.toml"
assert toml_path.is_file(), f"Missing sample TOML file: {toml_path}"
fmt, parsed = parse_config(toml_path)
assert fmt == "toml"
flat_items = flatten_config(fmt, parsed)
assert flat_items
defaults_yaml = generate_defaults_yaml("jinjaturtle", flat_items)
defaults = yaml.safe_load(defaults_yaml)
# defaults should be a non-empty dict
assert isinstance(defaults, dict)
assert defaults, "Expected non-empty defaults for TOML sample"
# all keys should be lowercase, start with prefix, and have no spaces
for key in defaults:
assert key.startswith("jinjaturtle_")
assert key == key.lower()
assert " " not in key
# template generation **now with original_text**
original_text = toml_path.read_text(encoding="utf-8")
template = generate_template(
fmt, parsed, "jinjaturtle", original_text=original_text
)
assert isinstance(template, str)
assert template.strip()
# comments from the original file should now be preserved
assert "# This is a TOML document" in template
# each default variable name should appear in the template as a Jinja placeholder
for var_name in defaults:
assert (
var_name in template
), f"Variable {var_name} not referenced in TOML template"
def test_parse_config_toml_missing_tomllib(monkeypatch):
"""
Force tomllib to None to hit the RuntimeError branch when parsing TOML.
"""
toml_path = SAMPLES_DIR / "tom.toml"
# Simulate an environment without tomllib/tomli
monkeypatch.setattr(toml_module, "tomllib", None)
with pytest.raises(RuntimeError) as exc:
parse_config(toml_path, fmt="toml")
assert "tomllib/tomli is required" in str(exc.value)
def test_generate_template_fallback_toml():
"""
When original_text is not provided, generate_template should use the
structural fallback path for TOML configs.
"""
parsed_toml = {
"title": "Example",
"server": {"port": 8080, "host": "127.0.0.1"},
"logging": {
"file": {"path": "/tmp/app.log"}
}, # nested table to hit recursive walk
}
tmpl_toml = generate_template("toml", parsed=parsed_toml, role_prefix="role")
assert "[server]" in tmpl_toml
assert "role_server_port" in tmpl_toml
assert "[logging]" in tmpl_toml or "[logging.file]" in tmpl_toml
def test_generate_toml_template_from_text_edge_cases():
# Cover CRLF newlines, lines without '=', empty keys, and inline tables
# that both parse successfully and fail parsing.
text = (
"# comment\r\n"
"[table]\r\n"
"noequals\r\n"
" = 42\r\n"
'inline_good = { name = "abc", value = 1 }\r\n'
"inline_bad = { invalid = }\r\n"
)
handler = TomlHandler()
tmpl = handler._generate_toml_template_from_text("role", text)
# The good inline table should expand into two separate variables.
assert "role_table_inline_good_name" in tmpl
assert "role_table_inline_good_value" in tmpl
# The bad inline table should fall back to scalar handling.
assert "role_table_inline_bad" in tmpl
# Ensure the lines without '=' / empty key were handled without exploding.
assert "[table]" in tmpl
assert "noequals" in tmpl