mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-06-30 12:48:10 +00:00
Some checks failed
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Check Documentation Broken Links / Check broken links (push) Has been cancelled
Vulnerability Scan / pip-audit (push) Has been cancelled
Nightly Canary Release / Check for new commits (push) Has been cancelled
Nightly Canary Release / Build nightly packages (push) Has been cancelled
Nightly Canary Release / Publish nightly to PyPI (push) Has been cancelled
Mark stale issues and pull requests / stale (push) Has been cancelled
* feat(cli): introduce JSON crew project support and TUI enhancements - Added support for creating and running JSON-defined crew projects, allowing users to scaffold projects with a new `create_json_crew.py` file. - Implemented a full-screen Textual TUI for crew execution in `crew_run_tui.py`, enhancing user interaction with a two-column layout. - Updated `run_crew.py` to prioritize JSON crew projects and added daemon mode for running without TUI. - Introduced interactive pickers in `tui_picker.py` for improved CLI prompts. - Enhanced validation for JSON crew files in `validate.py` to ensure proper structure and agent definitions. - Updated `.gitignore` to exclude demo and crewai directories. * feat: update LLM model references to gpt-5.4-mini - Changed default LLM model from gpt-4o-mini to gpt-5.4-mini across various files, including CLI options, JSON crew configurations, and agent definitions. - Enhanced benchmark and human feedback functionalities to utilize the new model. - Improved user interface elements in the TUI for better interaction and feedback during execution. - Added support for new skills directory in JSON crew project creation. * feat(benchmark): add crew-level benchmarking functionality - Introduced a new `benchmark` command in the CLI for crew-level benchmarking, allowing users to specify agents, models, and timeout settings. - Implemented `CrewBenchmarkCase` to handle crew-level benchmark cases with inputs and criteria. - Enhanced the benchmark runner to support progress tracking and detailed reporting of results for multiple models. - Added tests for loading crew benchmark cases and validating their structure. - Updated existing benchmark functions to accommodate the new crew-level execution model. * feat(cli): enhance JSON crew project functionality and TUI improvements - Added optional agent-level guardrails and advanced options in JSON crew configurations to improve output validation and flexibility. - Updated the TUI to better handle plan step statuses, including visual indicators for task completion and failure. - Introduced methods for parsing and managing step observation events, ensuring accurate updates to task statuses during execution. - Enhanced validation for JSON crew projects, ensuring proper structure and error handling for agent and task definitions. - Added comprehensive tests for new features and validation logic, ensuring robustness in JSON crew project handling. * refactor(cli): streamline JSON crew project handling and improve validation - Refactored JSON crew project loading and validation logic to enhance clarity and maintainability. - Introduced utility functions for finding JSON crew files, improving code reuse across modules. - Removed deprecated benchmark functionality and associated tests to simplify the codebase. - Updated CLI commands to utilize the new JSON project structure, ensuring compatibility with recent changes. - Enhanced test coverage for JSON crew project features, ensuring robust validation and error handling. * feat(cli): enhance activity log navigation and focus management - Added functionality to focus on the activity log when navigating through log entries. - Implemented refresh logic for the log panel to ensure updates are displayed correctly during navigation. - Improved keyboard navigation for log entries, allowing users to expand and scroll through logs seamlessly. - Added tests to verify the correct behavior of log navigation and focus management in the TUI. * feat(cli): enhance JSON crew project interaction and input handling - Introduced a new function to enable prompt line editing for better user experience during input prompts. - Updated the JSON crew project wizards to show interpolation hints for dynamic values, improving user guidance. - Enhanced the handling of missing input placeholders by prompting users for required values during crew setup. - Refactored the crew run logic to ensure proper loading and preparation of JSON-defined crews, including runtime input management. - Added tests to verify the correct behavior of new input handling features and JSON crew project interactions. * feat(cli): improve crew project input prompts and event handling - Enhanced the `_prompt_text` function to allow for configurable spacing before prompts, improving user experience during input collection. - Updated the wizards for agent and task creation to utilize the new prompt configuration, ensuring a more compact and streamlined interaction. - Introduced new plan step lifecycle events (`PlanStepStartedEvent`, `PlanStepCompletedEvent`) to better track the execution status of plan steps. - Refactored the step executor to emit these events during the execution of tasks, improving observability and debugging capabilities. - Added tests to verify the correct behavior of new prompt handling and event emissions during crew project execution. * fix: refine json-first crew interactions * fix: prioritize common json crew tools * fix: make json crew more tools expandable * fix: show json crew tools by category * feat(memory): update default embedder to OpenAI text-embedding-3-large and enhance memory compatibility - Changed the default embedding model for Memory to OpenAI text-embedding-3-large, which uses 3072-dimensional vectors. - Added warnings regarding compatibility issues with existing local memory stores created with 1536-dimensional embeddings. - Updated documentation to reflect the new default embedder and its configuration options. - Enhanced the CLI and codebase to support the new embedding model across various components, ensuring a seamless transition for users. * fix: address PR review feedback for JSON-first crews Review blockers: - Forward trained_agents_file to JSON crews: crewai run -f now exports CREWAI_TRAINED_AGENTS_FILE for the in-process JSON crew path - Wizard agent picker: Esc/cancel now reprompts instead of silently assigning the first agent - JSON tool resolution hard-fails: unknown tool names, missing custom tool files, and invalid custom tool modules raise JSONProjectError with actionable messages instead of warn-and-continue - Embedding dimension mismatch: LanceDB and Qdrant Edge storages raise EmbeddingDimensionMismatchError with reset/pin guidance instead of silently zero-filling vectors or returning empty search results - Custom tool code execution documented in loader docstring and the scaffolded project README CI fixes: - ruff format across lib/ - All 133 PR-introduced mypy errors fixed (llm.py lazy-litellm and cli.py lazy command shims now use TYPE_CHECKING imports; textual is_mounted misuse fixed; pick_many overloads; misc annotations) Bot review comments: - Empty except blocks now have explanatory comments or debug logging - Removed unused _C_BG/_C_PANEL/_C_BORDER globals and redundant import re; tests use a single import style for create_json_crew Tests: trained-agents propagation, wizard cancel, tool resolution failures, and dimension mismatch guidance. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: address second round of PR review comments Cursor Bugbot: - Wizard agent slugs: strip to [a-z0-9_] and fall back to agent_<n> so symbol-only roles can't produce an empty agents/.jsonc filename - Wizard task names: dedupe against prior task names and fall back to task_<n> for symbol-only descriptions CodeRabbit: - Agent.message(): import Task explicitly at runtime instead of relying on the namespace injection done by crewai/__init__ - Async executor: move the native-tools-unsupported fallback from _ainvoke_loop_react (self-recursion) to _ainvoke_loop_native_tools, mirroring the sync implementation - StepExecutor downgrade: keep the in-step conversation and append the text-tooling instructions instead of rebuilding messages, so completed native tool calls are not re-executed - crewai-files: extension-based MIME lookup now runs before byte sniffing so csv/xml types are not degraded to text/plain - Memory storages: validate every record in a save() batch against a consistent embedding dimension (LanceDB previously checked only the first record); added mixed-batch tests - _print_post_tui_summary now typed against CrewRunApp - Docs: Azure OpenAI default embedder change called out in the memory migration warning and provider table Code quality bots: - Removed unused _C_YELLOW/_C_CYAN (crew_run_tui) and _GREEN (tui_picker) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(cli): accordion tool picker in JSON crew wizard The flat tool list had grown to ~90 rows. The picker now shows: - Common tools always visible at the top - Every other category as a single expandable row with tool and selection counts (e.g. "Search & Research (27 tools, 2 selected)") - Expanding a category collapses the previously expanded one - Selections persist across expand/collapse via new preselected support in pick_many; cursor follows the toggled category row tui_picker gains preselected + initial_cursor options on pick_many, and Esc in multi-select now confirms the current selection instead of discarding it (required so collapsing can't silently drop choices). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor(cli): remove --daemon flag from crewai run The flag only affected JSON crew projects — classic and flow projects ignored it entirely, which made the behavior inconsistent. Removed the option, the daemon code path (_run_json_crew_daemon), and its helper (_load_json_crew_with_inputs). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test: update run command tests after --daemon removal lib/crewai/tests/cli/test_run_crew.py still asserted the old run_crew(trained_agents_file=..., daemon=False) call signature. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(cli): exit codes, mid-run quit, async statuses, hyphen placeholders Addresses the latest Bugbot review round: - Failed JSON crew runs now exit non-zero (SystemExit(1)) so scripts and CI don't treat failures as success, mirroring the classic path - Quitting the TUI mid-run now ends the process (os._exit(130)); kickoff runs in a thread worker that cannot be force-cancelled, so letting the CLI return would leave LLM/tool work burning tokens in the background - Sidebar task statuses are now async-safe: completion/failure events resolve the task's own row via identity instead of assuming the most recently started task, and starting a task no longer blanket-marks earlier active rows as done - The runtime-input prompt regex now accepts hyphenated placeholder names ({my-topic}), matching kickoff's interpolation pattern Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: validation safety, custom tool sandboxing, TUI log integrity, memory error surfacing - Deploy validation no longer executes project code: validation mode checks tool declarations structurally (well-formed entries, custom tool file exists) without importing or instantiating anything. custom:<name> resolution only happens on the actual run path. - custom:<name> is constrained to [A-Za-z_][A-Za-z0-9_]* and the resolved path must stay inside the project's tools/ directory, so custom:../foo or absolute-path names cannot execute code outside it. Tool paths resolve relative to the crew project root, not cwd. - TUI task logs are built from per-task state captured at task start (idx, description, agent, start time); an out-of-order completion takes its output from the event and no longer steals or resets the current task's streamed steps/output. - EmbeddingDimensionMismatchError now inherits ValueError instead of RuntimeError so background saves surface it through MemorySaveFailedEvent instead of silently dropping the save; the shutdown catch in _background_encode_batch is narrowed to the "cannot schedule new futures" case. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(cli): declared project type wins over crew.json presence A flow project that also contains a crew.json(c) file now runs and validates as the flow it declares in pyproject.toml instead of being hijacked by the JSON crew path. Both crewai run (_has_json_crew) and deploy validation (_is_json_crew) check tool.crewai.type; a missing or unreadable pyproject still means a bare JSON crew project. Also documents why StepObservationFailedEvent intentionally marks the plan step "done": the event signals an observer failure, not a step failure, and the executor continues past it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(cli): type the declared_type locals so mypy stays clean Comparing an Any-typed .get() chain returns Any, which tripped no-any-return on the previous commit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
471 lines
17 KiB
Python
471 lines
17 KiB
Python
import os
|
|
import tempfile
|
|
import unittest
|
|
import unittest.mock
|
|
from contextlib import contextmanager
|
|
from datetime import datetime, timedelta
|
|
from pathlib import Path
|
|
from unittest import mock
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
from crewai_cli.shared.token_manager import TokenManager
|
|
from crewai_cli.tools.main import ToolCommand
|
|
from pytest import raises
|
|
|
|
|
|
@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)
|
|
|
|
|
|
@pytest.fixture
|
|
def tool_command():
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
with patch.object(
|
|
TokenManager, "_get_secure_storage_path", return_value=Path(temp_dir)
|
|
):
|
|
TokenManager().save_tokens(
|
|
"test-token", (datetime.now() + timedelta(seconds=36000)).timestamp()
|
|
)
|
|
tool_command = ToolCommand()
|
|
with patch.object(tool_command, "login"):
|
|
yield tool_command
|
|
|
|
|
|
@patch("crewai_cli.tools.main.subprocess.run")
|
|
def test_create_success(mock_subprocess, capsys, tool_command):
|
|
with in_temp_dir():
|
|
tool_command.create("test-tool")
|
|
output = capsys.readouterr().out
|
|
assert "Creating custom tool test_tool..." in output
|
|
|
|
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_subprocess.assert_called_once_with(["git", "init"], check=True)
|
|
|
|
|
|
@patch("crewai_cli.tools.main.subprocess.run")
|
|
@patch("crewai_cli.plus_api.PlusAPI.get_tool")
|
|
@patch("crewai_cli.tools.main.ToolCommand._print_current_organization")
|
|
def test_install_success(
|
|
mock_print_org, mock_get, mock_subprocess_run, capsys, tool_command
|
|
):
|
|
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.install("sample-tool")
|
|
output = capsys.readouterr().out
|
|
assert "Successfully installed sample-tool" in output
|
|
|
|
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,
|
|
)
|
|
|
|
mock_print_org.assert_called_once()
|
|
|
|
|
|
@patch("crewai_cli.tools.main.subprocess.run")
|
|
@patch("crewai_cli.plus_api.PlusAPI.get_tool")
|
|
def test_install_success_from_pypi(mock_get, mock_subprocess_run, capsys, tool_command):
|
|
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"},
|
|
"source": "pypi",
|
|
}
|
|
mock_get.return_value = mock_get_response
|
|
mock_subprocess_run.return_value = MagicMock(stderr=None)
|
|
|
|
tool_command.install("sample-tool")
|
|
output = capsys.readouterr().out
|
|
assert "Successfully installed sample-tool" in output
|
|
|
|
mock_get.assert_has_calls([mock.call("sample-tool"), mock.call().json()])
|
|
mock_subprocess_run.assert_any_call(
|
|
[
|
|
"uv",
|
|
"add",
|
|
"sample-tool",
|
|
],
|
|
capture_output=False,
|
|
text=True,
|
|
check=True,
|
|
env=unittest.mock.ANY,
|
|
)
|
|
|
|
|
|
@patch("crewai_cli.plus_api.PlusAPI.get_tool")
|
|
def test_install_tool_not_found(mock_get, capsys, tool_command):
|
|
mock_get_response = MagicMock()
|
|
mock_get_response.status_code = 404
|
|
mock_get.return_value = mock_get_response
|
|
|
|
with raises(SystemExit):
|
|
tool_command.install("non-existent-tool")
|
|
output = capsys.readouterr().out
|
|
assert "No tool found with this name" in output
|
|
|
|
mock_get.assert_called_once_with("non-existent-tool")
|
|
|
|
|
|
@patch("crewai_cli.plus_api.PlusAPI.get_tool")
|
|
def test_install_api_error(mock_get, capsys, tool_command):
|
|
mock_get_response = MagicMock()
|
|
mock_get_response.status_code = 500
|
|
mock_get.return_value = mock_get_response
|
|
|
|
with raises(SystemExit):
|
|
tool_command.install("error-tool")
|
|
output = capsys.readouterr().out
|
|
assert "Failed to get tool details" in output
|
|
|
|
mock_get.assert_called_once_with("error-tool")
|
|
|
|
|
|
@patch("crewai_cli.tools.main.git.Repository")
|
|
def test_publish_when_not_in_sync(mock_repository, capsys, tool_command):
|
|
mock_repository.return_value.is_synced.return_value = False
|
|
|
|
with raises(SystemExit):
|
|
tool_command.publish(is_public=True)
|
|
|
|
output = capsys.readouterr().out
|
|
assert "Local changes need to be resolved before publishing" in output
|
|
mock_repository.return_value.is_synced.assert_called_once_with()
|
|
|
|
|
|
@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.tools.main.git.Repository.fetch")
|
|
@patch("crewai_cli.plus_api.PlusAPI.publish_tool")
|
|
@patch("crewai_cli.tools.main.git.Repository.is_synced", return_value=False)
|
|
@patch(
|
|
"crewai.utilities.project_utils.extract_available_exports",
|
|
return_value=[{"name": "SampleTool"}],
|
|
)
|
|
@patch(
|
|
"crewai.utilities.project_utils.extract_tools_metadata",
|
|
return_value=[{"name": "SampleTool", "humanized_name": "sample_tool", "description": "A sample tool", "run_params_schema": {}, "init_params_schema": {}, "env_vars": []}],
|
|
)
|
|
@patch("crewai_cli.tools.main.ToolCommand._print_current_organization")
|
|
def test_publish_when_not_in_sync_and_force(
|
|
mock_print_org,
|
|
mock_tools_metadata,
|
|
mock_available_exports,
|
|
mock_is_synced,
|
|
mock_publish,
|
|
mock_fetch,
|
|
mock_open,
|
|
mock_listdir,
|
|
mock_subprocess_run,
|
|
mock_get_project_description,
|
|
mock_get_project_version,
|
|
mock_get_project_name,
|
|
tool_command,
|
|
):
|
|
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.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,
|
|
env=unittest.mock.ANY,
|
|
)
|
|
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,
|
|
available_exports=[{"name": "SampleTool"}],
|
|
tools_metadata=[{"name": "SampleTool", "humanized_name": "sample_tool", "description": "A sample tool", "run_params_schema": {}, "init_params_schema": {}, "env_vars": []}],
|
|
)
|
|
mock_print_org.assert_called_once()
|
|
|
|
|
|
@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.tools.main.git.Repository.fetch")
|
|
@patch("crewai_cli.plus_api.PlusAPI.publish_tool")
|
|
@patch("crewai_cli.tools.main.git.Repository.is_synced", return_value=True)
|
|
@patch(
|
|
"crewai.utilities.project_utils.extract_available_exports",
|
|
return_value=[{"name": "SampleTool"}],
|
|
)
|
|
@patch(
|
|
"crewai.utilities.project_utils.extract_tools_metadata",
|
|
return_value=[{"name": "SampleTool", "humanized_name": "sample_tool", "description": "A sample tool", "run_params_schema": {}, "init_params_schema": {}, "env_vars": []}],
|
|
)
|
|
def test_publish_success(
|
|
mock_tools_metadata,
|
|
mock_available_exports,
|
|
mock_is_synced,
|
|
mock_publish,
|
|
mock_fetch,
|
|
mock_open,
|
|
mock_listdir,
|
|
mock_subprocess_run,
|
|
mock_get_project_description,
|
|
mock_get_project_version,
|
|
mock_get_project_name,
|
|
tool_command,
|
|
):
|
|
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.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,
|
|
env=unittest.mock.ANY,
|
|
)
|
|
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,
|
|
available_exports=[{"name": "SampleTool"}],
|
|
tools_metadata=[{"name": "SampleTool", "humanized_name": "sample_tool", "description": "A sample tool", "run_params_schema": {}, "init_params_schema": {}, "env_vars": []}],
|
|
)
|
|
|
|
|
|
@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.utilities.project_utils.extract_available_exports",
|
|
return_value=[{"name": "SampleTool"}],
|
|
)
|
|
@patch(
|
|
"crewai.utilities.project_utils.extract_tools_metadata",
|
|
return_value=[{"name": "SampleTool", "humanized_name": "sample_tool", "description": "A sample tool", "run_params_schema": {}, "init_params_schema": {}, "env_vars": []}],
|
|
)
|
|
def test_publish_failure(
|
|
mock_tools_metadata,
|
|
mock_available_exports,
|
|
mock_publish,
|
|
mock_open,
|
|
mock_listdir,
|
|
mock_subprocess_run,
|
|
mock_get_project_description,
|
|
mock_get_project_version,
|
|
mock_get_project_name,
|
|
capsys,
|
|
tool_command,
|
|
):
|
|
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
|
|
|
|
with raises(SystemExit):
|
|
tool_command.publish(is_public=True)
|
|
output = capsys.readouterr().out
|
|
assert "Failed to complete operation" in output
|
|
assert "Name is already taken" in output
|
|
|
|
mock_publish.assert_called_once()
|
|
|
|
|
|
@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.utilities.project_utils.extract_available_exports",
|
|
return_value=[{"name": "SampleTool"}],
|
|
)
|
|
@patch(
|
|
"crewai.utilities.project_utils.extract_tools_metadata",
|
|
return_value=[{"name": "SampleTool", "humanized_name": "sample_tool", "description": "A sample tool", "run_params_schema": {}, "init_params_schema": {}, "env_vars": []}],
|
|
)
|
|
def test_publish_api_error(
|
|
mock_tools_metadata,
|
|
mock_available_exports,
|
|
mock_publish,
|
|
mock_open,
|
|
mock_listdir,
|
|
mock_subprocess_run,
|
|
mock_get_project_description,
|
|
mock_get_project_version,
|
|
mock_get_project_name,
|
|
capsys,
|
|
tool_command,
|
|
):
|
|
mock_response = MagicMock()
|
|
mock_response.status_code = 500
|
|
mock_response.json.return_value = {"error": "Internal Server Error"}
|
|
mock_response.is_success = False
|
|
mock_publish.return_value = mock_response
|
|
|
|
with raises(SystemExit):
|
|
tool_command.publish(is_public=True)
|
|
output = capsys.readouterr().out
|
|
assert "Request to Enterprise API failed" in output
|
|
|
|
mock_publish.assert_called_once()
|
|
|
|
|
|
@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)
|
|
@patch(
|
|
"crewai.utilities.project_utils.extract_available_exports",
|
|
return_value=[{"name": "SampleTool"}],
|
|
)
|
|
@patch(
|
|
"crewai.utilities.project_utils.extract_tools_metadata",
|
|
side_effect=Exception("Failed to extract metadata"),
|
|
)
|
|
def test_publish_metadata_extraction_failure_continues_with_warning(
|
|
mock_tools_metadata,
|
|
mock_available_exports,
|
|
mock_is_synced,
|
|
mock_publish,
|
|
mock_open,
|
|
mock_listdir,
|
|
mock_subprocess_run,
|
|
mock_get_project_description,
|
|
mock_get_project_version,
|
|
mock_get_project_name,
|
|
capsys,
|
|
tool_command,
|
|
):
|
|
"""Test that metadata extraction failure shows warning but continues publishing."""
|
|
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.publish(is_public=True)
|
|
|
|
output = capsys.readouterr().out
|
|
assert "Warning: Could not extract tool metadata" in output
|
|
assert "Publishing will continue without detailed metadata" in output
|
|
assert "No tool metadata extracted" in output
|
|
mock_publish.assert_called_once_with(
|
|
handle="sample-tool",
|
|
is_public=True,
|
|
version="1.0.0",
|
|
description="A sample tool",
|
|
encoded_file=unittest.mock.ANY,
|
|
available_exports=[{"name": "SampleTool"}],
|
|
tools_metadata=[],
|
|
)
|
|
|
|
|
|
@patch("crewai_cli.tools.main.Settings")
|
|
def test_print_current_organization_with_org(mock_settings, capsys, tool_command):
|
|
mock_settings_instance = MagicMock()
|
|
mock_settings_instance.org_uuid = "test-org-uuid"
|
|
mock_settings_instance.org_name = "Test Organization"
|
|
mock_settings.return_value = mock_settings_instance
|
|
tool_command._print_current_organization()
|
|
output = capsys.readouterr().out
|
|
assert "Current organization: Test Organization (test-org-uuid)" in output
|
|
|
|
|
|
@patch("crewai_cli.tools.main.Settings")
|
|
def test_print_current_organization_without_org(mock_settings, capsys, tool_command):
|
|
mock_settings_instance = MagicMock()
|
|
mock_settings_instance.org_uuid = None
|
|
mock_settings_instance.org_name = None
|
|
mock_settings.return_value = mock_settings_instance
|
|
tool_command._print_current_organization()
|
|
output = capsys.readouterr().out
|
|
assert "No organization currently set" in output
|
|
assert "org switch <org_id>" in output
|