Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a03397-4d51-7fd1-8ff2-946eb22ea2bc
This commit is contained in:
parent
9000640b2e
commit
9fcfba3c17
16 changed files with 415 additions and 80 deletions
|
|
@ -18,7 +18,7 @@ def test_root_help_exposes_repository_shorthand_and_command(capsys):
|
|||
main(["--help"])
|
||||
assert exc.value.code == 0
|
||||
output = capsys.readouterr().out
|
||||
assert "tamq [--detach] [--command COMMAND] REPO [REPO ...]" in output
|
||||
assert "tamq [--detach] [--command COMMAND] [--no-display] REPO" in output
|
||||
assert "With no --command" in output
|
||||
assert "@TARGET: MESSAGE" in output
|
||||
|
||||
|
|
@ -44,10 +44,12 @@ def test_start_parser_supports_command_and_cmd_alias():
|
|||
canonical = parser.parse_args(["start", "--command", "codex --quiet", "a", "b"])
|
||||
compatibility = parser.parse_args(["start", "--cmd", "claude", "a"])
|
||||
neutral = parser.parse_args(["start", "a", "b"])
|
||||
inbox_only = parser.parse_args(["start", "--no-display", "a"])
|
||||
assert canonical.initial_command == "codex --quiet"
|
||||
assert canonical.repos == ["a", "b"]
|
||||
assert compatibility.initial_command == "claude"
|
||||
assert neutral.initial_command is None
|
||||
assert inbox_only.no_display is True
|
||||
|
||||
|
||||
def test_repository_first_and_command_first_start_shorthand():
|
||||
|
|
@ -62,6 +64,11 @@ def test_repository_first_and_command_first_start_shorthand():
|
|||
"codex",
|
||||
"flex-auth",
|
||||
]
|
||||
assert normalize_argv(["--no-display", "flex-auth"]) == [
|
||||
"start",
|
||||
"--no-display",
|
||||
"flex-auth",
|
||||
]
|
||||
assert normalize_argv(["status"]) == ["status"]
|
||||
assert normalize_argv(["--verbose", "flex-auth", "audit-core"]) == [
|
||||
"--verbose",
|
||||
|
|
@ -165,7 +172,7 @@ def test_registration_failure_rolls_back_created_windows(monkeypatch, capsys):
|
|||
|
||||
monkeypatch.setattr("tamq.cli.TmuxManager", Manager)
|
||||
monkeypatch.setattr("tamq.cli.preflight_runtime_paths", lambda: None)
|
||||
monkeypatch.setattr("tamq.cli.ensure_manual_service", lambda: True)
|
||||
monkeypatch.setattr("tamq.cli.ensure_output_service", lambda: True)
|
||||
monkeypatch.setattr("tamq.cli.request", failed_registration)
|
||||
assert main(["start", "--detach", "a"]) == 1
|
||||
assert rolled_back == [endpoint]
|
||||
|
|
@ -177,3 +184,5 @@ def test_tap_requires_explicit_command_and_service(capsys):
|
|||
assert "--tap requires an explicit --command" in capsys.readouterr().err
|
||||
assert main(["start", "--tap", "--no-service", "--command", "sh", "a"]) == 2
|
||||
assert "--tap cannot be combined with --no-service" in capsys.readouterr().err
|
||||
assert main(["start", "--tap", "--no-display", "--command", "sh", "a"]) == 2
|
||||
assert "--tap cannot be combined with --no-display" in capsys.readouterr().err
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from tamq.cli import ensure_manual_service, parse_size
|
||||
from tamq.cli import ensure_manual_service, ensure_output_service, parse_size
|
||||
|
||||
|
||||
def test_parse_size():
|
||||
|
|
@ -22,3 +22,23 @@ def test_manual_service_restarts_legacy_broker(monkeypatch):
|
|||
assert ensure_manual_service() is True
|
||||
assert len(starts) == 2
|
||||
assert stops == [True]
|
||||
|
||||
|
||||
def test_output_service_requires_terminal_output_capability(monkeypatch):
|
||||
capabilities = iter([
|
||||
["manual_delivery"],
|
||||
["manual_delivery", "terminal_output"],
|
||||
])
|
||||
starts = []
|
||||
stops = []
|
||||
|
||||
async def request(payload):
|
||||
return {"capabilities": next(capabilities)}
|
||||
|
||||
monkeypatch.setattr("tamq.cli.ensure_service", lambda: starts.append(True) or True)
|
||||
monkeypatch.setattr("tamq.cli.stop_service_process", lambda: stops.append(True) or 42)
|
||||
monkeypatch.setattr("tamq.cli.request", request)
|
||||
|
||||
assert ensure_output_service() is True
|
||||
assert len(starts) == 2
|
||||
assert stops == [True]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
from tamq.control import shell_quote
|
||||
from tamq import control
|
||||
from tamq.control import ControlModeClient, shell_quote
|
||||
from tamq.tmux import shell_join
|
||||
|
||||
|
||||
|
|
@ -10,3 +11,19 @@ def test_shell_quote():
|
|||
|
||||
def test_tmux_shell_join_quotes_arguments():
|
||||
assert shell_join(["tamq", "tap", "--repo", "net-kingdom", "--", "codex"]) == "tamq tap --repo net-kingdom -- codex"
|
||||
|
||||
|
||||
def test_pane_tty_is_resolved_through_tmux(monkeypatch):
|
||||
calls = []
|
||||
|
||||
def run(command, **kwargs):
|
||||
calls.append(command)
|
||||
return type("Result", (), {"returncode": 0, "stdout": "/dev/pts/7\n", "stderr": ""})()
|
||||
|
||||
monkeypatch.setattr(control.subprocess, "run", run)
|
||||
client = ControlModeClient("tamq", tmux_command=("tmux", "-L", "test"))
|
||||
assert client.pane_tty("tamq:audit-core") == "/dev/pts/7"
|
||||
assert calls == [[
|
||||
"tmux", "-L", "test", "display-message", "-p", "-t",
|
||||
"tamq:audit-core", "#{pane_tty}",
|
||||
]]
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ def test_isolated_installed_tool_session_smoke(tmp_path):
|
|||
)
|
||||
assert run(str(tamq), "--version").stdout.strip() == "0.1.0"
|
||||
root_help = run(str(tamq), "--help").stdout
|
||||
assert "tamq [--detach] [--command COMMAND] REPO [REPO ...]" in root_help
|
||||
assert "tamq [--detach] [--command COMMAND] [--no-display] REPO" in root_help
|
||||
started = json.loads(
|
||||
run(
|
||||
str(tamq),
|
||||
|
|
@ -102,7 +102,7 @@ def test_isolated_installed_tool_session_smoke(tmp_path):
|
|||
).stdout
|
||||
)
|
||||
assert started["repos"] == ["railiance-platform", "activity-core"]
|
||||
assert started["delivery_mode"] == "manual"
|
||||
assert started["delivery_mode"] == "output"
|
||||
rows = run(
|
||||
*tmux,
|
||||
"list-windows",
|
||||
|
|
@ -168,7 +168,7 @@ def test_isolated_installed_tool_session_smoke(tmp_path):
|
|||
assert [item["endpoint_id"] for item in pre_delivery_status["endpoints"]] == [
|
||||
started["instance_id"]
|
||||
]
|
||||
assert pre_delivery_status["endpoints"][0]["delivery_mode"] == "manual"
|
||||
assert pre_delivery_status["endpoints"][0]["delivery_mode"] == "output"
|
||||
|
||||
target_before = run(
|
||||
*tmux, "capture-pane", "-p", "-t", "tamq:activity-core"
|
||||
|
|
@ -183,6 +183,7 @@ def test_isolated_installed_tool_session_smoke(tmp_path):
|
|||
)
|
||||
deadline = time.monotonic() + 5
|
||||
inbox = []
|
||||
target_after = target_before
|
||||
while time.monotonic() < deadline:
|
||||
inbox = [
|
||||
json.loads(line)
|
||||
|
|
@ -190,7 +191,10 @@ def test_isolated_installed_tool_session_smoke(tmp_path):
|
|||
str(tamq), "inbox", "--repo", "activity-core", "--json"
|
||||
).stdout.splitlines()
|
||||
]
|
||||
if inbox:
|
||||
target_after = run(
|
||||
*tmux, "capture-pane", "-p", "-t", "tamq:activity-core"
|
||||
).stdout
|
||||
if inbox and inbox[-1]["displayed_at"] is not None and "#railiance-platform: installed-message" in target_after:
|
||||
break
|
||||
time.sleep(0.05)
|
||||
assert inbox, run(
|
||||
|
|
@ -199,14 +203,13 @@ def test_isolated_installed_tool_session_smoke(tmp_path):
|
|||
assert inbox[-1]["sender_repo"] == "railiance-platform"
|
||||
assert inbox[-1]["body"] == "installed-message"
|
||||
assert inbox[-1]["state"] == "pending"
|
||||
assert inbox[-1]["displayed_at"] is not None
|
||||
message_id = inbox[-1]["message_id"]
|
||||
assert run(str(tamq), "inbox", "--repo", "activity-core").stdout == (
|
||||
f"#railiance-platform: installed-message [{message_id}]\n"
|
||||
)
|
||||
target_after = run(
|
||||
*tmux, "capture-pane", "-p", "-t", "tamq:activity-core"
|
||||
).stdout
|
||||
assert target_after == target_before
|
||||
assert target_after != target_before
|
||||
assert f"#railiance-platform: installed-message [{message_id}]" in target_after
|
||||
|
||||
assert run(str(tamq), "ack", message_id).stdout.strip() == message_id
|
||||
assert run(str(tamq), "inbox", "--repo", "activity-core").stdout == ""
|
||||
|
|
@ -289,7 +292,7 @@ def test_isolated_installed_tool_session_smoke(tmp_path):
|
|||
).stdout
|
||||
)
|
||||
assert recovered["instance_id"] != started["instance_id"]
|
||||
assert recovered["delivery_mode"] == "manual"
|
||||
assert recovered["delivery_mode"] == "output"
|
||||
recovered_rows = run(
|
||||
*tmux,
|
||||
"list-windows",
|
||||
|
|
|
|||
|
|
@ -19,6 +19,9 @@ class FakeControl:
|
|||
def inject(self, window, text):
|
||||
self.injected.append((window, text))
|
||||
|
||||
def pane_tty(self, window):
|
||||
return f"/dev/pts/{window.rsplit(':', 1)[-1]}"
|
||||
|
||||
def close(self):
|
||||
return None
|
||||
|
||||
|
|
@ -46,6 +49,58 @@ def test_service_never_injects_manual_endpoint_messages(tmp_path, monkeypatch):
|
|||
assert store.list()[0]["state"] == "pending"
|
||||
|
||||
|
||||
def test_service_writes_output_once_and_retains_pending_ack(tmp_path, monkeypatch):
|
||||
store = Store(tmp_path / "queue.sqlite3")
|
||||
store.register_endpoint("tmux-amq-9-boot", 9, "tamq", ["repo-b"], "output")
|
||||
message_id = store.add("repo-a", "repo-b", "continue")
|
||||
writes = []
|
||||
monkeypatch.setattr("tamq.service.ControlModeClient", FakeControl)
|
||||
monkeypatch.setattr(
|
||||
"tamq.service.write_terminal_output",
|
||||
lambda path, text: writes.append((path, text)),
|
||||
)
|
||||
service = Service(store=store)
|
||||
|
||||
service._deliver_once()
|
||||
service._deliver_once()
|
||||
|
||||
assert writes == [
|
||||
(
|
||||
"/dev/pts/repo-b",
|
||||
f"\r\n#repo-a: continue [{message_id}]\r\n",
|
||||
)
|
||||
]
|
||||
row = store.list()[0]
|
||||
assert row["state"] == "pending"
|
||||
assert row["displayed_at"] is not None
|
||||
assert store.db.execute("SELECT COUNT(*) FROM leases").fetchone()[0] == 0
|
||||
|
||||
|
||||
def test_failed_terminal_output_remains_undisplayed_and_retryable(tmp_path, monkeypatch):
|
||||
store = Store(tmp_path / "queue.sqlite3")
|
||||
store.register_endpoint("tmux-amq-9-boot", 9, "tamq", ["repo-b"], "output")
|
||||
store.add("repo-a", "repo-b", "continue")
|
||||
attempts = []
|
||||
monkeypatch.setattr("tamq.service.ControlModeClient", FakeControl)
|
||||
|
||||
def fail_once(path, text):
|
||||
attempts.append((path, text))
|
||||
if len(attempts) == 1:
|
||||
raise OSError("temporary failure")
|
||||
|
||||
monkeypatch.setattr("tamq.service.write_terminal_output", fail_once)
|
||||
service = Service(store=store)
|
||||
|
||||
service._deliver_once()
|
||||
assert store.list()[0]["displayed_at"] is None
|
||||
store.db.execute("UPDATE leases SET expires_at=0")
|
||||
store.db.commit()
|
||||
service._deliver_once()
|
||||
|
||||
assert len(attempts) == 2
|
||||
assert store.list()[0]["displayed_at"] is not None
|
||||
|
||||
|
||||
def test_service_disconnects_disappeared_tmux_endpoint(tmp_path, monkeypatch):
|
||||
store = Store(tmp_path / "queue.sqlite3")
|
||||
store.register_endpoint("tmux-amq-9-boot", 9, "tamq", ["repo-b"])
|
||||
|
|
|
|||
|
|
@ -41,5 +41,21 @@ def test_store_migrates_legacy_endpoint_rows_to_manual_delivery(tmp_path):
|
|||
assert store.endpoints()[0]["delivery_mode"] == "manual"
|
||||
assert store.db.execute(
|
||||
"SELECT value FROM metadata WHERE key='schema_version'"
|
||||
).fetchone()[0] == "2"
|
||||
).fetchone()[0] == "3"
|
||||
assert "displayed_at" in {
|
||||
row["name"] for row in store.db.execute("PRAGMA table_info(messages)")
|
||||
}
|
||||
|
||||
|
||||
def test_mark_displayed_releases_lease_without_acknowledging(tmp_path):
|
||||
store = Store(tmp_path / "queue.sqlite3")
|
||||
message_id = store.add("a", "b", "hello")
|
||||
lease_id = store.claim(message_id, "endpoint")
|
||||
assert lease_id is not None
|
||||
|
||||
assert store.mark_displayed(message_id, lease_id) is True
|
||||
row = store.list()[0]
|
||||
assert row["state"] == "pending"
|
||||
assert row["displayed_at"] is not None
|
||||
assert store.db.execute("SELECT COUNT(*) FROM leases").fetchone()[0] == 0
|
||||
store.close()
|
||||
|
|
|
|||
45
tests/test_terminal_output.py
Normal file
45
tests/test_terminal_output.py
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
import os
|
||||
import pty
|
||||
import select
|
||||
|
||||
import pytest
|
||||
|
||||
from tamq.terminal import (
|
||||
TerminalOutputError,
|
||||
format_comment,
|
||||
terminal_frame,
|
||||
write_terminal_output,
|
||||
)
|
||||
|
||||
|
||||
def test_comment_format_escapes_controls_and_prefixes_every_line():
|
||||
assert format_comment("repo-a", "first\nsecond\x1b[31m", "m-1") == (
|
||||
"#repo-a: first\n# second\\x1b[31m [m-1]"
|
||||
)
|
||||
assert terminal_frame("repo-a", "hello", "m-1") == (
|
||||
"\r\n#repo-a: hello [m-1]\r\n"
|
||||
)
|
||||
|
||||
|
||||
def test_terminal_output_reaches_pty_output_but_not_input():
|
||||
master, slave = pty.openpty()
|
||||
try:
|
||||
tty_path = os.ttyname(slave)
|
||||
write_terminal_output(tty_path, terminal_frame("repo-a", "hello", "m-1"))
|
||||
|
||||
readable, _, _ = select.select([master], [], [], 1)
|
||||
assert readable == [master]
|
||||
assert b"#repo-a: hello [m-1]" in os.read(master, 4096)
|
||||
readable_input, _, _ = select.select([slave], [], [], 0)
|
||||
assert readable_input == []
|
||||
finally:
|
||||
os.close(master)
|
||||
os.close(slave)
|
||||
|
||||
|
||||
def test_terminal_output_rejects_non_pty_paths(tmp_path):
|
||||
target = tmp_path / "ordinary-file"
|
||||
target.write_text("untouched", encoding="utf-8")
|
||||
with pytest.raises(TerminalOutputError, match="non-PTY"):
|
||||
write_terminal_output(target, "message")
|
||||
assert target.read_text(encoding="utf-8") == "untouched"
|
||||
Loading…
Add table
Add a link
Reference in a new issue