Move iterator handling functions to corelibs_iterator, corelibs_hash and corelibs_dump_data modules
Deprecate math helpers in favor of built-in math functions
This commit is contained in:
@@ -12,8 +12,11 @@ dependencies = [
|
||||
"corelibs-encryption>=1.0.0",
|
||||
"corelibs-enum-base>=1.0.0",
|
||||
"corelibs-file>=1.0.0",
|
||||
"corelibs-hash>=1.0.0",
|
||||
"corelibs-iterator>=1.0.0",
|
||||
"corelibs-json>=1.0.0",
|
||||
"corelibs-regex-checks>=1.0.0",
|
||||
"corelibs-search>=1.0.0",
|
||||
"corelibs-stack-trace>=1.0.0",
|
||||
"corelibs-text-colors>=1.0.0",
|
||||
"corelibs-var>=1.0.0",
|
||||
|
||||
@@ -25,7 +25,7 @@ def is_bom_encoded(file_path: Path) -> bool:
|
||||
return is_bom_encoding_ng(file_path)
|
||||
|
||||
|
||||
@deprecated("Use corelibs_file.file_bom_encoding.is_bom_encoded_info instead")
|
||||
@deprecated("Use corelibs_file.file_bom_encoding.get_bom_encoding_info instead")
|
||||
def is_bom_encoded_info(file_path: Path) -> BomEncodingInfo:
|
||||
"""
|
||||
Enhanced BOM detection with additional file analysis
|
||||
|
||||
@@ -2,27 +2,31 @@
|
||||
wrapper around search path
|
||||
"""
|
||||
|
||||
from typing import Any, TypedDict, NotRequired
|
||||
from typing import Any
|
||||
from warnings import deprecated
|
||||
from corelibs_search.data_search import (
|
||||
ArraySearchList as CorelibsArraySearchList,
|
||||
find_in_array_from_list as corelibs_find_in_array_from_list,
|
||||
key_lookup as corelibs_key_lookup,
|
||||
value_lookup as corelibs_value_lookup
|
||||
)
|
||||
|
||||
|
||||
class ArraySearchList(TypedDict):
|
||||
class ArraySearchList(CorelibsArraySearchList):
|
||||
"""find in array from list search dict"""
|
||||
key: str
|
||||
value: str | bool | int | float | list[str | None]
|
||||
case_sensitive: NotRequired[bool]
|
||||
|
||||
|
||||
@deprecated("Use find_in_array_from_list()")
|
||||
@deprecated("Use corelibs_search.data_search.find_in_array_from_list instead")
|
||||
def array_search(
|
||||
search_params: list[ArraySearchList],
|
||||
data: list[dict[str, Any]],
|
||||
return_index: bool = False
|
||||
) -> list[dict[str, Any]]:
|
||||
"""depreacted, old call order"""
|
||||
return find_in_array_from_list(data, search_params, return_index)
|
||||
return corelibs_find_in_array_from_list(data, search_params, return_index)
|
||||
|
||||
|
||||
@deprecated("Use corelibs_search.data_search.find_in_array_from_list instead")
|
||||
def find_in_array_from_list(
|
||||
data: list[dict[str, Any]],
|
||||
search_params: list[ArraySearchList],
|
||||
@@ -48,69 +52,14 @@ def find_in_array_from_list(
|
||||
list: list of found elements, or if return index
|
||||
list of dics with "index" and "data", where "data" holds the result list
|
||||
"""
|
||||
if not isinstance(search_params, list): # type: ignore
|
||||
raise ValueError("search_params must be a list")
|
||||
keys: list[str] = []
|
||||
# check that key and value exist and are set
|
||||
for search in search_params:
|
||||
if not search.get('key') or not search.get('value'):
|
||||
raise KeyError(
|
||||
f"Either Key '{search.get('key', '')}' or "
|
||||
f"Value '{search.get('value', '')}' is missing or empty"
|
||||
)
|
||||
# if double key -> abort
|
||||
if search.get("key") in keys:
|
||||
raise KeyError(
|
||||
f"Key {search.get('key', '')} already exists in search_params"
|
||||
)
|
||||
keys.append(str(search['key']))
|
||||
|
||||
return_items: list[dict[str, Any]] = []
|
||||
for si_idx, search_item in enumerate(data):
|
||||
# for each search entry, all must match
|
||||
matching = 0
|
||||
for search in search_params:
|
||||
# either Value direct or if Value is list then any of those items can match
|
||||
# values are compared in lower case if case senstive is off
|
||||
# lower case left side
|
||||
# TODO: allow nested Keys. eg "Key: ["Key a", "key b"]" to be ["Key a"]["key b"]
|
||||
if search.get("case_sensitive", True) is False:
|
||||
search_value = search_item.get(str(search['key']), "").lower()
|
||||
else:
|
||||
search_value = search_item.get(str(search['key']), "")
|
||||
# lower case right side
|
||||
if isinstance(search['value'], list):
|
||||
search_in = [
|
||||
str(k).lower()
|
||||
if search.get("case_sensitive", True) is False else k
|
||||
for k in search['value']
|
||||
]
|
||||
elif search.get("case_sensitive", True) is False:
|
||||
search_in = str(search['value']).lower()
|
||||
else:
|
||||
search_in = search['value']
|
||||
# compare check
|
||||
if (
|
||||
(
|
||||
isinstance(search_in, list) and
|
||||
search_value in search_in
|
||||
) or
|
||||
search_value == search_in
|
||||
):
|
||||
matching += 1
|
||||
if len(search_params) == matching:
|
||||
if return_index is True:
|
||||
# the data is now in "data sub set"
|
||||
return_items.append({
|
||||
"index": si_idx,
|
||||
"data": search_item
|
||||
})
|
||||
else:
|
||||
return_items.append(search_item)
|
||||
# return all found or empty list
|
||||
return return_items
|
||||
return corelibs_find_in_array_from_list(
|
||||
data,
|
||||
search_params,
|
||||
return_index
|
||||
)
|
||||
|
||||
|
||||
@deprecated("Use corelibs_search.data_search.key_lookup instead")
|
||||
def key_lookup(haystack: dict[str, str], key: str) -> str:
|
||||
"""
|
||||
simple key lookup in haystack, erturns empty string if not found
|
||||
@@ -122,9 +71,10 @@ def key_lookup(haystack: dict[str, str], key: str) -> str:
|
||||
Returns:
|
||||
str: _description_
|
||||
"""
|
||||
return haystack.get(key, "")
|
||||
return corelibs_key_lookup(haystack, key)
|
||||
|
||||
|
||||
@deprecated("Use corelibs_search.data_search.value_lookup instead")
|
||||
def value_lookup(haystack: dict[str, str], value: str, raise_on_many: bool = False) -> str:
|
||||
"""
|
||||
find by value, if not found returns empty, if not raise on many returns the first one
|
||||
@@ -140,11 +90,6 @@ def value_lookup(haystack: dict[str, str], value: str, raise_on_many: bool = Fal
|
||||
Returns:
|
||||
str: _description_
|
||||
"""
|
||||
keys = [__key for __key, __value in haystack.items() if __value == value]
|
||||
if not keys:
|
||||
return ""
|
||||
if raise_on_many is True and len(keys) > 1:
|
||||
raise ValueError("More than one element found with the same name")
|
||||
return keys[0]
|
||||
return corelibs_value_lookup(haystack, value, raise_on_many)
|
||||
|
||||
# __END__
|
||||
|
||||
@@ -2,9 +2,16 @@
|
||||
Various helper functions for type data clean up
|
||||
"""
|
||||
|
||||
from typing import Any, cast
|
||||
from warnings import deprecated
|
||||
from typing import Any
|
||||
from corelibs_iterator.dict_support import (
|
||||
delete_keys_from_set as corelibs_delete_keys_from_set,
|
||||
convert_to_dict_type,
|
||||
set_entry as corelibs_set_entry
|
||||
)
|
||||
|
||||
|
||||
@deprecated("Use corelibs_iterator.dict_support.delete_keys_from_set instead")
|
||||
def delete_keys_from_set(
|
||||
set_data: dict[str, Any] | list[Any] | str, keys: list[str]
|
||||
) -> dict[str, Any] | list[Any] | Any:
|
||||
@@ -19,24 +26,10 @@ def delete_keys_from_set(
|
||||
dict[str, Any] | list[Any] | None: _description_
|
||||
"""
|
||||
# skip everything if there is no keys list
|
||||
if not keys:
|
||||
return set_data
|
||||
if isinstance(set_data, dict):
|
||||
for key, value in set_data.copy().items():
|
||||
if key in keys:
|
||||
del set_data[key]
|
||||
if isinstance(value, (dict, list)):
|
||||
delete_keys_from_set(value, keys) # type: ignore Partly unknown
|
||||
elif isinstance(set_data, list):
|
||||
for value in set_data:
|
||||
if isinstance(value, (dict, list)):
|
||||
delete_keys_from_set(value, keys) # type: ignore Partly unknown
|
||||
else:
|
||||
set_data = [set_data]
|
||||
|
||||
return set_data
|
||||
return corelibs_delete_keys_from_set(set_data, keys)
|
||||
|
||||
|
||||
@deprecated("Use corelibs_iterator.dict_support.convert_to_dict_type instead")
|
||||
def build_dict(
|
||||
any_dict: Any, ignore_entries: list[str] | None = None
|
||||
) -> dict[str, Any | list[Any] | dict[Any, Any]]:
|
||||
@@ -49,18 +42,10 @@ def build_dict(
|
||||
Returns:
|
||||
dict[str, Any | list[Any]]: _description_
|
||||
"""
|
||||
if ignore_entries is None:
|
||||
return cast(dict[str, Any | list[Any] | dict[Any, Any]], any_dict)
|
||||
# ignore entries can be one key or key nested
|
||||
# return {
|
||||
# key: value for key, value in any_dict.items() if key not in ignore_entries
|
||||
# }
|
||||
return cast(
|
||||
dict[str, Any | list[Any] | dict[Any, Any]],
|
||||
delete_keys_from_set(any_dict, ignore_entries)
|
||||
)
|
||||
return convert_to_dict_type(any_dict, ignore_entries)
|
||||
|
||||
|
||||
@deprecated("Use corelibs_iterator.dict_support.set_entry instead")
|
||||
def set_entry(dict_set: dict[str, Any], key: str, value_set: Any) -> dict[str, Any]:
|
||||
"""
|
||||
set a new entry in the dict set
|
||||
@@ -73,9 +58,6 @@ def set_entry(dict_set: dict[str, Any], key: str, value_set: Any) -> dict[str, A
|
||||
Returns:
|
||||
dict[str, Any] -- _description_
|
||||
"""
|
||||
if not dict_set.get(key):
|
||||
dict_set[key] = {}
|
||||
dict_set[key] = value_set
|
||||
return dict_set
|
||||
return corelibs_set_entry(dict_set, key, value_set)
|
||||
|
||||
# __END__
|
||||
|
||||
@@ -2,8 +2,11 @@
|
||||
Dict helpers
|
||||
"""
|
||||
|
||||
|
||||
from typing import TypeAlias, Union, Dict, List, Any, cast
|
||||
from warnings import deprecated
|
||||
from typing import TypeAlias, Union, Dict, List, Any
|
||||
from corelibs_dump_data.dict_mask import (
|
||||
mask as corelibs_mask
|
||||
)
|
||||
|
||||
# definitions for the mask run below
|
||||
MaskableValue: TypeAlias = Union[str, int, float, bool, None]
|
||||
@@ -11,6 +14,7 @@ NestedDict: TypeAlias = Dict[str, Union[MaskableValue, List[Any], 'NestedDict']]
|
||||
ProcessableValue: TypeAlias = Union[MaskableValue, List[Any], NestedDict]
|
||||
|
||||
|
||||
@deprecated("use corelibs_dump_data.dict_mask.mask instead")
|
||||
def mask(
|
||||
data_set: dict[str, Any],
|
||||
mask_keys: list[str] | None = None,
|
||||
@@ -26,7 +30,7 @@ def mask(
|
||||
and end with '_', remove to search string in string
|
||||
|
||||
Arguments:
|
||||
data_set {dict[str, str]} -- _description_
|
||||
data_set {dict[str, Any]} -- _description_
|
||||
|
||||
Keyword Arguments:
|
||||
mask_keys {list[str] | None} -- _description_ (default: {None})
|
||||
@@ -37,49 +41,12 @@ def mask(
|
||||
Returns:
|
||||
dict[str, str] -- _description_
|
||||
"""
|
||||
if skip is True:
|
||||
return data_set
|
||||
if mask_keys is None:
|
||||
mask_keys = ["encryption", "password", "secret"]
|
||||
else:
|
||||
# make sure it is lower case
|
||||
mask_keys = [mask_key.lower() for mask_key in mask_keys]
|
||||
|
||||
def should_mask_key(key: str) -> bool:
|
||||
"""Check if a key should be masked"""
|
||||
__key_lower = key.lower()
|
||||
return any(
|
||||
__key_lower.startswith(mask_key) or
|
||||
__key_lower.endswith(mask_key) or
|
||||
f"{mask_str_edges}{mask_key}{mask_str_edges}" in __key_lower
|
||||
for mask_key in mask_keys
|
||||
)
|
||||
|
||||
def mask_recursive(obj: ProcessableValue) -> ProcessableValue:
|
||||
"""Recursively mask values in nested structures"""
|
||||
if isinstance(obj, dict):
|
||||
return {
|
||||
key: mask_value(value) if should_mask_key(key) else mask_recursive(value)
|
||||
for key, value in obj.items()
|
||||
}
|
||||
if isinstance(obj, list):
|
||||
return [mask_recursive(item) for item in obj]
|
||||
return obj
|
||||
|
||||
def mask_value(value: Any) -> Any:
|
||||
"""Handle masking based on value type"""
|
||||
if isinstance(value, list):
|
||||
# Mask each individual value in the list
|
||||
return [mask_str for _ in cast('list[Any]', value)]
|
||||
if isinstance(value, dict):
|
||||
# Recursively process the dictionary instead of masking the whole thing
|
||||
return mask_recursive(cast('ProcessableValue', value))
|
||||
# Mask primitive values
|
||||
return mask_str
|
||||
|
||||
return {
|
||||
key: mask_value(value) if should_mask_key(key) else mask_recursive(value)
|
||||
for key, value in data_set.items()
|
||||
}
|
||||
return corelibs_mask(
|
||||
data_set,
|
||||
mask_keys,
|
||||
mask_str,
|
||||
mask_str_edges,
|
||||
skip
|
||||
)
|
||||
|
||||
# __END__
|
||||
|
||||
@@ -2,11 +2,16 @@
|
||||
Various dictionary, object and list hashers
|
||||
"""
|
||||
|
||||
import json
|
||||
import hashlib
|
||||
from typing import Any, cast, Sequence
|
||||
from warnings import deprecated
|
||||
from typing import Any
|
||||
from corelibs_hash.fingerprint import (
|
||||
hash_object as corelibs_hash_object,
|
||||
dict_hash_frozen as corelibs_dict_hash_frozen,
|
||||
dict_hash_crc as corelibs_dict_hash_crc
|
||||
)
|
||||
|
||||
|
||||
@deprecated("use corelibs_hash.fingerprint.hash_object instead")
|
||||
def hash_object(obj: Any) -> str:
|
||||
"""
|
||||
RECOMMENDED for new use
|
||||
@@ -18,20 +23,10 @@ def hash_object(obj: Any) -> str:
|
||||
Returns:
|
||||
str -- _description_
|
||||
"""
|
||||
def normalize(o: Any) -> Any:
|
||||
if isinstance(o, dict):
|
||||
# Sort by repr of keys to handle mixed types (str, int, etc.)
|
||||
o = cast(dict[Any, Any], o)
|
||||
return tuple(sorted((repr(k), normalize(v)) for k, v in o.items()))
|
||||
if isinstance(o, (list, tuple)):
|
||||
o = cast(Sequence[Any], o)
|
||||
return tuple(normalize(item) for item in o)
|
||||
return repr(o)
|
||||
|
||||
normalized = normalize(obj)
|
||||
return hashlib.sha256(str(normalized).encode()).hexdigest()
|
||||
return corelibs_hash_object(obj)
|
||||
|
||||
|
||||
@deprecated("use corelibs_hash.fingerprint.hash_object instead")
|
||||
def dict_hash_frozen(data: dict[Any, Any]) -> int:
|
||||
"""
|
||||
NOT RECOMMENDED, use dict_hash_crc or hash_object instead
|
||||
@@ -44,9 +39,10 @@ def dict_hash_frozen(data: dict[Any, Any]) -> int:
|
||||
Returns:
|
||||
str: _description_
|
||||
"""
|
||||
return hash(frozenset(data.items()))
|
||||
return corelibs_dict_hash_frozen(data)
|
||||
|
||||
|
||||
@deprecated("use corelibs_hash.fingerprint.dict_hash_crc and for new use hash_object instead")
|
||||
def dict_hash_crc(data: dict[Any, Any] | list[Any]) -> str:
|
||||
"""
|
||||
LEGACY METHOD, must be kept for fallback, if used by other code, DO NOT CHANGE
|
||||
@@ -60,14 +56,6 @@ def dict_hash_crc(data: dict[Any, Any] | list[Any]) -> str:
|
||||
Returns:
|
||||
str: sha256 hash, prefiex with HO_ if fallback used
|
||||
"""
|
||||
try:
|
||||
return hashlib.sha256(
|
||||
# IT IS IMPORTANT THAT THE BELOW CALL STAYS THE SAME AND DOES NOT CHANGE OR WE WILL GET DIFFERENT HASHES
|
||||
# separators=(',', ':') to get rid of spaces, but if this is used the hash will be different, DO NOT ADD
|
||||
json.dumps(data, sort_keys=True, ensure_ascii=True, default=str).encode('utf-8')
|
||||
).hexdigest()
|
||||
except TypeError:
|
||||
# Fallback tod different hasher, will return DIFFERENT hash than above, so only usable in int/str key mixes
|
||||
return "HO_" + hash_object(data)
|
||||
return corelibs_dict_hash_crc(data)
|
||||
|
||||
# __END__
|
||||
|
||||
@@ -2,10 +2,16 @@
|
||||
List type helpers
|
||||
"""
|
||||
|
||||
import json
|
||||
from warnings import deprecated
|
||||
from typing import Any, Sequence
|
||||
from corelibs_iterator.list_support import (
|
||||
convert_to_list as corelibs_convert_to_list,
|
||||
is_list_in_list as corelibs_is_list_in_list,
|
||||
make_unique_list_of_dicts as corelibs_make_unique_list_of_dicts
|
||||
)
|
||||
|
||||
|
||||
@deprecated("use corelibs_iterator.list_support.convert_to_list instead")
|
||||
def convert_to_list(
|
||||
entry: str | int | float | bool | Sequence[str | int | float | bool | Sequence[Any]]
|
||||
) -> Sequence[str | int | float | bool | Sequence[Any]]:
|
||||
@@ -18,11 +24,10 @@ def convert_to_list(
|
||||
Returns:
|
||||
list[str | int | float | bool] -- _description_
|
||||
"""
|
||||
if isinstance(entry, list):
|
||||
return entry
|
||||
return [entry]
|
||||
return corelibs_convert_to_list(entry)
|
||||
|
||||
|
||||
@deprecated("use corelibs_iterator.list_support.is_list_in_list instead")
|
||||
def is_list_in_list(
|
||||
list_a: Sequence[str | int | float | bool | Sequence[Any]],
|
||||
list_b: Sequence[str | int | float | bool | Sequence[Any]]
|
||||
@@ -38,14 +43,10 @@ def is_list_in_list(
|
||||
Returns:
|
||||
list[Any] -- _description_
|
||||
"""
|
||||
# Create sets of (value, type) tuples
|
||||
set_a = set((item, type(item)) for item in list_a)
|
||||
set_b = set((item, type(item)) for item in list_b)
|
||||
|
||||
# Get the difference and extract just the values
|
||||
return [item for item, _ in set_a - set_b]
|
||||
return corelibs_is_list_in_list(list_a, list_b)
|
||||
|
||||
|
||||
@deprecated("use corelibs_iterator.list_support.make_unique_list_of_dicts instead")
|
||||
def make_unique_list_of_dicts(dict_list: list[Any]) -> list[Any]:
|
||||
"""
|
||||
Create a list of unique dictionary entries
|
||||
@@ -56,20 +57,6 @@ def make_unique_list_of_dicts(dict_list: list[Any]) -> list[Any]:
|
||||
Returns:
|
||||
list[Any] -- _description_
|
||||
"""
|
||||
try:
|
||||
# try json dumps, can fail with int and str index types
|
||||
return list(
|
||||
{
|
||||
json.dumps(d, sort_keys=True, ensure_ascii=True, separators=(',', ':')): d
|
||||
for d in dict_list
|
||||
}.values()
|
||||
)
|
||||
except TypeError:
|
||||
# Fallback for non-serializable entries, slow but works
|
||||
unique: list[Any] = []
|
||||
for d in dict_list:
|
||||
if d not in unique:
|
||||
unique.append(d)
|
||||
return unique
|
||||
return corelibs_make_unique_list_of_dicts(dict_list)
|
||||
|
||||
# __END__
|
||||
|
||||
@@ -4,10 +4,10 @@ helper functions for jmespath interfaces
|
||||
|
||||
from warnings import deprecated
|
||||
from typing import Any
|
||||
from corelibs_json.jmespath_support import jmespath_search as jmespath_search_ng
|
||||
from corelibs_search.jmespath_search import jmespath_search as jmespath_search_ng
|
||||
|
||||
|
||||
@deprecated("Use corelibs_json.jmespath_support.jmespath_search instead")
|
||||
@deprecated("Use corelibs_search.jmespath_search.jmespath_search instead")
|
||||
def jmespath_search(search_data: dict[Any, Any] | list[Any], search_params: str) -> Any:
|
||||
"""
|
||||
jmespath search wrapper
|
||||
|
||||
@@ -2,9 +2,11 @@
|
||||
Various math helpers
|
||||
"""
|
||||
|
||||
from warnings import deprecated
|
||||
import math
|
||||
|
||||
|
||||
@deprecated("Use math.gcd instead")
|
||||
def gcd(a: int, b: int):
|
||||
"""
|
||||
Calculate: Greatest Common Divisor
|
||||
@@ -19,6 +21,7 @@ def gcd(a: int, b: int):
|
||||
return math.gcd(a, b)
|
||||
|
||||
|
||||
@deprecated("Use math.lcm instead")
|
||||
def lcd(a: int, b: int):
|
||||
"""
|
||||
Calculate: Least Common Denominator
|
||||
|
||||
@@ -1,601 +0,0 @@
|
||||
"""
|
||||
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"]
|
||||
@@ -1,652 +0,0 @@
|
||||
"""
|
||||
iterator_handling.dict_helper tests
|
||||
"""
|
||||
|
||||
# pylint: disable=use-implicit-booleaness-not-comparison
|
||||
|
||||
from typing import Any
|
||||
import pytest
|
||||
from corelibs.iterator_handling.dict_helpers 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__
|
||||
@@ -1,291 +0,0 @@
|
||||
"""
|
||||
tests for corelibs.iterator_handling.dict_helpers
|
||||
"""
|
||||
|
||||
from typing import Any
|
||||
import pytest
|
||||
from corelibs.iterator_handling.dict_mask import mask
|
||||
|
||||
|
||||
def test_mask_default_behavior():
|
||||
"""Test masking with default mask_keys"""
|
||||
data = {
|
||||
"username": "john_doe",
|
||||
"password": "secret123",
|
||||
"email": "john@example.com",
|
||||
"api_secret": "abc123",
|
||||
"encryption_key": "xyz789"
|
||||
}
|
||||
|
||||
result = mask(data)
|
||||
|
||||
assert result["username"] == "john_doe"
|
||||
assert result["password"] == "***"
|
||||
assert result["email"] == "john@example.com"
|
||||
assert result["api_secret"] == "***"
|
||||
assert result["encryption_key"] == "***"
|
||||
|
||||
|
||||
def test_mask_custom_keys():
|
||||
"""Test masking with custom mask_keys"""
|
||||
data = {
|
||||
"username": "john_doe",
|
||||
"token": "abc123",
|
||||
"api_key": "xyz789",
|
||||
"password": "secret123"
|
||||
}
|
||||
|
||||
result = mask(data, mask_keys=["token", "api"])
|
||||
|
||||
assert result["username"] == "john_doe"
|
||||
assert result["token"] == "***"
|
||||
assert result["api_key"] == "***"
|
||||
assert result["password"] == "secret123" # Not masked with custom keys
|
||||
|
||||
|
||||
def test_mask_custom_mask_string():
|
||||
"""Test masking with custom mask string"""
|
||||
data = {"password": "secret123"}
|
||||
|
||||
result = mask(data, mask_str="[HIDDEN]")
|
||||
|
||||
assert result["password"] == "[HIDDEN]"
|
||||
|
||||
|
||||
def test_mask_case_insensitive():
|
||||
"""Test that masking is case insensitive"""
|
||||
data = {
|
||||
"PASSWORD": "secret123",
|
||||
"Secret_Key": "abc123",
|
||||
"ENCRYPTION_data": "xyz789"
|
||||
}
|
||||
|
||||
result = mask(data)
|
||||
|
||||
assert result["PASSWORD"] == "***"
|
||||
assert result["Secret_Key"] == "***"
|
||||
assert result["ENCRYPTION_data"] == "***"
|
||||
|
||||
|
||||
def test_mask_key_patterns():
|
||||
"""Test different key matching patterns (start, end, contains)"""
|
||||
data = {
|
||||
"password_hash": "hash123", # starts with
|
||||
"user_password": "secret123", # ends with
|
||||
"my_secret_key": "abc123", # contains with edges
|
||||
"secretvalue": "xyz789", # contains without edges
|
||||
"startsecretvalue": "xyz123", # contains without edges
|
||||
"normal_key": "normal_value"
|
||||
}
|
||||
|
||||
result = mask(data)
|
||||
|
||||
assert result["password_hash"] == "***"
|
||||
assert result["user_password"] == "***"
|
||||
assert result["my_secret_key"] == "***"
|
||||
assert result["secretvalue"] == "***" # will mask beacuse starts with
|
||||
assert result["startsecretvalue"] == "xyz123" # will not mask
|
||||
assert result["normal_key"] == "normal_value"
|
||||
|
||||
|
||||
def test_mask_custom_edges():
|
||||
"""Test masking with custom edge characters"""
|
||||
data = {
|
||||
"my-secret-key": "abc123",
|
||||
"my_secret_key": "xyz789"
|
||||
}
|
||||
|
||||
result = mask(data, mask_str_edges="-")
|
||||
|
||||
assert result["my-secret-key"] == "***"
|
||||
assert result["my_secret_key"] == "xyz789" # Underscore edges don't match
|
||||
|
||||
|
||||
def test_mask_empty_edges():
|
||||
"""Test masking with empty edge characters (substring matching)"""
|
||||
data = {
|
||||
"secretvalue": "abc123",
|
||||
"mysecretkey": "xyz789",
|
||||
"normal_key": "normal_value"
|
||||
}
|
||||
|
||||
result = mask(data, mask_str_edges="")
|
||||
|
||||
assert result["secretvalue"] == "***"
|
||||
assert result["mysecretkey"] == "***"
|
||||
assert result["normal_key"] == "normal_value"
|
||||
|
||||
|
||||
def test_mask_nested_dict():
|
||||
"""Test masking nested dictionaries"""
|
||||
data = {
|
||||
"user": {
|
||||
"name": "john",
|
||||
"password": "secret123",
|
||||
"profile": {
|
||||
"email": "john@example.com",
|
||||
"encryption_key": "abc123"
|
||||
}
|
||||
},
|
||||
"api_secret": "xyz789"
|
||||
}
|
||||
|
||||
result = mask(data)
|
||||
|
||||
assert result["user"]["name"] == "john"
|
||||
assert result["user"]["password"] == "***"
|
||||
assert result["user"]["profile"]["email"] == "john@example.com"
|
||||
assert result["user"]["profile"]["encryption_key"] == "***"
|
||||
assert result["api_secret"] == "***"
|
||||
|
||||
|
||||
def test_mask_lists():
|
||||
"""Test masking lists and nested structures with lists"""
|
||||
data = {
|
||||
"users": [
|
||||
{"name": "john", "password": "secret1"},
|
||||
{"name": "jane", "password": "secret2"}
|
||||
],
|
||||
"secrets": ["secret1", "secret2", "secret3"]
|
||||
}
|
||||
|
||||
result = mask(data)
|
||||
print(f"R {result['secrets']}")
|
||||
|
||||
assert result["users"][0]["name"] == "john"
|
||||
assert result["users"][0]["password"] == "***"
|
||||
assert result["users"][1]["name"] == "jane"
|
||||
assert result["users"][1]["password"] == "***"
|
||||
assert result["secrets"] == ["***", "***", "***"]
|
||||
|
||||
|
||||
def test_mask_mixed_types():
|
||||
"""Test masking with different value types"""
|
||||
data = {
|
||||
"password": "string_value",
|
||||
"secret_number": 12345,
|
||||
"encryption_flag": True,
|
||||
"secret_float": 3.14,
|
||||
"password_none": None,
|
||||
"normal_key": "normal_value"
|
||||
}
|
||||
|
||||
result = mask(data)
|
||||
|
||||
assert result["password"] == "***"
|
||||
assert result["secret_number"] == "***"
|
||||
assert result["encryption_flag"] == "***"
|
||||
assert result["secret_float"] == "***"
|
||||
assert result["password_none"] == "***"
|
||||
assert result["normal_key"] == "normal_value"
|
||||
|
||||
|
||||
def test_mask_skip_true():
|
||||
"""Test that skip=True returns original data unchanged"""
|
||||
data = {
|
||||
"password": "secret123",
|
||||
"encryption_key": "abc123",
|
||||
"normal_key": "normal_value"
|
||||
}
|
||||
|
||||
result = mask(data, skip=True)
|
||||
|
||||
assert result == data
|
||||
assert result is data # Should return the same object
|
||||
|
||||
|
||||
def test_mask_empty_dict():
|
||||
"""Test masking empty dictionary"""
|
||||
data: dict[str, Any] = {}
|
||||
|
||||
result = mask(data)
|
||||
|
||||
assert result == {}
|
||||
|
||||
|
||||
def test_mask_none_mask_keys():
|
||||
"""Test explicit None mask_keys uses defaults"""
|
||||
data = {"password": "secret123", "token": "abc123"}
|
||||
|
||||
result = mask(data, mask_keys=None)
|
||||
|
||||
assert result["password"] == "***"
|
||||
assert result["token"] == "abc123" # Not in default keys
|
||||
|
||||
|
||||
def test_mask_empty_mask_keys():
|
||||
"""Test empty mask_keys list"""
|
||||
data = {"password": "secret123", "secret": "abc123"}
|
||||
|
||||
result = mask(data, mask_keys=[])
|
||||
|
||||
assert result["password"] == "secret123"
|
||||
assert result["secret"] == "abc123"
|
||||
|
||||
|
||||
def test_mask_complex_nested_structure():
|
||||
"""Test masking complex nested structure"""
|
||||
data = {
|
||||
"config": {
|
||||
"database": {
|
||||
"host": "localhost",
|
||||
"password": "db_secret",
|
||||
"users": [
|
||||
{"name": "admin", "password": "admin123"},
|
||||
{"name": "user", "secret_key": "user456"}
|
||||
]
|
||||
},
|
||||
"api": {
|
||||
"endpoints": ["api1", "api2"],
|
||||
"encryption_settings": {
|
||||
"enabled": True,
|
||||
"secret": "api_secret"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result = mask(data)
|
||||
|
||||
assert result["config"]["database"]["host"] == "localhost"
|
||||
assert result["config"]["database"]["password"] == "***"
|
||||
assert result["config"]["database"]["users"][0]["name"] == "admin"
|
||||
assert result["config"]["database"]["users"][0]["password"] == "***"
|
||||
assert result["config"]["database"]["users"][1]["name"] == "user"
|
||||
assert result["config"]["database"]["users"][1]["secret_key"] == "***"
|
||||
assert result["config"]["api"]["endpoints"] == ["api1", "api2"]
|
||||
assert result["config"]["api"]["encryption_settings"]["enabled"] is True
|
||||
assert result["config"]["api"]["encryption_settings"]["secret"] == "***"
|
||||
|
||||
|
||||
def test_mask_preserves_original_data():
|
||||
"""Test that original data is not modified"""
|
||||
original_data = {
|
||||
"password": "secret123",
|
||||
"username": "john_doe"
|
||||
}
|
||||
data_copy = original_data.copy()
|
||||
|
||||
result = mask(original_data)
|
||||
|
||||
assert original_data == data_copy # Original unchanged
|
||||
assert result != original_data # Result is different
|
||||
assert result["password"] == "***"
|
||||
assert original_data["password"] == "secret123"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("mask_key,expected_keys", [
|
||||
(["pass"], ["password", "user_pass", "my_pass_key"]),
|
||||
(["key"], ["api_key", "secret_key", "my_key_value"]),
|
||||
(["token"], ["token", "auth_token", "my_token_here"]),
|
||||
])
|
||||
def test_mask_parametrized_keys(mask_key: list[str], expected_keys: list[str]):
|
||||
"""Parametrized test for different mask key patterns"""
|
||||
data = {key: "value" for key in expected_keys}
|
||||
data["normal_entry"] = "normal_value"
|
||||
|
||||
result = mask(data, mask_keys=mask_key)
|
||||
|
||||
for key in expected_keys:
|
||||
assert result[key] == "***"
|
||||
assert result["normal_entry"] == "normal_value"
|
||||
@@ -1,565 +0,0 @@
|
||||
"""
|
||||
tests for corelibs.iterator_handling.fingerprint
|
||||
"""
|
||||
|
||||
from typing import Any
|
||||
import pytest
|
||||
from corelibs.iterator_handling.fingerprint import dict_hash_frozen, dict_hash_crc, hash_object
|
||||
|
||||
|
||||
class TestHashObject:
|
||||
"""Tests for hash_object function"""
|
||||
|
||||
def test_hash_object_simple_dict(self):
|
||||
"""Test hashing a simple dictionary with hash_object"""
|
||||
data = {"key1": "value1", "key2": "value2"}
|
||||
result = hash_object(data)
|
||||
|
||||
assert isinstance(result, str)
|
||||
assert len(result) == 64 # SHA256 produces 64 hex characters
|
||||
|
||||
def test_hash_object_mixed_keys(self):
|
||||
"""Test hash_object with mixed int and string keys"""
|
||||
data = {"key1": "value1", 1: "value2", 2: "value3"}
|
||||
result = hash_object(data)
|
||||
|
||||
assert isinstance(result, str)
|
||||
assert len(result) == 64
|
||||
|
||||
def test_hash_object_consistency(self):
|
||||
"""Test that hash_object produces consistent results"""
|
||||
data = {"str_key": "value", 123: "number_key"}
|
||||
hash1 = hash_object(data)
|
||||
hash2 = hash_object(data)
|
||||
|
||||
assert hash1 == hash2
|
||||
|
||||
def test_hash_object_order_independence(self):
|
||||
"""Test that hash_object is order-independent"""
|
||||
data1 = {"a": 1, 1: "one", "b": 2, 2: "two"}
|
||||
data2 = {2: "two", "b": 2, 1: "one", "a": 1}
|
||||
hash1 = hash_object(data1)
|
||||
hash2 = hash_object(data2)
|
||||
|
||||
assert hash1 == hash2
|
||||
|
||||
def test_hash_object_list_of_dicts_mixed_keys(self):
|
||||
"""Test hash_object with list of dicts containing mixed keys"""
|
||||
data = [
|
||||
{"name": "item1", 1: "value1"},
|
||||
{"name": "item2", 2: "value2"}
|
||||
]
|
||||
result = hash_object(data)
|
||||
|
||||
assert isinstance(result, str)
|
||||
assert len(result) == 64
|
||||
|
||||
def test_hash_object_nested_mixed_keys(self):
|
||||
"""Test hash_object with nested structures containing mixed keys"""
|
||||
data = {
|
||||
"outer": {
|
||||
"inner": "value",
|
||||
1: "mixed_key"
|
||||
},
|
||||
2: "another_mixed"
|
||||
}
|
||||
result = hash_object(data)
|
||||
|
||||
assert isinstance(result, str)
|
||||
assert len(result) == 64
|
||||
|
||||
def test_hash_object_different_data(self):
|
||||
"""Test that different data produces different hashes"""
|
||||
data1 = {"key": "value", 1: "one"}
|
||||
data2 = {"key": "value", 2: "two"}
|
||||
hash1 = hash_object(data1)
|
||||
hash2 = hash_object(data2)
|
||||
|
||||
assert hash1 != hash2
|
||||
|
||||
def test_hash_object_complex_nested(self):
|
||||
"""Test hash_object with complex nested structures"""
|
||||
data = {
|
||||
"level1": {
|
||||
"level2": {
|
||||
1: "value",
|
||||
"key": [1, 2, {"nested": "deep", 3: "int_key"}]
|
||||
}
|
||||
}
|
||||
}
|
||||
result = hash_object(data)
|
||||
|
||||
assert isinstance(result, str)
|
||||
assert len(result) == 64
|
||||
|
||||
def test_hash_object_list_with_tuples(self):
|
||||
"""Test hash_object with lists containing tuples"""
|
||||
data = [("a", 1), ("b", 2), {1: "mixed", "key": "value"}]
|
||||
result = hash_object(data)
|
||||
|
||||
assert isinstance(result, str)
|
||||
assert len(result) == 64
|
||||
|
||||
|
||||
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
|
||||
|
||||
def test_dict_hash_crc_fallback_mixed_keys(self):
|
||||
"""Test dict_hash_crc fallback with mixed int and string keys"""
|
||||
data = {"key1": "value1", 1: "value2", 2: "value3"}
|
||||
result = dict_hash_crc(data)
|
||||
|
||||
assert isinstance(result, str)
|
||||
# Fallback prefixes with "HO_"
|
||||
assert result.startswith("HO_")
|
||||
# Hash should be 64 chars + 3 char prefix = 67 total
|
||||
assert len(result) == 67
|
||||
|
||||
def test_dict_hash_crc_fallback_consistency(self):
|
||||
"""Test that fallback produces consistent hashes"""
|
||||
data = {"str_key": "value", 123: "number_key", 456: "another"}
|
||||
hash1 = dict_hash_crc(data)
|
||||
hash2 = dict_hash_crc(data)
|
||||
|
||||
assert hash1 == hash2
|
||||
assert hash1.startswith("HO_")
|
||||
|
||||
def test_dict_hash_crc_fallback_order_independence(self):
|
||||
"""Test that fallback is order-independent for mixed-key dicts"""
|
||||
data1 = {"a": 1, 1: "one", "b": 2, 2: "two"}
|
||||
data2 = {2: "two", "b": 2, 1: "one", "a": 1}
|
||||
hash1 = dict_hash_crc(data1)
|
||||
hash2 = dict_hash_crc(data2)
|
||||
|
||||
assert hash1 == hash2
|
||||
assert hash1.startswith("HO_")
|
||||
|
||||
def test_dict_hash_crc_fallback_list_of_dicts_mixed_keys(self):
|
||||
"""Test fallback with list of dicts containing mixed keys"""
|
||||
data = [
|
||||
{"name": "item1", 1: "value1"},
|
||||
{"name": "item2", 2: "value2"},
|
||||
{3: "value3", "type": "mixed"}
|
||||
]
|
||||
result = dict_hash_crc(data)
|
||||
|
||||
assert isinstance(result, str)
|
||||
assert result.startswith("HO_")
|
||||
assert len(result) == 67
|
||||
|
||||
def test_dict_hash_crc_fallback_nested_mixed_keys(self):
|
||||
"""Test fallback with nested dicts containing mixed keys"""
|
||||
data = {
|
||||
"outer": {
|
||||
"inner": "value",
|
||||
1: "mixed_key"
|
||||
},
|
||||
2: "another_mixed"
|
||||
}
|
||||
result = dict_hash_crc(data)
|
||||
|
||||
assert isinstance(result, str)
|
||||
assert result.startswith("HO_")
|
||||
assert len(result) == 67
|
||||
|
||||
def test_dict_hash_crc_fallback_different_data(self):
|
||||
"""Test that different mixed-key data produces different hashes"""
|
||||
data1 = {"key": "value", 1: "one"}
|
||||
data2 = {"key": "value", 2: "two"}
|
||||
hash1 = dict_hash_crc(data1)
|
||||
hash2 = dict_hash_crc(data2)
|
||||
|
||||
assert hash1 != hash2
|
||||
assert hash1.startswith("HO_")
|
||||
assert hash2.startswith("HO_")
|
||||
|
||||
def test_dict_hash_crc_fallback_complex_structure(self):
|
||||
"""Test fallback with complex nested structure with mixed keys"""
|
||||
data = [
|
||||
{
|
||||
"id": 1,
|
||||
1: "first",
|
||||
"data": {
|
||||
"nested": "value",
|
||||
100: "nested_int_key"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
2: "second",
|
||||
"items": [1, 2, 3]
|
||||
}
|
||||
]
|
||||
result = dict_hash_crc(data)
|
||||
|
||||
assert isinstance(result, str)
|
||||
assert result.startswith("HO_")
|
||||
assert len(result) == 67
|
||||
|
||||
def test_dict_hash_crc_no_fallback_string_keys_only(self):
|
||||
"""Test that string-only keys don't trigger fallback"""
|
||||
data = {"key1": "value1", "key2": "value2", "key3": "value3"}
|
||||
result = dict_hash_crc(data)
|
||||
|
||||
assert isinstance(result, str)
|
||||
assert not result.startswith("HO_")
|
||||
assert len(result) == 64
|
||||
|
||||
def test_dict_hash_crc_no_fallback_int_keys_only(self):
|
||||
"""Test that int-only keys don't trigger fallback"""
|
||||
data = {1: "one", 2: "two", 3: "three"}
|
||||
result = dict_hash_crc(data)
|
||||
|
||||
assert isinstance(result, str)
|
||||
assert not result.startswith("HO_")
|
||||
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)
|
||||
@@ -1,522 +0,0 @@
|
||||
"""
|
||||
iterator_handling.list_helepr tests
|
||||
"""
|
||||
|
||||
from typing import Any
|
||||
import pytest
|
||||
from corelibs.iterator_handling.list_helpers import convert_to_list, is_list_in_list, make_unique_list_of_dicts
|
||||
|
||||
|
||||
class TestConvertToList:
|
||||
"""Test cases for convert_to_list function"""
|
||||
|
||||
def test_string_input(self):
|
||||
"""Test with string inputs"""
|
||||
assert convert_to_list("hello") == ["hello"]
|
||||
assert convert_to_list("") == [""]
|
||||
assert convert_to_list("123") == ["123"]
|
||||
assert convert_to_list("true") == ["true"]
|
||||
|
||||
def test_integer_input(self):
|
||||
"""Test with integer inputs"""
|
||||
assert convert_to_list(42) == [42]
|
||||
assert convert_to_list(0) == [0]
|
||||
assert convert_to_list(-10) == [-10]
|
||||
assert convert_to_list(999999) == [999999]
|
||||
|
||||
def test_float_input(self):
|
||||
"""Test with float inputs"""
|
||||
assert convert_to_list(3.14) == [3.14]
|
||||
assert convert_to_list(0.0) == [0.0]
|
||||
assert convert_to_list(-2.5) == [-2.5]
|
||||
assert convert_to_list(1.0) == [1.0]
|
||||
|
||||
def test_boolean_input(self):
|
||||
"""Test with boolean inputs"""
|
||||
assert convert_to_list(True) == [True]
|
||||
assert convert_to_list(False) == [False]
|
||||
|
||||
def test_list_input_unchanged(self):
|
||||
"""Test that list inputs are returned unchanged"""
|
||||
# String lists
|
||||
str_list = ["a", "b", "c"]
|
||||
assert convert_to_list(str_list) == str_list
|
||||
assert convert_to_list(str_list) is str_list # Same object reference
|
||||
|
||||
# Integer lists
|
||||
int_list = [1, 2, 3]
|
||||
assert convert_to_list(int_list) == int_list
|
||||
assert convert_to_list(int_list) is int_list
|
||||
|
||||
# Float lists
|
||||
float_list = [1.1, 2.2, 3.3]
|
||||
assert convert_to_list(float_list) == float_list
|
||||
assert convert_to_list(float_list) is float_list
|
||||
|
||||
# Boolean lists
|
||||
bool_list = [True, False, True]
|
||||
assert convert_to_list(bool_list) == bool_list
|
||||
assert convert_to_list(bool_list) is bool_list
|
||||
|
||||
# Mixed lists
|
||||
mixed_list = [1, "hello", 3.14, True]
|
||||
assert convert_to_list(mixed_list) == mixed_list
|
||||
assert convert_to_list(mixed_list) is mixed_list
|
||||
|
||||
# Empty list
|
||||
empty_list: list[int] = []
|
||||
assert convert_to_list(empty_list) == empty_list
|
||||
assert convert_to_list(empty_list) is empty_list
|
||||
|
||||
def test_nested_lists(self):
|
||||
"""Test with nested lists (should still return the same list)"""
|
||||
nested_list: list[list[int]] = [[1, 2], [3, 4]]
|
||||
assert convert_to_list(nested_list) == nested_list
|
||||
assert convert_to_list(nested_list) is nested_list
|
||||
|
||||
def test_single_element_lists(self):
|
||||
"""Test with single element lists"""
|
||||
single_str = ["hello"]
|
||||
assert convert_to_list(single_str) == single_str
|
||||
assert convert_to_list(single_str) is single_str
|
||||
|
||||
single_int = [42]
|
||||
assert convert_to_list(single_int) == single_int
|
||||
assert convert_to_list(single_int) is single_int
|
||||
|
||||
|
||||
class TestIsListInList:
|
||||
"""Test cases for is_list_in_list function"""
|
||||
|
||||
def test_string_lists(self):
|
||||
"""Test with string lists"""
|
||||
list_a = ["a", "b", "c", "d"]
|
||||
list_b = ["b", "d", "e"]
|
||||
result = is_list_in_list(list_a, list_b)
|
||||
assert set(result) == {"a", "c"}
|
||||
assert isinstance(result, list)
|
||||
|
||||
def test_integer_lists(self):
|
||||
"""Test with integer lists"""
|
||||
list_a = [1, 2, 3, 4, 5]
|
||||
list_b = [2, 4, 6]
|
||||
result = is_list_in_list(list_a, list_b)
|
||||
assert set(result) == {1, 3, 5}
|
||||
assert isinstance(result, list)
|
||||
|
||||
def test_float_lists(self):
|
||||
"""Test with float lists"""
|
||||
list_a = [1.1, 2.2, 3.3, 4.4]
|
||||
list_b = [2.2, 4.4, 5.5]
|
||||
result = is_list_in_list(list_a, list_b)
|
||||
assert set(result) == {1.1, 3.3}
|
||||
assert isinstance(result, list)
|
||||
|
||||
def test_boolean_lists(self):
|
||||
"""Test with boolean lists"""
|
||||
list_a = [True, False, True]
|
||||
list_b = [True]
|
||||
result = is_list_in_list(list_a, list_b)
|
||||
assert set(result) == {False}
|
||||
assert isinstance(result, list)
|
||||
|
||||
def test_mixed_type_lists(self):
|
||||
"""Test with mixed type lists"""
|
||||
list_a = [1, "hello", 3.14, True, "world"]
|
||||
list_b = ["hello", True, 42]
|
||||
result = is_list_in_list(list_a, list_b)
|
||||
assert set(result) == {1, 3.14, "world"}
|
||||
assert isinstance(result, list)
|
||||
|
||||
def test_empty_lists(self):
|
||||
"""Test with empty lists"""
|
||||
# Empty list_a
|
||||
assert is_list_in_list([], [1, 2, 3]) == []
|
||||
|
||||
# Empty list_b
|
||||
list_a = [1, 2, 3]
|
||||
result = is_list_in_list(list_a, [])
|
||||
assert set(result) == {1, 2, 3}
|
||||
|
||||
# Both empty
|
||||
assert is_list_in_list([], []) == []
|
||||
|
||||
def test_no_common_elements(self):
|
||||
"""Test when lists have no common elements"""
|
||||
list_a = [1, 2, 3]
|
||||
list_b = [4, 5, 6]
|
||||
result = is_list_in_list(list_a, list_b)
|
||||
assert set(result) == {1, 2, 3}
|
||||
|
||||
def test_all_elements_common(self):
|
||||
"""Test when all elements in list_a are in list_b"""
|
||||
list_a = [1, 2, 3]
|
||||
list_b = [1, 2, 3, 4, 5]
|
||||
result = is_list_in_list(list_a, list_b)
|
||||
assert result == []
|
||||
|
||||
def test_identical_lists(self):
|
||||
"""Test with identical lists"""
|
||||
list_a = [1, 2, 3]
|
||||
list_b = [1, 2, 3]
|
||||
result = is_list_in_list(list_a, list_b)
|
||||
assert result == []
|
||||
|
||||
def test_duplicate_elements(self):
|
||||
"""Test with duplicate elements in lists"""
|
||||
list_a = [1, 2, 2, 3, 3, 3]
|
||||
list_b = [2, 4]
|
||||
result = is_list_in_list(list_a, list_b)
|
||||
# Should return unique elements only (set behavior)
|
||||
assert set(result) == {1, 3}
|
||||
assert isinstance(result, list)
|
||||
|
||||
def test_list_b_larger_than_list_a(self):
|
||||
"""Test when list_b is larger than list_a"""
|
||||
list_a = [1, 2]
|
||||
list_b = [2, 3, 4, 5, 6, 7, 8]
|
||||
result = is_list_in_list(list_a, list_b)
|
||||
assert set(result) == {1}
|
||||
|
||||
def test_order_independence(self):
|
||||
"""Test that order doesn't matter due to set operations"""
|
||||
list_a = [3, 1, 4, 1, 5]
|
||||
list_b = [1, 2, 6]
|
||||
result = is_list_in_list(list_a, list_b)
|
||||
assert set(result) == {3, 4, 5}
|
||||
|
||||
|
||||
# Parametrized tests for more comprehensive coverage
|
||||
class TestParametrized:
|
||||
"""Parametrized tests for better coverage"""
|
||||
|
||||
@pytest.mark.parametrize("input_value,expected", [
|
||||
("hello", ["hello"]),
|
||||
(42, [42]),
|
||||
(3.14, [3.14]),
|
||||
(True, [True]),
|
||||
(False, [False]),
|
||||
("", [""]),
|
||||
(0, [0]),
|
||||
(0.0, [0.0]),
|
||||
(-1, [-1]),
|
||||
(-2.5, [-2.5]),
|
||||
])
|
||||
def test_convert_to_list_parametrized(self, input_value: Any, expected: Any):
|
||||
"""Test convert_to_list with various single values"""
|
||||
assert convert_to_list(input_value) == expected
|
||||
|
||||
@pytest.mark.parametrize("input_list", [
|
||||
[1, 2, 3],
|
||||
["a", "b", "c"],
|
||||
[1.1, 2.2, 3.3],
|
||||
[True, False],
|
||||
[1, "hello", 3.14, True],
|
||||
[],
|
||||
[42],
|
||||
[[1, 2], [3, 4]],
|
||||
])
|
||||
def test_convert_to_list_with_lists_parametrized(self, input_list: Any):
|
||||
"""Test convert_to_list with various list inputs"""
|
||||
result = convert_to_list(input_list)
|
||||
assert result == input_list
|
||||
assert result is input_list # Same object reference
|
||||
|
||||
@pytest.mark.parametrize("list_a,list_b,expected_set", [
|
||||
([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[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}),
|
||||
])
|
||||
def test_is_list_in_list_parametrized(self, list_a: list[Any], list_b: list[Any], expected_set: Any):
|
||||
"""Test is_list_in_list with various input combinations"""
|
||||
result = is_list_in_list(list_a, list_b)
|
||||
assert set(result) == expected_set
|
||||
assert isinstance(result, list)
|
||||
|
||||
|
||||
# Edge cases and special scenarios
|
||||
class TestEdgeCases:
|
||||
"""Test edge cases and special scenarios"""
|
||||
|
||||
def test_convert_to_list_with_none_like_values(self):
|
||||
"""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
|
||||
|
||||
def test_is_list_in_list_preserves_type_distinctions(self):
|
||||
"""Test that different types are treated as different"""
|
||||
list_a = [1, "1", 1.0, True]
|
||||
list_b = [1] # Only integer 1
|
||||
result = is_list_in_list(list_a, list_b)
|
||||
|
||||
# Note: This test depends on how Python's set handles type equality
|
||||
# 1, 1.0, and True are considered equal in sets
|
||||
# "1" is different from 1
|
||||
# expected_items = {"1"} # String "1" should remain
|
||||
assert "1" in result
|
||||
assert isinstance(result, list)
|
||||
|
||||
def test_large_lists(self):
|
||||
"""Test with large lists"""
|
||||
large_list_a = list(range(1000))
|
||||
large_list_b = list(range(500, 1500))
|
||||
result = is_list_in_list(large_list_a, large_list_b)
|
||||
expected = list(range(500)) # 0 to 499
|
||||
assert set(result) == set(expected)
|
||||
|
||||
def test_memory_efficiency(self):
|
||||
"""Test that convert_to_list doesn't create unnecessary copies"""
|
||||
original_list = [1, 2, 3, 4, 5]
|
||||
result = convert_to_list(original_list)
|
||||
|
||||
# Should be the same object, not a copy
|
||||
assert result is original_list
|
||||
|
||||
# Modifying the original should affect the result
|
||||
original_list.append(6)
|
||||
assert 6 in result
|
||||
|
||||
|
||||
# Performance tests (optional)
|
||||
class TestPerformance:
|
||||
"""Performance-related tests"""
|
||||
|
||||
def test_is_list_in_list_with_duplicates_performance(self):
|
||||
"""Test that function handles duplicates efficiently"""
|
||||
# List with many duplicates
|
||||
list_a = [1, 2, 3] * 100 # 300 elements, many duplicates
|
||||
list_b = [2] * 50 # 50 elements, all the same
|
||||
|
||||
result = is_list_in_list(list_a, list_b)
|
||||
|
||||
# Should still work correctly despite duplicates
|
||||
assert set(result) == {1, 3}
|
||||
assert isinstance(result, list)
|
||||
|
||||
|
||||
class TestMakeUniqueListOfDicts:
|
||||
"""Test cases for make_unique_list_of_dicts function"""
|
||||
|
||||
def test_basic_duplicate_removal(self):
|
||||
"""Test basic removal of duplicate dictionaries"""
|
||||
dict_list = [
|
||||
{"a": 1, "b": 2},
|
||||
{"a": 1, "b": 2},
|
||||
{"a": 3, "b": 4}
|
||||
]
|
||||
result = make_unique_list_of_dicts(dict_list)
|
||||
assert len(result) == 2
|
||||
assert {"a": 1, "b": 2} in result
|
||||
assert {"a": 3, "b": 4} in result
|
||||
|
||||
def test_order_independent_duplicates(self):
|
||||
"""Test that dictionaries with different key orders are treated as duplicates"""
|
||||
dict_list = [
|
||||
{"a": 1, "b": 2},
|
||||
{"b": 2, "a": 1}, # Same content, different order
|
||||
{"a": 3, "b": 4}
|
||||
]
|
||||
result = make_unique_list_of_dicts(dict_list)
|
||||
assert len(result) == 2
|
||||
assert {"a": 1, "b": 2} in result
|
||||
assert {"a": 3, "b": 4} in result
|
||||
|
||||
def test_empty_list(self):
|
||||
"""Test with empty list"""
|
||||
result = make_unique_list_of_dicts([])
|
||||
assert result == []
|
||||
assert isinstance(result, list)
|
||||
|
||||
def test_single_dict(self):
|
||||
"""Test with single dictionary"""
|
||||
dict_list = [{"a": 1, "b": 2}]
|
||||
result = make_unique_list_of_dicts(dict_list)
|
||||
assert result == [{"a": 1, "b": 2}]
|
||||
|
||||
def test_all_unique(self):
|
||||
"""Test when all dictionaries are unique"""
|
||||
dict_list = [
|
||||
{"a": 1},
|
||||
{"b": 2},
|
||||
{"c": 3},
|
||||
{"d": 4}
|
||||
]
|
||||
result = make_unique_list_of_dicts(dict_list)
|
||||
assert len(result) == 4
|
||||
for d in dict_list:
|
||||
assert d in result
|
||||
|
||||
def test_all_duplicates(self):
|
||||
"""Test when all dictionaries are duplicates"""
|
||||
dict_list = [
|
||||
{"a": 1, "b": 2},
|
||||
{"a": 1, "b": 2},
|
||||
{"a": 1, "b": 2},
|
||||
{"b": 2, "a": 1}
|
||||
]
|
||||
result = make_unique_list_of_dicts(dict_list)
|
||||
assert len(result) == 1
|
||||
assert result[0] == {"a": 1, "b": 2}
|
||||
|
||||
def test_nested_values(self):
|
||||
"""Test with nested structures as values"""
|
||||
dict_list = [
|
||||
{"a": [1, 2], "b": 3},
|
||||
{"a": [1, 2], "b": 3},
|
||||
{"a": [1, 3], "b": 3}
|
||||
]
|
||||
result = make_unique_list_of_dicts(dict_list)
|
||||
assert len(result) == 2
|
||||
assert {"a": [1, 2], "b": 3} in result
|
||||
assert {"a": [1, 3], "b": 3} in result
|
||||
|
||||
def test_different_value_types(self):
|
||||
"""Test with different value types"""
|
||||
dict_list = [
|
||||
{"str": "hello", "int": 42, "float": 3.14, "bool": True},
|
||||
{"str": "hello", "int": 42, "float": 3.14, "bool": True},
|
||||
{"str": "world", "int": 99, "float": 2.71, "bool": False}
|
||||
]
|
||||
result = make_unique_list_of_dicts(dict_list)
|
||||
assert len(result) == 2
|
||||
|
||||
def test_empty_dicts(self):
|
||||
"""Test with empty dictionaries"""
|
||||
dict_list: list[Any] = [
|
||||
{},
|
||||
{},
|
||||
{"a": 1}
|
||||
]
|
||||
result = make_unique_list_of_dicts(dict_list)
|
||||
assert len(result) == 2
|
||||
assert {} in result
|
||||
assert {"a": 1} in result
|
||||
|
||||
def test_single_key_dicts(self):
|
||||
"""Test with single key dictionaries"""
|
||||
dict_list = [
|
||||
{"a": 1},
|
||||
{"a": 1},
|
||||
{"a": 2},
|
||||
{"b": 1}
|
||||
]
|
||||
result = make_unique_list_of_dicts(dict_list)
|
||||
assert len(result) == 3
|
||||
assert {"a": 1} in result
|
||||
assert {"a": 2} in result
|
||||
assert {"b": 1} in result
|
||||
|
||||
def test_many_keys(self):
|
||||
"""Test with dictionaries containing many keys"""
|
||||
dict1 = {"a": 1, "b": 2, "c": 3, "d": 4, "e": 5}
|
||||
dict2 = {"e": 5, "d": 4, "c": 3, "b": 2, "a": 1} # Same, different order
|
||||
dict3 = {"a": 1, "b": 2, "c": 3, "d": 4, "e": 6} # Different value
|
||||
dict_list = [dict1, dict2, dict3]
|
||||
result = make_unique_list_of_dicts(dict_list)
|
||||
assert len(result) == 2
|
||||
|
||||
def test_numeric_keys(self):
|
||||
"""Test with numeric keys"""
|
||||
dict_list = [
|
||||
{1: "one", 2: "two"},
|
||||
{2: "two", 1: "one"},
|
||||
{1: "one", 2: "three"}
|
||||
]
|
||||
result = make_unique_list_of_dicts(dict_list)
|
||||
assert len(result) == 2
|
||||
|
||||
def test_none_values(self):
|
||||
"""Test with None values"""
|
||||
dict_list = [
|
||||
{"a": None, "b": 2},
|
||||
{"a": None, "b": 2},
|
||||
{"a": 1, "b": None}
|
||||
]
|
||||
result = make_unique_list_of_dicts(dict_list)
|
||||
assert len(result) == 2
|
||||
assert {"a": None, "b": 2} in result
|
||||
assert {"a": 1, "b": None} in result
|
||||
|
||||
def test_mixed_key_types(self):
|
||||
"""Test with mixed key types (string and numeric)"""
|
||||
dict_list = [
|
||||
{"a": 1, 1: "one"},
|
||||
{1: "one", "a": 1},
|
||||
{"a": 2, 1: "one"}
|
||||
]
|
||||
result = make_unique_list_of_dicts(dict_list)
|
||||
assert len(result) == 2
|
||||
|
||||
@pytest.mark.parametrize("dict_list,expected_length", [
|
||||
([{"a": 1}, {"a": 1}, {"a": 1}], 1),
|
||||
([{"a": 1}, {"a": 2}, {"a": 3}], 3),
|
||||
([{"a": 1, "b": 2}, {"b": 2, "a": 1}], 1),
|
||||
([{}, {}], 1),
|
||||
([{"x": [1, 2]}, {"x": [1, 2]}], 1),
|
||||
([{"a": 1}, {"b": 2}, {"c": 3}], 3),
|
||||
]) # pyright: ignore[reportUnknownArgumentType]
|
||||
def test_parametrized_unique_dicts(self, dict_list: list[Any], expected_length: int):
|
||||
"""Test make_unique_list_of_dicts with various input combinations"""
|
||||
result = make_unique_list_of_dicts(dict_list)
|
||||
assert len(result) == expected_length
|
||||
assert isinstance(result, list)
|
||||
|
||||
def test_large_list(self):
|
||||
"""Test with a large list of dictionaries"""
|
||||
dict_list = [{"id": i % 100, "value": f"val_{i % 100}"} for i in range(1000)]
|
||||
result = make_unique_list_of_dicts(dict_list)
|
||||
# Should have 100 unique dicts (0-99)
|
||||
assert len(result) == 100
|
||||
|
||||
def test_preserves_last_occurrence(self):
|
||||
"""Test behavior with duplicate entries"""
|
||||
# The function uses dict comprehension, which keeps last occurrence
|
||||
dict_list = [
|
||||
{"a": 1, "b": 2},
|
||||
{"a": 3, "b": 4},
|
||||
{"a": 1, "b": 2}
|
||||
]
|
||||
result = make_unique_list_of_dicts(dict_list)
|
||||
assert len(result) == 2
|
||||
# Just verify correct unique count, order may vary
|
||||
|
||||
def test_nested_dicts(self):
|
||||
"""Test with nested dictionaries"""
|
||||
dict_list = [
|
||||
{"outer": {"inner": 1}},
|
||||
{"outer": {"inner": 1}},
|
||||
{"outer": {"inner": 2}}
|
||||
]
|
||||
result = make_unique_list_of_dicts(dict_list)
|
||||
assert len(result) == 2
|
||||
|
||||
def test_string_values_case_sensitive(self):
|
||||
"""Test that string values are case-sensitive"""
|
||||
dict_list = [
|
||||
{"name": "John"},
|
||||
{"name": "john"},
|
||||
{"name": "JOHN"},
|
||||
{"name": "John"}
|
||||
]
|
||||
result = make_unique_list_of_dicts(dict_list)
|
||||
assert len(result) == 3
|
||||
|
||||
def test_boolean_values(self):
|
||||
"""Test with boolean values"""
|
||||
dict_list = [
|
||||
{"flag": True, "count": 1},
|
||||
{"count": 1, "flag": True},
|
||||
{"flag": False, "count": 1}
|
||||
]
|
||||
result = make_unique_list_of_dicts(dict_list)
|
||||
assert len(result) == 2
|
||||
assert {"flag": True, "count": 1} in result
|
||||
assert {"flag": False, "count": 1} in result
|
||||
|
||||
# __END__
|
||||
@@ -1,121 +0,0 @@
|
||||
"""
|
||||
Unit tests for math_helpers module
|
||||
"""
|
||||
|
||||
from corelibs.math_handling.math_helpers import gcd, lcd
|
||||
|
||||
|
||||
class TestGcd:
|
||||
"""Test cases for the gcd (Greatest Common Divisor) function"""
|
||||
|
||||
def test_gcd_basic_positive_numbers(self):
|
||||
"""Test GCD with basic positive numbers"""
|
||||
assert gcd(12, 8) == 4
|
||||
assert gcd(15, 10) == 5
|
||||
assert gcd(21, 14) == 7
|
||||
|
||||
def test_gcd_coprime_numbers(self):
|
||||
"""Test GCD with coprime numbers (GCD should be 1)"""
|
||||
assert gcd(13, 7) == 1
|
||||
assert gcd(17, 19) == 1
|
||||
assert gcd(25, 49) == 1
|
||||
|
||||
def test_gcd_same_numbers(self):
|
||||
"""Test GCD with same numbers"""
|
||||
assert gcd(5, 5) == 5
|
||||
assert gcd(100, 100) == 100
|
||||
|
||||
def test_gcd_with_zero(self):
|
||||
"""Test GCD when one or both numbers are zero"""
|
||||
assert gcd(0, 5) == 5
|
||||
assert gcd(5, 0) == 5
|
||||
assert gcd(0, 0) == 0
|
||||
|
||||
def test_gcd_with_one(self):
|
||||
"""Test GCD when one number is 1"""
|
||||
assert gcd(1, 5) == 1
|
||||
assert gcd(100, 1) == 1
|
||||
|
||||
def test_gcd_large_numbers(self):
|
||||
"""Test GCD with large numbers"""
|
||||
assert gcd(1000000, 500000) == 500000
|
||||
assert gcd(123456, 789012) == 12
|
||||
|
||||
def test_gcd_reversed_order(self):
|
||||
"""Test GCD is commutative (order doesn't matter)"""
|
||||
assert gcd(12, 8) == gcd(8, 12)
|
||||
assert gcd(100, 35) == gcd(35, 100)
|
||||
|
||||
def test_gcd_negative_numbers(self):
|
||||
"""Test GCD with negative numbers"""
|
||||
assert gcd(-12, 8) == 4
|
||||
assert gcd(12, -8) == 4
|
||||
assert gcd(-12, -8) == 4
|
||||
|
||||
def test_gcd_multiples(self):
|
||||
"""Test GCD when one number is a multiple of the other"""
|
||||
assert gcd(10, 5) == 5
|
||||
assert gcd(100, 25) == 25
|
||||
assert gcd(7, 21) == 7
|
||||
|
||||
|
||||
class TestLcd:
|
||||
"""Test cases for the lcd (Least Common Denominator/Multiple) function"""
|
||||
|
||||
def test_lcd_basic_positive_numbers(self):
|
||||
"""Test LCD with basic positive numbers"""
|
||||
assert lcd(4, 6) == 12
|
||||
assert lcd(3, 5) == 15
|
||||
assert lcd(12, 8) == 24
|
||||
|
||||
def test_lcd_coprime_numbers(self):
|
||||
"""Test LCD with coprime numbers (should be their product)"""
|
||||
assert lcd(7, 13) == 91
|
||||
assert lcd(11, 13) == 143
|
||||
assert lcd(5, 7) == 35
|
||||
|
||||
def test_lcd_same_numbers(self):
|
||||
"""Test LCD with same numbers"""
|
||||
assert lcd(5, 5) == 5
|
||||
assert lcd(100, 100) == 100
|
||||
|
||||
def test_lcd_with_one(self):
|
||||
"""Test LCD when one number is 1"""
|
||||
assert lcd(1, 5) == 5
|
||||
assert lcd(100, 1) == 100
|
||||
|
||||
def test_lcd_with_zero(self):
|
||||
"""Test LCD when one or both numbers are zero"""
|
||||
assert lcd(0, 5) == 0
|
||||
assert lcd(5, 0) == 0
|
||||
assert lcd(0, 0) == 0
|
||||
|
||||
def test_lcd_large_numbers(self):
|
||||
"""Test LCD with large numbers"""
|
||||
assert lcd(100, 150) == 300
|
||||
assert lcd(1000, 500) == 1000
|
||||
|
||||
def test_lcd_reversed_order(self):
|
||||
"""Test LCD is commutative (order doesn't matter)"""
|
||||
assert lcd(4, 6) == lcd(6, 4)
|
||||
assert lcd(12, 18) == lcd(18, 12)
|
||||
|
||||
def test_lcd_negative_numbers(self):
|
||||
"""Test LCD with negative numbers"""
|
||||
assert lcd(-4, 6) == 12
|
||||
assert lcd(4, -6) == 12
|
||||
assert lcd(-4, -6) == 12
|
||||
|
||||
def test_lcd_multiples(self):
|
||||
"""Test LCD when one number is a multiple of the other"""
|
||||
assert lcd(5, 10) == 10
|
||||
assert lcd(3, 9) == 9
|
||||
assert lcd(25, 100) == 100
|
||||
|
||||
def test_lcd_gcd_relationship(self):
|
||||
"""Test the mathematical relationship between LCD and GCD: lcd(a,b) * gcd(a,b) = a * b"""
|
||||
test_cases = [(12, 8), (15, 10), (21, 14), (100, 35)]
|
||||
for a, b in test_cases:
|
||||
assert lcd(a, b) * gcd(a, b) == a * b
|
||||
|
||||
# __END__
|
||||
Reference in New Issue
Block a user