|
17 | 17 | import asyncio |
18 | 18 | import builtins |
19 | 19 | import io |
| 20 | +import pathlib |
20 | 21 | import struct |
21 | 22 | import sys |
22 | 23 | import threading |
@@ -82,6 +83,70 @@ def test__load_skill_from_dir(tmp_path): |
82 | 83 | assert skill.resources.get_script("script1.sh").src == "echo hello" |
83 | 84 |
|
84 | 85 |
|
| 86 | +def _write_nested_skill(tmp_path): |
| 87 | + """Writes a skill whose resources live in subdirectories.""" |
| 88 | + skill_dir = tmp_path / "nested-skill" |
| 89 | + skill_dir.mkdir() |
| 90 | + (skill_dir / "SKILL.md").write_text("""--- |
| 91 | +name: nested-skill |
| 92 | +description: Test description |
| 93 | +--- |
| 94 | +Test instructions |
| 95 | +""") |
| 96 | + |
| 97 | + scripts_dir = skill_dir / "scripts" / "runtime" |
| 98 | + scripts_dir.mkdir(parents=True) |
| 99 | + (scripts_dir / "helper.py").write_text("helper source") |
| 100 | + |
| 101 | + ref_dir = skill_dir / "references" / "deep" / "deeper" |
| 102 | + ref_dir.mkdir(parents=True) |
| 103 | + (ref_dir / "note.md").write_text("nested note") |
| 104 | + |
| 105 | + assets_dir = skill_dir / "assets" / "templates" |
| 106 | + assets_dir.mkdir(parents=True) |
| 107 | + (assets_dir / "tmpl.txt").write_text("template body") |
| 108 | + |
| 109 | + return skill_dir |
| 110 | + |
| 111 | + |
| 112 | +def test__load_skill_from_dir_nested_resources_use_forward_slash_keys(tmp_path): |
| 113 | + """Resources in subdirectories are keyed with forward slashes.""" |
| 114 | + skill = _load_skill_from_dir(_write_nested_skill(tmp_path)) |
| 115 | + |
| 116 | + assert skill.resources.get_script("runtime/helper.py").src == "helper source" |
| 117 | + assert skill.resources.get_reference("deep/deeper/note.md") == "nested note" |
| 118 | + assert skill.resources.get_asset("templates/tmpl.txt") == "template body" |
| 119 | + |
| 120 | + |
| 121 | +def test__load_skill_from_dir_nested_resources_on_windows_paths(tmp_path): |
| 122 | + """Windows-style separators still produce forward-slash keys. |
| 123 | +
|
| 124 | + Regression test for the Windows-only defect where `_load_dir` keyed resources |
| 125 | + with `str(relative_path)`. On Windows that is backslash-separated, while |
| 126 | + callers such as `load_skill_resource` look resources up with forward slashes, |
| 127 | + so every resource in a subdirectory was unreachable. |
| 128 | +
|
| 129 | + The bug cannot reproduce on a POSIX test runner, where `str()` already yields |
| 130 | + forward slashes, so the Windows flavour of `relative_to` is simulated here. |
| 131 | +
|
| 132 | + Args: |
| 133 | + tmp_path: pytest fixture providing a temporary directory. |
| 134 | + """ |
| 135 | + skill_dir = _write_nested_skill(tmp_path) |
| 136 | + real_relative_to = pathlib.Path.relative_to |
| 137 | + |
| 138 | + def windows_relative_to(self, *args, **kwargs): |
| 139 | + return pathlib.PureWindowsPath(real_relative_to(self, *args, **kwargs)) |
| 140 | + |
| 141 | + with mock.patch.object(pathlib.Path, "relative_to", windows_relative_to): |
| 142 | + skill = _load_skill_from_dir(skill_dir) |
| 143 | + |
| 144 | + assert list(skill.resources.scripts) == ["runtime/helper.py"] |
| 145 | + assert skill.resources.get_script("runtime/helper.py").src == "helper source" |
| 146 | + assert skill.resources.get_reference("deep/deeper/note.md") == "nested note" |
| 147 | + assert skill.resources.get_asset("templates/tmpl.txt") == "template body" |
| 148 | + |
| 149 | + |
85 | 150 | def test_allowed_tools_yaml_key(tmp_path): |
86 | 151 | """Tests that allowed-tools YAML key loads correctly.""" |
87 | 152 | skill_dir = tmp_path / "my-skill" |
|
0 commit comments