claire/tests/test_rclaude_wrapper.py
Natalie 87099fd99b feat(pull): multi-host enumeration via rclaude --tsv
Replace local-only _claude-projects --sessions path with
'rclaude list sessions --tsv' (full uuids, separate cwd/mtime
columns) and add 'rclaude triage --tsv' ingestion emitting
TriageRecorded events. Diff against the sessions projection
to skip no-op events for unchanged (host, cwd, mtime) and
(priority, status, summary) tuples.
2026-05-18 03:23:55 -07:00

105 lines
4.1 KiB
Python

from __future__ import annotations
import subprocess
import pytest
from clare.rclaude import Rclaude, RclaudeError
def _fake_runner(stdout: str = "", stderr: str = "", returncode: int = 0):
def runner(*args, **kwargs):
return subprocess.CompletedProcess(
args=args[0] if args else [], returncode=returncode, stdout=stdout, stderr=stderr
)
return runner
def test_rclaude_raises_when_binary_missing(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr("shutil.which", lambda _: None)
rcl = Rclaude(binary="rclaude")
with pytest.raises(RclaudeError, match="not found on PATH"):
rcl.version()
def test_rclaude_raises_on_nonzero_exit(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr("shutil.which", lambda _: "/usr/local/bin/rclaude")
rcl = Rclaude(
binary="rclaude",
runner=_fake_runner(stderr="boom", returncode=1),
)
with pytest.raises(RclaudeError, match="exited 1"):
rcl.version()
def test_rclaude_list_tmux_parses_tsv(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr("shutil.which", lambda _: "/usr/local/bin/rclaude")
output = (
"local\ttmux\tclaude-natalie-foo-1715964800\t1 windows (created ...)\n"
"apricot\ttmux\tclaude-natalie-bar-1715964900\t2 windows (created ...)\n"
)
rcl = Rclaude(runner=_fake_runner(stdout=output))
rows = rcl.list_tmux()
assert len(rows) == 2
assert rows[0].host == "local"
assert rows[0].session_name == "claude-natalie-foo-1715964800"
assert rows[0].detail.startswith("1 windows")
assert rows[1].host == "apricot"
def test_rclaude_list_sessions_parses_tsv(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr("shutil.which", lambda _: "/usr/local/bin/rclaude")
# `rclaude list sessions --tsv` interleaves tmux rows with session rows;
# the wrapper should skip the tmux ones.
output = (
"local\ttmux\tclaude-foo\t1 windows\n"
"local\tsession\t94fc4f45-0160-4fb8-9c23-475a0c63c983\thello world\t/Users/x/Code\t1779098754\n"
"apricot\tsession\tbd306333-eb2d-4b65-8a50-2179b2697756\tworking on rclaude\t/home/u/proj\t1779098693\n"
)
rcl = Rclaude(runner=_fake_runner(stdout=output))
rows = rcl.list_sessions()
assert len(rows) == 2
assert rows[0].host == "local"
assert str(rows[0].uuid) == "94fc4f45-0160-4fb8-9c23-475a0c63c983"
assert rows[0].cwd == "/Users/x/Code"
assert rows[0].mtime_epoch == 1779098754
assert rows[0].snippet == "hello world"
assert rows[1].host == "apricot"
assert rows[1].cwd == "/home/u/proj"
def test_rclaude_triage_parses_tsv(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr("shutil.which", lambda _: "/usr/local/bin/rclaude")
output = (
"local\ttriage\t94fc4f45-0160-4fb8-9c23-475a0c63c983\t1\tin_progress"
"\tMulti-agent setup\tResume #31\t/Users/x/Code\t1779098754\n"
"apricot\ttriage\t41daf63a-245b-4194-b9ee-b704f833400f\t2\tblocked"
"\tWrong branch\tUpdate config\t/home/u/proj\t1779098796\n"
)
rcl = Rclaude(runner=_fake_runner(stdout=output))
rows = rcl.triage()
assert len(rows) == 2
assert rows[0].host == "local"
assert str(rows[0].uuid) == "94fc4f45-0160-4fb8-9c23-475a0c63c983"
assert rows[0].priority == 1
assert rows[0].status == "in_progress"
assert rows[0].summary == "Multi-agent setup"
assert rows[0].next_action == "Resume #31"
assert rows[0].cwd == "/Users/x/Code"
assert rows[0].mtime_epoch == 1779098754
assert rows[1].host == "apricot"
assert rows[1].priority == 2
def test_rclaude_triage_passes_flags(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr("shutil.which", lambda _: "/usr/local/bin/rclaude")
captured_args: list[list[str]] = []
def runner(args, **kwargs):
captured_args.append(list(args))
return subprocess.CompletedProcess(args=args, returncode=0, stdout="", stderr="")
rcl = Rclaude(runner=runner)
rcl.triage(refresh=True, limit=5)
assert captured_args == [["rclaude", "triage", "--tsv", "--refresh", "--limit", "5"]]