mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-07-03 14:09:24 +00:00
JSON crews and declarative flows now resolve from `[tool.crewai]`
metadata instead of implicit filename discovery. This makes project type
selection deterministic, prevents stray `crew.json(c)` files from changing
CLI behavior, and centralizes definition path validation for run, install,
deploy validation, plotting, and memory reset paths.
`[tool.crewai].definition` must be a project-local file path. Absolute
paths, `~`, missing files, directories, and paths escaping the project root
are rejected so deploy and runtime commands use the same contract.
Breaking changes and migration paths:
* JSON crew projects are no longer discovered from `crew.json` or
`crew.jsonc` alone. Add explicit metadata:
```toml
[tool.crewai]
type = "crew"
definition = "crew.jsonc"
```
* Declarative flow projects must use a valid project-local definition path:
```toml
[tool.crewai]
type = "flow"
definition = "flows/research.yaml"
```
* `Flow.from_definition(definition)` is removed. Use:
```python
Flow.from_declaration(contents=definition)
```
* `FlowDefinition.to_json()` and `FlowDefinition.to_yaml()` are removed.
Use `FlowDefinition.to_dict()` and serialize with the caller's JSON or
YAML library.
* `FlowDefinition.from_dict()` is removed. Use:
```python
FlowDefinition.from_declaration(contents=data)
```
* `FlowDefinition.json_schema()` is removed. Use Pydantic's schema API only
where schema generation is intentionally needed:
```python
FlowDefinition.model_json_schema(by_alias=True)
```
* `crewai_cli.run_crew.find_crew_json_file()` and `_has_json_crew()` are
removed. Use `configured_project_json_crew()` or the shared
`crewai_core.project.configured_project_definition("crew")` helper.
* `crewai reset-memories` now only loads JSON crews declared through
`[tool.crewai].definition`, and invalid declared JSON crew definitions
fail instead of silently falling back to classic crew discovery.
346 lines
12 KiB
Python
346 lines
12 KiB
Python
"""Tests for CLI commands that require crewai core (reset-memories).
|
|
|
|
Non-core CLI tests (train, test, version, deploy, login, flow_add_crew)
|
|
have moved to lib/cli/tests/test_cli.py.
|
|
"""
|
|
|
|
from pathlib import Path
|
|
from unittest import mock
|
|
|
|
from click.testing import CliRunner
|
|
from crewai.crew import Crew
|
|
from crewai.memory.unified_memory import Memory
|
|
from crewai_cli.cli import reset_memories
|
|
import pytest
|
|
|
|
|
|
@pytest.fixture
|
|
def runner():
|
|
return CliRunner()
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_crew():
|
|
_mock = mock.Mock(spec=Crew, name="test_crew")
|
|
_mock.name = "test_crew"
|
|
return _mock
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_get_crews(mock_crew):
|
|
with mock.patch(
|
|
"crewai.utilities.reset_memories.get_crews", return_value=[mock_crew]
|
|
) as mock_get_crew, mock.patch(
|
|
"crewai.utilities.reset_memories.get_flows", return_value=[]
|
|
), mock.patch(
|
|
"crewai.utilities.reset_memories._get_json_crew", return_value=None
|
|
):
|
|
yield mock_get_crew
|
|
|
|
|
|
def test_reset_all_memories(mock_get_crews, runner):
|
|
result = runner.invoke(reset_memories, ["-a"])
|
|
|
|
call_count = 0
|
|
for crew in mock_get_crews.return_value:
|
|
crew.reset_memories.assert_called_once_with(command_type="all")
|
|
assert (
|
|
f"[Crew ({crew.name})] Reset memories command has been completed."
|
|
in result.output
|
|
)
|
|
call_count += 1
|
|
|
|
assert call_count == 1, "reset_memories should have been called once"
|
|
|
|
|
|
def test_reset_memory(mock_get_crews, runner):
|
|
result = runner.invoke(reset_memories, ["-m"])
|
|
call_count = 0
|
|
for crew in mock_get_crews.return_value:
|
|
crew.reset_memories.assert_called_once_with(command_type="memory")
|
|
assert (
|
|
f"[Crew ({crew.name})] Memory has been reset." in result.output
|
|
)
|
|
call_count += 1
|
|
|
|
assert call_count == 1, "reset_memories should have been called once"
|
|
|
|
|
|
def test_reset_short_flag_deprecated_maps_to_memory(mock_get_crews, runner):
|
|
result = runner.invoke(reset_memories, ["-s"])
|
|
assert "deprecated" in result.output.lower()
|
|
for crew in mock_get_crews.return_value:
|
|
crew.reset_memories.assert_called_once_with(command_type="memory")
|
|
assert f"[Crew ({crew.name})] Memory has been reset." in result.output
|
|
|
|
|
|
def test_reset_entity_flag_deprecated_maps_to_memory(mock_get_crews, runner):
|
|
result = runner.invoke(reset_memories, ["-e"])
|
|
assert "deprecated" in result.output.lower()
|
|
for crew in mock_get_crews.return_value:
|
|
crew.reset_memories.assert_called_once_with(command_type="memory")
|
|
assert f"[Crew ({crew.name})] Memory has been reset." in result.output
|
|
|
|
|
|
def test_reset_long_flag_deprecated_maps_to_memory(mock_get_crews, runner):
|
|
result = runner.invoke(reset_memories, ["-l"])
|
|
assert "deprecated" in result.output.lower()
|
|
for crew in mock_get_crews.return_value:
|
|
crew.reset_memories.assert_called_once_with(command_type="memory")
|
|
assert f"[Crew ({crew.name})] Memory has been reset." in result.output
|
|
|
|
|
|
def test_reset_kickoff_outputs(mock_get_crews, runner):
|
|
result = runner.invoke(reset_memories, ["-k"])
|
|
call_count = 0
|
|
for crew in mock_get_crews.return_value:
|
|
crew.reset_memories.assert_called_once_with(command_type="kickoff_outputs")
|
|
assert (
|
|
f"[Crew ({crew.name})] Latest Kickoff outputs stored has been reset."
|
|
in result.output
|
|
)
|
|
call_count += 1
|
|
|
|
assert call_count == 1, "reset_memories should have been called once"
|
|
|
|
|
|
def test_reset_multiple_legacy_flags_collapsed_to_single_memory_reset(mock_get_crews, runner):
|
|
result = runner.invoke(reset_memories, ["-s", "-l"])
|
|
assert "deprecated" in result.output.lower()
|
|
call_count = 0
|
|
for crew in mock_get_crews.return_value:
|
|
crew.reset_memories.assert_called_once_with(command_type="memory")
|
|
assert f"[Crew ({crew.name})] Memory has been reset." in result.output
|
|
call_count += 1
|
|
|
|
assert call_count == 1, "reset_memories should have been called once"
|
|
|
|
|
|
def test_reset_knowledge(mock_get_crews, runner):
|
|
result = runner.invoke(reset_memories, ["--knowledge"])
|
|
call_count = 0
|
|
for crew in mock_get_crews.return_value:
|
|
crew.reset_memories.assert_called_once_with(command_type="knowledge")
|
|
assert f"[Crew ({crew.name})] Knowledge has been reset." in result.output
|
|
call_count += 1
|
|
|
|
assert call_count == 1, "reset_memories should have been called once"
|
|
|
|
|
|
def test_reset_agent_knowledge(mock_get_crews, runner):
|
|
result = runner.invoke(reset_memories, ["--agent-knowledge"])
|
|
call_count = 0
|
|
for crew in mock_get_crews.return_value:
|
|
crew.reset_memories.assert_called_once_with(command_type="agent_knowledge")
|
|
assert f"[Crew ({crew.name})] Agents knowledge has been reset." in result.output
|
|
call_count += 1
|
|
|
|
assert call_count == 1, "reset_memories should have been called once"
|
|
|
|
|
|
def test_reset_memory_from_many_crews(mock_get_crews, runner):
|
|
crews = []
|
|
for crew_id in ["id-1234", "id-5678"]:
|
|
mock_crew = mock.Mock(spec=Crew)
|
|
mock_crew.name = None
|
|
mock_crew.id = crew_id
|
|
crews.append(mock_crew)
|
|
|
|
mock_get_crews.return_value = crews
|
|
|
|
result = runner.invoke(reset_memories, ["--knowledge"])
|
|
|
|
call_count = 0
|
|
for crew in crews:
|
|
call_count += 1
|
|
crew.reset_memories.assert_called_once_with(command_type="knowledge")
|
|
assert f"[Crew ({crew.id})] Knowledge has been reset." in result.output
|
|
|
|
assert call_count == 2, "reset_memories should have been called twice"
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_flow():
|
|
_mock = mock.Mock()
|
|
_mock.name = "TestFlow"
|
|
_mock.memory = mock.Mock()
|
|
_mock.memory.reset = mock.Mock()
|
|
return _mock
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_get_flows(mock_flow):
|
|
with mock.patch(
|
|
"crewai.utilities.reset_memories.get_flows", return_value=[mock_flow]
|
|
) as mock_get_flow, mock.patch(
|
|
"crewai.utilities.reset_memories.get_crews", return_value=[]
|
|
), mock.patch(
|
|
"crewai.utilities.reset_memories._get_json_crew", return_value=None
|
|
):
|
|
yield mock_get_flow
|
|
|
|
|
|
def test_reset_flow_memory(mock_get_flows, mock_flow, runner):
|
|
result = runner.invoke(reset_memories, ["-m"])
|
|
mock_flow.memory.reset.assert_called_once()
|
|
assert "[Flow (TestFlow)] Memory has been reset." in result.output
|
|
|
|
|
|
def test_reset_flow_unified_memory_uses_full_reset(runner, tmp_path):
|
|
flow = mock.Mock()
|
|
flow.name = "TestFlow"
|
|
flow.memory = Memory(
|
|
storage=str(tmp_path / "db"),
|
|
llm=mock.Mock(),
|
|
embedder=lambda texts: [[0.1] * 4 for _ in texts],
|
|
)
|
|
|
|
with mock.patch(
|
|
"crewai.utilities.reset_memories.get_flows", return_value=[flow]
|
|
), mock.patch(
|
|
"crewai.utilities.reset_memories.get_crews", return_value=[]
|
|
), mock.patch(
|
|
"crewai.utilities.reset_memories._get_json_crew", return_value=None
|
|
), mock.patch.object(
|
|
Memory, "reset_all"
|
|
) as reset_all, mock.patch.object(
|
|
Memory, "reset"
|
|
) as reset:
|
|
result = runner.invoke(reset_memories, ["-m"])
|
|
|
|
reset_all.assert_called_once_with()
|
|
reset.assert_not_called()
|
|
assert "[Flow (TestFlow)] Memory has been reset." in result.output
|
|
|
|
|
|
def test_reset_flow_all_memories(mock_get_flows, mock_flow, runner):
|
|
result = runner.invoke(reset_memories, ["-a"])
|
|
mock_flow.memory.reset.assert_called_once()
|
|
assert "[Flow (TestFlow)] Reset memories command has been completed." in result.output
|
|
|
|
|
|
def test_reset_flow_knowledge_no_effect(mock_get_flows, mock_flow, runner):
|
|
result = runner.invoke(reset_memories, ["--knowledge"])
|
|
mock_flow.memory.reset.assert_not_called()
|
|
assert "[Flow (TestFlow)]" not in result.output
|
|
|
|
|
|
def test_reset_no_crew_or_flow_found(runner):
|
|
with mock.patch(
|
|
"crewai.utilities.reset_memories.get_crews", return_value=[]
|
|
), mock.patch(
|
|
"crewai.utilities.reset_memories.get_flows", return_value=[]
|
|
), mock.patch(
|
|
"crewai.utilities.reset_memories._get_json_crew", return_value=None
|
|
):
|
|
result = runner.invoke(reset_memories, ["-m"])
|
|
assert "No crew or flow found." in result.output
|
|
|
|
|
|
def test_reset_json_crew_memory(mock_crew, runner, monkeypatch, tmp_path):
|
|
monkeypatch.chdir(tmp_path)
|
|
(tmp_path / "crew.jsonc").write_text("{}")
|
|
(tmp_path / "pyproject.toml").write_text(
|
|
'[tool.crewai]\ntype = "crew"\ndefinition = "crew.jsonc"\n'
|
|
)
|
|
|
|
with mock.patch(
|
|
"crewai.utilities.reset_memories.get_crews", return_value=[]
|
|
), mock.patch(
|
|
"crewai.utilities.reset_memories.get_flows", return_value=[]
|
|
), mock.patch(
|
|
"crewai.utilities.reset_memories.load_crew",
|
|
return_value=(mock_crew, {}),
|
|
) as mock_load_crew:
|
|
result = runner.invoke(reset_memories, ["-m"])
|
|
|
|
mock_load_crew.assert_called_once_with((tmp_path / "crew.jsonc").resolve())
|
|
mock_crew.reset_memories.assert_called_once_with(command_type="memory")
|
|
assert f"[Crew ({mock_crew.name})] Memory has been reset." in result.output
|
|
|
|
|
|
def test_reset_invalid_json_crew_blocks_reset(
|
|
mock_crew, runner, monkeypatch, tmp_path
|
|
):
|
|
monkeypatch.chdir(tmp_path)
|
|
(tmp_path / "crew.jsonc").write_text("{invalid")
|
|
(tmp_path / "pyproject.toml").write_text(
|
|
'[tool.crewai]\ntype = "crew"\ndefinition = "crew.jsonc"\n'
|
|
)
|
|
|
|
with mock.patch(
|
|
"crewai.utilities.reset_memories.get_crews", return_value=[mock_crew]
|
|
), mock.patch(
|
|
"crewai.utilities.reset_memories.get_flows", return_value=[]
|
|
), mock.patch(
|
|
"crewai.utilities.reset_memories.load_crew",
|
|
side_effect=ValueError("invalid JSON"),
|
|
) as mock_load_crew:
|
|
result = runner.invoke(reset_memories, ["-m"])
|
|
|
|
mock_load_crew.assert_called_once_with((tmp_path / "crew.jsonc").resolve())
|
|
mock_crew.reset_memories.assert_not_called()
|
|
assert "An unexpected error occurred: invalid JSON" in result.output
|
|
|
|
|
|
def test_reset_json_crew_skipped_for_declared_flow_project(
|
|
mock_crew, runner, monkeypatch, tmp_path
|
|
):
|
|
monkeypatch.chdir(tmp_path)
|
|
(tmp_path / "crew.jsonc").write_text("{}")
|
|
(tmp_path / "pyproject.toml").write_text('[tool.crewai]\ntype = "flow"\n')
|
|
|
|
with mock.patch(
|
|
"crewai.utilities.reset_memories.get_crews", return_value=[]
|
|
), mock.patch(
|
|
"crewai.utilities.reset_memories.get_flows", return_value=[]
|
|
), mock.patch(
|
|
"crewai.utilities.reset_memories.load_crew",
|
|
return_value=(mock_crew, {}),
|
|
) as mock_load_crew:
|
|
result = runner.invoke(reset_memories, ["-m"])
|
|
|
|
mock_load_crew.assert_not_called()
|
|
mock_crew.reset_memories.assert_not_called()
|
|
assert "No crew or flow found." in result.output
|
|
|
|
|
|
def test_reset_crew_and_flow_memory(mock_crew, mock_flow, runner):
|
|
with mock.patch(
|
|
"crewai.utilities.reset_memories.get_crews", return_value=[mock_crew]
|
|
), mock.patch(
|
|
"crewai.utilities.reset_memories.get_flows", return_value=[mock_flow]
|
|
), mock.patch(
|
|
"crewai.utilities.reset_memories._get_json_crew", return_value=None
|
|
):
|
|
result = runner.invoke(reset_memories, ["-m"])
|
|
mock_crew.reset_memories.assert_called_once_with(command_type="memory")
|
|
mock_flow.memory.reset.assert_called_once()
|
|
assert f"[Crew ({mock_crew.name})] Memory has been reset." in result.output
|
|
assert "[Flow (TestFlow)] Memory has been reset." in result.output
|
|
|
|
|
|
def test_reset_flow_memory_none(runner):
|
|
mock_flow = mock.Mock()
|
|
mock_flow.name = "NoMemFlow"
|
|
mock_flow.memory = None
|
|
with mock.patch(
|
|
"crewai.utilities.reset_memories.get_crews", return_value=[]
|
|
), mock.patch(
|
|
"crewai.utilities.reset_memories.get_flows", return_value=[mock_flow]
|
|
), mock.patch(
|
|
"crewai.utilities.reset_memories._get_json_crew", return_value=None
|
|
):
|
|
result = runner.invoke(reset_memories, ["-m"])
|
|
assert "[Flow (NoMemFlow)] Memory has been reset." in result.output
|
|
|
|
|
|
def test_reset_no_memory_flags(runner):
|
|
result = runner.invoke(
|
|
reset_memories,
|
|
)
|
|
assert (
|
|
result.output
|
|
== "Please specify at least one memory type to reset using the appropriate flags.\n"
|
|
)
|