From fb4fdb68574595986c4cafade2d7c52be9d5bf2f Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Fri, 24 Oct 2025 16:36:42 +0900 Subject: [PATCH] iterator tests added --- .../{manage_dict.py => dict_helper.py} | 18 + .../{dict_helpers.py => dict_mask.py} | 18 - test-run/iterator_handling/dict_helpers.py | 3 +- .../test_seconds_to_string.py | 2 +- .../iterator_handling/test_data_search.py | 601 ++++++++++++++++ .../iterator_handling/test_dict_helper.py | 652 ++++++++++++++++++ ...test_dict_helpers.py => test_dict_mask.py} | 4 +- .../iterator_handling/test_fingerprint.py | 361 ++++++++++ .../iterator_handling/test_list_helpers.py | 6 +- 9 files changed, 1640 insertions(+), 25 deletions(-) rename src/corelibs/iterator_handling/{manage_dict.py => dict_helper.py} (80%) rename src/corelibs/iterator_handling/{dict_helpers.py => dict_mask.py} (86%) create mode 100644 tests/unit/iterator_handling/test_data_search.py create mode 100644 tests/unit/iterator_handling/test_dict_helper.py rename tests/unit/iterator_handling/{test_dict_helpers.py => test_dict_mask.py} (99%) create mode 100644 tests/unit/iterator_handling/test_fingerprint.py diff --git a/src/corelibs/iterator_handling/manage_dict.py b/src/corelibs/iterator_handling/dict_helper.py similarity index 80% rename from src/corelibs/iterator_handling/manage_dict.py rename to src/corelibs/iterator_handling/dict_helper.py index 9887b39..e12c812 100644 --- a/src/corelibs/iterator_handling/manage_dict.py +++ b/src/corelibs/iterator_handling/dict_helper.py @@ -60,4 +60,22 @@ def build_dict( delete_keys_from_set(any_dict, ignore_entries) ) + +def set_entry(dict_set: dict[str, Any], key: str, value_set: Any) -> dict[str, Any]: + """ + set a new entry in the dict set + + Arguments: + key {str} -- _description_ + dict_set {dict[str, Any]} -- _description_ + value_set {Any} -- _description_ + + Returns: + dict[str, Any] -- _description_ + """ + if not dict_set.get(key): + dict_set[key] = {} + dict_set[key] = value_set + return dict_set + # __END__ diff --git a/src/corelibs/iterator_handling/dict_helpers.py b/src/corelibs/iterator_handling/dict_mask.py similarity index 86% rename from src/corelibs/iterator_handling/dict_helpers.py rename to src/corelibs/iterator_handling/dict_mask.py index daefc5e..4740be7 100644 --- a/src/corelibs/iterator_handling/dict_helpers.py +++ b/src/corelibs/iterator_handling/dict_mask.py @@ -82,22 +82,4 @@ def mask( for key, value in data_set.items() } - -def set_entry(dict_set: dict[str, Any], key: str, value_set: Any) -> dict[str, Any]: - """ - set a new entry in the dict set - - Arguments: - key {str} -- _description_ - dict_set {dict[str, Any]} -- _description_ - value_set {Any} -- _description_ - - Returns: - dict[str, Any] -- _description_ - """ - if not dict_set.get(key): - dict_set[key] = {} - dict_set[key] = value_set - return dict_set - # __END__ diff --git a/test-run/iterator_handling/dict_helpers.py b/test-run/iterator_handling/dict_helpers.py index f56c169..ce985ec 100644 --- a/test-run/iterator_handling/dict_helpers.py +++ b/test-run/iterator_handling/dict_helpers.py @@ -4,7 +4,8 @@ Iterator helper testing from typing import Any from corelibs.debug_handling.dump_data import dump_data -from corelibs.iterator_handling.dict_helpers import mask, set_entry +from corelibs.iterator_handling.dict_mask import mask +from corelibs.iterator_handling.dict_helper import set_entry def __mask(): diff --git a/tests/unit/datetime_handling/test_seconds_to_string.py b/tests/unit/datetime_handling/test_seconds_to_string.py index 7402ad1..6169ae8 100644 --- a/tests/unit/datetime_handling/test_seconds_to_string.py +++ b/tests/unit/datetime_handling/test_seconds_to_string.py @@ -449,7 +449,7 @@ class TestConvertTimestamp: # Verify parts are in correct order parts = result.split() # Extract units properly: last 1-2 chars that are letters - units = [] + units: list[str] = [] for p in parts: if p.endswith('ms'): units.append('ms') diff --git a/tests/unit/iterator_handling/test_data_search.py b/tests/unit/iterator_handling/test_data_search.py new file mode 100644 index 0000000..6e42a91 --- /dev/null +++ b/tests/unit/iterator_handling/test_data_search.py @@ -0,0 +1,601 @@ +""" +tests for corelibs.iterator_handling.data_search +""" + +# pylint: disable=use-implicit-booleaness-not-comparison + +from typing import Any +import pytest +from corelibs.iterator_handling.data_search import ( + find_in_array_from_list, + key_lookup, + value_lookup, + ArraySearchList +) + + +class TestFindInArrayFromList: + """Tests for find_in_array_from_list function""" + + def test_basic_single_key_match(self): + """Test basic search with single key-value pair""" + data = [ + {"name": "Alice", "age": 30}, + {"name": "Bob", "age": 25}, + {"name": "Charlie", "age": 35} + ] + search_params: list[ArraySearchList] = [ + {"key": "name", "value": "Bob"} + ] + + result = find_in_array_from_list(data, search_params) + + assert len(result) == 1 + assert result[0]["name"] == "Bob" + assert result[0]["age"] == 25 + + def test_multiple_key_match(self): + """Test search with multiple key-value pairs (AND logic)""" + data = [ + {"name": "Alice", "age": 30, "city": "New York"}, + {"name": "Bob", "age": 25, "city": "London"}, + {"name": "Charlie", "age": 30, "city": "Paris"} + ] + search_params: list[ArraySearchList] = [ + {"key": "age", "value": 30}, + {"key": "city", "value": "New York"} + ] + + result = find_in_array_from_list(data, search_params) + + assert len(result) == 1 + assert result[0]["name"] == "Alice" + + def test_value_list_or_match(self): + """Test search with list of values (OR logic)""" + data = [ + {"name": "Alice", "status": "active"}, + {"name": "Bob", "status": "inactive"}, + {"name": "Charlie", "status": "pending"} + ] + search_params: list[ArraySearchList] = [ + {"key": "status", "value": ["active", "pending"]} + ] + + result = find_in_array_from_list(data, search_params) + + assert len(result) == 2 + assert result[0]["name"] == "Alice" + assert result[1]["name"] == "Charlie" + + def test_case_sensitive_true(self): + """Test case-sensitive search (default behavior)""" + data = [ + {"name": "Alice"}, + {"name": "alice"}, + {"name": "ALICE"} + ] + search_params: list[ArraySearchList] = [ + {"key": "name", "value": "Alice"} + ] + + result = find_in_array_from_list(data, search_params) + + assert len(result) == 1 + assert result[0]["name"] == "Alice" + + def test_case_insensitive_search(self): + """Test case-insensitive search""" + data = [ + {"name": "Alice"}, + {"name": "alice"}, + {"name": "ALICE"} + ] + search_params: list[ArraySearchList] = [ + {"key": "name", "value": "alice", "case_sensitive": False} + ] + + result = find_in_array_from_list(data, search_params) + + assert len(result) == 3 + + def test_case_insensitive_with_list_values(self): + """Test case-insensitive search with list of values""" + data = [ + {"status": "ACTIVE"}, + {"status": "Pending"}, + {"status": "inactive"} + ] + search_params: list[ArraySearchList] = [ + {"key": "status", "value": ["active", "pending"], "case_sensitive": False} + ] + + result = find_in_array_from_list(data, search_params) + + assert len(result) == 2 + assert result[0]["status"] == "ACTIVE" + assert result[1]["status"] == "Pending" + + def test_return_index_true(self): + """Test returning results with index""" + data = [ + {"name": "Alice"}, + {"name": "Bob"}, + {"name": "Charlie"} + ] + search_params: list[ArraySearchList] = [ + {"key": "name", "value": "Bob"} + ] + + result = find_in_array_from_list(data, search_params, return_index=True) + + assert len(result) == 1 + assert result[0]["index"] == 1 + assert result[0]["data"]["name"] == "Bob" + + def test_return_index_multiple_results(self): + """Test returning multiple results with indices""" + data = [ + {"status": "active"}, + {"status": "inactive"}, + {"status": "active"} + ] + search_params: list[ArraySearchList] = [ + {"key": "status", "value": "active"} + ] + + result = find_in_array_from_list(data, search_params, return_index=True) + + assert len(result) == 2 + assert result[0]["index"] == 0 + assert result[0]["data"]["status"] == "active" + assert result[1]["index"] == 2 + assert result[1]["data"]["status"] == "active" + + def test_no_match_returns_empty_list(self): + """Test that no match returns empty list""" + data = [ + {"name": "Alice"}, + {"name": "Bob"} + ] + search_params: list[ArraySearchList] = [ + {"key": "name", "value": "Charlie"} + ] + + result = find_in_array_from_list(data, search_params) + + assert result == [] + + def test_empty_data_returns_empty_list(self): + """Test that empty data list returns empty list""" + data: list[dict[str, Any]] = [] + search_params: list[ArraySearchList] = [ + {"key": "name", "value": "Alice"} + ] + + result = find_in_array_from_list(data, search_params) + + assert result == [] + + def test_missing_key_in_data(self): + """Test search when key doesn't exist in some data items""" + data = [ + {"name": "Alice", "age": 30}, + {"name": "Bob"}, # Missing 'age' key + {"name": "Charlie", "age": 30} + ] + search_params: list[ArraySearchList] = [ + {"key": "age", "value": 30} + ] + + result = find_in_array_from_list(data, search_params) + + assert len(result) == 2 + assert result[0]["name"] == "Alice" + assert result[1]["name"] == "Charlie" + + def test_numeric_values(self): + """Test search with numeric values""" + data = [ + {"id": 1, "score": 95}, + {"id": 2, "score": 87}, + {"id": 3, "score": 95} + ] + search_params: list[ArraySearchList] = [ + {"key": "score", "value": 95} + ] + + result = find_in_array_from_list(data, search_params) + + assert len(result) == 2 + assert result[0]["id"] == 1 + assert result[1]["id"] == 3 + + def test_boolean_values(self): + """Test search with boolean values""" + data = [ + {"name": "Alice", "active": True}, + {"name": "Bob", "active": False}, + {"name": "Charlie", "active": True} + ] + search_params: list[ArraySearchList] = [ + {"key": "active", "value": True} + ] + + result = find_in_array_from_list(data, search_params) + + assert len(result) == 2 + assert result[0]["name"] == "Alice" + assert result[1]["name"] == "Charlie" + + def test_float_values(self): + """Test search with float values""" + data = [ + {"name": "Product A", "price": 19.99}, + {"name": "Product B", "price": 29.99}, + {"name": "Product C", "price": 19.99} + ] + search_params: list[ArraySearchList] = [ + {"key": "price", "value": 19.99} + ] + + result = find_in_array_from_list(data, search_params) + + assert len(result) == 2 + assert result[0]["name"] == "Product A" + assert result[1]["name"] == "Product C" + + def test_mixed_value_types_in_list(self): + """Test search with mixed types in value list""" + data = [ + {"id": "1", "value": "active"}, + {"id": 2, "value": "pending"}, + {"id": "3", "value": "active"} + ] + search_params: list[ArraySearchList] = [ + {"key": "id", "value": ["1", "3"]} + ] + + result = find_in_array_from_list(data, search_params) + + assert len(result) == 2 + assert result[0]["id"] == "1" + assert result[1]["id"] == "3" + + def test_complex_multi_criteria_search(self): + """Test complex search with multiple criteria""" + data = [ + {"name": "Alice", "age": 30, "city": "New York", "status": "active"}, + {"name": "Bob", "age": 25, "city": "London", "status": "active"}, + {"name": "Charlie", "age": 30, "city": "Paris", "status": "inactive"}, + {"name": "David", "age": 30, "city": "New York", "status": "active"} + ] + search_params: list[ArraySearchList] = [ + {"key": "age", "value": 30}, + {"key": "city", "value": "New York"}, + {"key": "status", "value": "active"} + ] + + result = find_in_array_from_list(data, search_params) + + assert len(result) == 2 + assert result[0]["name"] == "Alice" + assert result[1]["name"] == "David" + + def test_invalid_search_params_not_list(self): + """Test that non-list search_params raises ValueError""" + data = [{"name": "Alice"}] + search_params = {"key": "name", "value": "Alice"} # type: ignore + + with pytest.raises(ValueError, match="search_params must be a list"): + find_in_array_from_list(data, search_params) # type: ignore + + def test_missing_key_in_search_params(self): + """Test that missing 'key' in search_params raises KeyError""" + data = [{"name": "Alice"}] + search_params: list[dict[str, Any]] = [ + {"value": "Alice"} # Missing 'key' + ] + + with pytest.raises(KeyError, match="Either Key '' or Value 'Alice' is missing or empty"): + find_in_array_from_list(data, search_params) # type: ignore + + def test_missing_value_in_search_params(self): + """Test that missing 'value' in search_params raises KeyError""" + data = [{"name": "Alice"}] + search_params: list[dict[str, Any]] = [ + {"key": "name"} # Missing 'value' + ] + + with pytest.raises(KeyError, match="Either Key 'name' or Value"): + find_in_array_from_list(data, search_params) # type: ignore + + def test_empty_key_in_search_params(self): + """Test that empty 'key' in search_params raises KeyError""" + data = [{"name": "Alice"}] + search_params: list[dict[str, Any]] = [ + {"key": "", "value": "Alice"} + ] + + with pytest.raises(KeyError, match="Either Key '' or Value 'Alice' is missing or empty"): + find_in_array_from_list(data, search_params) # type: ignore + + def test_empty_value_in_search_params(self): + """Test that empty 'value' in search_params raises KeyError""" + data = [{"name": "Alice"}] + search_params: list[dict[str, Any]] = [ + {"key": "name", "value": ""} + ] + + with pytest.raises(KeyError, match="Either Key 'name' or Value '' is missing or empty"): + find_in_array_from_list(data, search_params) # type: ignore + + def test_duplicate_key_in_search_params(self): + """Test that duplicate keys in search_params raises KeyError""" + data = [{"name": "Alice", "age": 30}] + search_params: list[ArraySearchList] = [ + {"key": "name", "value": "Alice"}, + {"key": "name", "value": "Bob"} # Duplicate key + ] + + with pytest.raises(KeyError, match="Key name already exists in search_params"): + find_in_array_from_list(data, search_params) + + def test_partial_match_fails(self): + """Test that partial match (not all criteria) returns no result""" + data = [ + {"name": "Alice", "age": 30, "city": "New York"} + ] + search_params: list[ArraySearchList] = [ + {"key": "name", "value": "Alice"}, + {"key": "age", "value": 25} # Doesn't match + ] + + result = find_in_array_from_list(data, search_params) + + assert result == [] + + def test_none_value_in_list(self): + """Test search with None in value list""" + data = [ + {"name": "Alice", "nickname": "Ally"}, + {"name": "Bob", "nickname": None}, + {"name": "Charlie", "nickname": "Chuck"} + ] + search_params: list[ArraySearchList] = [ + {"key": "nickname", "value": [None, "Chuck"]} + ] + + result = find_in_array_from_list(data, search_params) + + assert len(result) == 2 + assert result[0]["name"] == "Bob" + assert result[1]["name"] == "Charlie" + + @pytest.mark.parametrize("test_value,expected_count", [ + ("active", 1), + ("inactive", 1), + ("pending", 1), + ("archived", 0) + ]) + def test_parametrized_status_search(self, test_value: str, expected_count: int): + """Parametrized test for different status values""" + data = [ + {"id": 1, "status": "active"}, + {"id": 2, "status": "inactive"}, + {"id": 3, "status": "pending"} + ] + search_params: list[ArraySearchList] = [ + {"key": "status", "value": test_value} + ] + + result = find_in_array_from_list(data, search_params) + + assert len(result) == expected_count + + +class TestKeyLookup: + """Tests for key_lookup function""" + + def test_key_exists(self): + """Test lookup when key exists""" + haystack = {"name": "Alice", "age": "30", "city": "New York"} + + result = key_lookup(haystack, "name") + + assert result == "Alice" + + def test_key_not_exists(self): + """Test lookup when key doesn't exist returns empty string""" + haystack = {"name": "Alice", "age": "30"} + + result = key_lookup(haystack, "city") + + assert result == "" + + def test_empty_dict(self): + """Test lookup in empty dictionary""" + haystack: dict[str, str] = {} + + result = key_lookup(haystack, "name") + + assert result == "" + + def test_multiple_lookups(self): + """Test multiple lookups in same dictionary""" + haystack = {"first": "John", "last": "Doe", "email": "john@example.com"} + + assert key_lookup(haystack, "first") == "John" + assert key_lookup(haystack, "last") == "Doe" + assert key_lookup(haystack, "email") == "john@example.com" + assert key_lookup(haystack, "phone") == "" + + def test_numeric_string_values(self): + """Test lookup with numeric string values""" + haystack = {"count": "42", "price": "19.99"} + + assert key_lookup(haystack, "count") == "42" + assert key_lookup(haystack, "price") == "19.99" + + def test_empty_string_value(self): + """Test lookup when value is empty string""" + haystack = {"name": "", "city": "New York"} + + result = key_lookup(haystack, "name") + + assert result == "" + + def test_whitespace_value(self): + """Test lookup when value contains whitespace""" + haystack = {"name": " Alice ", "message": " "} + + assert key_lookup(haystack, "name") == " Alice " + assert key_lookup(haystack, "message") == " " + + @pytest.mark.parametrize("key,expected", [ + ("a", "1"), + ("b", "2"), + ("c", "3"), + ("d", "") + ]) + def test_parametrized_lookup(self, key: str, expected: str): + """Parametrized test for key lookup""" + haystack = {"a": "1", "b": "2", "c": "3"} + + result = key_lookup(haystack, key) + + assert result == expected + + +class TestValueLookup: + """Tests for value_lookup function""" + + def test_value_exists_single(self): + """Test lookup when value exists once""" + haystack = {"name": "Alice", "username": "alice123", "email": "alice@example.com"} + + result = value_lookup(haystack, "Alice") + + assert result == "name" + + def test_value_not_exists(self): + """Test lookup when value doesn't exist returns empty string""" + haystack = {"name": "Alice", "username": "alice123"} + + result = value_lookup(haystack, "Bob") + + assert result == "" + + def test_value_exists_multiple_no_raise(self): + """Test lookup when value exists multiple times, returns first""" + haystack = {"key1": "duplicate", "key2": "unique", "key3": "duplicate"} + + result = value_lookup(haystack, "duplicate") + + assert result in ["key1", "key3"] # Order may vary in dict + + def test_value_exists_multiple_raise_on_many_false(self): + """Test lookup with multiple matches and raise_on_many=False""" + haystack = {"a": "same", "b": "same", "c": "different"} + + result = value_lookup(haystack, "same", raise_on_many=False) + + assert result in ["a", "b"] + + def test_value_exists_multiple_raise_on_many_true(self): + """Test lookup with multiple matches and raise_on_many=True raises ValueError""" + haystack = {"a": "same", "b": "same", "c": "different"} + + with pytest.raises(ValueError, match="More than one element found with the same name"): + value_lookup(haystack, "same", raise_on_many=True) + + def test_value_exists_single_raise_on_many_true(self): + """Test lookup with single match and raise_on_many=True works fine""" + haystack = {"name": "Alice", "username": "alice123"} + + result = value_lookup(haystack, "Alice", raise_on_many=True) + + assert result == "name" + + def test_empty_dict(self): + """Test lookup in empty dictionary""" + haystack: dict[str, str] = {} + + result = value_lookup(haystack, "Alice") + + assert result == "" + + def test_empty_dict_raise_on_many(self): + """Test lookup in empty dictionary with raise_on_many=True""" + haystack: dict[str, str] = {} + + result = value_lookup(haystack, "Alice", raise_on_many=True) + + assert result == "" + + def test_numeric_string_values(self): + """Test lookup with numeric string values""" + haystack = {"id": "123", "count": "456", "score": "123"} + + result = value_lookup(haystack, "456") + + assert result == "count" + + def test_empty_string_value(self): + """Test lookup for empty string value""" + haystack = {"name": "", "city": "New York", "country": ""} + + result = value_lookup(haystack, "") + + assert result in ["name", "country"] + + def test_whitespace_value(self): + """Test lookup for whitespace value""" + haystack = {"a": " spaces ", "b": "normal", "c": " spaces "} + + result = value_lookup(haystack, " spaces ") + + assert result in ["a", "c"] + + def test_case_sensitive_lookup(self): + """Test that lookup is case-sensitive""" + haystack = {"name": "Alice", "username": "alice", "email": "ALICE"} + + assert value_lookup(haystack, "Alice") == "name" + assert value_lookup(haystack, "alice") == "username" + assert value_lookup(haystack, "ALICE") == "email" + assert value_lookup(haystack, "aLiCe") == "" + + def test_special_characters(self): + """Test lookup with special characters""" + haystack = {"key1": "test@example.com", "key2": "test#value", "key3": "test@example.com"} + + result = value_lookup(haystack, "test@example.com") + + assert result in ["key1", "key3"] + + @pytest.mark.parametrize("value,expected_key", [ + ("value1", "a"), + ("value2", "b"), + ("value3", "c"), + ("nonexistent", "") + ]) + def test_parametrized_lookup(self, value: str, expected_key: str): + """Parametrized test for value lookup""" + haystack = {"a": "value1", "b": "value2", "c": "value3"} + + result = value_lookup(haystack, value) + + assert result == expected_key + + def test_duplicate_values_consistent_return(self): + """Test that lookup with duplicates consistently returns one of the keys""" + haystack = {"x": "dup", "y": "dup", "z": "dup"} + + # Should return same key consistently + result1 = value_lookup(haystack, "dup") + result2 = value_lookup(haystack, "dup") + result3 = value_lookup(haystack, "dup") + + assert result1 == result2 == result3 + assert result1 in ["x", "y", "z"] diff --git a/tests/unit/iterator_handling/test_dict_helper.py b/tests/unit/iterator_handling/test_dict_helper.py new file mode 100644 index 0000000..5b2b862 --- /dev/null +++ b/tests/unit/iterator_handling/test_dict_helper.py @@ -0,0 +1,652 @@ +""" +iterator_handling.dict_helper tests +""" + +# pylint: disable=use-implicit-booleaness-not-comparison + +from typing import Any +import pytest +from corelibs.iterator_handling.dict_helper import ( + delete_keys_from_set, + build_dict, + set_entry, +) + + +class TestDeleteKeysFromSet: + """Test cases for delete_keys_from_set function""" + + def test_delete_single_key_from_dict(self): + """Test deleting a single key from a dictionary""" + set_data = {"a": 1, "b": 2, "c": 3} + keys = ["b"] + result = delete_keys_from_set(set_data, keys) + assert result == {"a": 1, "c": 3} + assert "b" not in result + + def test_delete_multiple_keys_from_dict(self): + """Test deleting multiple keys from a dictionary""" + set_data = {"a": 1, "b": 2, "c": 3, "d": 4} + keys = ["b", "d"] + result = delete_keys_from_set(set_data, keys) + assert result == {"a": 1, "c": 3} + assert "b" not in result + assert "d" not in result + + def test_delete_all_keys_from_dict(self): + """Test deleting all keys from a dictionary""" + set_data = {"a": 1, "b": 2} + keys = ["a", "b"] + result = delete_keys_from_set(set_data, keys) + assert result == {} + + def test_delete_nonexistent_key(self): + """Test deleting a key that doesn't exist""" + set_data = {"a": 1, "b": 2} + keys = ["c", "d"] + result = delete_keys_from_set(set_data, keys) + assert result == {"a": 1, "b": 2} + + def test_delete_keys_from_nested_dict(self): + """Test deleting keys from nested dictionaries""" + set_data = { + "a": 1, + "b": {"c": 2, "d": 3, "e": 4}, + "f": 5 + } + keys = ["d", "f"] + result = delete_keys_from_set(set_data, keys) + assert result == {"a": 1, "b": {"c": 2, "e": 4}} + assert "d" not in result["b"] # type: ignore + assert "f" not in result + + def test_delete_keys_from_deeply_nested_dict(self): + """Test deleting keys from deeply nested structures""" + set_data = { + "a": 1, + "b": { + "c": 2, + "d": { + "e": 3, + "f": 4 + } + }, + "g": 5 + } + keys = ["f", "g"] + result = delete_keys_from_set(set_data, keys) + assert result == {"a": 1, "b": {"c": 2, "d": {"e": 3}}} + assert "g" not in result + + def test_delete_keys_from_list(self): + """Test with list containing dictionaries""" + set_data = [ + {"a": 1, "b": 2}, + {"c": 3, "d": 4}, + {"e": 5, "f": 6} + ] + keys = ["b", "d", "f"] + result = delete_keys_from_set(set_data, keys) + assert result == [ + {"a": 1}, + {"c": 3}, + {"e": 5} + ] + + def test_delete_keys_from_list_with_nested_dicts(self): + """Test with list containing nested dictionaries""" + set_data = [ + {"a": 1, "b": {"c": 2, "d": 3}}, + {"e": 4, "f": {"g": 5, "h": 6}} + ] + keys = ["d", "h"] + result = delete_keys_from_set(set_data, keys) + assert result == [ + {"a": 1, "b": {"c": 2}}, + {"e": 4, "f": {"g": 5}} + ] + + def test_delete_keys_from_dict_with_list_values(self): + """Test with dictionary containing list values""" + set_data = { + "a": [{"b": 1, "c": 2}, {"d": 3, "e": 4}], + "f": 5 + } + keys = ["c", "e"] + result = delete_keys_from_set(set_data, keys) + assert result == { + "a": [{"b": 1}, {"d": 3}], + "f": 5 + } + + def test_empty_keys_list(self): + """Test with empty keys list - should return data unchanged""" + set_data = {"a": 1, "b": 2, "c": 3} + keys: list[str] = [] + result = delete_keys_from_set(set_data, keys) + assert result == set_data + + def test_empty_dict(self): + """Test with empty dictionary""" + set_data: dict[str, Any] = {} + keys = ["a", "b"] + result = delete_keys_from_set(set_data, keys) + assert result == {} + + def test_empty_list(self): + """Test with empty list""" + set_data: list[Any] = [] + keys = ["a", "b"] + result = delete_keys_from_set(set_data, keys) + assert result == [] + + def test_string_input(self): + """Test with string input - should convert to list""" + set_data = "hello" + keys = ["a"] + result = delete_keys_from_set(set_data, keys) + assert result == ["hello"] + + def test_complex_mixed_structure(self): + """Test with complex mixed structure""" + set_data = { + "users": [ + { + "name": "Alice", + "age": 30, + "password": "secret1", + "profile": { + "email": "alice@example.com", + "password": "secret2" + } + }, + { + "name": "Bob", + "age": 25, + "password": "secret3", + "profile": { + "email": "bob@example.com", + "password": "secret4" + } + } + ], + "metadata": { + "count": 2, + "password": "admin" + } + } + keys = ["password"] + result = delete_keys_from_set(set_data, keys) + + # Check that all password fields are removed + assert "password" not in result["metadata"] # type: ignore + for user in result["users"]: # type: ignore + assert "password" not in user + assert "password" not in user["profile"] + + # Check that other fields remain + assert result["users"][0]["name"] == "Alice" # type: ignore + assert result["users"][1]["name"] == "Bob" # type: ignore + assert result["metadata"]["count"] == 2 # type: ignore + + def test_dict_with_none_values(self): + """Test with dictionary containing None values""" + set_data = {"a": 1, "b": None, "c": 3} + keys = ["b"] + result = delete_keys_from_set(set_data, keys) + assert result == {"a": 1, "c": 3} + + def test_dict_with_various_value_types(self): + """Test with dictionary containing various value types""" + set_data = { + "int": 42, + "float": 3.14, + "bool": True, + "str": "hello", + "list": [1, 2, 3], + "dict": {"nested": "value"}, + "none": None + } + keys = ["bool", "none"] + result = delete_keys_from_set(set_data, keys) + assert "bool" not in result + assert "none" not in result + assert len(result) == 5 + + +class TestBuildDict: + """Test cases for build_dict function""" + + def test_build_dict_without_ignore_entries(self): + """Test build_dict without ignore_entries (None)""" + input_dict = {"a": 1, "b": 2, "c": 3} + result = build_dict(input_dict) + assert result == input_dict + assert result is input_dict # Should return same object + + def test_build_dict_with_ignore_entries_single(self): + """Test build_dict with single ignore entry""" + input_dict = {"a": 1, "b": 2, "c": 3} + ignore = ["b"] + result = build_dict(input_dict, ignore) + assert result == {"a": 1, "c": 3} + assert "b" not in result + + def test_build_dict_with_ignore_entries_multiple(self): + """Test build_dict with multiple ignore entries""" + input_dict = {"a": 1, "b": 2, "c": 3, "d": 4} + ignore = ["b", "d"] + result = build_dict(input_dict, ignore) + assert result == {"a": 1, "c": 3} + + def test_build_dict_with_nested_ignore(self): + """Test build_dict with nested structures""" + input_dict = { + "a": 1, + "b": {"c": 2, "d": 3}, + "e": 4 + } + ignore = ["d", "e"] + result = build_dict(input_dict, ignore) + assert result == {"a": 1, "b": {"c": 2}} + assert "e" not in result + assert "d" not in result["b"] # type: ignore + + def test_build_dict_with_empty_ignore_list(self): + """Test build_dict with empty ignore list""" + input_dict = {"a": 1, "b": 2} + ignore: list[str] = [] + result = build_dict(input_dict, ignore) + assert result == input_dict + + def test_build_dict_with_nonexistent_ignore_keys(self): + """Test build_dict with keys that don't exist""" + input_dict = {"a": 1, "b": 2} + ignore = ["c", "d"] + result = build_dict(input_dict, ignore) + assert result == {"a": 1, "b": 2} + + def test_build_dict_ignore_all_keys(self): + """Test build_dict ignoring all keys""" + input_dict = {"a": 1, "b": 2} + ignore = ["a", "b"] + result = build_dict(input_dict, ignore) + assert result == {} + + def test_build_dict_with_complex_structure(self): + """Test build_dict with complex nested structure""" + input_dict = { + "ResponseMetadata": { + "RequestId": "12345", + "HTTPStatusCode": 200, + "RetryAttempts": 0 + }, + "data": { + "id": 1, + "name": "Test", + "ResponseMetadata": {"internal": "value"} + }, + "status": "success" + } + ignore = ["ResponseMetadata", "RetryAttempts"] + result = build_dict(input_dict, ignore) + + # ResponseMetadata should be removed at all levels + assert "ResponseMetadata" not in result + assert "ResponseMetadata" not in result["data"] # type: ignore + assert result["data"]["name"] == "Test" # type: ignore + assert result["status"] == "success" # type: ignore + + def test_build_dict_with_list_values(self): + """Test build_dict with lists containing dictionaries""" + input_dict = { + "items": [ + {"id": 1, "temp": "remove"}, + {"id": 2, "temp": "remove"} + ], + "temp": "also_remove" + } + ignore = ["temp"] + result = build_dict(input_dict, ignore) + + assert "temp" not in result + assert "temp" not in result["items"][0] # type: ignore + assert "temp" not in result["items"][1] # type: ignore + assert result["items"][0]["id"] == 1 # type: ignore + assert result["items"][1]["id"] == 2 # type: ignore + + def test_build_dict_empty_input(self): + """Test build_dict with empty dictionary""" + input_dict: dict[str, Any] = {} + result = build_dict(input_dict, ["a", "b"]) + assert result == {} + + def test_build_dict_preserves_type_annotation(self): + """Test that build_dict preserves proper type""" + input_dict = {"a": 1, "b": [1, 2, 3], "c": {"nested": "value"}} + result = build_dict(input_dict) + assert isinstance(result, dict) + assert isinstance(result["b"], list) + assert isinstance(result["c"], dict) + + +class TestSetEntry: + """Test cases for set_entry function""" + + def test_set_entry_new_key(self): + """Test setting a new key in dictionary""" + dict_set: dict[str, Any] = {} + key = "new_key" + value = "new_value" + result = set_entry(dict_set, key, value) + assert result[key] == value + assert len(result) == 1 + + def test_set_entry_existing_key(self): + """Test overwriting an existing key""" + dict_set = {"key": "old_value"} + key = "key" + value = "new_value" + result = set_entry(dict_set, key, value) + assert result[key] == value + assert result[key] != "old_value" + + def test_set_entry_with_dict_value(self): + """Test setting a dictionary as value""" + dict_set: dict[str, Any] = {} + key = "config" + value = {"setting1": True, "setting2": "value"} + result = set_entry(dict_set, key, value) + assert result[key] == value + assert isinstance(result[key], dict) + + def test_set_entry_with_list_value(self): + """Test setting a list as value""" + dict_set: dict[str, Any] = {} + key = "items" + value = [1, 2, 3, 4] + result = set_entry(dict_set, key, value) + assert result[key] == value + assert isinstance(result[key], list) + + def test_set_entry_with_none_value(self): + """Test setting None as value""" + dict_set: dict[str, Any] = {} + key = "nullable" + value = None + result = set_entry(dict_set, key, value) + assert result[key] is None + assert key in result + + def test_set_entry_with_integer_value(self): + """Test setting integer value""" + dict_set: dict[str, Any] = {} + key = "count" + value = 42 + result = set_entry(dict_set, key, value) + assert result[key] == 42 + assert isinstance(result[key], int) + + def test_set_entry_with_float_value(self): + """Test setting float value""" + dict_set: dict[str, Any] = {} + key = "price" + value = 19.99 + result = set_entry(dict_set, key, value) + assert result[key] == 19.99 + assert isinstance(result[key], float) + + def test_set_entry_with_boolean_value(self): + """Test setting boolean value""" + dict_set: dict[str, Any] = {} + key = "enabled" + value = True + result = set_entry(dict_set, key, value) + assert result[key] is True + assert isinstance(result[key], bool) + + def test_set_entry_multiple_times(self): + """Test setting multiple entries""" + dict_set: dict[str, Any] = {} + set_entry(dict_set, "key1", "value1") + set_entry(dict_set, "key2", "value2") + set_entry(dict_set, "key3", "value3") + + assert len(dict_set) == 3 + assert dict_set["key1"] == "value1" + assert dict_set["key2"] == "value2" + assert dict_set["key3"] == "value3" + + def test_set_entry_overwrites_existing(self): + """Test that setting an existing key overwrites it""" + dict_set = {"key": {"old": "data"}} + value = {"new": "data"} + result = set_entry(dict_set, "key", value) + assert result["key"] == {"new": "data"} + assert "old" not in result["key"] + + def test_set_entry_modifies_original_dict(self): + """Test that set_entry modifies the original dictionary""" + dict_set: dict[str, Any] = {} + result = set_entry(dict_set, "key", "value") + assert result is dict_set + assert dict_set["key"] == "value" + + def test_set_entry_with_empty_string_value(self): + """Test setting empty string as value""" + dict_set: dict[str, Any] = {} + key = "empty" + value = "" + result = set_entry(dict_set, key, value) + assert result[key] == "" + assert key in result + + def test_set_entry_with_complex_nested_structure(self): + """Test setting complex nested structure""" + dict_set: dict[str, Any] = {} + key = "complex" + value = { + "level1": { + "level2": { + "level3": ["a", "b", "c"] + } + } + } + result = set_entry(dict_set, key, value) + assert result[key]["level1"]["level2"]["level3"] == ["a", "b", "c"] + + +# Parametrized tests for more comprehensive coverage +class TestParametrized: + """Parametrized tests for better coverage""" + + @pytest.mark.parametrize("set_data,keys,expected", [ + ({"a": 1, "b": 2}, ["b"], {"a": 1}), + ({"a": 1, "b": 2, "c": 3}, ["a", "c"], {"b": 2}), + ({"a": 1}, ["a"], {}), + ({"a": 1, "b": 2}, ["c"], {"a": 1, "b": 2}), + ({}, ["a"], {}), + ({"a": {"b": 1, "c": 2}}, ["c"], {"a": {"b": 1}}), + ]) + def test_delete_keys_parametrized( + self, + set_data: dict[str, Any], + keys: list[str], + expected: dict[str, Any] + ): + """Test delete_keys_from_set with various inputs""" + result = delete_keys_from_set(set_data, keys) + assert result == expected + + @pytest.mark.parametrize("input_dict,ignore,expected", [ + ({"a": 1, "b": 2}, ["b"], {"a": 1}), + ({"a": 1, "b": 2}, ["c"], {"a": 1, "b": 2}), + ({"a": 1, "b": 2}, [], {"a": 1, "b": 2}), + ({"a": 1}, ["a"], {}), + ({}, ["a"], {}), + ]) + def test_build_dict_parametrized( + self, + input_dict: dict[str, Any], + ignore: list[str], + expected: dict[str, Any] + ): + """Test build_dict with various inputs""" + result = build_dict(input_dict, ignore) + assert result == expected + + @pytest.mark.parametrize("key,value", [ + ("string_key", "string_value"), + ("int_key", 42), + ("float_key", 3.14), + ("bool_key", True), + ("list_key", [1, 2, 3]), + ("dict_key", {"nested": "value"}), + ("none_key", None), + ("empty_key", ""), + ("zero_key", 0), + ("false_key", False), + ]) + def test_set_entry_parametrized(self, key: str, value: Any): + """Test set_entry with various value types""" + dict_set: dict[str, Any] = {} + result = set_entry(dict_set, key, value) + assert result[key] == value + + +# Edge cases and integration tests +class TestEdgeCases: + """Test edge cases and special scenarios""" + + def test_delete_keys_preserves_modification(self): + """Test that original dict is modified""" + set_data = {"a": 1, "b": 2, "c": 3} + keys = ["b"] + result = delete_keys_from_set(set_data, keys) + # The function modifies the original dict + assert result is set_data + assert "b" not in set_data + + def test_build_dict_with_aws_typedef_scenario(self): + """Test build_dict mimicking AWS TypedDict usage""" + # Simulating AWS response with ResponseMetadata + aws_response: dict[str, Any] = { + "Items": [ + {"id": "1", "name": "Item1"}, + {"id": "2", "name": "Item2"} + ], + "Count": 2, + "ScannedCount": 2, + "ResponseMetadata": { + "RequestId": "abc123", + "HTTPStatusCode": 200, + "HTTPHeaders": {}, + "RetryAttempts": 0 + } + } + result = build_dict(aws_response, ["ResponseMetadata"]) + + assert "ResponseMetadata" not in result + assert result["Count"] == 2 # type: ignore + assert len(result["Items"]) == 2 # type: ignore + + def test_set_entry_idempotency(self): + """Test that calling set_entry multiple times with same value is idempotent""" + dict_set: dict[str, Any] = {} + value = "test_value" + + result1 = set_entry(dict_set, "key", value) + result2 = set_entry(dict_set, "key", value) + result3 = set_entry(dict_set, "key", value) + + assert result1 is result2 is result3 + assert result1["key"] == value + assert len(result1) == 1 + + def test_delete_keys_with_circular_reference_protection(self): + """Test that function handles normal cases without circular issues""" + # Python dicts can't have true circular references easily + # but we can test deep nesting + set_data = { + "level1": { + "level2": { + "level3": { + "level4": { + "data": "value", + "remove": "this" + } + } + } + } + } + keys = ["remove"] + result = delete_keys_from_set(set_data, keys) + assert "remove" not in result["level1"]["level2"]["level3"]["level4"] # type: ignore + assert result["level1"]["level2"]["level3"]["level4"]["data"] == "value" # type: ignore + + def test_build_dict_none_ignore_vs_empty_ignore(self): + """Test difference between None and empty list for ignore_entries""" + input_dict = {"a": 1, "b": 2} + + result_none = build_dict(input_dict, None) + result_empty = build_dict(input_dict, []) + + assert result_none == input_dict + assert result_empty == input_dict + # With None, it returns the same object + assert result_none is input_dict + # With empty list, it goes through delete_keys_from_set + assert result_empty is input_dict + + +# Integration tests +class TestIntegration: + """Integration tests combining multiple functions""" + + def test_build_dict_then_set_entry(self): + """Test using build_dict followed by set_entry""" + original = { + "a": 1, + "b": 2, + "remove_me": "gone" + } + cleaned = build_dict(original, ["remove_me"]) + result = set_entry(cleaned, "c", 3) + + assert result == {"a": 1, "b": 2, "c": 3} + assert "remove_me" not in result + + def test_delete_keys_then_set_entry(self): + """Test using delete_keys_from_set followed by set_entry""" + data = {"a": 1, "b": 2, "c": 3} + cleaned = delete_keys_from_set(data, ["b"]) + result = set_entry(cleaned, "d", 4) # type: ignore + + assert result == {"a": 1, "c": 3, "d": 4} + + def test_multiple_operations_chain(self): + """Test chaining multiple operations""" + data = { + "user": { + "name": "Alice", + "password": "secret", + "email": "alice@example.com" + }, + "metadata": { + "created": "2024-01-01", + "password": "admin" + } + } + + # Remove passwords + cleaned = build_dict(data, ["password"]) + + # Add new field + result = set_entry(cleaned, "processed", True) + + assert "password" not in result["user"] # type: ignore + assert "password" not in result["metadata"] # type: ignore + assert result["processed"] is True # type: ignore + assert result["user"]["name"] == "Alice" # type: ignore + +# __END__ diff --git a/tests/unit/iterator_handling/test_dict_helpers.py b/tests/unit/iterator_handling/test_dict_mask.py similarity index 99% rename from tests/unit/iterator_handling/test_dict_helpers.py rename to tests/unit/iterator_handling/test_dict_mask.py index da973e7..15be1b5 100644 --- a/tests/unit/iterator_handling/test_dict_helpers.py +++ b/tests/unit/iterator_handling/test_dict_mask.py @@ -2,9 +2,9 @@ tests for corelibs.iterator_handling.dict_helpers """ -import pytest from typing import Any -from corelibs.iterator_handling.dict_helpers import mask +import pytest +from corelibs.iterator_handling.dict_mask import mask def test_mask_default_behavior(): diff --git a/tests/unit/iterator_handling/test_fingerprint.py b/tests/unit/iterator_handling/test_fingerprint.py new file mode 100644 index 0000000..daf7f2a --- /dev/null +++ b/tests/unit/iterator_handling/test_fingerprint.py @@ -0,0 +1,361 @@ +""" +tests for corelibs.iterator_handling.fingerprint +""" + +from typing import Any +import pytest +from corelibs.iterator_handling.fingerprint import dict_hash_frozen, dict_hash_crc + + +class TestDictHashFrozen: + """Tests for dict_hash_frozen function""" + + def test_dict_hash_frozen_simple_dict(self): + """Test hashing a simple dictionary""" + data = {"key1": "value1", "key2": "value2"} + result = dict_hash_frozen(data) + + assert isinstance(result, int) + assert result != 0 + + def test_dict_hash_frozen_consistency(self): + """Test that same dict produces same hash""" + data = {"name": "John", "age": 30, "city": "Tokyo"} + hash1 = dict_hash_frozen(data) + hash2 = dict_hash_frozen(data) + + assert hash1 == hash2 + + def test_dict_hash_frozen_order_independence(self): + """Test that dict order doesn't affect hash""" + data1 = {"a": 1, "b": 2, "c": 3} + data2 = {"c": 3, "a": 1, "b": 2} + hash1 = dict_hash_frozen(data1) + hash2 = dict_hash_frozen(data2) + + assert hash1 == hash2 + + def test_dict_hash_frozen_empty_dict(self): + """Test hashing an empty dictionary""" + data: dict[Any, Any] = {} + result = dict_hash_frozen(data) + + assert isinstance(result, int) + + def test_dict_hash_frozen_different_dicts(self): + """Test that different dicts produce different hashes""" + data1 = {"key1": "value1"} + data2 = {"key2": "value2"} + hash1 = dict_hash_frozen(data1) + hash2 = dict_hash_frozen(data2) + + assert hash1 != hash2 + + def test_dict_hash_frozen_various_types(self): + """Test hashing dict with various value types""" + data = { + "string": "value", + "int": 42, + "float": 3.14, + "bool": True, + "none": None + } + result = dict_hash_frozen(data) + + assert isinstance(result, int) + + def test_dict_hash_frozen_numeric_keys(self): + """Test hashing dict with numeric keys""" + data = {1: "one", 2: "two", 3: "three"} + result = dict_hash_frozen(data) + + assert isinstance(result, int) + + def test_dict_hash_frozen_tuple_values(self): + """Test hashing dict with tuple values""" + data = {"coord1": (1, 2), "coord2": (3, 4)} + result = dict_hash_frozen(data) + + assert isinstance(result, int) + + def test_dict_hash_frozen_value_change_changes_hash(self): + """Test that changing a value changes the hash""" + data1 = {"key": "value1"} + data2 = {"key": "value2"} + hash1 = dict_hash_frozen(data1) + hash2 = dict_hash_frozen(data2) + + assert hash1 != hash2 + + +class TestDictHashCrc: + """Tests for dict_hash_crc function""" + + def test_dict_hash_crc_simple_dict(self): + """Test hashing a simple dictionary""" + data = {"key1": "value1", "key2": "value2"} + result = dict_hash_crc(data) + + assert isinstance(result, str) + assert len(result) == 64 # SHA256 produces 64 hex characters + + def test_dict_hash_crc_simple_list(self): + """Test hashing a simple list""" + data = ["item1", "item2", "item3"] + result = dict_hash_crc(data) + + assert isinstance(result, str) + assert len(result) == 64 + + def test_dict_hash_crc_consistency_dict(self): + """Test that same dict produces same hash""" + data = {"name": "John", "age": 30, "city": "Tokyo"} + hash1 = dict_hash_crc(data) + hash2 = dict_hash_crc(data) + + assert hash1 == hash2 + + def test_dict_hash_crc_consistency_list(self): + """Test that same list produces same hash""" + data = [1, 2, 3, 4, 5] + hash1 = dict_hash_crc(data) + hash2 = dict_hash_crc(data) + + assert hash1 == hash2 + + def test_dict_hash_crc_order_independence_dict(self): + """Test that dict order doesn't affect hash (sort_keys=True)""" + data1 = {"a": 1, "b": 2, "c": 3} + data2 = {"c": 3, "a": 1, "b": 2} + hash1 = dict_hash_crc(data1) + hash2 = dict_hash_crc(data2) + + assert hash1 == hash2 + + def test_dict_hash_crc_order_dependence_list(self): + """Test that list order affects hash""" + data1 = [1, 2, 3] + data2 = [3, 2, 1] + hash1 = dict_hash_crc(data1) + hash2 = dict_hash_crc(data2) + + assert hash1 != hash2 + + def test_dict_hash_crc_empty_dict(self): + """Test hashing an empty dictionary""" + data: dict[Any, Any] = {} + result = dict_hash_crc(data) + + assert isinstance(result, str) + assert len(result) == 64 + + def test_dict_hash_crc_empty_list(self): + """Test hashing an empty list""" + data: list[Any] = [] + result = dict_hash_crc(data) + + assert isinstance(result, str) + assert len(result) == 64 + + def test_dict_hash_crc_different_dicts(self): + """Test that different dicts produce different hashes""" + data1 = {"key1": "value1"} + data2 = {"key2": "value2"} + hash1 = dict_hash_crc(data1) + hash2 = dict_hash_crc(data2) + + assert hash1 != hash2 + + def test_dict_hash_crc_different_lists(self): + """Test that different lists produce different hashes""" + data1 = ["item1", "item2"] + data2 = ["item3", "item4"] + hash1 = dict_hash_crc(data1) + hash2 = dict_hash_crc(data2) + + assert hash1 != hash2 + + def test_dict_hash_crc_nested_dict(self): + """Test hashing nested dictionaries""" + data = { + "user": { + "name": "John", + "address": { + "city": "Tokyo", + "country": "Japan" + } + } + } + result = dict_hash_crc(data) + + assert isinstance(result, str) + assert len(result) == 64 + + def test_dict_hash_crc_nested_list(self): + """Test hashing nested lists""" + data = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] + result = dict_hash_crc(data) + + assert isinstance(result, str) + assert len(result) == 64 + + def test_dict_hash_crc_mixed_nested(self): + """Test hashing mixed nested structures""" + data = { + "items": [1, 2, 3], + "meta": { + "count": 3, + "tags": ["a", "b", "c"] + } + } + result = dict_hash_crc(data) + + assert isinstance(result, str) + assert len(result) == 64 + + def test_dict_hash_crc_various_types_dict(self): + """Test hashing dict with various value types""" + data = { + "string": "value", + "int": 42, + "float": 3.14, + "bool": True, + "none": None, + "list": [1, 2, 3], + "nested_dict": {"inner": "value"} + } + result = dict_hash_crc(data) + + assert isinstance(result, str) + assert len(result) == 64 + + def test_dict_hash_crc_various_types_list(self): + """Test hashing list with various value types""" + data = ["string", 42, 3.14, True, None, [1, 2], {"key": "value"}] + result = dict_hash_crc(data) + + assert isinstance(result, str) + assert len(result) == 64 + + def test_dict_hash_crc_value_change_changes_hash(self): + """Test that changing a value changes the hash""" + data1 = {"key": "value1"} + data2 = {"key": "value2"} + hash1 = dict_hash_crc(data1) + hash2 = dict_hash_crc(data2) + + assert hash1 != hash2 + + def test_dict_hash_crc_hex_format(self): + """Test that hash is in hexadecimal format""" + data = {"test": "data"} + result = dict_hash_crc(data) + + # All characters should be valid hex + assert all(c in "0123456789abcdef" for c in result) + + def test_dict_hash_crc_unicode_handling(self): + """Test hashing dict with unicode characters""" + data = { + "japanese": "ζ—₯本θͺž", + "emoji": "πŸŽ‰", + "chinese": "δΈ­ζ–‡" + } + result = dict_hash_crc(data) + + assert isinstance(result, str) + assert len(result) == 64 + + def test_dict_hash_crc_special_characters(self): + """Test hashing dict with special characters""" + data = { + "quotes": "\"quoted\"", + "newline": "line1\nline2", + "tab": "col1\tcol2", + "backslash": "path\\to\\file" + } + result = dict_hash_crc(data) + + assert isinstance(result, str) + assert len(result) == 64 + + +class TestComparisonBetweenHashFunctions: + """Tests comparing dict_hash_frozen and dict_hash_crc""" + + def test_both_functions_are_deterministic(self): + """Test that both functions produce consistent results""" + data = {"a": 1, "b": 2, "c": 3} + + frozen_hash1 = dict_hash_frozen(data) + frozen_hash2 = dict_hash_frozen(data) + crc_hash1 = dict_hash_crc(data) + crc_hash2 = dict_hash_crc(data) + + assert frozen_hash1 == frozen_hash2 + assert crc_hash1 == crc_hash2 + + def test_both_functions_handle_empty_dict(self): + """Test that both functions can hash empty dict""" + data: dict[Any, Any] = {} + + frozen_result = dict_hash_frozen(data) + crc_result = dict_hash_crc(data) + + assert isinstance(frozen_result, int) + assert isinstance(crc_result, str) + + def test_both_functions_detect_changes(self): + """Test that both functions detect value changes""" + data1 = {"key": "value1"} + data2 = {"key": "value2"} + + frozen_hash1 = dict_hash_frozen(data1) + frozen_hash2 = dict_hash_frozen(data2) + crc_hash1 = dict_hash_crc(data1) + crc_hash2 = dict_hash_crc(data2) + + assert frozen_hash1 != frozen_hash2 + assert crc_hash1 != crc_hash2 + + def test_both_functions_handle_order_independence(self): + """Test that both functions are order-independent for dicts""" + data1 = {"x": 10, "y": 20, "z": 30} + data2 = {"z": 30, "x": 10, "y": 20} + + frozen_hash1 = dict_hash_frozen(data1) + frozen_hash2 = dict_hash_frozen(data2) + crc_hash1 = dict_hash_crc(data1) + crc_hash2 = dict_hash_crc(data2) + + assert frozen_hash1 == frozen_hash2 + assert crc_hash1 == crc_hash2 + + +@pytest.mark.parametrize("data,expected_type,expected_length", [ + ({"key": "value"}, str, 64), + ([1, 2, 3], str, 64), + ({"nested": {"key": "value"}}, str, 64), + ([[1, 2], [3, 4]], str, 64), + ({}, str, 64), + ([], str, 64), +]) +def test_dict_hash_crc_parametrized(data: dict[Any, Any] | list[Any], expected_type: type, expected_length: int): + """Parametrized test for dict_hash_crc with various inputs""" + result = dict_hash_crc(data) + + assert isinstance(result, expected_type) + assert len(result) == expected_length + + +@pytest.mark.parametrize("data", [ + {"key": "value"}, + {"a": 1, "b": 2}, + {"x": 10, "y": 20, "z": 30}, + {}, +]) +def test_dict_hash_frozen_parametrized(data: dict[Any, Any]): + """Parametrized test for dict_hash_frozen with various inputs""" + result = dict_hash_frozen(data) + + assert isinstance(result, int) diff --git a/tests/unit/iterator_handling/test_list_helpers.py b/tests/unit/iterator_handling/test_list_helpers.py index 694f781..75206f4 100644 --- a/tests/unit/iterator_handling/test_list_helpers.py +++ b/tests/unit/iterator_handling/test_list_helpers.py @@ -226,8 +226,8 @@ class TestParametrized: ([1, 2, 3], [2], {1, 3}), (["a", "b", "c"], ["b", "d"], {"a", "c"}), ([1, 2, 3], [4, 5, 6], {1, 2, 3}), - ([1, 2, 3], [1, 2, 3], set()), - ([], [1, 2, 3], set()), + ([1, 2, 3], [1, 2, 3], set[int]()), + ([], [1, 2, 3], set[int]()), ([1, 2, 3], [], {1, 2, 3}), ([True, False], [True], {False}), ([1.1, 2.2, 3.3], [2.2], {1.1, 3.3}), @@ -247,7 +247,7 @@ class TestEdgeCases: """Test convert_to_list with None-like values (if function supports them)""" # Note: Based on type hints, None is not supported, but testing behavior # This test might need to be adjusted based on actual function behavior - pass + # pass def test_is_list_in_list_preserves_type_distinctions(self): """Test that different types are treated as different"""