|
| 1 | +"""Tests for openclaw_agent parameter handling in spawn backends.""" |
| 2 | + |
| 3 | +from __future__ import annotations |
| 4 | + |
| 5 | +from unittest.mock import MagicMock |
| 6 | + |
| 7 | + |
| 8 | +# --------------------------------------------------------------------------- |
| 9 | +# TmuxBackend tests |
| 10 | +# --------------------------------------------------------------------------- |
| 11 | + |
| 12 | +def _make_tmux_mocks(monkeypatch, captured: dict, *, tmux_ok: bool = True): |
| 13 | + """Patch tmux, shutil.which, register_agent, and time.sleep for TmuxBackend tests.""" |
| 14 | + monkeypatch.setattr("clawteam.spawn.tmux_backend.shutil.which", lambda name: "/usr/bin/tmux" if name == "tmux" else None) |
| 15 | + monkeypatch.setattr("clawteam.spawn.command_validation.shutil.which", lambda name, path=None: f"/usr/bin/{name}") |
| 16 | + |
| 17 | + def fake_run(cmd, **kwargs): |
| 18 | + result = MagicMock() |
| 19 | + result.returncode = 0 |
| 20 | + result.stdout = "pane-id\n" if "list-panes" in cmd else b"" |
| 21 | + result.stderr = b"" |
| 22 | + captured.setdefault("runs", []).append(cmd) |
| 23 | + if "new-session" in cmd or "new-window" in cmd: |
| 24 | + # Capture the full command string for assertion |
| 25 | + captured["spawn_cmd"] = cmd |
| 26 | + return result |
| 27 | + |
| 28 | + monkeypatch.setattr("clawteam.spawn.tmux_backend.subprocess.run", fake_run) |
| 29 | + monkeypatch.setattr("clawteam.spawn.tmux_backend.time.sleep", lambda _: None) |
| 30 | + monkeypatch.setattr("clawteam.spawn.registry.register_agent", lambda **_: None) |
| 31 | + monkeypatch.setattr("clawteam.spawn.tmux_backend._confirm_workspace_trust_if_prompted", lambda *a, **kw: False) |
| 32 | + |
| 33 | + |
| 34 | +def test_tmux_backend_includes_agent_flag_when_openclaw_agent_set(monkeypatch, capsys): |
| 35 | + """tmux_backend.spawn() with openclaw_agent='researcher' should include --agent researcher in command.""" |
| 36 | + from clawteam.spawn.tmux_backend import TmuxBackend |
| 37 | + |
| 38 | + captured: dict = {} |
| 39 | + _make_tmux_mocks(monkeypatch, captured) |
| 40 | + |
| 41 | + backend = TmuxBackend() |
| 42 | + backend.spawn( |
| 43 | + command=["openclaw"], |
| 44 | + agent_name="researcher", |
| 45 | + agent_id="agent-1", |
| 46 | + agent_type="general-purpose", |
| 47 | + team_name="test-team", |
| 48 | + prompt="hello world", |
| 49 | + openclaw_agent="researcher", |
| 50 | + ) |
| 51 | + |
| 52 | + # The spawn command (new-session or new-window) should contain --agent researcher |
| 53 | + spawn_cmd = captured.get("spawn_cmd", []) |
| 54 | + # The full shell command is the last element in the tmux new-session/new-window call |
| 55 | + full_shell_cmd = spawn_cmd[-1] if spawn_cmd else "" |
| 56 | + assert "--agent researcher" in full_shell_cmd, ( |
| 57 | + f"Expected '--agent researcher' in final command, got: {full_shell_cmd!r}" |
| 58 | + ) |
| 59 | + |
| 60 | + # Warning should be printed to stderr |
| 61 | + stderr_output = capsys.readouterr().err |
| 62 | + assert "openclaw_agent" in stderr_output or "--agent" in stderr_output |
| 63 | + |
| 64 | + |
| 65 | +def test_tmux_backend_excludes_agent_flag_when_not_set(monkeypatch): |
| 66 | + """tmux_backend.spawn() without openclaw_agent should not include --agent in the openclaw command.""" |
| 67 | + from clawteam.spawn.tmux_backend import TmuxBackend |
| 68 | + |
| 69 | + captured: dict = {} |
| 70 | + _make_tmux_mocks(monkeypatch, captured) |
| 71 | + |
| 72 | + backend = TmuxBackend() |
| 73 | + backend.spawn( |
| 74 | + command=["openclaw"], |
| 75 | + agent_name="worker", |
| 76 | + agent_id="agent-2", |
| 77 | + agent_type="general-purpose", |
| 78 | + team_name="test-team", |
| 79 | + prompt="hello world", |
| 80 | + openclaw_agent=None, |
| 81 | + ) |
| 82 | + |
| 83 | + spawn_cmd = captured.get("spawn_cmd", []) |
| 84 | + full_shell_cmd = spawn_cmd[-1] if spawn_cmd else "" |
| 85 | + # The exit hook always contains "--agent <name>" for lifecycle; we only want to |
| 86 | + # verify the openclaw command itself (before the ";") does not carry --agent. |
| 87 | + # Split on ";" to isolate the openclaw command portion. |
| 88 | + openclaw_part = full_shell_cmd.split(";") |
| 89 | + # Find the segment containing "openclaw tui" (the actual agent command) |
| 90 | + openclaw_cmd_segment = next( |
| 91 | + (seg for seg in openclaw_part if "openclaw" in seg and "lifecycle" not in seg), "" |
| 92 | + ) |
| 93 | + assert "--agent" not in openclaw_cmd_segment, ( |
| 94 | + f"Expected no '--agent' in openclaw command segment, got: {openclaw_cmd_segment!r}" |
| 95 | + ) |
| 96 | + |
| 97 | + |
| 98 | +# --------------------------------------------------------------------------- |
| 99 | +# SubprocessBackend tests |
| 100 | +# --------------------------------------------------------------------------- |
| 101 | + |
| 102 | +def test_subprocess_backend_raises_with_openclaw_agent(monkeypatch): |
| 103 | + """subprocess_backend.spawn() with openclaw_agent should raise NotImplementedError.""" |
| 104 | + import pytest |
| 105 | + from clawteam.spawn.subprocess_backend import SubprocessBackend |
| 106 | + |
| 107 | + backend = SubprocessBackend() |
| 108 | + with pytest.raises(NotImplementedError, match="subprocess backend"): |
| 109 | + backend.spawn( |
| 110 | + command=["codex"], |
| 111 | + agent_name="worker", |
| 112 | + agent_id="agent-3", |
| 113 | + agent_type="general-purpose", |
| 114 | + team_name="test-team", |
| 115 | + openclaw_agent="researcher", |
| 116 | + ) |
0 commit comments