mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-01-09 08:08:32 +00:00
Change Tool Repository authentication scope (#1378)
This commit adds a new command for adding custom PyPI indexes credentials to the project. This was changed because credentials are now user-scoped instead of organization.
This commit is contained in:
@@ -264,6 +264,7 @@ def deploy_remove(uuid: Optional[str]):
|
|||||||
@click.argument("handle")
|
@click.argument("handle")
|
||||||
def tool_install(handle: str):
|
def tool_install(handle: str):
|
||||||
tool_cmd = ToolCommand()
|
tool_cmd = ToolCommand()
|
||||||
|
tool_cmd.login()
|
||||||
tool_cmd.install(handle)
|
tool_cmd.install(handle)
|
||||||
|
|
||||||
|
|
||||||
@@ -272,6 +273,7 @@ def tool_install(handle: str):
|
|||||||
@click.option("--private", "is_public", flag_value=False)
|
@click.option("--private", "is_public", flag_value=False)
|
||||||
def tool_publish(is_public: bool):
|
def tool_publish(is_public: bool):
|
||||||
tool_cmd = ToolCommand()
|
tool_cmd = ToolCommand()
|
||||||
|
tool_cmd.login()
|
||||||
tool_cmd.publish(is_public)
|
tool_cmd.publish(is_public)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from typing import Dict, Any
|
import requests
|
||||||
|
from requests.exceptions import JSONDecodeError
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
from crewai.cli.plus_api import PlusAPI
|
from crewai.cli.plus_api import PlusAPI
|
||||||
from crewai.cli.utils import get_auth_token
|
from crewai.cli.utils import get_auth_token
|
||||||
@@ -27,14 +28,44 @@ class PlusAPIMixin:
|
|||||||
console.print("Run 'crewai signup' to sign up/login.", style="bold green")
|
console.print("Run 'crewai signup' to sign up/login.", style="bold green")
|
||||||
raise SystemExit
|
raise SystemExit
|
||||||
|
|
||||||
def _handle_plus_api_error(self, json_response: Dict[str, Any]) -> None:
|
def _validate_response(self, response: requests.Response) -> None:
|
||||||
"""
|
"""
|
||||||
Handle and display error messages from API responses.
|
Handle and display error messages from API responses.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
json_response (Dict[str, Any]): The JSON response containing error information.
|
response (requests.Response): The response from the Plus API
|
||||||
"""
|
"""
|
||||||
error = json_response.get("error", "Unknown error")
|
try:
|
||||||
message = json_response.get("message", "No message provided")
|
json_response = response.json()
|
||||||
console.print(f"Error: {error}", style="bold red")
|
except (JSONDecodeError, ValueError):
|
||||||
console.print(f"Message: {message}", style="bold red")
|
console.print(
|
||||||
|
"Failed to parse response from Enterprise API failed. Details:",
|
||||||
|
style="bold red",
|
||||||
|
)
|
||||||
|
console.print(f"Status Code: {response.status_code}")
|
||||||
|
console.print(f"Response:\n{response.content}")
|
||||||
|
raise SystemExit
|
||||||
|
|
||||||
|
if response.status_code == 422:
|
||||||
|
console.print(
|
||||||
|
"Failed to complete operation. Please fix the following errors:",
|
||||||
|
style="bold red",
|
||||||
|
)
|
||||||
|
for field, messages in json_response.items():
|
||||||
|
for message in messages:
|
||||||
|
console.print(
|
||||||
|
f"* [bold red]{field.capitalize()}[/bold red] {message}"
|
||||||
|
)
|
||||||
|
raise SystemExit
|
||||||
|
|
||||||
|
if not response.ok:
|
||||||
|
console.print(
|
||||||
|
"Request to Enterprise API failed. Details:", style="bold red"
|
||||||
|
)
|
||||||
|
details = (
|
||||||
|
json_response.get("error")
|
||||||
|
or json_response.get("message")
|
||||||
|
or response.content
|
||||||
|
)
|
||||||
|
console.print(f"{details}")
|
||||||
|
raise SystemExit
|
||||||
|
|||||||
@@ -79,11 +79,8 @@ class DeployCommand(BaseCommand, PlusAPIMixin):
|
|||||||
self._standard_no_param_error_message()
|
self._standard_no_param_error_message()
|
||||||
return
|
return
|
||||||
|
|
||||||
json_response = response.json()
|
self._validate_response(response)
|
||||||
if response.status_code == 200:
|
self._display_deployment_info(response.json())
|
||||||
self._display_deployment_info(json_response)
|
|
||||||
else:
|
|
||||||
self._handle_plus_api_error(json_response)
|
|
||||||
|
|
||||||
def create_crew(self, confirm: bool = False) -> None:
|
def create_crew(self, confirm: bool = False) -> None:
|
||||||
"""
|
"""
|
||||||
@@ -106,12 +103,10 @@ class DeployCommand(BaseCommand, PlusAPIMixin):
|
|||||||
|
|
||||||
self._confirm_input(env_vars, remote_repo_url, confirm)
|
self._confirm_input(env_vars, remote_repo_url, confirm)
|
||||||
payload = self._create_payload(env_vars, remote_repo_url)
|
payload = self._create_payload(env_vars, remote_repo_url)
|
||||||
|
|
||||||
response = self.plus_api_client.create_crew(payload)
|
response = self.plus_api_client.create_crew(payload)
|
||||||
if response.status_code == 201:
|
|
||||||
self._display_creation_success(response.json())
|
self._validate_response(response)
|
||||||
else:
|
self._display_creation_success(response.json())
|
||||||
self._handle_plus_api_error(response.json())
|
|
||||||
|
|
||||||
def _confirm_input(
|
def _confirm_input(
|
||||||
self, env_vars: Dict[str, str], remote_repo_url: str, confirm: bool
|
self, env_vars: Dict[str, str], remote_repo_url: str, confirm: bool
|
||||||
@@ -218,11 +213,8 @@ class DeployCommand(BaseCommand, PlusAPIMixin):
|
|||||||
self._standard_no_param_error_message()
|
self._standard_no_param_error_message()
|
||||||
return
|
return
|
||||||
|
|
||||||
json_response = response.json()
|
self._validate_response(response)
|
||||||
if response.status_code == 200:
|
self._display_crew_status(response.json())
|
||||||
self._display_crew_status(json_response)
|
|
||||||
else:
|
|
||||||
self._handle_plus_api_error(json_response)
|
|
||||||
|
|
||||||
def _display_crew_status(self, status_data: Dict[str, str]) -> None:
|
def _display_crew_status(self, status_data: Dict[str, str]) -> None:
|
||||||
"""
|
"""
|
||||||
@@ -253,10 +245,8 @@ class DeployCommand(BaseCommand, PlusAPIMixin):
|
|||||||
self._standard_no_param_error_message()
|
self._standard_no_param_error_message()
|
||||||
return
|
return
|
||||||
|
|
||||||
if response.status_code == 200:
|
self._validate_response(response)
|
||||||
self._display_logs(response.json())
|
self._display_logs(response.json())
|
||||||
else:
|
|
||||||
self._handle_plus_api_error(response.json())
|
|
||||||
|
|
||||||
def remove_crew(self, uuid: Optional[str]) -> None:
|
def remove_crew(self, uuid: Optional[str]) -> None:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -27,6 +27,9 @@ class PlusAPI:
|
|||||||
url = urljoin(self.base_url, endpoint)
|
url = urljoin(self.base_url, endpoint)
|
||||||
return requests.request(method, url, headers=self.headers, **kwargs)
|
return requests.request(method, url, headers=self.headers, **kwargs)
|
||||||
|
|
||||||
|
def login_to_tool_repository(self):
|
||||||
|
return self._make_request("POST", f"{self.TOOLS_RESOURCE}/login")
|
||||||
|
|
||||||
def get_tool(self, handle: str):
|
def get_tool(self, handle: str):
|
||||||
return self._make_request("GET", f"{self.TOOLS_RESOURCE}/{handle}")
|
return self._make_request("GET", f"{self.TOOLS_RESOURCE}/{handle}")
|
||||||
|
|
||||||
|
|||||||
@@ -64,23 +64,8 @@ class ToolCommand(BaseCommand, PlusAPIMixin):
|
|||||||
description=project_description,
|
description=project_description,
|
||||||
encoded_file=f"data:application/x-gzip;base64,{encoded_tarball}",
|
encoded_file=f"data:application/x-gzip;base64,{encoded_tarball}",
|
||||||
)
|
)
|
||||||
if publish_response.status_code == 422:
|
|
||||||
console.print(
|
|
||||||
"[bold red]Failed to publish tool. Please fix the following errors:[/bold red]"
|
|
||||||
)
|
|
||||||
for field, messages in publish_response.json().items():
|
|
||||||
for message in messages:
|
|
||||||
console.print(
|
|
||||||
f"* [bold red]{field.capitalize()}[/bold red] {message}"
|
|
||||||
)
|
|
||||||
|
|
||||||
raise SystemExit
|
self._validate_response(publish_response)
|
||||||
elif publish_response.status_code != 200:
|
|
||||||
self._handle_plus_api_error(publish_response.json())
|
|
||||||
console.print(
|
|
||||||
"Failed to publish tool. Please try again later.", style="bold red"
|
|
||||||
)
|
|
||||||
raise SystemExit
|
|
||||||
|
|
||||||
published_handle = publish_response.json()["handle"]
|
published_handle = publish_response.json()["handle"]
|
||||||
console.print(
|
console.print(
|
||||||
@@ -103,15 +88,32 @@ class ToolCommand(BaseCommand, PlusAPIMixin):
|
|||||||
)
|
)
|
||||||
raise SystemExit
|
raise SystemExit
|
||||||
|
|
||||||
self._add_repository_to_poetry(get_response.json())
|
|
||||||
self._add_package(get_response.json())
|
self._add_package(get_response.json())
|
||||||
|
|
||||||
console.print(f"Succesfully installed {handle}", style="bold green")
|
console.print(f"Succesfully installed {handle}", style="bold green")
|
||||||
|
|
||||||
def _add_repository_to_poetry(self, tool_details):
|
def login(self):
|
||||||
repository_handle = f"crewai-{tool_details['repository']['handle']}"
|
login_response = self.plus_api_client.login_to_tool_repository()
|
||||||
repository_url = tool_details["repository"]["url"]
|
|
||||||
repository_credentials = tool_details["repository"]["credentials"]
|
if login_response.status_code != 200:
|
||||||
|
console.print(
|
||||||
|
"Failed to authenticate to the tool repository. Make sure you have the access to tools.",
|
||||||
|
style="bold red",
|
||||||
|
)
|
||||||
|
raise SystemExit
|
||||||
|
|
||||||
|
login_response_json = login_response.json()
|
||||||
|
for repository in login_response_json["repositories"]:
|
||||||
|
self._add_repository_to_poetry(
|
||||||
|
repository, login_response_json["credential"]
|
||||||
|
)
|
||||||
|
|
||||||
|
console.print(
|
||||||
|
"Succesfully authenticated to the tool repository.", style="bold green"
|
||||||
|
)
|
||||||
|
|
||||||
|
def _add_repository_to_poetry(self, repository, credentials):
|
||||||
|
repository_handle = f"crewai-{repository['handle']}"
|
||||||
|
|
||||||
add_repository_command = [
|
add_repository_command = [
|
||||||
"poetry",
|
"poetry",
|
||||||
@@ -119,7 +121,7 @@ class ToolCommand(BaseCommand, PlusAPIMixin):
|
|||||||
"add",
|
"add",
|
||||||
"--priority=explicit",
|
"--priority=explicit",
|
||||||
repository_handle,
|
repository_handle,
|
||||||
repository_url,
|
repository["url"],
|
||||||
]
|
]
|
||||||
add_repository_result = subprocess.run(
|
add_repository_result = subprocess.run(
|
||||||
add_repository_command, text=True, check=True
|
add_repository_command, text=True, check=True
|
||||||
@@ -133,8 +135,8 @@ class ToolCommand(BaseCommand, PlusAPIMixin):
|
|||||||
"poetry",
|
"poetry",
|
||||||
"config",
|
"config",
|
||||||
f"http-basic.{repository_handle}",
|
f"http-basic.{repository_handle}",
|
||||||
repository_credentials,
|
credentials["username"],
|
||||||
'""',
|
credentials["password"],
|
||||||
]
|
]
|
||||||
add_repository_credentials_result = subprocess.run(
|
add_repository_credentials_result = subprocess.run(
|
||||||
add_repository_credentials_command,
|
add_repository_credentials_command,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import click
|
|||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
import importlib.metadata
|
||||||
|
|
||||||
from crewai.cli.authentication.utils import TokenManager
|
from crewai.cli.authentication.utils import TokenManager
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
@@ -162,29 +163,9 @@ def _get_nested_value(data: Dict[str, Any], keys: List[str]) -> Any:
|
|||||||
return reduce(dict.__getitem__, keys, data)
|
return reduce(dict.__getitem__, keys, data)
|
||||||
|
|
||||||
|
|
||||||
def get_crewai_version(poetry_lock_path: str = "poetry.lock") -> str:
|
def get_crewai_version() -> str:
|
||||||
"""Get the version number of crewai from the poetry.lock file."""
|
"""Get the version number of CrewAI running the CLI"""
|
||||||
try:
|
return importlib.metadata.version("crewai")
|
||||||
with open(poetry_lock_path, "r") as f:
|
|
||||||
lock_content = f.read()
|
|
||||||
|
|
||||||
match = re.search(
|
|
||||||
r'\[\[package\]\]\s*name\s*=\s*"crewai"\s*version\s*=\s*"([^"]+)"',
|
|
||||||
lock_content,
|
|
||||||
re.DOTALL,
|
|
||||||
)
|
|
||||||
if match:
|
|
||||||
return match.group(1)
|
|
||||||
else:
|
|
||||||
print("crewai package not found in poetry.lock")
|
|
||||||
return "no-version-found"
|
|
||||||
|
|
||||||
except FileNotFoundError:
|
|
||||||
print(f"Error: {poetry_lock_path} not found.")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error reading the poetry.lock file: {e}")
|
|
||||||
|
|
||||||
return "no-version-found"
|
|
||||||
|
|
||||||
|
|
||||||
def fetch_and_json_env_file(env_file_path: str = ".env") -> dict:
|
def fetch_and_json_env_file(env_file_path: str = ".env") -> dict:
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
import unittest
|
import pytest
|
||||||
from io import StringIO
|
import requests
|
||||||
from unittest.mock import MagicMock, patch
|
|
||||||
import sys
|
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.deploy.main import DeployCommand
|
||||||
from crewai.cli.utils import parse_toml
|
from crewai.cli.utils import parse_toml
|
||||||
@@ -33,13 +37,65 @@ class TestDeployCommand(unittest.TestCase):
|
|||||||
with self.assertRaises(SystemExit):
|
with self.assertRaises(SystemExit):
|
||||||
DeployCommand()
|
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:
|
with patch("sys.stdout", new=StringIO()) as fake_out:
|
||||||
self.deploy_command._handle_plus_api_error(
|
self.deploy_command._validate_response(mock_response)
|
||||||
{"error": "Test error", "message": "Test message"}
|
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())
|
assert "Status Code: 500" in output
|
||||||
self.assertIn("Message: Test message", fake_out.getvalue())
|
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):
|
def test_standard_no_param_error_message(self):
|
||||||
with patch("sys.stdout", new=StringIO()) as fake_out:
|
with patch("sys.stdout", new=StringIO()) as fake_out:
|
||||||
@@ -207,30 +263,7 @@ class TestDeployCommand(unittest.TestCase):
|
|||||||
project_name = get_project_name()
|
project_name = get_project_name()
|
||||||
self.assertEqual(project_name, "test_project")
|
self.assertEqual(project_name, "test_project")
|
||||||
|
|
||||||
@patch(
|
def test_get_crewai_version(self):
|
||||||
"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):
|
|
||||||
from crewai.cli.utils import get_crewai_version
|
from crewai.cli.utils import get_crewai_version
|
||||||
|
|
||||||
version = get_crewai_version()
|
assert isinstance(get_crewai_version(), str)
|
||||||
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())
|
|
||||||
|
|||||||
@@ -11,15 +11,22 @@ class TestPlusAPI(unittest.TestCase):
|
|||||||
|
|
||||||
def test_init(self):
|
def test_init(self):
|
||||||
self.assertEqual(self.api.api_key, self.api_key)
|
self.assertEqual(self.api.api_key, self.api_key)
|
||||||
self.assertEqual(
|
self.assertEqual(self.api.headers["Authorization"], f"Bearer {self.api_key}")
|
||||||
self.api.headers,
|
self.assertEqual(self.api.headers["Content-Type"], "application/json")
|
||||||
{
|
self.assertTrue("CrewAI-CLI/" in self.api.headers["User-Agent"])
|
||||||
"Authorization": f"Bearer {self.api_key}",
|
self.assertTrue(self.api.headers["X-Crewai-Version"])
|
||||||
"Content-Type": "application/json",
|
|
||||||
"User-Agent": "CrewAI-CLI/no-version-found",
|
@patch("crewai.cli.plus_api.PlusAPI._make_request")
|
||||||
"X-Crewai-Version": "no-version-found",
|
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")
|
@patch("crewai.cli.plus_api.PlusAPI._make_request")
|
||||||
def test_get_tool(self, mock_make_request):
|
def test_get_tool(self, mock_make_request):
|
||||||
|
|||||||
@@ -13,11 +13,7 @@ class TestToolCommand(unittest.TestCase):
|
|||||||
mock_get_response.status_code = 200
|
mock_get_response.status_code = 200
|
||||||
mock_get_response.json.return_value = {
|
mock_get_response.json.return_value = {
|
||||||
"handle": "sample-tool",
|
"handle": "sample-tool",
|
||||||
"repository": {
|
"repository": {"handle": "sample-repo", "url": "https://example.com/repo"},
|
||||||
"handle": "sample-repo",
|
|
||||||
"url": "https://example.com/repo",
|
|
||||||
"credentials": "my_very_secret",
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
mock_get.return_value = mock_get_response
|
mock_get.return_value = mock_get_response
|
||||||
mock_subprocess_run.return_value = MagicMock(stderr=None)
|
mock_subprocess_run.return_value = MagicMock(stderr=None)
|
||||||
@@ -29,30 +25,6 @@ class TestToolCommand(unittest.TestCase):
|
|||||||
output = fake_out.getvalue()
|
output = fake_out.getvalue()
|
||||||
|
|
||||||
mock_get.assert_called_once_with("sample-tool")
|
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(
|
mock_subprocess_run.assert_any_call(
|
||||||
["poetry", "add", "--source", "crewai-sample-repo", "sample-tool"],
|
["poetry", "add", "--source", "crewai-sample-repo", "sample-tool"],
|
||||||
capture_output=False,
|
capture_output=False,
|
||||||
@@ -182,7 +154,7 @@ class TestToolCommand(unittest.TestCase):
|
|||||||
output = fake_out.getvalue()
|
output = fake_out.getvalue()
|
||||||
|
|
||||||
mock_publish.assert_called_once()
|
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)
|
self.assertIn("Name is already taken", output)
|
||||||
|
|
||||||
@patch("crewai.cli.tools.main.get_project_name", return_value="sample-tool")
|
@patch("crewai.cli.tools.main.get_project_name", return_value="sample-tool")
|
||||||
@@ -210,9 +182,11 @@ class TestToolCommand(unittest.TestCase):
|
|||||||
mock_get_project_version,
|
mock_get_project_version,
|
||||||
mock_get_project_name,
|
mock_get_project_name,
|
||||||
):
|
):
|
||||||
mock_publish_response = MagicMock()
|
mock_response = MagicMock()
|
||||||
mock_publish_response.status_code = 500
|
mock_response.status_code = 500
|
||||||
mock_publish.return_value = mock_publish_response
|
mock_response.json.return_value = {"error": "Internal Server Error"}
|
||||||
|
mock_response.ok = False
|
||||||
|
mock_publish.return_value = mock_response
|
||||||
|
|
||||||
tool_command = ToolCommand()
|
tool_command = ToolCommand()
|
||||||
|
|
||||||
@@ -222,8 +196,55 @@ class TestToolCommand(unittest.TestCase):
|
|||||||
output = fake_out.getvalue()
|
output = fake_out.getvalue()
|
||||||
|
|
||||||
mock_publish.assert_called_once()
|
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__":
|
mock_subprocess_run.return_value = MagicMock(stderr=None)
|
||||||
unittest.main()
|
|
||||||
|
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