From e7082ea99fc502169287b86e043b8bf4260c19d8 Mon Sep 17 00:00:00 2001 From: HexLab98 Date: Tue, 7 Jul 2026 02:59:03 +0700 Subject: [PATCH] test(cron): cover absolute skill path normalization (#59824) Add unit tests for normalize_skill_lookup_name and a cron scheduler regression that absolute paths under the skills dir reach skill_view as relative lookups. --- tests/agent/test_skill_utils.py | 39 +++++++++++++++++++++++++++++++++ tests/cron/test_scheduler.py | 25 +++++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/tests/agent/test_skill_utils.py b/tests/agent/test_skill_utils.py index ab3cfe280ae..7fcc21eebcc 100644 --- a/tests/agent/test_skill_utils.py +++ b/tests/agent/test_skill_utils.py @@ -333,3 +333,42 @@ class TestSkillMatchesPlatformTermux: "agent.skill_utils.is_termux", return_value=False ): assert skill_matches_platform(fm) is True + + +class TestNormalizeSkillLookupName: + def test_relative_path_unchanged(self, tmp_path, monkeypatch): + from agent.skill_utils import normalize_skill_lookup_name + + monkeypatch.setattr("agent.skill_utils.get_skills_dir", lambda: tmp_path / "skills") + assert normalize_skill_lookup_name("foo/bar") == "foo/bar" + + def test_absolute_under_skills_dir_becomes_relative(self, tmp_path, monkeypatch): + from agent.skill_utils import normalize_skill_lookup_name + + skills_dir = tmp_path / "skills" + skill_dir = skills_dir / "category" / "my-skill" + skill_dir.mkdir(parents=True) + monkeypatch.setattr("agent.skill_utils.get_skills_dir", lambda: skills_dir) + assert normalize_skill_lookup_name(str(skill_dir)) == "category/my-skill" + + def test_absolute_via_symlink_uses_lexical_relative_path(self, tmp_path, monkeypatch): + from agent.skill_utils import normalize_skill_lookup_name + + skills_dir = tmp_path / "skills" + skills_dir.mkdir() + external = tmp_path / "external" / "my-skill" + external.mkdir(parents=True) + link = skills_dir / "my-skill" + try: + link.symlink_to(external) + except OSError: + pytest.skip("Symlinks not supported") + monkeypatch.setattr("agent.skill_utils.get_skills_dir", lambda: skills_dir) + assert normalize_skill_lookup_name(str(link)) == "my-skill" + + def test_untrusted_absolute_returned_unchanged(self, tmp_path, monkeypatch): + from agent.skill_utils import normalize_skill_lookup_name + + monkeypatch.setattr("agent.skill_utils.get_skills_dir", lambda: tmp_path / "skills") + outside = str(tmp_path / "outside" / "skill") + assert normalize_skill_lookup_name(outside) == outside diff --git a/tests/cron/test_scheduler.py b/tests/cron/test_scheduler.py index 775840ecec8..663efc87368 100644 --- a/tests/cron/test_scheduler.py +++ b/tests/cron/test_scheduler.py @@ -2885,6 +2885,31 @@ class TestBuildJobPromptMissingSkill: assert "go" in result +class TestBuildJobPromptAbsoluteSkillPath: + """Cron jobs may store absolute skill paths; normalize before skill_view.""" + + def test_absolute_skill_path_normalized_before_skill_view(self, tmp_path): + skills_dir = tmp_path / "skills" + skill_dir = skills_dir / "alpha-skill" + skill_dir.mkdir(parents=True) + (skill_dir / "SKILL.md").write_text("# Alpha\nDo alpha.") + absolute_path = str(skill_dir) + seen_names: list[str] = [] + + def _skill_view(name: str) -> str: + seen_names.append(name) + if name == "alpha-skill": + return json.dumps({"success": True, "content": "# Alpha\nDo alpha."}) + return json.dumps({"success": False, "error": f"Skill '{name}' not found."}) + + with patch("agent.skill_utils.get_skills_dir", return_value=skills_dir), \ + patch("tools.skills_tool.skill_view", side_effect=_skill_view): + result = _build_job_prompt({"skills": [absolute_path], "prompt": "go"}) + + assert seen_names == ["alpha-skill"] + assert "Do alpha." in result + + class TestBuildJobPromptBumpUse: """Verify that cron jobs bump skill usage counters so the curator sees them as active."""