Files

136 lines
4.7 KiB
Python

"""Unit tests for engine.template."""
from __future__ import annotations
import random
import pytest
from engine.template import resolve_variables
def _ctx(**kwargs):
"""Return a minimal ctx dict with required engine keys + extra kwargs."""
return {
"__wp_rng__": random.Random(0),
"__wp_warnings__": [],
**kwargs,
}
class TestResolveVariables:
def test_basic_substitution(self):
assert resolve_variables("$name", _ctx(name="Alice")) == "Alice"
def test_missing_var_dropped(self):
# Missing vars resolve to empty string so the prompt sent to the
# model never contains literal "$unknown" tokens.
assert resolve_variables("$unknown", _ctx()) == ""
def test_missing_var_collapses_surrounding_whitespace(self):
assert resolve_variables("a $missing b", _ctx()) == "a b"
def test_missing_var_collapses_comma_gap(self):
assert resolve_variables("photo, $missing, cinematic", _ctx()) == "photo, cinematic"
def test_missing_var_trims_trailing_punct(self):
assert resolve_variables("portrait of $subject.", _ctx()) == "portrait of."
def test_dollar_escape(self):
assert resolve_variables("$$name", _ctx(name="Alice")) == "$name"
def test_dollar_escape_twice(self):
assert resolve_variables("$$$$", _ctx()) == "$$"
def test_internal_key_never_substituted(self):
# Phase 5: internal keys ($__seed) resolve to "" (not literal "$__seed").
# The old ad-hoc regex kept them verbatim; resolve_text drops them silently.
# Callers must never embed internal keys in templates — this is engine policy.
assert resolve_variables("$__seed", _ctx()) == ""
def test_mixed(self):
ctx = _ctx(name="Alice", count=3)
assert resolve_variables(
"Hi $name — you have $$ $count items",
ctx,
) == "Hi Alice — you have $ 3 items"
def test_non_string_value_casts_to_str(self):
assert resolve_variables("$n", _ctx(n=42)) == "42"
assert resolve_variables("$b", _ctx(b=True)) == "true"
def test_word_boundary_stops_at_non_identifier(self):
assert resolve_variables("$name.", _ctx(name="A")) == "A."
def test_adjacent_identifier_chars_extend_name(self):
assert resolve_variables(
"$name1", _ctx(name="A", name1="B")
) == "B"
def test_empty_template(self):
assert resolve_variables("", _ctx(a="x")) == ""
def test_no_dollar_no_change(self):
assert resolve_variables("plain text", _ctx(x="y")) == "plain text"
@pytest.mark.parametrize(
("template", "expected"),
[
("$$", "$"),
("$$$name", "$Alice"),
("$name$$", "Alice$"),
],
)
def test_escape_combinations(self, template, expected):
assert resolve_variables(template, _ctx(name="Alice")) == expected
def test_resolve_variables_leaves_inline_pick_literal_on_assembler():
# The assembler surface is seedless (always rolls from seed 0), so an
# inline pick there would freeze deterministically — misleading. Engine
# policy: render `{a|b}` verbatim and let the user produce the random
# value in a seeded module, referencing its $var. $var substitution
# still happens, so only the brace block stays literal.
ctx = {"__wp_rng__": random.Random(42), "__wp_warnings__": [], "color": "red"}
out = resolve_variables("$color {a|b}", ctx)
assert out == "red {a|b}"
def test_resolve_variables_leaves_multi_pick_literal_on_assembler():
# Same rule for the multi-pick `{N$$sep$$...}` form.
ctx = {"__wp_rng__": random.Random(42), "__wp_warnings__": []}
assert resolve_variables("{2$$, $$a|b|c}", ctx) == "{2$$, $$a|b|c}"
def test_resolve_variables_legacy_dollar_var_unchanged():
ctx = {
"__wp_rng__": random.Random(0),
"__wp_warnings__": [],
"style": "anime",
"subject": "girl",
}
assert resolve_variables("a $style $subject", ctx) == "a anime girl"
@pytest.mark.parametrize("value", [
"",
" ",
None,
"LISTVAR_EMPTY",
"LISTVAR_BLANK",
])
def test_empty_value_drops_the_dangling_joining_word(value):
from engine.syntax.types import ListVar
if value == "LISTVAR_EMPTY":
value = ListVar([], ", ")
elif value == "LISTVAR_BLANK":
value = ListVar(["", ""], "")
ctx = _ctx() if value is None else _ctx(props=value)
assert resolve_variables("a knight with $props", ctx) == "a knight"
def test_non_empty_multi_pick_keeps_the_joining_word():
from engine.syntax.types import ListVar
ctx = _ctx(props=ListVar(["sword", "shield"], " and "))
assert resolve_variables("a knight with $props", ctx) == "a knight with sword and shield"