Skip to content

Commit a39e4e6

Browse files
committed
test(query_list): improve test coverage
why: Several areas of the code needed better test coverage what: - Added tests for keygetter with nested objects and error cases - Added tests for QueryList slicing operations - Added tests for QueryList list behavior and pk_key attribute - Added tests for LOOKUP_NAME_MAP completeness - Added tests for lookup_startswith and lookup_endswith functions - Added tests for SkipDefaultFieldsReprMixin
1 parent 414b2a6 commit a39e4e6

File tree

1 file changed

+199
-0
lines changed

1 file changed

+199
-0
lines changed

tests/_internal/test_query_list.py

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,24 @@
88
import pytest
99

1010
from libtmux._internal.query_list import (
11+
LOOKUP_NAME_MAP,
1112
MultipleObjectsReturned,
1213
ObjectDoesNotExist,
1314
PKRequiredException,
1415
QueryList,
1516
keygetter,
1617
lookup_contains,
18+
lookup_endswith,
1719
lookup_exact,
1820
lookup_icontains,
21+
lookup_iendswith,
1922
lookup_iexact,
2023
lookup_in,
2124
lookup_iregex,
25+
lookup_istartswith,
2226
lookup_nin,
2327
lookup_regex,
28+
lookup_startswith,
2429
parse_lookup,
2530
)
2631

@@ -619,3 +624,197 @@ def test_filter_error_handling() -> None:
619624
empty_args: dict[str, t.Any] = {"": "test"}
620625
result = ql.filter(**empty_args)
621626
assert len(result) == 0
627+
628+
629+
def test_lookup_startswith_endswith_functions() -> None:
630+
"""Test startswith and endswith lookup functions with various types."""
631+
# Test lookup_startswith
632+
assert lookup_startswith("test123", "test") # Basic match
633+
assert not lookup_startswith("test123", "123") # No match at start
634+
assert not lookup_startswith(["test"], "test") # Invalid type for data
635+
assert not lookup_startswith("test", ["test"]) # Invalid type for rhs
636+
assert not lookup_startswith("test", 123) # type: ignore # Invalid type for rhs
637+
638+
# Test lookup_istartswith
639+
assert lookup_istartswith("TEST123", "test") # Case-insensitive match
640+
assert lookup_istartswith("test123", "TEST") # Case-insensitive match reverse
641+
assert not lookup_istartswith("test123", "123") # No match at start
642+
assert not lookup_istartswith(["test"], "test") # Invalid type for data
643+
assert not lookup_istartswith("test", ["test"]) # Invalid type for rhs
644+
assert not lookup_istartswith("test", 123) # type: ignore # Invalid type for rhs
645+
646+
# Test lookup_endswith
647+
assert lookup_endswith("test123", "123") # Basic match
648+
assert not lookup_endswith("test123", "test") # No match at end
649+
assert not lookup_endswith(["test"], "test") # Invalid type for data
650+
assert not lookup_endswith("test", ["test"]) # Invalid type for rhs
651+
assert not lookup_endswith("test", 123) # type: ignore # Invalid type for rhs
652+
653+
# Test lookup_iendswith
654+
assert lookup_iendswith("test123", "123") # Basic match
655+
assert lookup_iendswith("test123", "123") # Case-insensitive match
656+
assert lookup_iendswith("test123", "123") # Case-insensitive match reverse
657+
assert not lookup_iendswith("test123", "test") # No match at end
658+
assert not lookup_iendswith(["test"], "test") # Invalid type for data
659+
assert not lookup_iendswith("test", ["test"]) # Invalid type for rhs
660+
assert not lookup_iendswith("test", 123) # type: ignore # Invalid type for rhs
661+
662+
663+
def test_query_list_eq_numeric_comparison() -> None:
664+
"""Test QueryList __eq__ method with numeric comparisons."""
665+
# Test exact numeric matches
666+
ql1 = QueryList([{"a": 1, "b": 2.0}])
667+
ql2 = QueryList([{"a": 1, "b": 2.0}])
668+
assert ql1 == ql2
669+
670+
# Test numeric comparison within tolerance (difference < 1)
671+
ql3 = QueryList([{"a": 1.1, "b": 2.1}])
672+
assert ql1 == ql3 # Should be equal since difference is less than 1
673+
674+
# Test numeric comparison outside tolerance (difference > 1)
675+
ql4 = QueryList([{"a": 2.5, "b": 3.5}])
676+
assert ql1 != ql4 # Should not be equal since difference is more than 1
677+
678+
# Test mixed numeric types
679+
ql5 = QueryList([{"a": 1, "b": 2}]) # int instead of float
680+
assert ql1 == ql5 # Should be equal since values are equivalent
681+
682+
# Test with nested numeric values
683+
ql6 = QueryList([{"a": {"x": 1.0, "y": 2.0}}])
684+
ql7 = QueryList([{"a": {"x": 1.1, "y": 2.1}}])
685+
assert ql6 == ql7 # Should be equal since differences are less than 1
686+
687+
# Test with mixed content
688+
ql10 = QueryList([{"a": 1, "b": "test"}])
689+
ql11 = QueryList([{"a": 1.1, "b": "test"}])
690+
assert ql10 == ql11 # Should be equal since numeric difference is less than 1
691+
692+
# Test with non-dict content (exact equality required)
693+
ql8 = QueryList([1, 2, 3])
694+
ql9 = QueryList([1, 2, 3])
695+
assert ql8 == ql9 # Should be equal since values are exactly the same
696+
assert ql8 != QueryList(
697+
[1.1, 2.1, 3.1]
698+
) # Should not be equal since values are different
699+
700+
701+
def test_keygetter_nested_objects() -> None:
702+
"""Test keygetter function with nested objects."""
703+
704+
@dataclasses.dataclass
705+
class Food:
706+
fruit: list[str] = dataclasses.field(default_factory=list)
707+
breakfast: str | None = None
708+
709+
@dataclasses.dataclass
710+
class Restaurant:
711+
place: str
712+
city: str
713+
state: str
714+
food: Food = dataclasses.field(default_factory=Food)
715+
716+
# Test with nested dataclass
717+
restaurant = Restaurant(
718+
place="Largo",
719+
city="Tampa",
720+
state="Florida",
721+
food=Food(fruit=["banana", "orange"], breakfast="cereal"),
722+
)
723+
assert keygetter(restaurant, "food") == Food(
724+
fruit=["banana", "orange"], breakfast="cereal"
725+
)
726+
assert keygetter(restaurant, "food__breakfast") == "cereal"
727+
assert keygetter(restaurant, "food__fruit") == ["banana", "orange"]
728+
729+
# Test with non-existent attribute (returns None due to exception handling)
730+
with suppress(Exception):
731+
assert keygetter(restaurant, "nonexistent") is None
732+
733+
# Test with invalid path format (returns the object itself)
734+
assert keygetter(restaurant, "") == restaurant
735+
assert keygetter(restaurant, "__") == restaurant
736+
737+
# Test with non-mapping object (returns the object itself)
738+
non_mapping = "not a mapping"
739+
assert keygetter(non_mapping, "any_key") == non_mapping # type: ignore
740+
741+
742+
def test_query_list_slicing() -> None:
743+
"""Test QueryList slicing operations."""
744+
ql = QueryList([1, 2, 3, 4, 5])
745+
746+
# Test positive indices
747+
assert ql[1:3] == QueryList([2, 3])
748+
assert ql[0:5:2] == QueryList([1, 3, 5])
749+
750+
# Test negative indices
751+
assert ql[-3:] == QueryList([3, 4, 5])
752+
assert ql[:-2] == QueryList([1, 2, 3])
753+
assert ql[-4:-2] == QueryList([2, 3])
754+
755+
# Test steps
756+
assert ql[::2] == QueryList([1, 3, 5])
757+
assert ql[::-1] == QueryList([5, 4, 3, 2, 1])
758+
assert ql[4:0:-2] == QueryList([5, 3])
759+
760+
# Test empty slices
761+
assert ql[5:] == QueryList([])
762+
assert ql[-1:-5] == QueryList([])
763+
764+
765+
def test_query_list_attributes() -> None:
766+
"""Test QueryList list behavior and pk_key attribute."""
767+
# Test list behavior
768+
ql = QueryList([1, 2, 3])
769+
assert list(ql) == [1, 2, 3]
770+
assert len(ql) == 3
771+
assert ql[0] == 1
772+
assert ql[-1] == 3
773+
774+
# Test pk_key attribute with objects
775+
@dataclasses.dataclass
776+
class Item:
777+
id: str
778+
value: int
779+
780+
items = [Item("1", 1), Item("2", 2)]
781+
ql = QueryList(items)
782+
ql.pk_key = "id"
783+
assert ql.items() == [("1", items[0]), ("2", items[1])]
784+
785+
# Test pk_key with non-existent attribute
786+
ql.pk_key = "nonexistent"
787+
with pytest.raises(AttributeError):
788+
ql.items()
789+
790+
# Test pk_key with None
791+
ql.pk_key = None
792+
with pytest.raises(PKRequiredException):
793+
ql.items()
794+
795+
796+
def test_lookup_name_map() -> None:
797+
"""Test LOOKUP_NAME_MAP contains all lookup functions."""
798+
# Test all lookup functions are in the map
799+
assert LOOKUP_NAME_MAP["eq"] == lookup_exact
800+
assert LOOKUP_NAME_MAP["exact"] == lookup_exact
801+
assert LOOKUP_NAME_MAP["iexact"] == lookup_iexact
802+
assert LOOKUP_NAME_MAP["contains"] == lookup_contains
803+
assert LOOKUP_NAME_MAP["icontains"] == lookup_icontains
804+
assert LOOKUP_NAME_MAP["startswith"] == lookup_startswith
805+
assert LOOKUP_NAME_MAP["istartswith"] == lookup_istartswith
806+
assert LOOKUP_NAME_MAP["endswith"] == lookup_endswith
807+
assert LOOKUP_NAME_MAP["iendswith"] == lookup_iendswith
808+
assert LOOKUP_NAME_MAP["in"] == lookup_in
809+
assert LOOKUP_NAME_MAP["nin"] == lookup_nin
810+
assert LOOKUP_NAME_MAP["regex"] == lookup_regex
811+
assert LOOKUP_NAME_MAP["iregex"] == lookup_iregex
812+
813+
# Test lookup functions behavior through the map
814+
data = "test123"
815+
assert LOOKUP_NAME_MAP["contains"](data, "test")
816+
assert LOOKUP_NAME_MAP["icontains"](data, "TEST")
817+
assert LOOKUP_NAME_MAP["startswith"](data, "test")
818+
assert LOOKUP_NAME_MAP["endswith"](data, "123")
819+
assert not LOOKUP_NAME_MAP["in"](data, ["other", "values"])
820+
assert LOOKUP_NAME_MAP["regex"](data, r"\d+")

0 commit comments

Comments
 (0)