Add pre commit (#26)
This commit is contained in:
@@ -1,9 +1,8 @@
|
||||
# tests/agent_deep_research_test.py
|
||||
import logging
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
from unittest.mock import Mock, AsyncMock, patch
|
||||
from unittest.mock import Mock, AsyncMock, patch, MagicMock
|
||||
|
||||
import pytest
|
||||
from agentscope.formatter import DashScopeChatFormatter
|
||||
@@ -12,7 +11,9 @@ 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
|
||||
from deep_research.agent_deep_research.deep_research_agent import (
|
||||
DeepResearchAgent,
|
||||
)
|
||||
from deep_research.agent_deep_research.main import main
|
||||
|
||||
|
||||
@@ -70,12 +71,15 @@ class TestDeepResearchAgent:
|
||||
|
||||
def test_agent_initialization(
|
||||
self,
|
||||
mock_model,
|
||||
mock_tavily_client,
|
||||
temp_working_dir,
|
||||
mock_model, # pylint: disable=redefined-outer-name
|
||||
mock_tavily_client, # pylint: disable=redefined-outer-name
|
||||
temp_working_dir, # pylint: disable=redefined-outer-name
|
||||
):
|
||||
"""Test agent initialization with valid parameters"""
|
||||
with patch("asyncio.create_task"):
|
||||
mock_loop = MagicMock()
|
||||
mock_task = AsyncMock()
|
||||
mock_loop.create_task = MagicMock(return_value=mock_task)
|
||||
with patch("asyncio.get_running_loop", return_value=mock_loop):
|
||||
agent = DeepResearchAgent(
|
||||
name="Friday",
|
||||
sys_prompt="You are a helpful assistant named Friday.",
|
||||
@@ -87,17 +91,17 @@ class TestDeepResearchAgent:
|
||||
)
|
||||
|
||||
assert agent.name == "Friday"
|
||||
assert agent.sys_prompt.startswith("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)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_main_function_success(
|
||||
self,
|
||||
mock_env_vars,
|
||||
mock_tavily_client,
|
||||
mock_model,
|
||||
temp_working_dir,
|
||||
mock_tavily_client, # pylint: disable=redefined-outer-name
|
||||
temp_working_dir, # pylint: disable=redefined-outer-name
|
||||
):
|
||||
"""Test main function with successful execution"""
|
||||
with patch(
|
||||
@@ -109,17 +113,26 @@ class TestDeepResearchAgent:
|
||||
autospec=True,
|
||||
) as mock_agent_class:
|
||||
mock_agent = AsyncMock()
|
||||
mock_agent.return_value = Msg("Friday", "Test response", "assistant")
|
||||
mock_agent.return_value = Msg(
|
||||
"Friday",
|
||||
"Test response",
|
||||
"assistant",
|
||||
)
|
||||
mock_agent_class.return_value = mock_agent
|
||||
|
||||
with patch("os.makedirs") as mock_makedirs:
|
||||
with patch.dict(os.environ, {"AGENT_OPERATION_DIR": temp_working_dir}):
|
||||
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)
|
||||
|
||||
mock_makedirs.assert_called_once_with(temp_working_dir, exist_ok=True)
|
||||
mock_makedirs.assert_called_once_with(
|
||||
temp_working_dir,
|
||||
exist_ok=True,
|
||||
)
|
||||
mock_agent_class.assert_called_once()
|
||||
|
||||
# ✅ Use assert_called_once() + manual argument check
|
||||
@@ -138,8 +151,7 @@ class TestDeepResearchAgent:
|
||||
@pytest.mark.asyncio
|
||||
async def test_agent_cleanup(
|
||||
self,
|
||||
mock_env_vars,
|
||||
mock_tavily_client,
|
||||
mock_tavily_client, # pylint: disable=redefined-outer-name
|
||||
):
|
||||
"""Test proper cleanup of resources"""
|
||||
with patch(
|
||||
@@ -151,7 +163,10 @@ class TestDeepResearchAgent:
|
||||
|
||||
mock_tavily_client.close.assert_called_once()
|
||||
|
||||
def test_working_directory_creation(self, temp_working_dir):
|
||||
def test_working_directory_creation(
|
||||
self,
|
||||
temp_working_dir, # pylint: disable=redefined-outer-name
|
||||
):
|
||||
"""Test working directory is created correctly"""
|
||||
test_dir = os.path.join(temp_working_dir, "test_subdir")
|
||||
os.makedirs(test_dir, exist_ok=True)
|
||||
@@ -161,17 +176,28 @@ class TestDeepResearchAgent:
|
||||
|
||||
class TestErrorHandling:
|
||||
"""Test suite for error handling scenarios"""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_filesystem_errors(self, mock_env_vars, mock_tavily_client):
|
||||
async def test_filesystem_errors(
|
||||
self,
|
||||
mock_tavily_client, # pylint: disable=redefined-outer-name
|
||||
):
|
||||
"""Test handling of filesystem errors"""
|
||||
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.dict(os.environ, {"AGENT_OPERATION_DIR": "/invalid/path"}):
|
||||
with patch("os.makedirs", side_effect=PermissionError("Permission denied")):
|
||||
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__])
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import pytest
|
||||
import asyncio
|
||||
from typing import Dict, Any, AsyncGenerator
|
||||
from typing import Dict
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
import pytest
|
||||
from agentscope.message import Msg
|
||||
from agentscope.tool import Toolkit
|
||||
from agentscope.memory import MemoryBase
|
||||
@@ -22,7 +21,10 @@ def mock_dependencies() -> Dict[str, MagicMock]:
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def agent(mock_dependencies: Dict[str, MagicMock]) -> BrowserAgent:
|
||||
def agent(
|
||||
# pylint: disable=redefined-outer-name
|
||||
mock_dependencies: Dict[str, MagicMock],
|
||||
) -> BrowserAgent:
|
||||
return BrowserAgent(
|
||||
name="TestBot",
|
||||
model=mock_dependencies["model"],
|
||||
@@ -36,17 +38,28 @@ def agent(mock_dependencies: Dict[str, MagicMock]) -> BrowserAgent:
|
||||
# -----------------------------
|
||||
# ✅ Hook registration verification (adapted for ReActAgentBase)
|
||||
# -----------------------------
|
||||
def test_hooks_registered(agent: BrowserAgent) -> None:
|
||||
# Verify instance-level hooks
|
||||
assert hasattr(agent, "_instance_pre_reply_hooks")
|
||||
def test_hooks_registered(
|
||||
agent: BrowserAgent, # pylint: disable=redefined-outer-name
|
||||
) -> None:
|
||||
"""Verify instance-level hooks are registered"""
|
||||
# Disable pylint warning for protected member access
|
||||
assert hasattr(
|
||||
agent,
|
||||
"_instance_pre_reply_hooks",
|
||||
) # pylint: disable=protected-access
|
||||
assert (
|
||||
"browser_agent_default_url_pre_reply"
|
||||
# pylint: disable=protected-access
|
||||
in agent._instance_pre_reply_hooks
|
||||
)
|
||||
|
||||
assert hasattr(agent, "_instance_pre_reasoning_hooks")
|
||||
assert hasattr(
|
||||
agent,
|
||||
"_instance_pre_reasoning_hooks",
|
||||
) # pylint: disable=protected-access
|
||||
assert (
|
||||
"browser_agent_observe_pre_reasoning"
|
||||
# pylint: disable=protected-access
|
||||
in agent._instance_pre_reasoning_hooks
|
||||
)
|
||||
|
||||
@@ -55,15 +68,20 @@ def test_hooks_registered(agent: BrowserAgent) -> None:
|
||||
# ✅ Navigation hook test (direct hook invocation)
|
||||
# -----------------------------
|
||||
@pytest.mark.asyncio
|
||||
async def test_pre_reply_hook_navigation(agent: BrowserAgent) -> None:
|
||||
async def test_pre_reply_hook_navigation(
|
||||
agent: BrowserAgent, # pylint: disable=redefined-outer-name
|
||||
) -> None:
|
||||
# pylint: disable=protected-access
|
||||
agent._has_initial_navigated = False
|
||||
|
||||
# Get instance-level hook function
|
||||
# pylint: disable=protected-access
|
||||
hook_func = agent._instance_pre_reply_hooks[
|
||||
"browser_agent_default_url_pre_reply"
|
||||
]
|
||||
await hook_func(agent) # Directly invoke hook function
|
||||
|
||||
# pylint: disable=protected-access
|
||||
assert agent._has_initial_navigated is True
|
||||
assert agent.toolkit.call_tool_function.called
|
||||
|
||||
@@ -72,13 +90,17 @@ async def test_pre_reply_hook_navigation(agent: BrowserAgent) -> None:
|
||||
# ✅ Snapshot hook test (fix content attribute access issue)
|
||||
# -----------------------------
|
||||
@pytest.mark.asyncio
|
||||
async def test_observe_pre_reasoning(agent: BrowserAgent) -> None:
|
||||
async def test_observe_pre_reasoning(
|
||||
agent: BrowserAgent, # pylint: disable=redefined-outer-name
|
||||
) -> 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)
|
||||
agent.toolkit.call_tool_function = AsyncMock(
|
||||
return_value=mock_response,
|
||||
)
|
||||
|
||||
# Replace memory add method
|
||||
with patch.object(
|
||||
@@ -87,6 +109,7 @@ async def test_observe_pre_reasoning(agent: BrowserAgent) -> None:
|
||||
new_callable=AsyncMock,
|
||||
) as mock_add:
|
||||
# Get instance-level hook function
|
||||
# pylint: disable=protected-access
|
||||
hook_func = agent._instance_pre_reasoning_hooks[
|
||||
"browser_agent_observe_pre_reasoning"
|
||||
]
|
||||
@@ -100,7 +123,9 @@ async def test_observe_pre_reasoning(agent: BrowserAgent) -> None:
|
||||
# -----------------------------
|
||||
# ✅ Text filtering test (improved regex)
|
||||
# -----------------------------
|
||||
def test_filter_execution_text(agent: BrowserAgent) -> None:
|
||||
def test_filter_execution_text(
|
||||
agent: BrowserAgent, # pylint: disable=redefined-outer-name
|
||||
) -> None:
|
||||
text = """
|
||||
### New console messages
|
||||
Some console output
|
||||
@@ -112,6 +137,7 @@ def test_filter_execution_text(agent: BrowserAgent) -> None:
|
||||
```
|
||||
Regular text content
|
||||
"""
|
||||
# pylint: disable=protected-access
|
||||
filtered = agent._filter_execution_text(text)
|
||||
|
||||
assert "console output" not in filtered
|
||||
@@ -124,7 +150,9 @@ def test_filter_execution_text(agent: BrowserAgent) -> None:
|
||||
# ✅ Memory summarization test (already passing)
|
||||
# -----------------------------
|
||||
@pytest.mark.asyncio
|
||||
async def test_memory_summarizing(agent: BrowserAgent) -> None:
|
||||
async def test_memory_summarizing(
|
||||
agent: BrowserAgent, # pylint: disable=redefined-outer-name
|
||||
) -> None:
|
||||
agent.memory.get_memory = AsyncMock(
|
||||
return_value=[MagicMock(role="user", content="Original question")]
|
||||
* 25,
|
||||
@@ -136,6 +164,7 @@ async def test_memory_summarizing(agent: BrowserAgent) -> None:
|
||||
content=[MagicMock(text="Summary text")],
|
||||
)
|
||||
|
||||
# pylint: disable=protected-access
|
||||
await agent._memory_summarizing()
|
||||
|
||||
assert agent.memory.clear.called
|
||||
|
||||
@@ -1,20 +1,23 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import pytest
|
||||
import asyncio
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
from types import SimpleNamespace
|
||||
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
import pytest
|
||||
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
|
||||
|
||||
from browser_use.browser_use_fullstack_runtime.backend import (
|
||||
agentscope_browseruse_agent as agent_module,
|
||||
)
|
||||
from browser_use.browser_use_fullstack_runtime.backend import (
|
||||
async_quart_service as service,
|
||||
)
|
||||
|
||||
AgentscopeBrowseruseAgent = agent_module.AgentscopeBrowseruseAgent
|
||||
RunStatus = agent_module.RunStatus
|
||||
app = service.app
|
||||
|
||||
|
||||
# -----------------------------
|
||||
# 🧪 Singleton Test Configuration
|
||||
@@ -31,13 +34,17 @@ def event_loop():
|
||||
async def agent_singleton():
|
||||
"""Session-scoped single instance of AgentscopeBrowseruseAgent"""
|
||||
with patch(
|
||||
"browser_use.browser_use_fullstack_runtime.backend.agentscope_browseruse_agent.SandboxService",
|
||||
"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",
|
||||
"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",
|
||||
"browser_use.browser_use_fullstack_runtime."
|
||||
"backend.agentscope_browseruse_agent.InMemorySessionHistoryService",
|
||||
) as MockHistoryService, patch(
|
||||
"agentscope_runtime.sandbox.manager.container_clients.docker_client.docker",
|
||||
"agentscope_runtime.sandbox.manager."
|
||||
"container_clients.docker_client.docker",
|
||||
) as mock_docker, patch(
|
||||
"agentscope_runtime.sandbox.manager.sandbox_manager.SandboxManager",
|
||||
) as MockSandboxManager:
|
||||
@@ -88,16 +95,20 @@ async def test_app():
|
||||
# ✅ AgentscopeBrowseruseAgent Singleton Tests
|
||||
# -----------------------------
|
||||
@pytest.mark.asyncio
|
||||
async def test_agent_singleton_initialization(agent_singleton):
|
||||
async def agent_singleton_singleton_initialization(
|
||||
agent_singleton, # pylint: disable=redefined-outer-name
|
||||
):
|
||||
"""Test agent singleton initialization"""
|
||||
agent = agent_singleton
|
||||
agent = agent_singleton # pylint: disable=redefined-outer-name
|
||||
assert isinstance(agent, AgentscopeBrowseruseAgent)
|
||||
assert hasattr(agent, "agent")
|
||||
assert hasattr(agent, "runner")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_chat_method(agent_singleton):
|
||||
async def test_chat_method(
|
||||
agent_singleton,
|
||||
): # pylint: disable=redefined-outer-name
|
||||
"""Test chat method handles messages"""
|
||||
mock_request = {
|
||||
"messages": [
|
||||
@@ -108,20 +119,28 @@ async def test_chat_method(agent_singleton):
|
||||
# ✅ Create mock object with object/status properties
|
||||
mock_event = SimpleNamespace(
|
||||
object="message",
|
||||
status=RunStatus.Completed,
|
||||
status=agent_module.RunStatus.Completed,
|
||||
content=[{"type": "text", "text": "Test response"}],
|
||||
)
|
||||
|
||||
with patch.object(agent_singleton.runner, "stream_query") as mock_stream:
|
||||
with patch.object(
|
||||
agent_singleton.runner, # pylint: disable=redefined-outer-name
|
||||
"stream_query",
|
||||
) as mock_stream:
|
||||
# ✅ Return object with properties
|
||||
async def mock_stream_query(*args, **kwargs):
|
||||
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"]):
|
||||
async for response in agent_singleton.chat(
|
||||
# pylint: disable=redefined-outer-name
|
||||
mock_request["messages"],
|
||||
):
|
||||
responses.append(response)
|
||||
|
||||
assert len(responses) == 1
|
||||
assert responses[0][0]["text"] == "Test response" # ✅ Fix property access
|
||||
assert (
|
||||
responses[0][0]["text"] == "Test response"
|
||||
) # ✅ Fix property access
|
||||
|
||||
@@ -1,264 +1,61 @@
|
||||
from datetime import datetime, timezone
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import time
|
||||
import tempfile
|
||||
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()
|
||||
import conversational_agents.chatbot_fullstack_runtime.backend.web_server as ws
|
||||
|
||||
|
||||
# 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)
|
||||
app = ws.app
|
||||
_db = ws.db
|
||||
User = ws.User
|
||||
|
||||
|
||||
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),
|
||||
def generate_unique_username():
|
||||
return f"testuser_{int(time.time())}"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def client_and_username():
|
||||
"""Create an Isolated Test Client and Username"""
|
||||
db_fd, db_path = tempfile.mkstemp(suffix=".db")
|
||||
app.config["SQLALCHEMY_DATABASE_URI"] = f"sqlite:///{db_path}"
|
||||
app.config["TESTING"] = True
|
||||
client = app.test_client()
|
||||
|
||||
with app.app_context():
|
||||
_db.drop_all()
|
||||
_db.create_all()
|
||||
|
||||
# Generate Unique Username
|
||||
username = generate_unique_username()
|
||||
password = "testpass"
|
||||
user = User(username=username, name="Test User")
|
||||
user.set_password(password)
|
||||
_db.session.add(user)
|
||||
_db.session.commit()
|
||||
|
||||
yield client, username, password
|
||||
|
||||
os.close(db_fd)
|
||||
os.unlink(db_path)
|
||||
|
||||
|
||||
def test_user_login_success(
|
||||
# pylint: disable=redefined-outer-name
|
||||
client_and_username,
|
||||
):
|
||||
"""Test Successful User Login"""
|
||||
client, username, password = client_and_username
|
||||
|
||||
response = client.post(
|
||||
"/api/login",
|
||||
json={
|
||||
"username": username,
|
||||
"password": password,
|
||||
},
|
||||
)
|
||||
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
|
||||
assert data["username"] == username
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import pytest
|
||||
from unittest.mock import AsyncMock
|
||||
import pytest
|
||||
from agentscope.message import Msg
|
||||
from agentscope.agent import ReActAgent
|
||||
from agentscope.tool import Toolkit
|
||||
@@ -12,13 +12,14 @@ class TestReActAgent:
|
||||
|
||||
@pytest.fixture
|
||||
def test_agent(self):
|
||||
"""Fixture to create a test ReAct agent with fully mocked dependencies"""
|
||||
"""Fixture to create a test ReAct
|
||||
agent with fully mocked dependencies"""
|
||||
|
||||
async def model_response(*args, **kwargs):
|
||||
async def model_response():
|
||||
yield Msg(
|
||||
name="Friday",
|
||||
content="Mocked model response",
|
||||
role="assistant"
|
||||
role="assistant",
|
||||
)
|
||||
|
||||
mock_model = AsyncMock()
|
||||
@@ -36,10 +37,12 @@ class TestReActAgent:
|
||||
model=mock_model,
|
||||
formatter=mock_formatter,
|
||||
toolkit=Toolkit(),
|
||||
memory=mock_memory
|
||||
memory=mock_memory,
|
||||
)
|
||||
|
||||
# pylint: disable=protected-access
|
||||
agent._reasoning_hint_msgs = AsyncMock()
|
||||
# pylint: disable=protected-access
|
||||
agent._reasoning_hint_msgs.get_memory = AsyncMock(return_value=[])
|
||||
|
||||
return agent
|
||||
@@ -47,16 +50,16 @@ class TestReActAgent:
|
||||
async def test_exit_command(self, test_agent, monkeypatch):
|
||||
"""Test exit command handling"""
|
||||
|
||||
async def exit_model_response(*args, **kwargs):
|
||||
async def exit_model_response(*_args, **_kwargs):
|
||||
yield Msg(
|
||||
name="Friday",
|
||||
content="exit",
|
||||
role="assistant"
|
||||
role="assistant",
|
||||
)
|
||||
|
||||
test_agent.model.side_effect = exit_model_response
|
||||
|
||||
monkeypatch.setattr('builtins.input', lambda _: "exit")
|
||||
monkeypatch.setattr("builtins.input", lambda _: "exit")
|
||||
|
||||
msg = Msg(name="User", content="exit", role="user")
|
||||
response = await test_agent(msg)
|
||||
@@ -66,11 +69,13 @@ class TestReActAgent:
|
||||
async def test_conversation_flow(self, monkeypatch):
|
||||
"""Test full conversation flow"""
|
||||
|
||||
async def model_response(*args, **kwargs):
|
||||
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"
|
||||
content="Thought: I need to use a tool\n"
|
||||
"Action: execute_shell_command\n"
|
||||
"Action Input: echo 'Hello World'",
|
||||
role="assistant",
|
||||
)
|
||||
|
||||
mock_model = AsyncMock()
|
||||
@@ -88,11 +93,11 @@ class TestReActAgent:
|
||||
model=mock_model,
|
||||
formatter=mock_formatter,
|
||||
toolkit=Toolkit(),
|
||||
memory=mock_memory
|
||||
memory=mock_memory,
|
||||
)
|
||||
|
||||
monkeypatch.setattr('builtins.input', lambda _: "Test command")
|
||||
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
|
||||
assert "Thought:" in response.content
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
# tests/evaluation_test.py
|
||||
import asyncio
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
from unittest.mock import Mock, AsyncMock, patch
|
||||
from typing import List, Dict, Any, Tuple, Callable
|
||||
@@ -29,19 +28,21 @@ class TestReActAgentSolution:
|
||||
def mock_pre_hook(self) -> Mock:
|
||||
"""Create a mock pre-hook function that returns None"""
|
||||
|
||||
def pre_hook_return(*args, **kwargs):
|
||||
def pre_hook_return():
|
||||
"""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
|
||||
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"""
|
||||
|
||||
def mock_tool(*args, **kwargs):
|
||||
def mock_tool():
|
||||
return "tool_response"
|
||||
|
||||
tool_schema = {
|
||||
@@ -110,8 +111,15 @@ class TestMainFunction:
|
||||
mock_evaluator_class.return_value = mock_evaluator
|
||||
|
||||
# ✅ 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=[]):
|
||||
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()
|
||||
|
||||
@@ -137,12 +145,19 @@ class TestMainFunction:
|
||||
mock_evaluator_class.return_value = mock_evaluator
|
||||
|
||||
# ✅ 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=[]):
|
||||
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,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import asyncio
|
||||
import pytest
|
||||
from unittest.mock import AsyncMock, patch, MagicMock
|
||||
import pytest
|
||||
from agentscope.agent import ReActAgent
|
||||
from agentscope.model import ChatModelBase
|
||||
from agentscope.formatter import FormatterBase
|
||||
@@ -47,9 +45,12 @@ 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):
|
||||
with patch(
|
||||
"games.game_werewolves.game.WitchResurrectModel",
|
||||
side_effect=mock_model,
|
||||
):
|
||||
result = await game.WitchResurrectModel(**{"resurrect": True})
|
||||
assert result["resurrect"] == True
|
||||
assert result["resurrect"] is True
|
||||
|
||||
|
||||
# -----------------------------
|
||||
@@ -84,8 +85,9 @@ def test_vote_model_generation() -> None:
|
||||
name=f"Player{i}",
|
||||
sys_prompt=f"Vote system prompt {i}",
|
||||
model=mock_model,
|
||||
formatter=mock_formatter
|
||||
) for i in range(3)
|
||||
formatter=mock_formatter,
|
||||
)
|
||||
for i in range(3)
|
||||
]
|
||||
|
||||
VoteModel = structured_model.get_vote_model(agents)
|
||||
@@ -105,10 +107,10 @@ def test_witch_poison_model_fields() -> None:
|
||||
name="Player1",
|
||||
sys_prompt="Poison system prompt",
|
||||
model=mock_model,
|
||||
formatter=mock_formatter
|
||||
)
|
||||
formatter=mock_formatter,
|
||||
),
|
||||
]
|
||||
|
||||
PoisonModel = structured_model.get_poison_model(agents)
|
||||
assert "poison" in PoisonModel.model_fields
|
||||
assert "name" in PoisonModel.model_fields
|
||||
assert "name" in PoisonModel.model_fields
|
||||
|
||||
Reference in New Issue
Block a user