257 lines
7.3 KiB
Python
257 lines
7.3 KiB
Python
import pytest
|
|
|
|
from core.utils import SelectConf, select
|
|
from core.Expando import Expando
|
|
|
|
|
|
def test_i_can_select_basic_attributes():
|
|
"""Test basic selection of existing attributes."""
|
|
items = [
|
|
Expando({"name": "alice", "age": 25, "city": "paris"}),
|
|
Expando({"name": "bob", "age": 30, "city": "london"})
|
|
]
|
|
settings = [
|
|
SelectConf(attr="name"),
|
|
SelectConf(attr="age")
|
|
]
|
|
|
|
result = select(items, settings)
|
|
|
|
assert len(result) == 2
|
|
assert result[0].name == "alice"
|
|
assert result[0].age == 25
|
|
assert not hasattr(result[0], "city") # city not selected
|
|
assert result[1].name == "bob"
|
|
assert result[1].age == 30
|
|
|
|
|
|
def test_i_can_select_with_renamed_attributes():
|
|
"""Test renaming attributes using SelectConf.to."""
|
|
items = [
|
|
Expando({"name": "alice", "age": 25}),
|
|
Expando({"name": "bob", "age": 30})
|
|
]
|
|
settings = [
|
|
SelectConf(attr="name", to="full_name"),
|
|
SelectConf(attr="age", to="years")
|
|
]
|
|
|
|
result = select(items, settings)
|
|
|
|
assert len(result) == 2
|
|
assert result[0].full_name == "alice"
|
|
assert result[0].years == 25
|
|
assert not hasattr(result[0], "name") # original name not present
|
|
assert not hasattr(result[0], "age") # original name not present
|
|
assert result[1].full_name == "bob"
|
|
assert result[1].years == 30
|
|
|
|
|
|
def test_i_can_select_with_formatter():
|
|
"""Test applying formatters to selected attributes."""
|
|
items = [
|
|
Expando({"name": "alice", "age": 25}),
|
|
Expando({"name": "bob", "age": 30})
|
|
]
|
|
settings = [
|
|
SelectConf(attr="name", formatter=lambda item: item.name.upper()),
|
|
SelectConf(attr="age", formatter=lambda item: item.age * 2)
|
|
]
|
|
|
|
result = select(items, settings)
|
|
|
|
assert len(result) == 2
|
|
assert result[0].name == "ALICE"
|
|
assert result[0].age == 50
|
|
assert result[1].name == "BOB"
|
|
assert result[1].age == 60
|
|
|
|
|
|
def test_i_can_select_with_formatter_and_rename():
|
|
"""Test combining formatter and renaming."""
|
|
items = [
|
|
Expando({"name": "alice", "age": 25}),
|
|
Expando({"name": "bob", "age": 30})
|
|
]
|
|
settings = [
|
|
SelectConf(attr="name", to="upper_name", formatter=lambda item: item.name.upper()),
|
|
SelectConf(attr="age", to="double_age", formatter=lambda item: item.age * 2)
|
|
]
|
|
|
|
result = select(items, settings)
|
|
|
|
assert len(result) == 2
|
|
assert result[0].upper_name == "ALICE"
|
|
assert result[0].double_age == 50
|
|
assert not hasattr(result[0], "name")
|
|
assert not hasattr(result[0], "age")
|
|
assert result[1].upper_name == "BOB"
|
|
assert result[1].double_age == 60
|
|
|
|
|
|
def test_i_can_handle_empty_items_list():
|
|
"""Test that empty items list returns empty list."""
|
|
items = []
|
|
settings = [SelectConf(attr="name")]
|
|
|
|
result = select(items, settings)
|
|
|
|
assert result == []
|
|
|
|
|
|
def test_i_can_handle_empty_settings_list():
|
|
"""Test that empty settings list returns items with empty properties."""
|
|
items = [
|
|
Expando({"name": "alice", "age": 25}),
|
|
Expando({"name": "bob", "age": 30})
|
|
]
|
|
settings = []
|
|
|
|
result = select(items, settings)
|
|
|
|
assert len(result) == 2
|
|
# Each result should be an Expando with empty properties
|
|
assert result[0]._props == {}
|
|
assert result[1]._props == {}
|
|
|
|
|
|
def test_i_can_handle_missing_attributes():
|
|
"""Test that missing attributes return None (normal behavior)."""
|
|
items = [
|
|
Expando({"name": "alice"}), # no age attribute
|
|
Expando({"name": "bob", "age": 30})
|
|
]
|
|
settings = [
|
|
SelectConf(attr="name"),
|
|
SelectConf(attr="age") # missing in first item
|
|
]
|
|
|
|
result = select(items, settings)
|
|
|
|
assert len(result) == 2
|
|
assert result[0].name == "alice"
|
|
assert result[0].age is None # missing attribute returns None
|
|
assert result[1].name == "bob"
|
|
assert result[1].age == 30
|
|
|
|
|
|
def test_i_can_handle_whitespace_in_attr_names():
|
|
"""Test that whitespace in attribute names is stripped."""
|
|
items = [
|
|
Expando({"name": "alice", "age": 25})
|
|
]
|
|
settings = [
|
|
SelectConf(attr=" name "), # whitespace around attr
|
|
SelectConf(attr="\tage\n") # tabs and newlines
|
|
]
|
|
|
|
result = select(items, settings)
|
|
|
|
assert len(result) == 1
|
|
assert result[0].name == "alice"
|
|
assert result[0].age == 25
|
|
|
|
|
|
def test_i_can_select_multiple_attributes():
|
|
"""Test selecting multiple attributes from multiple items."""
|
|
items = [
|
|
Expando({"name": "alice", "age": 25, "city": "paris", "country": "france"}),
|
|
Expando({"name": "bob", "age": 30, "city": "london", "country": "uk"}),
|
|
Expando({"name": "charlie", "age": 35, "city": "madrid", "country": "spain"})
|
|
]
|
|
settings = [
|
|
SelectConf(attr="name"),
|
|
SelectConf(attr="city", to="location"),
|
|
SelectConf(attr="country", formatter=lambda item: item.country.upper())
|
|
]
|
|
|
|
result = select(items, settings)
|
|
|
|
assert len(result) == 3
|
|
assert result[0].name == "alice"
|
|
assert result[0].location == "paris"
|
|
assert result[0].country == "FRANCE"
|
|
assert result[1].name == "bob"
|
|
assert result[1].location == "london"
|
|
assert result[1].country == "UK"
|
|
assert result[2].name == "charlie"
|
|
assert result[2].location == "madrid"
|
|
assert result[2].country == "SPAIN"
|
|
|
|
|
|
def test_i_can_handle_formatter_with_whole_item():
|
|
"""Test that formatter receives the whole item, not just the attribute value."""
|
|
items = [
|
|
Expando({"first_name": "alice", "last_name": "smith"}),
|
|
Expando({"first_name": "bob", "last_name": "jones"})
|
|
]
|
|
settings = [
|
|
SelectConf(
|
|
attr="first_name",
|
|
to="full_name",
|
|
formatter=lambda item: f"{item.get('first_name')} {item.get('last_name')}"
|
|
)
|
|
]
|
|
|
|
result = select(items, settings)
|
|
|
|
assert len(result) == 2
|
|
assert result[0].full_name == "alice smith"
|
|
assert result[1].full_name == "bob jones"
|
|
|
|
|
|
def test_i_cannot_select_when_formatter_raises_exception():
|
|
"""Test that formatter exceptions are propagated."""
|
|
items = [
|
|
Expando({"name": "alice", "value": None})
|
|
]
|
|
settings = [
|
|
SelectConf(
|
|
attr="value",
|
|
formatter=lambda item: item.get("value").upper() # will fail on None
|
|
)
|
|
]
|
|
|
|
with pytest.raises(AttributeError):
|
|
select(items, settings)
|
|
|
|
|
|
def test_i_can_handle_none_values_in_formatter():
|
|
"""Test formatter handling None values gracefully when designed to do so."""
|
|
items = [
|
|
Expando({"name": "alice", "value": None}),
|
|
Expando({"name": "bob", "value": "test"})
|
|
]
|
|
settings = [
|
|
SelectConf(
|
|
attr="value",
|
|
formatter=lambda item: item.get("value").upper() if item.get("value") else "NO_VALUE"
|
|
)
|
|
]
|
|
|
|
result = select(items, settings)
|
|
|
|
assert len(result) == 2
|
|
assert result[0].value == "NO_VALUE"
|
|
assert result[1].value == "TEST"
|
|
|
|
|
|
def test_i_can_select_nested_attributes():
|
|
"""Test selecting nested attributes using Expando.get() path notation."""
|
|
items = [
|
|
Expando({"user": {"profile": {"name": "alice"}}, "age": 25}),
|
|
Expando({"user": {"profile": {"name": "bob"}}, "age": 30})
|
|
]
|
|
settings = [
|
|
SelectConf(attr="user.profile.name", to="name"),
|
|
SelectConf(attr="age")
|
|
]
|
|
|
|
result = select(items, settings)
|
|
|
|
assert len(result) == 2
|
|
assert result[0].name == "alice"
|
|
assert result[0].age == 25
|
|
assert result[1].name == "bob"
|
|
assert result[1].age == 30
|