mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-09-22 02:46:39 +00:00
feat(cli): improve platform integration setup UX (#7453)
* fix(cli): silence tool import warnings during platform setup * feat(cli): validate platform integrations concurrently * fix(cli): persist platform token during crew setup * fix(core): make settings write probe concurrency-safe * test(cli): simplify event loop fallback stub
This commit is contained in:
@@ -1,6 +1,10 @@
|
||||
import asyncio
|
||||
import builtins
|
||||
import keyword
|
||||
import shutil
|
||||
import tempfile
|
||||
import threading
|
||||
import warnings
|
||||
from pathlib import Path
|
||||
from unittest import mock
|
||||
|
||||
@@ -667,6 +671,118 @@ def test_json_wizard_platform_catalog_contains_every_supported_app():
|
||||
]
|
||||
|
||||
|
||||
def test_platform_auth_suppresses_warnings_only_while_importing_tools(
|
||||
monkeypatch,
|
||||
):
|
||||
class FakeApplicationSelector:
|
||||
@classmethod
|
||||
def from_string(cls, value: str) -> str:
|
||||
return value
|
||||
|
||||
fake_client = mock.Mock()
|
||||
fake_client.get_actions.return_value = [object()]
|
||||
fake_integrations_client = mock.Mock(
|
||||
ApplicationSelector=FakeApplicationSelector,
|
||||
client_for_selector=lambda _selector: fake_client,
|
||||
)
|
||||
original_import = builtins.__import__
|
||||
|
||||
def import_with_warning(name, globals=None, locals=None, fromlist=(), level=0):
|
||||
if name == "crewai_tools.tools.crewai_platform_tools.integrations_client":
|
||||
warnings.warn("optional dependency import warning", UserWarning)
|
||||
return fake_integrations_client
|
||||
return original_import(name, globals, locals, fromlist, level)
|
||||
|
||||
monkeypatch.setattr(builtins, "__import__", import_with_warning)
|
||||
monkeypatch.setenv("CREWAI_PLATFORM_INTEGRATION_TOKEN", "test-token")
|
||||
|
||||
with warnings.catch_warnings(record=True) as caught_warnings:
|
||||
warnings.simplefilter("always")
|
||||
json_crew._setup_platform_auth([{"tools": ["platform:github"]}])
|
||||
warnings.warn("warning after import", UserWarning)
|
||||
|
||||
assert [str(warning.message) for warning in caught_warnings] == [
|
||||
"warning after import"
|
||||
]
|
||||
|
||||
|
||||
def test_platform_validation_checks_apps_concurrently():
|
||||
class FakeApplicationSelector:
|
||||
@classmethod
|
||||
def from_string(cls, value: str) -> str:
|
||||
return value
|
||||
|
||||
barrier = threading.Barrier(2)
|
||||
|
||||
class FakeClient:
|
||||
def get_actions(self, _selectors):
|
||||
barrier.wait(timeout=1)
|
||||
return [object()]
|
||||
|
||||
results = asyncio.run(
|
||||
json_crew._check_platform_apps_concurrently(
|
||||
["github", "gmail"],
|
||||
FakeApplicationSelector,
|
||||
lambda _selector: FakeClient(),
|
||||
)
|
||||
)
|
||||
|
||||
assert all(actions and error is None for actions, error in results)
|
||||
|
||||
|
||||
def test_platform_validation_announces_concurrent_apps_together(capsys):
|
||||
class FakeApplicationSelector:
|
||||
@classmethod
|
||||
def from_string(cls, value: str) -> str:
|
||||
return value
|
||||
|
||||
class FakeClient:
|
||||
def get_actions(self, _selectors):
|
||||
return [object()]
|
||||
|
||||
failed_apps, token_invalid = json_crew._validate_platform_apps(
|
||||
["github", "gmail", "google_calendar"],
|
||||
FakeApplicationSelector,
|
||||
lambda _selector: FakeClient(),
|
||||
)
|
||||
|
||||
output = capsys.readouterr().out
|
||||
assert (failed_apps, token_invalid) == ([], False)
|
||||
assert "Checking GitHub, Gmail, Google Calendar integrations together on AMP" in output
|
||||
assert "Checking CrewAI Platform Integration Token and GitHub" not in output
|
||||
|
||||
|
||||
def test_platform_validation_falls_back_to_sequential_checks(monkeypatch, capsys):
|
||||
class FakeApplicationSelector:
|
||||
@classmethod
|
||||
def from_string(cls, value: str) -> str:
|
||||
return value
|
||||
|
||||
checked_apps: list[str] = []
|
||||
|
||||
class FakeClient:
|
||||
def __init__(self, app: str):
|
||||
self.app = app
|
||||
|
||||
def get_actions(self, _selectors):
|
||||
checked_apps.append(self.app)
|
||||
return [object()]
|
||||
|
||||
monkeypatch.setattr(json_crew.asyncio, "get_running_loop", object)
|
||||
|
||||
failed_apps, token_invalid = json_crew._validate_platform_apps(
|
||||
["github", "gmail"],
|
||||
FakeApplicationSelector,
|
||||
lambda app: FakeClient(app),
|
||||
)
|
||||
|
||||
assert (failed_apps, token_invalid) == ([], False)
|
||||
assert checked_apps == ["github", "gmail"]
|
||||
output = capsys.readouterr().out
|
||||
assert "Checking CrewAI Platform Integration Token and GitHub integration" in output
|
||||
assert "Checking CrewAI Platform Integration Token and Gmail integration" in output
|
||||
|
||||
|
||||
def test_multi_picker_skips_separator_on_initial_cursor(monkeypatch):
|
||||
cursors: list[int] = []
|
||||
|
||||
@@ -889,6 +1005,44 @@ def test_json_create_provider_preselects_default_model(tmp_path, monkeypatch):
|
||||
assert '"knowledge_sources": []' in agent_template
|
||||
|
||||
|
||||
def test_json_create_saves_platform_token_to_env_file(tmp_path, monkeypatch):
|
||||
monkeypatch.chdir(tmp_path)
|
||||
monkeypatch.setattr(
|
||||
json_crew,
|
||||
"_wizard_agents_and_tasks",
|
||||
lambda **_: (
|
||||
[
|
||||
{
|
||||
"name": "researcher",
|
||||
"role": "Researcher",
|
||||
"goal": "Research",
|
||||
"backstory": "Researcher",
|
||||
"llm": "openai/gpt-5.5",
|
||||
"tools": ["platform:github"],
|
||||
"planning": False,
|
||||
"allow_delegation": False,
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"name": "research_task",
|
||||
"description": "Research",
|
||||
"expected_output": "Findings",
|
||||
"agent": "researcher",
|
||||
"context": [],
|
||||
}
|
||||
],
|
||||
{"process": "sequential", "memory": False, "inputs": {}},
|
||||
),
|
||||
)
|
||||
monkeypatch.setattr(json_crew, "_setup_platform_auth", lambda _agents: "token")
|
||||
|
||||
json_crew.create_json_crew("Platform Crew", skip_provider=True)
|
||||
|
||||
env_file = tmp_path / "platform_crew" / ".env"
|
||||
assert "CREWAI_PLATFORM_INTEGRATION_TOKEN=token" in env_file.read_text()
|
||||
|
||||
|
||||
def test_json_crew_uses_template_files():
|
||||
template_names = {
|
||||
"pyproject.toml",
|
||||
|
||||
Reference in New Issue
Block a user