Files
IxMxAMAR-ComfyUI-Utility-Me…/tests/unit/test_programming_control_flow.py
IxMxAMAR 15e4b0da4e release(v0.1.2): fix critical loader collision + 6 Gemini-review findings
Critical:
- rename nodes/ -> mp_nodes/, shared/ -> mp_shared/ to avoid shadowing
  ComfyUI core's nodes.py via sys.path injection (root cause of v0.1.1
  'nodes is not a package' startup failure)
- http_post now streams response and enforces 50MB cap mid-stream (DoS)
- AES key derivation: HKDF -> PBKDF2-HMAC-SHA256 600k iters + random salt
  (work factor against weak-key brute force; ciphertext format changed)
- fs_mkdir/copy/move/delete confined to ComfyUI input/output/temp + ~ by
  default; UTILITY_MEGAPACK_ALLOW_ARBITRARY_PATHS=1 to override

UX / correctness:
- combo settings now declare options array (themes dropdown was empty)
- LATENT default 1x4x8x8 zeros -> {} (avoids Flux/SD3 shape crashes)
- @op decorator rejects reserved widget names mode/theme at register time
- _pad_outputs docstring updated to match relaxed opaque-type behavior
2026-05-13 21:36:54 +05:30

99 lines
3.2 KiB
Python

"""Tests for the Control Flow category."""
import pytest
from mp_nodes.programming import ProgrammingNode
def run(mode, **kwargs):
return ProgrammingNode().process(mode=mode, theme="(use pack default)", **kwargs)
class TestForLoop:
def test_basic(self):
assert run("for_loop", start=0, end=5, step=1)[4] == [0, 1, 2, 3, 4]
def test_step_2(self):
assert run("for_loop", start=0, end=10, step=2)[4] == [0, 2, 4, 6, 8]
class TestWhileLoop:
def test_simple_counter(self):
out = run("while_loop", expr="i<5", max_iters=100)[4]
assert out == [0, 1, 2, 3, 4]
def test_max_iters_caps(self):
out = run("while_loop", expr="i<999999", max_iters=10)[4]
assert len(out) == 10
def test_dunder_in_expr_raises(self):
with pytest.raises(RuntimeError, match="forbidden '__'"):
run("while_loop", expr="i.__class__", max_iters=10)
class TestIfElse:
def test_true(self):
assert run("if_else", condition=True, when_true="yes", when_false="no")[0] == "yes"
def test_false(self):
assert run("if_else", condition=False, when_true="yes", when_false="no")[0] == "no"
class TestCompare:
def test_lt_true(self):
assert run("compare", a=1.0, op="<", b=2.0)[3] is True
def test_eq_true(self):
assert run("compare", a=2.0, op="==", b=2.0)[3] is True
def test_ne_true(self):
assert run("compare", a=1.0, op="!=", b=2.0)[3] is True
def test_unknown_op_raises(self):
with pytest.raises(RuntimeError, match="unknown compare op"):
run("compare", a=1.0, op="xx", b=2.0)
class TestEvalExpr:
def test_basic_arithmetic(self):
assert run("eval_expr", expr="2 + 3 * 4", vars_json="{}")[6] == 14
def test_with_vars(self):
assert run("eval_expr", expr="x * y", vars_json='{"x": 3, "y": 4}')[6] == 12
def test_dunder_blocked(self):
with pytest.raises(RuntimeError, match="forbidden '__'"):
run("eval_expr", expr="x.__class__", vars_json='{"x": 1}')
def test_undefined_name_raises(self):
with pytest.raises(RuntimeError, match="undefined name"):
run("eval_expr", expr="undefined_var + 1", vars_json="{}")
class TestNullCoalesce:
def test_a_wins_when_present(self):
assert run("null_coalesce", a="first", b="second", c="third")[0] == "first"
def test_b_wins_when_a_empty(self):
assert run("null_coalesce", a="", b="second", c="third")[0] == "second"
def test_c_wins_when_a_b_empty(self):
assert run("null_coalesce", a="", b="", c="third")[0] == "third"
def test_all_empty_returns_empty(self):
assert run("null_coalesce", a="", b="", c="")[0] == ""
class TestSwitchCase:
def test_known_case(self):
out = run("switch_case", value="apple", cases_json='{"apple":"red","banana":"yellow"}')[0]
assert out == "red"
def test_unknown_falls_to_default(self):
out = run("switch_case", value="grape", cases_json='{"apple":"red","_":"unknown"}')[0]
assert out == "unknown"
def test_no_default_returns_empty_string(self):
out = run("switch_case", value="grape", cases_json='{"apple":"red"}')[0]
assert out == ""