Files
hermes-webui/tests/test_issue6240_windows_skill_symlink_fallback.py
T
Rod Boev 704e19adfe fix(#6240): fall back when test skills symlink is unavailable (#6276)
Fall back to a copytree (with read-only handling) when the test-server fixture can't create the skills symlink on native Windows without SeCreateSymbolicLinkPrivilege (WinError 1314). Test-infra only. Thanks @rodboev. Closes #6240.
2026-07-18 19:56:06 -07:00

123 lines
4.1 KiB
Python

from __future__ import annotations
import pathlib
import stat
import pytest
import tests.conftest as _conftest
def _make_skill_tree(root: pathlib.Path) -> None:
skill_dir = root / "demo-skill"
skill_dir.mkdir(parents=True)
(skill_dir / "SKILL.md").write_text("# Demo\n", encoding="utf-8")
def _fail_unexpected_call(message: str):
def _fail(*_args, **_kwargs):
raise AssertionError(message)
return _fail
def _assert_not_writable(path: pathlib.Path) -> None:
assert path.stat().st_mode & stat.S_IWUSR == 0
assert path.stat().st_mode & stat.S_IWGRP == 0
assert path.stat().st_mode & stat.S_IWOTH == 0
def test_seed_test_skills_uses_symlink_path_when_available(monkeypatch, tmp_path):
real_skills = tmp_path / "real-skills"
test_skills = tmp_path / "test-skills"
_make_skill_tree(real_skills)
seen = {}
def fake_symlink(self, target):
seen["args"] = (self, target)
self.mkdir(parents=True)
(self / "marker.txt").write_text("symlink path", encoding="utf-8")
monkeypatch.setattr(pathlib.Path, "symlink_to", fake_symlink)
monkeypatch.setattr(_conftest.shutil, "copytree", _fail_unexpected_call("copytree should not run"))
_conftest._seed_test_skills(real_skills, test_skills)
assert seen["args"] == (test_skills, real_skills)
assert (test_skills / "marker.txt").read_text(encoding="utf-8") == "symlink path"
def test_seed_test_skills_windows_symlink_error_falls_back_to_copy(monkeypatch, tmp_path):
real_skills = tmp_path / "real-skills"
test_skills = tmp_path / "test-skills"
_make_skill_tree(real_skills)
source_skill = real_skills / "demo-skill" / "SKILL.md"
source_mode = source_skill.stat().st_mode
def fail_symlink(self, target):
exc = OSError("[WinError 1314] A required privilege is not held by the client")
exc.winerror = 1314
raise exc
monkeypatch.setattr(pathlib.Path, "symlink_to", fail_symlink)
monkeypatch.setattr(_conftest, "WINDOWS", True)
_conftest._seed_test_skills(real_skills, test_skills)
assert test_skills.is_dir()
assert not test_skills.is_symlink()
copied_skill = test_skills / "demo-skill" / "SKILL.md"
assert copied_skill.read_text(encoding="utf-8") == "# Demo\n"
assert source_skill.stat().st_mode == source_mode
_assert_not_writable(copied_skill)
_conftest._rmtree_retry(test_skills)
assert not test_skills.exists()
def test_seed_test_skills_windows_non_privilege_error_reraises(monkeypatch, tmp_path):
real_skills = tmp_path / "real-skills"
test_skills = tmp_path / "test-skills"
_make_skill_tree(real_skills)
def fail_symlink(self, target):
exc = OSError("unexpected symlink failure")
exc.winerror = 5
raise exc
monkeypatch.setattr(pathlib.Path, "symlink_to", fail_symlink)
monkeypatch.setattr(_conftest, "WINDOWS", True)
with pytest.raises(OSError, match="unexpected symlink failure"):
_conftest._seed_test_skills(real_skills, test_skills)
def test_seed_test_skills_reraises_symlink_error_off_windows(monkeypatch, tmp_path):
real_skills = tmp_path / "real-skills"
test_skills = tmp_path / "test-skills"
_make_skill_tree(real_skills)
def fail_symlink(self, target):
raise OSError("symlink blocked")
monkeypatch.setattr(pathlib.Path, "symlink_to", fail_symlink)
monkeypatch.setattr(_conftest, "WINDOWS", False)
with pytest.raises(OSError, match="symlink blocked"):
_conftest._seed_test_skills(real_skills, test_skills)
def test_seed_test_skills_existing_target_is_noop(monkeypatch, tmp_path):
real_skills = tmp_path / "real-skills"
test_skills = tmp_path / "test-skills"
_make_skill_tree(real_skills)
test_skills.mkdir()
(test_skills / "existing.txt").write_text("keep", encoding="utf-8")
monkeypatch.setattr(pathlib.Path, "symlink_to", _fail_unexpected_call("symlink_to should not run"))
monkeypatch.setattr(_conftest.shutil, "copytree", _fail_unexpected_call("copytree should not run"))
_conftest._seed_test_skills(real_skills, test_skills)
assert (test_skills / "existing.txt").read_text(encoding="utf-8") == "keep"