import os import tempfile import unittest import unittest.mock from contextlib import contextmanager from unittest import mock from unittest.mock import MagicMock, patch from pytest import raises from crewai.cli.tools.main import ToolCommand @contextmanager def in_temp_dir(): 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(mock_subprocess, capsys): with in_temp_dir(): tool_command = ToolCommand() with patch.object(tool_command, "login") as mock_login: tool_command.create("test-tool") output = capsys.readouterr().out assert os.path.isdir("test_tool") assert os.path.isfile(os.path.join("test_tool", "README.md")) assert os.path.isfile(os.path.join("test_tool", "pyproject.toml")) assert os.path.isfile( os.path.join("test_tool", "src", "test_tool", "__init__.py") ) assert 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() assert "class TestTool" in content mock_login.assert_called_once() mock_subprocess.assert_called_once_with(["git", "init"], check=True) assert "Creating custom tool test_tool..." in output @patch("crewai.cli.tools.main.subprocess.run") @patch("crewai.cli.plus_api.PlusAPI.get_tool") def test_install_success(mock_get, mock_subprocess_run, capsys): mock_get_response = MagicMock() mock_get_response.status_code = 200 mock_get_response.json.return_value = { "handle": "sample-tool", "repository": {"handle": "sample-repo", "url": "https://example.com/repo"}, } mock_get.return_value = mock_get_response mock_subprocess_run.return_value = MagicMock(stderr=None) tool_command = ToolCommand() tool_command.install("sample-tool") output = capsys.readouterr().out mock_get.assert_has_calls([mock.call("sample-tool"), mock.call().json()]) mock_subprocess_run.assert_any_call( [ "uv", "add", "--index", "sample-repo=https://example.com/repo", "sample-tool", ], capture_output=False, text=True, check=True, env=unittest.mock.ANY, ) assert "Successfully installed sample-tool" in output @patch("crewai.cli.plus_api.PlusAPI.get_tool") def test_install_tool_not_found(mock_get, capsys): mock_get_response = MagicMock() mock_get_response.status_code = 404 mock_get.return_value = mock_get_response tool_command = ToolCommand() try: tool_command.install("non-existent-tool") except SystemExit: pass output = capsys.readouterr().out mock_get.assert_called_once_with("non-existent-tool") assert "No tool found with this name" in output @patch("crewai.cli.plus_api.PlusAPI.get_tool") def test_install_api_error(mock_get, capsys): mock_get_response = MagicMock() mock_get_response.status_code = 500 mock_get.return_value = mock_get_response tool_command = ToolCommand() try: tool_command.install("error-tool") except SystemExit: pass output = capsys.readouterr().out mock_get.assert_called_once_with("error-tool") assert "Failed to get tool details" in output @patch("crewai.cli.tools.main.git.Repository.is_synced", return_value=False) def test_publish_when_not_in_sync(mock_is_synced, capsys): with raises(SystemExit): tool_command = ToolCommand() tool_command.publish(is_public=True) output = capsys.readouterr().out assert "Local changes need to be resolved before publishing" in output @patch("crewai.cli.tools.main.get_project_name", return_value="sample-tool") @patch("crewai.cli.tools.main.get_project_version", return_value="1.0.0") @patch("crewai.cli.tools.main.get_project_description", return_value="A sample tool") @patch("crewai.cli.tools.main.subprocess.run") @patch("crewai.cli.tools.main.os.listdir", return_value=["sample-tool-1.0.0.tar.gz"]) @patch( "crewai.cli.tools.main.open", new_callable=unittest.mock.mock_open, read_data=b"sample tarball content", ) @patch("crewai.cli.plus_api.PlusAPI.publish_tool") @patch("crewai.cli.tools.main.git.Repository.is_synced", return_value=False) def test_publish_when_not_in_sync_and_force( mock_is_synced, mock_publish, mock_open, mock_listdir, mock_subprocess_run, mock_get_project_description, mock_get_project_version, mock_get_project_name, ): mock_publish_response = MagicMock() mock_publish_response.status_code = 200 mock_publish_response.json.return_value = {"handle": "sample-tool"} mock_publish.return_value = mock_publish_response tool_command = ToolCommand() tool_command.publish(is_public=True, force=True) mock_get_project_name.assert_called_with(require=True) mock_get_project_version.assert_called_with(require=True) mock_get_project_description.assert_called_with(require=False) mock_subprocess_run.assert_called_with( ["uv", "build", "--sdist", "--out-dir", unittest.mock.ANY], check=True, capture_output=False, ) mock_open.assert_called_with(unittest.mock.ANY, "rb") mock_publish.assert_called_with( handle="sample-tool", is_public=True, version="1.0.0", description="A sample tool", encoded_file=unittest.mock.ANY, ) @patch("crewai.cli.tools.main.get_project_name", return_value="sample-tool") @patch("crewai.cli.tools.main.get_project_version", return_value="1.0.0") @patch("crewai.cli.tools.main.get_project_description", return_value="A sample tool") @patch("crewai.cli.tools.main.subprocess.run") @patch("crewai.cli.tools.main.os.listdir", return_value=["sample-tool-1.0.0.tar.gz"]) @patch( "crewai.cli.tools.main.open", new_callable=unittest.mock.mock_open, read_data=b"sample tarball content", ) @patch("crewai.cli.plus_api.PlusAPI.publish_tool") @patch("crewai.cli.tools.main.git.Repository.is_synced", return_value=True) def test_publish_success( mock_is_synced, mock_publish, mock_open, mock_listdir, mock_subprocess_run, mock_get_project_description, mock_get_project_version, mock_get_project_name, ): mock_publish_response = MagicMock() mock_publish_response.status_code = 200 mock_publish_response.json.return_value = {"handle": "sample-tool"} mock_publish.return_value = mock_publish_response tool_command = ToolCommand() tool_command.publish(is_public=True) mock_get_project_name.assert_called_with(require=True) mock_get_project_version.assert_called_with(require=True) mock_get_project_description.assert_called_with(require=False) mock_subprocess_run.assert_called_with( ["uv", "build", "--sdist", "--out-dir", unittest.mock.ANY], check=True, capture_output=False, ) mock_open.assert_called_with(unittest.mock.ANY, "rb") mock_publish.assert_called_with( handle="sample-tool", is_public=True, version="1.0.0", description="A sample tool", encoded_file=unittest.mock.ANY, ) @patch("crewai.cli.tools.main.get_project_name", return_value="sample-tool") @patch("crewai.cli.tools.main.get_project_version", return_value="1.0.0") @patch("crewai.cli.tools.main.get_project_description", return_value="A sample tool") @patch("crewai.cli.tools.main.subprocess.run") @patch("crewai.cli.tools.main.os.listdir", return_value=["sample-tool-1.0.0.tar.gz"]) @patch( "crewai.cli.tools.main.open", new_callable=unittest.mock.mock_open, read_data=b"sample tarball content", ) @patch("crewai.cli.plus_api.PlusAPI.publish_tool") def test_publish_failure( mock_publish, mock_open, mock_listdir, mock_subprocess_run, mock_get_project_description, mock_get_project_version, mock_get_project_name, capsys, ): mock_publish_response = MagicMock() mock_publish_response.status_code = 422 mock_publish_response.json.return_value = {"name": ["is already taken"]} mock_publish.return_value = mock_publish_response tool_command = ToolCommand() try: tool_command.publish(is_public=True) except SystemExit: pass output = capsys.readouterr().out mock_publish.assert_called_once() assert "Failed to complete operation" in output assert "Name is already taken" in output @patch("crewai.cli.tools.main.get_project_name", return_value="sample-tool") @patch("crewai.cli.tools.main.get_project_version", return_value="1.0.0") @patch("crewai.cli.tools.main.get_project_description", return_value="A sample tool") @patch("crewai.cli.tools.main.subprocess.run") @patch("crewai.cli.tools.main.os.listdir", return_value=["sample-tool-1.0.0.tar.gz"]) @patch( "crewai.cli.tools.main.open", new_callable=unittest.mock.mock_open, read_data=b"sample tarball content", ) @patch("crewai.cli.plus_api.PlusAPI.publish_tool") def test_publish_api_error( mock_publish, mock_open, mock_listdir, mock_subprocess_run, mock_get_project_description, mock_get_project_version, mock_get_project_name, capsys, ): 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() try: tool_command.publish(is_public=True) except SystemExit: pass output = capsys.readouterr().out mock_publish.assert_called_once() assert "Request to Enterprise API failed" in output