1+ import pytest
2+ import threading
3+ from unittest .mock import Mock , MagicMock
4+ from backend .agents .agent_run_manager import AgentRunManager , agent_run_manager
5+
6+
7+ class TestAgentRunManager :
8+ def setup_method (self ):
9+ """Reset manager before each test"""
10+ # Create a fresh instance for testing
11+ self .manager = AgentRunManager ()
12+ # Clear any existing state
13+ self .manager .agent_runs .clear ()
14+
15+ def test_singleton_pattern (self ):
16+ """Test that AgentRunManager is a singleton"""
17+ manager1 = AgentRunManager ()
18+ manager2 = AgentRunManager ()
19+ assert manager1 is manager2
20+
21+ def test_get_run_key (self ):
22+ """Test _get_run_key method generates correct keys"""
23+ key1 = self .manager ._get_run_key (123 , "user1" )
24+ key2 = self .manager ._get_run_key (456 , "user1" )
25+ key3 = self .manager ._get_run_key (123 , "user2" )
26+
27+ assert key1 == "user1:123"
28+ assert key2 == "user1:456"
29+ assert key3 == "user2:123"
30+ assert key1 != key2
31+ assert key1 != key3
32+ assert key2 != key3
33+
34+ def test_register_agent_run (self ):
35+ """Test registering an agent run"""
36+ conversation_id = 123
37+ user_id = "user1"
38+ mock_run_info = Mock ()
39+
40+ self .manager .register_agent_run (conversation_id , mock_run_info , user_id )
41+
42+ # Check that the run is registered with correct key
43+ run_key = f"{ user_id } :{ conversation_id } "
44+ assert run_key in self .manager .agent_runs
45+ assert self .manager .agent_runs [run_key ] == mock_run_info
46+
47+ def test_register_agent_run_multiple_users (self ):
48+ """Test registering agent runs for multiple users with same conversation_id"""
49+ conversation_id = 123
50+ user1_id = "user1"
51+ user2_id = "user2"
52+ mock_run_info1 = Mock ()
53+ mock_run_info2 = Mock ()
54+
55+ # Register runs for different users with same conversation_id
56+ self .manager .register_agent_run (conversation_id , mock_run_info1 , user1_id )
57+ self .manager .register_agent_run (conversation_id , mock_run_info2 , user2_id )
58+
59+ # Both should be registered with different keys
60+ key1 = f"{ user1_id } :{ conversation_id } "
61+ key2 = f"{ user2_id } :{ conversation_id } "
62+ assert key1 in self .manager .agent_runs
63+ assert key2 in self .manager .agent_runs
64+ assert self .manager .agent_runs [key1 ] == mock_run_info1
65+ assert self .manager .agent_runs [key2 ] == mock_run_info2
66+
67+ def test_register_agent_run_same_user_different_conversations (self ):
68+ """Test registering agent runs for same user with different conversation_ids"""
69+ user_id = "user1"
70+ conv_id1 = 123
71+ conv_id2 = 456
72+ mock_run_info1 = Mock ()
73+ mock_run_info2 = Mock ()
74+
75+ # Register runs for same user with different conversation_ids
76+ self .manager .register_agent_run (conv_id1 , mock_run_info1 , user_id )
77+ self .manager .register_agent_run (conv_id2 , mock_run_info2 , user_id )
78+
79+ # Both should be registered with different keys
80+ key1 = f"{ user_id } :{ conv_id1 } "
81+ key2 = f"{ user_id } :{ conv_id2 } "
82+ assert key1 in self .manager .agent_runs
83+ assert key2 in self .manager .agent_runs
84+ assert self .manager .agent_runs [key1 ] == mock_run_info1
85+ assert self .manager .agent_runs [key2 ] == mock_run_info2
86+
87+ def test_unregister_agent_run (self ):
88+ """Test unregistering an agent run"""
89+ conversation_id = 123
90+ user_id = "user1"
91+ mock_run_info = Mock ()
92+
93+ # Register first
94+ self .manager .register_agent_run (conversation_id , mock_run_info , user_id )
95+ run_key = f"{ user_id } :{ conversation_id } "
96+ assert run_key in self .manager .agent_runs
97+
98+ # Then unregister
99+ self .manager .unregister_agent_run (conversation_id , user_id )
100+ assert run_key not in self .manager .agent_runs
101+
102+ def test_unregister_agent_run_nonexistent (self ):
103+ """Test unregistering a non-existent agent run"""
104+ # Should not raise an exception
105+ self .manager .unregister_agent_run (999 , "nonexistent_user" )
106+ assert len (self .manager .agent_runs ) == 0
107+
108+ def test_get_agent_run_info (self ):
109+ """Test getting agent run info"""
110+ conversation_id = 123
111+ user_id = "user1"
112+ mock_run_info = Mock ()
113+
114+ # Initially no run info
115+ assert self .manager .get_agent_run_info (conversation_id , user_id ) is None
116+
117+ # Register a run
118+ self .manager .register_agent_run (conversation_id , mock_run_info , user_id )
119+
120+ # Should return the registered run info
121+ retrieved_info = self .manager .get_agent_run_info (conversation_id , user_id )
122+ assert retrieved_info == mock_run_info
123+
124+ def test_get_agent_run_info_wrong_user (self ):
125+ """Test getting agent run info with wrong user_id"""
126+ conversation_id = 123
127+ user1_id = "user1"
128+ user2_id = "user2"
129+ mock_run_info = Mock ()
130+
131+ # Register run for user1
132+ self .manager .register_agent_run (conversation_id , mock_run_info , user1_id )
133+
134+ # Try to get run info for user2 (should return None)
135+ retrieved_info = self .manager .get_agent_run_info (conversation_id , user2_id )
136+ assert retrieved_info is None
137+
138+ def test_stop_agent_run (self ):
139+ """Test stopping an agent run"""
140+ conversation_id = 123
141+ user_id = "user1"
142+ mock_run_info = Mock ()
143+ mock_stop_event = Mock ()
144+ mock_run_info .stop_event = mock_stop_event
145+
146+ # Register a run
147+ self .manager .register_agent_run (conversation_id , mock_run_info , user_id )
148+
149+ # Stop the run
150+ result = self .manager .stop_agent_run (conversation_id , user_id )
151+
152+ assert result is True
153+ mock_stop_event .set .assert_called_once ()
154+
155+ def test_stop_agent_run_nonexistent (self ):
156+ """Test stopping a non-existent agent run"""
157+ result = self .manager .stop_agent_run (999 , "nonexistent_user" )
158+ assert result is False
159+
160+ def test_stop_agent_run_wrong_user (self ):
161+ """Test stopping an agent run with wrong user_id"""
162+ conversation_id = 123
163+ user1_id = "user1"
164+ user2_id = "user2"
165+ mock_run_info = Mock ()
166+
167+ # Register run for user1
168+ self .manager .register_agent_run (conversation_id , mock_run_info , user1_id )
169+
170+ # Try to stop run for user2 (should return False)
171+ result = self .manager .stop_agent_run (conversation_id , user2_id )
172+ assert result is False
173+
174+ def test_thread_safety (self ):
175+ """Test thread safety of the manager"""
176+ conversation_id = 123
177+ user_id = "user1"
178+ mock_run_info = Mock ()
179+
180+ def register_run ():
181+ self .manager .register_agent_run (conversation_id , mock_run_info , user_id )
182+
183+ def unregister_run ():
184+ self .manager .unregister_agent_run (conversation_id , user_id )
185+
186+ # Create multiple threads
187+ threads = []
188+ for i in range (10 ):
189+ if i % 2 == 0 :
190+ thread = threading .Thread (target = register_run )
191+ else :
192+ thread = threading .Thread (target = unregister_run )
193+ threads .append (thread )
194+
195+ # Start all threads
196+ for thread in threads :
197+ thread .start ()
198+
199+ # Wait for all threads to complete
200+ for thread in threads :
201+ thread .join ()
202+
203+ # The manager should still be in a consistent state
204+ # (exact state depends on timing, but should not crash)
205+ assert isinstance (self .manager .agent_runs , dict )
206+
207+ def test_debug_mode_same_conversation_id (self ):
208+ """Test that debug mode with same conversation_id (-1) works for different users"""
209+ conversation_id = - 1 # Debug mode
210+ user1_id = "user1"
211+ user2_id = "user2"
212+ mock_run_info1 = Mock ()
213+ mock_run_info2 = Mock ()
214+
215+ # Register runs for different users with same conversation_id (-1)
216+ self .manager .register_agent_run (conversation_id , mock_run_info1 , user1_id )
217+ self .manager .register_agent_run (conversation_id , mock_run_info2 , user2_id )
218+
219+ # Both should be registered with different keys
220+ key1 = f"{ user1_id } :{ conversation_id } "
221+ key2 = f"{ user2_id } :{ conversation_id } "
222+ assert key1 in self .manager .agent_runs
223+ assert key2 in self .manager .agent_runs
224+ assert self .manager .agent_runs [key1 ] == mock_run_info1
225+ assert self .manager .agent_runs [key2 ] == mock_run_info2
226+
227+ # Should be able to get and stop each run independently
228+ retrieved1 = self .manager .get_agent_run_info (conversation_id , user1_id )
229+ retrieved2 = self .manager .get_agent_run_info (conversation_id , user2_id )
230+ assert retrieved1 == mock_run_info1
231+ assert retrieved2 == mock_run_info2
232+
233+ # Stop one run, the other should still exist
234+ result1 = self .manager .stop_agent_run (conversation_id , user1_id )
235+ assert result1 is True
236+
237+ # user1's run should be stopped, user2's should still exist
238+ retrieved1_after = self .manager .get_agent_run_info (conversation_id , user1_id )
239+ retrieved2_after = self .manager .get_agent_run_info (conversation_id , user2_id )
240+ assert retrieved1_after == mock_run_info1 # Still exists but stopped
241+ assert retrieved2_after == mock_run_info2 # Still exists and running
242+
243+ def test_global_instance (self ):
244+ """Test that the global agent_run_manager instance works"""
245+ conversation_id = 123
246+ user_id = "user1"
247+ mock_run_info = Mock ()
248+
249+ # Use the global instance
250+ agent_run_manager .register_agent_run (conversation_id , mock_run_info , user_id )
251+
252+ # Should be able to retrieve it
253+ retrieved_info = agent_run_manager .get_agent_run_info (conversation_id , user_id )
254+ assert retrieved_info == mock_run_info
255+
256+ # Should be able to stop it
257+ result = agent_run_manager .stop_agent_run (conversation_id , user_id )
258+ assert result is True
259+
260+ # Clean up
261+ agent_run_manager .unregister_agent_run (conversation_id , user_id )
262+
263+ def test_key_generation_edge_cases (self ):
264+ """Test _get_run_key with edge cases"""
265+ # Test with empty string user_id
266+ key1 = self .manager ._get_run_key (123 , "" )
267+ assert key1 == ":123"
268+
269+ # Test with special characters in user_id
270+ key2 = self .manager ._get_run_key (123 , "user:with:colons" )
271+ assert key2 == "user:with:colons:123"
272+
273+ # Test with negative conversation_id
274+ key3 = self .manager ._get_run_key (- 1 , "user1" )
275+ assert key3 == "user1:-1"
276+
277+ # Test with zero conversation_id
278+ key4 = self .manager ._get_run_key (0 , "user1" )
279+ assert key4 == "user1:0"
280+
281+ def test_concurrent_registration_same_key (self ):
282+ """Test concurrent registration with same key (should overwrite)"""
283+ conversation_id = 123
284+ user_id = "user1"
285+ mock_run_info1 = Mock ()
286+ mock_run_info2 = Mock ()
287+
288+ # Register first run
289+ self .manager .register_agent_run (conversation_id , mock_run_info1 , user_id )
290+
291+ # Register second run with same key (should overwrite)
292+ self .manager .register_agent_run (conversation_id , mock_run_info2 , user_id )
293+
294+ # Should have the second run info
295+ retrieved_info = self .manager .get_agent_run_info (conversation_id , user_id )
296+ assert retrieved_info == mock_run_info2
297+ assert retrieved_info != mock_run_info1
0 commit comments