mirror of
https://github.com/crewAIInc/crewAI.git
synced 2025-12-16 04:18:35 +00:00
feat: improve docs and logging for Multi-Org actions in CLI (#2980)
* docs: add organization management in our CLI docs * feat: improve user feedback when user is not authenticated * feat: improve logging about current organization while publishing/install a Tool * feat: improve logging when Agent repository is not found during fetch * fix linter offences * test: fix auth token error
This commit is contained in:
@@ -200,6 +200,37 @@ Deploy the crew or flow to [CrewAI Enterprise](https://app.crewai.com).
|
|||||||
```
|
```
|
||||||
- Reads your local project configuration.
|
- Reads your local project configuration.
|
||||||
- Prompts you to confirm the environment variables (like `OPENAI_API_KEY`, `SERPER_API_KEY`) found locally. These will be securely stored with the deployment on the Enterprise platform. Ensure your sensitive keys are correctly configured locally (e.g., in a `.env` file) before running this.
|
- Prompts you to confirm the environment variables (like `OPENAI_API_KEY`, `SERPER_API_KEY`) found locally. These will be securely stored with the deployment on the Enterprise platform. Ensure your sensitive keys are correctly configured locally (e.g., in a `.env` file) before running this.
|
||||||
|
|
||||||
|
### 11. Organization Management
|
||||||
|
|
||||||
|
Manage your CrewAI Enterprise organizations.
|
||||||
|
|
||||||
|
```shell Terminal
|
||||||
|
crewai org [COMMAND] [OPTIONS]
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Commands:
|
||||||
|
|
||||||
|
- `list`: List all organizations you belong to
|
||||||
|
```shell Terminal
|
||||||
|
crewai org list
|
||||||
|
```
|
||||||
|
|
||||||
|
- `current`: Display your currently active organization
|
||||||
|
```shell Terminal
|
||||||
|
crewai org current
|
||||||
|
```
|
||||||
|
|
||||||
|
- `switch`: Switch to a specific organization
|
||||||
|
```shell Terminal
|
||||||
|
crewai org switch <organization_id>
|
||||||
|
```
|
||||||
|
|
||||||
|
<Note>
|
||||||
|
You must be authenticated to CrewAI Enterprise to use these organization management commands.
|
||||||
|
</Note>
|
||||||
|
|
||||||
|
- **Create a deployment** (continued):
|
||||||
- Links the deployment to the corresponding remote GitHub repository (it usually detects this automatically).
|
- Links the deployment to the corresponding remote GitHub repository (it usually detects this automatically).
|
||||||
|
|
||||||
- **Deploy the Crew**: Once you are authenticated, you can deploy your crew or flow to CrewAI Enterprise.
|
- **Deploy the Crew**: Once you are authenticated, you can deploy your crew or flow to CrewAI Enterprise.
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
from rich.table import Table
|
from rich.table import Table
|
||||||
|
|
||||||
|
from requests import HTTPError
|
||||||
from crewai.cli.command import BaseCommand, PlusAPIMixin
|
from crewai.cli.command import BaseCommand, PlusAPIMixin
|
||||||
from crewai.cli.config import Settings
|
from crewai.cli.config import Settings
|
||||||
|
|
||||||
@@ -16,7 +17,7 @@ class OrganizationCommand(BaseCommand, PlusAPIMixin):
|
|||||||
response = self.plus_api_client.get_organizations()
|
response = self.plus_api_client.get_organizations()
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
orgs = response.json()
|
orgs = response.json()
|
||||||
|
|
||||||
if not orgs:
|
if not orgs:
|
||||||
console.print("You don't belong to any organizations yet.", style="yellow")
|
console.print("You don't belong to any organizations yet.", style="yellow")
|
||||||
return
|
return
|
||||||
@@ -26,8 +27,14 @@ class OrganizationCommand(BaseCommand, PlusAPIMixin):
|
|||||||
table.add_column("ID", style="green")
|
table.add_column("ID", style="green")
|
||||||
for org in orgs:
|
for org in orgs:
|
||||||
table.add_row(org["name"], org["uuid"])
|
table.add_row(org["name"], org["uuid"])
|
||||||
|
|
||||||
console.print(table)
|
console.print(table)
|
||||||
|
except HTTPError as e:
|
||||||
|
if e.response.status_code == 401:
|
||||||
|
console.print("You are not logged in to any organization. Use 'crewai login' to login.", style="bold red")
|
||||||
|
return
|
||||||
|
console.print(f"Failed to retrieve organization list: {str(e)}", style="bold red")
|
||||||
|
raise SystemExit(1)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
console.print(f"Failed to retrieve organization list: {str(e)}", style="bold red")
|
console.print(f"Failed to retrieve organization list: {str(e)}", style="bold red")
|
||||||
raise SystemExit(1)
|
raise SystemExit(1)
|
||||||
@@ -37,18 +44,24 @@ class OrganizationCommand(BaseCommand, PlusAPIMixin):
|
|||||||
response = self.plus_api_client.get_organizations()
|
response = self.plus_api_client.get_organizations()
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
orgs = response.json()
|
orgs = response.json()
|
||||||
|
|
||||||
org = next((o for o in orgs if o["uuid"] == org_id), None)
|
org = next((o for o in orgs if o["uuid"] == org_id), None)
|
||||||
if not org:
|
if not org:
|
||||||
console.print(f"Organization with id '{org_id}' not found.", style="bold red")
|
console.print(f"Organization with id '{org_id}' not found.", style="bold red")
|
||||||
return
|
return
|
||||||
|
|
||||||
settings = Settings()
|
settings = Settings()
|
||||||
settings.org_name = org["name"]
|
settings.org_name = org["name"]
|
||||||
settings.org_uuid = org["uuid"]
|
settings.org_uuid = org["uuid"]
|
||||||
settings.dump()
|
settings.dump()
|
||||||
|
|
||||||
console.print(f"Successfully switched to {org['name']} ({org['uuid']})", style="bold green")
|
console.print(f"Successfully switched to {org['name']} ({org['uuid']})", style="bold green")
|
||||||
|
except HTTPError as e:
|
||||||
|
if e.response.status_code == 401:
|
||||||
|
console.print("You are not logged in to any organization. Use 'crewai login' to login.", style="bold red")
|
||||||
|
return
|
||||||
|
console.print(f"Failed to retrieve organization list: {str(e)}", style="bold red")
|
||||||
|
raise SystemExit(1)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
console.print(f"Failed to switch organization: {str(e)}", style="bold red")
|
console.print(f"Failed to switch organization: {str(e)}", style="bold red")
|
||||||
raise SystemExit(1)
|
raise SystemExit(1)
|
||||||
|
|||||||
@@ -91,6 +91,7 @@ class ToolCommand(BaseCommand, PlusAPIMixin):
|
|||||||
console.print(
|
console.print(
|
||||||
f"[green]Found these tools to publish: {', '.join([e['name'] for e in available_exports])}[/green]"
|
f"[green]Found these tools to publish: {', '.join([e['name'] for e in available_exports])}[/green]"
|
||||||
)
|
)
|
||||||
|
self._print_current_organization()
|
||||||
|
|
||||||
with tempfile.TemporaryDirectory() as temp_build_dir:
|
with tempfile.TemporaryDirectory() as temp_build_dir:
|
||||||
subprocess.run(
|
subprocess.run(
|
||||||
@@ -136,6 +137,7 @@ class ToolCommand(BaseCommand, PlusAPIMixin):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def install(self, handle: str):
|
def install(self, handle: str):
|
||||||
|
self._print_current_organization()
|
||||||
get_response = self.plus_api_client.get_tool(handle)
|
get_response = self.plus_api_client.get_tool(handle)
|
||||||
|
|
||||||
if get_response.status_code == 404:
|
if get_response.status_code == 404:
|
||||||
@@ -182,7 +184,7 @@ class ToolCommand(BaseCommand, PlusAPIMixin):
|
|||||||
settings.dump()
|
settings.dump()
|
||||||
|
|
||||||
console.print(
|
console.print(
|
||||||
"Successfully authenticated to the tool repository.", style="bold green"
|
f"Successfully authenticated to the tool repository as {settings.org_name} ({settings.org_uuid}).", style="bold green"
|
||||||
)
|
)
|
||||||
|
|
||||||
def _add_package(self, tool_details: dict[str, Any]):
|
def _add_package(self, tool_details: dict[str, Any]):
|
||||||
@@ -240,3 +242,10 @@ class ToolCommand(BaseCommand, PlusAPIMixin):
|
|||||||
)
|
)
|
||||||
|
|
||||||
return env
|
return env
|
||||||
|
|
||||||
|
def _print_current_organization(self):
|
||||||
|
settings = Settings()
|
||||||
|
if settings.org_uuid:
|
||||||
|
console.print(f"Current organization: {settings.org_name} ({settings.org_uuid})", style="bold blue")
|
||||||
|
else:
|
||||||
|
console.print("No organization currently set. We recommend setting one before using: `crewai org switch <org_id>` command.", style="yellow")
|
||||||
|
|||||||
@@ -20,7 +20,10 @@ from crewai.utilities.errors import AgentRepositoryError
|
|||||||
from crewai.utilities.exceptions.context_window_exceeding_exception import (
|
from crewai.utilities.exceptions.context_window_exceeding_exception import (
|
||||||
LLMContextLengthExceededException,
|
LLMContextLengthExceededException,
|
||||||
)
|
)
|
||||||
|
from rich.console import Console
|
||||||
|
from crewai.cli.config import Settings
|
||||||
|
|
||||||
|
console = Console()
|
||||||
|
|
||||||
def parse_tools(tools: List[BaseTool]) -> List[CrewStructuredTool]:
|
def parse_tools(tools: List[BaseTool]) -> List[CrewStructuredTool]:
|
||||||
"""Parse tools to be used for the task."""
|
"""Parse tools to be used for the task."""
|
||||||
@@ -435,6 +438,13 @@ def show_agent_logs(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _print_current_organization():
|
||||||
|
settings = Settings()
|
||||||
|
if settings.org_uuid:
|
||||||
|
console.print(f"Fetching agent from organization: {settings.org_name} ({settings.org_uuid})", style="bold blue")
|
||||||
|
else:
|
||||||
|
console.print("No organization currently set. We recommend setting one before using: `crewai org switch <org_id>` command.", style="yellow")
|
||||||
|
|
||||||
def load_agent_from_repository(from_repository: str) -> Dict[str, Any]:
|
def load_agent_from_repository(from_repository: str) -> Dict[str, Any]:
|
||||||
attributes: Dict[str, Any] = {}
|
attributes: Dict[str, Any] = {}
|
||||||
if from_repository:
|
if from_repository:
|
||||||
@@ -444,15 +454,18 @@ def load_agent_from_repository(from_repository: str) -> Dict[str, Any]:
|
|||||||
from crewai.cli.plus_api import PlusAPI
|
from crewai.cli.plus_api import PlusAPI
|
||||||
|
|
||||||
client = PlusAPI(api_key=get_auth_token())
|
client = PlusAPI(api_key=get_auth_token())
|
||||||
|
_print_current_organization()
|
||||||
response = client.get_agent(from_repository)
|
response = client.get_agent(from_repository)
|
||||||
if response.status_code == 404:
|
if response.status_code == 404:
|
||||||
raise AgentRepositoryError(
|
raise AgentRepositoryError(
|
||||||
f"Agent {from_repository} does not exist, make sure the name is correct or the agent is available on your organization"
|
f"Agent {from_repository} does not exist, make sure the name is correct or the agent is available on your organization."
|
||||||
|
f"\nIf you are using the wrong organization, switch to the correct one using `crewai org switch <org_id>` command.",
|
||||||
)
|
)
|
||||||
|
|
||||||
if response.status_code != 200:
|
if response.status_code != 200:
|
||||||
raise AgentRepositoryError(
|
raise AgentRepositoryError(
|
||||||
f"Agent {from_repository} could not be loaded: {response.text}"
|
f"Agent {from_repository} could not be loaded: {response.text}"
|
||||||
|
f"\nIf you are using the wrong organization, switch to the correct one using `crewai org switch <org_id>` command.",
|
||||||
)
|
)
|
||||||
|
|
||||||
agent = response.json()
|
agent = response.json()
|
||||||
|
|||||||
@@ -2126,3 +2126,60 @@ def test_agent_from_repository_agent_not_found(mock_get_agent, mock_get_auth_tok
|
|||||||
match="Agent test_agent does not exist, make sure the name is correct or the agent is available on your organization",
|
match="Agent test_agent does not exist, make sure the name is correct or the agent is available on your organization",
|
||||||
):
|
):
|
||||||
Agent(from_repository="test_agent")
|
Agent(from_repository="test_agent")
|
||||||
|
|
||||||
|
|
||||||
|
@patch("crewai.cli.plus_api.PlusAPI.get_agent")
|
||||||
|
@patch("crewai.utilities.agent_utils.Settings")
|
||||||
|
@patch("crewai.utilities.agent_utils.console")
|
||||||
|
def test_agent_from_repository_displays_org_info(mock_console, mock_settings, mock_get_agent, mock_get_auth_token):
|
||||||
|
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
|
||||||
|
|
||||||
|
mock_get_response = MagicMock()
|
||||||
|
mock_get_response.status_code = 200
|
||||||
|
mock_get_response.json.return_value = {
|
||||||
|
"role": "test role",
|
||||||
|
"goal": "test goal",
|
||||||
|
"backstory": "test backstory",
|
||||||
|
"tools": []
|
||||||
|
}
|
||||||
|
mock_get_agent.return_value = mock_get_response
|
||||||
|
|
||||||
|
agent = Agent(from_repository="test_agent")
|
||||||
|
|
||||||
|
mock_console.print.assert_any_call(
|
||||||
|
"Fetching agent from organization: Test Organization (test-org-uuid)",
|
||||||
|
style="bold blue"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert agent.role == "test role"
|
||||||
|
assert agent.goal == "test goal"
|
||||||
|
assert agent.backstory == "test backstory"
|
||||||
|
|
||||||
|
|
||||||
|
@patch("crewai.cli.plus_api.PlusAPI.get_agent")
|
||||||
|
@patch("crewai.utilities.agent_utils.Settings")
|
||||||
|
@patch("crewai.utilities.agent_utils.console")
|
||||||
|
def test_agent_from_repository_without_org_set(mock_console, mock_settings, mock_get_agent, mock_get_auth_token):
|
||||||
|
mock_settings_instance = MagicMock()
|
||||||
|
mock_settings_instance.org_uuid = None
|
||||||
|
mock_settings_instance.org_name = None
|
||||||
|
mock_settings.return_value = mock_settings_instance
|
||||||
|
|
||||||
|
mock_get_response = MagicMock()
|
||||||
|
mock_get_response.status_code = 401
|
||||||
|
mock_get_response.text = "Unauthorized access"
|
||||||
|
mock_get_agent.return_value = mock_get_response
|
||||||
|
|
||||||
|
with pytest.raises(
|
||||||
|
AgentRepositoryError,
|
||||||
|
match="Agent test_agent could not be loaded: Unauthorized access"
|
||||||
|
):
|
||||||
|
Agent(from_repository="test_agent")
|
||||||
|
|
||||||
|
mock_console.print.assert_any_call(
|
||||||
|
"No organization currently set. We recommend setting one before using: `crewai org switch <org_id>` command.",
|
||||||
|
style="yellow"
|
||||||
|
)
|
||||||
|
|||||||
@@ -33,9 +33,9 @@ def mock_settings():
|
|||||||
def test_org_list_command(mock_org_command_class, runner):
|
def test_org_list_command(mock_org_command_class, runner):
|
||||||
mock_org_instance = MagicMock()
|
mock_org_instance = MagicMock()
|
||||||
mock_org_command_class.return_value = mock_org_instance
|
mock_org_command_class.return_value = mock_org_instance
|
||||||
|
|
||||||
result = runner.invoke(list)
|
result = runner.invoke(list)
|
||||||
|
|
||||||
assert result.exit_code == 0
|
assert result.exit_code == 0
|
||||||
mock_org_command_class.assert_called_once()
|
mock_org_command_class.assert_called_once()
|
||||||
mock_org_instance.list.assert_called_once()
|
mock_org_instance.list.assert_called_once()
|
||||||
@@ -45,9 +45,9 @@ def test_org_list_command(mock_org_command_class, runner):
|
|||||||
def test_org_switch_command(mock_org_command_class, runner):
|
def test_org_switch_command(mock_org_command_class, runner):
|
||||||
mock_org_instance = MagicMock()
|
mock_org_instance = MagicMock()
|
||||||
mock_org_command_class.return_value = mock_org_instance
|
mock_org_command_class.return_value = mock_org_instance
|
||||||
|
|
||||||
result = runner.invoke(switch, ['test-id'])
|
result = runner.invoke(switch, ['test-id'])
|
||||||
|
|
||||||
assert result.exit_code == 0
|
assert result.exit_code == 0
|
||||||
mock_org_command_class.assert_called_once()
|
mock_org_command_class.assert_called_once()
|
||||||
mock_org_instance.switch.assert_called_once_with('test-id')
|
mock_org_instance.switch.assert_called_once_with('test-id')
|
||||||
@@ -57,9 +57,9 @@ def test_org_switch_command(mock_org_command_class, runner):
|
|||||||
def test_org_current_command(mock_org_command_class, runner):
|
def test_org_current_command(mock_org_command_class, runner):
|
||||||
mock_org_instance = MagicMock()
|
mock_org_instance = MagicMock()
|
||||||
mock_org_command_class.return_value = mock_org_instance
|
mock_org_command_class.return_value = mock_org_instance
|
||||||
|
|
||||||
result = runner.invoke(current)
|
result = runner.invoke(current)
|
||||||
|
|
||||||
assert result.exit_code == 0
|
assert result.exit_code == 0
|
||||||
mock_org_command_class.assert_called_once()
|
mock_org_command_class.assert_called_once()
|
||||||
mock_org_instance.current.assert_called_once()
|
mock_org_instance.current.assert_called_once()
|
||||||
@@ -70,7 +70,7 @@ class TestOrganizationCommand(unittest.TestCase):
|
|||||||
with patch.object(OrganizationCommand, '__init__', return_value=None):
|
with patch.object(OrganizationCommand, '__init__', return_value=None):
|
||||||
self.org_command = OrganizationCommand()
|
self.org_command = OrganizationCommand()
|
||||||
self.org_command.plus_api_client = MagicMock()
|
self.org_command.plus_api_client = MagicMock()
|
||||||
|
|
||||||
@patch('crewai.cli.organization.main.console')
|
@patch('crewai.cli.organization.main.console')
|
||||||
@patch('crewai.cli.organization.main.Table')
|
@patch('crewai.cli.organization.main.Table')
|
||||||
def test_list_organizations_success(self, mock_table, mock_console):
|
def test_list_organizations_success(self, mock_table, mock_console):
|
||||||
@@ -82,11 +82,11 @@ class TestOrganizationCommand(unittest.TestCase):
|
|||||||
]
|
]
|
||||||
self.org_command.plus_api_client = MagicMock()
|
self.org_command.plus_api_client = MagicMock()
|
||||||
self.org_command.plus_api_client.get_organizations.return_value = mock_response
|
self.org_command.plus_api_client.get_organizations.return_value = mock_response
|
||||||
|
|
||||||
mock_console.print = MagicMock()
|
mock_console.print = MagicMock()
|
||||||
|
|
||||||
self.org_command.list()
|
self.org_command.list()
|
||||||
|
|
||||||
self.org_command.plus_api_client.get_organizations.assert_called_once()
|
self.org_command.plus_api_client.get_organizations.assert_called_once()
|
||||||
mock_table.assert_called_once_with(title="Your Organizations")
|
mock_table.assert_called_once_with(title="Your Organizations")
|
||||||
mock_table.return_value.add_column.assert_has_calls([
|
mock_table.return_value.add_column.assert_has_calls([
|
||||||
@@ -105,12 +105,12 @@ class TestOrganizationCommand(unittest.TestCase):
|
|||||||
mock_response.json.return_value = []
|
mock_response.json.return_value = []
|
||||||
self.org_command.plus_api_client = MagicMock()
|
self.org_command.plus_api_client = MagicMock()
|
||||||
self.org_command.plus_api_client.get_organizations.return_value = mock_response
|
self.org_command.plus_api_client.get_organizations.return_value = mock_response
|
||||||
|
|
||||||
self.org_command.list()
|
self.org_command.list()
|
||||||
|
|
||||||
self.org_command.plus_api_client.get_organizations.assert_called_once()
|
self.org_command.plus_api_client.get_organizations.assert_called_once()
|
||||||
mock_console.print.assert_called_once_with(
|
mock_console.print.assert_called_once_with(
|
||||||
"You don't belong to any organizations yet.",
|
"You don't belong to any organizations yet.",
|
||||||
style="yellow"
|
style="yellow"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -118,14 +118,14 @@ class TestOrganizationCommand(unittest.TestCase):
|
|||||||
def test_list_organizations_api_error(self, mock_console):
|
def test_list_organizations_api_error(self, mock_console):
|
||||||
self.org_command.plus_api_client = MagicMock()
|
self.org_command.plus_api_client = MagicMock()
|
||||||
self.org_command.plus_api_client.get_organizations.side_effect = requests.exceptions.RequestException("API Error")
|
self.org_command.plus_api_client.get_organizations.side_effect = requests.exceptions.RequestException("API Error")
|
||||||
|
|
||||||
with pytest.raises(SystemExit):
|
with pytest.raises(SystemExit):
|
||||||
self.org_command.list()
|
self.org_command.list()
|
||||||
|
|
||||||
|
|
||||||
self.org_command.plus_api_client.get_organizations.assert_called_once()
|
self.org_command.plus_api_client.get_organizations.assert_called_once()
|
||||||
mock_console.print.assert_called_once_with(
|
mock_console.print.assert_called_once_with(
|
||||||
"Failed to retrieve organization list: API Error",
|
"Failed to retrieve organization list: API Error",
|
||||||
style="bold red"
|
style="bold red"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -140,12 +140,12 @@ class TestOrganizationCommand(unittest.TestCase):
|
|||||||
]
|
]
|
||||||
self.org_command.plus_api_client = MagicMock()
|
self.org_command.plus_api_client = MagicMock()
|
||||||
self.org_command.plus_api_client.get_organizations.return_value = mock_response
|
self.org_command.plus_api_client.get_organizations.return_value = mock_response
|
||||||
|
|
||||||
mock_settings_instance = MagicMock()
|
mock_settings_instance = MagicMock()
|
||||||
mock_settings_class.return_value = mock_settings_instance
|
mock_settings_class.return_value = mock_settings_instance
|
||||||
|
|
||||||
self.org_command.switch("test-id")
|
self.org_command.switch("test-id")
|
||||||
|
|
||||||
self.org_command.plus_api_client.get_organizations.assert_called_once()
|
self.org_command.plus_api_client.get_organizations.assert_called_once()
|
||||||
mock_settings_instance.dump.assert_called_once()
|
mock_settings_instance.dump.assert_called_once()
|
||||||
assert mock_settings_instance.org_name == "Test Org"
|
assert mock_settings_instance.org_name == "Test Org"
|
||||||
@@ -165,9 +165,9 @@ class TestOrganizationCommand(unittest.TestCase):
|
|||||||
]
|
]
|
||||||
self.org_command.plus_api_client = MagicMock()
|
self.org_command.plus_api_client = MagicMock()
|
||||||
self.org_command.plus_api_client.get_organizations.return_value = mock_response
|
self.org_command.plus_api_client.get_organizations.return_value = mock_response
|
||||||
|
|
||||||
self.org_command.switch("non-existent-id")
|
self.org_command.switch("non-existent-id")
|
||||||
|
|
||||||
self.org_command.plus_api_client.get_organizations.assert_called_once()
|
self.org_command.plus_api_client.get_organizations.assert_called_once()
|
||||||
mock_console.print.assert_called_once_with(
|
mock_console.print.assert_called_once_with(
|
||||||
"Organization with id 'non-existent-id' not found.",
|
"Organization with id 'non-existent-id' not found.",
|
||||||
@@ -181,9 +181,9 @@ class TestOrganizationCommand(unittest.TestCase):
|
|||||||
mock_settings_instance.org_name = "Test Org"
|
mock_settings_instance.org_name = "Test Org"
|
||||||
mock_settings_instance.org_uuid = "test-id"
|
mock_settings_instance.org_uuid = "test-id"
|
||||||
mock_settings_class.return_value = mock_settings_instance
|
mock_settings_class.return_value = mock_settings_instance
|
||||||
|
|
||||||
self.org_command.current()
|
self.org_command.current()
|
||||||
|
|
||||||
self.org_command.plus_api_client.get_organizations.assert_not_called()
|
self.org_command.plus_api_client.get_organizations.assert_not_called()
|
||||||
mock_console.print.assert_called_once_with(
|
mock_console.print.assert_called_once_with(
|
||||||
"Currently logged in to organization Test Org (test-id)",
|
"Currently logged in to organization Test Org (test-id)",
|
||||||
@@ -196,11 +196,49 @@ class TestOrganizationCommand(unittest.TestCase):
|
|||||||
mock_settings_instance = MagicMock()
|
mock_settings_instance = MagicMock()
|
||||||
mock_settings_instance.org_uuid = None
|
mock_settings_instance.org_uuid = None
|
||||||
mock_settings_class.return_value = mock_settings_instance
|
mock_settings_class.return_value = mock_settings_instance
|
||||||
|
|
||||||
self.org_command.current()
|
self.org_command.current()
|
||||||
|
|
||||||
assert mock_console.print.call_count == 3
|
assert mock_console.print.call_count == 3
|
||||||
mock_console.print.assert_any_call(
|
mock_console.print.assert_any_call(
|
||||||
"You're not currently logged in to any organization.",
|
"You're not currently logged in to any organization.",
|
||||||
style="yellow"
|
style="yellow"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@patch('crewai.cli.organization.main.console')
|
||||||
|
def test_list_organizations_unauthorized(self, mock_console):
|
||||||
|
mock_response = MagicMock()
|
||||||
|
mock_http_error = requests.exceptions.HTTPError(
|
||||||
|
"401 Client Error: Unauthorized",
|
||||||
|
response=MagicMock(status_code=401)
|
||||||
|
)
|
||||||
|
|
||||||
|
mock_response.raise_for_status.side_effect = mock_http_error
|
||||||
|
self.org_command.plus_api_client.get_organizations.return_value = mock_response
|
||||||
|
|
||||||
|
self.org_command.list()
|
||||||
|
|
||||||
|
self.org_command.plus_api_client.get_organizations.assert_called_once()
|
||||||
|
mock_console.print.assert_called_once_with(
|
||||||
|
"You are not logged in to any organization. Use 'crewai login' to login.",
|
||||||
|
style="bold red"
|
||||||
|
)
|
||||||
|
|
||||||
|
@patch('crewai.cli.organization.main.console')
|
||||||
|
def test_switch_organization_unauthorized(self, mock_console):
|
||||||
|
mock_response = MagicMock()
|
||||||
|
mock_http_error = requests.exceptions.HTTPError(
|
||||||
|
"401 Client Error: Unauthorized",
|
||||||
|
response=MagicMock(status_code=401)
|
||||||
|
)
|
||||||
|
|
||||||
|
mock_response.raise_for_status.side_effect = mock_http_error
|
||||||
|
self.org_command.plus_api_client.get_organizations.return_value = mock_response
|
||||||
|
|
||||||
|
self.org_command.switch("test-id")
|
||||||
|
|
||||||
|
self.org_command.plus_api_client.get_organizations.assert_called_once()
|
||||||
|
mock_console.print.assert_called_once_with(
|
||||||
|
"You are not logged in to any organization. Use 'crewai login' to login.",
|
||||||
|
style="bold red"
|
||||||
|
)
|
||||||
|
|||||||
@@ -56,7 +56,8 @@ def test_create_success(mock_subprocess, capsys, tool_command):
|
|||||||
|
|
||||||
@patch("crewai.cli.tools.main.subprocess.run")
|
@patch("crewai.cli.tools.main.subprocess.run")
|
||||||
@patch("crewai.cli.plus_api.PlusAPI.get_tool")
|
@patch("crewai.cli.plus_api.PlusAPI.get_tool")
|
||||||
def test_install_success(mock_get, mock_subprocess_run, capsys, tool_command):
|
@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 = MagicMock()
|
||||||
mock_get_response.status_code = 200
|
mock_get_response.status_code = 200
|
||||||
mock_get_response.json.return_value = {
|
mock_get_response.json.return_value = {
|
||||||
@@ -85,6 +86,9 @@ def test_install_success(mock_get, mock_subprocess_run, capsys, tool_command):
|
|||||||
env=unittest.mock.ANY,
|
env=unittest.mock.ANY,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Verify _print_current_organization was called
|
||||||
|
mock_print_org.assert_called_once()
|
||||||
|
|
||||||
@patch("crewai.cli.tools.main.subprocess.run")
|
@patch("crewai.cli.tools.main.subprocess.run")
|
||||||
@patch("crewai.cli.plus_api.PlusAPI.get_tool")
|
@patch("crewai.cli.plus_api.PlusAPI.get_tool")
|
||||||
def test_install_success_from_pypi(mock_get, mock_subprocess_run, capsys, tool_command):
|
def test_install_success_from_pypi(mock_get, mock_subprocess_run, capsys, tool_command):
|
||||||
@@ -166,7 +170,9 @@ def test_publish_when_not_in_sync(mock_is_synced, capsys, tool_command):
|
|||||||
@patch("crewai.cli.plus_api.PlusAPI.publish_tool")
|
@patch("crewai.cli.plus_api.PlusAPI.publish_tool")
|
||||||
@patch("crewai.cli.tools.main.git.Repository.is_synced", return_value=False)
|
@patch("crewai.cli.tools.main.git.Repository.is_synced", return_value=False)
|
||||||
@patch("crewai.cli.tools.main.extract_available_exports", return_value=[{"name": "SampleTool"}])
|
@patch("crewai.cli.tools.main.extract_available_exports", return_value=[{"name": "SampleTool"}])
|
||||||
|
@patch("crewai.cli.tools.main.ToolCommand._print_current_organization")
|
||||||
def test_publish_when_not_in_sync_and_force(
|
def test_publish_when_not_in_sync_and_force(
|
||||||
|
mock_print_org,
|
||||||
mock_available_exports,
|
mock_available_exports,
|
||||||
mock_is_synced,
|
mock_is_synced,
|
||||||
mock_publish,
|
mock_publish,
|
||||||
@@ -202,6 +208,7 @@ def test_publish_when_not_in_sync_and_force(
|
|||||||
encoded_file=unittest.mock.ANY,
|
encoded_file=unittest.mock.ANY,
|
||||||
available_exports=[{"name": "SampleTool"}],
|
available_exports=[{"name": "SampleTool"}],
|
||||||
)
|
)
|
||||||
|
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_name", return_value="sample-tool")
|
||||||
@@ -329,3 +336,27 @@ def test_publish_api_error(
|
|||||||
assert "Request to Enterprise API failed" in output
|
assert "Request to Enterprise API failed" in output
|
||||||
|
|
||||||
mock_publish.assert_called_once()
|
mock_publish.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@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
|
||||||
|
|||||||
Reference in New Issue
Block a user