From 05d095ba45cc64b65526e433b993ec2d56200cb3 Mon Sep 17 00:00:00 2001 From: Andrew Chen <48723787+chuenchen309@users.noreply.github.com> Date: Thu, 16 Jul 2026 01:40:38 +0800 Subject: [PATCH] fix(condition): assert_in/assert_not_in wrongly reject falsy values present in the list _assert_in and _assert_not_in short-circuit on `if not value`, but this checks the truthiness of the value being tested for membership, not whether it's absent. A falsy value that is legitimately present in the expected list (e.g. an empty string in a picklist that allows an empty option) is silently rejected by "in" and silently accepted by "not in" before the actual membership check ever runs. Every other comparator in this file (_assert_equal, _assert_greater_than, etc.) guards with `if value is None`, not `if not value` -- these two are the only outliers. Fix: match the file's own established pattern, guard on `value is None` instead. Verified: new test (test_process_conditions_in_matches_falsy_value_present_in_list) red before fix, green after; full test_condition_processor.py suite (5 tests) plus tests/nodes/if_else/ (24 tests) pass; full test suite (604 tests) passes; ruff + ty clean. Co-Authored-By: Claude Opus 4.8 --- src/graphon/utils/condition/processor.py | 4 +-- tests/utils/test_condition_processor.py | 32 ++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/graphon/utils/condition/processor.py b/src/graphon/utils/condition/processor.py index 62128cf2..ded71d83 100644 --- a/src/graphon/utils/condition/processor.py +++ b/src/graphon/utils/condition/processor.py @@ -499,7 +499,7 @@ def _assert_not_null(*, value: Any) -> bool: def _assert_in(*, value: Any, expected: Any) -> bool: - if not value: + if value is None: return False match expected: @@ -512,7 +512,7 @@ def _assert_in(*, value: Any, expected: Any) -> bool: def _assert_not_in(*, value: Any, expected: Any) -> bool: - if not value: + if value is None: return True match expected: diff --git a/tests/utils/test_condition_processor.py b/tests/utils/test_condition_processor.py index 5e7a513f..34266e6e 100644 --- a/tests/utils/test_condition_processor.py +++ b/tests/utils/test_condition_processor.py @@ -125,6 +125,38 @@ def test_process_conditions_contains_supports_string_and_list_values() -> None: assert list_result.final_result is True +def test_process_conditions_in_matches_falsy_value_present_in_list() -> None: + condition_processor = ConditionProcessor() + variable_pool = VariablePool() + variable_pool.add(["test_node_id", "choice"], "") + + in_result = condition_processor.process_conditions( + variable_pool=variable_pool, + conditions=[ + Condition( + variable_selector=["test_node_id", "choice"], + comparison_operator="in", + value=["", "a", "b"], + ), + ], + operator="and", + ) + not_in_result = condition_processor.process_conditions( + variable_pool=variable_pool, + conditions=[ + Condition( + variable_selector=["test_node_id", "choice"], + comparison_operator="not in", + value=["", "a", "b"], + ), + ], + operator="and", + ) + + assert in_result.final_result is True + assert not_in_result.final_result is False + + def test_process_conditions_resolves_templates_from_read_only_variable_pool() -> None: condition_processor = ConditionProcessor() variable_pool = VariablePool()