Terminal / tests /test_workflow_integration.py
Baida07's picture
sync: 175 file da Baida98/AI@d1881b9c (2026-08-25 07:39 UTC) [deploy-all]
b85ebc9 verified
Raw
History Blame
6.37 kB
import ast
import asyncio
import sys
import types
import unittest
from enum import IntEnum
from pathlib import Path
from pydantic import ValidationError
BACKEND_ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(BACKEND_ROOT))
def _install_auth_stub() -> None:
auth_guard = types.ModuleType("api.auth_guard")
class AuthRole(IntEnum):
MACHINE = 1
auth_guard.AuthRole = AuthRole
auth_guard.require_role = lambda _role: (lambda: None)
sys.modules["api.auth_guard"] = auth_guard
_install_auth_stub()
from agents.workflow_engine import Workflow, WorkflowExecutor, WorkflowStep
from api import workflows
class FakeKernel:
def __init__(self, resolutions):
self.resolutions = resolutions
self.calls = []
async def resolve_capability(self, tool_name):
self.calls.append(tool_name)
return self.resolutions.get(tool_name, {"status": "error"})
class FakeExecutor:
def __init__(self, results=None):
self.results = results or {}
self.calls = []
async def run_tool(self, tool_name, inputs, timeout=30.0, worker_hint=None):
self.calls.append({
"tool_name": tool_name,
"inputs": inputs,
"timeout": timeout,
"worker_hint": worker_hint,
})
result = self.results.get(tool_name, {"success": True, "output": tool_name})
if isinstance(result, Exception):
raise result
return result
class WorkflowExecutorTests(unittest.IsolatedAsyncioTestCase):
async def test_uses_resolved_worker_hint_and_records_completed_step(self):
kernel = FakeKernel({"search": {"status": "resolved", "worker": {"id": "worker-eu"}}})
executor = FakeExecutor()
engine = WorkflowExecutor(kernel=kernel, executor=executor)
workflow = Workflow(name="ricerca", steps=[WorkflowStep(tool_name="search", args={"query": "AI"})])
result = await engine.execute_workflow(workflow)
self.assertEqual(result.status, "completed")
self.assertEqual(result.steps[0].status, "completed")
self.assertIsNotNone(result.steps[0].started_at)
self.assertIsNotNone(result.steps[0].finished_at)
self.assertEqual(executor.calls[0]["worker_hint"], "worker-eu")
async def test_preserves_local_fallback_when_no_worker_is_resolved(self):
kernel = FakeKernel({})
executor = FakeExecutor()
engine = WorkflowExecutor(kernel=kernel, executor=executor)
workflow = Workflow(name="fallback", steps=[WorkflowStep(tool_name="local", args={"value": 1})])
result = await engine.execute_workflow(workflow)
self.assertEqual(result.status, "completed")
self.assertIsNone(executor.calls[0]["worker_hint"])
async def test_stops_after_unsuccessful_tool_result(self):
kernel = FakeKernel({})
executor = FakeExecutor({"first": {"success": False, "error": "denied"}})
engine = WorkflowExecutor(kernel=kernel, executor=executor)
workflow = Workflow(
name="errore",
steps=[
WorkflowStep(tool_name="first", args={}),
WorkflowStep(tool_name="second", args={}),
],
)
result = await engine.execute_workflow(workflow)
self.assertEqual(result.status, "failed")
self.assertEqual(result.steps[0].status, "failed")
self.assertEqual(result.steps[0].error, "denied")
self.assertEqual(result.steps[1].status, "pending")
self.assertEqual([call["tool_name"] for call in executor.calls], ["first"])
class WorkflowApiTests(unittest.IsolatedAsyncioTestCase):
def setUp(self):
self.previous_executor = workflows._workflow_executor
self.previous_tasks = workflows._workflow_tasks.copy()
workflows._workflow_executor = None
workflows._workflow_tasks.clear()
async def asyncTearDown(self):
for task in list(workflows._workflow_tasks.values()):
task.cancel()
try:
await task
except asyncio.CancelledError:
pass
workflows._workflow_tasks.clear()
workflows._workflow_executor = self.previous_executor
workflows._workflow_tasks.update(self.previous_tasks)
def test_router_exposes_start_and_status_paths(self):
routes = {
(route.path, method)
for route in workflows.router.routes
for method in (getattr(route, "methods", set()) or set())
}
self.assertEqual(workflows.router.prefix, "/api/workflows")
self.assertIn(("/api/workflows", "POST"), routes)
self.assertIn(("/api/workflows/{workflow_id}", "GET"), routes)
def test_main_router_map_mounts_workflows(self):
tree = ast.parse((BACKEND_ROOT / "main.py").read_text(encoding="utf-8"))
router_map = next(
node.value
for node in tree.body
if isinstance(node, ast.Assign)
and any(isinstance(target, ast.Name) and target.id == "_ROUTER_MAP" for target in node.targets)
)
routes = {
key.value: value.value
for key, value in zip(router_map.keys, router_map.values)
if isinstance(key, ast.Constant) and isinstance(value, ast.Constant)
}
self.assertEqual(routes.get("workflows"), "workflows")
def test_start_contract_rejects_empty_steps(self):
with self.assertRaises(ValidationError):
workflows.WorkflowStartIn(name="vuoto", steps=[])
async def test_start_and_get_workflow_use_background_execution(self):
engine = WorkflowExecutor(kernel=FakeKernel({}), executor=FakeExecutor())
workflows._workflow_executor = engine
body = workflows.WorkflowStartIn(
name="API workflow",
steps=[WorkflowStep(tool_name="local", args={"n": 1})],
)
started = await workflows.start_workflow(body)
task = workflows._workflow_tasks[started.workflow_id]
await task
fetched = await workflows.get_workflow(started.workflow_id)
self.assertEqual(fetched.workflow_id, started.workflow_id)
self.assertEqual(fetched.status, "completed")
self.assertEqual(fetched.steps[0].status, "completed")
if __name__ == "__main__":
unittest.main()