mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-07-05 06:59:23 +00:00
Support ZIP deployment fallback and JSON crew project env runs (#6166)
* Update crewAI CLI with various enhancements and fixes - Updated `create_json_crew.py` to require `crewai[tools]>=1.14.7`. - Enhanced `git.py` with improved repository initialization, including automatic initial commit creation and exclusion patterns for initial commits. - Modified `install_crew.py` to allow error handling during installation with an optional `raise_on_error` parameter. - Expanded `plus_api.py` to include methods for creating and updating crews from ZIP files. - Introduced a new `archive.py` for creating deployable ZIP archives of CrewAI projects, ensuring local artifacts are excluded. - Updated `run_crew.py` to manage JSON crew dependencies and run crews in the project's environment. - Enhanced deployment logic in `main.py` to handle ZIP uploads and improve user feedback during deployment processes. - Added tests for new functionalities and ensured existing tests reflect recent changes in behavior and requirements. * fix(cli): address deploy zip review feedback * fix(cli): sync missing lockfile before deploy * fix(cli): preserve remote deploy on git setup warnings * test(cli): use single deploy main import style * fix(cli): skip project install for json crew sync * fix(cli): load json runner from source checkout * fix(cli): skip json crew sync when locked * fix(cli): address deploy zip review feedback * fix(cli): pass env on zip redeploy * fix(cli): harden json run and zip fallback * fix(cli): validate before deploy lock install * fix(cli): respect poetry lock for json runs * fix(cli): align json zip wrapper detection * fix(deps): bump starlette audit floor * fix(cli): avoid auth retry for deploy exits * fix(cli): update json zip script entrypoints
This commit is contained in:
@@ -1,16 +1,172 @@
|
||||
import sys
|
||||
import unittest
|
||||
from io import StringIO
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
from unittest.mock import MagicMock, Mock, patch
|
||||
|
||||
import pytest
|
||||
import json
|
||||
|
||||
import crewai_cli.deploy.main as deploy_main
|
||||
import httpx
|
||||
from crewai_cli.deploy.main import DeployCommand
|
||||
from crewai_cli.deploy.validate import Severity, ValidationResult
|
||||
from crewai_cli.utils import parse_toml
|
||||
|
||||
|
||||
def test_ensure_lockfile_for_deploy_runs_install_when_lock_missing(
|
||||
monkeypatch, tmp_path: Path
|
||||
):
|
||||
monkeypatch.chdir(tmp_path)
|
||||
(tmp_path / "pyproject.toml").write_text("[project]\nname = 'demo'\n")
|
||||
calls = []
|
||||
|
||||
def fake_install_crew(proxy_options, *, raise_on_error=False):
|
||||
calls.append((proxy_options, raise_on_error))
|
||||
|
||||
monkeypatch.setattr("crewai_cli.install_crew.install_crew", fake_install_crew)
|
||||
|
||||
deploy_main._ensure_lockfile_for_deploy()
|
||||
|
||||
assert calls == [([], True)]
|
||||
|
||||
|
||||
def test_ensure_lockfile_for_deploy_skips_when_lock_exists(monkeypatch, tmp_path: Path):
|
||||
monkeypatch.chdir(tmp_path)
|
||||
(tmp_path / "pyproject.toml").write_text("[project]\nname = 'demo'\n")
|
||||
(tmp_path / "uv.lock").write_text("# lock\n")
|
||||
calls = []
|
||||
|
||||
def fake_install_crew(proxy_options, *, raise_on_error=False):
|
||||
calls.append((proxy_options, raise_on_error))
|
||||
|
||||
monkeypatch.setattr("crewai_cli.install_crew.install_crew", fake_install_crew)
|
||||
|
||||
deploy_main._ensure_lockfile_for_deploy()
|
||||
|
||||
assert calls == []
|
||||
|
||||
|
||||
def test_ensure_lockfile_for_deploy_skips_without_pyproject(
|
||||
monkeypatch, tmp_path: Path
|
||||
):
|
||||
monkeypatch.chdir(tmp_path)
|
||||
calls = []
|
||||
|
||||
def fake_install_crew(proxy_options, *, raise_on_error=False):
|
||||
calls.append((proxy_options, raise_on_error))
|
||||
|
||||
monkeypatch.setattr("crewai_cli.install_crew.install_crew", fake_install_crew)
|
||||
|
||||
deploy_main._ensure_lockfile_for_deploy()
|
||||
|
||||
assert calls == []
|
||||
|
||||
|
||||
def test_ensure_lockfile_for_deploy_failure_exits_nonzero(
|
||||
monkeypatch, tmp_path: Path
|
||||
):
|
||||
monkeypatch.chdir(tmp_path)
|
||||
(tmp_path / "pyproject.toml").write_text("[project]\nname = 'demo'\n")
|
||||
|
||||
def fake_install_crew(proxy_options, *, raise_on_error=False):
|
||||
raise subprocess.CalledProcessError(42, ["uv", "sync"])
|
||||
|
||||
monkeypatch.setattr("crewai_cli.install_crew.install_crew", fake_install_crew)
|
||||
|
||||
with pytest.raises(SystemExit) as exc_info:
|
||||
deploy_main._ensure_lockfile_for_deploy()
|
||||
|
||||
assert exc_info.value.code == 42
|
||||
|
||||
|
||||
class _FakeDeployValidator:
|
||||
def __init__(self, results: list[ValidationResult]):
|
||||
self.results = results
|
||||
|
||||
@property
|
||||
def errors(self) -> list[ValidationResult]:
|
||||
return [
|
||||
result
|
||||
for result in self.results
|
||||
if result.severity is Severity.ERROR
|
||||
]
|
||||
|
||||
def run(self) -> list[ValidationResult]:
|
||||
return self.results
|
||||
|
||||
|
||||
def test_prepare_project_for_deploy_blocks_install_when_validation_fails(
|
||||
monkeypatch, tmp_path: Path
|
||||
):
|
||||
monkeypatch.chdir(tmp_path)
|
||||
(tmp_path / "pyproject.toml").write_text("[project]\nname = 'demo'\n")
|
||||
install_calls = []
|
||||
rendered_results = []
|
||||
missing_lockfile = ValidationResult(
|
||||
Severity.ERROR,
|
||||
"missing_lockfile",
|
||||
"Expected to find a lockfile",
|
||||
)
|
||||
invalid_config = ValidationResult(
|
||||
Severity.ERROR,
|
||||
"invalid_crew_json",
|
||||
"crew.jsonc has invalid configuration",
|
||||
)
|
||||
|
||||
monkeypatch.setattr(
|
||||
deploy_main,
|
||||
"DeployValidator",
|
||||
lambda: _FakeDeployValidator([missing_lockfile, invalid_config]),
|
||||
)
|
||||
monkeypatch.setattr(deploy_main, "render_report", rendered_results.append)
|
||||
|
||||
def fake_install_crew(proxy_options, *, raise_on_error=False):
|
||||
install_calls.append((proxy_options, raise_on_error))
|
||||
|
||||
monkeypatch.setattr("crewai_cli.install_crew.install_crew", fake_install_crew)
|
||||
|
||||
assert deploy_main._prepare_project_for_deploy(skip_validate=False) is False
|
||||
|
||||
assert install_calls == []
|
||||
assert [[result.code for result in results] for results in rendered_results] == [
|
||||
["invalid_crew_json"]
|
||||
]
|
||||
|
||||
|
||||
def test_prepare_project_for_deploy_creates_missing_lock_after_validation(
|
||||
monkeypatch, tmp_path: Path
|
||||
):
|
||||
monkeypatch.chdir(tmp_path)
|
||||
(tmp_path / "pyproject.toml").write_text("[project]\nname = 'demo'\n")
|
||||
install_calls = []
|
||||
missing_lockfile = ValidationResult(
|
||||
Severity.ERROR,
|
||||
"missing_lockfile",
|
||||
"Expected to find a lockfile",
|
||||
)
|
||||
validators = [
|
||||
_FakeDeployValidator([missing_lockfile]),
|
||||
_FakeDeployValidator([]),
|
||||
]
|
||||
|
||||
def fake_validator():
|
||||
return validators.pop(0)
|
||||
|
||||
def fake_install_crew(proxy_options, *, raise_on_error=False):
|
||||
install_calls.append((proxy_options, raise_on_error))
|
||||
(tmp_path / "uv.lock").write_text("# lock\n")
|
||||
|
||||
monkeypatch.setattr(deploy_main, "DeployValidator", fake_validator)
|
||||
monkeypatch.setattr(deploy_main, "render_report", lambda results: None)
|
||||
monkeypatch.setattr("crewai_cli.install_crew.install_crew", fake_install_crew)
|
||||
|
||||
assert deploy_main._prepare_project_for_deploy(skip_validate=False) is True
|
||||
|
||||
assert install_calls == [([], True)]
|
||||
assert validators == []
|
||||
|
||||
|
||||
class TestDeployCommand(unittest.TestCase):
|
||||
@patch("crewai_cli.command.get_auth_token")
|
||||
@patch("crewai_cli.deploy.main.get_project_name")
|
||||
@@ -28,19 +184,25 @@ class TestDeployCommand(unittest.TestCase):
|
||||
self.mock_get_auth_token.return_value = "test_token"
|
||||
self.mock_get_project_name.return_value = "test_project"
|
||||
|
||||
self.deploy_command = DeployCommand()
|
||||
self.deploy_command = deploy_main.DeployCommand()
|
||||
self.mock_client = self.deploy_command.plus_api_client
|
||||
|
||||
def test_init_success(self):
|
||||
self.assertEqual(self.deploy_command.project_name, "test_project")
|
||||
self.mock_plus_api.assert_called_once_with(api_key="test_token")
|
||||
|
||||
@patch("builtins.input")
|
||||
def test_confirm_zip_input_only_confirms_env_vars(self, mock_input):
|
||||
self.deploy_command._confirm_zip_input({"MODEL": "openai/gpt-5"}, False)
|
||||
|
||||
mock_input.assert_called_once_with("Press Enter to continue with 1 env vars: MODEL")
|
||||
|
||||
@patch("crewai_cli.command.get_auth_token")
|
||||
def test_init_failure(self, mock_get_auth_token):
|
||||
mock_get_auth_token.side_effect = Exception("Auth failed")
|
||||
|
||||
with self.assertRaises(SystemExit):
|
||||
DeployCommand()
|
||||
deploy_main.DeployCommand()
|
||||
|
||||
def test_validate_response_successful_response(self):
|
||||
mock_response = Mock(spec=httpx.Response)
|
||||
@@ -123,8 +285,15 @@ class TestDeployCommand(unittest.TestCase):
|
||||
)
|
||||
self.assertIn("2023-01-01 - INFO: Test log", fake_out.getvalue())
|
||||
|
||||
@patch("crewai_cli.deploy.main.git.Repository")
|
||||
@patch("crewai_cli.deploy.main.DeployCommand._display_deployment_info")
|
||||
def test_deploy_with_uuid(self, mock_display):
|
||||
def test_deploy_with_uuid(self, mock_display, mock_repository):
|
||||
mock_repository.return_value.origin_url.return_value = (
|
||||
"https://github.com/test/repo.git"
|
||||
)
|
||||
mock_repository.return_value.create_initial_commit_if_needed.return_value = (
|
||||
False
|
||||
)
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
mock_response.json.return_value = {"uuid": "test-uuid"}
|
||||
@@ -135,8 +304,15 @@ class TestDeployCommand(unittest.TestCase):
|
||||
self.mock_client.deploy_by_uuid.assert_called_once_with("test-uuid")
|
||||
mock_display.assert_called_once_with({"uuid": "test-uuid"})
|
||||
|
||||
@patch("crewai_cli.deploy.main.git.Repository")
|
||||
@patch("crewai_cli.deploy.main.DeployCommand._display_deployment_info")
|
||||
def test_deploy_with_project_name(self, mock_display):
|
||||
def test_deploy_with_project_name(self, mock_display, mock_repository):
|
||||
mock_repository.return_value.origin_url.return_value = (
|
||||
"https://github.com/test/repo.git"
|
||||
)
|
||||
mock_repository.return_value.create_initial_commit_if_needed.return_value = (
|
||||
False
|
||||
)
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
mock_response.json.return_value = {"uuid": "test-uuid"}
|
||||
@@ -147,17 +323,142 @@ class TestDeployCommand(unittest.TestCase):
|
||||
self.mock_client.deploy_by_name.assert_called_once_with("test_project")
|
||||
mock_display.assert_called_once_with({"uuid": "test-uuid"})
|
||||
|
||||
@patch("crewai_cli.deploy.main.create_project_zip")
|
||||
@patch("crewai_cli.deploy.main.git.Repository")
|
||||
@patch("crewai_cli.deploy.main.DeployCommand._display_deployment_info")
|
||||
def test_deploy_with_remote_keeps_remote_path_when_fetch_fails(
|
||||
self, mock_display, mock_repository, mock_create_project_zip
|
||||
):
|
||||
repository = mock_repository.return_value
|
||||
repository.origin_url.return_value = "https://github.com/test/repo.git"
|
||||
repository.fetch.side_effect = ValueError("fetch failed")
|
||||
repository.create_initial_commit_if_needed.return_value = False
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
mock_response.is_success = True
|
||||
mock_response.json.return_value = {"uuid": "test-uuid"}
|
||||
self.mock_client.deploy_by_name.return_value = mock_response
|
||||
|
||||
with patch("sys.stdout", new=StringIO()) as fake_out:
|
||||
self.deploy_command.deploy(skip_validate=True)
|
||||
output = fake_out.getvalue()
|
||||
|
||||
mock_repository.assert_called_once_with(fetch=False)
|
||||
repository.fetch.assert_called_once_with()
|
||||
self.assertIn("Continuing with remote deployment", output)
|
||||
self.mock_client.deploy_by_name.assert_called_once_with("test_project")
|
||||
self.mock_client.update_crew_from_zip.assert_not_called()
|
||||
mock_create_project_zip.assert_not_called()
|
||||
mock_display.assert_called_once_with({"uuid": "test-uuid"})
|
||||
|
||||
@patch("crewai_cli.deploy.main.create_project_zip")
|
||||
@patch("crewai_cli.deploy.main.git.Repository")
|
||||
@patch("crewai_cli.deploy.main.DeployCommand._display_deployment_info")
|
||||
def test_deploy_with_remote_keeps_remote_path_when_initial_commit_fails(
|
||||
self, mock_display, mock_repository, mock_create_project_zip
|
||||
):
|
||||
repository = mock_repository.return_value
|
||||
repository.origin_url.return_value = "https://github.com/test/repo.git"
|
||||
repository.create_initial_commit_if_needed.side_effect = RuntimeError(
|
||||
"commit failed"
|
||||
)
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
mock_response.is_success = True
|
||||
mock_response.json.return_value = {"uuid": "test-uuid"}
|
||||
self.mock_client.deploy_by_name.return_value = mock_response
|
||||
|
||||
with patch("sys.stdout", new=StringIO()) as fake_out:
|
||||
self.deploy_command.deploy(skip_validate=True)
|
||||
output = fake_out.getvalue()
|
||||
|
||||
mock_repository.assert_called_once_with(fetch=False)
|
||||
repository.fetch.assert_called_once_with()
|
||||
self.assertIn("Continuing with remote deployment", output)
|
||||
self.mock_client.deploy_by_name.assert_called_once_with("test_project")
|
||||
self.mock_client.update_crew_from_zip.assert_not_called()
|
||||
mock_create_project_zip.assert_not_called()
|
||||
mock_display.assert_called_once_with({"uuid": "test-uuid"})
|
||||
|
||||
@patch("crewai_cli.deploy.main.create_project_zip")
|
||||
@patch("crewai_cli.deploy.main.fetch_and_json_env_file")
|
||||
@patch("crewai_cli.deploy.main.git.Repository.origin_url")
|
||||
@patch("crewai_cli.deploy.main.git.Repository")
|
||||
@patch("crewai_cli.deploy.main.DeployCommand._display_deployment_info")
|
||||
def test_deploy_with_uuid_without_remote_updates_from_zip(
|
||||
self, mock_display, mock_repository, mock_fetch_env, mock_create_project_zip
|
||||
):
|
||||
mock_fetch_env.return_value = {"ENV_VAR": "value"}
|
||||
mock_repository.return_value.origin_url.return_value = None
|
||||
mock_repository.return_value.create_initial_commit_if_needed.return_value = (
|
||||
False
|
||||
)
|
||||
mock_create_project_zip.return_value = Path("/tmp/test_project.zip")
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
mock_response.json.return_value = {"uuid": "test-uuid"}
|
||||
self.mock_client.update_crew_from_zip.return_value = mock_response
|
||||
|
||||
self.deploy_command.deploy(uuid="test-uuid", skip_validate=True)
|
||||
|
||||
self.mock_client.update_crew_from_zip.assert_called_once_with(
|
||||
"test-uuid",
|
||||
Path("/tmp/test_project.zip"),
|
||||
env={"ENV_VAR": "value"},
|
||||
)
|
||||
self.mock_client.deploy_by_uuid.assert_not_called()
|
||||
mock_display.assert_called_once_with({"uuid": "test-uuid"})
|
||||
|
||||
@patch("crewai_cli.deploy.main.create_project_zip")
|
||||
@patch("crewai_cli.deploy.main.fetch_and_json_env_file")
|
||||
@patch("crewai_cli.deploy.main.git.Repository")
|
||||
@patch("crewai_cli.deploy.main.DeployCommand._display_deployment_info")
|
||||
def test_deploy_with_project_name_without_remote_updates_from_zip(
|
||||
self, mock_display, mock_repository, mock_fetch_env, mock_create_project_zip
|
||||
):
|
||||
mock_fetch_env.return_value = {"ENV_VAR": "value"}
|
||||
mock_repository.return_value.origin_url.return_value = None
|
||||
mock_repository.return_value.create_initial_commit_if_needed.return_value = (
|
||||
False
|
||||
)
|
||||
mock_create_project_zip.return_value = Path("/tmp/test_project.zip")
|
||||
status_response = MagicMock()
|
||||
status_response.status_code = 200
|
||||
status_response.is_success = True
|
||||
status_response.json.return_value = {"uuid": "test-uuid"}
|
||||
update_response = MagicMock()
|
||||
update_response.status_code = 200
|
||||
update_response.json.return_value = {"uuid": "test-uuid"}
|
||||
self.mock_client.crew_status_by_name.return_value = status_response
|
||||
self.mock_client.update_crew_from_zip.return_value = update_response
|
||||
|
||||
self.deploy_command.deploy(skip_validate=True)
|
||||
|
||||
self.mock_client.crew_status_by_name.assert_called_once_with("test_project")
|
||||
self.mock_client.update_crew_from_zip.assert_called_once_with(
|
||||
"test-uuid",
|
||||
Path("/tmp/test_project.zip"),
|
||||
env={"ENV_VAR": "value"},
|
||||
)
|
||||
self.mock_client.deploy_by_name.assert_not_called()
|
||||
mock_display.assert_called_once_with({"uuid": "test-uuid"})
|
||||
|
||||
@patch("crewai_cli.deploy.main.fetch_and_json_env_file")
|
||||
@patch("crewai_cli.deploy.main.git.Repository")
|
||||
@patch("builtins.input")
|
||||
@pytest.mark.timeout(180)
|
||||
def test_create_crew(self, mock_input, mock_git_origin_url, mock_fetch_env):
|
||||
def test_create_crew(self, mock_input, mock_repository, mock_fetch_env):
|
||||
mock_fetch_env.return_value = {"ENV_VAR": "value"}
|
||||
mock_git_origin_url.return_value = "https://github.com/test/repo.git"
|
||||
mock_repository.return_value.origin_url.return_value = (
|
||||
"https://github.com/test/repo.git"
|
||||
)
|
||||
mock_repository.return_value.create_initial_commit_if_needed.return_value = (
|
||||
False
|
||||
)
|
||||
mock_input.return_value = ""
|
||||
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 201
|
||||
mock_response.is_success = True
|
||||
mock_response.json.return_value = {"uuid": "new-uuid", "status": "created"}
|
||||
self.mock_client.create_crew.return_value = mock_response
|
||||
|
||||
@@ -166,38 +467,127 @@ class TestDeployCommand(unittest.TestCase):
|
||||
self.assertIn("Deployment created successfully!", fake_out.getvalue())
|
||||
self.assertIn("new-uuid", fake_out.getvalue())
|
||||
|
||||
@patch("crewai_cli.deploy.main.create_project_zip")
|
||||
@patch("crewai_cli.deploy.main.fetch_and_json_env_file")
|
||||
@patch("crewai_cli.deploy.main.git.Repository")
|
||||
def test_create_crew_without_git_repo_shows_setup_help(
|
||||
self, mock_repository, mock_fetch_env
|
||||
def test_create_crew_without_git_repo_initializes_and_uses_zip(
|
||||
self, mock_repository, mock_fetch_env, mock_create_project_zip
|
||||
):
|
||||
mock_fetch_env.return_value = {"ENV_VAR": "value"}
|
||||
mock_repository.side_effect = ValueError("not a Git repository")
|
||||
initialized_repository = MagicMock()
|
||||
initialized_repository.origin_url.return_value = None
|
||||
mock_repository.initialize.return_value = initialized_repository
|
||||
mock_create_project_zip.return_value = Path("/tmp/test_project.zip")
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 201
|
||||
mock_response.is_success = True
|
||||
mock_response.json.return_value = {"uuid": "zip-uuid", "status": "created"}
|
||||
self.mock_client.create_crew_from_zip.return_value = mock_response
|
||||
|
||||
with patch("sys.stdout", new=StringIO()) as fake_out:
|
||||
self.deploy_command.create_crew(skip_validate=True)
|
||||
self.deploy_command.create_crew(confirm=True, skip_validate=True)
|
||||
output = fake_out.getvalue()
|
||||
|
||||
self.assertIn("Deployment requires a Git repository", output)
|
||||
self.assertIn("git init", output)
|
||||
self.assertIn("git remote add origin <your-repo-url>", output)
|
||||
self.assertIn("Initialized a local Git repository", output)
|
||||
self.assertIn("Deploying from a ZIP upload", output)
|
||||
mock_repository.initialize.assert_called_once_with()
|
||||
mock_create_project_zip.assert_called_once_with(
|
||||
"test_project", repository=initialized_repository
|
||||
)
|
||||
self.mock_client.create_crew_from_zip.assert_called_once_with(
|
||||
Path("/tmp/test_project.zip"),
|
||||
name="test_project",
|
||||
env={"ENV_VAR": "value"},
|
||||
)
|
||||
self.mock_client.create_crew.assert_not_called()
|
||||
|
||||
@patch("crewai_cli.deploy.main.git.Repository")
|
||||
def test_prepare_git_repository_returns_repo_when_init_commit_fails(
|
||||
self, mock_repository
|
||||
):
|
||||
recovered_repository = MagicMock()
|
||||
mock_repository.side_effect = [
|
||||
ValueError("not a Git repository"),
|
||||
recovered_repository,
|
||||
]
|
||||
mock_repository.initialize.side_effect = RuntimeError("commit failed")
|
||||
|
||||
with patch("sys.stdout", new=StringIO()) as fake_out:
|
||||
repository = self.deploy_command._prepare_git_repository()
|
||||
|
||||
self.assertIs(repository, recovered_repository)
|
||||
self.assertIn("Git auto-setup did not complete", fake_out.getvalue())
|
||||
mock_repository.initialize.assert_called_once_with()
|
||||
self.assertEqual(mock_repository.call_count, 2)
|
||||
|
||||
@patch("crewai_cli.deploy.main.create_project_zip")
|
||||
@patch("crewai_cli.deploy.main.fetch_and_json_env_file")
|
||||
@patch("crewai_cli.deploy.main.git.Repository")
|
||||
def test_create_crew_without_remote_shows_remote_help(
|
||||
self, mock_repository, mock_fetch_env
|
||||
def test_create_crew_without_remote_uses_zip(
|
||||
self, mock_repository, mock_fetch_env, mock_create_project_zip
|
||||
):
|
||||
mock_fetch_env.return_value = {"ENV_VAR": "value"}
|
||||
mock_repository.return_value.origin_url.return_value = None
|
||||
mock_repository.return_value.create_initial_commit_if_needed.return_value = (
|
||||
False
|
||||
)
|
||||
mock_create_project_zip.return_value = Path("/tmp/test_project.zip")
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 201
|
||||
mock_response.is_success = True
|
||||
mock_response.json.return_value = {"uuid": "zip-uuid", "status": "created"}
|
||||
self.mock_client.create_crew_from_zip.return_value = mock_response
|
||||
|
||||
with patch("sys.stdout", new=StringIO()) as fake_out:
|
||||
self.deploy_command.create_crew(skip_validate=True)
|
||||
self.deploy_command.create_crew(confirm=True, skip_validate=True)
|
||||
output = fake_out.getvalue()
|
||||
|
||||
self.assertIn("No remote repository URL found.", output)
|
||||
self.assertIn("git remote add origin <your-repo-url>", output)
|
||||
self.assertIn("git push -u origin HEAD", output)
|
||||
self.assertIn("No origin remote found.", output)
|
||||
self.assertIn("Deploying from a ZIP upload", output)
|
||||
mock_create_project_zip.assert_called_once_with(
|
||||
"test_project", repository=mock_repository.return_value
|
||||
)
|
||||
self.mock_client.create_crew_from_zip.assert_called_once_with(
|
||||
Path("/tmp/test_project.zip"),
|
||||
name="test_project",
|
||||
env={"ENV_VAR": "value"},
|
||||
)
|
||||
self.mock_client.create_crew.assert_not_called()
|
||||
|
||||
@patch("crewai_cli.deploy.main.create_project_zip")
|
||||
@patch("crewai_cli.deploy.main.fetch_and_json_env_file")
|
||||
@patch("crewai_cli.deploy.main.git.Repository")
|
||||
def test_create_crew_without_remote_uses_git_file_list_when_commit_fails(
|
||||
self, mock_repository, mock_fetch_env, mock_create_project_zip
|
||||
):
|
||||
mock_fetch_env.return_value = {"ENV_VAR": "value"}
|
||||
repository = mock_repository.return_value
|
||||
repository.origin_url.return_value = None
|
||||
repository.create_initial_commit_if_needed.side_effect = RuntimeError(
|
||||
"commit failed"
|
||||
)
|
||||
mock_create_project_zip.return_value = Path("/tmp/test_project.zip")
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 201
|
||||
mock_response.is_success = True
|
||||
mock_response.json.return_value = {"uuid": "zip-uuid", "status": "created"}
|
||||
self.mock_client.create_crew_from_zip.return_value = mock_response
|
||||
|
||||
with patch("sys.stdout", new=StringIO()) as fake_out:
|
||||
self.deploy_command.create_crew(confirm=True, skip_validate=True)
|
||||
output = fake_out.getvalue()
|
||||
|
||||
self.assertIn("Continuing with ZIP deployment using Git", output)
|
||||
self.assertIn("file listing", output)
|
||||
mock_create_project_zip.assert_called_once_with(
|
||||
"test_project", repository=repository
|
||||
)
|
||||
self.mock_client.create_crew_from_zip.assert_called_once_with(
|
||||
Path("/tmp/test_project.zip"),
|
||||
name="test_project",
|
||||
env={"ENV_VAR": "value"},
|
||||
)
|
||||
self.mock_client.create_crew.assert_not_called()
|
||||
|
||||
def test_list_crews(self):
|
||||
|
||||
Reference in New Issue
Block a user