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

166 lines
5.3 KiB
Python

"""Tests for self_improve/acceptance.py."""
from __future__ import annotations
from pathlib import Path
import pytest
from crewai.skills.self_improve.acceptance import (
_format_skill_md,
accept_proposal,
reject_proposal,
)
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="Senior Researcher",
name="cite-sources",
description="Always cite sources in research outputs",
body="# Cite Sources\n\n*Always cite sources.*\n\n## When to use\n\nWhenever you write a research summary.\n",
rationale="Seen in 3 of 4 traces",
confidence=0.8,
derived_from_runs=["run_a", "run_b", "run_c"],
)
@pytest.fixture
def stores(tmp_path: Path):
return SkillStore(root=tmp_path), ProposalStore(root=tmp_path)
class TestFormatSkillMd:
def test_includes_yaml_frontmatter(self, proposal: SkillProposal) -> None:
out = _format_skill_md(proposal)
assert out.startswith("---\n")
# Values are JSON-quoted (valid YAML scalars).
assert 'name: "cite-sources"' in out
assert '"Always cite sources in research outputs"' in out
assert "# Cite Sources" in out
def test_quotes_descriptions_with_special_chars(self) -> None:
prop = SkillProposal(
agent_role="r",
name="n",
description='Has a "quote" and: colon',
body="b",
rationale="r",
confidence=0.7,
)
out = _format_skill_md(prop)
# JSON-quoted: backslash-escapes the inner quote, retains the colon literally.
assert 'description: "Has a \\"quote\\" and: colon"' in out
def test_passes_through_body_with_existing_frontmatter(self) -> None:
prop = SkillProposal(
agent_role="r",
name="n",
description="d",
body="---\nname: n\n---\n# Body\n",
rationale="r",
confidence=0.7,
)
out = _format_skill_md(prop)
# No double frontmatter
assert out.count("---") == 2 # one open, one close
class TestAcceptProposal:
def test_writes_skill_md_at_expected_path(
self, stores, proposal: SkillProposal
) -> None:
skill_store, proposal_store = stores
proposal_store.save(proposal)
path = accept_proposal(
proposal,
skill_store=skill_store,
proposal_store=proposal_store,
)
assert path.name == "SKILL.md"
# role gets slugified
assert "senior-researcher" in str(path)
assert "cite-sources" in str(path)
body = path.read_text()
assert body.startswith("---\n")
assert "# Cite Sources" in body
def test_removes_proposal_from_queue_on_accept(
self, stores, proposal: SkillProposal
) -> None:
skill_store, proposal_store = stores
proposal_store.save(proposal)
assert proposal_store.find(proposal.id) is not None
accept_proposal(
proposal, skill_store=skill_store, proposal_store=proposal_store
)
assert proposal_store.find(proposal.id) is None
def test_refuses_to_overwrite_existing(
self, stores, proposal: SkillProposal
) -> None:
skill_store, proposal_store = stores
proposal_store.save(proposal)
accept_proposal(
proposal, skill_store=skill_store, proposal_store=proposal_store
)
# Re-save and re-accept the same proposal id (or a fresh one) → conflict
proposal_store.save(proposal)
with pytest.raises(FileExistsError):
accept_proposal(
proposal,
skill_store=skill_store,
proposal_store=proposal_store,
)
def test_force_overwrites(self, stores, proposal: SkillProposal) -> None:
skill_store, proposal_store = stores
proposal_store.save(proposal)
accept_proposal(
proposal, skill_store=skill_store, proposal_store=proposal_store
)
proposal_store.save(proposal)
path = accept_proposal(
proposal,
skill_store=skill_store,
proposal_store=proposal_store,
force=True,
)
assert path.exists()
class TestRejectProposal:
def test_removes_from_queue(self, stores, proposal: SkillProposal) -> None:
_, proposal_store = stores
proposal_store.save(proposal)
assert reject_proposal(proposal, proposal_store=proposal_store) is True
assert proposal_store.find(proposal.id) is None
def test_returns_false_when_missing(
self, stores, proposal: SkillProposal
) -> None:
_, proposal_store = stores
assert reject_proposal(proposal, proposal_store=proposal_store) is False
class TestSkillStore:
def test_has_any_detects_at_least_one_skill(
self, stores, proposal: SkillProposal
) -> None:
skill_store, proposal_store = stores
assert skill_store.has_any("Senior Researcher") is False
proposal_store.save(proposal)
accept_proposal(
proposal, skill_store=skill_store, proposal_store=proposal_store
)
assert skill_store.has_any("Senior Researcher") is True