from deerflow.config.skill_evolution_config import SkillEvolutionConfig def test_review_worker_config_defaults(): cfg = SkillEvolutionConfig() assert cfg.review_worker.enabled is False assert cfg.review_worker.min_tool_calls == 5 assert cfg.review_worker.timeout_seconds == 120 assert cfg.review_worker.model_name is None assert cfg.review_worker.max_messages == 60 def test_review_worker_config_from_dict(): cfg = SkillEvolutionConfig.model_validate({ "enabled": True, "review_worker": {"enabled": True, "min_tool_calls": 5, "model_name": "my-model"}, }) assert cfg.review_worker.enabled is True assert cfg.review_worker.min_tool_calls == 5 assert cfg.review_worker.model_name == "my-model" from unittest.mock import AsyncMock, MagicMock, patch import asyncio import pytest from langchain_core.messages import HumanMessage def _make_ai_message(tool_calls_data: list[dict]): msg = MagicMock() msg.__class__.__name__ = "AIMessage" tcs = [] for tc in tool_calls_data: m = MagicMock() m.name = tc["name"] m.args = tc.get("args", {}) tcs.append(m) msg.tool_calls = tcs return msg def test_extract_tool_call_count_counts_all(): from deerflow.skills.review_worker import extract_tool_call_count msgs = [ HumanMessage(content="do something"), _make_ai_message([{"name": "bash"}, {"name": "read_file"}]), _make_ai_message([{"name": "write_file"}]), ] assert extract_tool_call_count(msgs) == 3 def test_extract_tool_call_count_empty(): from deerflow.skills.review_worker import extract_tool_call_count assert extract_tool_call_count([]) == 0 def test_maybe_start_review_disabled_returns_false(): from deerflow.skills.review_worker import maybe_start_review cfg = MagicMock() cfg.skill_evolution.enabled = False cfg.skill_evolution.review_worker.enabled = True cfg.skill_evolution.review_worker.min_tool_calls = 2 msgs = [_make_ai_message([{"name": "bash"}, {"name": "read_file"}, {"name": "write_file"}])] assert maybe_start_review("thread-1", msgs, cfg) is False def test_maybe_start_review_too_few_tool_calls_returns_false(): from deerflow.skills.review_worker import maybe_start_review cfg = MagicMock() cfg.skill_evolution.enabled = True cfg.skill_evolution.review_worker.enabled = True cfg.skill_evolution.review_worker.min_tool_calls = 5 msgs = [_make_ai_message([{"name": "bash"}])] assert maybe_start_review("thread-4", msgs, cfg) is False def test_maybe_start_review_starts_task(): from deerflow.skills import review_worker as rw # Reset module state so thread-3 has no lock rw._locks.pop("thread-3", None) rw._pending.pop("thread-3", None) cfg = MagicMock() cfg.skill_evolution.enabled = True cfg.skill_evolution.review_worker.enabled = True cfg.skill_evolution.review_worker.min_tool_calls = 2 cfg.skill_evolution.review_worker.timeout_seconds = 30 cfg.skill_evolution.review_worker.max_messages = 20 cfg.skill_evolution.review_worker.model_name = None msgs = [_make_ai_message([{"name": "bash"}, {"name": "read_file"}, {"name": "write_file"}])] with patch("deerflow.skills.review_worker.asyncio") as mock_asyncio: fake_lock = MagicMock() fake_lock.locked.return_value = False mock_asyncio.Lock.return_value = fake_lock mock_asyncio.create_task = MagicMock() result = rw.maybe_start_review("thread-3", msgs, cfg) assert result is True mock_asyncio.create_task.assert_called_once()