1
+ import pytest
2
+ import unittest .mock
3
+ import ydb
4
+ import asyncio
5
+ from ydb import _apis
6
+
7
+
8
+ # Common test constants and mock config
9
+ DISCOVERY_DISABLED_ERROR_MSG = "Discovery should not be executed when discovery is disabled"
10
+ TEST_ERROR = "Test error"
11
+ TEST_QUERY = "SELECT 1 + 2 AS sum"
12
+
13
+
14
+ @pytest .fixture
15
+ def mock_connection ():
16
+ """Mock a YDB connection to avoid actual connections."""
17
+ with unittest .mock .patch ('ydb.connection.Connection.ready_factory' ) as mock_factory :
18
+ # Setup the mock to return a connection-like object
19
+ mock_connection = unittest .mock .MagicMock ()
20
+ # Use the endpoint fixture value via the function parameter
21
+ mock_connection .endpoint = "localhost:2136" # Will be overridden in tests
22
+ mock_connection .node_id = "mock_node_id"
23
+ mock_factory .return_value = mock_connection
24
+ yield mock_factory
25
+
26
+
27
+ @pytest .fixture
28
+ def mock_aio_connection ():
29
+ """Mock a YDB async connection to avoid actual connections."""
30
+ with unittest .mock .patch ('ydb.aio.connection.Connection.__init__' ) as mock_init :
31
+ # Setup the mock to return None (as __init__ does)
32
+ mock_init .return_value = None
33
+
34
+ # Mock connection_ready method
35
+ with unittest .mock .patch ('ydb.aio.connection.Connection.connection_ready' ) as mock_ready :
36
+ # Create event loop if there isn't one currently
37
+ loop = asyncio .new_event_loop ()
38
+ asyncio .set_event_loop (loop )
39
+
40
+ future = asyncio .Future ()
41
+ future .set_result (None )
42
+ mock_ready .return_value = future
43
+ yield mock_init
44
+
45
+
46
+ def create_mock_discovery_resolver (path ):
47
+ """Create a mock discovery resolver that raises exception if called."""
48
+ def _mock_fixture ():
49
+ with unittest .mock .patch (path ) as mock_resolve :
50
+ # Configure mock to throw an exception if called
51
+ mock_resolve .side_effect = Exception (DISCOVERY_DISABLED_ERROR_MSG )
52
+ yield mock_resolve
53
+ return _mock_fixture
54
+
55
+
56
+ # Mock discovery resolvers to verify no discovery requests are made
57
+ mock_discovery_resolver = pytest .fixture (create_mock_discovery_resolver ('ydb.resolver.DiscoveryEndpointsResolver.context_resolve' ))
58
+ mock_aio_discovery_resolver = pytest .fixture (create_mock_discovery_resolver ('ydb.aio.resolver.DiscoveryEndpointsResolver.resolve' ))
59
+
60
+
61
+ # We'll use the fixtures from conftest.py instead of these mock fixtures
62
+ # However, we'll keep them for tests that don't need the real YDB container
63
+
64
+
65
+ # Basic unit tests for DriverConfig
66
+ def test_driver_config_has_disable_discovery_option (endpoint , database ):
67
+ """Test that DriverConfig has the disable_discovery option."""
68
+ config = ydb .DriverConfig (
69
+ endpoint = endpoint ,
70
+ database = database ,
71
+ disable_discovery = True
72
+ )
73
+ assert hasattr (config , "disable_discovery" )
74
+ assert config .disable_discovery is True
75
+
76
+
77
+ # Driver config fixtures
78
+ def create_driver_config (endpoint , database , disable_discovery ):
79
+ """Create a driver config with the given discovery setting."""
80
+ return ydb .DriverConfig (
81
+ endpoint = endpoint ,
82
+ database = database ,
83
+ disable_discovery = disable_discovery ,
84
+ )
85
+
86
+
87
+ @pytest .fixture
88
+ def driver_config_disabled_discovery (endpoint , database ):
89
+ """A driver config with discovery disabled"""
90
+ return create_driver_config (endpoint , database , True )
91
+
92
+
93
+ @pytest .fixture
94
+ def driver_config_enabled_discovery (endpoint , database ):
95
+ """A driver config with discovery enabled (default)"""
96
+ return create_driver_config (endpoint , database , False )
97
+
98
+
99
+ # Common test assertions
100
+ def assert_discovery_disabled (driver ):
101
+ """Assert that discovery is disabled in the driver."""
102
+ assert "Discovery is disabled" in driver .discovery_debug_details ()
103
+
104
+
105
+ def create_future_with_error ():
106
+ """Create a future with a test error."""
107
+ future = asyncio .Future ()
108
+ future .set_exception (ydb .issues .Error (TEST_ERROR ))
109
+ return future
110
+
111
+
112
+ def create_completed_future ():
113
+ """Create a completed future."""
114
+ future = asyncio .Future ()
115
+ future .set_result (None )
116
+ return future
117
+
118
+
119
+ # Mock tests for synchronous driver
120
+ def test_sync_driver_discovery_disabled_mock (driver_config_disabled_discovery , mock_connection , mock_discovery_resolver ):
121
+ """Test that when disable_discovery=True, the discovery thread is not started and resolver is not called (mock)."""
122
+ with unittest .mock .patch ('ydb.pool.Discovery' ) as mock_discovery_class :
123
+ driver = ydb .Driver (driver_config = driver_config_disabled_discovery )
124
+
125
+ try :
126
+ # Check that the discovery thread was not created
127
+ mock_discovery_class .assert_not_called ()
128
+
129
+ # Check that discovery is disabled in debug details
130
+ assert_discovery_disabled (driver )
131
+
132
+ # Execute a dummy call to verify no discovery requests are made
133
+ try :
134
+ driver (ydb .issues .Error (TEST_ERROR ), _apis .OperationService .Stub , "GetOperation" )
135
+ except ydb .issues .Error :
136
+ pass # Expected exception, we just want to ensure no discovery occurs
137
+
138
+ # Verify the mock wasn't called
139
+ assert not mock_discovery_resolver .called , "Discovery resolver should not be called when discovery is disabled"
140
+ finally :
141
+ # Clean up
142
+ driver .stop ()
143
+
144
+
145
+ def test_sync_driver_discovery_enabled_mock (driver_config_enabled_discovery , mock_connection ):
146
+ """Test that when disable_discovery=False, the discovery thread is started (mock)."""
147
+ with unittest .mock .patch ('ydb.pool.Discovery' ) as mock_discovery_class :
148
+ mock_discovery_instance = unittest .mock .MagicMock ()
149
+ mock_discovery_class .return_value = mock_discovery_instance
150
+
151
+ driver = ydb .Driver (driver_config = driver_config_enabled_discovery )
152
+
153
+ try :
154
+ # Check that the discovery thread was created and started
155
+ mock_discovery_class .assert_called_once ()
156
+ assert mock_discovery_instance .start .called
157
+ finally :
158
+ # Clean up
159
+ driver .stop ()
160
+
161
+
162
+ # Helper for setting up async driver test mocks
163
+ def setup_async_driver_mocks ():
164
+ """Set up common mocks for async driver tests."""
165
+ mocks = {}
166
+
167
+ # Create mock for Discovery class
168
+ discovery_patcher = unittest .mock .patch ('ydb.aio.pool.Discovery' )
169
+ mocks ['mock_discovery_class' ] = discovery_patcher .start ()
170
+
171
+ # Mock the event loop
172
+ loop_patcher = unittest .mock .patch ('asyncio.get_event_loop' )
173
+ mock_loop = loop_patcher .start ()
174
+ mock_loop_instance = unittest .mock .MagicMock ()
175
+ mock_loop .return_value = mock_loop_instance
176
+ mock_loop_instance .create_task .return_value = unittest .mock .MagicMock ()
177
+ mocks ['mock_loop' ] = mock_loop
178
+
179
+ # Mock the connection pool stop method
180
+ stop_patcher = unittest .mock .patch ('ydb.aio.pool.ConnectionPool.stop' )
181
+ mock_stop = stop_patcher .start ()
182
+ mock_stop .return_value = create_completed_future ()
183
+ mocks ['mock_stop' ] = mock_stop
184
+
185
+ # Add cleanup for all patchers
186
+ mocks ['patchers' ] = [discovery_patcher , loop_patcher , stop_patcher ]
187
+
188
+ return mocks
189
+
190
+
191
+ def teardown_async_mocks (mocks ):
192
+ """Clean up all mock patchers."""
193
+ for patcher in mocks ['patchers' ]:
194
+ patcher .stop ()
195
+
196
+
197
+ # Mock tests for asynchronous driver
198
+ @pytest .mark .asyncio
199
+ async def test_aio_driver_discovery_disabled_mock (driver_config_disabled_discovery , mock_aio_connection , mock_aio_discovery_resolver ):
200
+ """Test that when disable_discovery=True, the discovery is not created and resolver is not called (mock)."""
201
+ mocks = setup_async_driver_mocks ()
202
+
203
+ try :
204
+ # Mock the pool's call method to prevent unhandled exceptions
205
+ with unittest .mock .patch ('ydb.aio.pool.ConnectionPool.__call__' ) as mock_call :
206
+ mock_call .return_value = create_future_with_error ()
207
+
208
+ driver = ydb .aio .Driver (driver_config = driver_config_disabled_discovery )
209
+
210
+ try :
211
+ # Check that the discovery class was not instantiated
212
+ mocks ['mock_discovery_class' ].assert_not_called ()
213
+
214
+ # Check that discovery is disabled in debug details
215
+ assert_discovery_disabled (driver )
216
+
217
+ # Execute a dummy call to verify no discovery requests are made
218
+ try :
219
+ try :
220
+ await driver (ydb .issues .Error (TEST_ERROR ), _apis .OperationService .Stub , "GetOperation" )
221
+ except ydb .issues .Error :
222
+ pass # Expected exception, we just want to ensure no discovery occurs
223
+ except Exception as e :
224
+ if "discovery is disabled" in str (e ).lower ():
225
+ raise # If the error is related to discovery being disabled, re-raise it
226
+ pass # Other exceptions are expected as we're using mocks
227
+
228
+ # Verify the mock wasn't called
229
+ assert not mock_aio_discovery_resolver .called , "Discovery resolver should not be called when discovery is disabled"
230
+ finally :
231
+ # The stop method is already mocked, so we don't need to await it
232
+ pass
233
+ finally :
234
+ teardown_async_mocks (mocks )
235
+
236
+
237
+ @pytest .mark .asyncio
238
+ async def test_aio_driver_discovery_enabled_mock (driver_config_enabled_discovery , mock_aio_connection ):
239
+ """Test that when disable_discovery=False, the discovery is created (mock)."""
240
+ mocks = setup_async_driver_mocks ()
241
+
242
+ try :
243
+ mock_discovery_instance = unittest .mock .MagicMock ()
244
+ mocks ['mock_discovery_class' ].return_value = mock_discovery_instance
245
+
246
+ driver = ydb .aio .Driver (driver_config = driver_config_enabled_discovery )
247
+
248
+ try :
249
+ # Check that the discovery class was instantiated
250
+ mocks ['mock_discovery_class' ].assert_called_once ()
251
+ finally :
252
+ # The stop method is already mocked, so we don't need to await it
253
+ pass
254
+ finally :
255
+ teardown_async_mocks (mocks )
256
+
257
+
258
+ # Common integration test logic
259
+ def perform_integration_test_checks (driver , is_async = False ):
260
+ """Common assertions for integration tests."""
261
+ assert_discovery_disabled (driver )
262
+
263
+
264
+ # Integration tests with real YDB
265
+ def test_integration_disable_discovery (driver_config_disabled_discovery ):
266
+ """Integration test that tests the disable_discovery feature with a real YDB container."""
267
+ # Create driver with discovery disabled
268
+ driver = ydb .Driver (driver_config = driver_config_disabled_discovery )
269
+ try :
270
+ driver .wait (timeout = 15 )
271
+ perform_integration_test_checks (driver )
272
+
273
+ # Try to execute a simple query to ensure it works with discovery disabled
274
+ with ydb .SessionPool (driver ) as pool :
275
+ def query_callback (session ):
276
+ result_sets = session .transaction ().execute (TEST_QUERY , commit_tx = True )
277
+ assert len (result_sets ) == 1
278
+ assert result_sets [0 ].rows [0 ].sum == 3
279
+
280
+ pool .retry_operation_sync (query_callback )
281
+ finally :
282
+ driver .stop (timeout = 10 )
283
+
284
+
285
+ @pytest .mark .asyncio
286
+ async def test_integration_aio_disable_discovery (driver_config_disabled_discovery ):
287
+ """Integration test that tests the disable_discovery feature with a real YDB container (async)."""
288
+ # Create driver with discovery disabled
289
+ driver = ydb .aio .Driver (driver_config = driver_config_disabled_discovery )
290
+ try :
291
+ await driver .wait (timeout = 15 )
292
+ perform_integration_test_checks (driver , is_async = True )
293
+
294
+ # Try to execute a simple query to ensure it works with discovery disabled
295
+ session_pool = ydb .aio .SessionPool (driver , size = 10 )
296
+
297
+ async def query_callback (session ):
298
+ result_sets = await session .transaction ().execute (TEST_QUERY , commit_tx = True )
299
+ assert len (result_sets ) == 1
300
+ assert result_sets [0 ].rows [0 ].sum == 3
301
+
302
+ try :
303
+ await session_pool .retry_operation (query_callback )
304
+ finally :
305
+ await session_pool .stop ()
306
+ finally :
307
+ await driver .stop (timeout = 10 )
0 commit comments