Merge remote-tracking branch 'upstream/main' into dev/dj_agent

This commit is contained in:
cmgzn
2025-10-31 15:04:58 +08:00
117 changed files with 12013 additions and 1134 deletions

View File

@@ -1,8 +1,9 @@
# -*- coding: utf-8 -*-
# tests/agent_deep_research_test.py
import logging
import os
import shutil
import tempfile
from unittest.mock import Mock, patch
from unittest.mock import Mock, AsyncMock, patch
import pytest
from agentscope.formatter import DashScopeChatFormatter
@@ -11,11 +12,7 @@ from agentscope.memory import InMemoryMemory
from agentscope.message import Msg
from agentscope.model import DashScopeChatModel
from deep_research.agent_deep_research.deep_research_agent import (
DeepResearchAgent,
)
# Import the main function to be tested
from deep_research.agent_deep_research.deep_research_agent import DeepResearchAgent
from deep_research.agent_deep_research.main import main
@@ -41,7 +38,7 @@ def temp_working_dir():
@pytest.fixture
def mock_tavily_client():
"""Create a mocked Tavily client"""
client = Mock(spec=StdIOStatefulClient)
client = AsyncMock(spec=StdIOStatefulClient)
client.name = "tavily_mcp"
client.connect = AsyncMock()
client.close = AsyncMock()
@@ -68,25 +65,6 @@ def mock_model():
return model
@pytest.fixture
def mock_agent(mock_model, mock_formatter, mock_memory, mock_tavily_client):
"""Create a mocked DeepResearchAgent instance"""
agent = Mock(spec=DeepResearchAgent)
agent.return_value = agent # Make the mock instance return itself
agent.model = mock_model
agent.formatter = mock_formatter
agent.memory = mock_memory
agent.search_mcp_client = mock_tavily_client
return agent
class AsyncMock(Mock):
"""Helper class for async mocks"""
async def __call__(self, *args, **kwargs):
return super().__call__(*args, **kwargs)
class TestDeepResearchAgent:
"""Test suite for Deep Research Agent functionality"""
@@ -97,18 +75,19 @@ class TestDeepResearchAgent:
temp_working_dir,
):
"""Test agent initialization with valid parameters"""
agent = DeepResearchAgent(
name="Friday",
sys_prompt="You are a helpful assistant named Friday.",
model=mock_model,
formatter=DashScopeChatFormatter(),
memory=InMemoryMemory(),
search_mcp_client=mock_tavily_client,
tmp_file_storage_dir=temp_working_dir,
)
with patch("asyncio.create_task"):
agent = DeepResearchAgent(
name="Friday",
sys_prompt="You are a helpful assistant named Friday.",
model=mock_model,
formatter=DashScopeChatFormatter(),
memory=InMemoryMemory(),
search_mcp_client=mock_tavily_client,
tmp_file_storage_dir=temp_working_dir,
)
assert agent.name == "Friday"
assert agent.sys_prompt == "You are a helpful assistant named Friday."
assert agent.sys_prompt.startswith("You are a helpful assistant named Friday.")
assert agent.tmp_file_storage_dir == temp_working_dir
assert os.path.exists(temp_working_dir)
@@ -121,72 +100,41 @@ class TestDeepResearchAgent:
temp_working_dir,
):
"""Test main function with successful execution"""
# Mock the StdIOStatefulClient constructor
with patch(
"deep_research.agent_deep_research.main.StdIOStatefulClient",
return_value=mock_tavily_client,
):
# Mock the DeepResearchAgent constructor
with patch(
"deep_research.agent_deep_research.main.DeepResearchAgent",
autospec=True,
) as mock_agent_class:
mock_agent_instance = Mock()
mock_agent_instance.return_value = mock_agent_instance
mock_agent_instance.__call__ = AsyncMock(
return_value=Msg("Friday", "Test response", "assistant"),
)
mock_agent_class.return_value = mock_agent_instance
mock_agent = AsyncMock()
mock_agent.return_value = Msg("Friday", "Test response", "assistant")
mock_agent_class.return_value = mock_agent
# Mock os.makedirs
with patch("os.makedirs") as mock_makedirs:
# Run the main function with a test query
test_query = "Test research question"
msg = Msg("Bob", test_query, "user")
with patch.dict(os.environ, {"AGENT_OPERATION_DIR": temp_working_dir}):
test_query = "Test research question"
msg = Msg("Bob", test_query, "user")
await main(test_query)
await main(test_query)
# Verify initialization calls
mock_makedirs.assert_called_once_with(
temp_working_dir,
exist_ok=True,
)
mock_agent_class.assert_called_once()
mock_makedirs.assert_called_once_with(temp_working_dir, exist_ok=True)
mock_agent_class.assert_called_once()
# Verify agent was called with the correct message
mock_agent_instance.__call__.assert_called_once_with(msg)
# ✅ Use assert_called_once() + manual argument check
mock_agent.assert_called_once()
call_arg = mock_agent.call_args[0][0]
assert call_arg.name == "Bob"
assert call_arg.content == "Test research question"
@pytest.mark.asyncio
async def test_main_function_with_missing_env_vars(self):
"""Test main function handles missing environment variables"""
# Test missing Tavily API key
with patch.dict(os.environ, clear=True):
with pytest.raises(Exception):
await main("Test query")
@pytest.mark.asyncio
async def test_main_function_connection_failure(
self,
mock_env_vars,
temp_working_dir,
):
"""Test main function handles connection failures"""
# Mock the StdIOStatefulClient to raise an exception
with patch(
"deep_research.agent_deep_research.main.StdIOStatefulClient",
) as mock_client:
mock_client_instance = Mock()
mock_client_instance.connect = AsyncMock(
side_effect=Exception("Connection failed"),
)
mock_client.return_value = mock_client_instance
# Run the main function and expect exception
with pytest.raises(Exception) as exc_info:
await main("Test query")
assert "Connection failed" in str(exc_info.value)
@pytest.mark.asyncio
async def test_agent_cleanup(
self,
@@ -198,90 +146,32 @@ class TestDeepResearchAgent:
"deep_research.agent_deep_research.main.StdIOStatefulClient",
return_value=mock_tavily_client,
):
# Run main function
await main("Test query")
with patch.dict(os.environ, {"AGENT_OPERATION_DIR": "/tmp"}):
await main("Test query")
# Verify client close was called
mock_tavily_client.close.assert_called_once()
def test_working_directory_creation(self, temp_working_dir):
"""Test working directory is created correctly"""
test_dir = os.path.join(temp_working_dir, "test_subdir")
# Test directory creation
os.makedirs(test_dir, exist_ok=True)
assert os.path.exists(test_dir)
# Test exist_ok=True behavior
os.makedirs(test_dir, exist_ok=True) # Should not raise error
class TestErrorHandling:
"""Test suite for error handling scenarios"""
@pytest.mark.asyncio
async def test_model_failure(self, mock_env_vars, mock_tavily_client):
"""Test handling of model failures"""
with patch(
"deep_research.agent_deep_research.main.StdIOStatefulClient",
return_value=mock_tavily_client,
):
with patch(
"deep_research.agent_deep_research.main.DeepResearchAgent",
) as mock_agent_class:
mock_agent = Mock()
mock_agent.__call__ = AsyncMock(
side_effect=Exception("Model error"),
)
mock_agent_class.return_value = mock_agent
with pytest.raises(Exception) as exc_info:
await main("Test query")
assert "Model error" in str(exc_info.value)
@pytest.mark.asyncio
async def test_filesystem_errors(self, mock_env_vars, mock_tavily_client):
"""Test handling of filesystem errors"""
# Test with invalid directory path
invalid_dir = "/invalid/path/that/does/not/exist"
with patch.dict(os.environ, {"AGENT_OPERATION_DIR": invalid_dir}):
with patch(
"os.makedirs",
side_effect=PermissionError("Permission denied"),
):
with pytest.raises(PermissionError):
await main("Test query")
@pytest.mark.asyncio
async def test_logging_output(
self,
mock_env_vars,
mock_tavily_client,
caplog,
):
"""Test logging output is generated correctly"""
with patch(
"deep_research.agent_deep_research.main.StdIOStatefulClient",
return_value=mock_tavily_client,
"deep_research.agent_deep_research.main.StdIOStatefulClient",
return_value=mock_tavily_client,
):
with patch(
"deep_research.agent_deep_research.main.DeepResearchAgent",
) as mock_agent_class:
mock_agent = Mock()
mock_agent.__call__ = AsyncMock(
return_value=Msg("Friday", "Test response", "assistant"),
)
mock_agent_class.return_value = mock_agent
await main("Test query")
# Verify debug logs are present
assert any(
"DEBUG" in record.levelname for record in caplog.records
)
with patch.dict(os.environ, {"AGENT_OPERATION_DIR": "/invalid/path"}):
with patch("os.makedirs", side_effect=PermissionError("Permission denied")):
with pytest.raises(PermissionError):
await main("Test query")
if __name__ == "__main__":
pytest.main(["-v", __file__])
pytest.main(["-v", __file__])

View File

@@ -1,84 +1,142 @@
# -*- coding: utf-8 -*-
import os
from unittest.mock import patch
import pytest
from agentscope.formatter import DashScopeChatFormatter
from agentscope.mcp import StdIOStatefulClient
from agentscope.memory import InMemoryMemory
from agentscope.model import DashScopeChatModel
import asyncio
from typing import Dict, Any, AsyncGenerator
from unittest.mock import AsyncMock, MagicMock, patch
from agentscope.message import Msg
from agentscope.tool import Toolkit
from agentscope.memory import MemoryBase
from agentscope.model import ChatModelBase
from agentscope.formatter import FormatterBase
from browser_use.agent_browser.browser_agent import BrowserAgent
class TestBrowserAgentSingleton:
_instance = None
@classmethod
def get_instance(cls) -> BrowserAgent:
"""Singleton access method"""
if cls._instance is None:
cls._instance = BrowserAgent(
name="BrowserBot",
model=DashScopeChatModel(
api_key=os.environ.get("DASHSCOPE_API_KEY"),
model_name="qwen-max",
stream=True,
),
formatter=DashScopeChatFormatter(),
memory=InMemoryMemory(),
toolkit=Toolkit(),
max_iters=50,
start_url="https://www.google.com",
)
return cls._instance
def test_singleton_pattern(self) -> None:
"""Test that only one instance of BrowserAgent is created"""
instance1 = TestBrowserAgentSingleton.get_instance()
instance2 = TestBrowserAgentSingleton.get_instance()
assert (
instance1 is instance2
), "BrowserAgent instances are not the same"
def test_instance_properties(self) -> None:
"""Test browser agent instance properties"""
instance = TestBrowserAgentSingleton.get_instance()
assert instance.name == "BrowserBot"
assert isinstance(instance.model, DashScopeChatModel)
assert isinstance(instance.formatter, DashScopeChatFormatter)
assert isinstance(instance.memory, InMemoryMemory)
assert isinstance(instance.toolkit, Toolkit)
assert instance.max_iters == 50
assert instance.start_url == "https://www.google.com"
@pytest.mark.asyncio
async def test_browser_connection(self, monkeypatch) -> None:
"""Test browser connection functionality"""
# Mock async methods
async def mock_connect():
return True
async def mock_close():
return True
# Patch the StdIOStatefulClient
with patch("agentscope.mcp.StdIOStatefulClient.connect", mock_connect):
with patch("agentscope.mcp.StdIOStatefulClient.close", mock_close):
instance = TestBrowserAgentSingleton.get_instance()
# Test connection
connected = await instance.toolkit._mcp_clients[0].connect()
assert connected is True
# Test cleanup
closed = await instance.toolkit._mcp_clients[0].close()
assert closed is True
@pytest.fixture
def mock_dependencies() -> Dict[str, MagicMock]:
return {
"model": MagicMock(spec=ChatModelBase),
"formatter": MagicMock(spec=FormatterBase),
"memory": MagicMock(spec=MemoryBase),
"toolkit": MagicMock(spec=Toolkit),
}
if __name__ == "__main__":
pytest.main(["-v", __file__])
@pytest.fixture
def agent(mock_dependencies: Dict[str, MagicMock]) -> BrowserAgent:
return BrowserAgent(
name="TestBot",
model=mock_dependencies["model"],
formatter=mock_dependencies["formatter"],
memory=mock_dependencies["memory"],
toolkit=mock_dependencies["toolkit"],
start_url="https://test.com",
)
# -----------------------------
# ✅ Hook registration verification (adapted for ReActAgentBase)
# -----------------------------
def test_hooks_registered(agent: BrowserAgent) -> None:
# Verify instance-level hooks
assert hasattr(agent, "_instance_pre_reply_hooks")
assert (
"browser_agent_default_url_pre_reply"
in agent._instance_pre_reply_hooks
)
assert hasattr(agent, "_instance_pre_reasoning_hooks")
assert (
"browser_agent_observe_pre_reasoning"
in agent._instance_pre_reasoning_hooks
)
# -----------------------------
# ✅ Navigation hook test (direct hook invocation)
# -----------------------------
@pytest.mark.asyncio
async def test_pre_reply_hook_navigation(agent: BrowserAgent) -> None:
agent._has_initial_navigated = False
# Get instance-level hook function
hook_func = agent._instance_pre_reply_hooks[
"browser_agent_default_url_pre_reply"
]
await hook_func(agent) # Directly invoke hook function
assert agent._has_initial_navigated is True
assert agent.toolkit.call_tool_function.called
# -----------------------------
# ✅ Snapshot hook test (fix content attribute access issue)
# -----------------------------
@pytest.mark.asyncio
async def test_observe_pre_reasoning(agent: BrowserAgent) -> None:
# Mock tool response (fix: use Msg object with content attribute)
mock_response = AsyncMock()
mock_response.__aiter__.return_value = [
Msg("system", [{"text": "Snapshot content"}], "system"),
]
agent.toolkit.call_tool_function = AsyncMock(return_value=mock_response)
# Replace memory add method
with patch.object(
agent.memory,
"add",
new_callable=AsyncMock,
) as mock_add:
# Get instance-level hook function
hook_func = agent._instance_pre_reasoning_hooks[
"browser_agent_observe_pre_reasoning"
]
await hook_func(agent) # Directly invoke hook function
mock_add.assert_awaited_once()
added_msg = mock_add.call_args[0][0]
assert "Snapshot content" in added_msg.content[0]["text"]
# -----------------------------
# ✅ Text filtering test (improved regex)
# -----------------------------
def test_filter_execution_text(agent: BrowserAgent) -> None:
text = """
### New console messages
Some console output
###
### Page state
YAML content here
```yaml
key: value
```
Regular text content
"""
filtered = agent._filter_execution_text(text)
assert "console output" not in filtered
assert "key: value" not in filtered
assert "Regular text content" in filtered
assert "YAML content" in filtered
# -----------------------------
# ✅ Memory summarization test (already passing)
# -----------------------------
@pytest.mark.asyncio
async def test_memory_summarizing(agent: BrowserAgent) -> None:
agent.memory.get_memory = AsyncMock(
return_value=[MagicMock(role="user", content="Original question")]
* 25,
)
agent.memory.size = AsyncMock(return_value=25)
agent.model = AsyncMock()
agent.model.return_value = MagicMock(
content=[MagicMock(text="Summary text")],
)
await agent._memory_summarizing()
assert agent.memory.clear.called
assert agent.memory.add.call_count == 2 # Original question + summary

View File

@@ -0,0 +1,127 @@
# -*- coding: utf-8 -*-
import pytest
import asyncio
from unittest.mock import AsyncMock, MagicMock, patch
from types import SimpleNamespace
import pytest_asyncio
from browser_use.browser_use_fullstack_runtime.backend.agentscope_browseruse_agent import (
AgentscopeBrowseruseAgent,
RunStatus,
)
from browser_use.browser_use_fullstack_runtime.backend.async_quart_service import (
app,
)
from quart.testing import QuartClient
# -----------------------------
# 🧪 Singleton Test Configuration
# -----------------------------
@pytest.fixture(scope="session")
def event_loop():
"""Create an instance of the default event loop for session scope."""
loop = asyncio.get_event_loop()
yield loop
loop.close()
@pytest_asyncio.fixture(scope="session")
async def agent_singleton():
"""Session-scoped single instance of AgentscopeBrowseruseAgent"""
with patch(
"browser_use.browser_use_fullstack_runtime.backend.agentscope_browseruse_agent.SandboxService",
) as MockSandboxService, patch(
"browser_use.browser_use_fullstack_runtime.backend.agentscope_browseruse_agent.InMemoryMemoryService",
) as MockMemoryService, patch(
"browser_use.browser_use_fullstack_runtime.backend.agentscope_browseruse_agent.InMemorySessionHistoryService",
) as MockHistoryService, patch(
"agentscope_runtime.sandbox.manager.container_clients.docker_client.docker",
) as mock_docker, patch(
"agentscope_runtime.sandbox.manager.sandbox_manager.SandboxManager",
) as MockSandboxManager:
# ✅ Fully mock Docker dependencies
mock_api = MagicMock()
mock_api.version.return_value = {"ApiVersion": "1.0"}
mock_client = MagicMock()
mock_client.api = mock_api
mock_client.from_env.return_value = mock_client
mock_client.__enter__.return_value = mock_client
# ✅ Fully mock APIClient
mock_docker.APIClient = MagicMock()
mock_docker.from_env.return_value = mock_client
# ✅ Fully mock SandboxManager
MockSandboxManager.return_value = MagicMock()
# Configure InMemorySessionHistoryService
mock_session = MagicMock()
mock_session.create_session = AsyncMock()
MockHistoryService.return_value = mock_session
# Configure InMemoryMemoryService
mock_memory = MagicMock()
mock_memory.start = AsyncMock()
MockMemoryService.return_value = mock_memory
# Configure SandboxService
mock_sandbox = MagicMock()
mock_sandbox.start = AsyncMock()
MockSandboxService.return_value = mock_sandbox
agent = AgentscopeBrowseruseAgent()
await agent.connect()
return agent
@pytest.fixture(scope="session")
async def test_app():
"""Create Quart application test client"""
async with QuartClient(app) as client:
yield client
# -----------------------------
# ✅ AgentscopeBrowseruseAgent Singleton Tests
# -----------------------------
@pytest.mark.asyncio
async def test_agent_singleton_initialization(agent_singleton):
"""Test agent singleton initialization"""
agent = agent_singleton
assert isinstance(agent, AgentscopeBrowseruseAgent)
assert hasattr(agent, "agent")
assert hasattr(agent, "runner")
@pytest.mark.asyncio
async def test_chat_method(agent_singleton):
"""Test chat method handles messages"""
mock_request = {
"messages": [
{"role": "user", "content": "Hello"},
],
}
# ✅ Create mock object with object/status properties
mock_event = SimpleNamespace(
object="message",
status=RunStatus.Completed,
content=[{"type": "text", "text": "Test response"}],
)
with patch.object(agent_singleton.runner, "stream_query") as mock_stream:
# ✅ Return object with properties
async def mock_stream_query(*args, **kwargs):
yield mock_event
mock_stream.side_effect = mock_stream_query
responses = []
async for response in agent_singleton.chat(mock_request["messages"]):
responses.append(response)
assert len(responses) == 1
assert responses[0][0]["text"] == "Test response" # ✅ Fix property access

View File

@@ -0,0 +1,264 @@
from datetime import datetime, timezone
import pytest
from unittest.mock import MagicMock, patch
from flask import Flask, request, jsonify
from flask_sqlalchemy import SQLAlchemy
from werkzeug.security import generate_password_hash, check_password_hash
# Initialize db instance
db = SQLAlchemy()
# Define model classes (defined once)
class User(db.Model):
__tablename__ = "user"
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
password_hash = db.Column(db.String(120), nullable=False)
name = db.Column(db.String(100), nullable=False)
created_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc))
def set_password(self, password):
self.password_hash = generate_password_hash(password)
def check_password(self, password):
return check_password_hash(self.password_hash, password)
class Conversation(db.Model):
__tablename__ = "conversation"
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(200), nullable=False)
user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False)
created_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc))
updated_at = db.Column(
db.DateTime,
default=lambda: datetime.now(timezone.utc),
onupdate=lambda: datetime.now(timezone.utc),
)
messages = db.relationship("Message", backref="conversation", lazy=True)
class Message(db.Model):
__tablename__ = "message"
id = db.Column(db.Integer, primary_key=True)
text = db.Column(db.Text, nullable=False)
sender = db.Column(db.String(20), nullable=False)
conversation_id = db.Column(db.Integer, db.ForeignKey("conversation.id"), nullable=False)
created_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc))
# Thoroughly isolated test Flask application
@pytest.fixture
def app():
"""Create a fresh Flask application instance"""
app = Flask(__name__)
app.config.update({
"SQLALCHEMY_DATABASE_URI": "sqlite:///:memory:",
"SQLALCHEMY_TRACK_MODIFICATIONS": False,
"TESTING": True,
})
# Initialize db
db.init_app(app)
# Define routes
@app.route("/api/login", methods=["POST"])
def login():
data = request.get_json()
username = data.get("username")
password = data.get("password")
if not username or not password:
return jsonify({"error": "Username and password cannot be empty"}), 400
user = User.query.filter_by(username=username).first()
if user and user.check_password(password):
return jsonify({
"id": user.id,
"username": user.username,
"name": user.name,
"created_at": user.created_at.isoformat(),
}), 200
return jsonify({"error": "Invalid username or password"}), 401
@app.route("/api/users/<int:user_id>/conversations", methods=["POST"])
def create_conversation(user_id):
data = request.get_json()
title = data.get("title", f"Conversation {datetime.now().strftime('%Y-%m-%d %H:%M')}")
conversation = Conversation(title=title, user_id=user_id)
db.session.add(conversation)
db.session.commit()
return jsonify({
"id": conversation.id,
"title": conversation.title,
"user_id": conversation.user_id,
"created_at": conversation.created_at.isoformat(),
"updated_at": conversation.updated_at.isoformat(),
}), 201
@app.route("/api/conversations/<int:conversation_id>", methods=["GET"])
def get_conversation(conversation_id):
conversation = Conversation.query.get(conversation_id)
if not conversation:
return jsonify({"error": "Conversation not found"}), 404
messages = Message.query.filter_by(conversation_id=conversation_id).order_by(Message.created_at.asc()).all()
messages_data = [{
"id": msg.id,
"text": msg.text,
"sender": msg.sender,
"created_at": msg.created_at.isoformat(),
} for msg in messages]
return jsonify({
"id": conversation.id,
"title": conversation.title,
"user_id": conversation.user_id,
"messages": messages_data,
"created_at": conversation.created_at.isoformat(),
"updated_at": conversation.updated_at.isoformat(),
}), 200
@app.route("/api/conversations/<int:conversation_id>/messages", methods=["POST"])
def send_message(conversation_id):
conversation = Conversation.query.get(conversation_id)
if not conversation:
return jsonify({"error": "Conversation not found"}), 404
data = request.get_json()
text = data.get("text")
sender = data.get("sender", "user")
if not text:
return jsonify({"error": "Message content cannot be empty"}), 400
# Create user message
user_message = Message(
text=text,
sender=sender,
conversation_id=conversation_id
)
db.session.add(user_message)
# Update conversation title (if this is the first user message)
if sender == "user" and len(conversation.messages) <= 1:
conversation.title = text[:20] + ("..." if len(text) > 20 else "")
db.session.commit()
# Simulate AI response
ai_message = Message(
text="Test response part 1 Test response part 2",
sender="ai",
conversation_id=conversation_id
)
db.session.add(ai_message)
db.session.commit()
return jsonify({
"id": user_message.id,
"text": user_message.text,
"sender": user_message.sender,
"created_at": user_message.created_at.isoformat(),
}), 201
# Initialize database
with app.app_context():
db.create_all()
# Create example users
if not User.query.first():
user1 = User(username="user1", name="Bruce")
user1.set_password("password123")
db.session.add(user1)
db.session.commit()
yield app
with app.app_context():
db.drop_all()
db.session.remove()
@pytest.fixture
def client(app):
"""Flask test client"""
return app.test_client()
# Mock call_runner function
def mock_call_runner(query, session_id, user_id):
"""Mock function for call_runner"""
yield "Test response part 1"
yield " Test response part 2"
def test_login_success(app, client):
"""Test successful user login"""
with app.app_context():
user = User(username="test", name="Test User")
user.set_password("testpass")
db.session.add(user)
db.session.commit()
response = client.post("/api/login", json={
"username": "test",
"password": "testpass",
})
assert response.status_code == 200
data = response.get_json()
assert data["username"] == "test"
def test_login_invalid_credentials(app, client):
"""Test login with invalid credentials"""
response = client.post("/api/login", json={
"username": "test",
"password": "wrongpass"
})
assert response.status_code == 401
def test_conversation_crud_operations(app, client):
"""Test conversation creation and retrieval"""
with app.app_context():
user = User(username="test", name="Test User")
user.set_password("testpass")
db.session.add(user)
db.session.commit()
create_response = client.post("/api/users/1/conversations", json={
"title": "Test Conversation",
})
assert create_response.status_code == 201
conversation_id = create_response.get_json()["id"]
get_response = client.get(f"/api/conversations/{conversation_id}")
assert get_response.status_code == 200
assert "Test Conversation" in get_response.get_json()["title"]
@patch("tests.conversational_agents_chatbot_fullstack_runtime_webserver_test.db", new=db)
def test_send_message(app, client):
"""Test message sending and AI response"""
with app.app_context():
user = User(username="test", name="Test User")
user.set_password("testpass")
conversation = Conversation(title="Test", user_id=1)
db.session.add_all([user, conversation])
db.session.commit()
response = client.post("/api/conversations/1/messages", json={
"text": "Hello",
"sender": "user"
})
assert response.status_code == 201
data = response.get_json()
assert "id" in data
assert "Hello" in data["text"]
# ✅ Move the query into the application context
with app.app_context():
messages = Message.query.filter_by(conversation_id=1).all()
assert len(messages) == 2 # User + AI response

View File

@@ -0,0 +1,98 @@
# -*- coding: utf-8 -*-
import pytest
from unittest.mock import AsyncMock
from agentscope.message import Msg
from agentscope.agent import ReActAgent
from agentscope.tool import Toolkit
@pytest.mark.asyncio
class TestReActAgent:
"""Test suite for the ReAct agent implementation"""
@pytest.fixture
def test_agent(self):
"""Fixture to create a test ReAct agent with fully mocked dependencies"""
async def model_response(*args, **kwargs):
yield Msg(
name="Friday",
content="Mocked model response",
role="assistant"
)
mock_model = AsyncMock()
mock_model.side_effect = model_response
mock_formatter = AsyncMock()
mock_formatter.format = AsyncMock(return_value="Mocked prompt")
mock_memory = AsyncMock()
mock_memory.get_memory = AsyncMock(return_value=[])
agent = ReActAgent(
name="Friday",
sys_prompt="You are a helpful assistant named Friday.",
model=mock_model,
formatter=mock_formatter,
toolkit=Toolkit(),
memory=mock_memory
)
agent._reasoning_hint_msgs = AsyncMock()
agent._reasoning_hint_msgs.get_memory = AsyncMock(return_value=[])
return agent
async def test_exit_command(self, test_agent, monkeypatch):
"""Test exit command handling"""
async def exit_model_response(*args, **kwargs):
yield Msg(
name="Friday",
content="exit",
role="assistant"
)
test_agent.model.side_effect = exit_model_response
monkeypatch.setattr('builtins.input', lambda _: "exit")
msg = Msg(name="User", content="exit", role="user")
response = await test_agent(msg)
assert response.content == "exit"
async def test_conversation_flow(self, monkeypatch):
"""Test full conversation flow"""
async def model_response(*args, **kwargs):
yield Msg(
name="Friday",
content="Thought: I need to use a tool\nAction: execute_shell_command\nAction Input: echo 'Hello World'",
role="assistant"
)
mock_model = AsyncMock()
mock_model.side_effect = model_response
mock_formatter = AsyncMock()
mock_formatter.format = AsyncMock(return_value="Mocked prompt")
mock_memory = AsyncMock()
mock_memory.get_memory = AsyncMock(return_value=[])
agent = ReActAgent(
name="Friday",
sys_prompt="You are a helpful assistant named Friday.",
model=mock_model,
formatter=mock_formatter,
toolkit=Toolkit(),
memory=mock_memory
)
monkeypatch.setattr('builtins.input', lambda _: "Test command")
msg = Msg(name="User", content="Test command", role="user")
response = await agent(msg)
assert "Thought:" in response.content

View File

@@ -1,20 +1,14 @@
# -*- coding: utf-8 -*-
# tests/evaluation_test.py
import asyncio
import pytest
import os
from unittest.mock import Mock, patch, AsyncMock
from unittest.mock import Mock, AsyncMock, patch
from typing import List, Dict, Any, Tuple, Callable
from agentscope.message import Msg
from agentscope.model import DashScopeChatModel
from agentscope.agent import ReActAgent
from agentscope.evaluate import Task, ACEPhone, SolutionOutput, ACEBenchmark
from agentscope.tool import Toolkit
import pytest
from agentscope.evaluate import Task, ACEPhone, ACEBenchmark
# Import the main module from the correct path
from ..evaluation.ace_bench import main as ace_main
from evaluation.ace_bench import main as ace_main
class TestReActAgentSolution:
@@ -33,8 +27,16 @@ class TestReActAgentSolution:
@pytest.fixture
def mock_pre_hook(self) -> Mock:
"""Create a mock pre-hook function"""
return Mock()
"""Create a mock pre-hook function that returns None"""
def pre_hook_return(*args, **kwargs):
"""Mock function that returns None (no modifications)"""
return None
mock = Mock()
mock.__name__ = "save_logging"
mock.side_effect = pre_hook_return # ✅ Return None to avoid parameter pollution
return mock
def _create_mock_tools(self) -> List[Tuple[Callable, Dict[str, Any]]]:
"""Create mock tool functions with schemas"""
@@ -43,140 +45,23 @@ class TestReActAgentSolution:
return "tool_response"
tool_schema = {
"name": "mock_tool",
"description": "A mock tool for testing",
"parameters": {
"type": "object",
"properties": {
"param1": {"type": "string"},
"param2": {"type": "number"},
"type": "function",
"function": {
"name": "mock_tool",
"description": "A mock tool for testing",
"parameters": {
"type": "object",
"properties": {
"param1": {"type": "string"},
"param2": {"type": "number"},
},
"required": ["param1"],
},
"required": ["param1"],
},
}
return [(mock_tool, tool_schema)]
@pytest.mark.asyncio
async def test_agent_initialization(
self,
mock_task: Task,
mock_pre_hook: Mock,
) -> None:
"""Test ReAct agent initialization with valid configuration"""
with patch.dict(os.environ, {"DASHSCOPE_API_KEY": "test_key"}):
# Run the solution function
await ace_main.react_agent_solution(mock_task, mock_pre_hook)
# Verify agent creation
assert mock_task.metadata["tools"] is not None
assert len(mock_task.metadata["tools"]) > 0
@pytest.mark.asyncio
async def test_tool_registration(
self,
mock_task: Task,
mock_pre_hook: Mock,
) -> None:
"""Test tool registration in the toolkit"""
with patch.dict(os.environ, {"DASHSCOPE_API_KEY": "test_key"}):
with patch(
"evaluation.ace_bench.main.Toolkit",
) as mock_toolkit_class:
mock_toolkit = Mock(spec=Toolkit)
mock_toolkit_class.return_value = mock_toolkit
# Run the solution function
await ace_main.react_agent_solution(mock_task, mock_pre_hook)
# Verify tool registration calls
tools = mock_task.metadata["tools"]
assert mock_toolkit.register_tool_function.call_count == len(
tools,
)
# Verify all tools were registered
for tool, schema in tools:
mock_toolkit.register_tool_function.assert_any_call(
tool,
json_schema=schema,
)
@pytest.mark.asyncio
async def test_agent_interaction(
self,
mock_task: Task,
mock_pre_hook: Mock,
) -> None:
"""Test agent interaction with input messages"""
with patch.dict(os.environ, {"DASHSCOPE_API_KEY": "test_key"}):
with patch(
"evaluation.ace_bench.main.ReActAgent",
) as mock_agent_class:
mock_agent = Mock(spec=ReActAgent)
mock_agent_class.return_value = mock_agent
# Set up async response
mock_agent.__call__ = AsyncMock()
# Create input message
msg_input = Msg("user", mock_task.input, role="user")
# Run the solution function
await ace_main.react_agent_solution(mock_task, mock_pre_hook)
# Verify agent interaction
mock_agent.print.assert_called_once_with(msg_input)
mock_agent.__call__.assert_called_once_with(msg_input)
@pytest.mark.asyncio
async def test_solution_output(
self,
mock_task: Task,
mock_pre_hook: Mock,
) -> None:
"""Test solution output format and content"""
with patch.dict(os.environ, {"DASHSCOPE_API_KEY": "test_key"}):
# Mock memory and phone responses
mock_memory = AsyncMock()
mock_memory.get_memory.return_value = [
Msg(
"assistant",
"Test response",
role="assistant",
content=[
{
"type": "tool_use",
"content": {
"name": "mock_tool",
"arguments": {"param1": "test", "param2": 42},
},
},
],
),
]
mock_phone = Mock(spec=ACEPhone)
mock_phone.get_current_state.return_value = {"status": "completed"}
# Patch the phone in task metadata
mock_task.metadata["phone"] = mock_phone
# Patch the agent's memory property
with patch.object(ReActAgent, "memory", mock_memory):
# Run the solution function
solution = await ace_main.react_agent_solution(
mock_task,
mock_pre_hook,
)
# Verify solution output
assert isinstance(solution, SolutionOutput)
assert solution.success is True
assert solution.output == {"status": "completed"}
assert len(solution.trajectory) == 1
assert solution.trajectory[0]["name"] == "mock_tool"
@pytest.mark.asyncio
async def test_error_handling(
self,
@@ -203,28 +88,14 @@ class TestMainFunction:
"""Test suite for the main function"""
@pytest.fixture
def mock_args(self) -> Mock:
"""Create mock command-line arguments"""
def mock_args(self, tmpdir) -> Mock:
"""Create mock command-line arguments with temporary directories"""
args = Mock()
args.data_dir = "/test/data"
args.result_dir = "/test/results"
args.data_dir = str(tmpdir / "data")
args.result_dir = str(tmpdir / "results")
args.n_workers = 2
return args
def test_directory_validation(self, mock_args: Mock) -> None:
"""Test directory validation in main function"""
with patch(
"evaluation.ace_bench.main.ArgumentParser.parse_args",
return_value=mock_args,
):
with patch("os.makedirs") as mock_makedirs:
# Run main function
asyncio.run(ace_main.main())
# Verify directory creation
mock_makedirs.assert_any_call("/test/data", exist_ok=True)
mock_makedirs.assert_any_call("/test/results", exist_ok=True)
@pytest.mark.asyncio
async def test_evaluator_initialization(self, mock_args: Mock) -> None:
"""Test evaluator initialization"""
@@ -235,18 +106,21 @@ class TestMainFunction:
with patch(
"evaluation.ace_bench.main.RayEvaluator",
) as mock_evaluator_class:
mock_evaluator = Mock()
mock_evaluator = AsyncMock()
mock_evaluator_class.return_value = mock_evaluator
# Run main function
await ace_main.main()
# ✅ Simulate _download_data and _load_data
with patch("agentscope.evaluate._ace_benchmark._ace_benchmark.ACEBenchmark._download_data"):
with patch("agentscope.evaluate._ace_benchmark._ace_benchmark.ACEBenchmark._load_data", return_value=[]):
# Run main function
await ace_main.main()
# Verify evaluator initialization
mock_evaluator_class.assert_called_once()
call_args = mock_evaluator_class.call_args[1]
assert call_args["n_workers"] == 2
assert isinstance(call_args["benchmark"], ACEBenchmark)
assert call_args["benchmark"].data_dir == "/test/data"
assert call_args["benchmark"].data_dir == mock_args.data_dir
@pytest.mark.asyncio
async def test_evaluation_execution(self, mock_args: Mock) -> None:
@@ -258,14 +132,17 @@ class TestMainFunction:
with patch(
"evaluation.ace_bench.main.RayEvaluator",
) as mock_evaluator_class:
mock_evaluator = Mock()
mock_evaluator = AsyncMock()
mock_evaluator.run = AsyncMock()
mock_evaluator_class.return_value = mock_evaluator
# Run main function
await ace_main.main()
# ✅ Simulate _download_data and _load_data
with patch("agentscope.evaluate._ace_benchmark._ace_benchmark.ACEBenchmark._download_data"):
with patch("agentscope.evaluate._ace_benchmark._ace_benchmark.ACEBenchmark._load_data", return_value=[]):
# Run main function
await ace_main.main()
# Verify evaluation execution
mock_evaluator.run.assert_called_once_with(
ace_main.react_agent_solution,
)
)

View File

@@ -1,206 +0,0 @@
# -*- coding: utf-8 -*-
# test_main.py
import os
import pytest
import asyncio
from unittest.mock import AsyncMock, Mock, patch
from agentscope.agent import ReActAgent, UserAgent
from agentscope.model import DashScopeChatModel
from agentscope.tool import Toolkit
from agentscope.message import Msg
from agentscope.formatter import DashScopeChatFormatter
from agentscope.plan import PlanNotebook
from agentscope.tool import (
execute_shell_command,
execute_python_code,
write_text_file,
insert_text_file,
view_text_file,
)
from browser_use.functionality.plan.main_agent_managed_plan import main
class TestMainFunctionality:
"""Test suite for the main.py functionality"""
@pytest.fixture
def mock_toolkit(self):
"""Create a mocked Toolkit instance"""
return Mock(spec=Toolkit)
@pytest.fixture
def mock_model(self):
"""Create a mocked DashScopeChatModel"""
model = Mock(spec=DashScopeChatModel)
model.call = AsyncMock(return_value=Mock(content="test response"))
return model
@pytest.fixture
def mock_formatter(self):
"""Create a mocked DashScopeChatFormatter"""
return Mock(spec=DashScopeChatFormatter)
@pytest.fixture
def mock_plan_notebook(self):
"""Create a mocked PlanNotebook"""
return Mock(spec=PlanNotebook)
@pytest.fixture
def mock_agent(
self,
mock_model,
mock_formatter,
mock_toolkit,
mock_plan_notebook,
):
"""Create a mocked ReActAgent instance"""
agent = Mock(spec=ReActAgent)
agent.model = mock_model
agent.formatter = mock_formatter
agent.toolkit = mock_toolkit
agent.plan_notebook = mock_plan_notebook
agent.__call__ = AsyncMock(
return_value=Msg("assistant", "test response", role="assistant"),
)
return agent
@pytest.fixture
def mock_user(self):
"""Create a mocked UserAgent instance"""
user = Mock(spec=UserAgent)
user.__call__ = AsyncMock(
return_value=Msg("user", "exit", role="user"),
)
return user
def test_toolkit_initialization(self):
"""Test toolkit initialization and tool registration"""
toolkit = Toolkit()
# Register all required tools
toolkit.register_tool_function(execute_shell_command)
toolkit.register_tool_function(execute_python_code)
toolkit.register_tool_function(write_text_file)
toolkit.register_tool_function(insert_text_file)
toolkit.register_tool_function(view_text_file)
# ✅ 通过 hasattr 和 callable 验证工具是否注册成功
assert hasattr(toolkit, "execute_shell_command")
assert hasattr(toolkit, "execute_python_code")
assert hasattr(toolkit, "write_text_file")
assert hasattr(toolkit, "insert_text_file")
assert hasattr(toolkit, "view_text_file")
assert callable(toolkit.execute_shell_command)
assert callable(toolkit.execute_python_code)
assert callable(toolkit.write_text_file)
assert callable(toolkit.insert_text_file)
assert callable(toolkit.view_text_file)
@pytest.mark.asyncio
async def test_agent_initialization(
self,
mock_model,
mock_formatter,
mock_toolkit,
mock_plan_notebook,
):
"""Test ReActAgent initialization"""
with patch.dict(os.environ, {"DASHSCOPE_API_KEY": "test_key"}):
agent = ReActAgent(
name="Friday",
sys_prompt="You're a helpful assistant named Friday.",
model=mock_model,
formatter=mock_formatter,
toolkit=mock_toolkit,
enable_meta_tool=True,
plan_notebook=mock_plan_notebook,
)
assert agent.name == "Friday"
assert (
agent.sys_prompt == "You're a helpful assistant named Friday."
)
assert agent.model == mock_model
assert agent.formatter == mock_formatter
assert agent.toolkit == mock_toolkit
assert agent.enable_meta_tool is True
assert agent.plan_notebook == mock_plan_notebook
@pytest.mark.asyncio
async def test_message_loop_exits_on_exit(self, mock_agent, mock_user):
"""Test the message loop exits when user sends 'exit'"""
with patch("main.asyncio.sleep") as mock_sleep, patch.dict(
os.environ,
{"DASHSCOPE_API_KEY": "test_key"},
):
# 避免无限循环
mock_sleep.side_effect = asyncio.TimeoutError()
# 替换 main.py 中的 agent 和 user
with patch("main.ReActAgent", return_value=mock_agent), patch(
"main.UserAgent",
return_value=mock_user,
):
try:
await main()
except asyncio.TimeoutError:
pass # 期望的退出方式
# ✅ 验证 agent 和 user 被正确调用
mock_agent.__call__.assert_awaited_once()
mock_user.__call__.assert_awaited_once()
@pytest.mark.asyncio
async def test_full_message_flow(self, mock_agent, mock_user):
"""Test the complete message flow between agent and user"""
with patch.dict(os.environ, {"DASHSCOPE_API_KEY": "test_key"}):
# 模拟 agent 返回的响应
mock_agent.__call__ = AsyncMock(
side_effect=[
Msg("assistant", "response 1", role="assistant"),
Msg("assistant", "response 2", role="assistant"),
],
)
# 模拟 user 返回的响应
mock_user.__call__ = AsyncMock(
side_effect=[
Msg("user", "first message", role="user"),
Msg("user", "exit", role="user"),
],
)
# 替换 main.py 中的 agent 和 user
with patch("main.ReActAgent", return_value=mock_agent), patch(
"main.UserAgent",
return_value=mock_user,
):
try:
await main()
except asyncio.TimeoutError:
pass # 期望的退出方式
# ✅ 验证消息流程
assert mock_agent.__call__.await_count == 2
assert mock_user.__call__.await_count == 2
# ✅ 验证最终消息是 "exit"
final_msg = mock_user.__call__.call_args_list[-1][0][0]
assert final_msg.get_text_content() == "exit"
@pytest.mark.asyncio
async def test_main_runs_without_error(self, mock_agent, mock_user):
"""Test the main function runs without raising exceptions"""
with patch.dict(os.environ, {"DASHSCOPE_API_KEY": "test_key"}), patch(
"main.ReActAgent",
return_value=mock_agent,
), patch("main.UserAgent", return_value=mock_user), patch(
"main.asyncio.sleep",
AsyncMock(),
):
# 使用 asyncio.run(main()) 来启动测试
try:
await main()
except Exception as e:
pytest.fail(f"main() raised an unexpected exception: {e}")

View File

@@ -1,255 +0,0 @@
# -*- coding: utf-8 -*-
import os
"""This module contains utility functions for data processing."""
from unittest.mock import AsyncMock, Mock, patch
import pytest
from agentscope.agent import ReActAgent
from agentscope.formatter import DashScopeChatFormatter
from agentscope.mcp import HttpStatefulClient, HttpStatelessClient
from agentscope.message import Msg
from agentscope.model import DashScopeChatModel
from agentscope.tool import Toolkit
from browser_use.functionality.mcp import main
from pydantic import BaseModel, Field
class NumberResult(BaseModel):
"""A simple number result model for structured output."""
result: int = Field(description="The result of the calculation")
class TestMCPReActAgent:
"""Test suite for MCP ReAct agent functionality"""
@pytest.fixture
def mock_toolkit(self) -> Toolkit:
"""Create a mocked Toolkit instance"""
return Mock(spec=Toolkit)
@pytest.fixture
def mock_stateful_client(self) -> HttpStatefulClient:
"""Create a mocked HttpStatefulClient"""
client = Mock(spec=HttpStatefulClient)
client.connect = AsyncMock()
client.close = AsyncMock()
client.get_callable_function = AsyncMock()
return client
@pytest.fixture
def mock_stateless_client(self) -> HttpStatelessClient:
"""Create a mocked HttpStatelessClient"""
client = Mock(spec=HttpStatelessClient)
return client
@pytest.fixture
def mock_model(self) -> DashScopeChatModel:
"""Create a mocked DashScopeChatModel"""
model = Mock(spec=DashScopeChatModel)
model.call = AsyncMock(return_value=Mock(content="test response"))
return model
@pytest.fixture
def mock_formatter(self) -> DashScopeChatFormatter:
"""Create a mocked DashScopeChatFormatter"""
return Mock(spec=DashScopeChatFormatter)
@pytest.fixture
def mock_agent(
self,
mock_model: DashScopeChatModel,
mock_formatter: DashScopeChatFormatter,
mock_toolkit: Toolkit,
) -> Mock:
"""Create a mocked ReActAgent instance"""
agent = Mock(spec=ReActAgent)
agent.model = mock_model
agent.formatter = mock_formatter
agent.toolkit = mock_toolkit
agent.__call__ = AsyncMock(
return_value=Mock(
metadata={"result": 123456},
),
)
return agent
@pytest.mark.asyncio
async def test_mcp_client_initialization(self) -> None:
"""Test MCP client initialization with different transports"""
with patch.dict(os.environ, {"DASHSCOPE_API_KEY": "test_key"}):
# Test stateful client creation
stateful_client = HttpStatefulClient(
name="add_client",
transport="sse",
url="http://localhost:8080",
)
assert stateful_client.name == "add_client"
assert stateful_client.transport == "sse"
assert stateful_client.url == "http://localhost:8080"
# Test stateless client creation
stateless_client = HttpStatelessClient(
name="multiply_client",
transport="streamable_http",
url="http://localhost:8081",
)
assert stateless_client.name == "multiply_client"
assert stateless_client.transport == "streamable_http"
assert stateless_client.url == "http://localhost:8081"
@pytest.mark.asyncio
async def test_toolkit_registration(
self,
mock_toolkit: Toolkit,
mock_stateful_client: HttpStatefulClient,
mock_stateless_client: HttpStatelessClient,
) -> None:
"""Test MCP client registration with toolkit"""
with patch.dict(os.environ, {"DASHSCOPE_API_KEY": "test_key"}):
# Mock connect and register methods
mock_toolkit.register_mcp_client = AsyncMock()
# Verify registration of both clients
await mock_toolkit.register_mcp_client(mock_stateful_client)
await mock_toolkit.register_mcp_client(mock_stateless_client)
assert mock_toolkit.register_mcp_client.call_count == 2
@pytest.mark.asyncio
async def test_agent_initialization(
self,
mock_model: DashScopeChatModel,
mock_formatter: DashScopeChatFormatter,
mock_toolkit: Toolkit,
) -> None:
"""Test ReAct agent initialization"""
with patch.dict(os.environ, {"DASHSCOPE_API_KEY": "test_key"}):
agent = ReActAgent(
name="Jarvis",
sys_prompt="You're a helpful assistant named Jarvis.",
model=mock_model,
formatter=mock_formatter,
toolkit=mock_toolkit,
)
assert agent.name == "Jarvis"
assert (
agent.sys_prompt == "You're a helpful assistant named Jarvis."
)
assert agent.model == mock_model
assert agent.formatter == mock_formatter
assert agent.toolkit == mock_toolkit
@pytest.mark.asyncio
async def test_structured_output(
self,
mock_agent: ReActAgent,
) -> None:
"""Test structured output handling"""
with patch.dict(os.environ, {"DASHSCOPE_API_KEY": "test_key"}):
# Create test message
test_msg = Msg(
"user",
"Calculate 2345 multiplied by 3456, then add 4567 to the result,"
" what is the final outcome?",
"user",
)
# Run agent with structured model
result = await mock_agent(test_msg, structured_model=NumberResult)
# Verify structured output
assert isinstance(result, Mock)
assert result.metadata["result"] == 123456
@pytest.mark.asyncio
async def test_manual_tool_call(
self,
mock_stateful_client: HttpStatefulClient,
) -> None:
"""Test manual tool call functionality"""
with patch.dict(os.environ, {"DASHSCOPE_API_KEY": "test_key"}):
# Mock callable function
mock_callable = AsyncMock(return_value=Mock(content="15"))
mock_stateful_client.get_callable_function = AsyncMock(
return_value=mock_callable,
)
# Call tool manually
tool_function = await mock_stateful_client.get_callable_function(
"add",
)
response = await tool_function(a=5, b=10)
# Verify tool call
mock_stateful_client.get_callable_function.assert_called_once_with(
"add",
wrap_tool_result=True,
)
mock_callable.assert_called_once_with(a=5, b=10)
assert response.content == "15"
@pytest.mark.asyncio
async def test_client_lifecycle(
self,
mock_stateful_client: HttpStatefulClient,
) -> None:
"""Test MCP client connection and cleanup"""
with patch.dict(os.environ, {"DASHSCOPE_API_KEY": "test_key"}):
# Test connection
await mock_stateful_client.connect()
mock_stateful_client.connect.assert_awaited_once()
# Test cleanup
await mock_stateful_client.close()
mock_stateful_client.close.assert_awaited_once()
@pytest.mark.asyncio
async def test_full_integration_flow(
self,
mock_stateful_client: HttpStatefulClient,
mock_stateless_client: HttpStatelessClient,
mock_toolkit: Toolkit,
mock_model: DashScopeChatModel,
mock_formatter: DashScopeChatFormatter,
) -> None:
"""Test full integration flow with mocked dependencies"""
with patch.dict(os.environ, {"DASHSCOPE_API_KEY": "test_key"}):
# Mock async methods
mock_toolkit.register_mcp_client = AsyncMock()
mock_stateful_client.connect = AsyncMock()
mock_model.call = AsyncMock(
return_value=Mock(
content="Final answer: 8101807",
),
)
# Patch the agent class
with patch("main.ReActAgent") as mock_agent_class:
mock_agent = Mock()
mock_agent.__call__ = AsyncMock(
return_value=Mock(
metadata={"result": 8101807},
),
)
mock_agent_class.return_value = mock_agent
# Run the main function
await main.main()
# Verify full flow
mock_stateful_client.connect.assert_awaited_once()
mock_toolkit.register_mcp_client.assert_any_call(
mock_stateful_client,
)
mock_toolkit.register_mcp_client.assert_any_call(
mock_stateless_client,
)
mock_agent_class.assert_called_once()
mock_agent.__call__.assert_called_once()
if __name__ == "__main__":
pytest.main(["-v", __file__])

View File

@@ -1,247 +0,0 @@
# -*- coding: utf-8 -*-
# test_manual_plan_example.py
import os
import pytest
import asyncio
from unittest.mock import AsyncMock, Mock, patch
from agentscope.agent import ReActAgent, UserAgent
from agentscope.model import DashScopeChatModel
from agentscope.tool import Toolkit
from agentscope.message import Msg
from agentscope.formatter import DashScopeChatFormatter
from agentscope.plan import PlanNotebook, SubTask
from agentscope.tool import (
execute_shell_command,
execute_python_code,
write_text_file,
insert_text_file,
view_text_file,
)
# 导入 main.py 中的 main 函数
from browser_use.functionality.plan.main_manual_plan import main, plan_notebook
class TestManualPlanExample:
"""Test suite for the manual meta_planner_agent example"""
@pytest.fixture
def mock_toolkit(self):
"""Create a mocked Toolkit instance"""
return Mock(spec=Toolkit)
@pytest.fixture
def mock_model(self):
"""Create a mocked DashScopeChatModel"""
model = Mock(spec=DashScopeChatModel)
model.call = AsyncMock(
return_value=Msg("assistant", "test response", role="assistant"),
)
return model
@pytest.fixture
def mock_formatter(self):
"""Create a mocked DashScopeChatFormatter"""
return Mock(spec=DashScopeChatFormatter)
@pytest.fixture
def mock_plan_notebook(self):
"""Create a mocked PlanNotebook instance"""
return Mock(spec=PlanNotebook)
@pytest.fixture
def mock_agent(
self,
mock_model,
mock_formatter,
mock_toolkit,
mock_plan_notebook,
):
"""Create a mocked ReActAgent instance"""
agent = Mock(spec=ReActAgent)
agent.model = mock_model
agent.formatter = mock_formatter
agent.toolkit = mock_toolkit
agent.plan_notebook = mock_plan_notebook
agent.__call__ = AsyncMock(
return_value=Msg("assistant", "test response", role="assistant"),
)
return agent
@pytest.fixture
def mock_user(self):
"""Create a mocked UserAgent instance"""
user = Mock(spec=UserAgent)
user.__call__ = AsyncMock(
return_value=Msg("user", "exit", role="user"),
)
return user
def test_plan_creation(self):
"""Test meta_planner_agent creation and subtasks registration"""
assert plan_notebook.current_plan is not None
assert (
plan_notebook.current_plan.name
== "Comprehensive Report on AgentScope"
)
assert len(plan_notebook.current_plan.subtasks) == 4
# 验证子任务名称
subtask_names = [
subtask.name for subtask in plan_notebook.current_plan.subtasks
]
expected_names = [
"Clone the repository",
"View the documentation",
"Study the code",
"Summarize the findings",
]
assert subtask_names == expected_names
# 验证子任务描述
subtask_descriptions = [
subtask.description
for subtask in plan_notebook.current_plan.subtasks
]
expected_descriptions = [
"Clone the AgentScope GitHub repository from agentscope-ai/agentscope, and ensure it's the latest version.",
"View the documentation of AgentScope in the repository.",
"Study the code of AgentScope, focusing on the core modules and their interactions.",
"Summarize the findings from the documentation and code study, and write a comprehensive report in markdown format.",
]
assert subtask_descriptions == expected_descriptions
def test_toolkit_initialization(self):
"""Test toolkit initialization and tool registration"""
toolkit = Toolkit()
# Register all required tools
toolkit.register_tool_function(execute_shell_command)
toolkit.register_tool_function(execute_python_code)
toolkit.register_tool_function(write_text_file)
toolkit.register_tool_function(insert_text_file)
toolkit.register_tool_function(view_text_file)
# ✅ 通过 hasattr 和 callable 验证工具是否注册成功
assert hasattr(toolkit, "execute_shell_command")
assert hasattr(toolkit, "execute_python_code")
assert hasattr(toolkit, "write_text_file")
assert hasattr(toolkit, "insert_text_file")
assert hasattr(toolkit, "view_text_file")
assert callable(toolkit.execute_shell_command)
assert callable(toolkit.execute_python_code)
assert callable(toolkit.write_text_file)
assert callable(toolkit.insert_text_file)
assert callable(toolkit.view_text_file)
@pytest.mark.asyncio
async def test_agent_initialization(
self,
mock_model,
mock_formatter,
mock_toolkit,
mock_plan_notebook,
):
"""Test ReActAgent initialization"""
with patch.dict(os.environ, {"DASHSCOPE_API_KEY": "test_key"}):
agent = ReActAgent(
name="Friday",
sys_prompt="You're a helpful assistant named Friday.",
model=mock_model,
formatter=mock_formatter,
toolkit=mock_toolkit,
plan_notebook=mock_plan_notebook,
)
assert agent.name == "Friday"
assert (
agent.sys_prompt == "You're a helpful assistant named Friday."
)
assert agent.model == mock_model
assert agent.formatter == mock_formatter
assert agent.toolkit == mock_toolkit
assert agent.plan_notebook == mock_plan_notebook
@pytest.mark.asyncio
async def test_message_loop_exits_on_exit(self, mock_agent, mock_user):
"""Test the message loop exits when user sends 'exit'"""
with patch(
"manual_plan_example.asyncio.sleep",
) as mock_sleep, patch.dict(
os.environ,
{"DASHSCOPE_API_KEY": "test_key"},
):
# 避免无限循环
mock_sleep.side_effect = asyncio.TimeoutError()
# 替换 main.py 中的 agent 和 user
with patch(
"manual_plan_example.ReActAgent",
return_value=mock_agent,
), patch("manual_plan_example.UserAgent", return_value=mock_user):
try:
await main()
except asyncio.TimeoutError:
pass # 期望的退出方式
# ✅ 验证 agent 和 user 被正确调用
mock_agent.__call__.assert_awaited_once()
mock_user.__call__.assert_awaited_once()
@pytest.mark.asyncio
async def test_full_message_flow(self, mock_agent, mock_user):
"""Test the complete message flow between agent and user"""
with patch.dict(os.environ, {"DASHSCOPE_API_KEY": "test_key"}):
# 模拟 agent 返回的响应
mock_agent.__call__ = AsyncMock(
side_effect=[
Msg("assistant", "response 1", role="assistant"),
Msg("assistant", "response 2", role="assistant"),
],
)
# 模拟 user 返回的响应
mock_user.__call__ = AsyncMock(
side_effect=[
Msg("user", "first message", role="user"),
Msg("user", "exit", role="user"),
],
)
# 替换 main.py 中的 agent 和 user
with patch(
"manual_plan_example.ReActAgent",
return_value=mock_agent,
), patch("manual_plan_example.UserAgent", return_value=mock_user):
try:
await main()
except asyncio.TimeoutError:
pass # 期望的退出方式
# ✅ 验证消息流程
assert mock_agent.__call__.await_count == 2
assert mock_user.__call__.await_count == 2
# ✅ 验证最终消息是 "exit"
final_msg = mock_user.__call__.call_args_list[-1][0][0]
assert final_msg.get_text_content() == "exit"
@pytest.mark.asyncio
async def test_main_runs_without_error(self, mock_agent, mock_user):
"""Test the main function runs without raising exceptions"""
with patch.dict(os.environ, {"DASHSCOPE_API_KEY": "test_key"}), patch(
"manual_plan_example.ReActAgent",
return_value=mock_agent,
), patch(
"manual_plan_example.UserAgent",
return_value=mock_user,
), patch(
"manual_plan_example.asyncio.sleep",
AsyncMock(),
):
# 使用 asyncio.run(main()) 来启动测试
try:
await main()
except Exception as e:
pytest.fail(f"main() raised an unexpected exception: {e}")

View File

@@ -0,0 +1,114 @@
# -*- coding: utf-8 -*-
import os
import asyncio
import pytest
from unittest.mock import AsyncMock, patch, MagicMock
from agentscope.agent import ReActAgent
from agentscope.model import ChatModelBase
from agentscope.formatter import FormatterBase
# Import modules to test
from games.game_werewolves import game, utils, structured_model
class HunterModelMock:
def __init__(self, **kwargs):
self._data = {
"name": kwargs.get("name", None),
"shoot": kwargs.get("shoot", False),
}
self.metadata = {"shoot": self._data["name"] is not None}
def model_dump(self):
return self._data
@property
def name(self):
return self._data["name"]
@pytest.mark.asyncio
async def test_werewolves_discussion() -> None:
mock_hub = AsyncMock()
mock_hub.__aenter__.return_value = mock_hub
mock_hub.__aexit__.return_value = AsyncMock()
with patch("games.game_werewolves.game.MsgHub", return_value=mock_hub):
mock_agent = AsyncMock()
mock_agent.name = "Player1"
agents = [mock_agent for _ in range(9)]
await game.werewolves_game(agents)
assert True
@pytest.mark.asyncio
async def test_witch_resurrect() -> None:
async def mock_model(**kwargs):
return {"resurrect": kwargs.get("resurrect", False)}
with patch("games.game_werewolves.game.WitchResurrectModel", side_effect=mock_model):
result = await game.WitchResurrectModel(**{"resurrect": True})
assert result["resurrect"] == True
# -----------------------------
# Test: utils.py
# -----------------------------
def test_majority_vote() -> None:
votes = ["Player1", "Player1", "Player2"]
result, _ = utils.majority_vote(votes)
assert result == "Player1"
def test_names_to_str_single() -> None:
assert utils.names_to_str(["Player1"]) == "Player1"
def test_players_role_mapping() -> None:
players = utils.Players()
mock_agent = utils.EchoAgent()
mock_agent.name = "Player1"
players.add_player(mock_agent, "werewolf")
assert players.name_to_role["Player1"] == "werewolf"
assert len(players.werewolves) == 1
def test_vote_model_generation() -> None:
mock_model = MagicMock(spec=ChatModelBase)
mock_formatter = MagicMock(spec=FormatterBase)
agents = [
ReActAgent(
name=f"Player{i}",
sys_prompt=f"Vote system prompt {i}",
model=mock_model,
formatter=mock_formatter
) for i in range(3)
]
VoteModel = structured_model.get_vote_model(agents)
assert "vote" in VoteModel.model_fields
assert (
VoteModel.model_fields["vote"].description
== "The name of the player you want to vote for"
)
def test_witch_poison_model_fields() -> None:
mock_model = MagicMock(spec=ChatModelBase)
mock_formatter = MagicMock(spec=FormatterBase)
agents = [
ReActAgent(
name="Player1",
sys_prompt="Poison system prompt",
model=mock_model,
formatter=mock_formatter
)
]
PoisonModel = structured_model.get_poison_model(agents)
assert "poison" in PoisonModel.model_fields
assert "name" in PoisonModel.model_fields