77# Ensure src/ is in sys.path for imports
88sys .path .insert (0 , os .path .abspath (os .path .join (os .path .dirname (__file__ ), '../../src' )))
99
10- from utils import chatloop
10+ from src . utils import chatloop
1111
1212def test_chatloop_decorator_creation ():
1313 """Test that the chatloop decorator returns a proper wrapper function."""
@@ -96,4 +96,74 @@ async def test_func(user_input):
9696 await wrapped ()
9797
9898 # Verify function was called twice (once for each input)
99- assert call_count == 2
99+ assert call_count == 2
100+
101+ @pytest .mark .asyncio
102+ async def test_chatloop_basic_execution ():
103+ """Test the chatloop decorator runs a function once and exits on KeyboardInterrupt."""
104+ # Create a mock function to be decorated
105+ mock_func = MagicMock ()
106+ mock_func .return_value = asyncio .Future ()
107+ mock_func .return_value .set_result ("Test response" )
108+
109+ # Apply the decorator
110+ decorated = chatloop ("TestChat" )(mock_func )
111+
112+ # Mock input/print functions and simulate KeyboardInterrupt after first iteration
113+ with patch ('builtins.input' , side_effect = ["Test input" , KeyboardInterrupt ()]):
114+ with patch ('builtins.print' ) as mock_print :
115+ await decorated ("arg1" , kwarg1 = "value1" )
116+
117+ # Verify the function was called with correct parameters
118+ mock_func .assert_called_once_with ("Test input" , "arg1" , kwarg1 = "value1" )
119+
120+ # Verify output was printed
121+ assert any ("Test response" in str (call ) for call in mock_print .call_args_list )
122+
123+ @pytest .mark .asyncio
124+ async def test_chatloop_exception_handling ():
125+ """Test the chatloop decorator handles exceptions properly."""
126+ # Create a mock function that raises an exception
127+ mock_func = MagicMock ()
128+ mock_func .side_effect = [Exception ("Test error" ), KeyboardInterrupt ()]
129+
130+ # Apply the decorator
131+ decorated = chatloop ("TestChat" )(mock_func )
132+
133+ # Mock input/print and execute
134+ with patch ('builtins.input' , return_value = "Test input" ):
135+ with patch ('builtins.print' ) as mock_print :
136+ await decorated ()
137+
138+ # Verify error was printed
139+ assert any ("Error: Test error" in str (call ) for call in mock_print .call_args_list )
140+
141+ @pytest .mark .asyncio
142+ async def test_chatloop_multiple_iterations ():
143+ """Test the chatloop decorator handles multiple chat iterations."""
144+ # Create a sequence of responses
145+ mock_func = MagicMock ()
146+ response_future1 = asyncio .Future ()
147+ response_future1 .set_result ("Response 1" )
148+ response_future2 = asyncio .Future ()
149+ response_future2 .set_result ("Response 2" )
150+
151+ mock_func .side_effect = [response_future1 , response_future2 ]
152+
153+ # Apply the decorator
154+ decorated = chatloop ("TestChat" )(mock_func )
155+
156+ # Mock inputs and simulate KeyboardInterrupt after second iteration
157+ with patch ('builtins.input' , side_effect = ["Input 1" , "Input 2" , KeyboardInterrupt ()]):
158+ with patch ('builtins.print' ) as mock_print :
159+ await decorated ()
160+
161+ # Verify the function was called twice with correct inputs
162+ assert mock_func .call_count == 2
163+ mock_func .assert_any_call ("Input 1" )
164+ mock_func .assert_any_call ("Input 2" )
165+
166+ # Verify both responses were printed
167+ printed_strings = [str (call ) for call in mock_print .call_args_list ]
168+ assert any ("Response 1" in s for s in printed_strings )
169+ assert any ("Response 2" in s for s in printed_strings )
0 commit comments