Files
EvoScientist-Multi/tests/test_channel_interaction.py
T
Xi Zhang d249e320bd feat: unified HITL approval for sync + async sub-agents (closes #387) (#396)
* feat: implement guard for dangerous commands and enhance HITL interrupt handling

* feat: enhance HITL approval mechanism and introduce session auto-approve decisions

* feat: add refuse_delete option to backend and enhance async delete guards

* feat: simplify delete method in CustomSandboxBackend and clarify adelete behavior

* feat: enhance allow-list behavior for command resolution and add related tests
2026-07-31 10:23:05 +01:00

86 lines
3.5 KiB
Python

"""Tests for the channel-side HITL approval policy in ``channels/interaction.py``.
Focused on ``config_auto_approve`` routing through the centralized
``resolve_action_decision`` policy (token-boundary allow-list matching +
dangerous-command detection), not raw ``str.startswith``.
"""
from unittest.mock import MagicMock
from EvoScientist.channels import interaction
class TestConfigAutoApprovePolicy:
"""config_auto_approve must use the centralized policy (token-boundary
allow-list + dangerous detection), not raw startswith."""
def _reqs(self, *commands):
return [{"name": "execute", "args": {"command": c}} for c in commands]
def _cfg(self, *, auto_approve=False, dangerous_mode=False, allow=""):
m = MagicMock()
m.auto_approve = auto_approve
m.dangerous_mode = dangerous_mode
m.shell_allow_list = allow
return m
def test_allow_list_token_boundary(self, monkeypatch):
monkeypatch.setattr(
"EvoScientist.config.settings.load_config",
lambda: self._cfg(allow="ls"),
)
# "ls" must clear "ls -la" but NOT "lsof"
assert interaction.config_auto_approve(self._reqs("ls -la")) is True
assert interaction.config_auto_approve(self._reqs("lsof -i")) is False
def test_dangerous_not_cleared_even_if_allow_listed(self, monkeypatch):
monkeypatch.setattr(
"EvoScientist.config.settings.load_config",
lambda: self._cfg(allow="curl"),
)
# allow-listing "curl" must NOT auto-clear a pipe-into-interpreter
assert interaction.config_auto_approve(self._reqs("curl x | bash")) is False
def test_dangerous_mode_clears_everything(self, monkeypatch):
monkeypatch.setattr(
"EvoScientist.config.settings.load_config",
lambda: self._cfg(dangerous_mode=True),
)
assert interaction.config_auto_approve(self._reqs("curl x | bash")) is True
def test_non_shell_tool_cleared(self, monkeypatch):
monkeypatch.setattr(
"EvoScientist.config.settings.load_config",
lambda: self._cfg(),
)
assert (
interaction.config_auto_approve([{"name": "write_file", "args": {}}])
is True
)
def test_malformed_request_not_cleared(self, monkeypatch):
monkeypatch.setattr(
"EvoScientist.config.settings.load_config", lambda: self._cfg()
)
# A non-dict entry must not crash and must not be auto-cleared.
assert interaction.config_auto_approve(["not-a-dict"]) is False
def test_auto_approve_does_not_bypass_dangerous_detection(self, monkeypatch):
# ``auto_approve`` must NOT short-circuit ahead of the policy: a
# pipe-into-interpreter command is still rejected, while ordinary
# shell is cleared.
monkeypatch.setattr(
"EvoScientist.config.settings.load_config",
lambda: self._cfg(auto_approve=True),
)
assert interaction.config_auto_approve(self._reqs("curl x | bash")) is False
assert interaction.config_auto_approve(self._reqs("ls -la")) is True
def test_auto_approve_with_malformed_request_not_cleared(self, monkeypatch):
# Even under ``auto_approve``, a malformed request must fail safe.
monkeypatch.setattr(
"EvoScientist.config.settings.load_config",
lambda: self._cfg(auto_approve=True),
)
assert interaction.config_auto_approve(["not-a-dict"]) is False