feat: improve user feedback when user is not authenticated

This commit is contained in:
Lucas Gomide
2025-06-09 10:56:01 -03:00
parent 5b241f1459
commit 9de40c53f3
2 changed files with 82 additions and 31 deletions

View File

@@ -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)

View File

@@ -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"
)