88
99class TestAzureTopic (unittest .TestCase ):
1010
11+ @patch ('src.python_ms_core.core.topic.azure_topic.AutoLockRenewer' )
12+ @patch ('src.python_ms_core.core.topic.azure_topic.ServiceBusClient' )
13+ def test_init_sets_long_running_lock_renewal_defaults (self , mock_service_bus_client , mock_auto_lock_renewer ):
14+ mock_client = MagicMock ()
15+ mock_config = MagicMock (connection_string = 'Endpoint=sb://test/' )
16+ mock_renewer = MagicMock ()
17+
18+ mock_service_bus_client .from_connection_string .return_value = mock_client
19+ mock_client .get_topic_sender .return_value = MagicMock ()
20+ mock_auto_lock_renewer .return_value = mock_renewer
21+
22+ AzureTopic (config = mock_config , topic_name = 'mock-topic' , max_concurrent_messages = 1 )
23+
24+ mock_auto_lock_renewer .assert_called_once ()
25+ _ , kwargs = mock_auto_lock_renewer .call_args
26+ self .assertEqual (kwargs ['max_lock_renewal_duration' ], 86400 )
27+ self .assertEqual (kwargs ['max_workers' ], 2 )
28+ self .assertEqual (mock_renewer ._renew_period , 60 )
29+
1130 @patch ('src.python_ms_core.core.topic.azure_topic.AutoLockRenewer' )
1231 @patch ('src.python_ms_core.core.topic.azure_topic.ServiceBusClient' )
1332 def test_subscribe_settles_completed_tasks_on_receiver_loop (self , mock_service_bus_client , mock_auto_lock_renewer ):
@@ -16,6 +35,7 @@ def test_subscribe_settles_completed_tasks_on_receiver_loop(self, mock_service_b
1635 mock_message = MagicMock ()
1736 mock_future = MagicMock ()
1837 mock_config = MagicMock (connection_string = 'Endpoint=sb://test/' )
38+ mock_message ._lock_expired = False
1939
2040 mock_service_bus_client .from_connection_string .return_value = mock_client
2141 mock_client .get_topic_sender .return_value = MagicMock ()
@@ -48,6 +68,7 @@ def test_settle_task_logs_error_and_releases_slot(self, mock_service_bus_client,
4868 mock_message = MagicMock ()
4969 mock_future = MagicMock ()
5070 mock_config = MagicMock (connection_string = 'Endpoint=sb://test/' )
71+ mock_message ._lock_expired = False
5172
5273 mock_service_bus_client .from_connection_string .return_value = mock_client
5374 mock_client .get_topic_sender .return_value = MagicMock ()
@@ -65,6 +86,122 @@ def test_settle_task_logs_error_and_releases_slot(self, mock_service_bus_client,
6586 mock_logger .error .assert_called_once_with ('Error in settling message: Mocked settlement failure' )
6687 self .assertEqual (topic .internal_count , 0 )
6788
89+ @patch ('src.python_ms_core.core.topic.azure_topic.logger' )
90+ @patch ('src.python_ms_core.core.topic.azure_topic.AutoLockRenewer' )
91+ @patch ('src.python_ms_core.core.topic.azure_topic.ServiceBusClient' )
92+ def test_settle_task_skips_expired_message (self , mock_service_bus_client , mock_auto_lock_renewer , mock_logger ):
93+ mock_client = MagicMock ()
94+ mock_receiver = MagicMock ()
95+ mock_message = MagicMock ()
96+ mock_future = MagicMock ()
97+ mock_config = MagicMock (connection_string = 'Endpoint=sb://test/' )
98+
99+ mock_message ._lock_expired = True
100+ mock_message .message_id = 'message-1'
101+ mock_message .locked_until_utc = '2026-03-17T09:39:28Z'
102+ mock_message .auto_renew_error = None
103+ mock_service_bus_client .from_connection_string .return_value = mock_client
104+ mock_client .get_topic_sender .return_value = MagicMock ()
105+ mock_client .get_subscription_receiver .return_value = mock_receiver
106+ mock_future .result .return_value = [True , mock_message ]
107+
108+ topic = AzureTopic (config = mock_config , topic_name = 'mock-topic' , max_concurrent_messages = 1 )
109+ topic .receiver = mock_receiver
110+ topic .internal_count = 1
111+
112+ topic ._settle_task (mock_future , incoming_message = mock_message )
113+
114+ mock_receiver .complete_message .assert_not_called ()
115+ mock_receiver .abandon_message .assert_not_called ()
116+ mock_logger .error .assert_called_once_with (
117+ 'Skipping settlement for message message-1 because the lock expired at '
118+ '2026-03-17T09:39:28Z. auto_renew_error=None'
119+ )
120+ self .assertEqual (topic .internal_count , 0 )
121+
122+ @patch ('src.python_ms_core.core.topic.azure_topic.logger' )
123+ @patch ('src.python_ms_core.core.topic.azure_topic.AutoLockRenewer' )
124+ @patch ('src.python_ms_core.core.topic.azure_topic.ServiceBusClient' )
125+ def test_handle_lock_renew_failure_logs_when_sdk_returns_no_error (
126+ self ,
127+ mock_service_bus_client ,
128+ mock_auto_lock_renewer ,
129+ mock_logger ,
130+ ):
131+ mock_client = MagicMock ()
132+ mock_config = MagicMock (connection_string = 'Endpoint=sb://test/' )
133+ mock_message = MagicMock ()
134+
135+ mock_service_bus_client .from_connection_string .return_value = mock_client
136+ mock_client .get_topic_sender .return_value = MagicMock ()
137+ mock_message .message_id = 'message-1'
138+ mock_message .locked_until_utc = '2026-03-17T09:39:28Z'
139+ mock_message .auto_renew_error = None
140+
141+ topic = AzureTopic (config = mock_config , topic_name = 'mock-topic' , max_concurrent_messages = 1 )
142+ topic ._get_runtime_snapshot = MagicMock (
143+ return_value = (
144+ 'memory=rss_mb=128.00, vms_mb=256.00, num_threads=4, '
145+ 'cpu=process_percent=80.00, system_percent=91.00, '
146+ 'gc=enabled=True, counts=(1, 2, 3), thresholds=(700, 10, 10), '
147+ 'stats=gen0[collections=1, collected=2, uncollectable=0]'
148+ )
149+ )
150+
151+ topic ._handle_lock_renew_failure (mock_message , None )
152+
153+ mock_logger .error .assert_called_once_with (
154+ 'Error renewing lock for message message-1: lock expired before renewal could complete; '
155+ 'locked_until_utc=2026-03-17T09:39:28Z; '
156+ 'runtime_snapshot=memory=rss_mb=128.00, vms_mb=256.00, num_threads=4, '
157+ 'cpu=process_percent=80.00, system_percent=91.00, '
158+ 'gc=enabled=True, counts=(1, 2, 3), thresholds=(700, 10, 10), '
159+ 'stats=gen0[collections=1, collected=2, uncollectable=0]'
160+ )
161+
162+ @patch ('src.python_ms_core.core.topic.azure_topic.logger' )
163+ @patch ('src.python_ms_core.core.topic.azure_topic.AutoLockRenewer' )
164+ @patch ('src.python_ms_core.core.topic.azure_topic.ServiceBusClient' )
165+ def test_lock_renew_attempt_logs_memory_snapshot (
166+ self ,
167+ mock_service_bus_client ,
168+ mock_auto_lock_renewer ,
169+ mock_logger ,
170+ ):
171+ mock_client = MagicMock ()
172+ mock_config = MagicMock (connection_string = 'Endpoint=sb://test/' )
173+ mock_service_bus_client .from_connection_string .return_value = mock_client
174+ mock_client .get_topic_sender .return_value = MagicMock ()
175+ mock_auto_lock_renewer .return_value = MagicMock ()
176+
177+ topic = AzureTopic (config = mock_config , topic_name = 'mock-topic' , max_concurrent_messages = 1 )
178+ topic ._get_runtime_snapshot = MagicMock (
179+ return_value = (
180+ 'memory=rss_mb=64.00, vms_mb=128.00, num_threads=3, '
181+ 'cpu=process_percent=55.00, system_percent=70.00, '
182+ 'gc=enabled=True, counts=(4, 5, 6), thresholds=(700, 10, 10), '
183+ 'stats=gen0[collections=3, collected=10, uncollectable=0]'
184+ )
185+ )
186+
187+ mock_message = MagicMock ()
188+ mock_message .message_id = 'message-2'
189+ mock_message .locked_until_utc = '2026-03-17T09:39:33Z'
190+ topic .receiver = MagicMock ()
191+
192+ topic .lock_renew_receiver .renew_message_lock (mock_message )
193+
194+ mock_logger .info .assert_any_call (
195+ 'Attempting lock renewal for message %s; locked_until_utc=%s; runtime_snapshot=%s' ,
196+ 'message-2' ,
197+ '2026-03-17T09:39:33Z' ,
198+ 'memory=rss_mb=64.00, vms_mb=128.00, num_threads=3, '
199+ 'cpu=process_percent=55.00, system_percent=70.00, '
200+ 'gc=enabled=True, counts=(4, 5, 6), thresholds=(700, 10, 10), '
201+ 'stats=gen0[collections=3, collected=10, uncollectable=0]' ,
202+ )
203+ topic .receiver .renew_message_lock .assert_called_once_with (mock_message )
204+
68205
69206if __name__ == '__main__' :
70207 unittest .main ()
0 commit comments