@@ -1725,3 +1725,49 @@ def hold_lock_like_run_loop():
17251725 f"for { hold_seconds } s. The read path is blocked by the write lock "
17261726 f"(see HANG_REPRO.md)."
17271727 )
1728+
1729+
1730+ class TestEventServiceClose :
1731+ """Tests for EventService.close() awaiting conversation teardown."""
1732+
1733+ @pytest .mark .asyncio
1734+ async def test_close_awaits_conversation_close (self , event_service ):
1735+ """close() must await conversation.close(), not fire-and-forget."""
1736+ conversation = MagicMock (spec = Conversation )
1737+ event_service ._conversation = conversation
1738+
1739+ closed = asyncio .Event ()
1740+
1741+ def slow_close ():
1742+ # Simulate non-trivial teardown work
1743+ time .sleep (0.05 )
1744+ closed .set ()
1745+
1746+ conversation .close = slow_close
1747+
1748+ await event_service .close ()
1749+
1750+ assert closed .is_set (), (
1751+ "EventService.close() returned before conversation.close() finished"
1752+ )
1753+
1754+ @pytest .mark .asyncio
1755+ async def test_close_clears_conversation_reference (self , event_service ):
1756+ """close() must set _conversation to None after closing."""
1757+ conversation = MagicMock ()
1758+ event_service ._conversation = conversation
1759+
1760+ await event_service .close ()
1761+
1762+ assert event_service ._conversation is None
1763+
1764+ @pytest .mark .asyncio
1765+ async def test_close_is_idempotent (self , event_service ):
1766+ """Calling close() twice must not raise."""
1767+ conversation = MagicMock ()
1768+ event_service ._conversation = conversation
1769+
1770+ await event_service .close ()
1771+ await event_service .close () # second call — _conversation is already None
1772+
1773+ conversation .close .assert_called_once ()
0 commit comments