Files
2026-06-05 11:03:30 +08:00

226 lines
7.6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""F16 Skill System 单元测试
按 test-plan-v2.6.md §F16
- T1: 注册/查询(P0
- T2: 三层自由度(P0
- T3: 技能匹配(P0
- T4: 模板变量替换(P1
"""
import json
import pytest
pytestmark = pytest.mark.unit
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"