diff --git a/tests/test_cli/test_grants_cmd.py b/tests/test_cli/test_grants_cmd.py index fe7d9a6..e13892c 100644 --- a/tests/test_cli/test_grants_cmd.py +++ b/tests/test_cli/test_grants_cmd.py @@ -2,6 +2,7 @@ from __future__ import annotations +import json from typing import TYPE_CHECKING import pytest @@ -147,7 +148,10 @@ async def test_config_allow_paths_installed( ) -> None: outside = (tmp_path.parent / "outside-config").resolve() outside.mkdir(exist_ok=True) - document = tomlkit.parse(f'[agent]\nallow_paths = {{ "{outside}" = "read", "/does/not/exist" = "read" }}\n') + # JSON string escaping is TOML escaping too, so a Windows path's backslashes + # survive instead of reading as an escape (``\u``). + key = json.dumps(str(outside)) + document = tomlkit.parse(f'[agent]\nallow_paths = {{ {key} = "read", "/does/not/exist" = "read" }}\n') state, memory = await _new_state(tmp_path, document=document) try: assert state.instance_state.workspace.config_allow == {outside: AccessMode.READ} diff --git a/tests/test_cli/test_interrupt.py b/tests/test_cli/test_interrupt.py index d09e4e6..267bda6 100644 --- a/tests/test_cli/test_interrupt.py +++ b/tests/test_cli/test_interrupt.py @@ -3,6 +3,7 @@ from __future__ import annotations import asyncio import os import signal +import sys import threading import time from typing import TYPE_CHECKING @@ -35,6 +36,14 @@ if TYPE_CHECKING: import pytest as pytest_module +# The router is asyncio's SIGINT handler, which only exists where signals do; on +# Windows a test could not deliver one either — ``os.kill(pid, SIGINT)`` is +# ``TerminateProcess`` there, so it kills the runner instead of interrupting it. +pytestmark = pytest.mark.skipif( + sys.platform == "win32", + reason="SIGINT routing needs POSIX signal handling", +) + class FakeTarget: """Minimal ``InterruptTarget`` recording its cancels.""" diff --git a/tests/test_cli/test_skills_cmd.py b/tests/test_cli/test_skills_cmd.py index d7ac964..6e5becd 100644 --- a/tests/test_cli/test_skills_cmd.py +++ b/tests/test_cli/test_skills_cmd.py @@ -2,6 +2,7 @@ from __future__ import annotations +import json from typing import TYPE_CHECKING import pytest @@ -44,7 +45,9 @@ def _skill_notice_rows(state: ReplState) -> list[str]: def _config_doc(tmp_path: Path, *, paths: list[Path] | None = None, extra: str = "") -> tomlkit.TOMLDocument: lines = ["[skills]", "discover = []"] if paths: - rendered = ", ".join(f'"{path}"' for path in paths) + # JSON string escaping is TOML escaping too, so a Windows path's + # backslashes survive instead of reading as an escape (``\u``). + rendered = ", ".join(json.dumps(str(path)) for path in paths) lines.append(f"paths = [{rendered}]") if extra: lines.append(extra) @@ -215,12 +218,12 @@ async def test_skill_tools_are_offered_and_can_read_skill_files( skill_file = skills_state.skills.skills[0].skill_file if skills_state.skills else None assert skill_file is not None - read = await skills_state.agent.tools.execute("read_file", f'{{"path": "{skill_file}"}}') + read = await skills_state.agent.tools.execute("read_file", json.dumps({"path": str(skill_file)})) assert "Deploy the acme stack" in read denied = await skills_state.agent.tools.execute( "write_file", - f'{{"path": "{skill_file}", "content": "x"}}', + json.dumps({"path": str(skill_file), "content": "x"}), ) assert denied.startswith("error:") assert "escapes workspace root" in denied diff --git a/tests/test_skills/test_discovery.py b/tests/test_skills/test_discovery.py index 61e1013..6ca3061 100644 --- a/tests/test_skills/test_discovery.py +++ b/tests/test_skills/test_discovery.py @@ -1,13 +1,10 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from pathlib import Path from plyngent.config.models import SkillsConfig from plyngent.skills import SKILL_FILE, Skill, SkillStore, build_roots, validate_skill_name -if TYPE_CHECKING: - from pathlib import Path - def _write_skill(root: Path, name: str, *, description: str | None = "A test skill", body: str = "Body.") -> Path: directory = root / name @@ -28,12 +25,13 @@ def test_build_roots_orders_explicit_paths_first(tmp_path: Path) -> None: user_config_dir=tmp_path / "config", home=tmp_path / "home", ) - assert [(r.origin, r.scope, str(r.path)) for r in roots] == [ - ("config", "user", str(tmp_path / "home" / "team-skills")), - ("config", "user", "/srv/skills"), - ("plyngent", "user", str(tmp_path / "config" / "skills")), - ("claude", "user", str(tmp_path / "home" / ".claude" / "skills")), - ("claude", "project", str(tmp_path / "proj" / ".claude" / "skills")), + # Paths, not strings: a Windows run spells the same root differently. + assert [(r.origin, r.scope, r.path) for r in roots] == [ + ("config", "user", tmp_path / "home" / "team-skills"), + ("config", "user", Path("/srv/skills")), + ("plyngent", "user", tmp_path / "config" / "skills"), + ("claude", "user", tmp_path / "home" / ".claude" / "skills"), + ("claude", "project", tmp_path / "proj" / ".claude" / "skills"), ]