mirror of
https://github.com/eigent-ai/eigent.git
synced 2026-04-28 11:40:25 +00:00
feat: add backend unit tests with pytest (207 cases)
This commit is contained in:
parent
9c96495165
commit
cdfea63c5f
12 changed files with 5815 additions and 787 deletions
349
backend/tests/unit/controller/test_task_controller.py
Normal file
349
backend/tests/unit/controller/test_task_controller.py
Normal file
|
|
@ -0,0 +1,349 @@
|
|||
from unittest.mock import MagicMock, patch
|
||||
import pytest
|
||||
from fastapi import Response
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from app.controller.task_controller import start, put, take_control, add_agent, TakeControl
|
||||
from app.model.chat import NewAgent, UpdateData, TaskContent
|
||||
from app.service.task import Action
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestTaskController:
|
||||
"""Test cases for task controller endpoints."""
|
||||
|
||||
def test_start_task_success(self, mock_task_lock):
|
||||
"""Test successful task start."""
|
||||
task_id = "test_task_123"
|
||||
|
||||
with patch("app.controller.task_controller.get_task_lock", return_value=mock_task_lock), \
|
||||
patch("asyncio.run") as mock_run:
|
||||
|
||||
response = start(task_id)
|
||||
|
||||
assert isinstance(response, Response)
|
||||
assert response.status_code == 201
|
||||
mock_run.assert_called_once()
|
||||
|
||||
def test_update_task_success(self, mock_task_lock):
|
||||
"""Test successful task update."""
|
||||
task_id = "test_task_123"
|
||||
update_data = UpdateData(
|
||||
task=[
|
||||
TaskContent(id="subtask_1", content="Updated content 1"),
|
||||
TaskContent(id="subtask_2", content="Updated content 2")
|
||||
]
|
||||
)
|
||||
|
||||
with patch("app.controller.task_controller.get_task_lock", return_value=mock_task_lock), \
|
||||
patch("asyncio.run") as mock_run:
|
||||
|
||||
response = put(task_id, update_data)
|
||||
|
||||
assert isinstance(response, Response)
|
||||
assert response.status_code == 201
|
||||
mock_run.assert_called_once()
|
||||
|
||||
def test_take_control_pause_success(self, mock_task_lock):
|
||||
"""Test successful task pause control."""
|
||||
task_id = "test_task_123"
|
||||
control_data = TakeControl(action=Action.pause)
|
||||
|
||||
with patch("app.controller.task_controller.get_task_lock", return_value=mock_task_lock), \
|
||||
patch("asyncio.run") as mock_run:
|
||||
|
||||
response = take_control(task_id, control_data)
|
||||
|
||||
assert isinstance(response, Response)
|
||||
assert response.status_code == 204
|
||||
mock_run.assert_called_once()
|
||||
|
||||
def test_take_control_resume_success(self, mock_task_lock):
|
||||
"""Test successful task resume control."""
|
||||
task_id = "test_task_123"
|
||||
control_data = TakeControl(action=Action.resume)
|
||||
|
||||
with patch("app.controller.task_controller.get_task_lock", return_value=mock_task_lock), \
|
||||
patch("asyncio.run") as mock_run:
|
||||
|
||||
response = take_control(task_id, control_data)
|
||||
|
||||
assert isinstance(response, Response)
|
||||
assert response.status_code == 204
|
||||
mock_run.assert_called_once()
|
||||
|
||||
def test_add_agent_success(self, mock_task_lock):
|
||||
"""Test successful agent addition."""
|
||||
task_id = "test_task_123"
|
||||
new_agent = NewAgent(
|
||||
name="Test Agent",
|
||||
description="A test agent",
|
||||
tools=["search", "code"],
|
||||
mcp_tools=None,
|
||||
env_path=".env"
|
||||
)
|
||||
|
||||
with patch("app.controller.task_controller.get_task_lock", return_value=mock_task_lock), \
|
||||
patch("app.controller.task_controller.load_dotenv"), \
|
||||
patch("asyncio.run") as mock_run:
|
||||
|
||||
response = add_agent(task_id, new_agent)
|
||||
|
||||
assert isinstance(response, Response)
|
||||
assert response.status_code == 204
|
||||
mock_run.assert_called_once()
|
||||
|
||||
def test_start_task_nonexistent_task(self):
|
||||
"""Test start task with nonexistent task ID."""
|
||||
task_id = "nonexistent_task"
|
||||
|
||||
with patch("app.controller.task_controller.get_task_lock", side_effect=KeyError("Task not found")):
|
||||
with pytest.raises(KeyError):
|
||||
start(task_id)
|
||||
|
||||
def test_update_task_empty_data(self, mock_task_lock):
|
||||
"""Test update task with empty task list."""
|
||||
task_id = "test_task_123"
|
||||
update_data = UpdateData(task=[])
|
||||
|
||||
with patch("app.controller.task_controller.get_task_lock", return_value=mock_task_lock), \
|
||||
patch("asyncio.run") as mock_run:
|
||||
|
||||
response = put(task_id, update_data)
|
||||
|
||||
assert isinstance(response, Response)
|
||||
assert response.status_code == 201
|
||||
mock_run.assert_called_once()
|
||||
|
||||
def test_add_agent_with_mcp_tools(self, mock_task_lock):
|
||||
"""Test adding agent with MCP tools."""
|
||||
task_id = "test_task_123"
|
||||
new_agent = NewAgent(
|
||||
name="MCP Agent",
|
||||
description="An agent with MCP tools",
|
||||
tools=["search"],
|
||||
mcp_tools={"mcpServers": {"notion": {"config": "test"}}},
|
||||
env_path=".env"
|
||||
)
|
||||
|
||||
with patch("app.controller.task_controller.get_task_lock", return_value=mock_task_lock), \
|
||||
patch("app.controller.task_controller.load_dotenv"), \
|
||||
patch("asyncio.run") as mock_run:
|
||||
|
||||
response = add_agent(task_id, new_agent)
|
||||
|
||||
assert isinstance(response, Response)
|
||||
assert response.status_code == 204
|
||||
mock_run.assert_called_once()
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
class TestTaskControllerIntegration:
|
||||
"""Integration tests for task controller."""
|
||||
|
||||
def test_start_task_endpoint_integration(self, client: TestClient):
|
||||
"""Test start task endpoint through FastAPI test client."""
|
||||
task_id = "test_task_123"
|
||||
|
||||
with patch("app.controller.task_controller.get_task_lock") as mock_get_lock, \
|
||||
patch("asyncio.run"):
|
||||
|
||||
mock_task_lock = MagicMock()
|
||||
mock_get_lock.return_value = mock_task_lock
|
||||
|
||||
response = client.post(f"/task/{task_id}/start")
|
||||
|
||||
assert response.status_code == 201
|
||||
|
||||
def test_update_task_endpoint_integration(self, client: TestClient):
|
||||
"""Test update task endpoint through FastAPI test client."""
|
||||
task_id = "test_task_123"
|
||||
update_data = {
|
||||
"task": [
|
||||
{"id": "subtask_1", "content": "Updated content 1"},
|
||||
{"id": "subtask_2", "content": "Updated content 2"}
|
||||
]
|
||||
}
|
||||
|
||||
with patch("app.controller.task_controller.get_task_lock") as mock_get_lock, \
|
||||
patch("asyncio.run"):
|
||||
|
||||
mock_task_lock = MagicMock()
|
||||
mock_get_lock.return_value = mock_task_lock
|
||||
|
||||
response = client.put(f"/task/{task_id}", json=update_data)
|
||||
|
||||
assert response.status_code == 201
|
||||
|
||||
def test_take_control_pause_endpoint_integration(self, client: TestClient):
|
||||
"""Test take control pause endpoint through FastAPI test client."""
|
||||
task_id = "test_task_123"
|
||||
control_data = {"action": "pause"}
|
||||
|
||||
with patch("app.controller.task_controller.get_task_lock") as mock_get_lock, \
|
||||
patch("asyncio.run"):
|
||||
|
||||
mock_task_lock = MagicMock()
|
||||
mock_get_lock.return_value = mock_task_lock
|
||||
|
||||
response = client.put(f"/task/{task_id}/take-control", json=control_data)
|
||||
|
||||
assert response.status_code == 204
|
||||
|
||||
def test_take_control_resume_endpoint_integration(self, client: TestClient):
|
||||
"""Test take control resume endpoint through FastAPI test client."""
|
||||
task_id = "test_task_123"
|
||||
control_data = {"action": "resume"}
|
||||
|
||||
with patch("app.controller.task_controller.get_task_lock") as mock_get_lock, \
|
||||
patch("asyncio.run"):
|
||||
|
||||
mock_task_lock = MagicMock()
|
||||
mock_get_lock.return_value = mock_task_lock
|
||||
|
||||
response = client.put(f"/task/{task_id}/take-control", json=control_data)
|
||||
|
||||
assert response.status_code == 204
|
||||
|
||||
def test_add_agent_endpoint_integration(self, client: TestClient):
|
||||
"""Test add agent endpoint through FastAPI test client."""
|
||||
task_id = "test_task_123"
|
||||
agent_data = {
|
||||
"name": "Test Agent",
|
||||
"description": "A test agent",
|
||||
"tools": ["search", "code"],
|
||||
"mcp_tools": None,
|
||||
"env_path": ".env"
|
||||
}
|
||||
|
||||
with patch("app.controller.task_controller.get_task_lock") as mock_get_lock, \
|
||||
patch("app.controller.task_controller.load_dotenv"), \
|
||||
patch("asyncio.run"):
|
||||
|
||||
mock_task_lock = MagicMock()
|
||||
mock_get_lock.return_value = mock_task_lock
|
||||
|
||||
response = client.post(f"/task/{task_id}/add-agent", json=agent_data)
|
||||
|
||||
assert response.status_code == 204
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestTaskControllerErrorCases:
|
||||
"""Test error cases and edge conditions for task controller."""
|
||||
|
||||
def test_start_task_async_error(self, mock_task_lock):
|
||||
"""Test start task when async operation fails."""
|
||||
task_id = "test_task_123"
|
||||
|
||||
with patch("app.controller.task_controller.get_task_lock", return_value=mock_task_lock), \
|
||||
patch("asyncio.run", side_effect=Exception("Async error")):
|
||||
|
||||
with pytest.raises(Exception, match="Async error"):
|
||||
start(task_id)
|
||||
|
||||
def test_update_task_with_invalid_task_content(self, mock_task_lock):
|
||||
"""Test update task with invalid task content."""
|
||||
task_id = "test_task_123"
|
||||
# Create invalid update data that might cause validation errors
|
||||
update_data = UpdateData(task=[
|
||||
TaskContent(id="", content=""), # Empty ID and content
|
||||
TaskContent(id="valid_id", content="Valid content")
|
||||
])
|
||||
|
||||
with patch("app.controller.task_controller.get_task_lock", return_value=mock_task_lock), \
|
||||
patch("asyncio.run") as mock_run:
|
||||
|
||||
# Should handle invalid data gracefully or raise appropriate error
|
||||
response = put(task_id, update_data)
|
||||
assert response.status_code == 201
|
||||
|
||||
def test_take_control_invalid_action(self):
|
||||
"""Test take control with invalid action value."""
|
||||
task_id = "test_task_123"
|
||||
|
||||
# This should be caught by Pydantic validation
|
||||
with pytest.raises((ValueError, TypeError)):
|
||||
TakeControl(action="invalid_action")
|
||||
|
||||
def test_add_agent_env_load_failure(self, mock_task_lock):
|
||||
"""Test add agent when environment loading fails."""
|
||||
task_id = "test_task_123"
|
||||
new_agent = NewAgent(
|
||||
name="Test Agent",
|
||||
description="A test agent",
|
||||
tools=["search"],
|
||||
mcp_tools=None,
|
||||
env_path="nonexistent.env"
|
||||
)
|
||||
|
||||
with patch("app.controller.task_controller.get_task_lock", return_value=mock_task_lock), \
|
||||
patch("app.controller.task_controller.load_dotenv", side_effect=Exception("Env load failed")), \
|
||||
patch("asyncio.run"):
|
||||
|
||||
# Should handle environment load failure gracefully or raise error
|
||||
with pytest.raises(Exception, match="Env load failed"):
|
||||
add_agent(task_id, new_agent)
|
||||
|
||||
def test_add_agent_with_empty_name(self, mock_task_lock):
|
||||
"""Test add agent with empty name."""
|
||||
task_id = "test_task_123"
|
||||
new_agent = NewAgent(
|
||||
name="", # Empty name
|
||||
description="A test agent",
|
||||
tools=["search"],
|
||||
mcp_tools=None,
|
||||
env_path=".env"
|
||||
)
|
||||
|
||||
with patch("app.controller.task_controller.get_task_lock", return_value=mock_task_lock), \
|
||||
patch("app.controller.task_controller.load_dotenv"), \
|
||||
patch("asyncio.run"):
|
||||
|
||||
# Should handle empty name appropriately
|
||||
response = add_agent(task_id, new_agent)
|
||||
assert response.status_code == 204
|
||||
|
||||
def test_task_operations_with_concurrent_access(self, mock_task_lock):
|
||||
"""Test task operations with concurrent access scenarios."""
|
||||
task_id = "test_task_123"
|
||||
|
||||
# Simulate concurrent access by having the task lock be modified during operation
|
||||
def side_effect():
|
||||
mock_task_lock.status = "modified_during_operation"
|
||||
return None
|
||||
|
||||
mock_task_lock.put_queue.side_effect = side_effect
|
||||
|
||||
with patch("app.controller.task_controller.get_task_lock", return_value=mock_task_lock), \
|
||||
patch("asyncio.run") as mock_run:
|
||||
|
||||
response = start(task_id)
|
||||
assert response.status_code == 201
|
||||
|
||||
|
||||
@pytest.mark.model_backend
|
||||
class TestTaskControllerWithLLM:
|
||||
"""Tests that require LLM backend (marked for selective running)."""
|
||||
|
||||
def test_add_agent_with_real_model_integration(self, mock_task_lock):
|
||||
"""Test adding an agent that requires real model integration."""
|
||||
task_id = "test_task_123"
|
||||
new_agent = NewAgent(
|
||||
name="Real Model Agent",
|
||||
description="An agent that uses real models",
|
||||
tools=["search", "code"],
|
||||
mcp_tools=None,
|
||||
env_path=".env"
|
||||
)
|
||||
|
||||
# This test would involve real model creation and configuration
|
||||
# Marked as model_backend test for selective execution
|
||||
assert True # Placeholder
|
||||
|
||||
@pytest.mark.very_slow
|
||||
def test_full_task_workflow_integration(self):
|
||||
"""Test complete task workflow from start to completion (very slow test)."""
|
||||
# This test would run a complete task workflow including agent interactions
|
||||
# Marked as very_slow for execution only in full test mode
|
||||
assert True # Placeholder
|
||||
Loading…
Add table
Add a link
Reference in a new issue