Skip to content

Commit 6fe33ac

Browse files
committed
feat(tests): add tests for several functionalities
chore(tests): add docker marker so it can be excluded from a run
1 parent ae1fea6 commit 6fe33ac

10 files changed

Lines changed: 655 additions & 1 deletion

File tree

app/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import sys
33

44

5-
if sys.version_info < (3, 11) or sys.version_info > (3, 13):
5+
if sys.version_info < (3, 11) or sys.version_info > (3, 13): # pragma: no cover
66
print(
77
"Warning: Unsupported Python version {ver}, please use 3.11-3.13".format(
88
ver=".".join(map(str, sys.version_info))

tests/openmanus/agent/test_base.py

Lines changed: 304 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,304 @@
1+
from calendar import c
2+
from unittest.mock import AsyncMock, MagicMock, patch
3+
4+
import pytest
5+
from pydantic import Field
6+
7+
from app.agent.base import ROLE_TYPE, AgentState, BaseAgent, Message
8+
from app.llm import LLM
9+
from app.sandbox.client import SANDBOX_CLIENT
10+
from app.schema import Memory, Role
11+
12+
13+
class MockAgent(BaseAgent):
14+
15+
name: str
16+
description: str | None = None
17+
18+
system_prompt: str | None = None
19+
next_step_prompt: str | None = None
20+
21+
llm: LLM = Field(default_factory=LLM)
22+
memory: Memory = Field(default_factory=Memory)
23+
state: AgentState = AgentState.IDLE
24+
25+
max_steps: int = 10
26+
current_step: int = 0
27+
28+
async def step(self) -> str:
29+
return "Mock step result"
30+
31+
32+
class MockFeaturedAgent(BaseAgent):
33+
34+
name: str
35+
description: str | None = None
36+
37+
system_prompt: str | None = None
38+
next_step_prompt: str | None = None
39+
40+
llm: LLM = Field(default_factory=LLM)
41+
memory: Memory = Field(default_factory=Memory)
42+
state: AgentState = AgentState.IDLE
43+
44+
max_steps: int = 10
45+
current_step: int = 0
46+
47+
__cnt = 0
48+
49+
async def step(self) -> str:
50+
self.__cnt += 1
51+
if self.__cnt > 4:
52+
self.state = AgentState.FINISHED
53+
return "Mock step result {}".format((self.__cnt - 1) % 2)
54+
55+
56+
@pytest.fixture
57+
def mock_agent():
58+
return MockAgent(
59+
name="TestAgent",
60+
system_prompt="System prompt",
61+
next_step_prompt="Next step prompt",
62+
)
63+
64+
65+
@pytest.fixture
66+
def mock_featured_agent():
67+
return MockFeaturedAgent(
68+
name="TestAgent",
69+
system_prompt="System prompt",
70+
next_step_prompt="Next step prompt",
71+
)
72+
73+
74+
@pytest.mark.asyncio
75+
async def test_run_happy_path(mock_agent):
76+
mock_agent.step = AsyncMock(return_value="Mock step result")
77+
SANDBOX_CLIENT.cleanup = AsyncMock()
78+
result = await mock_agent.run()
79+
assert (
80+
result
81+
== """Step 1: Mock step result
82+
Step 2: Mock step result
83+
Step 3: Mock step result
84+
Step 4: Mock step result
85+
Step 5: Mock step result
86+
Step 6: Mock step result
87+
Step 7: Mock step result
88+
Step 8: Mock step result
89+
Step 9: Mock step result
90+
Step 10: Mock step result
91+
Terminated: Reached max steps (10)"""
92+
)
93+
assert mock_agent.current_step == 0
94+
assert mock_agent.state == AgentState.IDLE
95+
SANDBOX_CLIENT.cleanup.assert_called_once()
96+
97+
98+
@pytest.mark.asyncio
99+
async def test_run_with_request(mock_agent):
100+
mock_agent.step = AsyncMock(return_value="Mock step result")
101+
SANDBOX_CLIENT.cleanup = AsyncMock()
102+
result = await mock_agent.run(request="User request")
103+
assert (
104+
result
105+
== """Step 1: Mock step result
106+
Step 2: Mock step result
107+
Step 3: Mock step result
108+
Step 4: Mock step result
109+
Step 5: Mock step result
110+
Step 6: Mock step result
111+
Step 7: Mock step result
112+
Step 8: Mock step result
113+
Step 9: Mock step result
114+
Step 10: Mock step result
115+
Terminated: Reached max steps (10)"""
116+
)
117+
assert mock_agent.current_step == 0 # reset at max_steps
118+
assert mock_agent.state == AgentState.IDLE
119+
assert mock_agent.messages[0].role == Role.USER
120+
assert mock_agent.messages[0].content == "User request"
121+
SANDBOX_CLIENT.cleanup.assert_called_once()
122+
123+
124+
@pytest.mark.asyncio
125+
async def test_run_max_steps(mock_agent):
126+
mock_agent.step = AsyncMock(return_value="Mock step result")
127+
SANDBOX_CLIENT.cleanup = AsyncMock()
128+
mock_agent.max_steps = 2
129+
result = await mock_agent.run()
130+
assert (
131+
result
132+
== "Step 1: Mock step result\nStep 2: Mock step result\nTerminated: Reached max steps (2)"
133+
)
134+
assert mock_agent.current_step == 0
135+
assert mock_agent.state == AgentState.IDLE
136+
SANDBOX_CLIENT.cleanup.assert_called_once()
137+
138+
139+
@pytest.mark.asyncio
140+
async def test_run_agent_already_running(mock_agent):
141+
mock_agent.state = AgentState.RUNNING
142+
with pytest.raises(RuntimeError) as e:
143+
await mock_agent.run()
144+
assert str(e.value) == "Cannot run agent from state: AgentState.RUNNING"
145+
146+
147+
@pytest.mark.asyncio
148+
async def test_run_agent_stuck(mock_agent):
149+
mock_agent.step = AsyncMock(return_value="Stuck result")
150+
SANDBOX_CLIENT.cleanup = AsyncMock()
151+
mock_agent.duplicate_threshold = 1
152+
mock_agent.update_memory(Role.ASSISTANT, "Stuck result")
153+
mock_agent.update_memory(Role.ASSISTANT, "Stuck result")
154+
result = await mock_agent.run()
155+
assert "Observed duplicate responses" in mock_agent.next_step_prompt
156+
assert "Step 1: Stuck result" in result
157+
assert (
158+
mock_agent.current_step == 0
159+
) # it will run 10 times and just reset the counter
160+
assert mock_agent.state == AgentState.IDLE
161+
SANDBOX_CLIENT.cleanup.assert_called_once()
162+
163+
164+
@pytest.mark.asyncio
165+
async def test_update_memory_happy_path(mock_agent):
166+
mock_agent.update_memory(Role.USER, "User message")
167+
assert mock_agent.messages[0].role == Role.USER
168+
assert mock_agent.messages[0].content == "User message"
169+
170+
171+
@pytest.mark.asyncio
172+
async def test_update_memory_unsupported_role(mock_agent):
173+
with pytest.raises(ValueError) as e:
174+
mock_agent.update_memory("unsupported", "Content")
175+
assert str(e.value) == "Unsupported message role: unsupported"
176+
177+
178+
@pytest.mark.asyncio
179+
async def test_is_stuck_not_enough_messages(mock_agent):
180+
assert not mock_agent.is_stuck()
181+
182+
183+
@pytest.mark.asyncio
184+
async def test_is_stuck_no_duplicates(mock_agent):
185+
mock_agent.update_memory(Role.ASSISTANT, "Unique result 1")
186+
mock_agent.update_memory(Role.ASSISTANT, "Unique result 2")
187+
assert not mock_agent.is_stuck()
188+
189+
190+
@pytest.mark.asyncio
191+
async def test_is_stuck_with_duplicates(mock_agent):
192+
mock_agent.update_memory(Role.ASSISTANT, "Duplicate result")
193+
mock_agent.update_memory(Role.ASSISTANT, "Duplicate result")
194+
mock_agent.update_memory(Role.ASSISTANT, "Duplicate result")
195+
assert mock_agent.is_stuck()
196+
197+
198+
@pytest.mark.asyncio
199+
async def test_is_stuck_different_roles(mock_agent):
200+
mock_agent.update_memory(Role.ASSISTANT, "First Message")
201+
mock_agent.update_memory(Role.ASSISTANT, "Duplicate result")
202+
mock_agent.update_memory(Role.USER, "Duplicate result")
203+
mock_agent.update_memory(Role.ASSISTANT, "Duplicate result")
204+
assert not mock_agent.is_stuck()
205+
206+
207+
@pytest.mark.asyncio
208+
async def test_auto_fix_weird_llm(mock_agent):
209+
class not_a_llm_object:
210+
a = "hello"
211+
b = "handsome"
212+
c = "manna" and "poem"
213+
214+
mock_agent.llm = not_a_llm_object_instance = not_a_llm_object()
215+
216+
mock_agent.initialize_agent()
217+
assert (
218+
isinstance(mock_agent.llm, LLM)
219+
and not_a_llm_object_instance is not mock_agent.llm
220+
)
221+
222+
223+
@pytest.mark.asyncio
224+
async def test_auto_fix_weird_memory(mock_agent):
225+
class not_a_memory_object:
226+
a = "not"
227+
b = "memory"
228+
c = "object"
229+
d = "absolutely"
230+
231+
mock_agent.memory = not_a_memory_object_instance = not_a_memory_object()
232+
233+
mock_agent.initialize_agent()
234+
235+
assert isinstance(mock_agent.memory, Memory)
236+
assert not_a_memory_object_instance is not mock_agent.memory
237+
238+
239+
@pytest.mark.asyncio
240+
async def test_state_context_invalid_new_state(mock_agent):
241+
class not_a_state_object:
242+
a = "not"
243+
b = "state"
244+
c = "object"
245+
246+
new_state = not_a_state_object()
247+
248+
with pytest.raises(ValueError) as e:
249+
async with mock_agent.state_context(new_state):
250+
pass
251+
assert "Invalid state:" in str(e.value)
252+
253+
254+
@pytest.mark.asyncio
255+
async def test_state_context_raise_exception(mock_agent):
256+
prev_state = mock_agent.state
257+
with pytest.raises(RuntimeError) as e:
258+
async with mock_agent.state_context(AgentState.RUNNING):
259+
raise RuntimeError("Test exception 123123")
260+
# assert mock_agent.state == AgentState.ERROR
261+
assert str(e.value) == "Test exception 123123"
262+
assert mock_agent.state == prev_state
263+
264+
265+
@pytest.mark.asyncio
266+
async def test_run_with_request_featured_agent(mock_featured_agent):
267+
SANDBOX_CLIENT.cleanup = AsyncMock()
268+
result = await mock_featured_agent.run(request="User request")
269+
assert (
270+
result
271+
== """Step 1: Mock step result 0
272+
Step 2: Mock step result 1
273+
Step 3: Mock step result 0
274+
Step 4: Mock step result 1
275+
Step 5: Mock step result 0"""
276+
)
277+
assert mock_featured_agent.current_step == 5
278+
assert mock_featured_agent.state == AgentState.IDLE
279+
assert mock_featured_agent.messages[0].role == Role.USER
280+
assert mock_featured_agent.messages[0].content == "User request"
281+
SANDBOX_CLIENT.cleanup.assert_called_once()
282+
283+
284+
@pytest.mark.asyncio
285+
async def test_is_stuck_invalid_last_message(mock_agent):
286+
mock_agent.update_memory(Role.ASSISTANT, "First Message")
287+
mock_agent.update_memory(Role.ASSISTANT, "Duplicate result")
288+
mock_agent.update_memory(Role.USER, "Duplicate result")
289+
mock_agent.update_memory(Role.ASSISTANT, None)
290+
assert not mock_agent.is_stuck()
291+
292+
293+
@pytest.mark.asyncio
294+
async def test_agent_message_assignment(mock_agent):
295+
class test_messages_object:
296+
manna = "poem"
297+
298+
a = test_messages_object()
299+
300+
assert mock_agent.messages is not a
301+
302+
mock_agent.messages = a
303+
304+
assert mock_agent.messages is a

0 commit comments

Comments
 (0)