Skip to content

Commit 962184a

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 6a3f289 commit 962184a

File tree

2 files changed

+195
-190
lines changed

2 files changed

+195
-190
lines changed

tests/_internal/test_query_list.py

Lines changed: 195 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import dataclasses
44
import typing as t
5-
from collections.abc import Callable, Mapping
5+
from collections.abc import Callable, Mapping, Sequence
66
from contextlib import suppress
77

88
import pytest
@@ -22,6 +22,11 @@
2222
lookup_nin,
2323
lookup_regex,
2424
parse_lookup,
25+
lookup_startswith,
26+
lookup_istartswith,
27+
lookup_endswith,
28+
lookup_iendswith,
29+
LOOKUP_NAME_MAP,
2530
)
2631

2732
if t.TYPE_CHECKING:
@@ -619,3 +624,192 @@ 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([1.1, 2.1, 3.1]) # Should not be equal since values are different
697+
698+
699+
def test_keygetter_nested_objects() -> None:
700+
"""Test keygetter function with nested objects."""
701+
@dataclasses.dataclass
702+
class Food:
703+
fruit: list[str] = dataclasses.field(default_factory=list)
704+
breakfast: str | None = None
705+
706+
@dataclasses.dataclass
707+
class Restaurant:
708+
place: str
709+
city: str
710+
state: str
711+
food: Food = dataclasses.field(default_factory=Food)
712+
713+
# Test with nested dataclass
714+
restaurant = Restaurant(
715+
place="Largo",
716+
city="Tampa",
717+
state="Florida",
718+
food=Food(fruit=["banana", "orange"], breakfast="cereal"),
719+
)
720+
assert keygetter(restaurant, "food") == Food(fruit=["banana", "orange"], breakfast="cereal")
721+
assert keygetter(restaurant, "food__breakfast") == "cereal"
722+
assert keygetter(restaurant, "food__fruit") == ["banana", "orange"]
723+
724+
# Test with non-existent attribute (returns None due to exception handling)
725+
with suppress(Exception):
726+
assert keygetter(restaurant, "nonexistent") is None
727+
728+
# Test with invalid path format (returns the object itself)
729+
assert keygetter(restaurant, "") == restaurant
730+
assert keygetter(restaurant, "__") == restaurant
731+
732+
# Test with non-mapping object (returns the object itself)
733+
non_mapping = "not a mapping"
734+
assert keygetter(non_mapping, "any_key") == non_mapping # type: ignore
735+
736+
737+
def test_query_list_slicing() -> None:
738+
"""Test QueryList slicing operations."""
739+
ql = QueryList([1, 2, 3, 4, 5])
740+
741+
# Test positive indices
742+
assert ql[1:3] == QueryList([2, 3])
743+
assert ql[0:5:2] == QueryList([1, 3, 5])
744+
745+
# Test negative indices
746+
assert ql[-3:] == QueryList([3, 4, 5])
747+
assert ql[:-2] == QueryList([1, 2, 3])
748+
assert ql[-4:-2] == QueryList([2, 3])
749+
750+
# Test steps
751+
assert ql[::2] == QueryList([1, 3, 5])
752+
assert ql[::-1] == QueryList([5, 4, 3, 2, 1])
753+
assert ql[4:0:-2] == QueryList([5, 3])
754+
755+
# Test empty slices
756+
assert ql[5:] == QueryList([])
757+
assert ql[-1:-5] == QueryList([])
758+
759+
760+
def test_query_list_attributes() -> None:
761+
"""Test QueryList list behavior and pk_key attribute."""
762+
# Test list behavior
763+
ql = QueryList([1, 2, 3])
764+
assert list(ql) == [1, 2, 3]
765+
assert len(ql) == 3
766+
assert ql[0] == 1
767+
assert ql[-1] == 3
768+
769+
# Test pk_key attribute with objects
770+
@dataclasses.dataclass
771+
class Item:
772+
id: str
773+
value: int
774+
775+
items = [Item("1", 1), Item("2", 2)]
776+
ql = QueryList(items)
777+
ql.pk_key = "id"
778+
assert ql.items() == [("1", items[0]), ("2", items[1])]
779+
780+
# Test pk_key with non-existent attribute
781+
ql.pk_key = "nonexistent"
782+
with pytest.raises(AttributeError):
783+
ql.items()
784+
785+
# Test pk_key with None
786+
ql.pk_key = None
787+
with pytest.raises(PKRequiredException):
788+
ql.items()
789+
790+
791+
def test_lookup_name_map() -> None:
792+
"""Test LOOKUP_NAME_MAP contains all lookup functions."""
793+
# Test all lookup functions are in the map
794+
assert LOOKUP_NAME_MAP["eq"] == lookup_exact
795+
assert LOOKUP_NAME_MAP["exact"] == lookup_exact
796+
assert LOOKUP_NAME_MAP["iexact"] == lookup_iexact
797+
assert LOOKUP_NAME_MAP["contains"] == lookup_contains
798+
assert LOOKUP_NAME_MAP["icontains"] == lookup_icontains
799+
assert LOOKUP_NAME_MAP["startswith"] == lookup_startswith
800+
assert LOOKUP_NAME_MAP["istartswith"] == lookup_istartswith
801+
assert LOOKUP_NAME_MAP["endswith"] == lookup_endswith
802+
assert LOOKUP_NAME_MAP["iendswith"] == lookup_iendswith
803+
assert LOOKUP_NAME_MAP["in"] == lookup_in
804+
assert LOOKUP_NAME_MAP["nin"] == lookup_nin
805+
assert LOOKUP_NAME_MAP["regex"] == lookup_regex
806+
assert LOOKUP_NAME_MAP["iregex"] == lookup_iregex
807+
808+
# Test lookup functions behavior through the map
809+
data = "test123"
810+
assert LOOKUP_NAME_MAP["contains"](data, "test")
811+
assert LOOKUP_NAME_MAP["icontains"](data, "TEST")
812+
assert LOOKUP_NAME_MAP["startswith"](data, "test")
813+
assert LOOKUP_NAME_MAP["endswith"](data, "123")
814+
assert not LOOKUP_NAME_MAP["in"](data, ["other", "values"])
815+
assert LOOKUP_NAME_MAP["regex"](data, r"\d+")

0 commit comments

Comments
 (0)