from unittest.mock import MagicMock from deerflow.community.aio_sandbox.local_backend import LocalContainerBackend, _format_container_mount def test_format_container_mount_uses_mount_syntax_for_docker_windows_paths(): args = _format_container_mount("docker", "D:/deer-flow/backend/.deer-flow/threads", "/mnt/threads", False) assert args == [ "--mount", "type=bind,src=D:/deer-flow/backend/.deer-flow/threads,dst=/mnt/threads", ] def test_format_container_mount_marks_docker_readonly_mounts(): args = _format_container_mount("docker", "/host/path", "/mnt/path", True) assert args == [ "--mount", "type=bind,src=/host/path,dst=/mnt/path,readonly", ] def test_format_container_mount_keeps_volume_syntax_for_apple_container(): args = _format_container_mount("container", "/host/path", "/mnt/path", True) assert args == [ "-v", "/host/path:/mnt/path:ro", ] # ── extra_env injection ────────────────────────────────────────────────────── def _make_backend(runtime: str = "docker") -> LocalContainerBackend: """Build a minimal LocalContainerBackend without real config.""" backend = LocalContainerBackend.__new__(LocalContainerBackend) backend._runtime = runtime backend._container_prefix = "test" backend._environment = {} backend._config_mounts = [] backend._base_port = 9000 backend._image = "test-image:latest" return backend def test_start_container_injects_extra_env(monkeypatch): """_start_container must append -e KEY=VALUE for each extra_env entry.""" backend = _make_backend() captured: list[list[str]] = [] def fake_run(cmd, **_kwargs): captured.append(list(cmd)) result = MagicMock() result.returncode = 0 result.stdout = "fake-container-id\n" return result monkeypatch.setattr("deerflow.community.aio_sandbox.local_backend.subprocess.run", fake_run) backend._start_container("c", 9000, extra_env={"THREAD_ID": "thread-abc", "FOO": "bar"}) cmd = captured[0] assert "-e" in cmd env_pairs = {cmd[i + 1] for i in range(len(cmd)) if cmd[i] == "-e"} assert "THREAD_ID=thread-abc" in env_pairs assert "FOO=bar" in env_pairs def test_start_container_no_extra_env_does_not_inject(monkeypatch): """_start_container with no extra_env must not add unexpected -e flags.""" backend = _make_backend() captured: list[list[str]] = [] def fake_run(cmd, **_kwargs): captured.append(list(cmd)) result = MagicMock() result.returncode = 0 result.stdout = "fake-container-id\n" return result monkeypatch.setattr("deerflow.community.aio_sandbox.local_backend.subprocess.run", fake_run) backend._start_container("c", 9000) cmd = captured[0] env_pairs = {cmd[i + 1] for i in range(len(cmd)) if cmd[i] == "-e"} assert all("THREAD_ID" not in pair for pair in env_pairs) def test_start_container_extra_env_overrides_static_env(monkeypatch): """Runtime extra_env values must appear after static env, effectively overriding same-key entries.""" backend = _make_backend() backend._environment = {"MY_VAR": "static"} captured: list[list[str]] = [] def fake_run(cmd, **_kwargs): captured.append(list(cmd)) result = MagicMock() result.returncode = 0 result.stdout = "fake-container-id\n" return result monkeypatch.setattr("deerflow.community.aio_sandbox.local_backend.subprocess.run", fake_run) backend._start_container("c", 9000, extra_env={"MY_VAR": "runtime"}) cmd = captured[0] env_pairs = [cmd[i + 1] for i in range(len(cmd)) if cmd[i] == "-e"] # Both entries should be present; the runtime one comes after, which Docker respects assert "MY_VAR=static" in env_pairs assert "MY_VAR=runtime" in env_pairs assert env_pairs.index("MY_VAR=runtime") > env_pairs.index("MY_VAR=static")