aboutsummaryrefslogtreecommitdiff
path: root/.ai/scripts/tests/test_cross_agent_recv.py
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-05-06 21:59:52 -0500
committerCraig Jennings <c@cjennings.net>2026-05-06 21:59:52 -0500
commitd81b23ad6b6e437dfe3c338a00a4be39bc555146 (patch)
tree2d4b0d7890fd1fc70d81282b81fed2808c28a106 /.ai/scripts/tests/test_cross_agent_recv.py
parent201377f57430ef28d02e703a2191434bbee55c75 (diff)
downloadrulesets-d81b23ad6b6e437dfe3c338a00a4be39bc555146.tar.gz
rulesets-d81b23ad6b6e437dfe3c338a00a4be39bc555146.zip
chore(ai): initialize project notes and Claude tooling surfaces
Replace the seed notes.org with project-specific context (layout, install modes, task tracker location, recent inflection point). Bring in the synced template surfaces (protocols, workflows, scripts, references, retrospectives, someday-maybe) as tracked content for this content/documentation project.
Diffstat (limited to '.ai/scripts/tests/test_cross_agent_recv.py')
-rw-r--r--.ai/scripts/tests/test_cross_agent_recv.py176
1 files changed, 176 insertions, 0 deletions
diff --git a/.ai/scripts/tests/test_cross_agent_recv.py b/.ai/scripts/tests/test_cross_agent_recv.py
new file mode 100644
index 0000000..27c53a5
--- /dev/null
+++ b/.ai/scripts/tests/test_cross_agent_recv.py
@@ -0,0 +1,176 @@
+"""Tests for cross-agent-recv."""
+
+from __future__ import annotations
+
+import json
+import os
+import subprocess
+from pathlib import Path
+
+import pytest
+
+SCRIPT = Path(__file__).resolve().parent.parent / "cross-agent-comms" / "cross-agent-recv"
+
+
+def _make_message(path: Path, *, conv_id: str = "test-conv", seq: int = 1, msg_type: str = "request",
+ proto_version: str = "5", title: str = "Test", requires_tools: str | None = None,
+ body: str = "Body.\n") -> Path:
+ fm_lines = [
+ f"#+TITLE: {title}",
+ f"#+CONVERSATION_ID: {conv_id}",
+ f"#+MESSAGE_TYPE: {msg_type}",
+ f"#+SEQUENCE: {seq}",
+ "#+TIMESTAMP: 2026-04-27T05:00:00-05:00",
+ f"#+PROTOCOL_VERSION: {proto_version}",
+ ]
+ if requires_tools:
+ fm_lines.append(f"#+REQUIRES_TOOLS: {requires_tools}")
+ path.write_text("\n".join(fm_lines) + "\n\n" + body)
+ return path
+
+
+def _run(args: list[str], env: dict | None = None) -> subprocess.CompletedProcess:
+ return subprocess.run([str(SCRIPT), *args], capture_output=True, text=True, env=env)
+
+
+@pytest.fixture
+def isolated_env(tmp_path, monkeypatch):
+ fake_home = tmp_path / "home"
+ fake_home.mkdir()
+ monkeypatch.setenv("HOME", str(fake_home))
+ return fake_home
+
+
+def test_recv_help(isolated_env):
+ result = _run(["--help"], env={**os.environ, "HOME": str(isolated_env)})
+ assert result.returncode == 0
+ assert "Receive and decide" in result.stdout
+
+
+def test_recv_missing_file_rejects(isolated_env, tmp_path):
+ result = _run([str(tmp_path / "nope.org")], env={**os.environ, "HOME": str(isolated_env)})
+ assert result.returncode == 3 # reject
+
+
+def test_recv_malformed_frontmatter_rejects(isolated_env, tmp_path):
+ bad = tmp_path / "bad.org"
+ bad.write_text("not org-mode at all\n")
+ result = _run([str(bad), "--no-verify"], env={**os.environ, "HOME": str(isolated_env)})
+ assert result.returncode == 3
+ assert "decision: reject" in result.stdout
+
+
+def test_recv_missing_required_field_rejects(isolated_env, tmp_path):
+ msg = tmp_path / "msg.org"
+ # Missing PROTOCOL_VERSION among others.
+ msg.write_text("#+TITLE: x\n#+CONVERSATION_ID: c\n\nBody.\n")
+ result = _run([str(msg), "--no-verify"], env={**os.environ, "HOME": str(isolated_env)})
+ assert result.returncode == 3
+ assert "missing required" in result.stdout
+
+
+def test_recv_protocol_version_mismatch_query(isolated_env, tmp_path):
+ msg = _make_message(tmp_path / "msg.org", proto_version="4")
+ result = _run([str(msg), "--no-verify"], env={**os.environ, "HOME": str(isolated_env)})
+ assert result.returncode == 2 # query
+ assert "PROTOCOL_VERSION mismatch" in result.stdout
+
+
+def test_recv_invalid_message_type_rejects(isolated_env, tmp_path):
+ msg = _make_message(tmp_path / "msg.org", msg_type="banana")
+ result = _run([str(msg), "--no-verify"], env={**os.environ, "HOME": str(isolated_env)})
+ assert result.returncode == 3
+ assert "invalid MESSAGE_TYPE" in result.stdout
+
+
+def test_recv_missing_signature_rejects(isolated_env, tmp_path):
+ """When verify is on, a missing .asc sibling rejects."""
+ msg = _make_message(tmp_path / "msg.org")
+ # No .asc sidecar.
+ result = _run([str(msg)], env={**os.environ, "HOME": str(isolated_env)})
+ assert result.returncode == 3
+ assert "signature file missing" in result.stdout
+
+
+def test_recv_valid_processes(isolated_env, tmp_path):
+ """A valid message with --no-verify and no dedup match → process."""
+ msg = _make_message(tmp_path / "msg.org")
+ result = _run([str(msg), "--no-verify"], env={**os.environ, "HOME": str(isolated_env)})
+ assert result.returncode == 0 # process
+ assert "decision: process" in result.stdout
+ assert "sha256:" in result.stdout
+
+
+def test_recv_dedup_against_identical_existing(isolated_env, tmp_path):
+ """Same content + same SEQUENCE in same dir → dedup."""
+ inbox = tmp_path / "inbox"
+ inbox.mkdir()
+ first = _make_message(inbox / "20260427T100000Z-from-x-c.org", conv_id="c", seq=5)
+ # Second message with same content — name differs (canonical-style would have different timestamp).
+ second = _make_message(inbox / "20260427T100100Z-from-x-c.org", conv_id="c", seq=5)
+ # Bodies must be byte-identical for hash equality.
+ second.write_bytes(first.read_bytes())
+ result = _run([str(second), "--no-verify"], env={**os.environ, "HOME": str(isolated_env)})
+ assert result.returncode == 1 # dedup
+ assert "decision: dedup" in result.stdout
+
+
+def test_recv_collision_with_different_content_processes(isolated_env, tmp_path):
+ """Same SEQUENCE + same CONVERSATION_ID but different content → process both."""
+ inbox = tmp_path / "inbox"
+ inbox.mkdir()
+ _make_message(inbox / "20260427T100000Z-from-x-c.org", conv_id="c", seq=5, body="First body.\n")
+ second = _make_message(inbox / "20260427T100100Z-from-x-c.org", conv_id="c", seq=5, body="Different body.\n")
+ result = _run([str(second), "--no-verify"], env={**os.environ, "HOME": str(isolated_env)})
+ assert result.returncode == 0 # process
+ assert "decision: process" in result.stdout
+
+
+def test_recv_requires_tools_missing_query(isolated_env, tmp_path):
+ """REQUIRES_TOOLS naming a definitely-missing binary → query."""
+ msg = _make_message(tmp_path / "msg.org", requires_tools="definitely-not-installed-xyzzy-9000")
+ result = _run([str(msg), "--no-verify"], env={**os.environ, "HOME": str(isolated_env)})
+ assert result.returncode == 2 # query
+ assert "required tools unavailable" in result.stdout
+
+
+def test_recv_requires_tools_present_processes(isolated_env, tmp_path):
+ """REQUIRES_TOOLS naming a real binary → process."""
+ msg = _make_message(tmp_path / "msg.org", requires_tools="ls,cat")
+ result = _run([str(msg), "--no-verify"], env={**os.environ, "HOME": str(isolated_env)})
+ assert result.returncode == 0
+ assert "decision: process" in result.stdout
+
+
+def test_recv_json_output(isolated_env, tmp_path):
+ msg = _make_message(tmp_path / "msg.org")
+ result = _run([str(msg), "--no-verify", "--json"], env={**os.environ, "HOME": str(isolated_env)})
+ assert result.returncode == 0
+ payload = json.loads(result.stdout)
+ assert payload["decision"] == "process"
+ assert payload["message_type"] == "request"
+ assert payload["conversation_id"] == "test-conv"
+
+
+def test_recv_halt_blocks(isolated_env, tmp_path):
+ halt = isolated_env / ".config" / "cross-agent-comms" / "HALT"
+ halt.parent.mkdir(parents=True)
+ halt.write_text("halted\n")
+ msg = _make_message(tmp_path / "msg.org")
+ result = _run([str(msg), "--no-verify"], env={**os.environ, "HOME": str(isolated_env)})
+ assert result.returncode == 5
+ assert "halt active" in result.stderr.lower()
+
+
+def test_recv_halt_leaves_message_in_place(isolated_env, tmp_path):
+ """Per spec: under HALT, recv must NOT move/dedup/reject — leave file in place."""
+ halt = isolated_env / ".config" / "cross-agent-comms" / "HALT"
+ halt.parent.mkdir(parents=True)
+ halt.write_text("halted\n")
+ msg = _make_message(tmp_path / "msg.org")
+ pre_content = msg.read_text()
+ result = _run([str(msg), "--no-verify"], env={**os.environ, "HOME": str(isolated_env)})
+ assert result.returncode == 5
+ # File still exists with same content.
+ assert msg.exists()
+ assert msg.read_text() == pre_content