Files
crewAI/lib/crewai/tests/skills/self_improve/test_cli_skills.py
2026-05-05 14:42:25 -07:00

104 lines
3.6 KiB
Python

"""Tests for ``crewai skills proposals`` CLI commands."""
from __future__ import annotations
from pathlib import Path
from unittest.mock import patch
from click.testing import CliRunner
import pytest
from crewai.cli.skills_proposals import skills as skills_group
from crewai.skills.self_improve.models import SkillProposal
from crewai.skills.self_improve.storage import ProposalStore, SkillStore
@pytest.fixture
def proposal() -> SkillProposal:
return SkillProposal(
agent_role="researcher",
name="cite-sources",
description="Always cite sources",
body="# Cite Sources\n\nbody.",
rationale="seen in 3 traces",
confidence=0.75,
)
@pytest.fixture
def runner_with_root(tmp_path: Path):
"""CliRunner with the self-improve root patched at the storage layer."""
runner = CliRunner()
proposal_store = ProposalStore(root=tmp_path)
skill_store = SkillStore(root=tmp_path)
with patch(
"crewai.cli.skills_proposals.ProposalStore", return_value=proposal_store
), patch(
"crewai.skills.self_improve.acceptance.ProposalStore",
return_value=proposal_store,
), patch(
"crewai.skills.self_improve.acceptance.SkillStore", return_value=skill_store
):
yield runner, proposal_store, skill_store
class TestList:
def test_empty(self, runner_with_root) -> None:
runner, _, _ = runner_with_root
result = runner.invoke(skills_group, ["proposals", "list"])
assert result.exit_code == 0
assert "no pending proposals" in result.output
def test_one_pending(self, runner_with_root, proposal: SkillProposal) -> None:
runner, ps, _ = runner_with_root
ps.save(proposal)
result = runner.invoke(skills_group, ["proposals", "list"])
assert result.exit_code == 0
assert proposal.id in result.output
assert "cite-sources" in result.output
class TestShow:
def test_unknown_id_exits_nonzero(self, runner_with_root) -> None:
runner, _, _ = runner_with_root
result = runner.invoke(skills_group, ["proposals", "show", "prop_does_not_exist"])
assert result.exit_code == 1
assert "No proposal" in result.output
def test_prints_body(self, runner_with_root, proposal: SkillProposal) -> None:
runner, ps, _ = runner_with_root
ps.save(proposal)
result = runner.invoke(skills_group, ["proposals", "show", proposal.id])
assert result.exit_code == 0
assert "# Cite Sources" in result.output
assert "rationale" in result.output
class TestAccept:
def test_writes_skill_md_and_clears_queue(
self, runner_with_root, proposal: SkillProposal
) -> None:
runner, ps, ss = runner_with_root
ps.save(proposal)
result = runner.invoke(skills_group, ["proposals", "accept", proposal.id])
assert result.exit_code == 0, result.output
assert ps.find(proposal.id) is None
assert (ss.skill_dir("researcher", "cite-sources") / "SKILL.md").is_file()
def test_unknown_id_exits_nonzero(self, runner_with_root) -> None:
runner, _, _ = runner_with_root
result = runner.invoke(skills_group, ["proposals", "accept", "prop_nope"])
assert result.exit_code == 1
class TestReject:
def test_removes_from_queue(self, runner_with_root, proposal: SkillProposal) -> None:
runner, ps, _ = runner_with_root
ps.save(proposal)
result = runner.invoke(skills_group, ["proposals", "reject", proposal.id])
assert result.exit_code == 0
assert ps.find(proposal.id) is None