Skip to content

Commit 8b07704

Browse files
committed
✅ increase test coverage for encode function by adding tests for cycle detection and iterable filtering
1 parent d0236dd commit 8b07704

1 file changed

Lines changed: 114 additions & 0 deletions

File tree

tests/unit/encode_test.py

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,16 @@
55
from decimal import Decimal
66
from enum import Enum
77
from urllib.parse import quote
8+
from weakref import WeakKeyDictionary
89

910
import pytest
1011

1112
from qs_codec import Charset, EncodeOptions, Format, ListFormat, dumps, encode
13+
from qs_codec.encode import _encode, _sentinel
1214
from qs_codec.models.undefined import Undefined
15+
from qs_codec.models.weak_wrapper import WeakWrapper
1316
from qs_codec.utils.encode_utils import EncodeUtils
17+
from qs_codec.utils.utils import Utils
1418

1519

1620
class TestEncode:
@@ -1691,3 +1695,113 @@ def test_encodes_non_string_keys(self) -> None:
16911695
)
16921696
== "a=b"
16931697
)
1698+
1699+
1700+
class TestEncodeInternals:
1701+
def test_encode_cycle_detection_raises_on_same_step(self) -> None:
1702+
value: t.Dict[str, t.Any] = {"loop": {}}
1703+
side_channel: WeakKeyDictionary = WeakKeyDictionary()
1704+
parent_channel: WeakKeyDictionary = WeakKeyDictionary()
1705+
wrapper = WeakWrapper(value)
1706+
parent_channel[wrapper] = 1
1707+
side_channel[_sentinel] = parent_channel
1708+
1709+
with pytest.raises(ValueError, match="Circular reference detected"):
1710+
_encode(
1711+
value=value,
1712+
is_undefined=False,
1713+
side_channel=side_channel,
1714+
prefix="root",
1715+
comma_round_trip=False,
1716+
encoder=EncodeUtils.encode,
1717+
serialize_date=EncodeUtils.serialize_date,
1718+
sort=None,
1719+
filter=None,
1720+
formatter=Format.RFC3986.formatter,
1721+
format=Format.RFC3986,
1722+
generate_array_prefix=ListFormat.INDICES.generator,
1723+
allow_empty_lists=False,
1724+
strict_null_handling=False,
1725+
skip_nulls=False,
1726+
encode_dot_in_keys=False,
1727+
allow_dots=False,
1728+
encode_values_only=False,
1729+
charset=Charset.UTF8,
1730+
)
1731+
1732+
def test_encode_cycle_detection_marks_prior_visit_without_raising(self) -> None:
1733+
value: t.Dict[str, t.Any] = {"child": "value"}
1734+
side_channel: WeakKeyDictionary = WeakKeyDictionary()
1735+
parent_channel: WeakKeyDictionary = WeakKeyDictionary()
1736+
wrapper = WeakWrapper(value)
1737+
parent_channel[wrapper] = 99
1738+
side_channel[_sentinel] = parent_channel
1739+
1740+
tokens = _encode(
1741+
value=value,
1742+
is_undefined=False,
1743+
side_channel=side_channel,
1744+
prefix="root",
1745+
comma_round_trip=False,
1746+
encoder=EncodeUtils.encode,
1747+
serialize_date=EncodeUtils.serialize_date,
1748+
sort=None,
1749+
filter=None,
1750+
formatter=Format.RFC3986.formatter,
1751+
format=Format.RFC3986,
1752+
generate_array_prefix=ListFormat.INDICES.generator,
1753+
allow_empty_lists=False,
1754+
strict_null_handling=False,
1755+
skip_nulls=False,
1756+
encode_dot_in_keys=False,
1757+
allow_dots=False,
1758+
encode_values_only=False,
1759+
charset=Charset.UTF8,
1760+
)
1761+
1762+
assert tokens == ["root%5Bchild%5D=value"]
1763+
1764+
def test_encode_handles_iterable_filter_for_indexable_object(self, monkeypatch: pytest.MonkeyPatch) -> None:
1765+
class Indexable:
1766+
def __getitem__(self, key: str) -> str:
1767+
if key != "foo":
1768+
raise KeyError(key)
1769+
return "bar"
1770+
1771+
original = Utils.is_non_nullish_primitive
1772+
1773+
def fake_is_non_nullish_primitive(val: t.Any, skip_nulls: bool = False) -> bool:
1774+
if isinstance(val, Indexable):
1775+
return False
1776+
return original(val, skip_nulls)
1777+
1778+
monkeypatch.setattr(Utils, "is_non_nullish_primitive", staticmethod(fake_is_non_nullish_primitive))
1779+
1780+
tokens = _encode(
1781+
value=Indexable(),
1782+
is_undefined=False,
1783+
side_channel=WeakKeyDictionary(),
1784+
prefix="root",
1785+
comma_round_trip=False,
1786+
encoder=EncodeUtils.encode,
1787+
serialize_date=EncodeUtils.serialize_date,
1788+
sort=None,
1789+
filter=["foo"],
1790+
formatter=Format.RFC3986.formatter,
1791+
format=Format.RFC3986,
1792+
generate_array_prefix=ListFormat.INDICES.generator,
1793+
allow_empty_lists=False,
1794+
strict_null_handling=False,
1795+
skip_nulls=False,
1796+
encode_dot_in_keys=False,
1797+
allow_dots=False,
1798+
encode_values_only=False,
1799+
charset=Charset.UTF8,
1800+
)
1801+
1802+
assert tokens == ["root%5Bfoo%5D=bar"]
1803+
1804+
def test_encode_comma_format_serializes_datetime_without_custom_callable(self) -> None:
1805+
dt = datetime(2024, 1, 1)
1806+
options = EncodeOptions(list_format=ListFormat.COMMA, serialize_date="iso")
1807+
assert encode({"a": [dt]}, options) == f"a={dt.isoformat().replace(':', '%3A')}"

0 commit comments

Comments
 (0)