Files
DumiFlex-ComfyUI-Wildcard-P…/tests/engine/syntax/test_resolve_var_surface.py
T
DumiFlex a8b8a6dfbd feat(engine): gate VAR token by surface — combine/derivation/assembler only
Wildcard + fixed_values surfaces are binding PRODUCERS, not consumers
— they don't read upstream \$var. Combine + derivation + assembler do
(template interpolation is their reason to exist).

Adds VarOutOfSurfaceError to engine.syntax.types + extends SurfaceKind
literal with "fixed_values". _resolve_var checks surface membership
in _VAR_SURFACES_ALLOWED frozenset; out-of-surface refs warn + render
literal \$name in lenient mode (existing default), raise in strict
mode (used by tests + dev mode).

Lenient strategy means workflows saved before this cycle keep
resolving — the warning surfaces in toast / conflict scanner without
crashing the queue.

Test fixture adjustments document the new contract:
- tests/syntax/test_resolve.py default surface flips wildcard -> combine
  (these tests cover VAR resolution mechanics, not surface gating)
- tests/syntax/test_determinism.py corpus single_var + var_in_text
  cases use surface=combine
- tests/engine/modules/test_wildcard_handler.py:
    * test_resolve_var_substitution_in_option renamed to
      test_resolve_var_in_option_renders_literal_warns + asserts new
      lenient path
    * test_resolve_pinned_mode_resolves_inline_syntax swaps \$color for
      a {red|red} alternation that DOES resolve on wildcard surface

Surface gating tests live in tests/engine/syntax/test_resolve_var_surface.py
covering all 5 surfaces + lenient vs strict modes + internal-key skip.
2026-05-08 18:22:48 +03:00

85 lines
2.7 KiB
Python

"""Surface gating for $var token.
Combine, derivation, and assembler surfaces support $var reads.
Wildcard and fixed_values surfaces are binding PRODUCERS, not consumers
— $var reads in those surfaces warn + render literal in lenient mode,
raise VarOutOfSurfaceError in strict mode.
"""
import random
import pytest
from engine.syntax import resolve_text
from engine.syntax.types import VarOutOfSurfaceError
class _FakeCtx:
"""Minimal ResolveContext-shaped object for surface gating tests."""
def __init__(self, surface, strict=False, vars_=None):
self.surface = surface
self.strict = strict
self.developer_mode = False
self.max_ref_depth = 8
self.warnings = []
self._vars = vars_ or {}
self.rng = random.Random(0)
def get_var(self, name):
return self._vars.get(name)
def get_module(self, uuid):
return None
def test_var_resolves_on_combine_surface():
ctx = _FakeCtx("combine", vars_={"name": "world"})
assert resolve_text("hello $name", ctx) == "hello world"
def test_var_resolves_on_derivation_surface():
ctx = _FakeCtx("derivation", vars_={"name": "world"})
assert resolve_text("hello $name", ctx) == "hello world"
def test_var_resolves_on_assembler_surface():
ctx = _FakeCtx("assembler", vars_={"name": "world"})
assert resolve_text("hello $name", ctx) == "hello world"
def test_var_warns_on_wildcard_surface_lenient():
"""Wildcard surface: lenient strategy renders $name literal +
pushes a var_out_of_surface warning."""
ctx = _FakeCtx("wildcard", strict=False, vars_={"name": "world"})
result = resolve_text("hello $name", ctx)
assert result == "hello $name"
assert any(w.get("type") == "var_out_of_surface" for w in ctx.warnings)
def test_var_warns_on_fixed_values_surface_lenient():
ctx = _FakeCtx("fixed_values", strict=False, vars_={"name": "world"})
result = resolve_text("hello $name", ctx)
assert result == "hello $name"
assert any(w.get("type") == "var_out_of_surface" for w in ctx.warnings)
def test_var_raises_on_wildcard_surface_strict():
ctx = _FakeCtx("wildcard", strict=True)
with pytest.raises(VarOutOfSurfaceError):
resolve_text("hello $name", ctx)
def test_var_raises_on_fixed_values_surface_strict():
ctx = _FakeCtx("fixed_values", strict=True)
with pytest.raises(VarOutOfSurfaceError):
resolve_text("hello $name", ctx)
def test_internal_dunder_var_never_warns():
"""__-prefixed vars are engine bookkeeping; never warn even on
surfaces that disallow VAR."""
ctx = _FakeCtx("wildcard", strict=False)
result = resolve_text("$__wp_internal__", ctx)
assert result == ""
assert not any(w.get("type") == "var_out_of_surface" for w in ctx.warnings)