Skip to content

More coverage #576

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 11 commits into
base: master
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions tests/_internal/test_dataclasses.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"""Test dataclasses utilities."""

from __future__ import annotations

import dataclasses

from libtmux._internal.dataclasses import SkipDefaultFieldsReprMixin


@dataclasses.dataclass(repr=False)
class TestItem(SkipDefaultFieldsReprMixin):
"""Test class for SkipDefaultFieldsReprMixin."""

name: str
unit_price: float = 1.00
quantity_on_hand: int = 0


def test_skip_default_fields_repr() -> None:
"""Test SkipDefaultFieldsReprMixin repr behavior."""
# Test with only required field
item1 = TestItem("Test")
assert repr(item1) == "TestItem(name=Test)"

# Test with one default field modified
item2 = TestItem("Test", unit_price=2.00)
assert repr(item2) == "TestItem(name=Test, unit_price=2.0)"

# Test with all fields modified
item3 = TestItem("Test", unit_price=2.00, quantity_on_hand=5)
assert repr(item3) == "TestItem(name=Test, unit_price=2.0, quantity_on_hand=5)"

# Test modifying field after creation
item4 = TestItem("Test")
item4.unit_price = 2.05
assert repr(item4) == "TestItem(name=Test, unit_price=2.05)"

# Test with multiple fields modified after creation
item5 = TestItem("Test")
item5.unit_price = 2.05
item5.quantity_on_hand = 3
assert repr(item5) == "TestItem(name=Test, unit_price=2.05, quantity_on_hand=3)"
744 changes: 744 additions & 0 deletions tests/_internal/test_query_list.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,33 @@
from __future__ import annotations

import dataclasses
import re
import typing as t
from collections.abc import Callable, Mapping
from contextlib import suppress

import pytest

from libtmux._internal.query_list import (
LOOKUP_NAME_MAP,
MultipleObjectsReturned,
ObjectDoesNotExist,
PKRequiredException,
QueryList,
keygetter,
lookup_contains,
lookup_endswith,
lookup_exact,
lookup_icontains,
lookup_iendswith,
lookup_iexact,
lookup_in,
lookup_iregex,
lookup_istartswith,
lookup_nin,
lookup_regex,
lookup_startswith,
parse_lookup,
)

if t.TYPE_CHECKING:
@@ -291,3 +310,728 @@ def test_filter(
else:
assert qs.get(filter_expr) == expected_result
assert exc.match("No objects found")


def test_keygetter_error_handling() -> None:
"""Test error handling in keygetter function."""
# Test accessing non-existent key
obj: dict[str, int] = {"a": 1}
assert keygetter(obj, "b") is None

# Test accessing nested non-existent key
nested_obj: dict[str, dict[str, int]] = {"a": {"b": 1}}
assert keygetter(nested_obj, "a__c") is None

# Test with invalid object type
obj_none: t.Any = None
with suppress(Exception): # Exception is expected and logged
assert keygetter(obj_none, "any_key") is None


def test_parse_lookup_error_handling() -> None:
"""Test error handling in parse_lookup function."""
# Test with invalid object
assert parse_lookup({"field": "value"}, "nonexistent__invalid", "__invalid") is None

# Test with invalid lookup
obj: dict[str, str] = {"field": "value"}
# Type ignore since we're testing error handling with invalid types
assert parse_lookup(obj, "field", None) is None # type: ignore

# Test with non-string path
assert parse_lookup(obj, None, "__contains") is None # type: ignore


def test_lookup_functions_edge_cases() -> None:
"""Test edge cases for lookup functions."""
# Test lookup_exact with non-string types
assert lookup_exact("1", "1")
assert not lookup_exact(["a", "b"], "test")
assert not lookup_exact({"a": "1"}, "test")

# Test lookup_iexact with non-string types
assert not lookup_iexact(["a", "b"], "test")
assert not lookup_iexact({"a": "1"}, "test")

# Test lookup_contains with various types
assert lookup_contains(["a", "b"], "a")
assert lookup_contains("123", "1") # String contains substring
assert lookup_contains({"a": "1", "b": "2"}, "a")

# Test lookup_icontains with various types
assert lookup_icontains("TEST", "test")
assert lookup_icontains("test", "TEST")
# Keys are case-insensitive
assert lookup_icontains({"A": "1", "b": "2"}, "a")


def test_query_list_get_error_cases() -> None:
"""Test error cases for QueryList.get method."""
ql = QueryList([{"id": 1}, {"id": 2}, {"id": 2}])

# Test get with no results
with pytest.raises(ObjectDoesNotExist):
ql.get(id=3)

# Test get with multiple results
with pytest.raises(MultipleObjectsReturned):
ql.get(id=2)

# Test get with default
assert ql.get(id=3, default=None) is None


def test_query_list_filter_error_cases() -> None:
"""Test error cases for QueryList.filter method."""
ql = QueryList([{"id": 1}, {"id": 2}])

# Test filter with invalid field
assert len(ql.filter(nonexistent=1)) == 0

# Test filter with invalid lookup
assert len(ql.filter(id__invalid="test")) == 0


def test_query_list_methods() -> None:
"""Test additional QueryList methods."""
ql = QueryList([1, 2, 3])

# Test len
assert len(ql) == 3

# Test iter
assert list(iter(ql)) == [1, 2, 3]

# Test getitem
assert ql[0] == 1
assert ql[1:] == QueryList([2, 3])

# Test eq
assert ql == QueryList([1, 2, 3])
assert ql != QueryList([1, 2])
assert ql == [1, 2, 3] # QueryList should equal regular list with same contents

# Test bool
assert bool(ql) is True
assert bool(QueryList([])) is False


def test_lookup_functions_additional_edge_cases() -> None:
"""Test additional edge cases for lookup functions."""
# Test lookup_in with various types
assert not lookup_in("value", {"key": "value"}) # String in dict values
assert not lookup_in("key", {"key": "value"}) # String in dict keys
assert lookup_in("item", ["item", "other"]) # String in list
assert not lookup_in("missing", {"key": "value"}) # Missing key in dict
assert not lookup_in(123, "123") # type: ignore # Invalid type combination

# Test lookup_nin with various types
# Missing key in dict returns False
assert not lookup_nin("missing", {"key": "value"})
# String in dict values returns False
assert not lookup_nin("value", {"key": "value"})
assert lookup_nin("item", ["other", "another"]) # String not in list
assert not lookup_nin("item", ["item", "other"]) # String in list
assert not lookup_nin(123, "123") # type: ignore # Invalid type combination returns False

# Test lookup_regex with various types
assert lookup_regex("test123", r"\d+") # Match digits
assert not lookup_regex("test", r"\d+") # No match
assert not lookup_regex(123, r"\d+") # type: ignore # Invalid type
assert not lookup_regex("test", 123) # type: ignore # Invalid pattern type

# Test lookup_iregex with various types
assert lookup_iregex("TEST123", r"test\d+") # Case-insensitive match
assert not lookup_iregex("test", r"\d+") # No match
assert not lookup_iregex(123, r"\d+") # type: ignore # Invalid type
assert not lookup_iregex("test", 123) # type: ignore # Invalid pattern type


def test_query_list_items() -> None:
"""Test QueryList items() method."""
# Test items() without pk_key
ql = QueryList([{"id": 1}, {"id": 2}])
ql.pk_key = None # Initialize pk_key
with pytest.raises(PKRequiredException):
ql.items()


def test_query_list_filter_with_invalid_op() -> None:
"""Test QueryList filter with invalid operator."""
ql = QueryList([{"id": 1}, {"id": 2}])

# Test filter with no operator (defaults to exact)
result = ql.filter(id=1)
assert len(result) == 1
assert result[0]["id"] == 1

# Test filter with valid operator
result = ql.filter(id__exact=1)
assert len(result) == 1
assert result[0]["id"] == 1

# Test filter with multiple conditions
result = ql.filter(id__exact=1, id__in=[1, 2])
assert len(result) == 1
assert result[0]["id"] == 1


def test_query_list_filter_with_callable() -> None:
"""Test QueryList filter with callable."""
ql = QueryList([{"id": 1}, {"id": 2}, {"id": 3}])

# Test filter with callable
def is_even(x: dict[str, int]) -> bool:
return x["id"] % 2 == 0

filtered = ql.filter(is_even)
assert len(filtered) == 1
assert filtered[0]["id"] == 2

# Test filter with lambda
filtered = ql.filter(lambda x: x["id"] > 2)
assert len(filtered) == 1
assert filtered[0]["id"] == 3


def test_query_list_get_with_callable() -> None:
"""Test QueryList get with callable."""
ql = QueryList([{"id": 1}, {"id": 2}, {"id": 3}])

# Test get with callable
def get_id_2(x: dict[str, int]) -> bool:
return x["id"] == 2

result = ql.get(get_id_2)
assert result is not None and result["id"] == 2 # Check for None before indexing

# Test get with lambda
result = ql.get(lambda x: x["id"] == 3)
assert result is not None and result["id"] == 3 # Check for None before indexing

# Test get with callable returning multiple matches
def get_id_greater_than_1(x: dict[str, int]) -> bool:
return x["id"] > 1

with pytest.raises(MultipleObjectsReturned):
ql.get(get_id_greater_than_1)

# Test get with callable returning no matches
def get_id_greater_than_10(x: dict[str, int]) -> bool:
return x["id"] > 10

with pytest.raises(ObjectDoesNotExist):
ql.get(get_id_greater_than_10)


def test_query_list_eq_with_mappings() -> None:
"""Test QueryList __eq__ method with mappings."""
# Test comparing mappings with numeric values
ql1 = QueryList([{"a": 1, "b": 2}])
ql2 = QueryList([{"a": 1, "b": 2}])
assert ql1 == ql2

# Test comparing mappings with different values
ql3 = QueryList([{"a": 1, "b": 3}])
assert ql1 != ql3

# Test comparing with non-list
assert ql1 != "not a list"

# Test comparing mappings with different keys
ql4 = QueryList([{"a": 1, "c": 2}])
assert ql1 != ql4

# Test comparing mappings with close numeric values (within tolerance)
ql5 = QueryList([{"a": 1.0001, "b": 2.0001}])
assert ql1 == ql5 # Should be equal since difference is less than 1

# Test comparing mappings with different numeric values (outside tolerance)
ql6 = QueryList([{"a": 2.5, "b": 3.5}])
assert ql1 != ql6 # Should not be equal since difference is more than 1


def test_lookup_in_with_mappings() -> None:
"""Test lookup_in function with mappings."""
# Test with string in mapping keys
data: dict[str, str] = {"key": "value", "other": "value2"}
assert not lookup_in("missing", data) # Key not in mapping
assert not lookup_in("value", data) # Value not in mapping keys
assert not lookup_in("key", data) # Key in mapping but returns False

# Test with string in mapping values
assert not lookup_in("value", data) # Value in mapping but returns False

# Test with invalid combinations
assert not lookup_in(123, data) # type: ignore # Invalid type for data
assert not lookup_in("key", 123) # type: ignore # Invalid type for rhs

# Test with list in mapping
data_list: list[str] = ["value1", "value2"]
assert lookup_in("value1", data_list) # Value in list returns True


def test_lookup_nin_with_mappings() -> None:
"""Test lookup_nin function with mappings."""
# Test with string in mapping keys
data: dict[str, str] = {"key": "value", "other": "value2"}
assert not lookup_nin("missing", data) # Key not in mapping returns False
assert not lookup_nin("value", data) # Value not in mapping keys returns False
assert not lookup_nin("key", data) # Key in mapping returns False

# Test with string in mapping values
assert not lookup_nin("value", data) # Value in mapping returns False

# Test with invalid combinations
assert not lookup_nin(123, data) # type: ignore # Invalid type for data
assert not lookup_nin("key", 123) # type: ignore # Invalid type for rhs

# Test with list in mapping
data_list: list[str] = ["value1", "value2"]
assert not lookup_nin("value1", data_list) # Value in list returns False


def test_filter_error_handling() -> None:
"""Test error handling in filter method."""
ql: QueryList[Mapping[str, t.Any]] = QueryList([{"id": 1}, {"id": 2}])

# Test with non-existent field
result = ql.filter(nonexistent=1)
assert len(result) == 0

# Test with invalid lookup
result = ql.filter(id__invalid="test")
assert len(result) == 0

# Test with multiple conditions where one is invalid
result = ql.filter(id__exact=1, id__invalid="test")
assert len(result) == 0

# Test with non-string paths
with pytest.raises(TypeError):
# We need to use Any here because we're intentionally testing invalid types
numeric_key: t.Any = 123
numeric_args: dict[t.Any, t.Any] = {numeric_key: "test"}
ql.filter(**numeric_args)

# Test with None path
with pytest.raises(TypeError):
# We need to use Any here because we're intentionally testing invalid types
none_key: t.Any = None
none_args: dict[t.Any, t.Any] = {none_key: "test"}
ql.filter(**none_args)

# Test with empty path
empty_args: dict[str, t.Any] = {"": "test"}
result = ql.filter(**empty_args)
assert len(result) == 0


def test_lookup_startswith_endswith_functions() -> None:
"""Test startswith and endswith lookup functions with various types."""
# Test lookup_startswith
assert lookup_startswith("test123", "test") # Basic match
assert not lookup_startswith("test123", "123") # No match at start
assert not lookup_startswith(["test"], "test") # Invalid type for data
assert not lookup_startswith("test", ["test"]) # Invalid type for rhs
assert not lookup_startswith("test", 123) # type: ignore # Invalid type for rhs

# Test lookup_istartswith
assert lookup_istartswith("TEST123", "test") # Case-insensitive match
assert lookup_istartswith("test123", "TEST") # Case-insensitive match reverse
assert not lookup_istartswith("test123", "123") # No match at start
assert not lookup_istartswith(["test"], "test") # Invalid type for data
assert not lookup_istartswith("test", ["test"]) # Invalid type for rhs
assert not lookup_istartswith("test", 123) # type: ignore # Invalid type for rhs

# Test lookup_endswith
assert lookup_endswith("test123", "123") # Basic match
assert not lookup_endswith("test123", "test") # No match at end
assert not lookup_endswith(["test"], "test") # Invalid type for data
assert not lookup_endswith("test", ["test"]) # Invalid type for rhs
assert not lookup_endswith("test", 123) # type: ignore # Invalid type for rhs

# Test lookup_iendswith
assert lookup_iendswith("test123", "123") # Basic match
assert lookup_iendswith("test123", "123") # Case-insensitive match
assert lookup_iendswith("test123", "123") # Case-insensitive match reverse
assert not lookup_iendswith("test123", "test") # No match at end
assert not lookup_iendswith(["test"], "test") # Invalid type for data
assert not lookup_iendswith("test", ["test"]) # Invalid type for rhs
assert not lookup_iendswith("test", 123) # type: ignore # Invalid type for rhs


def test_query_list_eq_numeric_comparison() -> None:
"""Test QueryList __eq__ method with numeric comparisons."""
# Test exact numeric matches
ql1 = QueryList([{"a": 1, "b": 2.0}])
ql2 = QueryList([{"a": 1, "b": 2.0}])
assert ql1 == ql2

# Test numeric comparison within tolerance (difference < 1)
ql3 = QueryList([{"a": 1.1, "b": 2.1}])
assert ql1 == ql3 # Should be equal since difference is less than 1

# Test numeric comparison outside tolerance (difference > 1)
ql4 = QueryList([{"a": 2.5, "b": 3.5}])
assert ql1 != ql4 # Should not be equal since difference is more than 1

# Test mixed numeric types
ql5 = QueryList([{"a": 1, "b": 2}]) # int instead of float
assert ql1 == ql5 # Should be equal since values are equivalent

# Test with nested numeric values
ql6 = QueryList([{"a": {"x": 1.0, "y": 2.0}}])
ql7 = QueryList([{"a": {"x": 1.1, "y": 2.1}}])
assert ql6 == ql7 # Should be equal since differences are less than 1

# Test with mixed content
ql10 = QueryList([{"a": 1, "b": "test"}])
ql11 = QueryList([{"a": 1.1, "b": "test"}])
assert ql10 == ql11 # Should be equal since numeric difference is less than 1

# Test with non-dict content (exact equality required)
ql8 = QueryList([1, 2, 3])
ql9 = QueryList([1, 2, 3])
assert ql8 == ql9 # Should be equal since values are exactly the same
assert ql8 != QueryList(
[1.1, 2.1, 3.1]
) # Should not be equal since values are different


@dataclasses.dataclass
class Food(t.Mapping[str, t.Any]):
fruit: list[str] = dataclasses.field(default_factory=list)
breakfast: str | None = None

def __getitem__(self, key: str) -> t.Any:
return getattr(self, key)

def __iter__(self) -> t.Iterator[str]:
return iter(self.__dataclass_fields__)

def __len__(self) -> int:
return len(self.__dataclass_fields__)


@dataclasses.dataclass
class Restaurant(t.Mapping[str, t.Any]):
place: str
city: str
state: str
food: Food = dataclasses.field(default_factory=Food)

def __getitem__(self, key: str) -> t.Any:
return getattr(self, key)

def __iter__(self) -> t.Iterator[str]:
return iter(self.__dataclass_fields__)

def __len__(self) -> int:
return len(self.__dataclass_fields__)


def test_keygetter_nested_objects() -> None:
"""Test keygetter function with nested objects."""
# Test with nested dataclass that implements Mapping protocol
restaurant = Restaurant(
place="Largo",
city="Tampa",
state="Florida",
food=Food(fruit=["banana", "orange"], breakfast="cereal"),
)
assert keygetter(restaurant, "food") == Food(
fruit=["banana", "orange"], breakfast="cereal"
)
assert keygetter(restaurant, "food__breakfast") == "cereal"
assert keygetter(restaurant, "food__fruit") == ["banana", "orange"]

# Test with non-existent attribute (returns None due to exception handling)
with suppress(Exception):
assert keygetter(restaurant, "nonexistent") is None

# Test with invalid path format (returns the object itself)
assert keygetter(restaurant, "") == restaurant
assert keygetter(restaurant, "__") == restaurant

# Test with non-mapping object (returns the object itself)
non_mapping = "not a mapping"
assert (
keygetter(t.cast(t.Mapping[str, t.Any], non_mapping), "any_key") == non_mapping
)


def test_query_list_slicing() -> None:
"""Test QueryList slicing operations."""
ql = QueryList([1, 2, 3, 4, 5])

# Test positive indices
assert ql[1:3] == QueryList([2, 3])
assert ql[0:5:2] == QueryList([1, 3, 5])

# Test negative indices
assert ql[-3:] == QueryList([3, 4, 5])
assert ql[:-2] == QueryList([1, 2, 3])
assert ql[-4:-2] == QueryList([2, 3])

# Test steps
assert ql[::2] == QueryList([1, 3, 5])
assert ql[::-1] == QueryList([5, 4, 3, 2, 1])
assert ql[4:0:-2] == QueryList([5, 3])

# Test empty slices
assert ql[5:] == QueryList([])
assert ql[-1:-5] == QueryList([])


def test_query_list_attributes() -> None:
"""Test QueryList list behavior and pk_key attribute."""
# Test list behavior
ql = QueryList([1, 2, 3])
assert list(ql) == [1, 2, 3]
assert len(ql) == 3
assert ql[0] == 1
assert ql[-1] == 3

# Test pk_key attribute with objects
@dataclasses.dataclass
class Item(t.Mapping[str, t.Any]):
id: str
value: int

def __getitem__(self, key: str) -> t.Any:
return getattr(self, key)

def __iter__(self) -> t.Iterator[str]:
return iter(self.__dataclass_fields__)

def __len__(self) -> int:
return len(self.__dataclass_fields__)

items = [Item("1", 1), Item("2", 2)]
ql_items: QueryList[t.Any] = QueryList(items)
ql_items.pk_key = "id"
assert list(ql_items.items()) == [("1", items[0]), ("2", items[1])]

# Test pk_key with non-existent attribute
ql_items.pk_key = "nonexistent"
with pytest.raises(AttributeError):
ql_items.items()

# Test pk_key with None
ql_items.pk_key = None
with pytest.raises(PKRequiredException):
ql_items.items()


def test_lookup_name_map() -> None:
"""Test LOOKUP_NAME_MAP contains all lookup functions."""
# Test all lookup functions are in the map
assert LOOKUP_NAME_MAP["eq"] == lookup_exact
assert LOOKUP_NAME_MAP["exact"] == lookup_exact
assert LOOKUP_NAME_MAP["iexact"] == lookup_iexact
assert LOOKUP_NAME_MAP["contains"] == lookup_contains
assert LOOKUP_NAME_MAP["icontains"] == lookup_icontains
assert LOOKUP_NAME_MAP["startswith"] == lookup_startswith
assert LOOKUP_NAME_MAP["istartswith"] == lookup_istartswith
assert LOOKUP_NAME_MAP["endswith"] == lookup_endswith
assert LOOKUP_NAME_MAP["iendswith"] == lookup_iendswith
assert LOOKUP_NAME_MAP["in"] == lookup_in
assert LOOKUP_NAME_MAP["nin"] == lookup_nin
assert LOOKUP_NAME_MAP["regex"] == lookup_regex
assert LOOKUP_NAME_MAP["iregex"] == lookup_iregex

# Test lookup functions behavior through the map
data = "test123"
assert LOOKUP_NAME_MAP["contains"](data, "test")
assert LOOKUP_NAME_MAP["icontains"](data, "TEST")
assert LOOKUP_NAME_MAP["startswith"](data, "test")
assert LOOKUP_NAME_MAP["endswith"](data, "123")
assert not LOOKUP_NAME_MAP["in"](data, ["other", "values"])
assert LOOKUP_NAME_MAP["regex"](data, r"\d+")


def test_keygetter_additional_cases() -> None:
"""Test additional cases for keygetter function."""
# Test valid and invalid paths
obj = {"a": {"b": 1}}
assert keygetter(obj, "a__b") == 1 # Valid path
assert keygetter(obj, "x__y__z") is None # Invalid path returns None

# Test with non-string paths
assert keygetter(obj, None) is None # type: ignore # None path returns None
assert keygetter(obj, 123) is None # type: ignore # Non-string path returns None

# Test with empty paths
assert keygetter(obj, "") is None # Empty path returns None
assert keygetter(obj, " ") is None # Whitespace path returns None

# Test with nested paths that don't exist
nested_obj = {"level1": {"level2": {"level3": "value"}}}
assert keygetter(nested_obj, "level1__level2__level3") == "value" # Valid path
assert (
keygetter(nested_obj, "level1__level2__nonexistent") is None
) # Invalid leaf returns None
assert (
keygetter(nested_obj, "level1__nonexistent__level3") is None
) # Invalid mid returns None
assert (
keygetter(nested_obj, "nonexistent__level2__level3") is None
) # Invalid root returns None


def test_lookup_functions_more_edge_cases() -> None:
"""Test additional edge cases for lookup functions."""
# TODO: lookup_nin() should handle non-string values correctly
# Currently returns False for all non-string values
assert not lookup_nin(None, "test") # type: ignore # None value returns False
assert not lookup_nin(123, "test") # type: ignore # Non-string value returns False
assert not lookup_nin("test", None) # type: ignore # None right-hand side returns False
assert not lookup_nin("test", 123) # type: ignore # Non-string right-hand side returns False

# TODO: lookup_nin() should handle dict and list values correctly
# Currently returns True for dict not in list and string not in list
assert lookup_nin(
{"key": "value"}, ["not", "a", "string"]
) # Dict not in list returns True
assert lookup_nin(
"value", ["not", "a", "string"]
) # String not in list returns True
assert not lookup_nin(
"item", {"not": "a string"}
) # String not in dict returns False


def test_query_list_items_advanced() -> None:
"""Test advanced items operations in QueryList."""
# Test items() with mixed key types
data = [
{"id": 1, "name": "Alice"},
{"id": "2", "name": "Bob"}, # String ID
{"name": "Charlie", "uuid": "abc-123"}, # Different key name
{"composite": {"id": 4}, "name": "David"}, # Nested ID
]
ql = QueryList(data)
ql.pk_key = "id" # Initialize pk_key

# Test items() with missing keys
with pytest.raises(AttributeError):
_ = list(ql.items()) # Should raise AttributeError for missing keys

# Test items() with different key name
ql.pk_key = "uuid"
with pytest.raises(AttributeError):
_ = list(ql.items()) # Should raise AttributeError for missing keys

# Test items() with nested key
ql.pk_key = "composite__id"
with pytest.raises(AttributeError):
_ = list(ql.items()) # Should raise AttributeError for missing keys


def test_query_list_comparison_advanced() -> None:
"""Test advanced comparison operations in QueryList."""
# Test comparison with different types
ql1: QueryList[t.Any] = QueryList([1, 2, 3])
ql2: QueryList[t.Any] = QueryList([1.0, 2.0, 3.0])
assert ql1 == ql2 # Integer vs float comparison

ql3: QueryList[t.Any] = QueryList(["1", "2", "3"])
assert ql1 != ql3 # Integer vs string comparison

# Test comparison with nested structures
data1 = [{"user": {"id": 1, "name": "Alice"}}, {"user": {"id": 2, "name": "Bob"}}]
data2 = [{"user": {"id": 1, "name": "Alice"}}, {"user": {"id": 2, "name": "Bob"}}]
ql1 = QueryList(data1)
ql2 = QueryList(data2)
assert ql1 == ql2 # Deep equality comparison

# Modify nested structure
data2[1]["user"]["name"] = "Bobby"
ql2 = QueryList(data2)
assert ql1 != ql2 # Deep inequality detection

# Test comparison with custom objects
class Point:
def __init__(self, x: float, y: float) -> None:
self.x = x
self.y = y

def __eq__(self, other: object) -> bool:
if not isinstance(other, Point):
return NotImplemented
return abs(self.x - other.x) < 0.001 and abs(self.y - other.y) < 0.001

ql1 = QueryList[Point]([Point(1.0, 2.0), Point(3.0, 4.0)])
ql2 = QueryList[Point]([Point(1.0001, 1.9999), Point(3.0, 4.0)])
assert ql1 == ql2 # Custom equality comparison

# Test comparison edge cases
assert QueryList([]) == QueryList([]) # Empty lists
assert QueryList([]) != QueryList([1]) # Empty vs non-empty
assert QueryList([None]) == QueryList([None]) # None values
assert QueryList([float("nan")]) != QueryList([float("nan")]) # NaN values

# Test comparison with mixed types
mixed_data1 = [1, "2", 3.0, None, [4, 5], {"key": "value"}]
mixed_data2 = [1, "2", 3.0, None, [4, 5], {"key": "value"}]
ql1 = QueryList[t.Any](mixed_data1)
ql2 = QueryList[t.Any](mixed_data2)
assert ql1 == ql2 # Mixed type comparison

# Test comparison with different orders
ql1 = QueryList[int]([1, 2, 3])
ql2 = QueryList[int]([3, 2, 1])
assert ql1 != ql2 # Order matters


def test_lookup_functions_deep_matching() -> None:
"""Test deep matching behavior in lookup functions."""
# Test lookup_in with deep dictionary matching
data: dict[str, t.Any] = {"a": {"b": {"c": "value"}}}
rhs: dict[str, t.Any] = {"b": {"c": "value"}}
# Deep dictionary matching not implemented yet
assert not lookup_in(data, rhs)

# Test lookup_nin with deep dictionary matching
# Deep dictionary matching not implemented yet
assert not lookup_nin(data, rhs)

# Test lookup_in with pattern matching
pattern = re.compile(r"test\d+")
assert not lookup_in("test123", pattern) # Pattern matching not implemented yet
assert not lookup_nin("test123", pattern) # Pattern matching not implemented yet

# Test lookup_in with mixed types in list
mixed_list: list[str] = ["string", "123", "key:value"] # Convert to string list
# String in list returns True
assert lookup_in("key:value", mixed_list)
# String not in list returns True for nin
assert lookup_nin("other:value", mixed_list)

# Test invalid type combinations return False
invalid_obj = {"key": "123"} # type: dict[str, str] # Valid type but invalid content
assert lookup_in(invalid_obj, "test") is False # Invalid usage but valid types
assert lookup_in("test", invalid_obj) is False # Invalid usage but valid types


def test_parse_lookup_error_cases() -> None:
"""Test error cases in parse_lookup function."""
# Test with invalid path type
obj = {"field": "value"}
assert parse_lookup(obj, 123, "__contains") is None # type: ignore

# Test with invalid lookup type
assert parse_lookup(obj, "field", 123) is None # type: ignore

# Test with path not ending in lookup
assert parse_lookup(obj, "field", "__contains") is None

# Test with empty field name after rsplit
assert parse_lookup(obj, "__contains", "__contains") is None

# Test with invalid object type
assert parse_lookup(None, "field", "__contains") is None # type: ignore

# Test with path containing invalid characters
assert parse_lookup(obj, "field\x00", "__contains") is None