iterator tests added
This commit is contained in:
@@ -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__
|
||||
@@ -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__
|
||||
@@ -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():
|
||||
|
||||
@@ -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')
|
||||
|
||||
601
tests/unit/iterator_handling/test_data_search.py
Normal file
601
tests/unit/iterator_handling/test_data_search.py
Normal file
@@ -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"]
|
||||
652
tests/unit/iterator_handling/test_dict_helper.py
Normal file
652
tests/unit/iterator_handling/test_dict_helper.py
Normal file
@@ -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__
|
||||
@@ -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():
|
||||
361
tests/unit/iterator_handling/test_fingerprint.py
Normal file
361
tests/unit/iterator_handling/test_fingerprint.py
Normal file
@@ -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)
|
||||
@@ -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"""
|
||||
|
||||
Reference in New Issue
Block a user