From e452a2e645b0579f15148e8acc00cb19b2fb8860 Mon Sep 17 00:00:00 2001 From: StableLlama Date: Fri, 9 May 2025 23:38:54 +0200 Subject: [PATCH] Add more unit tests Little naming cleanup (first round) --- src/basic_data_handling/__init__.py | 6 +- src/basic_data_handling/casting_nodes.py | 4 +- ...{control_flow.py => control_flow_nodes.py} | 0 src/basic_data_handling/data_list_nodes.py | 4 +- src/basic_data_handling/string_nodes.py | 6 +- tests/test_casting_nodes.py | 81 +++++++++ tests/test_comparison_nodes.py | 115 ++++++++++++ tests/test_control_flow_nodes.py | 39 +++++ tests/test_data_list_nodes.py | 164 ++++++++++++++++++ tests/test_string_nodes.py | 4 +- 10 files changed, 411 insertions(+), 12 deletions(-) rename src/basic_data_handling/{control_flow.py => control_flow_nodes.py} (100%) create mode 100644 tests/test_casting_nodes.py create mode 100644 tests/test_comparison_nodes.py create mode 100644 tests/test_control_flow_nodes.py create mode 100644 tests/test_data_list_nodes.py diff --git a/src/basic_data_handling/__init__.py b/src/basic_data_handling/__init__.py index b900d85..6e867b9 100644 --- a/src/basic_data_handling/__init__.py +++ b/src/basic_data_handling/__init__.py @@ -1,4 +1,4 @@ -from . import (boolean_nodes, casting_nodes, comparison_nodes, control_flow, +from . import (boolean_nodes, casting_nodes, comparison_nodes, control_flow_nodes, data_list_nodes, dict_nodes, float_nodes, int_nodes, list_nodes, math_nodes, path_nodes, regex_nodes, set_nodes, string_nodes) @@ -6,7 +6,7 @@ NODE_CLASS_MAPPINGS = {} NODE_CLASS_MAPPINGS.update(boolean_nodes.NODE_CLASS_MAPPINGS) NODE_CLASS_MAPPINGS.update(casting_nodes.NODE_CLASS_MAPPINGS) NODE_CLASS_MAPPINGS.update(comparison_nodes.NODE_CLASS_MAPPINGS) -NODE_CLASS_MAPPINGS.update(control_flow.NODE_CLASS_MAPPINGS) +NODE_CLASS_MAPPINGS.update(control_flow_nodes.NODE_CLASS_MAPPINGS) NODE_CLASS_MAPPINGS.update(data_list_nodes.NODE_CLASS_MAPPINGS) NODE_CLASS_MAPPINGS.update(dict_nodes.NODE_CLASS_MAPPINGS) NODE_CLASS_MAPPINGS.update(float_nodes.NODE_CLASS_MAPPINGS) @@ -22,7 +22,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {} NODE_DISPLAY_NAME_MAPPINGS.update(boolean_nodes.NODE_DISPLAY_NAME_MAPPINGS) NODE_DISPLAY_NAME_MAPPINGS.update(casting_nodes.NODE_DISPLAY_NAME_MAPPINGS) NODE_DISPLAY_NAME_MAPPINGS.update(comparison_nodes.NODE_DISPLAY_NAME_MAPPINGS) -NODE_DISPLAY_NAME_MAPPINGS.update(control_flow.NODE_DISPLAY_NAME_MAPPINGS) +NODE_DISPLAY_NAME_MAPPINGS.update(control_flow_nodes.NODE_DISPLAY_NAME_MAPPINGS) NODE_DISPLAY_NAME_MAPPINGS.update(data_list_nodes.NODE_DISPLAY_NAME_MAPPINGS) NODE_DISPLAY_NAME_MAPPINGS.update(dict_nodes.NODE_DISPLAY_NAME_MAPPINGS) NODE_DISPLAY_NAME_MAPPINGS.update(float_nodes.NODE_DISPLAY_NAME_MAPPINGS) diff --git a/src/basic_data_handling/casting_nodes.py b/src/basic_data_handling/casting_nodes.py index 8681b06..ce6924b 100644 --- a/src/basic_data_handling/casting_nodes.py +++ b/src/basic_data_handling/casting_nodes.py @@ -51,7 +51,7 @@ class CastToInt: def convert_to_int(self, input: Any) -> tuple[int]: try: return (int(input),) - except ValueError: + except (ValueError, TypeError): raise ValueError(f"Cannot convert {input} to an INT.") @@ -79,7 +79,7 @@ class CastToFloat: def convert_to_float(self, input: Any) -> tuple[float]: try: return (float(input),) - except ValueError: + except (ValueError, TypeError): raise ValueError(f"Cannot convert {input} to a FLOAT.") diff --git a/src/basic_data_handling/control_flow.py b/src/basic_data_handling/control_flow_nodes.py similarity index 100% rename from src/basic_data_handling/control_flow.py rename to src/basic_data_handling/control_flow_nodes.py diff --git a/src/basic_data_handling/data_list_nodes.py b/src/basic_data_handling/data_list_nodes.py index 40d3198..8efe914 100644 --- a/src/basic_data_handling/data_list_nodes.py +++ b/src/basic_data_handling/data_list_nodes.py @@ -481,7 +481,7 @@ class DataListGetItem: def INPUT_TYPES(cls): return { "required": { - "input_list": ("*",), + "list": ("*",), "index": ("INT", {"default": 0}), } } @@ -557,7 +557,7 @@ class DataListContains: def INPUT_TYPES(cls): return { "required": { - "input_list": ("*",), + "list": ("*",), "value": ("*",), } } diff --git a/src/basic_data_handling/string_nodes.py b/src/basic_data_handling/string_nodes.py index 1996252..fe1e729 100644 --- a/src/basic_data_handling/string_nodes.py +++ b/src/basic_data_handling/string_nodes.py @@ -79,7 +79,7 @@ class StringCenter: return (string.center(width, fillchar),) -class StringConcat: +class DataStringConcat: """Combines two text strings together, joining them end-to-end.""" @classmethod def INPUT_TYPES(cls): @@ -1771,7 +1771,7 @@ NODE_CLASS_MAPPINGS = { "StringCapitalize": StringCapitalize, "StringCasefold": StringCasefold, "StringCenter": StringCenter, - "StringConcat": StringConcat, + "DataStringConcat": DataStringConcat, "StringCount": StringCount, "StringDecode": StringDecode, "StringEncode": StringEncode, @@ -1816,7 +1816,7 @@ NODE_DISPLAY_NAME_MAPPINGS = { "StringCapitalize": "capitalize", "StringCasefold": "casefold", "StringCenter": "center", - "StringConcat": "concat", + "DataStringConcat": "concat", "StringCount": "count", "StringDecode": "decode", "StringEncode": "encode", diff --git a/tests/test_casting_nodes.py b/tests/test_casting_nodes.py new file mode 100644 index 0000000..b65ec1e --- /dev/null +++ b/tests/test_casting_nodes.py @@ -0,0 +1,81 @@ +import pytest +from src.basic_data_handling.casting_nodes import (CastToString, CastToInt, CastToFloat, CastToBoolean, + CastToList, CastDataListToList, CastToSet, CastDataListToSet, + CastToDict) + + +def test_cast_to_string(): + node = CastToString() + assert node.convert_to_string(123) == ("123",) + assert node.convert_to_string(None) == ("None",) + assert node.convert_to_string(True) == ("True",) + assert node.convert_to_string("hello") == ("hello",) + + +def test_cast_to_int(): + node = CastToInt() + assert node.convert_to_int("123") == (123,) + assert node.convert_to_int(456) == (456,) + with pytest.raises(ValueError): + node.convert_to_int("abc") + with pytest.raises(ValueError): + node.convert_to_int(None) + + +def test_cast_to_float(): + node = CastToFloat() + assert node.convert_to_float("123.45") == (123.45,) + assert node.convert_to_float(67.89) == (67.89,) + assert node.convert_to_float(123) == (123.0,) + with pytest.raises(ValueError): + node.convert_to_float("abc") + with pytest.raises(ValueError): + node.convert_to_float(None) + + +def test_cast_to_boolean(): + node = CastToBoolean() + assert node.convert_to_boolean(0) == (False,) + assert node.convert_to_boolean(1) == (True,) + assert node.convert_to_boolean("") == (False,) + assert node.convert_to_boolean("non_empty_string") == (True,) + + +def test_cast_to_list(): + node = CastToList() + assert node.convert_to_list(123) == ([123],) + assert node.convert_to_list([1, 2, 3]) == ([1, 2, 3],) + assert node.convert_to_list("hello") == (["hello"],) + assert node.convert_to_list(None) == ([None],) + + +def test_cast_data_list_to_list(): + node = CastDataListToList() + assert node.convert(list=[1, 2, 3]) == ([1, 2, 3],) + assert node.convert(list=[]) == ([],) + assert node.convert(list=["a", "b", "c"]) == (["a", "b", "c"],) + + +def test_cast_to_set(): + node = CastToSet() + assert node.convert_to_set(123) == ({123},) + assert node.convert_to_set([1, 2, 2]) == ({1, 2},) + assert node.convert_to_set("abc") == ({"abc"},) + assert node.convert_to_set(None) == ({None},) + + +def test_cast_data_list_to_set(): + node = CastDataListToSet() + assert node.convert(list=[1, 2, 3, 3]) == ({1, 2, 3},) + assert node.convert(list=["a", "b", "b"]) == ({"a", "b"},) + assert node.convert(list=[]) == (set(),) + + +def test_cast_to_dict(): + node = CastToDict() + assert node.convert_to_dict({"key": "value"}) == ({"key": "value"},) + assert node.convert_to_dict([("key1", "value1"), ("key2", "value2")]) == ({"key1": "value1", "key2": "value2"},) + with pytest.raises(ValueError): + node.convert_to_dict(123) + with pytest.raises(ValueError): + node.convert_to_dict("string") diff --git a/tests/test_comparison_nodes.py b/tests/test_comparison_nodes.py new file mode 100644 index 0000000..310cb3e --- /dev/null +++ b/tests/test_comparison_nodes.py @@ -0,0 +1,115 @@ +import pytest +from src.basic_data_handling.comparison_nodes import ( + Equal, + NotEqual, + GreaterThan, + LessThan, + GreaterThanOrEqual, + LessThanOrEqual, + StringComparison, + NumberInRange, + IsNull, + CompareLength, +) + + +def test_equal(): + node = Equal() + assert node.compare(10, 10) == (True,) + assert node.compare(10, 20) == (False,) + assert node.compare("hello", "hello") == (True,) + assert node.compare([1, 2], [1, 2]) == (True,) + assert node.compare(None, None) == (True,) + + +def test_not_equal(): + node = NotEqual() + assert node.compare(10, 10) == (False,) + assert node.compare(10, 20) == (True,) + assert node.compare("hello", "world") == (True,) + assert node.compare([1, 2], [1, 3]) == (True,) + assert node.compare(None, []) == (True,) + + +def test_greater_than(): + node = GreaterThan() + assert node.compare(10, 5) == (True,) + assert node.compare(5, 10) == (False,) + assert node.compare(10, 10) == (False,) + assert node.compare(5.6, 2.3) == (True,) + + +def test_less_than(): + node = LessThan() + assert node.compare(5, 10) == (True,) + assert node.compare(10, 5) == (False,) + assert node.compare(10, 10) == (False,) + assert node.compare(2.3, 5.6) == (True,) + + +def test_greater_than_or_equal(): + node = GreaterThanOrEqual() + assert node.compare(10, 10) == (True,) + assert node.compare(15, 10) == (True,) + assert node.compare(5, 10) == (False,) + assert node.compare(5.5, 5.5) == (True,) + + +def test_less_than_or_equal(): + node = LessThanOrEqual() + assert node.compare(10, 10) == (True,) + assert node.compare(5, 15) == (True,) + assert node.compare(15, 5) == (False,) + assert node.compare(3.1, 3.1) == (True,) + + +def test_string_comparison(): + node = StringComparison() + + # Case-sensitive tests + assert node.compare("hello", "hello", "==", "True") == (True,) + assert node.compare("apple", "banana", "<", "True") == (True,) + assert node.compare("apple", "banana", ">", "True") == (False,) + assert node.compare("apple", "apple", "!=", "True") == (False,) + + # Case-insensitive tests + assert node.compare("Hello", "hello", "==", "False") == (True,) + assert node.compare("Apple", "BANANA", "<", "False") == (True,) + assert node.compare("Grape", "GRAPE", ">=", "False") == (True,) + + +def test_number_in_range(): + node = NumberInRange() + + # Inclusive range tests + assert node.check_range(50, 0, 100) == (True,) + assert node.check_range(0, 0, 100) == (True,) + + # Exclusive range tests + assert node.check_range(0, 0, 100, include_min="False") == (False,) + assert node.check_range(100, 0, 100, include_max="False") == (False,) + + # Out of range tests + assert node.check_range(-1, 0, 100) == (False,) + assert node.check_range(101, 0, 100) == (False,) + + +def test_is_null(): + node = IsNull() + assert node.check_null(None) == (True,) + assert node.check_null(0) == (False,) + assert node.check_null("") == (False,) + assert node.check_null([]) == (False,) + + +def test_compare_length(): + node = CompareLength() + + # Valid comparisons + assert node.compare_length([1, 2, 3], "==", 3) == (True, 3) + assert node.compare_length("hello", "!=", 4) == (True, 5) + assert node.compare_length((1, 2), ">", 1) == (True, 2) + + # Invalid comparisons + assert node.compare_length({"key": "value"}, "<", 1) == (False, 1) + assert node.compare_length(123, "==", 0) == (False, -1) # Non-container input edge case diff --git a/tests/test_control_flow_nodes.py b/tests/test_control_flow_nodes.py new file mode 100644 index 0000000..612790c --- /dev/null +++ b/tests/test_control_flow_nodes.py @@ -0,0 +1,39 @@ +import pytest +from src.basic_data_handling.control_flow_nodes import IfElse, SwitchCase + + +def test_if_else(): + node = IfElse() + + # Test where condition is True + assert node.execute(True, "Value if True", "Value if False") == ("Value if True",) + + # Test where condition is False + assert node.execute(False, "Value if True", "Value if False") == ("Value if False",) + + # Test edge cases + assert node.execute(True, 1, 2) == (1,) # Integers + assert node.execute(False, [1, 2, 3], None) == (None,) # Complex types + assert node.execute(True, None, "fallback") == (None,) # Fallback to True branch + + +def test_switch_case(): + node = SwitchCase() + + # Selector within range, valid cases + assert node.execute(0, "Case 0", "Case 1", "Case 2", "Case 3") == ("Case 0",) + assert node.execute(1, "Case 0", "Case 1", "Case 2", "Case 3") == ("Case 1",) + assert node.execute(3, "Case 0", "Case 1", "Case 2", "Case 3") == ("Case 3",) + + # Selector out of range, fallback to default case + assert node.execute(4, "Case 0", "Case 1", "Case 2", "Case 3", default="Default") == ("Default",) + assert node.execute(99, "Case 0", "Case 1", "Case 2", "Case 3", default="Default") == ("Default",) + + # Selector out of range, no default provided + assert node.execute(4, "Case 0", "Case 1", "Case 2", "Case 3") == (None,) + + # Edge cases + assert node.execute(0, None, "Case 1", "Case 2") == (None,) # Case with None + assert node.execute(2, "Case 0", "Case 1", "Case 2") == ("Case 2",) + assert node.execute(0, 123, False, "Case 2") == (123,) # Different data types + assert node.execute(10, "Case 0", "Case 1", "Case 2", default="Fallback") == ("Fallback",) diff --git a/tests/test_data_list_nodes.py b/tests/test_data_list_nodes.py new file mode 100644 index 0000000..2346550 --- /dev/null +++ b/tests/test_data_list_nodes.py @@ -0,0 +1,164 @@ +import pytest +from src.basic_data_handling.data_list_nodes import ( + DataListAppend, + DataListExtend, + DataListInsert, + DataListRemove, + DataListPop, + DataListClear, + DataListIndex, + DataListCount, + DataListSort, + DataListReverse, + DataListCopy, + DataListLength, + DataListSlice, + DataListGetItem, + DataListSetItem, + DataListContains, + DataListCreateEmpty, + DataListZip, + DataListFilter, + DataListMin, + DataListMax, +) + + +def test_append(): + node = DataListAppend() + assert node.append(list=[1, 2, 3], item=[4]) == ([1, 2, 3, 4],) + assert node.append(list=[], item=["test"]) == (["test"],) + assert node.append(list=[1, 2, 3], item=[]) == ([1, 2, 3],) + + +def test_extend(): + node = DataListExtend() + assert node.extend(list_a=[1, 2], list_b=[3, 4]) == ([1, 2, 3, 4],) + assert node.extend(list_a=[], list_b=["a", "b"]) == (["a", "b"],) + assert node.extend(list_a=[1], list_b=[]) == ([1],) + + +def test_insert(): + node = DataListInsert() + assert node.insert(list=[1, 2, 4], index=[2], item=[3]) == ([1, 2, 3, 4],) + assert node.insert(list=[], index=[0], item=["a"]) == (["a"],) + assert node.insert(list=[1, 2], index=[5], item=[3]) == ([1, 2, 3],) # Out-of-range insert + + +def test_remove(): + node = DataListRemove() + assert node.remove(list=[1, 2, 3], value=[2]) == ([1, 3], True) + assert node.remove(list=["a", "b", "a"], value=["a"]) == (["b", "a"], True) + assert node.remove(list=[1, 2, 3], value=[5]) == ([1, 2, 3], False) # Value not found + + +def test_pop(): + node = DataListPop() + assert node.pop(list=[1, 2, 3], index=[1]) == ([1, 3], 2) + assert node.pop(list=["a", "b", "c"], index=[-1]) == (["a", "b"], "c") # Negative index + assert node.pop(list=[], index=[0]) == ([], None) # Pop from empty list + + +def test_clear(): + node = DataListClear() + assert node.clear(list=[1, 2, 3]) == ([],) + assert node.clear(list=[]) == ([],) + + +def test_index(): + node = DataListIndex() + assert node.list_index(list=["a", "b", "c"], value=["b"]) == (1,) + assert node.list_index(list=[1, 2, 3], value=[5]) == (-1,) # Value not found + assert node.list_index(list=[1, 2, 1, 2], value=[1], start=[1]) == (2,) + + +def test_count(): + node = DataListCount() + assert node.count(list=[1, 2, 1, 2], value=[1]) == (2,) + assert node.count(list=["a", "b", "a"], value=["a"]) == (2,) + assert node.count(list=[], value=["x"]) == (0,) + + +def test_sort(): + node = DataListSort() + assert node.sort(list=[3, 2, 1]) == ([1, 2, 3],) + assert node.sort(list=["c", "a", "b"]) == (["a", "b", "c"],) + assert node.sort(list=[3, 1, 2], reverse=["True"]) == ([3, 2, 1],) # Reverse sort + + +def test_reverse(): + node = DataListReverse() + assert node.reverse(list=[1, 2, 3]) == ([3, 2, 1],) + assert node.reverse(list=[]) == ([],) + + +def test_copy(): + node = DataListCopy() + assert node.copy(list=[1, 2, 3]) == ([1, 2, 3],) + assert node.copy(list=[]) == ([],) + + +def test_length(): + node = DataListLength() + assert node.length(list=[1, 2, 3]) == (3,) + assert node.length(list=[]) == (0,) + + +def test_slice(): + node = DataListSlice() + assert node.slice(list=[1, 2, 3, 4, 5], start=[1], stop=[4]) == ([2, 3, 4],) + assert node.slice(list=[1, 2, 3], start=[0], stop=[2], step=[2]) == ([1],) + assert node.slice(list=["a", "b", "c"], start=[-2], stop=[3]) == (["b", "c"],) + + +def test_get_item(): + node = DataListGetItem() + assert node.get_item(list=[1, 2, 3], index=[1]) == (2,) + assert node.get_item(list=["a", "b", "c"], index=[-1]) == ("c",) # Negative index + assert node.get_item(list=[], index=[0]) == (None,) # Out of range + + +def test_set_item(): + node = DataListSetItem() + assert node.set_item(list=[1, 2, 3], index=[1], value=[9]) == ([1, 9, 3],) + assert node.set_item(list=["a", "b", "c"], index=[-1], value=["z"]) == (["a", "b", "z"],) + with pytest.raises(IndexError): + node.set_item(list=[], index=[0], value=["test"]) # Out of range + + +def test_contains(): + node = DataListContains() + assert node.contains(list=[1, 2, 3], value=[2]) == (True,) + assert node.contains(list=["a", "b"], value=["c"]) == (False,) + assert node.contains(list=[], value=["x"]) == (False,) + + +def test_create_empty(): + node = DataListCreateEmpty() + assert node.create_empty() == ([],) + + +def test_zip(): + node = DataListZip() + assert node.zip_lists(list1=[1, 2], list2=["a", "b"]) == ([[1, "a"], [2, "b"]],) + assert node.zip_lists(list1=[1], list2=["a", "b"]) == ([[1, "a"]],) # Shortest list length + + +def test_filter(): + node = DataListFilter() + assert node.filter_data(value=[1, 2, 3], filter=[False, True, False]) == ([1, 3],) + assert node.filter_data(value=[1, 2], filter=[True, True]) == ([],) + + +def test_min(): + node = DataListMin() + assert node.find_min(list=[3, 1, 2]) == (1,) + assert node.find_min(list=[-1, -5, 0]) == (-5,) + assert node.find_min(list=[]) == (None,) + + +def test_max(): + node = DataListMax() + assert node.find_max(list=[3, 1, 2]) == (3,) + assert node.find_max(list=[-1, -5, 0]) == (0,) + assert node.find_max(list=[]) == (None,) diff --git a/tests/test_string_nodes.py b/tests/test_string_nodes.py index ce48d0b..4a096d4 100644 --- a/tests/test_string_nodes.py +++ b/tests/test_string_nodes.py @@ -4,7 +4,7 @@ from src.basic_data_handling.string_nodes import ( StringCapitalize, StringCasefold, StringCenter, - StringConcat, + DataStringConcat, StringCount, StringDecode, StringEncode, @@ -72,7 +72,7 @@ def test_center(): assert node.center("test", 10, "ab") == ("aaatestaaa",) def test_concat(): - node = StringConcat() + node = DataStringConcat() assert node.concat("hello ", "world") == ("hello world",) assert node.concat("", "") == ("",) # Empty strings assert node.concat("123", "456") == ("123456",) # Numbers as strings