8
8
import pytest
9
9
10
10
from libtmux ._internal .query_list import (
11
+ LOOKUP_NAME_MAP ,
11
12
MultipleObjectsReturned ,
12
13
ObjectDoesNotExist ,
13
14
PKRequiredException ,
14
15
QueryList ,
15
16
keygetter ,
16
17
lookup_contains ,
18
+ lookup_endswith ,
17
19
lookup_exact ,
18
20
lookup_icontains ,
21
+ lookup_iendswith ,
19
22
lookup_iexact ,
20
23
lookup_in ,
21
24
lookup_iregex ,
25
+ lookup_istartswith ,
22
26
lookup_nin ,
23
27
lookup_regex ,
28
+ lookup_startswith ,
24
29
parse_lookup ,
25
30
)
26
31
@@ -619,3 +624,197 @@ def test_filter_error_handling() -> None:
619
624
empty_args : dict [str , t .Any ] = {"" : "test" }
620
625
result = ql .filter (** empty_args )
621
626
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