From 26bb8d15c9b8fbc5515db44d53f399e17b817026 Mon Sep 17 00:00:00 2001 From: cfdaily Date: Sun, 17 May 2026 06:11:21 +0800 Subject: [PATCH] auto-sync: 2026-05-17 06:11:21 --- tests/test_skill_system.py | 223 +++++++++++++++++++++++++++++++++++++ 1 file changed, 223 insertions(+) create mode 100644 tests/test_skill_system.py diff --git a/tests/test_skill_system.py b/tests/test_skill_system.py new file mode 100644 index 0000000..e262025 --- /dev/null +++ b/tests/test_skill_system.py @@ -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"