many tests patched sys.platform or a module's _IS_WINDOWS flag, then ran on linux ci. the patch selects the branch under test, but the host does not have the behavior the branch exists for. the test proves the patch, not the platform. some gated assertions never ran on any host. this commit adds three markers: linux_only, macos_only, windows_only. a conftest hook skips a marked test on the other hosts, with a clear reason. no test fakes a host now. two documented fakes remain (android/termux, freebsd) because no ci runner exists for them. each fake site got one of four treatments: - gate it: the real host supplies the platform; mocks cover real dependencies only, never host identity - patch the module's own probe when the subject is the probe's consumer - assert against the real host when the fake stood in for any non-x host - delete the patch when it set the value the host already has bare skipif(sys.platform != ...) guards became markers too. the lane model skips these on linux and never imports them on windows, so they ran on no host. platform parametrize tables are now one marked test per os. running on real hosts found real errors: a chrome-sandbox failure in test_gui_command that main hides, and two windows failures fixed here. the agents.md testing section now documents the policy.
107 lines
4.0 KiB
Python
107 lines
4.0 KiB
Python
"""Tests for the skill_view path boundary check.
|
|
|
|
Regression test: the original check used a hardcoded "/" separator which
|
|
fails on Windows where Path.resolve() returns backslash-separated paths.
|
|
Now uses Path.is_relative_to() which handles all platforms correctly.
|
|
"""
|
|
|
|
import pytest
|
|
from pathlib import Path
|
|
|
|
|
|
def _path_escapes_skill_dir(resolved: Path, skill_dir_resolved: Path) -> bool:
|
|
"""Reproduce the boundary check from tools/skills_tool.py.
|
|
|
|
Returns True when the resolved path is OUTSIDE the skill directory.
|
|
"""
|
|
return not resolved.is_relative_to(skill_dir_resolved)
|
|
|
|
|
|
class TestSkillViewPathBoundaryCheck:
|
|
"""Verify the path boundary check works on all platforms."""
|
|
|
|
def test_valid_subpath_allowed(self, tmp_path):
|
|
"""A file inside the skill directory must NOT be flagged."""
|
|
skill_dir = tmp_path / "skills" / "axolotl"
|
|
ref_file = skill_dir / "references" / "api.md"
|
|
skill_dir.mkdir(parents=True)
|
|
ref_file.parent.mkdir()
|
|
ref_file.write_text("content")
|
|
|
|
resolved = ref_file.resolve()
|
|
skill_dir_resolved = skill_dir.resolve()
|
|
|
|
assert _path_escapes_skill_dir(resolved, skill_dir_resolved) is False
|
|
|
|
|
|
def test_outside_path_blocked(self, tmp_path):
|
|
"""A file outside the skill directory must be flagged."""
|
|
skill_dir = tmp_path / "skills" / "axolotl"
|
|
skill_dir.mkdir(parents=True)
|
|
outside_file = tmp_path / "secret.env"
|
|
outside_file.write_text("SECRET=123")
|
|
|
|
resolved = outside_file.resolve()
|
|
skill_dir_resolved = skill_dir.resolve()
|
|
|
|
assert _path_escapes_skill_dir(resolved, skill_dir_resolved) is True
|
|
|
|
def test_sibling_skill_dir_blocked(self, tmp_path):
|
|
"""A file in a sibling skill directory must be flagged.
|
|
|
|
This catches prefix confusion: 'axolotl-v2' starts with 'axolotl'
|
|
as a string but is a different directory.
|
|
"""
|
|
skill_dir = tmp_path / "skills" / "axolotl"
|
|
sibling_dir = tmp_path / "skills" / "axolotl-v2"
|
|
skill_dir.mkdir(parents=True)
|
|
sibling_dir.mkdir(parents=True)
|
|
sibling_file = sibling_dir / "SKILL.md"
|
|
sibling_file.write_text("other skill")
|
|
|
|
resolved = sibling_file.resolve()
|
|
skill_dir_resolved = skill_dir.resolve()
|
|
|
|
assert _path_escapes_skill_dir(resolved, skill_dir_resolved) is True
|
|
|
|
def test_skill_dir_itself_allowed(self, tmp_path):
|
|
"""Requesting the skill directory itself must be allowed."""
|
|
skill_dir = tmp_path / "skills" / "axolotl"
|
|
skill_dir.mkdir(parents=True)
|
|
|
|
resolved = skill_dir.resolve()
|
|
skill_dir_resolved = skill_dir.resolve()
|
|
|
|
assert _path_escapes_skill_dir(resolved, skill_dir_resolved) is False
|
|
|
|
|
|
class TestOldCheckWouldFail:
|
|
"""Demonstrate the bug: the old hardcoded '/' check fails on Windows."""
|
|
|
|
def _old_path_escapes(self, resolved: Path, skill_dir_resolved: Path) -> bool:
|
|
"""The BROKEN check that used hardcoded '/'."""
|
|
return (
|
|
not str(resolved).startswith(str(skill_dir_resolved) + "/")
|
|
and resolved != skill_dir_resolved
|
|
)
|
|
|
|
# ``windows_only`` rather than ``skipif(os.sep == "/")``: the Windows CI
|
|
# job greps for the marker to decide which files to import, so a bare
|
|
# skipif leaves this running on no host at all.
|
|
@pytest.mark.windows_only
|
|
def test_old_check_false_positive_on_windows(self, tmp_path):
|
|
"""On Windows, the old check incorrectly blocks valid subpaths."""
|
|
skill_dir = tmp_path / "skills" / "axolotl"
|
|
ref_file = skill_dir / "references" / "api.md"
|
|
skill_dir.mkdir(parents=True)
|
|
ref_file.parent.mkdir()
|
|
ref_file.write_text("content")
|
|
|
|
resolved = ref_file.resolve()
|
|
skill_dir_resolved = skill_dir.resolve()
|
|
|
|
# Old check says it escapes (WRONG on Windows)
|
|
assert self._old_path_escapes(resolved, skill_dir_resolved) is True
|
|
# New check correctly allows it
|
|
assert _path_escapes_skill_dir(resolved, skill_dir_resolved) is False
|