mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-09-20 18:13:49 +00:00
Review findings, each reproduced before the fix: - The run-it-now offer wrote CREWAI_TRACING_ENABLED into ./.env before checking the directory is a crewAI project, then run_crew() died on a missing pyproject.toml with a traceback. Now: no pyproject.toml → one sentence, exit 1, nothing written. - httpx errors (AMP unreachable, a timeout) surfaced as tracebacks. Now the start says "Could not reach AMP to start the evaluation: …"; while waiting, an unreachable AMP or a 5xx is retried up to POLL_RETRIES consecutive times, then reported with the URL — the evaluation keeps running server-side either way. - The POST now carries a 120 s timeout (AMP reads the run's spans inside it), the poll 30 s. - A 200 whose body has no known status (a non-dict, no status, a status outside queued/running/done/failed) polled forever. Now it stops with the status it saw and the URL. - A 404 without a JSON message read "AMP holds no run <evaluation id>" while polling. _refused takes "run <id>" / "evaluation <id>" and says "AMP answered 404 for <subject>" — AMP's own message still wins. - The record's amp_base_url was ignored; the CLI now evaluates the run at the AMP it was traced to. --run keeps the configured AMP. - The post-run explanation names the third cause: a crewai older than the version that records the last run. Tests for each, plus the previously untested paths: Ctrl-C exits 130, a 2xx without an id, a refusal mid-poll, DMN opens no browser, --run skips the offer. 51 passed in lib/cli/tests (eval + plus_api). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
407 lines
15 KiB
Python
407 lines
15 KiB
Python
import os
|
|
import unittest
|
|
from unittest.mock import ANY, MagicMock, patch
|
|
|
|
from crewai_cli.plus_api import PlusAPI
|
|
|
|
|
|
class TestPlusAPI(unittest.TestCase):
|
|
def setUp(self):
|
|
self.api_key = "test_api_key"
|
|
self.api = PlusAPI(self.api_key)
|
|
self.org_uuid = "test-org-uuid"
|
|
|
|
def test_init(self):
|
|
self.assertEqual(self.api.api_key, self.api_key)
|
|
self.assertEqual(self.api.headers["Authorization"], f"Bearer {self.api_key}")
|
|
self.assertEqual(self.api.headers["Content-Type"], "application/json")
|
|
self.assertIn("CrewAI-CLI/", self.api.headers["User-Agent"])
|
|
self.assertTrue(self.api.headers["X-Crewai-Version"])
|
|
|
|
@patch("crewai_core.plus_api.PlusAPI._make_request")
|
|
def test_create_evaluation(self, mock_make_request):
|
|
mock_response = MagicMock()
|
|
mock_make_request.return_value = mock_response
|
|
|
|
response = self.api.create_evaluation("6f31fe1a-20bd-4bfe-a011-25d6b9341f62")
|
|
|
|
mock_make_request.assert_called_once_with(
|
|
"POST",
|
|
"/crewai_plus/api/v1/tracing/evaluations",
|
|
json={"execution_id": "6f31fe1a-20bd-4bfe-a011-25d6b9341f62"},
|
|
timeout=120.0, # AMP reads the run's spans inside this request
|
|
)
|
|
self.assertEqual(response, mock_response)
|
|
|
|
@patch("crewai_core.plus_api.PlusAPI._make_request")
|
|
def test_get_evaluation(self, mock_make_request):
|
|
mock_response = MagicMock()
|
|
mock_make_request.return_value = mock_response
|
|
|
|
response = self.api.get_evaluation("ev-1")
|
|
|
|
mock_make_request.assert_called_once_with(
|
|
"GET", "/crewai_plus/api/v1/tracing/evaluations/ev-1", timeout=30.0
|
|
)
|
|
self.assertEqual(response, mock_response)
|
|
|
|
@patch("crewai_core.plus_api.PlusAPI._make_request")
|
|
def test_login_to_tool_repository(self, mock_make_request):
|
|
mock_response = MagicMock()
|
|
mock_make_request.return_value = mock_response
|
|
|
|
response = self.api.login_to_tool_repository()
|
|
|
|
mock_make_request.assert_called_once_with(
|
|
"POST", "/crewai_plus/api/v1/tools/login", json={}
|
|
)
|
|
self.assertEqual(response, mock_response)
|
|
|
|
def assert_request_with_org_id(
|
|
self, mock_client_instance, method: str, endpoint: str, **kwargs
|
|
):
|
|
mock_client_instance.request.assert_called_once_with(
|
|
method,
|
|
f"{os.getenv('CREWAI_PLUS_URL')}{endpoint}",
|
|
headers={
|
|
"Authorization": ANY,
|
|
"Content-Type": ANY,
|
|
"User-Agent": ANY,
|
|
"X-Crewai-Version": ANY,
|
|
"X-Crewai-Organization-Id": self.org_uuid,
|
|
},
|
|
**kwargs,
|
|
)
|
|
|
|
@patch("crewai_core.plus_api.Settings")
|
|
@patch("crewai_core.plus_api.httpx.Client")
|
|
def test_login_to_tool_repository_with_org_uuid(
|
|
self, mock_client_class, mock_settings_class
|
|
):
|
|
mock_settings = MagicMock()
|
|
mock_settings.org_uuid = self.org_uuid
|
|
mock_settings.enterprise_base_url = os.getenv('CREWAI_PLUS_URL')
|
|
mock_settings_class.return_value = mock_settings
|
|
self.api = PlusAPI(self.api_key)
|
|
|
|
mock_client_instance = MagicMock()
|
|
mock_response = MagicMock()
|
|
mock_client_instance.request.return_value = mock_response
|
|
mock_client_class.return_value.__enter__.return_value = mock_client_instance
|
|
|
|
response = self.api.login_to_tool_repository()
|
|
|
|
self.assert_request_with_org_id(
|
|
mock_client_instance, "POST", "/crewai_plus/api/v1/tools/login", json={}
|
|
)
|
|
self.assertEqual(response, mock_response)
|
|
|
|
@patch("crewai_core.plus_api.PlusAPI._make_request")
|
|
def test_get_tool(self, mock_make_request):
|
|
mock_response = MagicMock()
|
|
mock_make_request.return_value = mock_response
|
|
|
|
response = self.api.get_tool("test_tool_handle")
|
|
mock_make_request.assert_called_once_with(
|
|
"GET", "/crewai_plus/api/v1/tools/test_tool_handle"
|
|
)
|
|
self.assertEqual(response, mock_response)
|
|
|
|
@patch("crewai_core.plus_api.Settings")
|
|
@patch("crewai_core.plus_api.httpx.Client")
|
|
def test_get_tool_with_org_uuid(self, mock_client_class, mock_settings_class):
|
|
mock_settings = MagicMock()
|
|
mock_settings.org_uuid = self.org_uuid
|
|
mock_settings.enterprise_base_url = os.getenv('CREWAI_PLUS_URL')
|
|
mock_settings_class.return_value = mock_settings
|
|
self.api = PlusAPI(self.api_key)
|
|
|
|
mock_client_instance = MagicMock()
|
|
mock_response = MagicMock()
|
|
mock_client_instance.request.return_value = mock_response
|
|
mock_client_class.return_value.__enter__.return_value = mock_client_instance
|
|
|
|
response = self.api.get_tool("test_tool_handle")
|
|
|
|
self.assert_request_with_org_id(
|
|
mock_client_instance, "GET", "/crewai_plus/api/v1/tools/test_tool_handle"
|
|
)
|
|
self.assertEqual(response, mock_response)
|
|
|
|
@patch("crewai_core.plus_api.PlusAPI._make_request")
|
|
def test_publish_tool(self, mock_make_request):
|
|
mock_response = MagicMock()
|
|
mock_make_request.return_value = mock_response
|
|
handle = "test_tool_handle"
|
|
public = True
|
|
version = "1.0.0"
|
|
description = "Test tool description"
|
|
encoded_file = "encoded_test_file"
|
|
|
|
response = self.api.publish_tool(
|
|
handle, public, version, description, encoded_file
|
|
)
|
|
|
|
params = {
|
|
"handle": handle,
|
|
"public": public,
|
|
"version": version,
|
|
"file": encoded_file,
|
|
"description": description,
|
|
"available_exports": None,
|
|
"tools_metadata": None,
|
|
}
|
|
mock_make_request.assert_called_once_with(
|
|
"POST", "/crewai_plus/api/v1/tools", json=params
|
|
)
|
|
self.assertEqual(response, mock_response)
|
|
|
|
@patch("crewai_core.plus_api.Settings")
|
|
@patch("crewai_core.plus_api.httpx.Client")
|
|
def test_publish_tool_with_org_uuid(self, mock_client_class, mock_settings_class):
|
|
mock_settings = MagicMock()
|
|
mock_settings.org_uuid = self.org_uuid
|
|
mock_settings.enterprise_base_url = os.getenv('CREWAI_PLUS_URL')
|
|
mock_settings_class.return_value = mock_settings
|
|
self.api = PlusAPI(self.api_key)
|
|
|
|
mock_client_instance = MagicMock()
|
|
mock_response = MagicMock()
|
|
mock_client_instance.request.return_value = mock_response
|
|
mock_client_class.return_value.__enter__.return_value = mock_client_instance
|
|
|
|
handle = "test_tool_handle"
|
|
public = True
|
|
version = "1.0.0"
|
|
description = "Test tool description"
|
|
encoded_file = "encoded_test_file"
|
|
|
|
response = self.api.publish_tool(
|
|
handle, public, version, description, encoded_file
|
|
)
|
|
|
|
expected_params = {
|
|
"handle": handle,
|
|
"public": public,
|
|
"version": version,
|
|
"file": encoded_file,
|
|
"description": description,
|
|
"available_exports": None,
|
|
"tools_metadata": None,
|
|
}
|
|
|
|
self.assert_request_with_org_id(
|
|
mock_client_instance, "POST", "/crewai_plus/api/v1/tools", json=expected_params
|
|
)
|
|
self.assertEqual(response, mock_response)
|
|
|
|
@patch("crewai_core.plus_api.PlusAPI._make_request")
|
|
def test_publish_tool_without_description(self, mock_make_request):
|
|
mock_response = MagicMock()
|
|
mock_make_request.return_value = mock_response
|
|
handle = "test_tool_handle"
|
|
public = False
|
|
version = "2.0.0"
|
|
description = None
|
|
encoded_file = "encoded_test_file"
|
|
|
|
response = self.api.publish_tool(
|
|
handle, public, version, description, encoded_file
|
|
)
|
|
|
|
params = {
|
|
"handle": handle,
|
|
"public": public,
|
|
"version": version,
|
|
"file": encoded_file,
|
|
"description": description,
|
|
"available_exports": None,
|
|
"tools_metadata": None,
|
|
}
|
|
mock_make_request.assert_called_once_with(
|
|
"POST", "/crewai_plus/api/v1/tools", json=params
|
|
)
|
|
self.assertEqual(response, mock_response)
|
|
|
|
@patch("crewai_core.plus_api.httpx.Client")
|
|
def test_make_request(self, mock_client_class):
|
|
mock_client_instance = MagicMock()
|
|
mock_response = MagicMock()
|
|
mock_client_instance.request.return_value = mock_response
|
|
mock_client_class.return_value.__enter__.return_value = mock_client_instance
|
|
|
|
response = self.api._make_request("GET", "test_endpoint")
|
|
|
|
mock_client_class.assert_called_once_with(trust_env=False, verify=True)
|
|
mock_client_instance.request.assert_called_once_with(
|
|
"GET", f"{self.api.base_url}/test_endpoint", headers=self.api.headers
|
|
)
|
|
self.assertEqual(response, mock_response)
|
|
|
|
@patch("crewai_core.plus_api.PlusAPI._make_request")
|
|
def test_deploy_by_name(self, mock_make_request):
|
|
self.api.deploy_by_name("test_project")
|
|
mock_make_request.assert_called_once_with(
|
|
"POST", "/crewai_plus/api/v1/crews/by-name/test_project/deploy"
|
|
)
|
|
|
|
@patch("crewai_core.plus_api.PlusAPI._make_request")
|
|
def test_deploy_by_uuid(self, mock_make_request):
|
|
self.api.deploy_by_uuid("test_uuid")
|
|
mock_make_request.assert_called_once_with(
|
|
"POST", "/crewai_plus/api/v1/crews/test_uuid/deploy"
|
|
)
|
|
|
|
@patch("crewai_core.plus_api.PlusAPI._make_request")
|
|
def test_crew_status_by_name(self, mock_make_request):
|
|
self.api.crew_status_by_name("test_project")
|
|
mock_make_request.assert_called_once_with(
|
|
"GET", "/crewai_plus/api/v1/crews/by-name/test_project/status"
|
|
)
|
|
|
|
@patch("crewai_core.plus_api.PlusAPI._make_request")
|
|
def test_crew_status_by_uuid(self, mock_make_request):
|
|
self.api.crew_status_by_uuid("test_uuid")
|
|
mock_make_request.assert_called_once_with(
|
|
"GET", "/crewai_plus/api/v1/crews/test_uuid/status"
|
|
)
|
|
|
|
@patch("crewai_core.plus_api.PlusAPI._make_request")
|
|
def test_crew_by_name(self, mock_make_request):
|
|
self.api.crew_by_name("test_project")
|
|
mock_make_request.assert_called_once_with(
|
|
"GET", "/crewai_plus/api/v1/crews/by-name/test_project/logs/deployment"
|
|
)
|
|
|
|
self.api.crew_by_name("test_project", "custom_log")
|
|
mock_make_request.assert_called_with(
|
|
"GET", "/crewai_plus/api/v1/crews/by-name/test_project/logs/custom_log"
|
|
)
|
|
|
|
@patch("crewai_core.plus_api.PlusAPI._make_request")
|
|
def test_crew_by_uuid(self, mock_make_request):
|
|
self.api.crew_by_uuid("test_uuid")
|
|
mock_make_request.assert_called_once_with(
|
|
"GET", "/crewai_plus/api/v1/crews/test_uuid/logs/deployment"
|
|
)
|
|
|
|
self.api.crew_by_uuid("test_uuid", "custom_log")
|
|
mock_make_request.assert_called_with(
|
|
"GET", "/crewai_plus/api/v1/crews/test_uuid/logs/custom_log"
|
|
)
|
|
|
|
@patch("crewai_core.plus_api.PlusAPI._make_request")
|
|
def test_delete_crew_by_name(self, mock_make_request):
|
|
self.api.delete_crew_by_name("test_project")
|
|
mock_make_request.assert_called_once_with(
|
|
"DELETE", "/crewai_plus/api/v1/crews/by-name/test_project"
|
|
)
|
|
|
|
@patch("crewai_core.plus_api.PlusAPI._make_request")
|
|
def test_delete_crew_by_uuid(self, mock_make_request):
|
|
self.api.delete_crew_by_uuid("test_uuid")
|
|
mock_make_request.assert_called_once_with(
|
|
"DELETE", "/crewai_plus/api/v1/crews/test_uuid"
|
|
)
|
|
|
|
@patch("crewai_core.plus_api.PlusAPI._make_request")
|
|
def test_list_crews(self, mock_make_request):
|
|
self.api.list_crews()
|
|
mock_make_request.assert_called_once_with("GET", "/crewai_plus/api/v1/crews")
|
|
|
|
@patch("crewai_core.plus_api.PlusAPI._make_request")
|
|
def test_create_crew(self, mock_make_request):
|
|
payload = {"name": "test_crew"}
|
|
self.api.create_crew(payload)
|
|
mock_make_request.assert_called_once_with(
|
|
"POST", "/crewai_plus/api/v1/crews", json=payload
|
|
)
|
|
|
|
@patch("crewai_cli.plus_api.PlusAPI._make_multipart_request")
|
|
def test_create_crew_from_zip(self, mock_make_multipart_request):
|
|
self.api.create_crew_from_zip(
|
|
"/tmp/test.zip",
|
|
name="test_crew",
|
|
env={"ENV_VAR": "value"},
|
|
)
|
|
mock_make_multipart_request.assert_called_once_with(
|
|
"POST",
|
|
"/crewai_plus/api/v1/crews/zip",
|
|
zip_file_path="/tmp/test.zip",
|
|
data={"name": "test_crew", "env[ENV_VAR]": "value"},
|
|
timeout=300,
|
|
)
|
|
|
|
@patch("crewai_cli.plus_api.PlusAPI._make_multipart_request")
|
|
def test_update_crew_from_zip(self, mock_make_multipart_request):
|
|
self.api.update_crew_from_zip(
|
|
"test_uuid",
|
|
"/tmp/test.zip",
|
|
env={"ENV_VAR": "value"},
|
|
)
|
|
mock_make_multipart_request.assert_called_once_with(
|
|
"POST",
|
|
"/crewai_plus/api/v1/crews/test_uuid/zip_update",
|
|
zip_file_path="/tmp/test.zip",
|
|
data={"env[ENV_VAR]": "value"},
|
|
timeout=300,
|
|
)
|
|
|
|
@patch("crewai_core.plus_api.Settings")
|
|
@patch.dict(os.environ, {"CREWAI_PLUS_URL": ""})
|
|
def test_custom_base_url(self, mock_settings_class):
|
|
mock_settings = MagicMock()
|
|
mock_settings.enterprise_base_url = "https://custom-url.com/api"
|
|
mock_settings_class.return_value = mock_settings
|
|
custom_api = PlusAPI("test_key")
|
|
self.assertEqual(
|
|
custom_api.base_url,
|
|
"https://custom-url.com/api",
|
|
)
|
|
|
|
@patch.dict(os.environ, {"CREWAI_PLUS_URL": "https://custom-url-from-env.com"})
|
|
def test_custom_base_url_from_env(self):
|
|
custom_api = PlusAPI("test_key")
|
|
self.assertEqual(
|
|
custom_api.base_url,
|
|
"https://custom-url-from-env.com",
|
|
)
|
|
|
|
|
|
@patch("crewai_core.plus_api.PlusAPI._make_request")
|
|
def test_get_agent(mock_make_request):
|
|
api = PlusAPI("test_api_key")
|
|
mock_response = MagicMock()
|
|
mock_make_request.return_value = mock_response
|
|
|
|
response = api.get_agent("test_agent_handle")
|
|
|
|
mock_make_request.assert_called_once_with(
|
|
"GET", "/crewai_plus/api/v1/agents/test_agent_handle"
|
|
)
|
|
assert response == mock_response
|
|
|
|
|
|
@patch("crewai_core.plus_api.PlusAPI._make_request")
|
|
@patch("crewai_core.plus_api.Settings")
|
|
def test_get_agent_with_org_uuid(mock_settings_class, mock_make_request):
|
|
org_uuid = "test-org-uuid"
|
|
mock_settings = MagicMock()
|
|
mock_settings.org_uuid = org_uuid
|
|
mock_settings.enterprise_base_url = os.getenv("CREWAI_PLUS_URL")
|
|
mock_settings_class.return_value = mock_settings
|
|
|
|
api = PlusAPI("test_api_key")
|
|
|
|
mock_response = MagicMock()
|
|
mock_make_request.return_value = mock_response
|
|
|
|
response = api.get_agent("test_agent_handle")
|
|
|
|
mock_make_request.assert_called_once_with(
|
|
"GET", "/crewai_plus/api/v1/agents/test_agent_handle"
|
|
)
|
|
assert "X-Crewai-Organization-Id" in api.headers
|
|
assert api.headers["X-Crewai-Organization-Id"] == org_uuid
|
|
assert response == mock_response
|