auto-sync: 2026-05-17 06:11:21

This commit is contained in:
cfdaily
2026-05-17 06:11:21 +08:00
parent b4d766d690
commit 26bb8d15c9
+223
View File
@@ -0,0 +1,223 @@
"""F16 Skill System 单元测试
按 test-plan-v2.6.md §F16
- T1: 注册/查询(P0
- T2: 三层自由度(P0
- T3: 技能匹配(P0
- T4: 模板变量替换(P1
"""
import json
import pytest
from pathlib import Path
from src.daemon.skill_system import (
Skill,
SkillExecutor,
SkillFreedom,
SkillRegistry,
)
# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------
@pytest.fixture
def registry():
return SkillRegistry()
@pytest.fixture
def registry_with_skills(registry):
registry.register(Skill(
id="code-review",
name="Code Review",
description="Review code quality and suggest improvements",
freedom=SkillFreedom.HIGH.value,
tags=["coding", "review"],
))
registry.register(Skill(
id="test-template",
name="Test Generator",
description="Generate test cases from code",
freedom=SkillFreedom.MEDIUM.value,
prompt_template="Write tests for {{module}} focusing on {{focus}}",
tags=["testing", "coding"],
))
registry.register(Skill(
id="deploy-script",
name="Deploy Script",
description="Run deployment script",
freedom=SkillFreedom.LOW.value,
script_path="/usr/local/bin/deploy.sh",
tags=["deployment"],
))
return registry
@pytest.fixture
def executor(registry_with_skills):
return SkillExecutor(registry=registry_with_skills)
@pytest.fixture
def skills_dir(tmp_path):
d = tmp_path / "skills"
d.mkdir()
(d / "skill1.json").write_text(json.dumps({
"id": "loaded-skill",
"name": "Loaded Skill",
"description": "Loaded from file",
"freedom": "high",
}))
(d / "skill2.json").write_text(json.dumps({
"id": "another",
"name": "Another",
"description": "Another skill",
"freedom": "medium",
"prompt_template": "Do {{action}}",
}))
return d
# ---------------------------------------------------------------------------
# T1: 注册/查询
# ---------------------------------------------------------------------------
class TestRegistry:
def test_register_and_get(self, registry):
skill = Skill(id="s1", name="Test", description="A test skill")
registry.register(skill)
assert registry.get("s1") is not None
assert registry.get("s1").name == "Test"
def test_unregister(self, registry):
registry.register(Skill(id="s1", name="T", description="D"))
assert registry.unregister("s1") is True
assert registry.get("s1") is None
def test_unregister_nonexistent(self, registry):
assert registry.unregister("nope") is False
def test_list_skills(self, registry_with_skills):
skills = registry_with_skills.list_skills()
assert len(skills) == 3
def test_list_by_freedom(self, registry_with_skills):
high = registry_with_skills.list_skills(freedom=SkillFreedom.HIGH.value)
assert len(high) == 1
assert high[0].id == "code-review"
def test_list_by_tags(self, registry_with_skills):
coding = registry_with_skills.list_skills(tags=["coding"])
assert len(coding) == 2
def test_list_enabled_only(self, registry):
registry.register(Skill(id="s1", name="On", description="D", enabled=True))
registry.register(Skill(id="s2", name="Off", description="D", enabled=False))
assert len(registry.list_skills()) == 1
def test_load_from_dir(self, skills_dir):
reg = SkillRegistry(skills_dir=skills_dir)
assert reg.count() == 2
assert reg.get("loaded-skill") is not None
# ---------------------------------------------------------------------------
# T2: 三层自由度
# ---------------------------------------------------------------------------
class TestFreedomLevels:
def test_high_freedom_prompt(self, executor):
result = executor.build_prompt("code-review")
assert "Skill: Code Review" in result
assert "Principle" in result
def test_medium_freedom_template(self, executor):
result = executor.build_prompt("test-template",
variables={"module": "auth.py", "focus": "edge cases"})
assert "auth.py" in result
assert "edge cases" in result
def test_low_freedom_script(self, executor):
result = executor.build_prompt("deploy-script")
assert "deploy.sh" in result
# ---------------------------------------------------------------------------
# T3: 技能匹配
# ---------------------------------------------------------------------------
class TestMatching:
def test_match_by_name(self, registry_with_skills):
results = registry_with_skills.match("review")
assert len(results) >= 1
assert results[0][0].id == "code-review"
assert results[0][1] > 0 # score > 0
def test_match_by_description(self, registry_with_skills):
results = registry_with_skills.match("deployment")
assert len(results) >= 1
def test_match_with_task_type(self, registry_with_skills):
results = registry_with_skills.match("test", task_type="testing")
assert len(results) >= 1
def test_match_no_results(self, registry_with_skills):
results = registry_with_skills.match("nonexistent_xyz_abc")
assert len(results) == 0
def test_match_limit(self, registry_with_skills):
results = registry_with_skills.match("code", limit=1)
assert len(results) <= 1
def test_match_excludes_disabled(self, registry):
registry.register(Skill(id="s1", name="Disabled Review",
description="review", enabled=False))
results = registry.match("review")
assert all(s.enabled for s, _ in results)
# ---------------------------------------------------------------------------
# T4: 模板变量替换 + 执行
# ---------------------------------------------------------------------------
class TestExecution:
def test_execute_high_freedom(self, executor):
result = executor.execute("code-review")
assert result["status"] == "success"
assert result["prompt"] is not None
def test_execute_medium_with_vars(self, executor):
result = executor.execute("test-template",
context={"module": "api", "focus": "errors"})
assert result["status"] == "success"
assert "api" in result["prompt"]
def test_execute_low_blocked(self, executor):
"""默认不允许脚本执行"""
result = executor.execute("deploy-script")
assert result["status"] == "error"
assert "not allowed" in result["error"]
def test_execute_low_allowed(self, registry_with_skills):
executor = SkillExecutor(registry=registry_with_skills, allow_scripts=True)
result = executor.execute("deploy-script")
assert result["status"] == "success"
def test_execute_not_found(self, executor):
result = executor.execute("nonexistent")
assert result["status"] == "error"
def test_execute_disabled(self, registry):
registry.register(Skill(id="s1", name="T", description="D", enabled=False))
executor = SkillExecutor(registry=registry)
result = executor.execute("s1")
assert result["status"] == "error"
assert "disabled" in result["error"]
def test_execution_log(self, executor):
executor.execute("code-review")
assert len(executor.execution_log) == 1
assert executor.execution_log[0]["skill_id"] == "code-review"