From 857b91eef6779eb0cecb0ad3e736ccd013928657 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Sun, 26 Oct 2025 00:02:07 +0000 Subject: [PATCH 01/11] Add comprehensive test cases for store tests - Add datetime, date, and time test cases (serialized as ISO strings) - Add UUID test case (serialized as string) - Add bytes test case (base64 encoded) - Add tuple and set test cases (serialized as lists) - Add deeply nested structure test cases (4-level nesting, nested lists/dicts) - Add edge case tests (empty strings, empty lists, empty dicts, unicode, special chars) - Add zero and negative number test cases - Add large list test case (1000 elements) - Expand LARGE_TEST_DATA to include large int, float, and list cases These new test cases will automatically be tested across all store implementations (Memory, Redis, DynamoDB, etc.) since they inherit from BaseStoreTests. Co-authored-by: William Easton --- .../src/key_value/shared_test/cases.py | 207 ++++++++++++++++++ 1 file changed, 207 insertions(+) diff --git a/key-value/key-value-shared-test/src/key_value/shared_test/cases.py b/key-value/key-value-shared-test/src/key_value/shared_test/cases.py index bfe304fe..7c9e97c5 100644 --- a/key-value/key-value-shared-test/src/key_value/shared_test/cases.py +++ b/key-value/key-value-shared-test/src/key_value/shared_test/cases.py @@ -1,9 +1,13 @@ +import base64 +import json from dataclasses import dataclass from datetime import datetime, timezone from typing import Any +from uuid import UUID FIXED_DATETIME = datetime(2025, 1, 1, 0, 0, 0, tzinfo=timezone.utc) FIXED_TIME = FIXED_DATETIME.time() +FIXED_UUID = UUID("12345678-1234-5678-1234-567812345678") @dataclass @@ -47,6 +51,140 @@ class Case: name="list-three", data={"list_key_1": [1, True, 3.0, "string"]}, json='{"list_key_1": [1, true, 3.0, "string"]}' ) +# Datetime cases (serialized as ISO format strings) +DATETIME_CASE: Case = Case( + name="datetime", + data={"datetime_key": FIXED_DATETIME.isoformat()}, + json='{"datetime_key": "2025-01-01T00:00:00+00:00"}', +) +DATE_CASE: Case = Case( + name="date", + data={"date_key": FIXED_DATETIME.date().isoformat()}, + json='{"date_key": "2025-01-01"}', +) +TIME_CASE: Case = Case( + name="time", + data={"time_key": FIXED_TIME.isoformat()}, + json='{"time_key": "00:00:00"}', +) + +# UUID case (serialized as string) +UUID_CASE: Case = Case( + name="uuid", + data={"uuid_key": str(FIXED_UUID)}, + json='{"uuid_key": "12345678-1234-5678-1234-567812345678"}', +) + +# Bytes case (base64 encoded) +BYTES_VALUE = b"hello world" +BYTES_CASE: Case = Case( + name="bytes", + data={"bytes_key": base64.b64encode(BYTES_VALUE).decode("ascii")}, + json=f'{{"bytes_key": "{base64.b64encode(BYTES_VALUE).decode("ascii")}"}}', +) + +# Tuple case (serializes as list in JSON) +TUPLE_CASE: Case = Case( + name="tuple", + data={"tuple_key": [1, "two", 3.0]}, + json='{"tuple_key": [1, "two", 3.0]}', +) + +# Set case (serializes as sorted list in JSON) +SET_CASE: Case = Case( + name="set", + data={"set_key": [1, 2, 3]}, + json='{"set_key": [1, 2, 3]}', +) + +# Deeply nested structure cases +DEEP_NESTED_CASE: Case = Case( + name="deep-nested", + data={"level1": {"level2": {"level3": {"level4": {"value": "deep"}}}}}, + json='{"level1": {"level2": {"level3": {"level4": {"value": "deep"}}}}}', +) + +NESTED_LIST_DICT_CASE: Case = Case( + name="nested-list-dict", + data={"items": [{"id": 1, "name": "item1"}, {"id": 2, "name": "item2"}]}, + json='{"items": [{"id": 1, "name": "item1"}, {"id": 2, "name": "item2"}]}', +) + +NESTED_DICT_LIST_CASE: Case = Case( + name="nested-dict-list", + data={"categories": {"fruits": ["apple", "banana"], "vegetables": ["carrot", "broccoli"]}}, + json='{"categories": {"fruits": ["apple", "banana"], "vegetables": ["carrot", "broccoli"]}}', +) + +MIXED_NESTED_CASE: Case = Case( + name="mixed-nested", + data={"outer": {"inner": [{"nested": [1, 2, 3]}]}}, + json='{"outer": {"inner": [{"nested": [1, 2, 3]}]}}', +) + +# Edge cases +EMPTY_STRING_CASE: Case = Case( + name="empty-string", + data={"empty_key": ""}, + json='{"empty_key": ""}', +) + +EMPTY_LIST_CASE: Case = Case( + name="empty-list", + data={"empty_list_key": []}, + json='{"empty_list_key": []}', +) + +EMPTY_DICT_CASE: Case = Case( + name="empty-dict", + data={"empty_dict_key": {}}, + json='{"empty_dict_key": {}}', +) + +UNICODE_CASE: Case = Case( + name="unicode", + data={"unicode_key": "Hello 世界 🌍 émojis"}, + json='{"unicode_key": "Hello 世界 🌍 émojis"}', +) + +SPECIAL_CHARS_CASE: Case = Case( + name="special-chars", + data={"special_key": "Line1\nLine2\tTabbed"}, + json='{"special_key": "Line1\\nLine2\\tTabbed"}', +) + +# Zero values +ZERO_INT_CASE: Case = Case( + name="zero-int", + data={"zero_int_key": 0}, + json='{"zero_int_key": 0}', +) + +ZERO_FLOAT_CASE: Case = Case( + name="zero-float", + data={"zero_float_key": 0.0}, + json='{"zero_float_key": 0.0}', +) + +NEGATIVE_INT_CASE: Case = Case( + name="negative-int", + data={"negative_int_key": -42}, + json='{"negative_int_key": -42}', +) + +NEGATIVE_FLOAT_CASE: Case = Case( + name="negative-float", + data={"negative_float_key": -3.14}, + json='{"negative_float_key": -3.14}', +) + +# Large collection case +LARGE_LIST_CASE: Case = Case( + name="large-list", + data={"large_list": list(range(1000))}, + json=f'{{"large_list": {json.dumps(list(range(1000)))}}}', +) + TEST_CASE_DATA: list[dict[str, Any]] = [ case.data @@ -65,6 +203,26 @@ class Case: LIST_CASE_ONE, LIST_CASE_TWO, LIST_CASE_THREE, + DATETIME_CASE, + DATE_CASE, + TIME_CASE, + UUID_CASE, + BYTES_CASE, + TUPLE_CASE, + SET_CASE, + DEEP_NESTED_CASE, + NESTED_LIST_DICT_CASE, + NESTED_DICT_LIST_CASE, + MIXED_NESTED_CASE, + EMPTY_STRING_CASE, + EMPTY_LIST_CASE, + EMPTY_DICT_CASE, + UNICODE_CASE, + SPECIAL_CHARS_CASE, + ZERO_INT_CASE, + ZERO_FLOAT_CASE, + NEGATIVE_INT_CASE, + NEGATIVE_FLOAT_CASE, ] ] TEST_CASE_JSON: list[str] = [ @@ -84,6 +242,26 @@ class Case: LIST_CASE_ONE, LIST_CASE_TWO, LIST_CASE_THREE, + DATETIME_CASE, + DATE_CASE, + TIME_CASE, + UUID_CASE, + BYTES_CASE, + TUPLE_CASE, + SET_CASE, + DEEP_NESTED_CASE, + NESTED_LIST_DICT_CASE, + NESTED_DICT_LIST_CASE, + MIXED_NESTED_CASE, + EMPTY_STRING_CASE, + EMPTY_LIST_CASE, + EMPTY_DICT_CASE, + UNICODE_CASE, + SPECIAL_CHARS_CASE, + ZERO_INT_CASE, + ZERO_FLOAT_CASE, + NEGATIVE_INT_CASE, + NEGATIVE_FLOAT_CASE, ] ] @@ -106,6 +284,26 @@ class Case: LIST_CASE_ONE, LIST_CASE_TWO, LIST_CASE_THREE, + DATETIME_CASE, + DATE_CASE, + TIME_CASE, + UUID_CASE, + BYTES_CASE, + TUPLE_CASE, + SET_CASE, + DEEP_NESTED_CASE, + NESTED_LIST_DICT_CASE, + NESTED_DICT_LIST_CASE, + MIXED_NESTED_CASE, + EMPTY_STRING_CASE, + EMPTY_LIST_CASE, + EMPTY_DICT_CASE, + UNICODE_CASE, + SPECIAL_CHARS_CASE, + ZERO_INT_CASE, + ZERO_FLOAT_CASE, + NEGATIVE_INT_CASE, + NEGATIVE_FLOAT_CASE, ] ] @@ -113,12 +311,18 @@ class Case: case.data for case in [ LARGE_STRING_CASE, + LARGE_INT_CASE, + LARGE_FLOAT_CASE, + LARGE_LIST_CASE, ] ] LARGE_TEST_DATA_JSON: list[str] = [ case.json for case in [ LARGE_STRING_CASE, + LARGE_INT_CASE, + LARGE_FLOAT_CASE, + LARGE_LIST_CASE, ] ] LARGE_TEST_DATA_ARGNAMES: tuple[str, str] = ("data", "json") @@ -127,6 +331,9 @@ class Case: case.name for case in [ LARGE_STRING_CASE, + LARGE_INT_CASE, + LARGE_FLOAT_CASE, + LARGE_LIST_CASE, ] ] From 0fdf0e002bc5d9988e39e06244ca619bf0433aa1 Mon Sep 17 00:00:00 2001 From: William Easton Date: Sat, 25 Oct 2025 19:33:44 -0500 Subject: [PATCH 02/11] Fixes unicode case --- .../key-value-shared-test/src/key_value/shared_test/cases.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/key-value/key-value-shared-test/src/key_value/shared_test/cases.py b/key-value/key-value-shared-test/src/key_value/shared_test/cases.py index 7c9e97c5..31f4807a 100644 --- a/key-value/key-value-shared-test/src/key_value/shared_test/cases.py +++ b/key-value/key-value-shared-test/src/key_value/shared_test/cases.py @@ -144,7 +144,7 @@ class Case: UNICODE_CASE: Case = Case( name="unicode", data={"unicode_key": "Hello 世界 🌍 émojis"}, - json='{"unicode_key": "Hello 世界 🌍 émojis"}', + json='{"unicode_key": "Hello \\u4e16\\u754c \\ud83c\\udf0d \\u00e9mojis"}', ) SPECIAL_CHARS_CASE: Case = Case( From b5ba1e193e97ad91e6cf421994f9dd151d0add15 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Sun, 26 Oct 2025 00:43:49 +0000 Subject: [PATCH 03/11] Add negative test cases for non-serializable types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit addresses the issue where test cases were pre-serializing values (datetime, UUID, bytes, etc.) to strings, rather than testing how stores handle these types. Changes: - Add test_put_nonserializable_types parametrized test to base.py that verifies stores reject non-JSON-serializable Python types (datetime, date, time, UUID, bytes, tuple, set, function, type) by raising SerializationError - Update comments in cases.py to clarify that datetime/UUID/bytes test cases are testing string handling, not object handling - Reference negative tests in base.py where actual objects are tested This fulfills the requirement from issue #96 to include negative test cases that assert certain types should fail. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: William Easton --- key-value/key-value-aio/tests/stores/base.py | 22 ++++++++++++++++++- .../src/key_value/shared_test/cases.py | 15 ++++++++----- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/key-value/key-value-aio/tests/stores/base.py b/key-value/key-value-aio/tests/stores/base.py index 36550e74..12c5fbae 100644 --- a/key-value/key-value-aio/tests/stores/base.py +++ b/key-value/key-value-aio/tests/stores/base.py @@ -46,10 +46,30 @@ async def test_empty_ttl(self, store: BaseStore): assert ttl == (None, None) async def test_put_serialization_errors(self, store: BaseStore): - """Tests that the put method does not raise an exception when called on a new store.""" + """Tests that the put method raises SerializationError for non-JSON-serializable Pydantic types.""" with pytest.raises(SerializationError): await store.put(collection="test", key="test", value={"test": AnyHttpUrl("https://test.com")}) + @pytest.mark.parametrize( + "name,value", + [ + ("datetime", __import__("datetime").datetime.now(__import__("datetime").timezone.utc)), + ("date", __import__("datetime").date(2025, 1, 1)), + ("time", __import__("datetime").time(12, 0, 0)), + ("uuid", __import__("uuid").UUID("12345678-1234-5678-1234-567812345678")), + ("bytes", b"hello world"), + ("tuple", (1, 2, 3)), + ("set", {1, 2, 3}), + ("function", lambda x: x), + ("type", type("TestClass", (), {})), + ], + ids=["datetime", "date", "time", "uuid", "bytes", "tuple", "set", "function", "type"], + ) + async def test_put_nonserializable_types(self, store: BaseStore, name: str, value: Any): # pyright: ignore[reportUnusedParameter] # noqa: ARG002 + """Tests that non-JSON-serializable Python types raise SerializationError.""" + with pytest.raises(SerializationError): + await store.put(collection="test", key="test", value={"test": value}) + async def test_get_put_get(self, store: BaseStore): assert await store.get(collection="test", key="test") is None await store.put(collection="test", key="test", value={"test": "test"}) diff --git a/key-value/key-value-shared-test/src/key_value/shared_test/cases.py b/key-value/key-value-shared-test/src/key_value/shared_test/cases.py index 31f4807a..aa768ffd 100644 --- a/key-value/key-value-shared-test/src/key_value/shared_test/cases.py +++ b/key-value/key-value-shared-test/src/key_value/shared_test/cases.py @@ -51,7 +51,8 @@ class Case: name="list-three", data={"list_key_1": [1, True, 3.0, "string"]}, json='{"list_key_1": [1, true, 3.0, "string"]}' ) -# Datetime cases (serialized as ISO format strings) +# ISO format string cases (these test string handling, not datetime object handling) +# Note: Actual datetime/date/time objects are tested in negative test cases (base.py) DATETIME_CASE: Case = Case( name="datetime", data={"datetime_key": FIXED_DATETIME.isoformat()}, @@ -68,14 +69,16 @@ class Case: json='{"time_key": "00:00:00"}', ) -# UUID case (serialized as string) +# UUID string case (tests string handling, not UUID object handling) +# Note: Actual UUID objects are tested in negative test cases (base.py) UUID_CASE: Case = Case( name="uuid", data={"uuid_key": str(FIXED_UUID)}, json='{"uuid_key": "12345678-1234-5678-1234-567812345678"}', ) -# Bytes case (base64 encoded) +# Base64-encoded string case (tests string handling, not bytes object handling) +# Note: Actual bytes objects are tested in negative test cases (base.py) BYTES_VALUE = b"hello world" BYTES_CASE: Case = Case( name="bytes", @@ -83,14 +86,16 @@ class Case: json=f'{{"bytes_key": "{base64.b64encode(BYTES_VALUE).decode("ascii")}"}}', ) -# Tuple case (serializes as list in JSON) +# List case (tests list handling, not tuple object handling) +# Note: Actual tuple objects are tested in negative test cases (base.py) TUPLE_CASE: Case = Case( name="tuple", data={"tuple_key": [1, "two", 3.0]}, json='{"tuple_key": [1, "two", 3.0]}', ) -# Set case (serializes as sorted list in JSON) +# List case (tests list handling, not set object handling) +# Note: Actual set objects are tested in negative test cases (base.py) SET_CASE: Case = Case( name="set", data={"set_key": [1, 2, 3]}, From df6e3cb8efc57ecece5e4a5abbcc05ac3d36089e Mon Sep 17 00:00:00 2001 From: William Easton Date: Sat, 25 Oct 2025 23:28:43 -0500 Subject: [PATCH 04/11] Improve test cases --- .../src/key_value/aio/stores/memory/store.py | 2 +- key-value/key-value-aio/tests/stores/base.py | 50 +- .../tests/stores/keyring/test_keyring.py | 2 - .../windows_registry/test_windows_registry.py | 2 - .../src/key_value/shared_test/cases.py | 900 ++++++++++++------ .../src/key_value/shared/code_gen/gather.py | 2 +- .../tests/utils/test_managed_entry.py | 29 +- .../sync/code_gen/stores/memory/store.py | 2 +- .../tests/code_gen/stores/base.py | 30 +- .../code_gen/stores/keyring/test_keyring.py | 2 - .../windows_registry/test_windows_registry.py | 2 - pyproject.toml | 1 + scripts/build_sync_library.py | 2 +- 13 files changed, 661 insertions(+), 365 deletions(-) diff --git a/key-value/key-value-aio/src/key_value/aio/stores/memory/store.py b/key-value/key-value-aio/src/key_value/aio/stores/memory/store.py index 8ef1a12c..f316d5b9 100644 --- a/key-value/key-value-aio/src/key_value/aio/stores/memory/store.py +++ b/key-value/key-value-aio/src/key_value/aio/stores/memory/store.py @@ -53,7 +53,7 @@ def _memory_cache_ttu(_key: Any, value: MemoryCacheEntry, now: float) -> float: return float(expiration_epoch) -def _memory_cache_getsizeof(value: MemoryCacheEntry) -> int: # pyright: ignore[reportUnusedParameter] # noqa: ARG001 +def _memory_cache_getsizeof(value: MemoryCacheEntry) -> int: # pyright: ignore[reportUnusedParameter] """Return size of cache entry (always 1 for entry counting).""" return 1 diff --git a/key-value/key-value-aio/tests/stores/base.py b/key-value/key-value-aio/tests/stores/base.py index 12c5fbae..dc6545bf 100644 --- a/key-value/key-value-aio/tests/stores/base.py +++ b/key-value/key-value-aio/tests/stores/base.py @@ -9,12 +9,11 @@ from key_value.shared.code_gen.sleep import asleep from key_value.shared.errors import InvalidTTLError, SerializationError from key_value.shared_test.cases import ( - LARGE_TEST_DATA_ARGNAMES, - LARGE_TEST_DATA_ARGVALUES, - LARGE_TEST_DATA_IDS, - SIMPLE_TEST_DATA_ARGNAMES, - SIMPLE_TEST_DATA_ARGVALUES, - SIMPLE_TEST_DATA_IDS, + LARGE_DATA_CASES, + NEGATIVE_SIMPLE_CASES, + SIMPLE_CASES, + NegativeCases, + PositiveCases, ) from pydantic import AnyHttpUrl @@ -50,40 +49,27 @@ async def test_put_serialization_errors(self, store: BaseStore): with pytest.raises(SerializationError): await store.put(collection="test", key="test", value={"test": AnyHttpUrl("https://test.com")}) - @pytest.mark.parametrize( - "name,value", - [ - ("datetime", __import__("datetime").datetime.now(__import__("datetime").timezone.utc)), - ("date", __import__("datetime").date(2025, 1, 1)), - ("time", __import__("datetime").time(12, 0, 0)), - ("uuid", __import__("uuid").UUID("12345678-1234-5678-1234-567812345678")), - ("bytes", b"hello world"), - ("tuple", (1, 2, 3)), - ("set", {1, 2, 3}), - ("function", lambda x: x), - ("type", type("TestClass", (), {})), - ], - ids=["datetime", "date", "time", "uuid", "bytes", "tuple", "set", "function", "type"], - ) - async def test_put_nonserializable_types(self, store: BaseStore, name: str, value: Any): # pyright: ignore[reportUnusedParameter] # noqa: ARG002 - """Tests that non-JSON-serializable Python types raise SerializationError.""" - with pytest.raises(SerializationError): - await store.put(collection="test", key="test", value={"test": value}) - async def test_get_put_get(self, store: BaseStore): assert await store.get(collection="test", key="test") is None await store.put(collection="test", key="test", value={"test": "test"}) assert await store.get(collection="test", key="test") == {"test": "test"} - @pytest.mark.parametrize(argnames=SIMPLE_TEST_DATA_ARGNAMES, argvalues=SIMPLE_TEST_DATA_ARGVALUES, ids=SIMPLE_TEST_DATA_IDS) - async def test_get_complex_put_get(self, store: BaseStore, data: dict[str, Any], json: str): # pyright: ignore[reportUnusedParameter, reportUnusedParameter] # noqa: ARG002 + @PositiveCases.parametrize(cases=SIMPLE_CASES) + async def test_models_put_get(self, store: BaseStore, data: dict[str, Any], json: str, round_trip: dict[str, Any]): # pyright: ignore[reportUnusedParameter, reportUnusedParameter] # noqa: ARG002 await store.put(collection="test", key="test", value=data) - assert await store.get(collection="test", key="test") == data + retrieved_data = await store.get(collection="test", key="test") + assert retrieved_data is not None + assert retrieved_data == round_trip + + @NegativeCases.parametrize(cases=NEGATIVE_SIMPLE_CASES) + async def test_negative_models_put_get(self, store: BaseStore, data: dict[str, Any], error: type[Exception]): # pyright: ignore[reportUnusedParameter, reportUnusedParameter] + with pytest.raises(error): + await store.put(collection="test", key="test", value=data) - @pytest.mark.parametrize(argnames=LARGE_TEST_DATA_ARGNAMES, argvalues=LARGE_TEST_DATA_ARGVALUES, ids=LARGE_TEST_DATA_IDS) - async def test_get_large_put_get(self, store: BaseStore, data: dict[str, Any], json: str): # pyright: ignore[reportUnusedParameter, reportUnusedParameter] # noqa: ARG002 + @PositiveCases.parametrize(cases=[LARGE_DATA_CASES]) + async def test_get_large_put_get(self, store: BaseStore, data: dict[str, Any], json: str, round_trip: dict[str, Any]): # pyright: ignore[reportUnusedParameter, reportUnusedParameter] # noqa: ARG002 await store.put(collection="test", key="test", value=data) - assert await store.get(collection="test", key="test") == data + assert await store.get(collection="test", key="test") == round_trip async def test_put_many_get(self, store: BaseStore): await store.put_many(collection="test", keys=["test", "test_2"], values=[{"test": "test"}, {"test": "test_2"}]) diff --git a/key-value/key-value-aio/tests/stores/keyring/test_keyring.py b/key-value/key-value-aio/tests/stores/keyring/test_keyring.py index 03c424fe..65024e72 100644 --- a/key-value/key-value-aio/tests/stores/keyring/test_keyring.py +++ b/key-value/key-value-aio/tests/stores/keyring/test_keyring.py @@ -1,7 +1,6 @@ from typing import Any import pytest -from key_value.shared_test.cases import LARGE_TEST_DATA_ARGNAMES, LARGE_TEST_DATA_ARGVALUES, LARGE_TEST_DATA_IDS from typing_extensions import override from key_value.aio.stores.base import BaseStore @@ -28,7 +27,6 @@ async def test_not_unbounded(self, store: BaseStore): ... @override @pytest.mark.skipif(condition=detect_on_windows(), reason="Keyrings do not support large values on Windows") - @pytest.mark.parametrize(argnames=LARGE_TEST_DATA_ARGNAMES, argvalues=LARGE_TEST_DATA_ARGVALUES, ids=LARGE_TEST_DATA_IDS) async def test_get_large_put_get(self, store: BaseStore, data: dict[str, Any], json: str): await store.put(collection="test", key="test", value=data) assert await store.get(collection="test", key="test") == data diff --git a/key-value/key-value-aio/tests/stores/windows_registry/test_windows_registry.py b/key-value/key-value-aio/tests/stores/windows_registry/test_windows_registry.py index dbdfe95f..9d636ac7 100644 --- a/key-value/key-value-aio/tests/stores/windows_registry/test_windows_registry.py +++ b/key-value/key-value-aio/tests/stores/windows_registry/test_windows_registry.py @@ -1,7 +1,6 @@ from typing import TYPE_CHECKING, Any import pytest -from key_value.shared_test.cases import LARGE_TEST_DATA_ARGNAMES, LARGE_TEST_DATA_ARGVALUES, LARGE_TEST_DATA_IDS from typing_extensions import override from key_value.aio.stores.base import BaseStore @@ -31,7 +30,6 @@ async def store(self) -> "WindowsRegistryStore": async def test_not_unbounded(self, store: BaseStore): ... @override - @pytest.mark.parametrize(argnames=LARGE_TEST_DATA_ARGNAMES, argvalues=LARGE_TEST_DATA_ARGVALUES, ids=LARGE_TEST_DATA_IDS) async def test_get_large_put_get(self, store: BaseStore, data: dict[str, Any], json: str): await store.put(collection="test", key="test", value=data) assert await store.get(collection="test", key="test") == data diff --git a/key-value/key-value-shared-test/src/key_value/shared_test/cases.py b/key-value/key-value-shared-test/src/key_value/shared_test/cases.py index aa768ffd..9f89b4c3 100644 --- a/key-value/key-value-shared-test/src/key_value/shared_test/cases.py +++ b/key-value/key-value-shared-test/src/key_value/shared_test/cases.py @@ -1,352 +1,682 @@ import base64 import json -from dataclasses import dataclass +from dataclasses import dataclass, field from datetime import datetime, timezone -from typing import Any +from typing import Any, Generic, TypeVar from uuid import UUID +import pytest +from _pytest.mark.structures import MarkDecorator +from key_value.shared.errors.key_value import SerializationError +from typing_extensions import Self + FIXED_DATETIME = datetime(2025, 1, 1, 0, 0, 0, tzinfo=timezone.utc) FIXED_TIME = FIXED_DATETIME.time() FIXED_UUID = UUID("12345678-1234-5678-1234-567812345678") @dataclass -class Case: +class BaseCase: name: str data: dict[str, Any] - json: str + round_trip: dict[str, Any] = field(default_factory=dict) + def __post_init__(self) -> None: + if not self.round_trip: + self.round_trip = self.data -NULL_CASE: Case = Case(name="null", data={"null_key": None}, json='{"null_key": null}') -BOOL_TRUE_CASE: Case = Case(name="bool-true", data={"bool_true_key": True}, json='{"bool_true_key": true}') -BOOL_FALSE_CASE: Case = Case(name="bool-false", data={"bool_false_key": False}, json='{"bool_false_key": false}') +@dataclass +class Case(BaseCase): + json: str = field(default_factory=str) -INT_CASE: Case = Case(name="int", data={"int_key": 1}, json='{"int_key": 1}') -LARGE_INT_CASE: Case = Case(name="large-int", data={"large_int_key": 1 * 10**18}, json=f'{{"large_int_key": {1 * 10**18}}}') -FLOAT_CASE: Case = Case(name="float", data={"float_key": 1.0}, json='{"float_key": 1.0}') -LARGE_FLOAT_CASE: Case = Case(name="large-float", data={"large_float_key": 1.0 * 10**63}, json=f'{{"large_float_key": {1.0 * 10**63}}}') +@dataclass +class NegativeCase(BaseCase): + error: type[Exception] = field(default_factory=lambda: SerializationError) -STRING_CASE: Case = Case(name="string", data={"string_key": "string_value"}, json='{"string_key": "string_value"}') -LARGE_STRING_CASE: Case = Case(name="large-string", data={"large_string_key": "a" * 10000}, json=f'{{"large_string_key": "{"a" * 10000}"}}') -DICT_CASE_ONE: Case = Case(name="dict-one", data={"dict_key_1": {"nested": "value"}}, json='{"dict_key_1": {"nested": "value"}}') -DICT_CASE_TWO: Case = Case( - name="dict-two", - data={"dict_key_1": {"nested": "value"}, "dict_key_2": {"nested": "value"}}, - json='{"dict_key_1": {"nested": "value"}, "dict_key_2": {"nested": "value"}}', -) -DICT_CASE_THREE: Case = Case( - name="dict-three", - data={"dict_key_1": {"nested": "value"}, "dict_key_2": {"nested": "value"}, "dict_key_3": {"nested": "value"}}, - json='{"dict_key_1": {"nested": "value"}, "dict_key_2": {"nested": "value"}, "dict_key_3": {"nested": "value"}}', -) +BC = TypeVar("BC", bound=BaseCase) -LIST_CASE_ONE: Case = Case(name="list", data={"list_key": [1, 2, 3]}, json='{"list_key": [1, 2, 3]}') -LIST_CASE_TWO: Case = Case( - name="list-two", data={"list_key_1": [1, 2, 3], "list_key_2": [1, 2, 3]}, json='{"list_key_1": [1, 2, 3], "list_key_2": [1, 2, 3]}' -) -LIST_CASE_THREE: Case = Case( - name="list-three", data={"list_key_1": [1, True, 3.0, "string"]}, json='{"list_key_1": [1, true, 3.0, "string"]}' -) -# ISO format string cases (these test string handling, not datetime object handling) -# Note: Actual datetime/date/time objects are tested in negative test cases (base.py) -DATETIME_CASE: Case = Case( - name="datetime", - data={"datetime_key": FIXED_DATETIME.isoformat()}, - json='{"datetime_key": "2025-01-01T00:00:00+00:00"}', -) -DATE_CASE: Case = Case( - name="date", - data={"date_key": FIXED_DATETIME.date().isoformat()}, - json='{"date_key": "2025-01-01"}', +@dataclass +class Cases(Generic[BC]): + case_type: str + cases: list[BC] = field(default_factory=list) + + def __init__(self, *args: BC, case_type: str): + self.case_type = case_type + self.cases = list(args) + + def add_case(self, case: BC) -> Self: + self.cases.append(case) + return self + + def to_ids(self) -> list[str]: + return [case.name for case in self.cases] + + +@dataclass +class PositiveCases(Cases[Case]): + def __init__(self, *args: Case, case_type: str): + super().__init__(*args, case_type=case_type) + + @classmethod + def to_argnames(cls) -> tuple[str, str, str]: + return ("data", "json", "round_trip") + + def to_argvalues(self) -> list[tuple[dict[str, Any], str, dict[str, Any]]]: + return [(case.data, case.json, case.round_trip) for case in self.cases] + + @classmethod + def parametrize(cls, cases: list[Self]) -> MarkDecorator: + argnames = cls.to_argnames() + argvalues = [row for case_group in cases for row in case_group.to_argvalues()] + ids = [f"{case_group.case_type}-{case.name}" for case_group in cases for case in case_group.cases] + return pytest.mark.parametrize(argnames=argnames, argvalues=argvalues, ids=ids) + + +@dataclass +class NegativeCases(Cases[NegativeCase]): + def __init__(self, *args: NegativeCase, case_type: str): + self.case_type = case_type + super().__init__(*args, case_type=case_type) + + @classmethod + def to_argnames(cls) -> tuple[str, str]: + return ("data", "error") + + def to_argvalues(self) -> list[tuple[dict[str, Any], type[Exception]]]: + return [(case.data, case.error) for case in self.cases] + + @classmethod + def parametrize(cls, cases: list[Self]) -> MarkDecorator: + argnames = cls.to_argnames() + argvalues = [row for case_group in cases for row in case_group.to_argvalues()] + ids = [f"{case_group.case_type}-{case.name}" for case_group in cases for case in case_group.cases] + return pytest.mark.parametrize(argnames=argnames, argvalues=argvalues, ids=ids) + + +# Add null test cases +NULL_CASES: PositiveCases = PositiveCases( + Case(name="value", data={"null_key": None}, json='{"null_key": null}'), + Case(name="list", data={"str_key": [None, None, None]}, json='{"str_key": [null, null, null]}'), + Case( + name="nested-dict-values", + data={"str_key": {"str_key_2": None, "str_key_3": None}}, + json='{"str_key": {"str_key_2": null, "str_key_3": null}}', + ), + Case( + name="nested-dict-keys", + data={"str_key": {"str_key_2": {None: "str_value"}}}, + json='{"str_key": {"str_key_2": {"null": "str_value"}}}', + round_trip={"str_key": {"str_key_2": {"null": "str_value"}}}, + ), + Case(name="no-implicit-serialization-in-keys", data={"null": "str_value"}, json='{"null": "str_value"}'), + Case(name="no-implicit-serialization-in-values", data={"str_key": "null"}, json='{"str_key": "null"}'), + Case(name="implicit-serialization-of-null-key", data={None: True}, json='{"null": true}', round_trip={"null": True}), # type: ignore + case_type="null", ) -TIME_CASE: Case = Case( - name="time", - data={"time_key": FIXED_TIME.isoformat()}, - json='{"time_key": "00:00:00"}', + +NEGATIVE_NULL_CASES: NegativeCases = NegativeCases(case_type="null") + +BOOLEAN_CASES: PositiveCases = PositiveCases( + Case(name="value-true", data={"bool_true_key": True}, json='{"bool_true_key": true}'), + Case(name="value-false", data={"bool_false_key": False}, json='{"bool_false_key": false}'), + Case(name="list", data={"str_key": [True, False, True]}, json='{"str_key": [true, false, true]}'), + Case( + name="nested-dict-values", + data={"str_key": {"str_key_2": True, "str_key_3": False}}, + json='{"str_key": {"str_key_2": true, "str_key_3": false}}', + ), + Case( + name="nested-dict-keys", + data={"str_key": {"str_key_2": {True: "str_value"}}}, + json='{"str_key": {"str_key_2": {"true": "str_value"}}}', + round_trip={"str_key": {"str_key_2": {"true": "str_value"}}}, + ), + Case(name="no-implicit-serialization-in-keys", data={"true": "str_value"}, json='{"true": "str_value"}'), + Case(name="no-implicit-serialization-in-values", data={"str_key": "true"}, json='{"str_key": "true"}'), + Case(name="implicit-serialization-of-true-key", data={True: True}, json='{"true": true}', round_trip={"true": True}), # type: ignore + Case(name="implicit-serialization-of-false-key", data={False: True}, json='{"false": true}', round_trip={"false": True}), # type: ignore + case_type="boolean", ) -# UUID string case (tests string handling, not UUID object handling) -# Note: Actual UUID objects are tested in negative test cases (base.py) -UUID_CASE: Case = Case( - name="uuid", - data={"uuid_key": str(FIXED_UUID)}, - json='{"uuid_key": "12345678-1234-5678-1234-567812345678"}', +NEGATIVE_BOOLEAN_CASES: NegativeCases = NegativeCases( + case_type="boolean", ) -# Base64-encoded string case (tests string handling, not bytes object handling) -# Note: Actual bytes objects are tested in negative test cases (base.py) -BYTES_VALUE = b"hello world" -BYTES_CASE: Case = Case( - name="bytes", - data={"bytes_key": base64.b64encode(BYTES_VALUE).decode("ascii")}, - json=f'{{"bytes_key": "{base64.b64encode(BYTES_VALUE).decode("ascii")}"}}', +INTEGER_CASES: PositiveCases = PositiveCases( + Case(name="int", data={"int_key": 1}, json='{"int_key": 1}'), + Case(name="value-negative", data={"negative_int_key": -42}, json='{"negative_int_key": -42}'), + Case(name="large-value", data={"large_int_key": 1 * 10**18}, json=f'{{"large_int_key": {1 * 10**18}}}'), + Case(name="key", data={1: True}, json='{"1": true}', round_trip={"1": True}), # type: ignore + Case(name="no-implicit-serialization-in-keys", data={"1": "str_value"}, json='{"1": "str_value"}'), + Case(name="no-implicit-serialization-in-values", data={"str_key": "1"}, json='{"str_key": "1"}'), + case_type="integer", ) -# List case (tests list handling, not tuple object handling) -# Note: Actual tuple objects are tested in negative test cases (base.py) -TUPLE_CASE: Case = Case( - name="tuple", - data={"tuple_key": [1, "two", 3.0]}, - json='{"tuple_key": [1, "two", 3.0]}', +NEGATIVE_INTEGER_CASES: NegativeCases = NegativeCases( + case_type="integer", ) -# List case (tests list handling, not set object handling) -# Note: Actual set objects are tested in negative test cases (base.py) -SET_CASE: Case = Case( - name="set", - data={"set_key": [1, 2, 3]}, - json='{"set_key": [1, 2, 3]}', +FLOAT_CASES: PositiveCases = PositiveCases( + Case(name="value", data={"float_key": 1.0}, json='{"float_key": 1.0}'), + Case(name="large-value", data={"large_float_key": 1.0 * 10**63}, json=f'{{"large_float_key": {1.0 * 10**63}}}'), + Case(name="no-implicit-serialization-in-keys", data={"1.0": "str_value"}, json='{"1.0": "str_value"}'), + Case(name="no-implicit-serialization-in-values", data={"str_key": "1.0"}, json='{"str_key": "1.0"}'), + Case(name="implicit-serialization-of-float-key", data={1.0: True}, json='{"1.0": true}', round_trip={"1.0": True}), # type: ignore + case_type="float", ) -# Deeply nested structure cases -DEEP_NESTED_CASE: Case = Case( - name="deep-nested", - data={"level1": {"level2": {"level3": {"level4": {"value": "deep"}}}}}, - json='{"level1": {"level2": {"level3": {"level4": {"value": "deep"}}}}}', +NEGATIVE_FLOAT_CASES: NegativeCases = NegativeCases( + case_type="float", ) -NESTED_LIST_DICT_CASE: Case = Case( - name="nested-list-dict", - data={"items": [{"id": 1, "name": "item1"}, {"id": 2, "name": "item2"}]}, - json='{"items": [{"id": 1, "name": "item1"}, {"id": 2, "name": "item2"}]}', +STRING_CASES: PositiveCases = PositiveCases( + Case(name="string", data={"string_key": "string_value"}, json='{"string_key": "string_value"}'), + Case(name="large-value", data={"large_string_key": "a" * 10000}, json=f'{{"large_string_key": "{"a" * 10000}"}}'), + Case( + name="unicode", + data={"unicode_key": "Hello 世界 🌍 émojis"}, + json='{"unicode_key": "Hello \\u4e16\\u754c \\ud83c\\udf0d \\u00e9mojis"}', + ), + Case(name="special-chars", data={"special_key": "Line1\nLine2\tTabbed"}, json='{"special_key": "Line1\\nLine2\\tTabbed"}'), + case_type="string", ) -NESTED_DICT_LIST_CASE: Case = Case( - name="nested-dict-list", - data={"categories": {"fruits": ["apple", "banana"], "vegetables": ["carrot", "broccoli"]}}, - json='{"categories": {"fruits": ["apple", "banana"], "vegetables": ["carrot", "broccoli"]}}', + +DATETIME_CASES: PositiveCases = PositiveCases(case_type="datetime") + +NEGATIVE_DATETIME_CASES: NegativeCases = NegativeCases( + NegativeCase(name="datetime-key", data={FIXED_DATETIME: True}), # type: ignore + NegativeCase(name="datetime-value", data={"str_key": FIXED_DATETIME}), + NegativeCase(name="date-key", data={FIXED_DATETIME.date(): True}), # type: ignore + NegativeCase(name="date-value", data={"str_key": FIXED_DATETIME.date()}), + NegativeCase(name="time-key", data={FIXED_TIME: True}), # type: ignore + NegativeCase(name="time-value", data={"str_key": FIXED_TIME}), + case_type="datetime", ) -MIXED_NESTED_CASE: Case = Case( - name="mixed-nested", - data={"outer": {"inner": [{"nested": [1, 2, 3]}]}}, - json='{"outer": {"inner": [{"nested": [1, 2, 3]}]}}', +UUID_CASES: PositiveCases = PositiveCases( + Case( + name="no-implicit-conversion-in-keys", + data={"12345678-1234-5678-1234-567812345678": "str_value"}, + json='{"12345678-1234-5678-1234-567812345678": "str_value"}', + ), + Case( + name="no-implicit-conversion-in-values", + data={"str_key": "12345678-1234-5678-1234-567812345678"}, + json='{"str_key": "12345678-1234-5678-1234-567812345678"}', + ), + case_type="uuid", ) -# Edge cases -EMPTY_STRING_CASE: Case = Case( - name="empty-string", - data={"empty_key": ""}, - json='{"empty_key": ""}', +NEGATIVE_UUID_CASES: NegativeCases = NegativeCases( + NegativeCase(name="key", data={FIXED_UUID: True}), # type: ignore + NegativeCase(name="value", data={"str_key": FIXED_UUID}), + case_type="uuid", ) -EMPTY_LIST_CASE: Case = Case( - name="empty-list", - data={"empty_list_key": []}, - json='{"empty_list_key": []}', +B_HELLO_WORLD: bytes = b"hello world" +B64_HELLO_WORLD: str = base64.b64encode(B_HELLO_WORLD).decode(encoding="ascii") + +BYTES_CASES: PositiveCases = PositiveCases( + Case(name="no-implicit-conversion-in-keys", data={B64_HELLO_WORLD: "str_value"}, json=f'{{"bytes_key": "{B64_HELLO_WORLD}"}}'), + Case(name="no-implicit-conversion-in-values", data={"str_key": B64_HELLO_WORLD}, json=f'{{"str_key": "{B64_HELLO_WORLD}"}}'), + case_type="bytes", ) -EMPTY_DICT_CASE: Case = Case( - name="empty-dict", - data={"empty_dict_key": {}}, - json='{"empty_dict_key": {}}', +NEGATIVE_BYTES_CASES: NegativeCases = NegativeCases( + NegativeCase(name="bytes-key", data={B_HELLO_WORLD: True}), # type: ignore + NegativeCase(name="bytes-value", data={"str_key": B_HELLO_WORLD}), + case_type="bytes", ) -UNICODE_CASE: Case = Case( - name="unicode", - data={"unicode_key": "Hello 世界 🌍 émojis"}, - json='{"unicode_key": "Hello \\u4e16\\u754c \\ud83c\\udf0d \\u00e9mojis"}', +SAMPLE_TUPLE: tuple[int, str, float] = (1, "two", 3.0) + +TUPLE_CASES: PositiveCases = PositiveCases( + Case(name="tuple", data={"tuple_key": SAMPLE_TUPLE}, json='{"tuple_key": [1, "two", 3.0]}'), + Case( + name="large-tuple", + data={"large_tuple_key": (1, "two", 3.0) * 1000}, + json=f'{{"large_tuple_key": {json.dumps((1, "two", 3.0) * 1000)}}}', + ), + case_type="tuple", ) -SPECIAL_CHARS_CASE: Case = Case( - name="special-chars", - data={"special_key": "Line1\nLine2\tTabbed"}, - json='{"special_key": "Line1\\nLine2\\tTabbed"}', +NEGATIVE_TUPLE_CASES: NegativeCases = NegativeCases( + NegativeCase(name="key", data={SAMPLE_TUPLE: True}), # type: ignore + NegativeCase(name="value", data={"str_key": SAMPLE_TUPLE}), + case_type="tuple", ) -# Zero values -ZERO_INT_CASE: Case = Case( - name="zero-int", - data={"zero_int_key": 0}, - json='{"zero_int_key": 0}', +SAMPLE_SET: set[int] = {1, 2, 3} + +SET_CASES: PositiveCases = PositiveCases(case_type="set") + +NEGATIVE_SET_CASES: NegativeCases = NegativeCases( + NegativeCase(name="small", data={"str_key": SAMPLE_SET}), + NegativeCase(name="large", data={"large_set_key": set(range(1000))}), + case_type="set", ) -ZERO_FLOAT_CASE: Case = Case( - name="zero-float", - data={"zero_float_key": 0.0}, - json='{"zero_float_key": 0.0}', +DEEP_NESTED_CASES: PositiveCases = PositiveCases( + Case( + name="simple", + data={"level1": {"level2": {"level3": {"level4": {"value": "deep"}}}}}, + json='{"level1": {"level2": {"level3": {"level4": {"value": "deep"}}}}}', + ), + case_type="deep-nested", ) -NEGATIVE_INT_CASE: Case = Case( - name="negative-int", - data={"negative_int_key": -42}, - json='{"negative_int_key": -42}', +NEGATIVE_DEEP_NESTED_CASES: NegativeCases = NegativeCases(case_type="deep-nested") + +NESTED_LIST_DICT_CASES: PositiveCases = PositiveCases( + Case( + name="nested-list-dict", + data={"items": [{"id": 1, "name": "item1"}, {"id": 2, "name": "item2"}]}, + json='{"items": [{"id": 1, "name": "item1"}, {"id": 2, "name": "item2"}]}', + ), + Case( + name="large-nested-list-dict", + data={"items": [{"id": 1, "name": "item1"}, {"id": 2, "name": "item2"}] * 1000}, + json=f'{{"items": {json.dumps([{"id": 1, "name": "item1"}, {"id": 2, "name": "item2"}] * 1000)}}}', + ), + case_type="nested-list-dict", ) -NEGATIVE_FLOAT_CASE: Case = Case( - name="negative-float", - data={"negative_float_key": -3.14}, - json='{"negative_float_key": -3.14}', +NEGATIVE_NESTED_LIST_DICT_CASES: NegativeCases = NegativeCases(case_type="nested-list-dict") + +NESTED_DICT_LIST_CASES: PositiveCases = PositiveCases( + Case( + name="nested-dict-list", + data={"categories": {"fruits": ["apple", "banana"], "vegetables": ["carrot", "broccoli"]}}, + json='{"categories": {"fruits": ["apple", "banana"], "vegetables": ["carrot", "broccoli"]}}', + ), + case_type="nested-dict-list", ) -# Large collection case -LARGE_LIST_CASE: Case = Case( - name="large-list", - data={"large_list": list(range(1000))}, - json=f'{{"large_list": {json.dumps(list(range(1000)))}}}', +NEGATIVE_NESTED_DICT_LIST_CASES: NegativeCases = NegativeCases(case_type="nested-dict-list") + +LARGE_DATA_CASES: PositiveCases = PositiveCases( + Case( + name="string", + data={"large_string_key": "a" * 10000}, + json=f'{{"large_string_key": "{"a" * 10000}"}}', + ), + Case( + name="int", + data={"large_int_key": 1 * 10**18}, + json=f'{{"large_int_key": {1 * 10**18}}}', + ), + Case( + name="float", + data={"large_float_key": 1.0 * 10**63}, + json=f'{{"large_float_key": {1.0 * 10**63}}}', + ), + Case( + name="list", + data={"large_list_key": list(range(1000))}, + json=f'{{"large_list_key": {json.dumps(list(range(1000)))}}}', + ), + case_type="large-data", ) +NEGATIVE_LARGE_DATA_CASES: NegativeCases = NegativeCases( + NegativeCase(name="string", data={"large_string_key": "a" * 10_000_000}, error=SerializationError), + case_type="large-data", +) -TEST_CASE_DATA: list[dict[str, Any]] = [ - case.data - for case in [ - NULL_CASE, - BOOL_TRUE_CASE, - BOOL_FALSE_CASE, - INT_CASE, - LARGE_INT_CASE, - FLOAT_CASE, - LARGE_FLOAT_CASE, - STRING_CASE, - DICT_CASE_ONE, - DICT_CASE_TWO, - DICT_CASE_THREE, - LIST_CASE_ONE, - LIST_CASE_TWO, - LIST_CASE_THREE, - DATETIME_CASE, - DATE_CASE, - TIME_CASE, - UUID_CASE, - BYTES_CASE, - TUPLE_CASE, - SET_CASE, - DEEP_NESTED_CASE, - NESTED_LIST_DICT_CASE, - NESTED_DICT_LIST_CASE, - MIXED_NESTED_CASE, - EMPTY_STRING_CASE, - EMPTY_LIST_CASE, - EMPTY_DICT_CASE, - UNICODE_CASE, - SPECIAL_CHARS_CASE, - ZERO_INT_CASE, - ZERO_FLOAT_CASE, - NEGATIVE_INT_CASE, - NEGATIVE_FLOAT_CASE, - ] -] -TEST_CASE_JSON: list[str] = [ - case.json - for case in [ - NULL_CASE, - BOOL_TRUE_CASE, - BOOL_FALSE_CASE, - INT_CASE, - LARGE_INT_CASE, - FLOAT_CASE, - LARGE_FLOAT_CASE, - STRING_CASE, - DICT_CASE_ONE, - DICT_CASE_TWO, - DICT_CASE_THREE, - LIST_CASE_ONE, - LIST_CASE_TWO, - LIST_CASE_THREE, - DATETIME_CASE, - DATE_CASE, - TIME_CASE, - UUID_CASE, - BYTES_CASE, - TUPLE_CASE, - SET_CASE, - DEEP_NESTED_CASE, - NESTED_LIST_DICT_CASE, - NESTED_DICT_LIST_CASE, - MIXED_NESTED_CASE, - EMPTY_STRING_CASE, - EMPTY_LIST_CASE, - EMPTY_DICT_CASE, - UNICODE_CASE, - SPECIAL_CHARS_CASE, - ZERO_INT_CASE, - ZERO_FLOAT_CASE, - NEGATIVE_INT_CASE, - NEGATIVE_FLOAT_CASE, - ] +SIMPLE_CASES: list[PositiveCases] = [ + NULL_CASES, + BOOLEAN_CASES, + INTEGER_CASES, + FLOAT_CASES, + STRING_CASES, + DATETIME_CASES, + UUID_CASES, ] -SIMPLE_TEST_DATA_ARGNAMES: tuple[str, str] = ("data", "json") -SIMPLE_TEST_DATA_ARGVALUES: list[tuple[dict[str, Any], str]] = list(zip(TEST_CASE_DATA, TEST_CASE_JSON, strict=True)) -SIMPLE_TEST_DATA_IDS: list[str] = [ - case.name - for case in [ - NULL_CASE, - BOOL_TRUE_CASE, - BOOL_FALSE_CASE, - INT_CASE, - LARGE_INT_CASE, - FLOAT_CASE, - LARGE_FLOAT_CASE, - STRING_CASE, - DICT_CASE_ONE, - DICT_CASE_TWO, - DICT_CASE_THREE, - LIST_CASE_ONE, - LIST_CASE_TWO, - LIST_CASE_THREE, - DATETIME_CASE, - DATE_CASE, - TIME_CASE, - UUID_CASE, - BYTES_CASE, - TUPLE_CASE, - SET_CASE, - DEEP_NESTED_CASE, - NESTED_LIST_DICT_CASE, - NESTED_DICT_LIST_CASE, - MIXED_NESTED_CASE, - EMPTY_STRING_CASE, - EMPTY_LIST_CASE, - EMPTY_DICT_CASE, - UNICODE_CASE, - SPECIAL_CHARS_CASE, - ZERO_INT_CASE, - ZERO_FLOAT_CASE, - NEGATIVE_INT_CASE, - NEGATIVE_FLOAT_CASE, - ] +NEGATIVE_SIMPLE_CASES: list[NegativeCases] = [ + NEGATIVE_NULL_CASES, + NEGATIVE_BOOLEAN_CASES, + NEGATIVE_INTEGER_CASES, + NEGATIVE_FLOAT_CASES, + NEGATIVE_DATETIME_CASES, + NEGATIVE_UUID_CASES, ] -LARGE_TEST_DATA_DATA: list[dict[str, Any]] = [ - case.data - for case in [ - LARGE_STRING_CASE, - LARGE_INT_CASE, - LARGE_FLOAT_CASE, - LARGE_LIST_CASE, - ] -] -LARGE_TEST_DATA_JSON: list[str] = [ - case.json - for case in [ - LARGE_STRING_CASE, - LARGE_INT_CASE, - LARGE_FLOAT_CASE, - LARGE_LIST_CASE, - ] -] -LARGE_TEST_DATA_ARGNAMES: tuple[str, str] = ("data", "json") -LARGE_TEST_DATA_ARGVALUES: list[tuple[dict[str, Any], str]] = list(zip(LARGE_TEST_DATA_DATA, LARGE_TEST_DATA_JSON, strict=True)) -LARGE_TEST_DATA_IDS: list[str] = [ - case.name - for case in [ - LARGE_STRING_CASE, - LARGE_INT_CASE, - LARGE_FLOAT_CASE, - LARGE_LIST_CASE, - ] -] -__all__ = [ - "LARGE_TEST_DATA_ARGNAMES", - "LARGE_TEST_DATA_ARGVALUES", - "LARGE_TEST_DATA_IDS", - "SIMPLE_TEST_DATA_ARGNAMES", - "SIMPLE_TEST_DATA_ARGVALUES", - "SIMPLE_TEST_DATA_IDS", -] +# SIMPLE + +# # ISO format string cases (these test string handling, not datetime object handling) +# # Note: Actual datetime/date/time objects are tested in negative test cases (base.py) +# DATETIME_CASE: Case = Case( +# name="datetime", +# data={"datetime_key": FIXED_DATETIME.isoformat()}, +# json='{"datetime_key": "2025-01-01T00:00:00+00:00"}', +# ) +# DATE_CASE: Case = Case( +# name="date", +# data={"date_key": FIXED_DATETIME.date().isoformat()}, +# json='{"date_key": "2025-01-01"}', +# ) +# TIME_CASE: Case = Case( +# name="time", +# data={"time_key": FIXED_TIME.isoformat()}, +# json='{"time_key": "00:00:00"}', +# ) + +# # UUID string case (tests string handling, not UUID object handling) +# # Note: Actual UUID objects are tested in negative test cases (base.py) +# UUID_CASE: Case = Case( +# name="uuid", +# data={"uuid_key": str(FIXED_UUID)}, +# json='{"uuid_key": "12345678-1234-5678-1234-567812345678"}', +# ) + +# # Base64-encoded string case (tests string handling, not bytes object handling) +# # Note: Actual bytes objects are tested in negative test cases (base.py) +# BYTES_VALUE = b"hello world" +# BYTES_CASE: Case = Case( +# name="bytes", +# data={"bytes_key": base64.b64encode(BYTES_VALUE).decode("ascii")}, +# json=f'{{"bytes_key": "{base64.b64encode(BYTES_VALUE).decode("ascii")}"}}', +# ) + +# # List case (tests list handling, not tuple object handling) +# # Note: Actual tuple objects are tested in negative test cases (base.py) +# TUPLE_CASE: Case = Case( +# name="tuple", +# data={"tuple_key": [1, "two", 3.0]}, +# json='{"tuple_key": [1, "two", 3.0]}', +# ) + +# # List case (tests list handling, not set object handling) +# # Note: Actual set objects are tested in negative test cases (base.py) +# SET_CASE: Case = Case( +# name="set", +# data={"set_key": {1, 2, 3}}, +# json='{"set_key": [1, 2, 3]}', +# ) + +# # Deeply nested structure cases +# DEEP_NESTED_CASE: Case = Case( +# name="deep-nested", +# data={"level1": {"level2": {"level3": {"level4": {"value": "deep"}}}}}, +# json='{"level1": {"level2": {"level3": {"level4": {"value": "deep"}}}}}', +# ) + +# NESTED_LIST_DICT_CASE: Case = Case( +# name="nested-list-dict", +# data={"items": [{"id": 1, "name": "item1"}, {"id": 2, "name": "item2"}]}, +# json='{"items": [{"id": 1, "name": "item1"}, {"id": 2, "name": "item2"}]}', +# ) + +# NESTED_DICT_LIST_CASE: Case = Case( +# name="nested-dict-list", +# data={"categories": {"fruits": ["apple", "banana"], "vegetables": ["carrot", "broccoli"]}}, +# json='{"categories": {"fruits": ["apple", "banana"], "vegetables": ["carrot", "broccoli"]}}', +# ) + +# MIXED_NESTED_CASE: Case = Case( +# name="mixed-nested", +# data={"outer": {"inner": [{"nested": [1, 2, 3]}]}}, +# json='{"outer": {"inner": [{"nested": [1, 2, 3]}]}}', +# ) + +# # Edge cases +# EMPTY_STRING_CASE: Case = Case( +# name="empty-string", +# data={"empty_key": ""}, +# json='{"empty_key": ""}', +# ) + +# EMPTY_LIST_CASE: Case = Case( +# name="empty-list", +# data={"empty_list_key": []}, +# json='{"empty_list_key": []}', +# ) + +# EMPTY_DICT_CASE: Case = Case( +# name="empty-dict", +# data={"empty_dict_key": {}}, +# json='{"empty_dict_key": {}}', +# ) + +# UNICODE_CASE: Case = Case( +# name="unicode", +# data={"unicode_key": "Hello 世界 🌍 émojis"}, +# json='{"unicode_key": "Hello \\u4e16\\u754c \\ud83c\\udf0d \\u00e9mojis"}', +# ) + +# SPECIAL_CHARS_CASE: Case = Case( +# name="special-chars", +# data={"special_key": "Line1\nLine2\tTabbed"}, +# json='{"special_key": "Line1\\nLine2\\tTabbed"}', +# ) + +# # Zero values +# ZERO_INT_CASE: Case = Case( +# name="zero-int", +# data={"zero_int_key": 0}, +# json='{"zero_int_key": 0}', +# ) + +# ZERO_FLOAT_CASE: Case = Case( +# name="zero-float", +# data={"zero_float_key": 0.0}, +# json='{"zero_float_key": 0.0}', +# ) + +# NEGATIVE_INT_CASE: Case = Case( +# name="negative-int", +# data={"negative_int_key": -42}, +# json='{"negative_int_key": -42}', +# ) + +# NEGATIVE_FLOAT_CASE: Case = Case( +# name="negative-float", +# data={"negative_float_key": -3.14}, +# json='{"negative_float_key": -3.14}', +# ) + +# # Large collection case +# LARGE_LIST_CASE: Case = Case( +# name="large-list", +# data={"large_list": list(range(1000))}, +# json=f'{{"large_list": {json.dumps(list(range(1000)))}}}', +# ) + + +# TEST_CASE_DATA: list[dict[str, Any]] = [ +# case.data +# for case in [ +# NULL_CASE, +# BOOL_TRUE_CASE, +# BOOL_FALSE_CASE, +# INT_CASE, +# LARGE_INT_CASE, +# FLOAT_CASE, +# LARGE_FLOAT_CASE, +# STRING_CASE, +# DICT_CASE_ONE, +# DICT_CASE_TWO, +# DICT_CASE_THREE, +# LIST_CASE_ONE, +# LIST_CASE_TWO, +# LIST_CASE_THREE, +# DATETIME_CASE, +# DATE_CASE, +# TIME_CASE, +# UUID_CASE, +# BYTES_CASE, +# TUPLE_CASE, +# SET_CASE, +# DEEP_NESTED_CASE, +# NESTED_LIST_DICT_CASE, +# NESTED_DICT_LIST_CASE, +# MIXED_NESTED_CASE, +# EMPTY_STRING_CASE, +# EMPTY_LIST_CASE, +# EMPTY_DICT_CASE, +# UNICODE_CASE, +# SPECIAL_CHARS_CASE, +# ZERO_INT_CASE, +# ZERO_FLOAT_CASE, +# NEGATIVE_INT_CASE, +# NEGATIVE_FLOAT_CASE, +# ] +# ] +# TEST_CASE_JSON: list[str] = [ +# case.json +# for case in [ +# NULL_CASE, +# BOOL_TRUE_CASE, +# BOOL_FALSE_CASE, +# INT_CASE, +# LARGE_INT_CASE, +# FLOAT_CASE, +# LARGE_FLOAT_CASE, +# STRING_CASE, +# DICT_CASE_ONE, +# DICT_CASE_TWO, +# DICT_CASE_THREE, +# LIST_CASE_ONE, +# LIST_CASE_TWO, +# LIST_CASE_THREE, +# DATETIME_CASE, +# DATE_CASE, +# TIME_CASE, +# UUID_CASE, +# BYTES_CASE, +# TUPLE_CASE, +# SET_CASE, +# DEEP_NESTED_CASE, +# NESTED_LIST_DICT_CASE, +# NESTED_DICT_LIST_CASE, +# MIXED_NESTED_CASE, +# EMPTY_STRING_CASE, +# EMPTY_LIST_CASE, +# EMPTY_DICT_CASE, +# UNICODE_CASE, +# SPECIAL_CHARS_CASE, +# ZERO_INT_CASE, +# ZERO_FLOAT_CASE, +# NEGATIVE_INT_CASE, +# NEGATIVE_FLOAT_CASE, +# ] +# ] + +# SIMPLE_TEST_DATA_ARGNAMES: tuple[str, str] = ("data", "json") +# SIMPLE_TEST_DATA_ARGVALUES: list[tuple[dict[str, Any], str]] = list(zip(TEST_CASE_DATA, TEST_CASE_JSON, strict=True)) +# SIMPLE_TEST_DATA_IDS: list[str] = [ +# case.name +# for case in [ +# NULL_CASE, +# BOOL_TRUE_CASE, +# BOOL_FALSE_CASE, +# INT_CASE, +# LARGE_INT_CASE, +# FLOAT_CASE, +# LARGE_FLOAT_CASE, +# STRING_CASE, +# DICT_CASE_ONE, +# DICT_CASE_TWO, +# DICT_CASE_THREE, +# LIST_CASE_ONE, +# LIST_CASE_TWO, +# LIST_CASE_THREE, +# DATETIME_CASE, +# DATE_CASE, +# TIME_CASE, +# UUID_CASE, +# BYTES_CASE, +# TUPLE_CASE, +# SET_CASE, +# DEEP_NESTED_CASE, +# NESTED_LIST_DICT_CASE, +# NESTED_DICT_LIST_CASE, +# MIXED_NESTED_CASE, +# EMPTY_STRING_CASE, +# EMPTY_LIST_CASE, +# EMPTY_DICT_CASE, +# UNICODE_CASE, +# SPECIAL_CHARS_CASE, +# ZERO_INT_CASE, +# ZERO_FLOAT_CASE, +# NEGATIVE_INT_CASE, +# NEGATIVE_FLOAT_CASE, +# ] +# ] + +# LARGE_TEST_DATA_DATA: list[dict[str, Any]] = [ +# case.data +# for case in [ +# LARGE_STRING_CASE, +# LARGE_INT_CASE, +# LARGE_FLOAT_CASE, +# LARGE_LIST_CASE, +# ] +# ] +# LARGE_TEST_DATA_JSON: list[str] = [ +# case.json +# for case in [ +# LARGE_STRING_CASE, +# LARGE_INT_CASE, +# LARGE_FLOAT_CASE, +# LARGE_LIST_CASE, +# ] +# ] +# LARGE_TEST_DATA_ARGNAMES: tuple[str, str] = ("data", "json") +# LARGE_TEST_DATA_ARGVALUES: list[tuple[dict[str, Any], str]] = list(zip(LARGE_TEST_DATA_DATA, LARGE_TEST_DATA_JSON, strict=True)) +# LARGE_TEST_DATA_IDS: list[str] = [ +# case.name +# for case in [ +# LARGE_STRING_CASE, +# LARGE_INT_CASE, +# LARGE_FLOAT_CASE, +# LARGE_LIST_CASE, +# ] +# ] + +# NEGATIVE_TEST_DATA_DATA: list[dict[str, Any]] = [ +# case.data +# for case in [ +# DATETIME_CASE, +# DATE_CASE, +# TIME_CASE, +# UUID_CASE, +# ] +# ] + +# NEGATIVE_TEST_DATA_JSON: list[str] = [ +# case.json +# for case in [ +# DATETIME_CASE, +# DATE_CASE, +# TIME_CASE, +# UUID_CASE, +# ] +# ] +# NEGATIVE_TEST_DATA_ARGNAMES: tuple[str, str] = ("data", "json") +# NEGATIVE_TEST_DATA_ARGVALUES: list[tuple[dict[str, Any], str]] = list(zip(NEGATIVE_TEST_DATA_DATA, NEGATIVE_TEST_DATA_JSON, strict=True)) +# NEGATIVE_TEST_DATA_IDS: list[str] = [ +# case.name +# for case in [ +# DATETIME_CASE, +# DATE_CASE, +# TIME_CASE, +# UUID_CASE, +# ] +# ] + +# __all__ = [ +# "LARGE_TEST_DATA_ARGNAMES", +# "LARGE_TEST_DATA_ARGVALUES", +# "LARGE_TEST_DATA_IDS", +# "SIMPLE_TEST_DATA_ARGNAMES", +# "SIMPLE_TEST_DATA_ARGVALUES", +# "SIMPLE_TEST_DATA_IDS", +# ] diff --git a/key-value/key-value-shared/src/key_value/shared/code_gen/gather.py b/key-value/key-value-shared/src/key_value/shared/code_gen/gather.py index dc159553..75ce8ad7 100644 --- a/key-value/key-value-shared/src/key_value/shared/code_gen/gather.py +++ b/key-value/key-value-shared/src/key_value/shared/code_gen/gather.py @@ -10,7 +10,7 @@ async def async_gather(*aws: Awaitable[Any], return_exceptions: bool = False) -> return await asyncio.gather(*aws, return_exceptions=return_exceptions) -def gather(*args: Any, **kwargs: Any) -> tuple[Any, ...]: # noqa: ARG001 # pyright: ignore[reportUnusedParameter] +def gather(*args: Any, **kwargs: Any) -> tuple[Any, ...]: # pyright: ignore[reportUnusedParameter] """ Equivalent to asyncio.gather(), converted to asyncio.gather() by async_to_sync. """ diff --git a/key-value/key-value-shared/tests/utils/test_managed_entry.py b/key-value/key-value-shared/tests/utils/test_managed_entry.py index 00304615..a7d91776 100644 --- a/key-value/key-value-shared/tests/utils/test_managed_entry.py +++ b/key-value/key-value-shared/tests/utils/test_managed_entry.py @@ -1,8 +1,7 @@ from datetime import datetime, timezone from typing import Any -import pytest -from key_value.shared_test.cases import SIMPLE_TEST_DATA_ARGNAMES, SIMPLE_TEST_DATA_ARGVALUES, SIMPLE_TEST_DATA_IDS +from key_value.shared_test.cases import SIMPLE_CASES, PositiveCases from key_value.shared.utils.managed_entry import dump_to_json, load_from_json @@ -10,30 +9,18 @@ FIXED_DATETIME_STRING = FIXED_DATETIME.isoformat() -@pytest.mark.parametrize( - argnames=SIMPLE_TEST_DATA_ARGNAMES, - argvalues=SIMPLE_TEST_DATA_ARGVALUES, - ids=SIMPLE_TEST_DATA_IDS, -) -def test_dump_to_json(data: dict[str, Any], json: str): +@PositiveCases.parametrize(cases=SIMPLE_CASES) +def test_dump_to_json(data: dict[str, Any], json: str, round_trip: dict[str, Any]): assert dump_to_json(data) == json -@pytest.mark.parametrize( - argnames=SIMPLE_TEST_DATA_ARGNAMES, - argvalues=SIMPLE_TEST_DATA_ARGVALUES, - ids=SIMPLE_TEST_DATA_IDS, -) -def test_load_from_json(data: dict[str, Any], json: str): +@SIMPLE_CASES.parametrize() +def test_load_from_json(data: dict[str, Any], json: str, round_trip: dict[str, Any]): assert load_from_json(json) == data -@pytest.mark.parametrize( - argnames=SIMPLE_TEST_DATA_ARGNAMES, - argvalues=SIMPLE_TEST_DATA_ARGVALUES, - ids=SIMPLE_TEST_DATA_IDS, -) -def test_roundtrip_json(data: dict[str, Any], json: str): +@SIMPLE_CASES.parametrize() +def test_roundtrip_json(data: dict[str, Any], json: str, round_trip: dict[str, Any]): dumped_json: str = dump_to_json(data) assert dumped_json == json - assert load_from_json(dumped_json) == data + assert load_from_json(dumped_json) == round_trip diff --git a/key-value/key-value-sync/src/key_value/sync/code_gen/stores/memory/store.py b/key-value/key-value-sync/src/key_value/sync/code_gen/stores/memory/store.py index 5a531a9e..a5cca47d 100644 --- a/key-value/key-value-sync/src/key_value/sync/code_gen/stores/memory/store.py +++ b/key-value/key-value-sync/src/key_value/sync/code_gen/stores/memory/store.py @@ -52,7 +52,7 @@ def _memory_cache_ttu(_key: Any, value: MemoryCacheEntry, now: float) -> float: return float(expiration_epoch) -def _memory_cache_getsizeof(value: MemoryCacheEntry) -> int: # pyright: ignore[reportUnusedParameter] # noqa: ARG001 +def _memory_cache_getsizeof(value: MemoryCacheEntry) -> int: # pyright: ignore[reportUnusedParameter] "Return size of cache entry (always 1 for entry counting)." return 1 diff --git a/key-value/key-value-sync/tests/code_gen/stores/base.py b/key-value/key-value-sync/tests/code_gen/stores/base.py index 6fb27b75..faf0936a 100644 --- a/key-value/key-value-sync/tests/code_gen/stores/base.py +++ b/key-value/key-value-sync/tests/code_gen/stores/base.py @@ -11,14 +11,7 @@ from key_value.shared.code_gen.gather import gather from key_value.shared.code_gen.sleep import sleep from key_value.shared.errors import InvalidTTLError, SerializationError -from key_value.shared_test.cases import ( - LARGE_TEST_DATA_ARGNAMES, - LARGE_TEST_DATA_ARGVALUES, - LARGE_TEST_DATA_IDS, - SIMPLE_TEST_DATA_ARGNAMES, - SIMPLE_TEST_DATA_ARGVALUES, - SIMPLE_TEST_DATA_IDS, -) +from key_value.shared_test.cases import LARGE_DATA_CASES, NEGATIVE_SIMPLE_CASES, SIMPLE_CASES, NegativeCases, PositiveCases from pydantic import AnyHttpUrl from key_value.sync.code_gen.stores.base import BaseContextManagerStore, BaseStore @@ -50,7 +43,7 @@ def test_empty_ttl(self, store: BaseStore): assert ttl == (None, None) def test_put_serialization_errors(self, store: BaseStore): - """Tests that the put method does not raise an exception when called on a new store.""" + """Tests that the put method raises SerializationError for non-JSON-serializable Pydantic types.""" with pytest.raises(SerializationError): store.put(collection="test", key="test", value={"test": AnyHttpUrl("https://test.com")}) @@ -59,15 +52,22 @@ def test_get_put_get(self, store: BaseStore): store.put(collection="test", key="test", value={"test": "test"}) assert store.get(collection="test", key="test") == {"test": "test"} - @pytest.mark.parametrize(argnames=SIMPLE_TEST_DATA_ARGNAMES, argvalues=SIMPLE_TEST_DATA_ARGVALUES, ids=SIMPLE_TEST_DATA_IDS) - def test_get_complex_put_get(self, store: BaseStore, data: dict[str, Any], json: str): # pyright: ignore[reportUnusedParameter, reportUnusedParameter] # noqa: ARG002 + @PositiveCases.parametrize(cases=SIMPLE_CASES) + def test_models_put_get(self, store: BaseStore, data: dict[str, Any], json: str, round_trip: dict[str, Any]): # pyright: ignore[reportUnusedParameter, reportUnusedParameter] # noqa: ARG002 store.put(collection="test", key="test", value=data) - assert store.get(collection="test", key="test") == data + retrieved_data = store.get(collection="test", key="test") + assert retrieved_data is not None + assert retrieved_data == round_trip + + @NegativeCases.parametrize(cases=NEGATIVE_SIMPLE_CASES) + def test_negative_models_put_get(self, store: BaseStore, data: dict[str, Any], error: type[Exception]): # pyright: ignore[reportUnusedParameter, reportUnusedParameter] + with pytest.raises(error): + store.put(collection="test", key="test", value=data) - @pytest.mark.parametrize(argnames=LARGE_TEST_DATA_ARGNAMES, argvalues=LARGE_TEST_DATA_ARGVALUES, ids=LARGE_TEST_DATA_IDS) - def test_get_large_put_get(self, store: BaseStore, data: dict[str, Any], json: str): # pyright: ignore[reportUnusedParameter, reportUnusedParameter] # noqa: ARG002 + @PositiveCases.parametrize(cases=[LARGE_DATA_CASES]) + def test_get_large_put_get(self, store: BaseStore, data: dict[str, Any], json: str, round_trip: dict[str, Any]): # pyright: ignore[reportUnusedParameter, reportUnusedParameter] # noqa: ARG002 store.put(collection="test", key="test", value=data) - assert store.get(collection="test", key="test") == data + assert store.get(collection="test", key="test") == round_trip def test_put_many_get(self, store: BaseStore): store.put_many(collection="test", keys=["test", "test_2"], values=[{"test": "test"}, {"test": "test_2"}]) diff --git a/key-value/key-value-sync/tests/code_gen/stores/keyring/test_keyring.py b/key-value/key-value-sync/tests/code_gen/stores/keyring/test_keyring.py index d2517831..4466b09a 100644 --- a/key-value/key-value-sync/tests/code_gen/stores/keyring/test_keyring.py +++ b/key-value/key-value-sync/tests/code_gen/stores/keyring/test_keyring.py @@ -4,7 +4,6 @@ from typing import Any import pytest -from key_value.shared_test.cases import LARGE_TEST_DATA_ARGNAMES, LARGE_TEST_DATA_ARGVALUES, LARGE_TEST_DATA_IDS from typing_extensions import override from key_value.sync.code_gen.stores.base import BaseStore @@ -31,7 +30,6 @@ def test_not_unbounded(self, store: BaseStore): ... @override @pytest.mark.skipif(condition=detect_on_windows(), reason="Keyrings do not support large values on Windows") - @pytest.mark.parametrize(argnames=LARGE_TEST_DATA_ARGNAMES, argvalues=LARGE_TEST_DATA_ARGVALUES, ids=LARGE_TEST_DATA_IDS) def test_get_large_put_get(self, store: BaseStore, data: dict[str, Any], json: str): store.put(collection="test", key="test", value=data) assert store.get(collection="test", key="test") == data diff --git a/key-value/key-value-sync/tests/code_gen/stores/windows_registry/test_windows_registry.py b/key-value/key-value-sync/tests/code_gen/stores/windows_registry/test_windows_registry.py index 3ca0b2e9..5c25631d 100644 --- a/key-value/key-value-sync/tests/code_gen/stores/windows_registry/test_windows_registry.py +++ b/key-value/key-value-sync/tests/code_gen/stores/windows_registry/test_windows_registry.py @@ -4,7 +4,6 @@ from typing import TYPE_CHECKING, Any import pytest -from key_value.shared_test.cases import LARGE_TEST_DATA_ARGNAMES, LARGE_TEST_DATA_ARGVALUES, LARGE_TEST_DATA_IDS from typing_extensions import override from key_value.sync.code_gen.stores.base import BaseStore @@ -34,7 +33,6 @@ def store(self) -> "WindowsRegistryStore": def test_not_unbounded(self, store: BaseStore): ... @override - @pytest.mark.parametrize(argnames=LARGE_TEST_DATA_ARGNAMES, argvalues=LARGE_TEST_DATA_ARGVALUES, ids=LARGE_TEST_DATA_IDS) def test_get_large_put_get(self, store: BaseStore, data: dict[str, Any], json: str): store.put(collection="test", key="test", value=data) assert store.get(collection="test", key="test") == data diff --git a/pyproject.toml b/pyproject.toml index 5f4dac64..b24cbf71 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,6 +21,7 @@ lint.fixable = ["ALL"] lint.ignore = [ "COM812", "PLR0913", # Too many arguments, MCP Servers have a lot of arguments, OKAY?! + "ARG001", # Unused argument, Pyright captures this for us ] lint.extend-select = [ "A", diff --git a/scripts/build_sync_library.py b/scripts/build_sync_library.py index e8c20656..052f7be6 100644 --- a/scripts/build_sync_library.py +++ b/scripts/build_sync_library.py @@ -122,7 +122,7 @@ def convert(source_path: Path, output_path: Path) -> None: print(output, file=f) -def async_to_sync(tree: ast.AST, filepath: Path | None = None) -> ast.AST: # noqa: ARG001 +def async_to_sync(tree: ast.AST, filepath: Path | None = None) -> ast.AST: tree = BlanksInserter().visit(tree) tree = RenameAsyncToSync().visit(tree) tree = AsyncToSync().visit(tree) From fcdcda1e6ac6b59c92fb1c8792e35391f11cfe24 Mon Sep 17 00:00:00 2001 From: William Easton Date: Sat, 25 Oct 2025 23:40:59 -0500 Subject: [PATCH 05/11] PR Feedback --- .../src/key_value/shared_test/cases.py | 336 +----------------- 1 file changed, 1 insertion(+), 335 deletions(-) diff --git a/key-value/key-value-shared-test/src/key_value/shared_test/cases.py b/key-value/key-value-shared-test/src/key_value/shared_test/cases.py index 9f89b4c3..7342b9ed 100644 --- a/key-value/key-value-shared-test/src/key_value/shared_test/cases.py +++ b/key-value/key-value-shared-test/src/key_value/shared_test/cases.py @@ -222,7 +222,7 @@ def parametrize(cls, cases: list[Self]) -> MarkDecorator: B64_HELLO_WORLD: str = base64.b64encode(B_HELLO_WORLD).decode(encoding="ascii") BYTES_CASES: PositiveCases = PositiveCases( - Case(name="no-implicit-conversion-in-keys", data={B64_HELLO_WORLD: "str_value"}, json=f'{{"bytes_key": "{B64_HELLO_WORLD}"}}'), + Case(name="no-implicit-conversion-in-keys", data={B64_HELLO_WORLD: "str_value"}, json=f'{{"{B64_HELLO_WORLD}": "str_value"}}'), Case(name="no-implicit-conversion-in-values", data={"str_key": B64_HELLO_WORLD}, json=f'{{"str_key": "{B64_HELLO_WORLD}"}}'), case_type="bytes", ) @@ -346,337 +346,3 @@ def parametrize(cls, cases: list[Self]) -> MarkDecorator: NEGATIVE_DATETIME_CASES, NEGATIVE_UUID_CASES, ] - - -# SIMPLE - -# # ISO format string cases (these test string handling, not datetime object handling) -# # Note: Actual datetime/date/time objects are tested in negative test cases (base.py) -# DATETIME_CASE: Case = Case( -# name="datetime", -# data={"datetime_key": FIXED_DATETIME.isoformat()}, -# json='{"datetime_key": "2025-01-01T00:00:00+00:00"}', -# ) -# DATE_CASE: Case = Case( -# name="date", -# data={"date_key": FIXED_DATETIME.date().isoformat()}, -# json='{"date_key": "2025-01-01"}', -# ) -# TIME_CASE: Case = Case( -# name="time", -# data={"time_key": FIXED_TIME.isoformat()}, -# json='{"time_key": "00:00:00"}', -# ) - -# # UUID string case (tests string handling, not UUID object handling) -# # Note: Actual UUID objects are tested in negative test cases (base.py) -# UUID_CASE: Case = Case( -# name="uuid", -# data={"uuid_key": str(FIXED_UUID)}, -# json='{"uuid_key": "12345678-1234-5678-1234-567812345678"}', -# ) - -# # Base64-encoded string case (tests string handling, not bytes object handling) -# # Note: Actual bytes objects are tested in negative test cases (base.py) -# BYTES_VALUE = b"hello world" -# BYTES_CASE: Case = Case( -# name="bytes", -# data={"bytes_key": base64.b64encode(BYTES_VALUE).decode("ascii")}, -# json=f'{{"bytes_key": "{base64.b64encode(BYTES_VALUE).decode("ascii")}"}}', -# ) - -# # List case (tests list handling, not tuple object handling) -# # Note: Actual tuple objects are tested in negative test cases (base.py) -# TUPLE_CASE: Case = Case( -# name="tuple", -# data={"tuple_key": [1, "two", 3.0]}, -# json='{"tuple_key": [1, "two", 3.0]}', -# ) - -# # List case (tests list handling, not set object handling) -# # Note: Actual set objects are tested in negative test cases (base.py) -# SET_CASE: Case = Case( -# name="set", -# data={"set_key": {1, 2, 3}}, -# json='{"set_key": [1, 2, 3]}', -# ) - -# # Deeply nested structure cases -# DEEP_NESTED_CASE: Case = Case( -# name="deep-nested", -# data={"level1": {"level2": {"level3": {"level4": {"value": "deep"}}}}}, -# json='{"level1": {"level2": {"level3": {"level4": {"value": "deep"}}}}}', -# ) - -# NESTED_LIST_DICT_CASE: Case = Case( -# name="nested-list-dict", -# data={"items": [{"id": 1, "name": "item1"}, {"id": 2, "name": "item2"}]}, -# json='{"items": [{"id": 1, "name": "item1"}, {"id": 2, "name": "item2"}]}', -# ) - -# NESTED_DICT_LIST_CASE: Case = Case( -# name="nested-dict-list", -# data={"categories": {"fruits": ["apple", "banana"], "vegetables": ["carrot", "broccoli"]}}, -# json='{"categories": {"fruits": ["apple", "banana"], "vegetables": ["carrot", "broccoli"]}}', -# ) - -# MIXED_NESTED_CASE: Case = Case( -# name="mixed-nested", -# data={"outer": {"inner": [{"nested": [1, 2, 3]}]}}, -# json='{"outer": {"inner": [{"nested": [1, 2, 3]}]}}', -# ) - -# # Edge cases -# EMPTY_STRING_CASE: Case = Case( -# name="empty-string", -# data={"empty_key": ""}, -# json='{"empty_key": ""}', -# ) - -# EMPTY_LIST_CASE: Case = Case( -# name="empty-list", -# data={"empty_list_key": []}, -# json='{"empty_list_key": []}', -# ) - -# EMPTY_DICT_CASE: Case = Case( -# name="empty-dict", -# data={"empty_dict_key": {}}, -# json='{"empty_dict_key": {}}', -# ) - -# UNICODE_CASE: Case = Case( -# name="unicode", -# data={"unicode_key": "Hello 世界 🌍 émojis"}, -# json='{"unicode_key": "Hello \\u4e16\\u754c \\ud83c\\udf0d \\u00e9mojis"}', -# ) - -# SPECIAL_CHARS_CASE: Case = Case( -# name="special-chars", -# data={"special_key": "Line1\nLine2\tTabbed"}, -# json='{"special_key": "Line1\\nLine2\\tTabbed"}', -# ) - -# # Zero values -# ZERO_INT_CASE: Case = Case( -# name="zero-int", -# data={"zero_int_key": 0}, -# json='{"zero_int_key": 0}', -# ) - -# ZERO_FLOAT_CASE: Case = Case( -# name="zero-float", -# data={"zero_float_key": 0.0}, -# json='{"zero_float_key": 0.0}', -# ) - -# NEGATIVE_INT_CASE: Case = Case( -# name="negative-int", -# data={"negative_int_key": -42}, -# json='{"negative_int_key": -42}', -# ) - -# NEGATIVE_FLOAT_CASE: Case = Case( -# name="negative-float", -# data={"negative_float_key": -3.14}, -# json='{"negative_float_key": -3.14}', -# ) - -# # Large collection case -# LARGE_LIST_CASE: Case = Case( -# name="large-list", -# data={"large_list": list(range(1000))}, -# json=f'{{"large_list": {json.dumps(list(range(1000)))}}}', -# ) - - -# TEST_CASE_DATA: list[dict[str, Any]] = [ -# case.data -# for case in [ -# NULL_CASE, -# BOOL_TRUE_CASE, -# BOOL_FALSE_CASE, -# INT_CASE, -# LARGE_INT_CASE, -# FLOAT_CASE, -# LARGE_FLOAT_CASE, -# STRING_CASE, -# DICT_CASE_ONE, -# DICT_CASE_TWO, -# DICT_CASE_THREE, -# LIST_CASE_ONE, -# LIST_CASE_TWO, -# LIST_CASE_THREE, -# DATETIME_CASE, -# DATE_CASE, -# TIME_CASE, -# UUID_CASE, -# BYTES_CASE, -# TUPLE_CASE, -# SET_CASE, -# DEEP_NESTED_CASE, -# NESTED_LIST_DICT_CASE, -# NESTED_DICT_LIST_CASE, -# MIXED_NESTED_CASE, -# EMPTY_STRING_CASE, -# EMPTY_LIST_CASE, -# EMPTY_DICT_CASE, -# UNICODE_CASE, -# SPECIAL_CHARS_CASE, -# ZERO_INT_CASE, -# ZERO_FLOAT_CASE, -# NEGATIVE_INT_CASE, -# NEGATIVE_FLOAT_CASE, -# ] -# ] -# TEST_CASE_JSON: list[str] = [ -# case.json -# for case in [ -# NULL_CASE, -# BOOL_TRUE_CASE, -# BOOL_FALSE_CASE, -# INT_CASE, -# LARGE_INT_CASE, -# FLOAT_CASE, -# LARGE_FLOAT_CASE, -# STRING_CASE, -# DICT_CASE_ONE, -# DICT_CASE_TWO, -# DICT_CASE_THREE, -# LIST_CASE_ONE, -# LIST_CASE_TWO, -# LIST_CASE_THREE, -# DATETIME_CASE, -# DATE_CASE, -# TIME_CASE, -# UUID_CASE, -# BYTES_CASE, -# TUPLE_CASE, -# SET_CASE, -# DEEP_NESTED_CASE, -# NESTED_LIST_DICT_CASE, -# NESTED_DICT_LIST_CASE, -# MIXED_NESTED_CASE, -# EMPTY_STRING_CASE, -# EMPTY_LIST_CASE, -# EMPTY_DICT_CASE, -# UNICODE_CASE, -# SPECIAL_CHARS_CASE, -# ZERO_INT_CASE, -# ZERO_FLOAT_CASE, -# NEGATIVE_INT_CASE, -# NEGATIVE_FLOAT_CASE, -# ] -# ] - -# SIMPLE_TEST_DATA_ARGNAMES: tuple[str, str] = ("data", "json") -# SIMPLE_TEST_DATA_ARGVALUES: list[tuple[dict[str, Any], str]] = list(zip(TEST_CASE_DATA, TEST_CASE_JSON, strict=True)) -# SIMPLE_TEST_DATA_IDS: list[str] = [ -# case.name -# for case in [ -# NULL_CASE, -# BOOL_TRUE_CASE, -# BOOL_FALSE_CASE, -# INT_CASE, -# LARGE_INT_CASE, -# FLOAT_CASE, -# LARGE_FLOAT_CASE, -# STRING_CASE, -# DICT_CASE_ONE, -# DICT_CASE_TWO, -# DICT_CASE_THREE, -# LIST_CASE_ONE, -# LIST_CASE_TWO, -# LIST_CASE_THREE, -# DATETIME_CASE, -# DATE_CASE, -# TIME_CASE, -# UUID_CASE, -# BYTES_CASE, -# TUPLE_CASE, -# SET_CASE, -# DEEP_NESTED_CASE, -# NESTED_LIST_DICT_CASE, -# NESTED_DICT_LIST_CASE, -# MIXED_NESTED_CASE, -# EMPTY_STRING_CASE, -# EMPTY_LIST_CASE, -# EMPTY_DICT_CASE, -# UNICODE_CASE, -# SPECIAL_CHARS_CASE, -# ZERO_INT_CASE, -# ZERO_FLOAT_CASE, -# NEGATIVE_INT_CASE, -# NEGATIVE_FLOAT_CASE, -# ] -# ] - -# LARGE_TEST_DATA_DATA: list[dict[str, Any]] = [ -# case.data -# for case in [ -# LARGE_STRING_CASE, -# LARGE_INT_CASE, -# LARGE_FLOAT_CASE, -# LARGE_LIST_CASE, -# ] -# ] -# LARGE_TEST_DATA_JSON: list[str] = [ -# case.json -# for case in [ -# LARGE_STRING_CASE, -# LARGE_INT_CASE, -# LARGE_FLOAT_CASE, -# LARGE_LIST_CASE, -# ] -# ] -# LARGE_TEST_DATA_ARGNAMES: tuple[str, str] = ("data", "json") -# LARGE_TEST_DATA_ARGVALUES: list[tuple[dict[str, Any], str]] = list(zip(LARGE_TEST_DATA_DATA, LARGE_TEST_DATA_JSON, strict=True)) -# LARGE_TEST_DATA_IDS: list[str] = [ -# case.name -# for case in [ -# LARGE_STRING_CASE, -# LARGE_INT_CASE, -# LARGE_FLOAT_CASE, -# LARGE_LIST_CASE, -# ] -# ] - -# NEGATIVE_TEST_DATA_DATA: list[dict[str, Any]] = [ -# case.data -# for case in [ -# DATETIME_CASE, -# DATE_CASE, -# TIME_CASE, -# UUID_CASE, -# ] -# ] - -# NEGATIVE_TEST_DATA_JSON: list[str] = [ -# case.json -# for case in [ -# DATETIME_CASE, -# DATE_CASE, -# TIME_CASE, -# UUID_CASE, -# ] -# ] -# NEGATIVE_TEST_DATA_ARGNAMES: tuple[str, str] = ("data", "json") -# NEGATIVE_TEST_DATA_ARGVALUES: list[tuple[dict[str, Any], str]] = list(zip(NEGATIVE_TEST_DATA_DATA, NEGATIVE_TEST_DATA_JSON, strict=True)) -# NEGATIVE_TEST_DATA_IDS: list[str] = [ -# case.name -# for case in [ -# DATETIME_CASE, -# DATE_CASE, -# TIME_CASE, -# UUID_CASE, -# ] -# ] - -# __all__ = [ -# "LARGE_TEST_DATA_ARGNAMES", -# "LARGE_TEST_DATA_ARGVALUES", -# "LARGE_TEST_DATA_IDS", -# "SIMPLE_TEST_DATA_ARGNAMES", -# "SIMPLE_TEST_DATA_ARGVALUES", -# "SIMPLE_TEST_DATA_IDS", -# ] From 2ad5d55c827f456008498590f3cd1d3d48f9e85e Mon Sep 17 00:00:00 2001 From: William Easton Date: Sat, 25 Oct 2025 23:49:51 -0500 Subject: [PATCH 06/11] Updates to tests --- .../key-value-aio/tests/stores/keyring/test_keyring.py | 5 ++--- .../tests/stores/windows_registry/test_windows_registry.py | 7 +------ .../key-value-shared/tests/utils/test_managed_entry.py | 4 ++-- .../tests/code_gen/stores/keyring/test_keyring.py | 5 ++--- .../stores/windows_registry/test_windows_registry.py | 7 +------ 5 files changed, 8 insertions(+), 20 deletions(-) diff --git a/key-value/key-value-aio/tests/stores/keyring/test_keyring.py b/key-value/key-value-aio/tests/stores/keyring/test_keyring.py index 65024e72..ecb0a922 100644 --- a/key-value/key-value-aio/tests/stores/keyring/test_keyring.py +++ b/key-value/key-value-aio/tests/stores/keyring/test_keyring.py @@ -27,6 +27,5 @@ async def test_not_unbounded(self, store: BaseStore): ... @override @pytest.mark.skipif(condition=detect_on_windows(), reason="Keyrings do not support large values on Windows") - async def test_get_large_put_get(self, store: BaseStore, data: dict[str, Any], json: str): - await store.put(collection="test", key="test", value=data) - assert await store.get(collection="test", key="test") == data + async def test_get_large_put_get(self, store: BaseStore, data: dict[str, Any], json: str, round_trip: dict[str, Any]): # pyright: ignore[reportUnusedParameter] + await super().test_get_large_put_get(store, data, json, round_trip=data) diff --git a/key-value/key-value-aio/tests/stores/windows_registry/test_windows_registry.py b/key-value/key-value-aio/tests/stores/windows_registry/test_windows_registry.py index 9d636ac7..2af0f04e 100644 --- a/key-value/key-value-aio/tests/stores/windows_registry/test_windows_registry.py +++ b/key-value/key-value-aio/tests/stores/windows_registry/test_windows_registry.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING import pytest from typing_extensions import override @@ -28,8 +28,3 @@ async def store(self) -> "WindowsRegistryStore": @override @pytest.mark.skip(reason="We do not test boundedness of registry stores") async def test_not_unbounded(self, store: BaseStore): ... - - @override - async def test_get_large_put_get(self, store: BaseStore, data: dict[str, Any], json: str): - await store.put(collection="test", key="test", value=data) - assert await store.get(collection="test", key="test") == data diff --git a/key-value/key-value-shared/tests/utils/test_managed_entry.py b/key-value/key-value-shared/tests/utils/test_managed_entry.py index a7d91776..527ab06b 100644 --- a/key-value/key-value-shared/tests/utils/test_managed_entry.py +++ b/key-value/key-value-shared/tests/utils/test_managed_entry.py @@ -14,12 +14,12 @@ def test_dump_to_json(data: dict[str, Any], json: str, round_trip: dict[str, Any assert dump_to_json(data) == json -@SIMPLE_CASES.parametrize() +@PositiveCases.parametrize(cases=SIMPLE_CASES) def test_load_from_json(data: dict[str, Any], json: str, round_trip: dict[str, Any]): assert load_from_json(json) == data -@SIMPLE_CASES.parametrize() +@PositiveCases.parametrize(cases=SIMPLE_CASES) def test_roundtrip_json(data: dict[str, Any], json: str, round_trip: dict[str, Any]): dumped_json: str = dump_to_json(data) assert dumped_json == json diff --git a/key-value/key-value-sync/tests/code_gen/stores/keyring/test_keyring.py b/key-value/key-value-sync/tests/code_gen/stores/keyring/test_keyring.py index 4466b09a..c6265044 100644 --- a/key-value/key-value-sync/tests/code_gen/stores/keyring/test_keyring.py +++ b/key-value/key-value-sync/tests/code_gen/stores/keyring/test_keyring.py @@ -30,6 +30,5 @@ def test_not_unbounded(self, store: BaseStore): ... @override @pytest.mark.skipif(condition=detect_on_windows(), reason="Keyrings do not support large values on Windows") - def test_get_large_put_get(self, store: BaseStore, data: dict[str, Any], json: str): - store.put(collection="test", key="test", value=data) - assert store.get(collection="test", key="test") == data + def test_get_large_put_get(self, store: BaseStore, data: dict[str, Any], json: str, round_trip: dict[str, Any]): # pyright: ignore[reportUnusedParameter] + super().test_get_large_put_get(store, data, json, round_trip=data) diff --git a/key-value/key-value-sync/tests/code_gen/stores/windows_registry/test_windows_registry.py b/key-value/key-value-sync/tests/code_gen/stores/windows_registry/test_windows_registry.py index 5c25631d..1fc346e8 100644 --- a/key-value/key-value-sync/tests/code_gen/stores/windows_registry/test_windows_registry.py +++ b/key-value/key-value-sync/tests/code_gen/stores/windows_registry/test_windows_registry.py @@ -1,7 +1,7 @@ # WARNING: this file is auto-generated by 'build_sync_library.py' # from the original file 'test_windows_registry.py' # DO NOT CHANGE! Change the original file instead. -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING import pytest from typing_extensions import override @@ -31,8 +31,3 @@ def store(self) -> "WindowsRegistryStore": @override @pytest.mark.skip(reason="We do not test boundedness of registry stores") def test_not_unbounded(self, store: BaseStore): ... - - @override - def test_get_large_put_get(self, store: BaseStore, data: dict[str, Any], json: str): - store.put(collection="test", key="test", value=data) - assert store.get(collection="test", key="test") == data From ce8107f403c946c3b23c29c7ba3aa0938f694fb7 Mon Sep 17 00:00:00 2001 From: William Easton Date: Sat, 25 Oct 2025 23:59:37 -0500 Subject: [PATCH 07/11] fix tests --- .../key-value-aio/tests/stores/keyring/test_keyring.py | 2 +- .../src/key_value/shared_test/cases.py | 8 ++++---- .../tests/code_gen/stores/keyring/test_keyring.py | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/key-value/key-value-aio/tests/stores/keyring/test_keyring.py b/key-value/key-value-aio/tests/stores/keyring/test_keyring.py index ecb0a922..6438e9d8 100644 --- a/key-value/key-value-aio/tests/stores/keyring/test_keyring.py +++ b/key-value/key-value-aio/tests/stores/keyring/test_keyring.py @@ -28,4 +28,4 @@ async def test_not_unbounded(self, store: BaseStore): ... @override @pytest.mark.skipif(condition=detect_on_windows(), reason="Keyrings do not support large values on Windows") async def test_get_large_put_get(self, store: BaseStore, data: dict[str, Any], json: str, round_trip: dict[str, Any]): # pyright: ignore[reportUnusedParameter] - await super().test_get_large_put_get(store, data, json, round_trip=data) + await super().test_get_large_put_get(store, data, json, round_trip=round_trip) diff --git a/key-value/key-value-shared-test/src/key_value/shared_test/cases.py b/key-value/key-value-shared-test/src/key_value/shared_test/cases.py index 7342b9ed..c9c2b41e 100644 --- a/key-value/key-value-shared-test/src/key_value/shared_test/cases.py +++ b/key-value/key-value-shared-test/src/key_value/shared_test/cases.py @@ -175,7 +175,7 @@ def parametrize(cls, cases: list[Self]) -> MarkDecorator: STRING_CASES: PositiveCases = PositiveCases( Case(name="string", data={"string_key": "string_value"}, json='{"string_key": "string_value"}'), - Case(name="large-value", data={"large_string_key": "a" * 10000}, json=f'{{"large_string_key": "{"a" * 10000}"}}'), + Case(name="large-value", data={"large_string_key": "a" * 2048}, json=f'{{"large_string_key": "{"a" * 10000}"}}'), Case( name="unicode", data={"unicode_key": "Hello 世界 🌍 émojis"}, @@ -236,11 +236,11 @@ def parametrize(cls, cases: list[Self]) -> MarkDecorator: SAMPLE_TUPLE: tuple[int, str, float] = (1, "two", 3.0) TUPLE_CASES: PositiveCases = PositiveCases( - Case(name="tuple", data={"tuple_key": SAMPLE_TUPLE}, json='{"tuple_key": [1, "two", 3.0]}'), + Case(name="tuple", data={"str_key": SAMPLE_TUPLE}, json='{"str_key": [1, "two", 3.0]}'), Case( name="large-tuple", - data={"large_tuple_key": (1, "two", 3.0) * 1000}, - json=f'{{"large_tuple_key": {json.dumps((1, "two", 3.0) * 1000)}}}', + data={"str_key": (1, "two", 3.0) * 1000}, + json=f'{{"str_key": {json.dumps((1, "two", 3.0) * 1000)}}}', ), case_type="tuple", ) diff --git a/key-value/key-value-sync/tests/code_gen/stores/keyring/test_keyring.py b/key-value/key-value-sync/tests/code_gen/stores/keyring/test_keyring.py index c6265044..efc505ec 100644 --- a/key-value/key-value-sync/tests/code_gen/stores/keyring/test_keyring.py +++ b/key-value/key-value-sync/tests/code_gen/stores/keyring/test_keyring.py @@ -31,4 +31,4 @@ def test_not_unbounded(self, store: BaseStore): ... @override @pytest.mark.skipif(condition=detect_on_windows(), reason="Keyrings do not support large values on Windows") def test_get_large_put_get(self, store: BaseStore, data: dict[str, Any], json: str, round_trip: dict[str, Any]): # pyright: ignore[reportUnusedParameter] - super().test_get_large_put_get(store, data, json, round_trip=data) + super().test_get_large_put_get(store, data, json, round_trip=round_trip) From 3ca660d31dc92fdf09ae1c5ed06a4f9459194414 Mon Sep 17 00:00:00 2001 From: William Easton Date: Sun, 26 Oct 2025 00:01:38 -0500 Subject: [PATCH 08/11] fix tests --- .../key-value-shared-test/src/key_value/shared_test/cases.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/key-value/key-value-shared-test/src/key_value/shared_test/cases.py b/key-value/key-value-shared-test/src/key_value/shared_test/cases.py index c9c2b41e..cc981e7f 100644 --- a/key-value/key-value-shared-test/src/key_value/shared_test/cases.py +++ b/key-value/key-value-shared-test/src/key_value/shared_test/cases.py @@ -175,7 +175,7 @@ def parametrize(cls, cases: list[Self]) -> MarkDecorator: STRING_CASES: PositiveCases = PositiveCases( Case(name="string", data={"string_key": "string_value"}, json='{"string_key": "string_value"}'), - Case(name="large-value", data={"large_string_key": "a" * 2048}, json=f'{{"large_string_key": "{"a" * 10000}"}}'), + Case(name="large-value", data={"large_string_key": "a" * 2048}, json=f'{{"large_string_key": "{"a" * 2048}"}}'), Case( name="unicode", data={"unicode_key": "Hello 世界 🌍 émojis"}, From a4a9d1d8ef86a6455736f173ea6ec52e39a29107 Mon Sep 17 00:00:00 2001 From: William Easton Date: Sun, 26 Oct 2025 00:04:27 -0500 Subject: [PATCH 09/11] fix tests --- key-value/key-value-shared/tests/utils/test_managed_entry.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/key-value/key-value-shared/tests/utils/test_managed_entry.py b/key-value/key-value-shared/tests/utils/test_managed_entry.py index 527ab06b..80ae5702 100644 --- a/key-value/key-value-shared/tests/utils/test_managed_entry.py +++ b/key-value/key-value-shared/tests/utils/test_managed_entry.py @@ -11,16 +11,19 @@ @PositiveCases.parametrize(cases=SIMPLE_CASES) def test_dump_to_json(data: dict[str, Any], json: str, round_trip: dict[str, Any]): + """Test that the dump_to_json function dumps the data to the matching JSON string""" assert dump_to_json(data) == json @PositiveCases.parametrize(cases=SIMPLE_CASES) def test_load_from_json(data: dict[str, Any], json: str, round_trip: dict[str, Any]): - assert load_from_json(json) == data + """Test that the load_from_json function loads the data (round-trip) from the matching JSON string""" + assert load_from_json(json) == round_trip @PositiveCases.parametrize(cases=SIMPLE_CASES) def test_roundtrip_json(data: dict[str, Any], json: str, round_trip: dict[str, Any]): + """Test that the dump_to_json and load_from_json functions roundtrip the data""" dumped_json: str = dump_to_json(data) assert dumped_json == json assert load_from_json(dumped_json) == round_trip From 1d46050592e6dc3fdec8666ecc8edd47c7f36430 Mon Sep 17 00:00:00 2001 From: William Easton Date: Sun, 26 Oct 2025 00:08:25 -0500 Subject: [PATCH 10/11] fix tests --- key-value/key-value-aio/tests/stores/keyring/test_keyring.py | 2 ++ .../tests/code_gen/stores/keyring/test_keyring.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/key-value/key-value-aio/tests/stores/keyring/test_keyring.py b/key-value/key-value-aio/tests/stores/keyring/test_keyring.py index 6438e9d8..c9f36d7c 100644 --- a/key-value/key-value-aio/tests/stores/keyring/test_keyring.py +++ b/key-value/key-value-aio/tests/stores/keyring/test_keyring.py @@ -1,6 +1,7 @@ from typing import Any import pytest +from key_value.shared_test.cases import LARGE_DATA_CASES, PositiveCases from typing_extensions import override from key_value.aio.stores.base import BaseStore @@ -27,5 +28,6 @@ async def test_not_unbounded(self, store: BaseStore): ... @override @pytest.mark.skipif(condition=detect_on_windows(), reason="Keyrings do not support large values on Windows") + @PositiveCases.parametrize(cases=[LARGE_DATA_CASES]) async def test_get_large_put_get(self, store: BaseStore, data: dict[str, Any], json: str, round_trip: dict[str, Any]): # pyright: ignore[reportUnusedParameter] await super().test_get_large_put_get(store, data, json, round_trip=round_trip) diff --git a/key-value/key-value-sync/tests/code_gen/stores/keyring/test_keyring.py b/key-value/key-value-sync/tests/code_gen/stores/keyring/test_keyring.py index efc505ec..3072f12e 100644 --- a/key-value/key-value-sync/tests/code_gen/stores/keyring/test_keyring.py +++ b/key-value/key-value-sync/tests/code_gen/stores/keyring/test_keyring.py @@ -4,6 +4,7 @@ from typing import Any import pytest +from key_value.shared_test.cases import LARGE_DATA_CASES, PositiveCases from typing_extensions import override from key_value.sync.code_gen.stores.base import BaseStore @@ -30,5 +31,6 @@ def test_not_unbounded(self, store: BaseStore): ... @override @pytest.mark.skipif(condition=detect_on_windows(), reason="Keyrings do not support large values on Windows") + @PositiveCases.parametrize(cases=[LARGE_DATA_CASES]) def test_get_large_put_get(self, store: BaseStore, data: dict[str, Any], json: str, round_trip: dict[str, Any]): # pyright: ignore[reportUnusedParameter] super().test_get_large_put_get(store, data, json, round_trip=round_trip) From 332a573eafd2305901535b98599449a1fde10253 Mon Sep 17 00:00:00 2001 From: William Easton Date: Sun, 26 Oct 2025 00:20:31 -0500 Subject: [PATCH 11/11] 1kb string --- .../key-value-shared-test/src/key_value/shared_test/cases.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/key-value/key-value-shared-test/src/key_value/shared_test/cases.py b/key-value/key-value-shared-test/src/key_value/shared_test/cases.py index cc981e7f..d83e5329 100644 --- a/key-value/key-value-shared-test/src/key_value/shared_test/cases.py +++ b/key-value/key-value-shared-test/src/key_value/shared_test/cases.py @@ -175,7 +175,7 @@ def parametrize(cls, cases: list[Self]) -> MarkDecorator: STRING_CASES: PositiveCases = PositiveCases( Case(name="string", data={"string_key": "string_value"}, json='{"string_key": "string_value"}'), - Case(name="large-value", data={"large_string_key": "a" * 2048}, json=f'{{"large_string_key": "{"a" * 2048}"}}'), + Case(name="1kb-value", data={"string_key": "a" * 1024}, json=f'{{"string_key": "{"a" * 1024}"}}'), Case( name="unicode", data={"unicode_key": "Hello 世界 🌍 émojis"},