mempalace/tests/test_cli_logstream.py

257 lines
8.7 KiB
Python

"""Tests for the RFC 003 logstream/artifact CLI commands.
Covers cmd_logstream (append/list/wait/ack) and cmd_artifact (put/get):
JSON and human output, exact-content stdout piping, timeout exit code,
and error exits. Uses SimpleNamespace args like the rest of test_cli.py.
"""
import json
import sys
from types import SimpleNamespace
import pytest
from mempalace.cli import cmd_artifact, cmd_logstream, main
def _append_args(palace, **overrides):
fields = dict(
palace=palace,
logstream_action="append",
type="task.request",
stream="project/mempalace",
room="delegation",
from_agent="mac-fable",
to_agent="windows-codex",
correlation_id="task_cli",
branch=None,
base_commit=None,
status=None,
body="Please fix the thing.",
body_file=None,
metadata=None,
artifact_id=None,
json=True,
)
fields.update(overrides)
return SimpleNamespace(**fields)
def _list_args(palace, **overrides):
fields = dict(
palace=palace,
logstream_action="list",
stream=None,
room=None,
type=None,
to_agent=None,
from_agent=None,
correlation_id=None,
status=None,
since_event_id=None,
since_created_at=None,
limit=50,
json=True,
)
fields.update(overrides)
return SimpleNamespace(**fields)
def _wait_args(palace, **overrides):
args = _list_args(palace, **overrides)
args.logstream_action = "wait"
del args.limit
if not hasattr(args, "timeout_ms"):
args.timeout_ms = 100
return args
def _put_args(palace, content, **overrides):
fields = dict(
palace=palace,
artifact_action="put",
kind="patch",
created_by="windows-codex",
content=content,
file=None,
metadata=None,
json=True,
)
fields.update(overrides)
return SimpleNamespace(**fields)
class TestLogstreamCli:
def test_append_then_list_json_round_trip(self, palace_path, capsys):
cmd_logstream(_append_args(palace_path))
appended = json.loads(capsys.readouterr().out)
assert appended["id"].startswith("evt_")
cmd_logstream(_list_args(palace_path, correlation_id="task_cli"))
listed = json.loads(capsys.readouterr().out)
assert listed["count"] == 1
assert listed["events"][0]["id"] == appended["id"]
assert listed["events"][0]["body"] == "Please fix the thing."
def test_append_human_output(self, palace_path, capsys):
cmd_logstream(_append_args(palace_path, json=False))
out = capsys.readouterr().out
assert "Appended:" in out
assert "task.request" in out
assert "project/mempalace/delegation" in out
assert "mac-fable->windows-codex" in out
def test_append_invalid_status_exits_1(self, palace_path, capsys):
with pytest.raises(SystemExit) as exc:
cmd_logstream(_append_args(palace_path, status="bogus"))
assert exc.value.code == 1
assert "status" in json.loads(capsys.readouterr().out)["error"]
def test_append_invalid_metadata_exits_1(self, palace_path, capsys):
with pytest.raises(SystemExit) as exc:
cmd_logstream(_append_args(palace_path, metadata="not json"))
assert exc.value.code == 1
assert "metadata" in json.loads(capsys.readouterr().out)["error"]
def test_body_file_stdin(self, palace_path, capsys, monkeypatch):
monkeypatch.setattr(sys, "stdin", __import__("io").StringIO("body from stdin\n"))
cmd_logstream(_append_args(palace_path, body=None, body_file="-"))
appended = json.loads(capsys.readouterr().out)
assert appended["body"] == "body from stdin\n"
def test_wait_existing_event_returns_immediately(self, palace_path, capsys):
cmd_logstream(_append_args(palace_path))
capsys.readouterr()
cmd_logstream(_wait_args(palace_path, correlation_id="task_cli", timeout_ms=5000))
result = json.loads(capsys.readouterr().out)
assert result["timed_out"] is False
assert result["count"] == 1
def test_wait_timeout_exits_2(self, palace_path, capsys):
with pytest.raises(SystemExit) as exc:
cmd_logstream(_wait_args(palace_path, correlation_id="task_never", timeout_ms=100))
assert exc.value.code == 2
result = json.loads(capsys.readouterr().out)
assert result["timed_out"] is True
def test_ack_round_trip(self, palace_path, capsys):
cmd_logstream(_append_args(palace_path))
appended = json.loads(capsys.readouterr().out)
cmd_logstream(
SimpleNamespace(
palace=palace_path,
logstream_action="ack",
event_id=appended["id"],
from_agent="windows-codex",
status="applied",
body="Done.",
json=True,
)
)
ack = json.loads(capsys.readouterr().out)
assert ack["type"] == "event.ack"
assert ack["to_agent"] == "mac-fable"
assert ack["correlation_id"] == "task_cli"
class TestArtifactCli:
PATCH = "diff --git a/x b/x\n+cli\n"
def test_put_then_get_stdout_is_exact(self, palace_path, capsys):
cmd_artifact(_put_args(palace_path, self.PATCH))
artifact = json.loads(capsys.readouterr().out)
assert artifact["id"].startswith("art_")
cmd_artifact(
SimpleNamespace(
palace=palace_path,
artifact_action="get",
artifact_id=artifact["id"],
out=None,
json=False,
)
)
# Exact content, nothing else — must survive `| git apply`.
assert capsys.readouterr().out == self.PATCH
def test_get_out_writes_file(self, palace_path, tmp_dir, capsys):
cmd_artifact(_put_args(palace_path, self.PATCH))
artifact = json.loads(capsys.readouterr().out)
out_path = f"{tmp_dir}/fetched.patch"
cmd_artifact(
SimpleNamespace(
palace=palace_path,
artifact_action="get",
artifact_id=artifact["id"],
out=out_path,
json=True,
)
)
meta = json.loads(capsys.readouterr().out)
assert "content" not in meta
assert meta["content_written_to"] == out_path
assert open(out_path, encoding="utf-8").read() == self.PATCH
def test_get_missing_exits_1(self, palace_path, capsys):
with pytest.raises(SystemExit) as exc:
cmd_artifact(
SimpleNamespace(
palace=palace_path,
artifact_action="get",
artifact_id="art_nope",
out=None,
json=True,
)
)
assert exc.value.code == 1
assert "not found" in json.loads(capsys.readouterr().out)["error"]
def test_put_reads_stdin_by_default(self, palace_path, capsys, monkeypatch):
monkeypatch.setattr(sys, "stdin", __import__("io").StringIO(self.PATCH))
cmd_artifact(_put_args(palace_path, None))
artifact = json.loads(capsys.readouterr().out)
assert artifact["size_bytes"] == len(self.PATCH.encode("utf-8"))
def test_event_can_reference_cli_artifact(self, palace_path, capsys):
cmd_artifact(_put_args(palace_path, self.PATCH))
artifact = json.loads(capsys.readouterr().out)
cmd_logstream(_append_args(palace_path, type="patch.ready", artifact_id=[artifact["id"]]))
event = json.loads(capsys.readouterr().out)
assert event["artifact_ids"] == [artifact["id"]]
class TestMainDispatch:
def test_main_dispatches_logstream_list(self, palace_path, capsys, monkeypatch):
monkeypatch.setattr(
sys,
"argv",
["mempalace", "--palace", palace_path, "logstream", "list", "--json"],
)
main()
result = json.loads(capsys.readouterr().out)
assert result == {"events": [], "count": 0}
def test_main_dispatches_artifact_put(self, palace_path, capsys, monkeypatch):
monkeypatch.setattr(
sys,
"argv",
[
"mempalace",
"--palace",
palace_path,
"artifact",
"put",
"--kind",
"note",
"--created-by",
"mac-fable",
"--content",
"hello",
"--json",
],
)
main()
artifact = json.loads(capsys.readouterr().out)
assert artifact["kind"] == "note"
assert artifact["size_bytes"] == 5