mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-10-26 21:26:39 +00:00
test: route four unguarded symlink fixtures through symlink_or_skip (#6619)
* test: route four unguarded symlink fixtures through symlink_or_skip `tests/support/symlinks.py` was added in #5414 so suites that need a real symlink skip cleanly when the host cannot create one. On a stock Windows host (no Developer Mode, not elevated) `Path.symlink_to` / `os.symlink` raise `OSError: [WinError 1314]`, and four fixtures still called them directly, so they reported hard errors instead of skipping: - tests/test_shared_reset_marker.py::test_marker_path_follows_a_symlinked_config - tests/test_view_image_middleware.py::test_host_image_copy_is_scoped_to_current_user_and_thread - tests/test_review_skill_package_tool.py::test_review_skill_package_rejects_symlink_planted_in_callers_outputs - tests/test_channel_file_attachments.py::TestInboundFileSandboxPerms::test_make_inbound_file_sandbox_readable_swap_after_lstat_does_not_follow_symlink These were the only symlink-related failures in a Windows run of the full backend suite; the other bare symlink call sites there are already guarded by a `skipif` or a `try/except OSError`. The view-image test plants its symlink after its scoping assertions, so those still run before the skip. The inbound-attachment test stages a symlink from inside a monkeypatched `os.lstat` to exercise a TOCTOU race; a host that cannot create a symlink cannot stage that race at all, so skipping is the only honest outcome. * test: make channel symlink race fixture portable --------- Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
This commit is contained in:
parent
db7340d2fa
commit
d37616e659
@ -585,6 +585,7 @@ class TestInboundFileSandboxPerms:
|
||||
# Best-effort helper: a missing file is a silent no-op.
|
||||
_make_inbound_file_sandbox_readable(tmp_path / "does-not-exist.txt")
|
||||
|
||||
@pytest.mark.skipif(os.name != "posix", reason="POSIX-only: Windows CRT chmod cannot express the 0600 mode this test asserts")
|
||||
def test_make_inbound_file_sandbox_readable_swap_after_lstat_does_not_follow_symlink(self, tmp_path, monkeypatch):
|
||||
from app.channels.manager import _make_inbound_file_sandbox_readable
|
||||
|
||||
@ -614,6 +615,12 @@ class TestInboundFileSandboxPerms:
|
||||
os.symlink(target, path)
|
||||
return st
|
||||
|
||||
# Staging the TOCTOU swap needs symlink creation; a host without the
|
||||
# privilege cannot stage the race at all, so skip before installing the hook.
|
||||
probe = tmp_path / ".symlink_probe"
|
||||
symlink_or_skip(probe, upload)
|
||||
probe.unlink()
|
||||
|
||||
monkeypatch.setattr(os, "lstat", racing_lstat)
|
||||
|
||||
_make_inbound_file_sandbox_readable(upload)
|
||||
|
||||
@ -4,6 +4,7 @@ from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
from support.symlinks import symlink_or_skip
|
||||
|
||||
from deerflow.config.paths import Paths, make_safe_user_id
|
||||
from deerflow.skills.storage.local_skill_storage import LocalSkillStorage
|
||||
@ -195,7 +196,7 @@ def test_review_skill_package_rejects_symlink_planted_in_callers_outputs(deploym
|
||||
outputs_dir = deployment.sandbox_outputs_dir("thread-1", user_id="alice")
|
||||
outputs_dir.mkdir(parents=True)
|
||||
link = outputs_dir / "borrowed-skill"
|
||||
link.symlink_to(victim_package, target_is_directory=True)
|
||||
symlink_or_skip(link, victim_package, target_is_directory=True)
|
||||
|
||||
command = review_skill_package.func(
|
||||
target=str(link),
|
||||
|
||||
@ -15,6 +15,7 @@ import json
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from support.symlinks import symlink_or_skip
|
||||
|
||||
import deerflow.config.shared_reset_marker as shared_reset_marker_module
|
||||
from deerflow.config.extensions_config import ExtensionsConfig
|
||||
@ -58,7 +59,7 @@ def test_marker_path_is_hidden_sibling_of_the_config(config_path: Path) -> None:
|
||||
def test_marker_path_follows_a_symlinked_config(tmp_path: Path, config_path: Path) -> None:
|
||||
link = tmp_path / "linked" / "extensions_config.json"
|
||||
link.parent.mkdir()
|
||||
link.symlink_to(config_path)
|
||||
symlink_or_skip(link, config_path)
|
||||
|
||||
assert SharedResetMarker("skills-cache-reset").path_for(link) == config_path.parent / ".extensions_config.json.skills-cache-reset.json"
|
||||
|
||||
|
||||
@ -33,6 +33,7 @@ from langchain.agents.middleware.types import ModelRequest
|
||||
from langchain_core.callbacks import BaseCallbackHandler
|
||||
from langchain_core.language_models.fake_chat_models import FakeMessagesListChatModel
|
||||
from langchain_core.messages import AIMessage, AnyMessage, HumanMessage, SystemMessage, ToolMessage
|
||||
from support.symlinks import symlink_or_skip
|
||||
|
||||
from deerflow.agents.middlewares.view_image_middleware import (
|
||||
_IMAGE_CONTEXT_MESSAGE_MARKER_KEY,
|
||||
@ -902,7 +903,7 @@ def test_host_image_copy_is_scoped_to_current_user_and_thread(tmp_path, monkeypa
|
||||
|
||||
alias_path = paths.sandbox_outputs_dir("thread-a", user_id="user-a") / "canary.png"
|
||||
alias_path.parent.mkdir(parents=True)
|
||||
alias_path.symlink_to(owner_path)
|
||||
symlink_or_skip(alias_path, owner_path)
|
||||
assert model_payloads("user-a", "thread-a", alias_path) == []
|
||||
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user