mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-01-09 08:08:32 +00:00
Merge branch 'main' into tm-basic-event-structure
This commit is contained in:
0
tests/cli/__init__.py
Normal file
0
tests/cli/__init__.py
Normal file
0
tests/cli/authentication/__init__.py
Normal file
0
tests/cli/authentication/__init__.py
Normal file
@@ -1,7 +1,11 @@
|
||||
import unittest
|
||||
from io import StringIO
|
||||
from unittest.mock import MagicMock, patch
|
||||
import pytest
|
||||
import requests
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
from io import StringIO
|
||||
from requests.exceptions import JSONDecodeError
|
||||
from unittest.mock import MagicMock, Mock, patch
|
||||
|
||||
from crewai.cli.deploy.main import DeployCommand
|
||||
from crewai.cli.utils import parse_toml
|
||||
@@ -33,13 +37,65 @@ class TestDeployCommand(unittest.TestCase):
|
||||
with self.assertRaises(SystemExit):
|
||||
DeployCommand()
|
||||
|
||||
def test_handle_plus_api_error(self):
|
||||
def test_validate_response_successful_response(self):
|
||||
mock_response = Mock(spec=requests.Response)
|
||||
mock_response.json.return_value = {"message": "Success"}
|
||||
mock_response.status_code = 200
|
||||
mock_response.ok = True
|
||||
|
||||
with patch("sys.stdout", new=StringIO()) as fake_out:
|
||||
self.deploy_command._handle_plus_api_error(
|
||||
{"error": "Test error", "message": "Test message"}
|
||||
self.deploy_command._validate_response(mock_response)
|
||||
assert fake_out.getvalue() == ""
|
||||
|
||||
def test_validate_response_json_decode_error(self):
|
||||
mock_response = Mock(spec=requests.Response)
|
||||
mock_response.json.side_effect = JSONDecodeError("Decode error", "", 0)
|
||||
mock_response.status_code = 500
|
||||
mock_response.content = b"Invalid JSON"
|
||||
|
||||
with patch("sys.stdout", new=StringIO()) as fake_out:
|
||||
with pytest.raises(SystemExit):
|
||||
self.deploy_command._validate_response(mock_response)
|
||||
output = fake_out.getvalue()
|
||||
assert (
|
||||
"Failed to parse response from Enterprise API failed. Details:"
|
||||
in output
|
||||
)
|
||||
self.assertIn("Error: Test error", fake_out.getvalue())
|
||||
self.assertIn("Message: Test message", fake_out.getvalue())
|
||||
assert "Status Code: 500" in output
|
||||
assert "Response:\nb'Invalid JSON'" in output
|
||||
|
||||
def test_validate_response_422_error(self):
|
||||
mock_response = Mock(spec=requests.Response)
|
||||
mock_response.json.return_value = {
|
||||
"field1": ["Error message 1"],
|
||||
"field2": ["Error message 2"],
|
||||
}
|
||||
mock_response.status_code = 422
|
||||
mock_response.ok = False
|
||||
|
||||
with patch("sys.stdout", new=StringIO()) as fake_out:
|
||||
with pytest.raises(SystemExit):
|
||||
self.deploy_command._validate_response(mock_response)
|
||||
output = fake_out.getvalue()
|
||||
assert (
|
||||
"Failed to complete operation. Please fix the following errors:"
|
||||
in output
|
||||
)
|
||||
assert "Field1 Error message 1" in output
|
||||
assert "Field2 Error message 2" in output
|
||||
|
||||
def test_validate_response_other_error(self):
|
||||
mock_response = Mock(spec=requests.Response)
|
||||
mock_response.json.return_value = {"error": "Something went wrong"}
|
||||
mock_response.status_code = 500
|
||||
mock_response.ok = False
|
||||
|
||||
with patch("sys.stdout", new=StringIO()) as fake_out:
|
||||
with pytest.raises(SystemExit):
|
||||
self.deploy_command._validate_response(mock_response)
|
||||
output = fake_out.getvalue()
|
||||
assert "Request to Enterprise API failed. Details:" in output
|
||||
assert "Details:\nSomething went wrong" in output
|
||||
|
||||
def test_standard_no_param_error_message(self):
|
||||
with patch("sys.stdout", new=StringIO()) as fake_out:
|
||||
@@ -207,30 +263,7 @@ class TestDeployCommand(unittest.TestCase):
|
||||
project_name = get_project_name()
|
||||
self.assertEqual(project_name, "test_project")
|
||||
|
||||
@patch(
|
||||
"builtins.open",
|
||||
new_callable=unittest.mock.mock_open,
|
||||
read_data="""
|
||||
[[package]]
|
||||
name = "crewai"
|
||||
version = "0.51.1"
|
||||
description = "Some description"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.10,<4.0"
|
||||
""",
|
||||
)
|
||||
def test_get_crewai_version(self, mock_open):
|
||||
def test_get_crewai_version(self):
|
||||
from crewai.cli.utils import get_crewai_version
|
||||
|
||||
version = get_crewai_version()
|
||||
self.assertEqual(version, "0.51.1")
|
||||
|
||||
@patch("builtins.open", side_effect=FileNotFoundError)
|
||||
def test_get_crewai_version_file_not_found(self, mock_open):
|
||||
from crewai.cli.utils import get_crewai_version
|
||||
|
||||
with patch("sys.stdout", new=StringIO()) as fake_out:
|
||||
version = get_crewai_version()
|
||||
self.assertEqual(version, "no-version-found")
|
||||
self.assertIn("Error: poetry.lock not found.", fake_out.getvalue())
|
||||
assert isinstance(get_crewai_version(), str)
|
||||
|
||||
@@ -11,15 +11,22 @@ class TestPlusAPI(unittest.TestCase):
|
||||
|
||||
def test_init(self):
|
||||
self.assertEqual(self.api.api_key, self.api_key)
|
||||
self.assertEqual(
|
||||
self.api.headers,
|
||||
{
|
||||
"Authorization": f"Bearer {self.api_key}",
|
||||
"Content-Type": "application/json",
|
||||
"User-Agent": "CrewAI-CLI/no-version-found",
|
||||
"X-Crewai-Version": "no-version-found",
|
||||
},
|
||||
self.assertEqual(self.api.headers["Authorization"], f"Bearer {self.api_key}")
|
||||
self.assertEqual(self.api.headers["Content-Type"], "application/json")
|
||||
self.assertTrue("CrewAI-CLI/" in self.api.headers["User-Agent"])
|
||||
self.assertTrue(self.api.headers["X-Crewai-Version"])
|
||||
|
||||
@patch("crewai.cli.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"
|
||||
)
|
||||
self.assertEqual(response, mock_response)
|
||||
|
||||
@patch("crewai.cli.plus_api.PlusAPI._make_request")
|
||||
def test_get_tool(self, mock_make_request):
|
||||
|
||||
100
tests/cli/test_utils.py
Normal file
100
tests/cli/test_utils.py
Normal file
@@ -0,0 +1,100 @@
|
||||
import pytest
|
||||
import shutil
|
||||
import tempfile
|
||||
import os
|
||||
from crewai.cli import utils
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def temp_tree():
|
||||
root_dir = tempfile.mkdtemp()
|
||||
|
||||
create_file(os.path.join(root_dir, "file1.txt"), "Hello, world!")
|
||||
create_file(os.path.join(root_dir, "file2.txt"), "Another file")
|
||||
os.mkdir(os.path.join(root_dir, "empty_dir"))
|
||||
nested_dir = os.path.join(root_dir, "nested_dir")
|
||||
os.mkdir(nested_dir)
|
||||
create_file(os.path.join(nested_dir, "nested_file.txt"), "Nested content")
|
||||
|
||||
yield root_dir
|
||||
|
||||
shutil.rmtree(root_dir)
|
||||
|
||||
|
||||
def create_file(path, content):
|
||||
with open(path, "w") as f:
|
||||
f.write(content)
|
||||
|
||||
|
||||
def test_tree_find_and_replace_file_content(temp_tree):
|
||||
utils.tree_find_and_replace(temp_tree, "world", "universe")
|
||||
with open(os.path.join(temp_tree, "file1.txt"), "r") as f:
|
||||
assert f.read() == "Hello, universe!"
|
||||
|
||||
|
||||
def test_tree_find_and_replace_file_name(temp_tree):
|
||||
old_path = os.path.join(temp_tree, "file2.txt")
|
||||
new_path = os.path.join(temp_tree, "file2_renamed.txt")
|
||||
os.rename(old_path, new_path)
|
||||
utils.tree_find_and_replace(temp_tree, "renamed", "modified")
|
||||
assert os.path.exists(os.path.join(temp_tree, "file2_modified.txt"))
|
||||
assert not os.path.exists(new_path)
|
||||
|
||||
|
||||
def test_tree_find_and_replace_directory_name(temp_tree):
|
||||
utils.tree_find_and_replace(temp_tree, "empty", "renamed")
|
||||
assert os.path.exists(os.path.join(temp_tree, "renamed_dir"))
|
||||
assert not os.path.exists(os.path.join(temp_tree, "empty_dir"))
|
||||
|
||||
|
||||
def test_tree_find_and_replace_nested_content(temp_tree):
|
||||
utils.tree_find_and_replace(temp_tree, "Nested", "Updated")
|
||||
with open(os.path.join(temp_tree, "nested_dir", "nested_file.txt"), "r") as f:
|
||||
assert f.read() == "Updated content"
|
||||
|
||||
|
||||
def test_tree_find_and_replace_no_matches(temp_tree):
|
||||
utils.tree_find_and_replace(temp_tree, "nonexistent", "replacement")
|
||||
assert set(os.listdir(temp_tree)) == {
|
||||
"file1.txt",
|
||||
"file2.txt",
|
||||
"empty_dir",
|
||||
"nested_dir",
|
||||
}
|
||||
|
||||
|
||||
def test_tree_copy_full_structure(temp_tree):
|
||||
dest_dir = tempfile.mkdtemp()
|
||||
try:
|
||||
utils.tree_copy(temp_tree, dest_dir)
|
||||
assert set(os.listdir(dest_dir)) == set(os.listdir(temp_tree))
|
||||
assert os.path.isfile(os.path.join(dest_dir, "file1.txt"))
|
||||
assert os.path.isfile(os.path.join(dest_dir, "file2.txt"))
|
||||
assert os.path.isdir(os.path.join(dest_dir, "empty_dir"))
|
||||
assert os.path.isdir(os.path.join(dest_dir, "nested_dir"))
|
||||
assert os.path.isfile(os.path.join(dest_dir, "nested_dir", "nested_file.txt"))
|
||||
finally:
|
||||
shutil.rmtree(dest_dir)
|
||||
|
||||
|
||||
def test_tree_copy_preserve_content(temp_tree):
|
||||
dest_dir = tempfile.mkdtemp()
|
||||
try:
|
||||
utils.tree_copy(temp_tree, dest_dir)
|
||||
with open(os.path.join(dest_dir, "file1.txt"), "r") as f:
|
||||
assert f.read() == "Hello, world!"
|
||||
with open(os.path.join(dest_dir, "nested_dir", "nested_file.txt"), "r") as f:
|
||||
assert f.read() == "Nested content"
|
||||
finally:
|
||||
shutil.rmtree(dest_dir)
|
||||
|
||||
|
||||
def test_tree_copy_to_existing_directory(temp_tree):
|
||||
dest_dir = tempfile.mkdtemp()
|
||||
try:
|
||||
create_file(os.path.join(dest_dir, "existing_file.txt"), "I was here first")
|
||||
utils.tree_copy(temp_tree, dest_dir)
|
||||
assert os.path.isfile(os.path.join(dest_dir, "existing_file.txt"))
|
||||
assert os.path.isfile(os.path.join(dest_dir, "file1.txt"))
|
||||
finally:
|
||||
shutil.rmtree(dest_dir)
|
||||
@@ -1,11 +1,59 @@
|
||||
from contextlib import contextmanager
|
||||
import tempfile
|
||||
import unittest
|
||||
import unittest.mock
|
||||
import os
|
||||
from crewai.cli.tools.main import ToolCommand
|
||||
from io import StringIO
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
|
||||
class TestToolCommand(unittest.TestCase):
|
||||
@contextmanager
|
||||
def in_temp_dir(self):
|
||||
original_dir = os.getcwd()
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
os.chdir(temp_dir)
|
||||
try:
|
||||
yield temp_dir
|
||||
finally:
|
||||
os.chdir(original_dir)
|
||||
|
||||
@patch("crewai.cli.tools.main.subprocess.run")
|
||||
def test_create_success(self, mock_subprocess):
|
||||
with self.in_temp_dir():
|
||||
tool_command = ToolCommand()
|
||||
|
||||
with patch.object(tool_command, "login") as mock_login, patch(
|
||||
"sys.stdout", new=StringIO()
|
||||
) as fake_out:
|
||||
tool_command.create("test-tool")
|
||||
output = fake_out.getvalue()
|
||||
|
||||
self.assertTrue(os.path.isdir("test_tool"))
|
||||
|
||||
self.assertTrue(os.path.isfile(os.path.join("test_tool", "README.md")))
|
||||
self.assertTrue(os.path.isfile(os.path.join("test_tool", "pyproject.toml")))
|
||||
self.assertTrue(
|
||||
os.path.isfile(
|
||||
os.path.join("test_tool", "src", "test_tool", "__init__.py")
|
||||
)
|
||||
)
|
||||
self.assertTrue(
|
||||
os.path.isfile(os.path.join("test_tool", "src", "test_tool", "tool.py"))
|
||||
)
|
||||
|
||||
with open(
|
||||
os.path.join("test_tool", "src", "test_tool", "tool.py"), "r"
|
||||
) as f:
|
||||
content = f.read()
|
||||
self.assertIn("class TestTool", content)
|
||||
|
||||
mock_login.assert_called_once()
|
||||
mock_subprocess.assert_called_once_with(["git", "init"], check=True)
|
||||
|
||||
self.assertIn("Creating custom tool test_tool...", output)
|
||||
|
||||
@patch("crewai.cli.tools.main.subprocess.run")
|
||||
@patch("crewai.cli.plus_api.PlusAPI.get_tool")
|
||||
def test_install_success(self, mock_get, mock_subprocess_run):
|
||||
@@ -13,11 +61,7 @@ class TestToolCommand(unittest.TestCase):
|
||||
mock_get_response.status_code = 200
|
||||
mock_get_response.json.return_value = {
|
||||
"handle": "sample-tool",
|
||||
"repository": {
|
||||
"handle": "sample-repo",
|
||||
"url": "https://example.com/repo",
|
||||
"credentials": "my_very_secret",
|
||||
},
|
||||
"repository": {"handle": "sample-repo", "url": "https://example.com/repo"},
|
||||
}
|
||||
mock_get.return_value = mock_get_response
|
||||
mock_subprocess_run.return_value = MagicMock(stderr=None)
|
||||
@@ -29,30 +73,6 @@ class TestToolCommand(unittest.TestCase):
|
||||
output = fake_out.getvalue()
|
||||
|
||||
mock_get.assert_called_once_with("sample-tool")
|
||||
mock_subprocess_run.assert_any_call(
|
||||
[
|
||||
"poetry",
|
||||
"source",
|
||||
"add",
|
||||
"--priority=explicit",
|
||||
"crewai-sample-repo",
|
||||
"https://example.com/repo",
|
||||
],
|
||||
text=True,
|
||||
check=True,
|
||||
)
|
||||
mock_subprocess_run.assert_any_call(
|
||||
[
|
||||
"poetry",
|
||||
"config",
|
||||
"http-basic.crewai-sample-repo",
|
||||
"my_very_secret",
|
||||
'""',
|
||||
],
|
||||
capture_output=False,
|
||||
text=True,
|
||||
check=True,
|
||||
)
|
||||
mock_subprocess_run.assert_any_call(
|
||||
["poetry", "add", "--source", "crewai-sample-repo", "sample-tool"],
|
||||
capture_output=False,
|
||||
@@ -182,7 +202,7 @@ class TestToolCommand(unittest.TestCase):
|
||||
output = fake_out.getvalue()
|
||||
|
||||
mock_publish.assert_called_once()
|
||||
self.assertIn("Failed to publish tool", output)
|
||||
self.assertIn("Failed to complete operation", output)
|
||||
self.assertIn("Name is already taken", output)
|
||||
|
||||
@patch("crewai.cli.tools.main.get_project_name", return_value="sample-tool")
|
||||
@@ -210,9 +230,11 @@ class TestToolCommand(unittest.TestCase):
|
||||
mock_get_project_version,
|
||||
mock_get_project_name,
|
||||
):
|
||||
mock_publish_response = MagicMock()
|
||||
mock_publish_response.status_code = 500
|
||||
mock_publish.return_value = mock_publish_response
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 500
|
||||
mock_response.json.return_value = {"error": "Internal Server Error"}
|
||||
mock_response.ok = False
|
||||
mock_publish.return_value = mock_response
|
||||
|
||||
tool_command = ToolCommand()
|
||||
|
||||
@@ -222,8 +244,55 @@ class TestToolCommand(unittest.TestCase):
|
||||
output = fake_out.getvalue()
|
||||
|
||||
mock_publish.assert_called_once()
|
||||
self.assertIn("Failed to publish tool", output)
|
||||
self.assertIn("Request to Enterprise API failed", output)
|
||||
|
||||
@patch("crewai.cli.plus_api.PlusAPI.login_to_tool_repository")
|
||||
@patch("crewai.cli.tools.main.subprocess.run")
|
||||
def test_login_success(self, mock_subprocess_run, mock_login):
|
||||
mock_login_response = MagicMock()
|
||||
mock_login_response.status_code = 200
|
||||
mock_login_response.json.return_value = {
|
||||
"repositories": [
|
||||
{
|
||||
"handle": "tools",
|
||||
"url": "https://example.com/repo",
|
||||
}
|
||||
],
|
||||
"credential": {"username": "user", "password": "pass"},
|
||||
}
|
||||
mock_login.return_value = mock_login_response
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
mock_subprocess_run.return_value = MagicMock(stderr=None)
|
||||
|
||||
tool_command = ToolCommand()
|
||||
|
||||
with patch("sys.stdout", new=StringIO()) as fake_out:
|
||||
tool_command.login()
|
||||
output = fake_out.getvalue()
|
||||
|
||||
mock_login.assert_called_once()
|
||||
mock_subprocess_run.assert_any_call(
|
||||
[
|
||||
"poetry",
|
||||
"source",
|
||||
"add",
|
||||
"--priority=explicit",
|
||||
"crewai-tools",
|
||||
"https://example.com/repo",
|
||||
],
|
||||
text=True,
|
||||
check=True,
|
||||
)
|
||||
mock_subprocess_run.assert_any_call(
|
||||
[
|
||||
"poetry",
|
||||
"config",
|
||||
"http-basic.crewai-tools",
|
||||
"user",
|
||||
"pass",
|
||||
],
|
||||
capture_output=False,
|
||||
text=True,
|
||||
check=True,
|
||||
)
|
||||
self.assertIn("Succesfully authenticated to the tool repository", output)
|
||||
|
||||
Reference in New Issue
Block a user