mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-07-01 13:18:10 +00:00
249 lines
7.1 KiB
Python
249 lines
7.1 KiB
Python
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
import subprocess
|
|
|
|
import click
|
|
import pytest
|
|
from click.testing import CliRunner
|
|
|
|
from crewai_cli.cli import flow_run
|
|
import crewai_cli.plot_flow as plot_flow_module
|
|
|
|
|
|
FLOW_YAML = """\
|
|
schema: crewai.flow/v1
|
|
name: TestFlow
|
|
config:
|
|
suppress_flow_events: true
|
|
methods:
|
|
begin:
|
|
start: true
|
|
do:
|
|
call: expression
|
|
expr: "'AI'"
|
|
"""
|
|
|
|
|
|
def _write_flow_project(project_root: Path) -> None:
|
|
(project_root / "flow.yaml").write_text(FLOW_YAML, encoding="utf-8")
|
|
(project_root / "pyproject.toml").write_text(
|
|
'[project]\nname = "demo"\n\n'
|
|
'[tool.crewai]\ntype = "flow"\ndefinition = "flow.yaml"\n',
|
|
encoding="utf-8",
|
|
)
|
|
|
|
|
|
def test_flow_kickoff_runs_configured_declarative_definition(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
tmp_path: Path,
|
|
) -> None:
|
|
_write_flow_project(tmp_path)
|
|
monkeypatch.chdir(tmp_path)
|
|
monkeypatch.setenv("UV_RUN_RECURSION_DEPTH", "1")
|
|
|
|
result = CliRunner().invoke(flow_run)
|
|
|
|
assert result.exit_code == 0
|
|
assert (
|
|
"The command 'crewai flow kickoff' is deprecated. Use 'crewai run' instead."
|
|
in result.output
|
|
)
|
|
assert "AI\n" in result.output
|
|
assert "Running the Flow" not in result.output
|
|
|
|
|
|
def test_plot_flow_runs_configured_declarative_definition(
|
|
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
) -> None:
|
|
_write_flow_project(tmp_path)
|
|
monkeypatch.chdir(tmp_path)
|
|
monkeypatch.setenv("UV_RUN_RECURSION_DEPTH", "1")
|
|
|
|
plot_flow_module.plot_flow()
|
|
|
|
|
|
def test_flow_kickoff_delegates_to_run_crew(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
calls = []
|
|
|
|
monkeypatch.setattr(
|
|
"crewai_cli.cli.run_crew",
|
|
lambda **kwargs: calls.append(kwargs),
|
|
)
|
|
|
|
result = CliRunner().invoke(flow_run)
|
|
|
|
assert result.exit_code == 0
|
|
assert calls == [
|
|
{"trained_agents_file": None, "definition": None, "inputs": None},
|
|
]
|
|
|
|
|
|
def test_plot_flow_keeps_python_entrypoint_without_definition(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
tmp_path: Path,
|
|
) -> None:
|
|
subprocess_calls = []
|
|
|
|
monkeypatch.chdir(tmp_path)
|
|
monkeypatch.setattr(
|
|
subprocess,
|
|
"run",
|
|
lambda command, **kwargs: subprocess_calls.append((command, kwargs)),
|
|
)
|
|
|
|
plot_flow_module.plot_flow()
|
|
|
|
assert subprocess_calls == [
|
|
(
|
|
["uv", "run", "plot"],
|
|
{"capture_output": False, "text": True, "check": True},
|
|
)
|
|
]
|
|
|
|
|
|
def test_configured_project_declarative_flow(
|
|
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
) -> None:
|
|
monkeypatch.chdir(tmp_path)
|
|
definition_path = tmp_path / "flow.yaml"
|
|
definition_path.write_text(FLOW_YAML, encoding="utf-8")
|
|
(tmp_path / "pyproject.toml").write_text(
|
|
'[tool.crewai]\ntype = "flow"\ndefinition = " flow.yaml "\n',
|
|
encoding="utf-8",
|
|
)
|
|
|
|
from crewai_cli.run_declarative_flow import configured_project_declarative_flow
|
|
|
|
assert configured_project_declarative_flow() == definition_path.resolve()
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("definition", "expected_error"),
|
|
[
|
|
("C:/tmp/flow.yaml", "must be relative to the project root"),
|
|
("~/flow.yaml", "must be a project-local path"),
|
|
("../flow.yaml", "must resolve inside the project root"),
|
|
],
|
|
)
|
|
def test_configured_project_declarative_flow_rejects_unsafe_paths(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
tmp_path: Path,
|
|
definition: str,
|
|
expected_error: str,
|
|
) -> None:
|
|
monkeypatch.chdir(tmp_path)
|
|
(tmp_path / "pyproject.toml").write_text(
|
|
f'[tool.crewai]\ntype = "flow"\ndefinition = "{definition}"\n',
|
|
encoding="utf-8",
|
|
)
|
|
|
|
from crewai_cli.run_declarative_flow import configured_project_declarative_flow
|
|
|
|
with pytest.raises(click.UsageError) as exc_info:
|
|
configured_project_declarative_flow()
|
|
|
|
assert expected_error in exc_info.value.message
|
|
|
|
|
|
def test_configured_project_declarative_flow_allows_normalized_project_path(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
tmp_path: Path,
|
|
) -> None:
|
|
monkeypatch.chdir(tmp_path)
|
|
definition_path = tmp_path / "flow.yaml"
|
|
definition_path.write_text(FLOW_YAML, encoding="utf-8")
|
|
(tmp_path / "src").mkdir()
|
|
(tmp_path / "pyproject.toml").write_text(
|
|
'[tool.crewai]\ntype = "flow"\ndefinition = "src/../flow.yaml"\n',
|
|
encoding="utf-8",
|
|
)
|
|
|
|
from crewai_cli.run_declarative_flow import configured_project_declarative_flow
|
|
|
|
assert configured_project_declarative_flow() == definition_path.resolve()
|
|
|
|
|
|
def test_configured_project_declarative_flow_rejects_absolute_path(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
tmp_path: Path,
|
|
) -> None:
|
|
monkeypatch.chdir(tmp_path)
|
|
definition = tmp_path / "flow.yaml"
|
|
(tmp_path / "pyproject.toml").write_text(
|
|
f'[tool.crewai]\ntype = "flow"\ndefinition = "{definition.as_posix()}"\n',
|
|
encoding="utf-8",
|
|
)
|
|
|
|
from crewai_cli.run_declarative_flow import configured_project_declarative_flow
|
|
|
|
with pytest.raises(click.UsageError) as exc_info:
|
|
configured_project_declarative_flow()
|
|
|
|
assert "must be relative to the project root" in exc_info.value.message
|
|
|
|
|
|
def test_configured_project_declarative_flow_rejects_symlink_escape(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
tmp_path: Path,
|
|
) -> None:
|
|
monkeypatch.chdir(tmp_path)
|
|
outside_definition = tmp_path.parent / "outside-flow.yaml"
|
|
outside_definition.write_text(FLOW_YAML, encoding="utf-8")
|
|
link = tmp_path / "flow.yaml"
|
|
try:
|
|
link.symlink_to(outside_definition)
|
|
except (NotImplementedError, OSError) as exc:
|
|
pytest.skip(f"symlinks unavailable: {exc}")
|
|
|
|
(tmp_path / "pyproject.toml").write_text(
|
|
'[tool.crewai]\ntype = "flow"\ndefinition = "flow.yaml"\n',
|
|
encoding="utf-8",
|
|
)
|
|
|
|
from crewai_cli.run_declarative_flow import configured_project_declarative_flow
|
|
|
|
with pytest.raises(click.UsageError) as exc_info:
|
|
configured_project_declarative_flow()
|
|
|
|
assert "must resolve inside the project root" in exc_info.value.message
|
|
|
|
|
|
def test_configured_project_declarative_flow_rejects_missing_file(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
tmp_path: Path,
|
|
) -> None:
|
|
monkeypatch.chdir(tmp_path)
|
|
(tmp_path / "pyproject.toml").write_text(
|
|
'[tool.crewai]\ntype = "flow"\ndefinition = "missing-flow.yaml"\n',
|
|
encoding="utf-8",
|
|
)
|
|
|
|
from crewai_cli.run_declarative_flow import configured_project_declarative_flow
|
|
|
|
with pytest.raises(click.UsageError) as exc_info:
|
|
configured_project_declarative_flow()
|
|
|
|
assert "must point to an existing file" in exc_info.value.message
|
|
|
|
|
|
def test_configured_project_declarative_flow_rejects_directory(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
tmp_path: Path,
|
|
) -> None:
|
|
monkeypatch.chdir(tmp_path)
|
|
(tmp_path / "flow.yaml").mkdir()
|
|
(tmp_path / "pyproject.toml").write_text(
|
|
'[tool.crewai]\ntype = "flow"\ndefinition = "flow.yaml"\n',
|
|
encoding="utf-8",
|
|
)
|
|
|
|
from crewai_cli.run_declarative_flow import configured_project_declarative_flow
|
|
|
|
with pytest.raises(click.UsageError) as exc_info:
|
|
configured_project_declarative_flow()
|
|
|
|
assert "must point to a regular file" in exc_info.value.message
|