44from typing import Any
55
66import pytest
7- from dirty_equals import IsFloat
7+ from dirty_equals import IsDatetime , IsFloat , IsInstance
88from inline_snapshot import snapshot
99from key_value .shared .stores .wait import async_wait_for_true
1010from key_value .shared .utils .managed_entry import ManagedEntry
1111from pymongo import AsyncMongoClient
12+ from pymongo .collection import ObjectId
1213from typing_extensions import override
1314
1415from key_value .aio .stores .base import BaseStore
@@ -92,9 +93,13 @@ def test_managed_entry_document_conversion_legacy_mode():
9293 assert round_trip_managed_entry .expires_at == expires_at
9394
9495
95- @pytest .mark .skipif (should_skip_docker_tests (), reason = "Docker is not available" )
96- class TestMongoDBStore (ContextManagerStoreTestMixin , BaseStoreTests ):
97- """Test MongoDBStore with native_storage=False (legacy mode) for backward compatibility."""
96+ async def clean_mongodb_database (store : MongoDBStore ) -> None :
97+ with contextlib .suppress (Exception ):
98+ _ = await store ._client .drop_database (name_or_database = MONGODB_TEST_DB ) # pyright: ignore[reportPrivateUsage]
99+
100+
101+ class BaseMongoDBStoreTests (ContextManagerStoreTestMixin , BaseStoreTests ):
102+ """Base class for MongoDB store tests."""
98103
99104 @pytest .fixture (autouse = True , scope = "session" , params = MONGODB_VERSIONS_TO_TEST )
100105 async def setup_mongodb (self , request : pytest .FixtureRequest ) -> AsyncGenerator [None , None ]:
@@ -107,49 +112,23 @@ async def setup_mongodb(self, request: pytest.FixtureRequest) -> AsyncGenerator[
107112
108113 yield
109114
110- @override
111- @pytest .fixture
112- async def store (self , setup_mongodb : None ) -> MongoDBStore :
113- # Use legacy mode (native_storage=False) to test backward compatibility
114- store = MongoDBStore (url = f"mongodb://{ MONGODB_HOST } :{ MONGODB_HOST_PORT } " , db_name = MONGODB_TEST_DB , native_storage = False )
115- # Ensure a clean db by dropping our default test collection if it exists
116- with contextlib .suppress (Exception ):
117- _ = await store ._client .drop_database (name_or_database = MONGODB_TEST_DB ) # pyright: ignore[reportPrivateUsage]
118-
119- return store
120-
121- @pytest .fixture
122- async def mongodb_store (self , store : MongoDBStore ) -> MongoDBStore :
123- return store
124-
125115 @pytest .mark .skip (reason = "Distributed Caches are unbounded" )
126116 @override
127117 async def test_not_unbounded (self , store : BaseStore ): ...
128118
129- async def test_mongodb_collection_name_sanitization (self , mongodb_store : MongoDBStore ):
119+ async def test_mongodb_collection_name_sanitization (self , store : MongoDBStore ):
130120 """Tests that a special characters in the collection name will not raise an error."""
131- await mongodb_store .put (collection = "test_collection!@#$%^&*()" , key = "test_key" , value = {"test" : "test" })
132- assert await mongodb_store .get (collection = "test_collection!@#$%^&*()" , key = "test_key" ) == {"test" : "test" }
121+ await store .put (collection = "test_collection!@#$%^&*()" , key = "test_key" , value = {"test" : "test" })
122+ assert await store .get (collection = "test_collection!@#$%^&*()" , key = "test_key" ) == {"test" : "test" }
133123
134- collections = await mongodb_store .collections ()
124+ collections = await store .collections ()
135125 assert collections == snapshot (["test_collection_-daf4a2ec" ])
136126
137127
138128@pytest .mark .skipif (should_skip_docker_tests (), reason = "Docker is not available" )
139- class TestMongoDBStoreNativeMode (ContextManagerStoreTestMixin , BaseStoreTests ):
129+ class TestMongoDBStoreNativeMode (BaseMongoDBStoreTests ):
140130 """Test MongoDBStore with native_storage=True (default)."""
141131
142- @pytest .fixture (autouse = True , scope = "session" , params = MONGODB_VERSIONS_TO_TEST )
143- async def setup_mongodb (self , request : pytest .FixtureRequest ) -> AsyncGenerator [None , None ]:
144- version = request .param
145-
146- with docker_container (f"mongodb-test-native-{ version } " , f"mongo:{ version } " , {str (MONGODB_HOST_PORT ): MONGODB_HOST_PORT }):
147- if not await async_wait_for_true (bool_fn = ping_mongodb , tries = WAIT_FOR_MONGODB_TIMEOUT , wait_time = 1 ):
148- msg = f"MongoDB { version } failed to start"
149- raise MongoDBFailedToStartError (msg )
150-
151- yield
152-
153132 @override
154133 @pytest .fixture
155134 async def store (self , setup_mongodb : None ) -> MongoDBStore :
@@ -160,36 +139,31 @@ async def store(self, setup_mongodb: None) -> MongoDBStore:
160139
161140 return store
162141
163- @pytest .fixture
164- async def mongodb_store (self , store : MongoDBStore ) -> MongoDBStore :
165- return store
166-
167- @pytest .mark .skip (reason = "Distributed Caches are unbounded" )
168- @override
169- async def test_not_unbounded (self , store : BaseStore ): ...
170-
171- async def test_value_stored_as_bson_dict (self , mongodb_store : MongoDBStore ):
142+ async def test_value_stored_as_bson_dict (self , store : MongoDBStore ):
172143 """Verify values are stored as BSON dicts, not JSON strings."""
173- await mongodb_store .put (collection = "test" , key = "test_key" , value = {"name" : "Alice" , "age" : 30 })
144+ await store .put (collection = "test" , key = "test_key" , value = {"name" : "Alice" , "age" : 30 })
174145
175146 # Get the raw MongoDB document
176- await mongodb_store ._setup_collection (collection = "test" ) # pyright: ignore[reportPrivateUsage]
177- sanitized_collection = mongodb_store ._sanitize_collection_name (collection = "test" ) # pyright: ignore[reportPrivateUsage]
178- collection = mongodb_store ._collections_by_name [sanitized_collection ] # pyright: ignore[reportPrivateUsage]
147+ await store ._setup_collection (collection = "test" ) # pyright: ignore[reportPrivateUsage]
148+ sanitized_collection = store ._sanitize_collection_name (collection = "test" ) # pyright: ignore[reportPrivateUsage]
149+ collection = store ._collections_by_name [sanitized_collection ] # pyright: ignore[reportPrivateUsage]
179150 doc = await collection .find_one ({"key" : "test_key" })
180151
181- # In native mode, value should be a dict with "dict" subfield
182- assert doc is not None
183- assert isinstance (doc ["value" ], dict )
184- assert "dict" in doc ["value" ]
185- assert doc ["value" ]["dict" ] == {"name" : "Alice" , "age" : 30 }
152+ assert doc == snapshot (
153+ {
154+ "_id" : IsInstance (expected_type = ObjectId ),
155+ "key" : "test_key" ,
156+ "created_at" : IsDatetime (),
157+ "value" : {"object" : {"name" : "Alice" , "age" : 30 }},
158+ }
159+ )
186160
187- async def test_migration_from_legacy_mode (self , mongodb_store : MongoDBStore ):
161+ async def test_migration_from_legacy_mode (self , store : MongoDBStore ):
188162 """Verify native mode can read legacy JSON string data."""
189163 # Manually insert a legacy document with JSON string value in the new format
190- await mongodb_store ._setup_collection (collection = "test" ) # pyright: ignore[reportPrivateUsage]
191- sanitized_collection = mongodb_store ._sanitize_collection_name (collection = "test" ) # pyright: ignore[reportPrivateUsage]
192- collection = mongodb_store ._collections_by_name [sanitized_collection ] # pyright: ignore[reportPrivateUsage]
164+ await store ._setup_collection (collection = "test" ) # pyright: ignore[reportPrivateUsage]
165+ sanitized_collection = store ._sanitize_collection_name (collection = "test" ) # pyright: ignore[reportPrivateUsage]
166+ collection = store ._collections_by_name [sanitized_collection ] # pyright: ignore[reportPrivateUsage]
193167
194168 await collection .insert_one (
195169 {
@@ -199,23 +173,56 @@ async def test_migration_from_legacy_mode(self, mongodb_store: MongoDBStore):
199173 )
200174
201175 # Should be able to read it in native mode
202- result = await mongodb_store .get (collection = "test" , key = "legacy_key" )
176+ result = await store .get (collection = "test" , key = "legacy_key" )
203177 assert result == {"legacy" : "data" }
204178
205- async def test_migration_from_old_format (self , mongodb_store : MongoDBStore ):
206- """Verify native mode can read old format where value is directly a string."""
207- # Manually insert an old document with value directly as JSON string
208- await mongodb_store ._setup_collection (collection = "test" ) # pyright: ignore[reportPrivateUsage]
209- sanitized_collection = mongodb_store ._sanitize_collection_name (collection = "test" ) # pyright: ignore[reportPrivateUsage]
210- collection = mongodb_store ._collections_by_name [sanitized_collection ] # pyright: ignore[reportPrivateUsage]
179+
180+ @pytest .mark .skipif (should_skip_docker_tests (), reason = "Docker is not available" )
181+ class TestMongoDBStoreNonNativeMode (BaseMongoDBStoreTests ):
182+ """Test MongoDBStore with native_storage=False (legacy mode) for backward compatibility."""
183+
184+ @override
185+ @pytest .fixture
186+ async def store (self , setup_mongodb : None ) -> MongoDBStore :
187+ store = MongoDBStore (url = f"mongodb://{ MONGODB_HOST } :{ MONGODB_HOST_PORT } " , db_name = MONGODB_TEST_DB , native_storage = False )
188+
189+ await clean_mongodb_database (store = store )
190+
191+ return store
192+
193+ async def test_value_stored_as_json (self , store : MongoDBStore ):
194+ """Verify values are stored as JSON strings."""
195+ await store .put (collection = "test" , key = "test_key" , value = {"name" : "Alice" , "age" : 30 })
196+
197+ # Get the raw MongoDB document
198+ await store ._setup_collection (collection = "test" ) # pyright: ignore[reportPrivateUsage]
199+ sanitized_collection = store ._sanitize_collection_name (collection = "test" ) # pyright: ignore[reportPrivateUsage]
200+ collection = store ._collections_by_name [sanitized_collection ] # pyright: ignore[reportPrivateUsage]
201+ doc = await collection .find_one ({"key" : "test_key" })
202+
203+ assert doc == snapshot (
204+ {
205+ "_id" : IsInstance (expected_type = ObjectId ),
206+ "key" : "test_key" ,
207+ "created_at" : IsDatetime (),
208+ "value" : {"string" : '{"age": 30, "name": "Alice"}' },
209+ }
210+ )
211+
212+ async def test_migration_from_native_mode (self , store : MongoDBStore ):
213+ """Verify native mode can read native mode JSON string data."""
214+ # Manually insert a legacy document with JSON string value in the new format
215+ await store ._setup_collection (collection = "test" ) # pyright: ignore[reportPrivateUsage]
216+ sanitized_collection = store ._sanitize_collection_name (collection = "test" ) # pyright: ignore[reportPrivateUsage]
217+ collection = store ._collections_by_name [sanitized_collection ] # pyright: ignore[reportPrivateUsage]
211218
212219 await collection .insert_one (
213220 {
214- "key" : "old_key " ,
215- "value" : '{"old ": "format"}' , # Old format: value directly as JSON string
221+ "key" : "legacy_key " ,
222+ "value" : { "object " : { "name" : "Alice" , "age" : 30 }},
216223 }
217224 )
218225
219226 # Should be able to read it in native mode
220- result = await mongodb_store .get (collection = "test" , key = "old_key " )
221- assert result == {"old " : "format" }
227+ result = await store .get (collection = "test" , key = "legacy_key " )
228+ assert result == {"name " : "Alice" , "age" : 30 }
0 commit comments