diff --git a/soup_cli/utils/data_pipeline.py b/soup_cli/utils/data_pipeline.py index b320dfe..3847acd 100644 --- a/soup_cli/utils/data_pipeline.py +++ b/soup_cli/utils/data_pipeline.py @@ -488,8 +488,10 @@ def load_pretokenized_dataset( real = os.path.realpath(tokenized_path) if not is_under_cwd(real): raise ValueError("tokenized_path must stay under cwd") + # Lstat the ORIGINAL (unresolved) path to detect symlinks at the entry + # point — mirrors v0.33.0 #22 / v0.43.0 Part C TOCTOU policy. try: - lst = os.lstat(real) + lst = os.lstat(tokenized_path) except OSError as exc: raise ValueError(f"tokenized_path not found: {tokenized_path!r}") from exc if _stat.S_ISLNK(lst.st_mode): diff --git a/soup_cli/utils/recipe_run.py b/soup_cli/utils/recipe_run.py index 3cebb8c..aae7673 100644 --- a/soup_cli/utils/recipe_run.py +++ b/soup_cli/utils/recipe_run.py @@ -164,12 +164,17 @@ def _redact_exc_message(exc: BaseException, limit: int = 256) -> str: lambda m: os.path.basename(m.group(0)) or m.group(0), msg, ) - # Windows drive-letter paths (``C:\\foo\\bar``). - msg = re.sub( - r"[A-Za-z]:[\\/][^\s:'\"]+", - lambda m: os.path.basename(m.group(0)) or m.group(0), - msg, - ) + # Windows drive-letter paths (``C:\\foo\\bar`` or ``C:/foo/bar``). + # Use a cross-platform basename: split on both ``/`` and ``\\`` so this + # works on POSIX hosts too (``os.path.basename`` only splits on ``/`` + # on POSIX, leaving ``\\``-separated components intact). + def _win_basename(m: "re.Match[str]") -> str: + raw = m.group(0) + # Split on either separator; last non-empty component is the basename. + parts = [p for p in re.split(r"[/\\]", raw) if p] + return parts[-1] if parts else raw + + msg = re.sub(r"[A-Za-z]:[\\/][^\s:'\"]+", _win_basename, msg) return msg[:limit] diff --git a/tests/test_v0537.py b/tests/test_v0537.py index 08c1e30..f67903b 100644 --- a/tests/test_v0537.py +++ b/tests/test_v0537.py @@ -891,6 +891,11 @@ def _create_test_app(): class TestToolEndpointsLive: + @pytest.fixture(autouse=True) + def _require_fastapi(self): + pytest.importorskip("fastapi") + pytest.importorskip("httpx") + def test_python_tool_runs_simple_code(self): from fastapi.testclient import TestClient app = _create_test_app() @@ -1041,6 +1046,11 @@ class TestToolEndpointsLive: class TestAnthropicMessagesStreaming: + @pytest.fixture(autouse=True) + def _require_fastapi(self): + pytest.importorskip("fastapi") + pytest.importorskip("httpx") + def test_stream_true_returns_sse(self): from fastapi.testclient import TestClient @@ -1425,6 +1435,11 @@ class TestReviewFixesForge: class TestReviewFixesVllmAnthropicLive: """v0.53.7 H-I + M-Q: live route tests with mocked AsyncLLMEngine.""" + @pytest.fixture(autouse=True) + def _require_fastapi(self): + pytest.importorskip("fastapi") + pytest.importorskip("httpx") + def _build_vllm_app(self): try: import fastapi # noqa: F401