Files
crewAI/lib/devtools/tests/test_toml_updates.py
Greyson LaLonde 8bae740899 fix: use regex for template pyproject.toml version bumps
tomlkit.parse() fails on Jinja placeholders like {{folder_name}}
in CLI template files. Switch to regex replacement for templates.
2026-04-09 00:13:07 +08:00

275 lines
8.5 KiB
Python

"""Tests for TOML-based version and dependency update functions."""
from pathlib import Path
from textwrap import dedent
from crewai_devtools.cli import (
_pin_crewai_deps,
_repin_crewai_install,
update_pyproject_version,
update_template_dependencies,
)
# --- update_pyproject_version ---
class TestUpdatePyprojectVersion:
def test_updates_version(self, tmp_path: Path) -> None:
pyproject = tmp_path / "pyproject.toml"
pyproject.write_text(
dedent("""\
[project]
name = "my-pkg"
version = "1.0.0"
""")
)
assert update_pyproject_version(pyproject, "2.0.0") is True
assert 'version = "2.0.0"' in pyproject.read_text()
def test_returns_false_when_already_current(self, tmp_path: Path) -> None:
pyproject = tmp_path / "pyproject.toml"
pyproject.write_text(
dedent("""\
[project]
name = "my-pkg"
version = "1.0.0"
""")
)
assert update_pyproject_version(pyproject, "1.0.0") is False
def test_returns_false_when_no_project_section(self, tmp_path: Path) -> None:
pyproject = tmp_path / "pyproject.toml"
pyproject.write_text("[tool.ruff]\nline-length = 88\n")
assert update_pyproject_version(pyproject, "1.0.0") is False
def test_returns_false_when_version_is_dynamic(self, tmp_path: Path) -> None:
pyproject = tmp_path / "pyproject.toml"
pyproject.write_text(
dedent("""\
[project]
name = "my-pkg"
dynamic = ["version"]
""")
)
assert update_pyproject_version(pyproject, "1.0.0") is False
assert 'version = "1.0.0"' not in pyproject.read_text()
def test_returns_false_for_missing_file(self, tmp_path: Path) -> None:
assert update_pyproject_version(tmp_path / "nope.toml", "1.0.0") is False
def test_preserves_comments_and_formatting(self, tmp_path: Path) -> None:
content = dedent("""\
# This is important
[project]
name = "my-pkg"
version = "1.0.0" # current version
description = "A package"
""")
pyproject = tmp_path / "pyproject.toml"
pyproject.write_text(content)
update_pyproject_version(pyproject, "2.0.0")
result = pyproject.read_text()
assert "# This is important" in result
assert 'description = "A package"' in result
# --- _pin_crewai_deps ---
class TestPinCrewaiDeps:
def test_pins_exact_version(self) -> None:
content = dedent("""\
[project]
dependencies = [
"crewai==1.0.0",
]
""")
result = _pin_crewai_deps(content, "2.0.0")
assert '"crewai==2.0.0"' in result
def test_pins_minimum_version(self) -> None:
content = dedent("""\
[project]
dependencies = [
"crewai>=1.0.0",
]
""")
result = _pin_crewai_deps(content, "2.0.0")
assert '"crewai==2.0.0"' in result
assert ">=" not in result
def test_pins_with_tools_extra(self) -> None:
content = dedent("""\
[project]
dependencies = [
"crewai[tools]==1.0.0",
]
""")
result = _pin_crewai_deps(content, "2.0.0")
assert '"crewai[tools]==2.0.0"' in result
def test_leaves_unrelated_deps_alone(self) -> None:
content = dedent("""\
[project]
dependencies = [
"requests>=2.0",
"crewai==1.0.0",
"click~=8.1",
]
""")
result = _pin_crewai_deps(content, "2.0.0")
assert '"requests>=2.0"' in result
assert '"click~=8.1"' in result
def test_handles_optional_dependencies(self) -> None:
content = dedent("""\
[project]
dependencies = []
[project.optional-dependencies]
tools = [
"crewai[tools]>=1.0.0",
]
""")
result = _pin_crewai_deps(content, "3.0.0")
assert '"crewai[tools]==3.0.0"' in result
def test_handles_multiple_crewai_entries(self) -> None:
content = dedent("""\
[project]
dependencies = [
"crewai==1.0.0",
"crewai[tools]==1.0.0",
]
""")
result = _pin_crewai_deps(content, "2.0.0")
assert '"crewai==2.0.0"' in result
assert '"crewai[tools]==2.0.0"' in result
def test_preserves_arbitrary_extras(self) -> None:
content = dedent("""\
[project]
dependencies = [
"crewai[a2a]==1.0.0",
]
""")
result = _pin_crewai_deps(content, "2.0.0")
assert '"crewai[a2a]==2.0.0"' in result
def test_no_deps_returns_unchanged(self) -> None:
content = dedent("""\
[project]
name = "empty"
""")
result = _pin_crewai_deps(content, "2.0.0")
assert "empty" in result
def test_skips_crewai_without_version_specifier(self) -> None:
content = dedent("""\
[project]
dependencies = [
"crewai-tools~=1.0",
]
""")
result = _pin_crewai_deps(content, "2.0.0")
assert '"crewai-tools~=1.0"' in result
def test_skips_crewai_extras_without_pin(self) -> None:
content = dedent("""\
[project]
dependencies = [
"crewai[tools]",
]
""")
result = _pin_crewai_deps(content, "2.0.0")
assert '"crewai[tools]"' in result
assert "==" not in result
# --- _repin_crewai_install ---
class TestRepinCrewaiInstall:
def test_repins_a2a_extra(self) -> None:
result = _repin_crewai_install('uv pip install "crewai[a2a]==1.14.0"', "2.0.0")
assert result == 'uv pip install "crewai[a2a]==2.0.0"'
def test_repins_tools_extra(self) -> None:
result = _repin_crewai_install('uv pip install "crewai[tools]==1.0.0"', "3.0.0")
assert result == 'uv pip install "crewai[tools]==3.0.0"'
def test_leaves_unrelated_commands_alone(self) -> None:
cmd = "uv pip install requests"
assert _repin_crewai_install(cmd, "2.0.0") == cmd
def test_handles_multiple_pins(self) -> None:
cmd = 'pip install "crewai[a2a]==1.0.0" "crewai[tools]==1.0.0"'
result = _repin_crewai_install(cmd, "2.0.0")
assert result == 'pip install "crewai[a2a]==2.0.0" "crewai[tools]==2.0.0"'
def test_preserves_surrounding_text(self) -> None:
cmd = 'echo hello && uv pip install "crewai[a2a]==1.14.0" && echo done'
result = _repin_crewai_install(cmd, "2.0.0")
assert (
result == 'echo hello && uv pip install "crewai[a2a]==2.0.0" && echo done'
)
def test_no_version_specifier_unchanged(self) -> None:
cmd = 'pip install "crewai[tools]>=1.0"'
assert _repin_crewai_install(cmd, "2.0.0") == cmd
# --- update_template_dependencies ---
class TestUpdateTemplateDependencies:
def test_updates_jinja_template(self, tmp_path: Path) -> None:
"""Template pyproject.toml files with Jinja placeholders should not break."""
tpl = tmp_path / "crew" / "pyproject.toml"
tpl.parent.mkdir()
tpl.write_text(
dedent("""\
[project]
name = "{{folder_name}}"
version = "0.1.0"
dependencies = [
"crewai[tools]==1.14.0"
]
[project.scripts]
{{folder_name}} = "{{folder_name}}.main:run"
""")
)
updated = update_template_dependencies(tmp_path, "2.0.0")
assert len(updated) == 1
content = tpl.read_text()
assert '"crewai[tools]==2.0.0"' in content
assert "{{folder_name}}" in content
def test_updates_bare_crewai(self, tmp_path: Path) -> None:
tpl = tmp_path / "pyproject.toml"
tpl.write_text('dependencies = [\n "crewai==1.0.0"\n]\n')
updated = update_template_dependencies(tmp_path, "3.0.0")
assert len(updated) == 1
assert '"crewai==3.0.0"' in tpl.read_text()
def test_skips_unrelated_deps(self, tmp_path: Path) -> None:
tpl = tmp_path / "pyproject.toml"
tpl.write_text('dependencies = [\n "requests>=2.0"\n]\n')
updated = update_template_dependencies(tmp_path, "2.0.0")
assert len(updated) == 0
assert '"requests>=2.0"' in tpl.read_text()