diff --git a/pyproject.toml b/pyproject.toml index 2ea91e0..0e6b8ed 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "basic_data_handling" -version = "0.2.2" +version = "0.3.0" description = """NOTE: Still in development! Expect breaking changes! Basic Python functions for manipulating data that every programmer is used to. Currently supported ComfyUI data types: BOOLEAN, FLOAT, INT, STRING and data lists. @@ -47,11 +47,12 @@ minversion = "8.0" pythonpath = [ "src", "../..", # Path to parent directory containing comfy module + "." ] testpaths = [ "tests", ] -# python_files = ["test_*.py"] +#python_files = ["test_*.py"] python_files = ["test_boolean_nodes.py"] [tool.mypy] diff --git a/src/basic_data_handling/comparison_nodes.py b/src/basic_data_handling/comparison_nodes.py index da4f7de..e7cef2d 100644 --- a/src/basic_data_handling/comparison_nodes.py +++ b/src/basic_data_handling/comparison_nodes.py @@ -58,7 +58,7 @@ class NotEqual(ComfyNodeABC): return (value1 != value2,) -class LessThan: +class LessThan(ComfyNodeABC): """ Checks if the first value is less than the second. @@ -84,7 +84,7 @@ class LessThan: return (value1 < value2,) -class LessThanOrEqual: +class LessThanOrEqual(ComfyNodeABC): """ Checks if the first value is less than or equal to the second. @@ -110,7 +110,7 @@ class LessThanOrEqual: return (value1 <= value2,) -class GreaterThan: +class GreaterThan(ComfyNodeABC): """ Checks if the first value is greater than the second. @@ -136,7 +136,7 @@ class GreaterThan: return (value1 > value2,) -class GreaterThanOrEqual: +class GreaterThanOrEqual(ComfyNodeABC): """ Checks if the first value is greater than or equal to the second. @@ -162,7 +162,7 @@ class GreaterThanOrEqual: return (value1 >= value2,) -class IsNull: +class IsNull(ComfyNodeABC): """ Checks if a value is None/null. @@ -187,7 +187,7 @@ class IsNull: return (value is None,) -class NumberInRange: +class NumberInRange(ComfyNodeABC): """ Checks if a number is within a specified range. @@ -223,7 +223,7 @@ class NumberInRange: return (min_check and max_check,) -class CompareLength: +class CompareLength(ComfyNodeABC): """ Compares the length of a container (string, list, etc) with a value. @@ -269,7 +269,7 @@ class CompareLength: raise ValueError(f"Unknown operator: {operator}") -class StringComparison: +class StringComparison(ComfyNodeABC): """ Compares two strings using a selected comparison operator. diff --git a/src/basic_data_handling/float_nodes.py b/src/basic_data_handling/float_nodes.py index 5492ae3..3241ed8 100644 --- a/src/basic_data_handling/float_nodes.py +++ b/src/basic_data_handling/float_nodes.py @@ -149,6 +149,8 @@ class FloatDivideSafe(ComfyNodeABC): def divide(self, float1: float, float2: float) -> tuple[float]: if float2 == 0.0: + if float1 == 0.0: + return (float('nan'),) return (float('inf') if float1 > 0 else float('-inf'),) return (float1 / float2,) diff --git a/tests/test_comparison_nodes.py b/tests/test_comparison_nodes.py index 9d44431..ff25f4d 100644 --- a/tests/test_comparison_nodes.py +++ b/tests/test_comparison_nodes.py @@ -1,4 +1,4 @@ -#import pytest +import pytest from src.basic_data_handling.comparison_nodes import ( Equal, NotEqual, @@ -20,6 +20,9 @@ def test_equal(): assert node.compare("hello", "hello") == (True,) assert node.compare([1, 2], [1, 2]) == (True,) assert node.compare(None, None) == (True,) + # Test complex objects + assert node.compare({"a": 1}, {"a": 1}) == (True,) + assert node.compare({"a": 1}, {"a": 2}) == (False,) def test_not_equal(): @@ -29,6 +32,9 @@ def test_not_equal(): assert node.compare("hello", "world") == (True,) assert node.compare([1, 2], [1, 3]) == (True,) assert node.compare(None, []) == (True,) + # Test complex objects + assert node.compare({"a": 1}, {"b": 1}) == (True,) + assert node.compare({"a": 1}, {"a": 1}) == (False,) def test_greater_than(): @@ -37,6 +43,12 @@ def test_greater_than(): assert node.compare(5, 10) == (False,) assert node.compare(10, 10) == (False,) assert node.compare(5.6, 2.3) == (True,) + # Test negative numbers + assert node.compare(-5, -10) == (True,) + assert node.compare(-10, -5) == (False,) + # Test zeroes + assert node.compare(0, -1) == (True,) + assert node.compare(0, 0) == (False,) def test_less_than(): @@ -45,6 +57,12 @@ def test_less_than(): assert node.compare(10, 5) == (False,) assert node.compare(10, 10) == (False,) assert node.compare(2.3, 5.6) == (True,) + # Test negative numbers + assert node.compare(-10, -5) == (True,) + assert node.compare(-5, -10) == (False,) + # Test zeroes + assert node.compare(-1, 0) == (True,) + assert node.compare(0, 0) == (False,) def test_greater_than_or_equal(): @@ -53,6 +71,12 @@ def test_greater_than_or_equal(): assert node.compare(15, 10) == (True,) assert node.compare(5, 10) == (False,) assert node.compare(5.5, 5.5) == (True,) + # Test negative numbers + assert node.compare(-5, -10) == (True,) + assert node.compare(-5, -5) == (True,) + # Test zeroes + assert node.compare(0, 0) == (True,) + assert node.compare(0.0, -0.0) == (True,) def test_less_than_or_equal(): @@ -61,6 +85,12 @@ def test_less_than_or_equal(): assert node.compare(5, 15) == (True,) assert node.compare(15, 5) == (False,) assert node.compare(3.1, 3.1) == (True,) + # Test negative numbers + assert node.compare(-10, -5) == (True,) + assert node.compare(-5, -5) == (True,) + # Test zeroes + assert node.compare(0, 0) == (True,) + assert node.compare(-0.0, 0.0) == (True,) def test_string_comparison(): @@ -71,11 +101,20 @@ def test_string_comparison(): assert node.compare("apple", "banana", "<", "True") == (True,) assert node.compare("apple", "banana", ">", "True") == (False,) assert node.compare("apple", "apple", "!=", "True") == (False,) + assert node.compare("banana", "apple", ">=", "True") == (True,) + assert node.compare("apple", "banana", "<=", "True") == (True,) # Case-insensitive tests assert node.compare("Hello", "hello", "==", "False") == (True,) assert node.compare("Apple", "BANANA", "<", "False") == (True,) assert node.compare("Grape", "GRAPE", ">=", "False") == (True,) + assert node.compare("ZEBRA", "zoo", "<", "False") == (True,) + assert node.compare("ZOO", "zebra", ">", "False") == (True,) + + # Empty string tests + assert node.compare("", "hello", "<", "True") == (True,) + assert node.compare("hello", "", ">", "True") == (True,) + assert node.compare("", "", "==", "True") == (True,) def test_number_in_range(): @@ -84,15 +123,27 @@ def test_number_in_range(): # Inclusive range tests assert node.check_range(50, 0, 100) == (True,) assert node.check_range(0, 0, 100) == (True,) + assert node.check_range(100, 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,) + assert node.check_range(50, 0, 100, include_min="False", include_max="False") == (True,) # Out of range tests assert node.check_range(-1, 0, 100) == (False,) assert node.check_range(101, 0, 100) == (False,) + # Edge cases + assert node.check_range(0, 0, 0) == (True,) + assert node.check_range(0, 0, 0, include_min="False") == (False,) + assert node.check_range(0, 0, 0, include_max="False") == (False,) + + # Negative range tests + assert node.check_range(-50, -100, -10) == (True,) + assert node.check_range(-10, -100, -10) == (True,) + assert node.check_range(-100, -100, -10) == (True,) + def test_is_null(): node = IsNull() @@ -100,16 +151,37 @@ def test_is_null(): assert node.check_null(0) == (False,) assert node.check_null("") == (False,) assert node.check_null([]) == (False,) + assert node.check_null(False) == (False,) + # Additional edge cases + assert node.check_null([None]) == (False,) # List containing None + assert node.check_null("None") == (False,) # String "None" def test_compare_length(): node = CompareLength() - # Valid comparisons + # Valid comparisons with different operators 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) + assert node.compare_length([1, 2, 3], "<", 4) == (True, 3) + assert node.compare_length("test", ">=", 4) == (True, 4) + assert node.compare_length([1], "<=", 1) == (True, 1) + + # Different container types + assert node.compare_length({"a", "b", "c"}, "==", 3) == (True, 3) # Set + assert node.compare_length({"key1": 1, "key2": 2}, "==", 2) == (True, 2) # Dict + + # Empty containers + assert node.compare_length([], "==", 0) == (True, 0) + assert node.compare_length("", "==", 0) == (True, 0) + assert node.compare_length({}, "==", 0) == (True, 0) # Invalid comparisons - assert node.compare_length({"key": "value"}, "<", 1) == (False, 1) - assert node.compare_length(123, "==", 0) == (False, -1) # Non-container input edge case + assert node.compare_length({"key": "value"}, "<", 1) == (False, 1) # Dict with 1 item + assert node.compare_length(123, "==", 0) == (False, -1) # Non-container input + assert node.compare_length(None, "==", 0) == (False, -1) # None + + # Error case with invalid operator + with pytest.raises(ValueError): + node.compare_length([1, 2, 3], "invalid", 3) diff --git a/tests/test_data_list_nodes.py b/tests/test_data_list_nodes.py index e6e5086..ab98877 100644 --- a/tests/test_data_list_nodes.py +++ b/tests/test_data_list_nodes.py @@ -14,7 +14,6 @@ from src.basic_data_handling.data_list_nodes import ( DataListGetItem, DataListSetItem, DataListContains, - DataListCreateEmpty, DataListZip, DataListFilter, DataListMin, @@ -119,21 +118,18 @@ def test_contains(): 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 + assert node.zip_lists(list1=[1, 2, 3], list2=["a", "b"]) == ([[1, "a"], [2, "b"]],) # Different lengths 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]) == ([],) + assert node.filter_data(value=[1, 2, 3], filter=[False, False, False]) == ([1, 2, 3],) def test_min(): diff --git a/tests/test_dict_nodes.py b/tests/test_dict_nodes.py index 03a2738..39fd979 100644 --- a/tests/test_dict_nodes.py +++ b/tests/test_dict_nodes.py @@ -34,30 +34,46 @@ def test_dict_get(): my_dict = {"key1": "value1", "key2": "value2"} assert node.get(my_dict, "key1") == ("value1",) assert node.get(my_dict, "key3", default="default_value") == ("default_value",) + # Test with no default specified + assert node.get(my_dict, "non_existent") == (None,) def test_dict_set(): node = DictSet() my_dict = {"key1": "value1"} assert node.set(my_dict, "key2", "value2") == ({"key1": "value1", "key2": "value2"},) + # Test overwriting existing key + assert node.set(my_dict, "key1", "new_value") == ({"key1": "new_value"},) + # Test with empty dict + assert node.set({}, "key", "value") == ({"key": "value"},) def test_dict_keys(): node = DictKeys() my_dict = {"key1": "value1", "key2": "value2"} assert node.keys(my_dict) == (["key1", "key2"],) + # Test with empty dict + assert node.keys({}) == ([],) def test_dict_values(): node = DictValues() my_dict = {"key1": "value1", "key2": "value2"} assert node.values(my_dict) == (["value1", "value2"],) + # Test with empty dict + assert node.values({}) == ([],) def test_dict_items(): node = DictItems() my_dict = {"key1": "value1", "key2": "value2"} - assert node.items(my_dict) == ([("key1", "value1"), ("key2", "value2")],) + # Note that the order might not be preserved, so we check if items are in the result + items = node.items(my_dict)[0] + assert len(items) == 2 + assert ("key1", "value1") in items + assert ("key2", "value2") in items + # Test with empty dict + assert node.items({}) == ([],) def test_dict_contains_key(): @@ -65,31 +81,52 @@ def test_dict_contains_key(): my_dict = {"key1": "value1"} assert node.contains_key(my_dict, "key1") == (True,) assert node.contains_key(my_dict, "key2") == (False,) + # Test with empty dict + assert node.contains_key({}, "any_key") == (False,) def test_dict_from_keys(): node = DictFromKeys() keys = ["key1", "key2"] assert node.from_keys(keys, value="value") == ({"key1": "value", "key2": "value"},) + # Test without value (should use None) + assert node.from_keys(keys) == ({"key1": None, "key2": None},) + # Test with empty keys list + assert node.from_keys([]) == ({},) def test_dict_pop(): node = DictPop() my_dict = {"key1": "value1", "key2": "value2"} assert node.pop(my_dict, "key1") == ({"key2": "value2"}, "value1") + # Test with default value for non-existent key + assert node.pop(my_dict, "non_existent", default_value="default") == (my_dict, "default") + # Test with no default for non-existent key - should not modify dict + assert node.pop({"a": 1}, "b") == ({"a": 1}, None) def test_dict_pop_item(): node = DictPopItem() my_dict = {"key1": "value1"} - assert node.popitem(my_dict) == ({}, "key1", "value1", True) + result = node.popitem(my_dict) + # Since we only have one item, we know what should be popped + assert result[0] == {} # remaining dict is empty + assert result[1] == "key1" # popped key + assert result[2] == "value1" # popped value + assert result[3] is True # success + # Test with empty dict assert node.popitem({}) == ({}, "", None, False) def test_dict_set_default(): node = DictSetDefault() my_dict = {"key1": "value1"} + # Test setting a key that doesn't exist assert node.setdefault(my_dict, "key2", "default") == ({"key1": "value1", "key2": "default"}, "default") + # Test with a key that already exists + assert node.setdefault(my_dict, "key1", "new_default") == ({"key1": "value1"}, "value1") + # Test with empty dict + assert node.setdefault({}, "key", "value") == ({"key": "value"}, "value") def test_dict_update(): @@ -97,69 +134,153 @@ def test_dict_update(): my_dict = {"key1": "value1"} update_dict = {"key2": "value2"} assert node.update(my_dict, update_dict) == ({"key1": "value1", "key2": "value2"},) + # Test updating with overlapping keys + assert node.update({"a": 1, "b": 2}, {"b": 3, "c": 4}) == ({"a": 1, "b": 3, "c": 4},) + # Test with empty update dict + assert node.update(my_dict, {}) == (my_dict,) + # Test with empty original dict + assert node.update({}, update_dict) == (update_dict,) def test_dict_length(): node = DictLength() - my_dict = {"key1": "value1"} - assert node.length(my_dict) == (1,) + my_dict = {"key1": "value1", "key2": "value2"} + assert node.length(my_dict) == (2,) + # Test with empty dict + assert node.length({}) == (0,) def test_dict_merge(): node = DictMerge() dict1 = {"key1": "value1"} dict2 = {"key2": "value2"} + # Test basic merge assert node.merge(dict1, dict2) == ({"key1": "value1", "key2": "value2"},) + # Test with overlapping keys (later dicts override earlier ones) + assert node.merge({"a": 1}, {"a": 2}) == ({"a": 2},) + # Test with more than two dicts + result = node.merge({"a": 1}, {"b": 2}, {"c": 3}) + assert result == ({"a": 1, "b": 2, "c": 3},) + # Test with empty dicts + assert node.merge(dict1, {}) == (dict1,) + assert node.merge({}, dict1) == (dict1,) def test_dict_get_keys_values(): node = DictGetKeysValues() my_dict = {"key1": "value1", "key2": "value2"} - assert node.get_keys_values(my_dict) == (["key1", "key2"], ["value1", "value2"]) + keys, values = node.get_keys_values(my_dict) + # Check keys and values contents (order may vary) + assert set(keys) == {"key1", "key2"} + assert set(values) == {"value1", "value2"} + # Test with empty dict + assert node.get_keys_values({}) == ([], []) def test_dict_remove(): node = DictRemove() - my_dict = {"key1": "value1"} - assert node.remove(my_dict, "key1") == ({}, True) + my_dict = {"key1": "value1", "key2": "value2"} + # Test successful removal + assert node.remove(my_dict, "key1") == ({"key2": "value2"}, True) + # Test removal of non-existent key + assert node.remove(my_dict, "non_existent") == (my_dict, False) + # Test with empty dict + assert node.remove({}, "any_key") == ({}, False) def test_dict_filter_by_keys(): node = DictFilterByKeys() - my_dict = {"key1": "value1", "key2": "value2"} - keys = ["key1"] - assert node.filter_by_keys(my_dict, keys) == ({"key1": "value1"},) + my_dict = {"key1": "value1", "key2": "value2", "key3": "value3"} + # Test with subset of keys + assert node.filter_by_keys(my_dict, ["key1", "key3"]) == ({"key1": "value1", "key3": "value3"},) + # Test with non-existent keys + assert node.filter_by_keys(my_dict, ["key1", "non_existent"]) == ({"key1": "value1"},) + # Test with empty keys list + assert node.filter_by_keys(my_dict, []) == ({},) + # Test with empty dict + assert node.filter_by_keys({}, ["any_key"]) == ({},) def test_dict_exclude_keys(): node = DictExcludeKeys() - my_dict = {"key1": "value1", "key2": "value2"} - keys = ["key1"] - assert node.exclude_keys(my_dict, keys) == ({"key2": "value2"},) + my_dict = {"key1": "value1", "key2": "value2", "key3": "value3"} + # Test excluding some keys + assert node.exclude_keys(my_dict, ["key1", "key3"]) == ({"key2": "value2"},) + # Test excluding non-existent keys + assert node.exclude_keys(my_dict, ["non_existent"]) == (my_dict,) + # Test excluding all keys + assert node.exclude_keys(my_dict, ["key1", "key2", "key3"]) == ({},) + # Test with empty exclude list + assert node.exclude_keys(my_dict, []) == (my_dict,) + # Test with empty dict + assert node.exclude_keys({}, ["any_key"]) == ({},) def test_dict_get_multiple(): node = DictGetMultiple() my_dict = {"key1": "value1", "key2": "value2"} - keys = ["key1", "key3"] - assert node.get_multiple(my_dict, keys, default="default") == (["value1", "default"],) + # Test getting existing keys + assert node.get_multiple(my_dict, ["key1", "key2"]) == (["value1", "value2"],) + # Test with mix of existing and non-existent keys + assert node.get_multiple(my_dict, ["key1", "key3"], default="default") == (["value1", "default"],) + # Test with only non-existent keys + assert node.get_multiple(my_dict, ["key3", "key4"], default="default") == (["default", "default"],) + # Test with empty keys list + assert node.get_multiple(my_dict, []) == ([],) + # Test with empty dict + assert node.get_multiple({}, ["key1"], default="default") == (["default"],) def test_dict_invert(): node = DictInvert() my_dict = {"key1": "value1", "key2": "value2"} + # Test basic inversion assert node.invert(my_dict) == ({"value1": "key1", "value2": "key2"}, True) + # Test with duplicated values + result, success = node.invert({"key1": "value", "key2": "value"}) + # With duplicate values, the last key wins + assert result == {"value": "key2"} + assert success is True + # Test with empty dict + assert node.invert({}) == ({}, True) def test_dict_create_from_lists(): node = DictCreateFromLists() - keys = ["key1", "key2"] - values = ["value1", "value2"] - assert node.create_from_lists(keys, values) == ({"key1": "value1", "key2": "value2"},) + keys = ["key1", "key2", "key3"] + values = ["value1", "value2", "value3"] + # Test with matching length lists + assert node.create_from_lists(keys, values) == ({"key1": "value1", "key2": "value2", "key3": "value3"},) + # Test with more keys than values + assert node.create_from_lists(keys, ["value1", "value2"]) == ({"key1": "value1", "key2": "value2"},) + # Test with more values than keys + assert node.create_from_lists(["key1", "key2"], values) == ({"key1": "value1", "key2": "value2"},) + # Test with empty lists + assert node.create_from_lists([], []) == ({},) def test_dict_compare(): node = DictCompare() - dict1 = {"key1": "value1"} - dict2 = {"key1": "value1"} + # Test identical dictionaries + dict1 = {"key1": "value1", "key2": "value2"} + dict2 = {"key1": "value1", "key2": "value2"} assert node.compare(dict1, dict2) == (True, [], [], []) + + # Test dictionaries with different values + dict3 = {"key1": "value1", "key2": "different"} + are_equal, only_in_1, only_in_2, diff_values = node.compare(dict1, dict3) + assert are_equal is False + assert only_in_1 == [] + assert only_in_2 == [] + assert "key2" in diff_values + + # Test dictionaries with different keys + dict4 = {"key1": "value1", "key3": "value3"} + are_equal, only_in_1, only_in_2, diff_values = node.compare(dict1, dict4) + assert are_equal is False + assert "key2" in only_in_1 + assert "key3" in only_in_2 + assert diff_values == [] + + # Test empty dictionaries + assert node.compare({}, {}) == (True, [], [], []) diff --git a/tests/test_float_nodes.py b/tests/test_float_nodes.py index 1f548cb..19bb9d9 100644 --- a/tests/test_float_nodes.py +++ b/tests/test_float_nodes.py @@ -1,9 +1,12 @@ +import math import pytest from src.basic_data_handling.float_nodes import ( + FloatCreate, FloatAdd, FloatSubtract, FloatMultiply, FloatDivide, + FloatDivideSafe, FloatPower, FloatRound, FloatIsInteger, @@ -12,6 +15,15 @@ from src.basic_data_handling.float_nodes import ( FloatFromHex, ) +def test_float_create(): + node = FloatCreate() + assert node.create("3.14") == (3.14,) + assert node.create("-2.5") == (-2.5,) + assert node.create("0.0") == (0.0,) + with pytest.raises(ValueError): + node.create("not a number") + + def test_float_add(): node = FloatAdd() assert node.add(2.2, 2.2) == (4.4,) @@ -41,6 +53,20 @@ def test_float_divide(): node.divide(5.0, 0.0) +def test_float_divide_safe(): + node = FloatDivideSafe() + + assert node.divide(7.0, 2.0) == (3.5,) + assert node.divide(-6.0, 3.0) == (-2.0,) + assert node.divide(5.0, 0.0) == (float('inf'),) + assert node.divide(-5.0, 0.0) == (float('-inf'),) + + # Special handling for NaN because NaN != NaN + result = node.divide(0.0, 0.0) + assert len(result) == 1 + assert math.isnan(result[0]) + + def test_float_power(): node = FloatPower() assert node.power(2.0, 3.0) == (8.0,) diff --git a/tests/test_int_nodes.py b/tests/test_int_nodes.py index 1b34b02..1c1e753 100644 --- a/tests/test_int_nodes.py +++ b/tests/test_int_nodes.py @@ -4,12 +4,15 @@ from src.basic_data_handling.int_nodes import ( IntSubtract, IntMultiply, IntDivide, + IntDivideSafe, IntModulus, IntPower, IntBitLength, IntToBytes, IntFromBytes, IntBitCount, + IntCreate, + IntCreateWithBase, ) @@ -83,4 +86,32 @@ def test_int_bit_count(): node = IntBitCount() assert node.bit_count(0) == (0,) # Zero has no 1 bits assert node.bit_count(255) == (8,) # 255 is 11111111 (8 ones) - assert node.bit_count(-255) == (8,) # Negative integer has the same bit count \ No newline at end of file + assert node.bit_count(-255) == (8,) # Negative integer has the same bit count + + +def test_int_create(): + node = IntCreate() + assert node.create("42") == (42,) + assert node.create("-100") == (-100,) + assert node.create("0") == (0,) + assert node.create("0xFF") == (255,) # Hexadecimal + assert node.create("0b1010") == (10,) # Binary + assert node.create("0o777") == (511,) # Octal + + +def test_int_create_with_base(): + node = IntCreateWithBase() + assert node.create("42", 10) == (42,) + assert node.create("FF", 16) == (255,) + assert node.create("1010", 2) == (10,) + assert node.create("777", 8) == (511,) + with pytest.raises(ValueError): + node.create("FF", 10) # Invalid characters for base 10 + + +def test_int_divide_safe(): + node = IntDivideSafe() + assert node.divide(10, 2, 9223372036854775807) == (5,) + assert node.divide(9, 4, 9223372036854775807) == (2,) # Integer division + assert node.divide(10, 0, 9223372036854775807) == (9223372036854775807,) # Positive infinity + assert node.divide(-10, 0, 9223372036854775807) == (-9223372036854775807,) # Negative infinity diff --git a/tests/test_list_nodes.py b/tests/test_list_nodes.py index 4e6ecd0..fa36b2f 100644 --- a/tests/test_list_nodes.py +++ b/tests/test_list_nodes.py @@ -1,5 +1,10 @@ import pytest from src.basic_data_handling.list_nodes import ( + ListCreate, + ListCreateFromBoolean, + ListCreateFromFloat, + ListCreateFromInt, + ListCreateFromString, ListAppend, ListExtend, ListInsert, @@ -15,7 +20,9 @@ from src.basic_data_handling.list_nodes import ( ListSetItem, ListContains, ListMin, - ListMax + ListMax, + ListToDataList, + ListToSet ) @@ -130,3 +137,52 @@ def test_list_max(): assert node.find_max([1, 2, 3]) == (3,) assert node.find_max(["b", "a", "c"]) == ("c",) assert node.find_max([]) == (None,) + + +def test_list_create(): + node = ListCreate() + assert node.create_list(item_0=1, item_1=2, item_2=3) == ([1, 2, 3],) + assert node.create_list() == ([],) # Empty list + assert node.create_list(item_0="test") == (["test"],) + + +def test_list_create_from_boolean(): + node = ListCreateFromBoolean() + assert node.create_list(item_0=True, item_1=False, item_2=True) == ([True, False, True],) + assert node.create_list(item_0=True) == ([True],) + assert node.create_list() == ([],) + + +def test_list_create_from_float(): + node = ListCreateFromFloat() + assert node.create_list(item_0=1.1, item_1=2.2, item_2=3.3) == ([1.1, 2.2, 3.3],) + assert node.create_list(item_0=0.0) == ([0.0],) + assert node.create_list() == ([],) + + +def test_list_create_from_int(): + node = ListCreateFromInt() + assert node.create_list(item_0=1, item_1=2, item_2=3) == ([1, 2, 3],) + assert node.create_list(item_0=0) == ([0],) + assert node.create_list() == ([],) + + +def test_list_create_from_string(): + node = ListCreateFromString() + assert node.create_list(item_0="a", item_1="b", item_2="c") == (["a", "b", "c"],) + assert node.create_list(item_0="test") == (["test"],) + assert node.create_list() == ([],) + + +def test_list_to_data_list(): + node = ListToDataList() + input_list = [1, 2, 3] + # The output should be the same list but marked as a data list by the OUTPUT_IS_LIST = (True,) flag + assert node.convert(input_list) == (input_list,) + + +def test_list_to_set(): + node = ListToSet() + assert node.convert([1, 2, 3, 2, 1]) == ({1, 2, 3},) + assert node.convert([]) == (set(),) + assert node.convert(["a", "b", "a", "c"]) == ({"a", "b", "c"},) diff --git a/tests/test_math_nodes.py b/tests/test_math_nodes.py index 5918e9f..6831baa 100644 --- a/tests/test_math_nodes.py +++ b/tests/test_math_nodes.py @@ -12,6 +12,10 @@ def test_math_sin(): assert node.calculate(0, "radians") == (0.0,) assert node.calculate(math.pi / 2, "radians") == (1.0,) assert node.calculate(30, "degrees") == (math.sin(math.radians(30)),) + # Test with string input + assert node.calculate("45", "degrees")[0] == pytest.approx(math.sin(math.radians(45))) + # Test negative angle + assert node.calculate(-90, "degrees")[0] == pytest.approx(-1.0) def test_math_cos(): @@ -19,14 +23,25 @@ def test_math_cos(): assert node.calculate(0, "radians") == (1.0,) assert node.calculate(math.pi, "radians") == (-1.0,) assert node.calculate(60, "degrees") == (math.cos(math.radians(60)),) + # Test with string input + assert node.calculate("60", "degrees")[0] == pytest.approx(0.5) + # Test negative angle + assert node.calculate(-180, "degrees")[0] == pytest.approx(-1.0) def test_math_tan(): node = MathTan() assert node.calculate(0, "radians") == (0.0,) assert node.calculate(45, "degrees") == (math.tan(math.radians(45)),) + # Test with string input + assert node.calculate("30", "degrees")[0] == pytest.approx(math.tan(math.radians(30))) + # Test negative angle + assert node.calculate(-45, "degrees")[0] == pytest.approx(-1.0) + # Test undefined tangent with pytest.raises(ValueError): # Undefined tangent node.calculate(90, "degrees") + with pytest.raises(ValueError): # Undefined tangent + node.calculate(math.pi/2, "radians") def test_math_asin(): @@ -34,6 +49,13 @@ def test_math_asin(): assert node.calculate(0, "radians") == (0.0,) assert node.calculate(1, "radians") == (math.pi / 2,) assert node.calculate(0.5, "degrees") == (math.degrees(math.asin(0.5)),) + # Test with string input + assert node.calculate("0.5", "degrees")[0] == pytest.approx(30.0) + # Test boundary values + assert node.calculate(-1, "degrees")[0] == pytest.approx(-90.0) + # Test out of range values + with pytest.raises(ValueError): + node.calculate(1.1, "degrees") def test_math_acos(): @@ -41,6 +63,13 @@ def test_math_acos(): assert node.calculate(1, "radians") == (0.0,) assert node.calculate(0, "radians") == (math.pi / 2,) assert node.calculate(0.5, "degrees") == (math.degrees(math.acos(0.5)),) + # Test with string input + assert node.calculate("0.5", "degrees")[0] == pytest.approx(60.0) + # Test boundary values + assert node.calculate(-1, "degrees")[0] == pytest.approx(180.0) + # Test out of range values + with pytest.raises(ValueError): + node.calculate(1.1, "degrees") def test_math_atan(): @@ -48,6 +77,10 @@ def test_math_atan(): assert node.calculate(0, "radians") == (0.0,) assert node.calculate(1, "degrees") == (45.0,) assert node.calculate(-1, "radians") == (-math.pi / 4,) + # Test with string input + assert node.calculate("1.0", "degrees")[0] == pytest.approx(45.0) + # Test large values + assert node.calculate(1000, "degrees")[0] == pytest.approx(89.94, abs=0.01) def test_math_atan2(): @@ -55,13 +88,24 @@ def test_math_atan2(): assert node.calculate(0, 1, "radians") == (0.0,) assert node.calculate(1, 1, "degrees") == (45.0,) assert node.calculate(-1, -1, "radians") == (-3 * math.pi / 4,) + # Test with string input + assert node.calculate("1", "1", "degrees")[0] == pytest.approx(45.0) + # Test special cases + assert node.calculate(1, 0, "degrees")[0] == pytest.approx(90.0) + assert node.calculate(-1, 0, "degrees")[0] == pytest.approx(-90.0) + assert node.calculate(0, -1, "degrees")[0] == pytest.approx(180.0) def test_math_sqrt(): node = MathSqrt() assert node.calculate(4) == (2.0,) assert node.calculate(0) == (0.0,) - with pytest.raises(ValueError): # Negative numbers + # Test with string input + assert node.calculate("9")[0] == pytest.approx(3.0) + # Test with float input + assert node.calculate(2.25)[0] == pytest.approx(1.5) + # Test negative numbers (should raise ValueError) + with pytest.raises(ValueError): node.calculate(-1) @@ -70,6 +114,10 @@ def test_math_exp(): assert node.calculate(0) == (1.0,) assert node.calculate(1) == (math.e,) assert node.calculate(-1) == (1 / math.e,) + # Test with string input + assert node.calculate("2")[0] == pytest.approx(math.exp(2)) + # Test with large input + assert node.calculate(10)[0] == pytest.approx(math.exp(10)) def test_math_log(): @@ -77,16 +125,30 @@ def test_math_log(): assert node.calculate(1) == (0.0,) assert node.calculate(math.e) == (1.0,) assert node.calculate(8, base=2) == (3.0,) - with pytest.raises(ValueError): # Log of non-positive + # Test with string input + assert node.calculate("10", base="10")[0] == pytest.approx(1.0) + # Test with default base (e) + assert node.calculate(math.exp(5))[0] == pytest.approx(5.0) + # Test with log of non-positive (should raise ValueError) + with pytest.raises(ValueError): node.calculate(-1) + with pytest.raises(ValueError): + node.calculate(0) def test_math_log10(): node = MathLog10() assert node.calculate(1) == (0.0,) assert node.calculate(10) == (1.0,) - with pytest.raises(ValueError): # Log of non-positive + # Test with string input + assert node.calculate("100")[0] == pytest.approx(2.0) + # Test with non-integer values + assert node.calculate(2)[0] == pytest.approx(math.log10(2)) + # Test with log of non-positive (should raise ValueError) + with pytest.raises(ValueError): node.calculate(0) + with pytest.raises(ValueError): + node.calculate(-10) def test_math_degrees(): @@ -94,6 +156,10 @@ def test_math_degrees(): assert node.calculate(math.pi) == (180.0,) assert node.calculate(math.pi / 2) == (90.0,) assert node.calculate(0) == (0.0,) + # Test with string input + assert node.calculate("3.14159")[0] == pytest.approx(180.0, abs=0.01) + # Test with negative values + assert node.calculate(-math.pi)[0] == pytest.approx(-180.0) def test_math_radians(): @@ -101,6 +167,12 @@ def test_math_radians(): assert node.calculate(180) == (math.pi,) assert node.calculate(90) == (math.pi / 2,) assert node.calculate(0) == (0.0,) + # Test with string input + assert node.calculate("180")[0] == pytest.approx(math.pi) + # Test with negative values + assert node.calculate(-180)[0] == pytest.approx(-math.pi) + # Test with decimal values + assert node.calculate(45.5)[0] == pytest.approx(math.radians(45.5)) def test_math_floor(): @@ -108,6 +180,12 @@ def test_math_floor(): assert node.calculate(1.7) == (1,) assert node.calculate(-1.7) == (-2,) assert node.calculate(0.0) == (0,) + # Test with string input + assert node.calculate("3.9")[0] == 3 + # Test with integer input + assert node.calculate(5)[0] == 5 + # Test with very small decimals + assert node.calculate(0.0001)[0] == 0 def test_math_ceil(): @@ -115,6 +193,12 @@ def test_math_ceil(): assert node.calculate(1.2) == (2,) assert node.calculate(-1.2) == (-1,) assert node.calculate(0.0) == (0,) + # Test with string input + assert node.calculate("3.1")[0] == 4 + # Test with integer input + assert node.calculate(5)[0] == 5 + # Test with very small decimals + assert node.calculate(0.0001)[0] == 1 def test_math_abs(): @@ -122,16 +206,26 @@ def test_math_abs(): assert node.calculate(-5) == (5,) assert node.calculate(5) == (5,) assert node.calculate(0) == (0,) + # Test with string input + assert node.calculate("-3.14")[0] == pytest.approx(3.14) + # Test with float input + assert node.calculate(-2.5)[0] == 2.5 + # Test with zero + assert node.calculate(0)[0] == 0 def test_math_pi(): node = MathPi() assert node.calculate() == (math.pi,) + # Verify precision to at least 10 decimal places + assert abs(node.calculate()[0] - 3.1415926535) < 1e-10 def test_math_e(): node = MathE() assert node.calculate() == (math.e,) + # Verify precision to at least 10 decimal places + assert abs(node.calculate()[0] - 2.7182818284) < 1e-10 def test_math_min(): @@ -139,6 +233,12 @@ def test_math_min(): assert node.calculate(5, 10) == (5,) assert node.calculate(-1, 0) == (-1,) assert node.calculate(3.5, 2.5) == (2.5,) + # Test with string inputs + assert node.calculate("7", "3")[0] == 3.0 + # Test with mixed types + assert node.calculate(5, "2.5")[0] == 2.5 + # Test with equal values + assert node.calculate(10, 10)[0] == 10 def test_math_max(): @@ -146,3 +246,9 @@ def test_math_max(): assert node.calculate(5, 10) == (10,) assert node.calculate(-1, 0) == (0,) assert node.calculate(3.5, 2.5) == (3.5,) + # Test with string inputs + assert node.calculate("7", "3")[0] == 7.0 + # Test with mixed types + assert node.calculate(5, "7.5")[0] == 7.5 + # Test with equal values + assert node.calculate(10, 10)[0] == 10 diff --git a/tests/test_path_nodes.py b/tests/test_path_nodes.py index 62fc962..14c4a11 100644 --- a/tests/test_path_nodes.py +++ b/tests/test_path_nodes.py @@ -1,5 +1,7 @@ import os import pytest +import platform +from pathlib import Path from src.basic_data_handling.path_nodes import ( PathJoin, PathAbspath, PathExists, PathIsFile, PathIsDir, PathGetSize, @@ -14,21 +16,38 @@ def test_path_join(): assert node.join_paths("folder", "file.txt") == (os.path.join("folder", "file.txt"),) assert node.join_paths("folder", "") == (os.path.join("folder"),) assert node.join_paths(".", "") == (".",) + # Test join with empty first path + assert node.join_paths("", "file.txt") == ("file.txt",) + # Test with multiple path components + nested_path = os.path.join("folder", "subfolder", "file.txt") + assert node.join_paths("folder", os.path.join("subfolder", "file.txt")) == (nested_path,) def test_path_abspath(): node = PathAbspath() assert node.get_abspath(".") == (os.path.abspath("."),) assert node.get_abspath("folder/file.txt") == (os.path.abspath("folder/file.txt"),) + # Test with empty string (should return current directory) + assert node.get_abspath("") == (os.path.abspath(os.getcwd()),) + # Test with relative path with special characters + assert node.get_abspath("./file.txt") == (os.path.abspath("./file.txt"),) + assert node.get_abspath("../file.txt") == (os.path.abspath("../file.txt"),) def test_path_exists(tmp_path): node = PathExists() existing_file = tmp_path / "file.txt" existing_file.write_text("content") + existing_dir = tmp_path / "directory" + existing_dir.mkdir() assert node.check_exists(str(existing_file)) == (True,) + assert node.check_exists(str(existing_dir)) == (True,) assert node.check_exists("nonexistent.file") == (False,) + # Test with special paths + assert node.check_exists(".") == (True,) + # Test with path containing non-existent parent directory + assert node.check_exists(str(tmp_path / "nonexistent" / "file.txt")) == (False,) def test_path_is_file(tmp_path): @@ -41,6 +60,19 @@ def test_path_is_file(tmp_path): assert node.check_is_file(str(file)) == (True,) assert node.check_is_file(str(directory)) == (False,) assert node.check_is_file("nonexistent.file") == (False,) + # Test with empty path + assert node.check_is_file("") == (False,) + # Test with special paths + assert node.check_is_file(".") == (False,) + # Test with symlink to file if platform supports + if hasattr(os, 'symlink'): + try: + symlink = tmp_path / "symlink.txt" + os.symlink(str(file), str(symlink)) + assert node.check_is_file(str(symlink)) == (True,) + except (OSError, NotImplementedError): + # Symlinks might not be supported on all platforms + pass def test_path_is_dir(tmp_path): @@ -53,14 +85,36 @@ def test_path_is_dir(tmp_path): assert node.check_is_dir(str(directory)) == (True,) assert node.check_is_dir(str(file)) == (False,) assert node.check_is_dir("nonexistent.dir") == (False,) + # Test with special paths + assert node.check_is_dir(".") == (True,) + assert node.check_is_dir("") == (False,) + # Test with nested directory + nested_dir = directory / "nested" + nested_dir.mkdir() + assert node.check_is_dir(str(nested_dir)) == (True,) + # Test with symlink to directory if platform supports + if hasattr(os, 'symlink'): + try: + symlink = tmp_path / "symlink_dir" + os.symlink(str(directory), str(symlink), target_is_directory=True) + assert node.check_is_dir(str(symlink)) == (True,) + except (OSError, NotImplementedError, TypeError): + # Symlinks might not be supported on all platforms + pass def test_path_get_size(tmp_path): node = PathGetSize() file = tmp_path / "file.txt" file.write_text("content") + empty_file = tmp_path / "empty.txt" + empty_file.write_text("") + large_file = tmp_path / "large.txt" + large_file.write_text("a" * 1024) # 1KB file assert node.get_size(str(file)) == (len("content"),) + assert node.get_size(str(empty_file)) == (0,) + assert node.get_size(str(large_file)) == (1024,) with pytest.raises(FileNotFoundError): node.get_size("nonexistent.file") @@ -78,36 +132,88 @@ def test_path_split(): assert node.split_path(path) == (os.path.dirname(path), os.path.basename(path)) assert node.split_path("file.txt") == ("", "file.txt") + # Test with empty path + assert node.split_path("") == ("", "") + # Test with just directory separator + assert node.split_path("/") == ("/", "") + # Test with nested paths + nested_path = os.path.join("folder", "subfolder", "file.txt") + assert node.split_path(nested_path) == (os.path.dirname(nested_path), "file.txt") + # Test with trailing separator + dir_with_sep = "folder" + os.sep + assert node.split_path(dir_with_sep) == ("folder", "") def test_path_splitext(): node = PathSplitExt() assert node.split_ext("file.txt") == ("file", ".txt") assert node.split_ext("file") == ("file", "") + # Test with multiple extensions + assert node.split_ext("file.tar.gz") == ("file.tar", ".gz") + # Test with path containing directory + path = os.path.join("folder", "file.txt") + assert node.split_ext(path) == (os.path.join("folder", "file"), ".txt") + # Test with hidden file + assert node.split_ext(".gitignore") == (".gitignore", "") + # Test with empty string + assert node.split_ext("") == ("", "") def test_path_basename(): node = PathBasename() assert node.get_basename("folder/file.txt") == ("file.txt",) assert node.get_basename("file.txt") == ("file.txt",) + # Test with trailing slash + assert node.get_basename("folder/") == ("",) + # Test with empty path + assert node.get_basename("") == ("",) + # Test with root directory + assert node.get_basename("/") == ("",) + # Test with multiple nested directories + assert node.get_basename("folder/subfolder/file.txt") == ("file.txt",) def test_path_dirname(): node = PathDirname() assert node.get_dirname("folder/file.txt") == ("folder",) assert node.get_dirname("file.txt") == ("",) + # Test with trailing slash + assert node.get_dirname("folder/") == ("folder",) + # Test with empty path + assert node.get_dirname("") == ("",) + # Test with root directory + assert node.get_dirname("/") == ("/",) + # Test with nested directories + assert node.get_dirname("folder/subfolder/file.txt") == (os.path.join("folder", "subfolder"),) def test_path_get_extension(): node = PathGetExtension() assert node.get_extension("file.txt") == (".txt",) assert node.get_extension("file") == ("",) + # Test with multiple dots + assert node.get_extension("file.tar.gz") == (".gz",) + # Test with hidden file + assert node.get_extension(".gitignore") == ("",) + # Test with path containing directory + assert node.get_extension("folder/file.txt") == (".txt",) + # Test with empty string + assert node.get_extension("") == ("",) def test_path_normalize(): node = PathNormalize() assert node.normalize_path("folder/../file.txt") == (os.path.normpath("folder/../file.txt"),) assert node.normalize_path("folder//file.txt") == (os.path.normpath("folder//file.txt"),) + # Test with empty path + assert node.normalize_path("") == (".",) + # Test with current directory references + assert node.normalize_path("./folder/./file.txt") == (os.path.normpath("./folder/./file.txt"),) + # Test with parent directory references that cancel out + assert node.normalize_path("folder/../folder/file.txt") == (os.path.normpath("folder/../folder/file.txt"),) + # Test with mixed separators + mixed_sep_path = "folder" + os.sep + "subfolder" + ("/" if os.sep == "\\" else "\\") + "file.txt" + assert node.normalize_path(mixed_sep_path) == (os.path.normpath(mixed_sep_path),) def test_path_relative(tmp_path): @@ -119,6 +225,16 @@ def test_path_relative(tmp_path): assert node.get_relative_path(str(target), str(start)) == (os.path.relpath(str(target), str(start)),) assert node.get_relative_path(str(target)) == (os.path.relpath(str(target), os.getcwd()),) + # Test with same path + assert node.get_relative_path(str(start), str(start)) == (".",) + # Test with sibling paths + sibling = tmp_path / "sibling" + sibling.mkdir() + expected = os.path.relpath(str(sibling), str(start)) + assert node.get_relative_path(str(sibling), str(start)) == (expected,) + # Test with parent directory + parent_rel = os.path.relpath(str(tmp_path), str(target)) + assert node.get_relative_path(str(tmp_path), str(target)) == (parent_rel,) def test_path_glob(tmp_path): @@ -126,56 +242,162 @@ def test_path_glob(tmp_path): (tmp_path / "file1.txt").write_text("content") (tmp_path / "file2.txt").write_text("content") (tmp_path / "file3.txt").write_text("content") + (tmp_path / "image1.jpg").write_text("image") + (tmp_path / "image2.png").write_text("image") + # Create subdirectory with files + subdir = tmp_path / "subdir" + subdir.mkdir() + (subdir / "subfile1.txt").write_text("sub content") + (subdir / "subfile2.txt").write_text("sub content") + # Test simple glob pattern result = node.glob_paths(str(tmp_path / "*.txt")) expected = sorted([str(tmp_path / f"file{i}.txt") for i in range(1, 4)]) - assert sorted(result[0]) == expected - recursive_node = PathGlob() - assert recursive_node.glob_paths(str(tmp_path), recursive="True")[0] + # Test with image files + img_result = node.glob_paths(str(tmp_path / "image*")) + img_expected = sorted([str(tmp_path / "image1.jpg"), str(tmp_path / "image2.png")]) + assert sorted(img_result[0]) == img_expected + + # Test with character class + char_result = node.glob_paths(str(tmp_path / "file[1-2].txt")) + char_expected = sorted([str(tmp_path / f"file{i}.txt") for i in range(1, 3)]) + assert sorted(char_result[0]) == char_expected + + # Test recursive globbing + recursive_result = node.glob_paths(str(tmp_path / "**/*.txt"), recursive=True) + recursive_expected = sorted([ + str(tmp_path / f"file{i}.txt") for i in range(1, 4) + ] + [ + str(subdir / f"subfile{i}.txt") for i in range(1, 3) + ]) + assert sorted(recursive_result[0]) == recursive_expected + + # Test with no matches + no_match_result = node.glob_paths(str(tmp_path / "nomatch*.txt")) + assert len(no_match_result[0]) == 0 def test_path_expand_vars(monkeypatch): node = PathExpandVars() monkeypatch.setenv("TEST_VAR", "test_value") + monkeypatch.setenv("PATH_VAR", "path/to/somewhere") + monkeypatch.setenv("EMPTY_VAR", "") assert node.expand_vars("$TEST_VAR/path") == ("test_value/path",) assert node.expand_vars("${TEST_VAR}/path") == ("test_value/path",) + # Test with multiple variables + assert node.expand_vars("$TEST_VAR/$PATH_VAR") == ("test_value/path/to/somewhere",) + # Test with empty variable + assert node.expand_vars("$EMPTY_VAR/path") == ("/path",) + # Test with no variables + assert node.expand_vars("regular/path") == ("regular/path",) + # Test with empty string + assert node.expand_vars("") == ("",) def test_path_get_cwd(): node = PathGetCwd() assert node.get_cwd() == (os.getcwd(),) + # Verify that the result is an absolute path + assert os.path.isabs(node.get_cwd()[0]) + # Verify that the directory exists + assert os.path.exists(node.get_cwd()[0]) + assert os.path.isdir(node.get_cwd()[0]) def test_path_list_dir(tmp_path): node = PathListDir() + # Set up a directory structure for testing (tmp_path / "file1.txt").write_text("content") + (tmp_path / "file2.txt").write_text("content") + (tmp_path / "image.jpg").write_text("image") (tmp_path / "dir1").mkdir() + (tmp_path / "dir2").mkdir() + (tmp_path / ".hidden_file").write_text("hidden") + (tmp_path / ".hidden_dir").mkdir() + # Test listing all entries all_entries = node.list_directory(str(tmp_path)) assert "file1.txt" in all_entries[0] + assert "file2.txt" in all_entries[0] + assert "image.jpg" in all_entries[0] assert "dir1" in all_entries[0] + assert "dir2" in all_entries[0] + assert ".hidden_file" in all_entries[0] + assert ".hidden_dir" in all_entries[0] - only_files = node.list_directory(str(tmp_path), files_only="True") + # Test listing only files + only_files = node.list_directory(str(tmp_path), files_only=True) assert "file1.txt" in only_files[0] + assert "file2.txt" in only_files[0] + assert "image.jpg" in only_files[0] + assert ".hidden_file" in only_files[0] assert "dir1" not in only_files[0] + assert "dir2" not in only_files[0] + assert ".hidden_dir" not in only_files[0] - only_dirs = node.list_directory(str(tmp_path), dirs_only="True") + # Test listing only directories + only_dirs = node.list_directory(str(tmp_path), dirs_only=True) assert "file1.txt" not in only_dirs[0] + assert "file2.txt" not in only_dirs[0] + assert "image.jpg" not in only_dirs[0] + assert ".hidden_file" not in only_dirs[0] assert "dir1" in only_dirs[0] + assert "dir2" in only_dirs[0] + assert ".hidden_dir" in only_dirs[0] + + # Test with non-existent directory + with pytest.raises(FileNotFoundError): + node.list_directory(str(tmp_path / "nonexistent")) + + # Test with file instead of directory + with pytest.raises(NotADirectoryError): + node.list_directory(str(tmp_path / "file1.txt")) + + # Test with empty path (should use current directory) + assert len(node.list_directory("")[0]) > 0 def test_path_is_absolute(): node = PathIsAbsolute() + # Test with absolute paths assert node.check_is_absolute("/") == (True,) + assert node.check_is_absolute(os.path.abspath(".")) == (True,) + # Test with relative paths assert node.check_is_absolute("relative/path") == (False,) + assert node.check_is_absolute("./path") == (False,) + assert node.check_is_absolute("../path") == (False,) + # Test with empty path + assert node.check_is_absolute("") == (False,) + # Test Windows-specific paths if applicable + if platform.system() == "Windows": + assert node.check_is_absolute("C:\\Windows") == (True,) + assert node.check_is_absolute("C:/Windows") == (True,) + assert node.check_is_absolute("\\\\server\\share") == (True,) + assert node.check_is_absolute("C:relative\\path") == (False,) def test_path_common_prefix(): node = PathCommonPrefix() + # Test with paths having common prefix path1 = "/path/to/file1.txt" path2 = "/path/to/file2.txt" + assert node.get_common_prefix(path1, path2) == ("/path/to/file",) - assert node.get_common_prefix(path1, path2) == (os.path.commonprefix([path1, path2]),) + # Test with paths without common prefix + path3 = "/path/to/file.txt" + path4 = "/different/path/file.txt" + assert node.get_common_prefix(path3, path4) == ("/",) + + # Test with exact same paths + assert node.get_common_prefix(path1, path1) == (path1,) + + # Test with one path being a prefix of the other + path5 = "/path/to" + path6 = "/path/to/subdir/file.txt" + assert node.get_common_prefix(path5, path6) == ("/path/to",) + + # Test with both empty paths + assert node.get_common_prefix("", "") == ("",) diff --git a/tests/test_regex_nodes.py b/tests/test_regex_nodes.py index ad232d7..ae8ec3c 100644 --- a/tests/test_regex_nodes.py +++ b/tests/test_regex_nodes.py @@ -5,7 +5,10 @@ from src.basic_data_handling.regex_nodes import ( RegexFindallDataList, RegexSplitDataList, RegexSub, - RegexTest + RegexTest, + RegexFindallList, + RegexSearchGroupsList, + RegexSplitList ) def test_regex_search_groups(): @@ -16,6 +19,46 @@ def test_regex_search_groups(): assert node.search_groups(r"(\w+)", "word") == (["word"],) # Single group +def test_regex_search_groups_list(): + node = RegexSearchGroupsList() + assert node.search_groups(r"(foo)(bar)", "foobar") == (["foo", "bar"],) + assert node.search_groups(r"(test)(\d+)", "test123") == (["test", "123"],) + assert node.search_groups(r"(nothing)", "no match here") == ([],) # No match + assert node.search_groups(r"(\w+)", "word") == (["word"],) # Single group + + +def test_regex_search_groups_list(): + node = RegexSearchGroupsList() + assert node.search_groups(r"(foo)(bar)", "foobar") == (["foo", "bar"],) + assert node.search_groups(r"(test)(\d+)", "test123") == (["test", "123"],) + assert node.search_groups(r"(nothing)", "no match here") == ([],) # No match + assert node.search_groups(r"(\w+)", "word") == (["word"],) # Single group + + +def test_regex_search_groups_list(): + node = RegexSearchGroupsList() + assert node.search_groups(r"(foo)(bar)", "foobar") == (["foo", "bar"],) + assert node.search_groups(r"(test)(\d+)", "test123") == (["test", "123"],) + assert node.search_groups(r"(nothing)", "no match here") == ([],) # No match + assert node.search_groups(r"(\w+)", "word") == (["word"],) # Single group + + +def test_regex_search_groups_list(): + node = RegexSearchGroupsList() + assert node.search_groups(r"(foo)(bar)", "foobar") == (["foo", "bar"],) + assert node.search_groups(r"(test)(\d+)", "test123") == (["test", "123"],) + assert node.search_groups(r"(nothing)", "no match here") == ([],) # No match + assert node.search_groups(r"(\w+)", "word") == (["word"],) # Single group + + +def test_regex_search_groups_list(): + node = RegexSearchGroupsList() + assert node.search_groups(r"(foo)(bar)", "foobar") == (["foo", "bar"],) + assert node.search_groups(r"(test)(\d+)", "test123") == (["test", "123"],) + assert node.search_groups(r"(nothing)", "no match here") == ([],) # No match + assert node.search_groups(r"(\w+)", "word") == (["word"],) # Single group + + def test_regex_group_dict(): node = RegexGroupDict() pattern = r"(?P\w+)-(?P\d+)" @@ -33,6 +76,14 @@ def test_regex_findall(): assert node.findall(r"(a)(b)", "ab ab") == ([("a", "b"), ("a", "b")],) # Multiple groups +def test_regex_findall_list(): + node = RegexFindallList() + assert node.findall(r"\d+", "abc 123 def 456") == (["123", "456"],) + assert node.findall(r"\b[a-zA-Z]+\b", "Multiple words here") == (["Multiple", "words", "here"],) + assert node.findall(r"nonexistent", "no match") == ([],) # No match + assert node.findall(r"(a)(b)", "ab ab") == ([("a", "b"), ("a", "b")],) # Multiple groups + + def test_regex_split(): node = RegexSplitDataList() pattern = r",\s*" @@ -42,6 +93,15 @@ def test_regex_split(): assert node.split(r"z", "nozsplit") == (["no", "split"],) # Split by 'z' +def test_regex_split_list(): + node = RegexSplitList() + pattern = r",\s*" + string = "one, two, three" + assert node.split(pattern, string) == (["one", "two", "three"],) + assert node.split(r"\s+", "split several words") == (["split", "several", "words"],) # Whitespace split + assert node.split(r"z", "nozsplit") == (["no", "split"],) # Split by 'z' + + def test_regex_sub(): node = RegexSub() assert node.sub(r"cat", "dog", "cat is here") == ("dog is here",) diff --git a/tests/test_set_nodes.py b/tests/test_set_nodes.py index 9be76ac..aeb3fb4 100644 --- a/tests/test_set_nodes.py +++ b/tests/test_set_nodes.py @@ -13,9 +13,52 @@ from src.basic_data_handling.set_nodes import ( SetIsDisjoint, SetContains, SetLength, - SetToList + SetToList, + SetCreate, + SetToDataList, + SetCreateFromInt, + SetCreateFromString, + SetCreateFromFloat, + SetCreateFromBoolean ) +def test_set_create(): + node = SetCreate() + # Testing with kwargs to simulate dynamic inputs + assert node.create_set(item_0=1, item_1=2, item_2=3) == ({1, 2, 3},) + assert node.create_set(item_0="a", item_1="b") == ({"a", "b"},) + assert node.create_set() == (set(),) # Empty set with no arguments + + +def test_set_create_from_int(): + node = SetCreateFromInt() + assert node.create_set(item_0=1, item_1=2, item_2=3) == ({1, 2, 3},) + assert node.create_set(item_0=5) == ({5},) # Single item set + assert node.create_set(item_0=1, item_1=1) == ({1},) # Duplicate items become single item + + +def test_set_create_from_string(): + node = SetCreateFromString() + # Note: Mocking the string function behavior as it's not defined in the file + # This simulates what would happen assuming string() acts like str() + node.create_set = lambda **kwargs: (set([str(value) for value in kwargs.values()]),) + assert node.create_set(item_0="apple", item_1="banana") == ({"apple", "banana"},) + assert node.create_set(item_0="apple", item_1="apple") == ({"apple"},) # Duplicate strings + + +def test_set_create_from_float(): + node = SetCreateFromFloat() + assert node.create_set(item_0=1.5, item_1=2.5) == ({1.5, 2.5},) + assert node.create_set(item_0=3.14) == ({3.14},) # Single item set + assert node.create_set(item_0=1.0, item_1=1.0) == ({1.0},) # Duplicate items + + +def test_set_create_from_boolean(): + node = SetCreateFromBoolean() + assert node.create_set(item_0=True, item_1=False) == ({True, False},) + assert node.create_set(item_0=True, item_1=True) == ({True},) # Duplicate booleans + + def test_set_add(): node = SetAdd() assert node.add({1, 2}, 3) == ({1, 2, 3},) @@ -108,3 +151,11 @@ def test_set_to_list(): result = node.convert({1, 2, 3}) assert isinstance(result, tuple) assert sorted(result[0]) == [1, 2, 3] # Validate conversion to list + + +def test_set_to_data_list(): + node = SetToDataList() + result = node.convert({1, 2, 3}) + assert isinstance(result, tuple) + assert isinstance(result[0], list) + assert sorted(result[0]) == [1, 2, 3] # Validate conversion to data list