@@ -110,6 +110,20 @@ def test_registers_to_llm(self, config, mock_llm_client):
110110 svc .create (llm_client = mock_llm_client )
111111 mock_instance .register_to_llm .assert_called_once_with (mock_llm_client )
112112
113+ def test_create_twice_replaces_manager (self , config , mock_llm_client ):
114+ """Second create() silently replaces manager."""
115+ with patch ("openspace.recording_service.RecordingManager" ) as MockRM :
116+ first = MagicMock ()
117+ second = MagicMock ()
118+ MockRM .side_effect = [first , second ]
119+
120+ svc = RecordingService (config = config )
121+ svc .create (llm_client = mock_llm_client )
122+ assert svc .manager is first
123+ svc .create (llm_client = mock_llm_client )
124+ assert svc .manager is second
125+ assert MockRM .call_count == 2
126+
113127
114128# ---------------------------------------------------------------------------
115129# RecordingService.wire()
@@ -133,6 +147,12 @@ def test_noop_when_no_manager(self, disabled_config, mock_llm_client, mock_groun
133147 svc .wire (grounding_client = mock_grounding_client )
134148 assert mock_grounding_client .recording_manager is None
135149
150+ def test_wire_without_create_is_noop (self , config , mock_grounding_client ):
151+ """wire() before create() is a safe noop."""
152+ svc = RecordingService (config = config )
153+ svc .wire (grounding_client = mock_grounding_client )
154+ assert mock_grounding_client .recording_manager is None
155+
136156
137157# ---------------------------------------------------------------------------
138158# RecordingService.cleanup()
@@ -141,7 +161,7 @@ def test_noop_when_no_manager(self, disabled_config, mock_llm_client, mock_groun
141161class TestRecordingServiceCleanup :
142162
143163 @pytest .mark .asyncio
144- async def test_stops_active_recording (self , config , mock_llm_client ):
164+ async def test_stops_active_recording_and_nulls_manager (self , config , mock_llm_client ):
145165 with patch ("openspace.recording_service.RecordingManager" ) as MockRM :
146166 mock_instance = MagicMock ()
147167 mock_instance .recording_status = True
@@ -152,6 +172,7 @@ async def test_stops_active_recording(self, config, mock_llm_client):
152172 svc .create (llm_client = mock_llm_client )
153173 await svc .cleanup ()
154174 mock_instance .stop .assert_awaited_once ()
175+ assert svc .manager is None
155176
156177 @pytest .mark .asyncio
157178 async def test_noop_when_not_recording (self , config , mock_llm_client ):
@@ -169,19 +190,38 @@ async def test_noop_when_not_recording(self, config, mock_llm_client):
169190 @pytest .mark .asyncio
170191 async def test_noop_when_no_manager (self , disabled_config ):
171192 svc = RecordingService (config = disabled_config )
172- await svc .cleanup () # No raise
193+ await svc .cleanup ()
194+ assert svc .manager is None
173195
174196 @pytest .mark .asyncio
175197 async def test_stop_exception_swallowed_and_logged (self , config , mock_llm_client ):
176- with patch ("openspace.recording_service.RecordingManager" ) as MockRM :
198+ with patch ("openspace.recording_service.RecordingManager" ) as MockRM , \
199+ patch ("openspace.recording_service.logger" ) as mock_logger :
177200 mock_instance = MagicMock ()
178201 mock_instance .recording_status = True
179202 mock_instance .stop = AsyncMock (side_effect = RuntimeError ("stop boom" ))
180203 MockRM .return_value = mock_instance
181204
182205 svc = RecordingService (config = config )
183206 svc .create (llm_client = mock_llm_client )
184- await svc .cleanup () # No raise — exception logged
207+ await svc .cleanup ()
208+ mock_logger .warning .assert_called_once ()
209+ assert "stop boom" in str (mock_logger .warning .call_args )
210+
211+ @pytest .mark .asyncio
212+ async def test_cleanup_twice_only_stops_once (self , config , mock_llm_client ):
213+ """Double cleanup is safe — second call is noop after manager nulled."""
214+ with patch ("openspace.recording_service.RecordingManager" ) as MockRM :
215+ mock_instance = MagicMock ()
216+ mock_instance .recording_status = True
217+ mock_instance .stop = AsyncMock ()
218+ MockRM .return_value = mock_instance
219+
220+ svc = RecordingService (config = config )
221+ svc .create (llm_client = mock_llm_client )
222+ await svc .cleanup ()
223+ await svc .cleanup () # second call — manager already None
224+ mock_instance .stop .assert_awaited_once ()
185225
186226
187227# ---------------------------------------------------------------------------
0 commit comments