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