"""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"