feat: deliver installable local alpha sessions
Some checks failed
tamq-ci / test (push) Failing after 6s
Some checks failed
tamq-ci / test (push) Failing after 6s
Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a03397-4d51-7fd1-8ff2-946eb22ea2bc
This commit is contained in:
parent
d56bd3c79f
commit
5774fbd548
18 changed files with 890 additions and 94 deletions
|
|
@ -1,4 +1,6 @@
|
|||
from tamq.cli import main
|
||||
import json
|
||||
|
||||
from tamq.cli import build_parser, main
|
||||
import pytest
|
||||
|
||||
|
||||
|
|
@ -13,6 +15,122 @@ def test_help_and_version(capsys):
|
|||
|
||||
def test_attach_delegates_to_tmux(monkeypatch):
|
||||
calls = []
|
||||
monkeypatch.delenv("TMUX", raising=False)
|
||||
monkeypatch.setattr("tamq.cli.subprocess.run", lambda args, check=False: calls.append((args, check)) or type("R", (), {"returncode": 0})())
|
||||
assert main(["attach", "--session", "demo"]) == 0
|
||||
assert calls == [(["tmux", "attach-session", "-t", "demo"], False)]
|
||||
|
||||
|
||||
def test_attach_switches_client_when_already_in_tmux(monkeypatch):
|
||||
calls = []
|
||||
monkeypatch.setenv("TMUX", "/tmp/tmux,1,0")
|
||||
monkeypatch.setattr("tamq.cli.subprocess.run", lambda args, check=False: calls.append(args) or type("R", (), {"returncode": 0})())
|
||||
assert main(["attach"]) == 0
|
||||
assert calls == [["tmux", "switch-client", "-t", "tamq"]]
|
||||
|
||||
|
||||
def test_start_parser_supports_command_and_cmd_alias():
|
||||
parser = build_parser()
|
||||
canonical = parser.parse_args(["start", "--command", "codex --quiet", "a", "b"])
|
||||
compatibility = parser.parse_args(["start", "--cmd", "claude", "a"])
|
||||
assert canonical.agent_command == "codex --quiet"
|
||||
assert canonical.repos == ["a", "b"]
|
||||
assert compatibility.agent_command == "claude"
|
||||
|
||||
|
||||
def test_detached_no_service_start_skips_socket_registration(monkeypatch, capsys):
|
||||
endpoint = type(
|
||||
"Endpoint",
|
||||
(),
|
||||
{
|
||||
"endpoint_id": "tmux-amq-42",
|
||||
"instance_key": "tmux-amq-42-boot",
|
||||
"pid": 42,
|
||||
"session": "tamq",
|
||||
"repos": ["a", "b"],
|
||||
},
|
||||
)()
|
||||
|
||||
class Manager:
|
||||
def preflight(self, repos, command):
|
||||
assert repos == ["a", "b"]
|
||||
assert command == "codex"
|
||||
return "plan"
|
||||
|
||||
def ensure_plan(self, plan):
|
||||
assert plan == "plan"
|
||||
return endpoint
|
||||
|
||||
monkeypatch.setattr("tamq.cli.TmuxManager", Manager)
|
||||
monkeypatch.setattr("tamq.cli.preflight_runtime_paths", lambda: None)
|
||||
monkeypatch.setattr("tamq.cli.request", lambda payload: pytest.fail("socket request must not run"))
|
||||
monkeypatch.setattr("tamq.cli.attach_session", lambda session: pytest.fail("detached start must not attach"))
|
||||
assert main(["start", "--detach", "--no-service", "a", "b"]) == 0
|
||||
summary = json.loads(capsys.readouterr().out)
|
||||
assert summary["service"] is False
|
||||
assert summary["messaging"] is False
|
||||
assert summary["repos"] == ["a", "b"]
|
||||
|
||||
|
||||
def test_interactive_no_service_start_attaches(monkeypatch, capsys):
|
||||
endpoint = type(
|
||||
"Endpoint",
|
||||
(),
|
||||
{
|
||||
"endpoint_id": "tmux-amq-42",
|
||||
"instance_key": "tmux-amq-42-boot",
|
||||
"session": "tamq",
|
||||
"repos": ["a"],
|
||||
},
|
||||
)()
|
||||
|
||||
class Manager:
|
||||
def preflight(self, repos, command):
|
||||
return "plan"
|
||||
|
||||
def ensure_plan(self, plan):
|
||||
return endpoint
|
||||
|
||||
attached = []
|
||||
monkeypatch.setattr("tamq.cli.TmuxManager", Manager)
|
||||
monkeypatch.setattr("tamq.cli.preflight_runtime_paths", lambda: None)
|
||||
monkeypatch.setattr("tamq.cli.attach_session", lambda session: attached.append(session) or 0)
|
||||
assert main(["start", "--no-service", "a"]) == 0
|
||||
assert attached == ["tamq"]
|
||||
assert json.loads(capsys.readouterr().out)["repos"] == ["a"]
|
||||
|
||||
|
||||
def test_registration_failure_rolls_back_created_windows(monkeypatch, capsys):
|
||||
endpoint = type(
|
||||
"Endpoint",
|
||||
(),
|
||||
{
|
||||
"endpoint_id": "tmux-amq-42",
|
||||
"instance_key": "tmux-amq-42-boot",
|
||||
"pid": 42,
|
||||
"session": "tamq",
|
||||
"repos": ["a"],
|
||||
},
|
||||
)()
|
||||
rolled_back = []
|
||||
|
||||
class Manager:
|
||||
def preflight(self, repos, command):
|
||||
return "plan"
|
||||
|
||||
def ensure_plan(self, plan):
|
||||
return endpoint
|
||||
|
||||
def rollback(self, value):
|
||||
rolled_back.append(value)
|
||||
|
||||
async def failed_registration(payload):
|
||||
return {"ok": False, "error": "denied"}
|
||||
|
||||
monkeypatch.setattr("tamq.cli.TmuxManager", Manager)
|
||||
monkeypatch.setattr("tamq.cli.preflight_runtime_paths", lambda: None)
|
||||
monkeypatch.setattr("tamq.cli.ensure_service", lambda: True)
|
||||
monkeypatch.setattr("tamq.cli.request", failed_registration)
|
||||
assert main(["start", "--detach", "a"]) == 1
|
||||
assert rolled_back == [endpoint]
|
||||
assert "endpoint registration failed: denied" in capsys.readouterr().err
|
||||
|
|
|
|||
|
|
@ -13,3 +13,12 @@ def test_endpoint_resolves_visible_pid_id(tmp_path):
|
|||
store = Store(tmp_path / "queue.sqlite3")
|
||||
store.register_endpoint("tmux-amq-1-boot", 1, "tamq", ["a"])
|
||||
assert store.endpoint("tmux-amq-1")["endpoint_id"] == "tmux-amq-1-boot"
|
||||
|
||||
|
||||
def test_endpoint_registration_retires_previous_instance_for_session(tmp_path):
|
||||
store = Store(tmp_path / "queue.sqlite3")
|
||||
store.register_endpoint("tmux-amq-1-old", 1, "tamq", ["a"])
|
||||
store.register_endpoint("tmux-amq-1-new", 1, "tamq", ["a", "b"])
|
||||
|
||||
assert store.endpoint("tmux-amq-1-old") is None
|
||||
assert [row["endpoint_id"] for row in store.endpoints()] == ["tmux-amq-1-new"]
|
||||
|
|
|
|||
180
tests/test_install_target.py
Normal file
180
tests/test_install_target.py
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
import json
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
def test_make_install_uses_isolated_uv_tool_install():
|
||||
makefile = Path("Makefile").read_text(encoding="utf-8")
|
||||
assert "install:" in makefile
|
||||
assert "uv tool install --force --reinstall --refresh ." in makefile
|
||||
assert "tamq --version" in makefile
|
||||
assert "uninstall:" in makefile
|
||||
assert "uv tool uninstall tmux-amq" in makefile
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
shutil.which("uv") is None or shutil.which("tmux") is None,
|
||||
reason="installed-package smoke requires uv and tmux",
|
||||
)
|
||||
def test_isolated_installed_tool_session_smoke(tmp_path):
|
||||
project = Path(__file__).resolve().parents[1]
|
||||
tool_dir = tmp_path / "tools"
|
||||
bin_dir = tmp_path / "bin"
|
||||
runtime_dir = tmp_path / "runtime"
|
||||
state_dir = tmp_path / "state"
|
||||
repo_a = tmp_path / "railiance-platform"
|
||||
repo_b = tmp_path / "activity-core"
|
||||
for path in (bin_dir, runtime_dir, repo_a, repo_b):
|
||||
path.mkdir(parents=True)
|
||||
|
||||
gita = bin_dir / "gita"
|
||||
gita.write_text(
|
||||
"#!/usr/bin/env python3\n"
|
||||
"import sys\n"
|
||||
f"paths = {{'railiance-platform': {str(repo_a)!r}, 'activity-core': {str(repo_b)!r}}}\n"
|
||||
"if sys.argv[1:] == ['ls']:\n"
|
||||
" print('railiance-platform activity-core')\n"
|
||||
"elif sys.argv[1:] == ['freeze']:\n"
|
||||
" for slug, path in paths.items(): print(f'local,{slug},{path}')\n"
|
||||
"else:\n"
|
||||
" raise SystemExit(2)\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
agent = bin_dir / "alpha-agent"
|
||||
agent.write_text(
|
||||
"#!/bin/sh\nprintf 'installed-alpha-ready\\n'\nexec sleep 30\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
gita.chmod(0o755)
|
||||
agent.chmod(0o755)
|
||||
|
||||
socket_name = f"tamq-installed-{os.getpid()}"
|
||||
env = os.environ.copy()
|
||||
env.update(
|
||||
{
|
||||
"PATH": f"{bin_dir}:{env['PATH']}",
|
||||
"UV_TOOL_DIR": str(tool_dir),
|
||||
"UV_TOOL_BIN_DIR": str(bin_dir),
|
||||
"UV_CACHE_DIR": str(tmp_path / "uv-cache"),
|
||||
"XDG_RUNTIME_DIR": str(runtime_dir),
|
||||
"TAMQ_STATE_DIR": str(state_dir),
|
||||
"TAMQ_TMUX_SOCKET": socket_name,
|
||||
}
|
||||
)
|
||||
tamq = bin_dir / "tamq"
|
||||
tmux = ["tmux", "-L", socket_name]
|
||||
|
||||
def run(*args: str) -> subprocess.CompletedProcess[str]:
|
||||
return subprocess.run(
|
||||
args,
|
||||
cwd=tmp_path,
|
||||
env=env,
|
||||
text=True,
|
||||
capture_output=True,
|
||||
check=True,
|
||||
timeout=30,
|
||||
)
|
||||
|
||||
try:
|
||||
subprocess.run(
|
||||
["uv", "tool", "install", "--force", "--reinstall", "--refresh", str(project)],
|
||||
cwd=project,
|
||||
env=env,
|
||||
text=True,
|
||||
capture_output=True,
|
||||
check=True,
|
||||
timeout=60,
|
||||
)
|
||||
assert run(str(tamq), "--version").stdout.strip() == "0.1.0"
|
||||
started = json.loads(
|
||||
run(
|
||||
str(tamq),
|
||||
"start",
|
||||
"--detach",
|
||||
"--command",
|
||||
"alpha-agent",
|
||||
"railiance-platform",
|
||||
"activity-core",
|
||||
).stdout
|
||||
)
|
||||
assert started["repos"] == ["railiance-platform", "activity-core"]
|
||||
rows = run(
|
||||
*tmux,
|
||||
"list-windows",
|
||||
"-t",
|
||||
"tamq",
|
||||
"-F",
|
||||
"#{window_name}|#{pane_current_path}|#{pane_pid}",
|
||||
).stdout.splitlines()
|
||||
parsed = {name: (path, pid) for name, path, pid in (row.split("|", 2) for row in rows)}
|
||||
assert parsed["railiance-platform"][0] == str(repo_a)
|
||||
assert parsed["activity-core"][0] == str(repo_b)
|
||||
|
||||
repeated = json.loads(
|
||||
run(
|
||||
str(tamq),
|
||||
"start",
|
||||
"--detach",
|
||||
"--command",
|
||||
"alpha-agent",
|
||||
"railiance-platform",
|
||||
"activity-core",
|
||||
).stdout
|
||||
)
|
||||
assert repeated["instance_id"] == started["instance_id"]
|
||||
repeated_rows = run(
|
||||
*tmux,
|
||||
"list-windows",
|
||||
"-t",
|
||||
"tamq",
|
||||
"-F",
|
||||
"#{window_name}|#{pane_current_path}|#{pane_pid}",
|
||||
).stdout.splitlines()
|
||||
assert repeated_rows == rows
|
||||
pre_delivery_status = json.loads(run(str(tamq), "status").stdout)
|
||||
assert pre_delivery_status["service"] is True
|
||||
assert [item["endpoint_id"] for item in pre_delivery_status["endpoints"]] == [
|
||||
started["instance_id"]
|
||||
]
|
||||
|
||||
message_id = run(
|
||||
str(tamq), "send", "@activity-core:", "installed-message"
|
||||
).stdout.strip()
|
||||
deadline = time.monotonic() + 5
|
||||
history = []
|
||||
while time.monotonic() < deadline:
|
||||
history = [
|
||||
json.loads(line)
|
||||
for line in run(str(tamq), "history", "--repo", "activity-core").stdout.splitlines()
|
||||
]
|
||||
if history and history[-1]["state"] == "injected":
|
||||
break
|
||||
time.sleep(0.05)
|
||||
assert history[-1]["message_id"] == message_id
|
||||
assert history[-1]["state"] == "injected"
|
||||
status = json.loads(run(str(tamq), "status").stdout)
|
||||
assert status["service"] is True
|
||||
assert [item["endpoint_id"] for item in status["endpoints"]] == [started["instance_id"]]
|
||||
assert run(str(tamq), "stop").returncode == 0
|
||||
finally:
|
||||
subprocess.run(
|
||||
[str(tamq), "stop"],
|
||||
cwd=tmp_path,
|
||||
env=env,
|
||||
text=True,
|
||||
capture_output=True,
|
||||
check=False,
|
||||
timeout=5,
|
||||
)
|
||||
subprocess.run(
|
||||
[*tmux, "kill-server"],
|
||||
env=env,
|
||||
text=True,
|
||||
capture_output=True,
|
||||
check=False,
|
||||
)
|
||||
113
tests/test_tmux_integration.py
Normal file
113
tests/test_tmux_integration.py
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
from uuid import uuid4
|
||||
|
||||
import pytest
|
||||
|
||||
from tamq.broker import BrokerIdentity, InputBroker
|
||||
from tamq.control import ControlModeClient
|
||||
from tamq.store import Store
|
||||
from tamq.tmux import LaunchPlan, TmuxManager
|
||||
|
||||
|
||||
@pytest.mark.skipif(shutil.which("tmux") is None, reason="tmux is not installed")
|
||||
def test_real_tmux_starts_two_repo_windows_and_reuses_them(tmp_path, monkeypatch):
|
||||
repo_a = tmp_path / "railiance-platform"
|
||||
repo_b = tmp_path / "activity-core"
|
||||
repo_a.mkdir()
|
||||
repo_b.mkdir()
|
||||
project_src = str(Path(__file__).resolve().parents[1] / "src")
|
||||
existing_pythonpath = os.environ.get("PYTHONPATH")
|
||||
monkeypatch.setenv("PYTHONPATH", project_src if not existing_pythonpath else f"{project_src}:{existing_pythonpath}")
|
||||
monkeypatch.setenv("TAMQ_STATE_DIR", str(tmp_path / "state"))
|
||||
|
||||
socket_name = f"tamq-pytest-{os.getpid()}-{uuid4().hex[:8]}"
|
||||
session = f"tamq-test-{uuid4().hex[:8]}"
|
||||
manager = TmuxManager(
|
||||
session,
|
||||
tmux_command=("tmux", "-L", socket_name),
|
||||
tamq_command=(sys.executable, "-m", "tamq.cli"),
|
||||
)
|
||||
plan = LaunchPlan(
|
||||
("railiance-platform", "activity-core"),
|
||||
{"railiance-platform": str(repo_a), "activity-core": str(repo_b)},
|
||||
("sh", "-c", "printf 'alpha-ready\\n'; exec sleep 30"),
|
||||
)
|
||||
|
||||
try:
|
||||
endpoint = manager.ensure_plan(plan)
|
||||
assert endpoint.created_session is True
|
||||
assert endpoint.created_windows == ("railiance-platform", "activity-core")
|
||||
|
||||
rows = manager._run(
|
||||
"list-windows",
|
||||
"-t",
|
||||
session,
|
||||
"-F",
|
||||
"#{window_name}|#{pane_current_path}|#{pane_pid}",
|
||||
).splitlines()
|
||||
parsed = {name: (path, pid) for name, path, pid in (row.split("|", 2) for row in rows)}
|
||||
assert set(parsed) == {"railiance-platform", "activity-core"}
|
||||
assert parsed["railiance-platform"][0] == str(repo_a)
|
||||
assert parsed["activity-core"][0] == str(repo_b)
|
||||
|
||||
deadline = time.monotonic() + 5
|
||||
while time.monotonic() < deadline:
|
||||
captures = [
|
||||
manager._run("capture-pane", "-p", "-t", f"{session}:{repo}")
|
||||
for repo in plan.repos
|
||||
]
|
||||
if all("alpha-ready" in capture for capture in captures):
|
||||
break
|
||||
time.sleep(0.05)
|
||||
assert all("alpha-ready" in capture for capture in captures)
|
||||
|
||||
reused = manager.ensure_plan(plan)
|
||||
assert reused.created_session is False
|
||||
assert reused.created_windows == ()
|
||||
reused_rows = manager._run(
|
||||
"list-windows",
|
||||
"-t",
|
||||
session,
|
||||
"-F",
|
||||
"#{window_name}|#{pane_current_path}|#{pane_pid}",
|
||||
).splitlines()
|
||||
reused_parsed = {name: (path, pid) for name, path, pid in (row.split("|", 2) for row in reused_rows)}
|
||||
assert reused_parsed == parsed
|
||||
assert reused.instance_id == endpoint.instance_id
|
||||
|
||||
store = Store(tmp_path / "state" / "tamq.sqlite3")
|
||||
control = ControlModeClient(
|
||||
session,
|
||||
tmux_command=("tmux", "-L", socket_name),
|
||||
)
|
||||
try:
|
||||
message_id = store.add("local", "activity-core", "integration-message")
|
||||
control.start()
|
||||
delivered = InputBroker(
|
||||
store,
|
||||
BrokerIdentity(endpoint.instance_id, "railiance-platform"),
|
||||
).deliver_pending(
|
||||
control,
|
||||
window_for_repo=lambda repo: f"{session}:{repo}",
|
||||
)
|
||||
assert delivered == 1
|
||||
assert store.list(state="injected")[0]["message_id"] == message_id
|
||||
|
||||
deadline = time.monotonic() + 5
|
||||
while time.monotonic() < deadline:
|
||||
capture = manager._run(
|
||||
"capture-pane", "-p", "-t", f"{session}:activity-core"
|
||||
)
|
||||
if "#local: integration-message" in capture:
|
||||
break
|
||||
time.sleep(0.05)
|
||||
assert "#local: integration-message" in capture
|
||||
finally:
|
||||
control.close()
|
||||
store.close()
|
||||
finally:
|
||||
manager._run("kill-server", check=False)
|
||||
|
|
@ -1,11 +1,17 @@
|
|||
from tamq import tmux
|
||||
import pytest
|
||||
|
||||
|
||||
def test_tmux_manager_builds_tap_windows(monkeypatch):
|
||||
def test_tmux_manager_builds_tap_windows(tmp_path, monkeypatch):
|
||||
calls = []
|
||||
manager = tmux.TmuxManager("tamq-test")
|
||||
repo_a = tmp_path / "a"
|
||||
repo_b = tmp_path / "b"
|
||||
repo_a.mkdir()
|
||||
repo_b.mkdir()
|
||||
monkeypatch.setattr(tmux, "validate_targets", lambda repos: None)
|
||||
monkeypatch.setattr(tmux, "repository_paths", lambda: {"a": "/tmp/a", "b": "/tmp/b"})
|
||||
monkeypatch.setattr(tmux, "repository_paths", lambda: {"a": str(repo_a), "b": str(repo_b)})
|
||||
monkeypatch.setattr(tmux.shutil, "which", lambda command: f"/bin/{command}")
|
||||
def run(*args, check=True):
|
||||
calls.append(args)
|
||||
if args[:2] == ("list-windows", "-t"):
|
||||
|
|
@ -15,6 +21,95 @@ def test_tmux_manager_builds_tap_windows(monkeypatch):
|
|||
return ""
|
||||
monkeypatch.setattr(manager, "_run", run)
|
||||
monkeypatch.setattr(tmux.subprocess, "run", lambda *args, **kwargs: type("R", (), {"returncode": 1})())
|
||||
endpoint = manager.ensure(["a", "b"], "codex")
|
||||
endpoint = manager.ensure(["a", "b"], "codex --quiet")
|
||||
assert endpoint.endpoint_id == "tmux-amq-42"
|
||||
assert any("tamq tap" in " ".join(call) for call in calls if call and call[0] == "send-keys")
|
||||
assert endpoint.instance_id.startswith("tmux-amq-42-")
|
||||
assert endpoint.repos == ["a", "b"]
|
||||
new_session = next(call for call in calls if call and call[0] == "new-session")
|
||||
assert new_session[-2:] == ("-c", str(repo_a))
|
||||
assert "sleep" not in new_session
|
||||
new_window = next(call for call in calls if call and call[0] == "new-window")
|
||||
assert new_window[-2:] == ("-c", str(repo_b))
|
||||
tap_calls = [call for call in calls if call and call[0] == "send-keys"]
|
||||
assert len(tap_calls) == 2
|
||||
assert all("tamq tap" in " ".join(call) for call in tap_calls)
|
||||
assert all("codex --quiet" in " ".join(call) for call in tap_calls)
|
||||
assert ("select-window", "-t", "tamq-test:a") in calls
|
||||
assert any(call and call[0] == "set-option" for call in calls)
|
||||
|
||||
|
||||
def test_tmux_manager_reuses_session_instance_id(tmp_path, monkeypatch):
|
||||
repo = tmp_path / "a"
|
||||
repo.mkdir()
|
||||
manager = tmux.TmuxManager("tamq-test")
|
||||
options = {"@tamq_managed": "1"}
|
||||
monkeypatch.setattr(tmux, "validate_targets", lambda repos: None)
|
||||
monkeypatch.setattr(tmux, "repository_paths", lambda: {"a": str(repo)})
|
||||
monkeypatch.setattr(tmux.shutil, "which", lambda command: f"/bin/{command}")
|
||||
|
||||
def run(*args, check=True):
|
||||
if args[:2] == ("display-message", "-p"):
|
||||
return "42"
|
||||
if args and args[0] == "show-options":
|
||||
return options.get(args[-1], "")
|
||||
if args and args[0] == "set-option":
|
||||
options[args[-2]] = args[-1]
|
||||
if args[:2] == ("list-windows", "-t"):
|
||||
return "a"
|
||||
return ""
|
||||
|
||||
monkeypatch.setattr(manager, "_run", run)
|
||||
monkeypatch.setattr(
|
||||
tmux.subprocess,
|
||||
"run",
|
||||
lambda *args, **kwargs: type("R", (), {"returncode": 0})(),
|
||||
)
|
||||
|
||||
first = manager.ensure(["a"])
|
||||
second = manager.ensure(["a"])
|
||||
assert first.instance_id == second.instance_id
|
||||
|
||||
|
||||
def test_preflight_rejects_foreign_session(tmp_path, monkeypatch):
|
||||
repo = tmp_path / "a"
|
||||
repo.mkdir()
|
||||
manager = tmux.TmuxManager("tamq-test")
|
||||
monkeypatch.setattr(tmux, "validate_targets", lambda repos: None)
|
||||
monkeypatch.setattr(tmux, "repository_paths", lambda: {"a": str(repo)})
|
||||
monkeypatch.setattr(tmux.shutil, "which", lambda command: f"/bin/{command}")
|
||||
monkeypatch.setattr(
|
||||
tmux.subprocess,
|
||||
"run",
|
||||
lambda *args, **kwargs: type("R", (), {"returncode": 0})(),
|
||||
)
|
||||
|
||||
def run(*args, check=True):
|
||||
if args[:2] == ("display-message", "-p"):
|
||||
return "42"
|
||||
return ""
|
||||
|
||||
monkeypatch.setattr(manager, "_run", run)
|
||||
with pytest.raises(tmux.TmuxError, match="not managed by tamq"):
|
||||
manager.preflight(["a"])
|
||||
|
||||
|
||||
def test_preflight_rejects_missing_agent(tmp_path, monkeypatch):
|
||||
repo = tmp_path / "a"
|
||||
repo.mkdir()
|
||||
manager = tmux.TmuxManager("tamq-test")
|
||||
monkeypatch.setattr(tmux, "validate_targets", lambda repos: None)
|
||||
monkeypatch.setattr(tmux, "repository_paths", lambda: {"a": str(repo)})
|
||||
monkeypatch.setattr(tmux.shutil, "which", lambda command: None if command == "missing-agent" else f"/bin/{command}")
|
||||
with pytest.raises(tmux.TmuxError, match="agent command not found"):
|
||||
manager.preflight(["a"], "missing-agent")
|
||||
|
||||
|
||||
def test_preflight_deduplicates_repositories(tmp_path, monkeypatch):
|
||||
repo = tmp_path / "a"
|
||||
repo.mkdir()
|
||||
manager = tmux.TmuxManager("tamq-test")
|
||||
monkeypatch.setattr(tmux, "validate_targets", lambda repos: None)
|
||||
monkeypatch.setattr(tmux, "repository_paths", lambda: {"a": str(repo)})
|
||||
monkeypatch.setattr(tmux.shutil, "which", lambda command: f"/bin/{command}")
|
||||
plan = manager.preflight(["a", "a"], "codex")
|
||||
assert plan.repos == ("a",)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue