import json import socket import threading from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer import pytest from coordination_engine.adapters import Hub, SafetyError, Tamq, socket_request from coordination_engine.config import Config @pytest.fixture def peer(tmp_path): path = tmp_path / "tamq.sock" server = socket.socket(socket.AF_UNIX) server.bind(str(path)) path.chmod(0o600) server.listen() server.settimeout(0.05) state = { "requests": [], "messages": {}, "endpoints": [{"endpoint_id": "tmux-amq-123", "repos": '["demo"]'}], "protocol": "0.1", "capabilities": ["bounded_delivery_ack_v1", "idempotent_send_v1"], } stop = threading.Event() def run(): while not stop.is_set(): try: conn, _ = server.accept() except socket.timeout: continue with conn: request = json.loads(conn.makefile("rb").readline()) state["requests"].append(request) op = request["op"] response = {"ok": True} if op == "ping": response.update( protocol=state["protocol"], capabilities=state["capabilities"] ) elif op == "endpoints": response["endpoints"] = state["endpoints"] elif op == "send": key = request["idempotency_key"] state["messages"].setdefault( key, "message-" + str(len(state["messages"])) ) response.update(message_id=state["messages"][key], state="pending") elif op == "message": response["message"] = { "state": "injected", "message_id": request["message_id"], } conn.sendall((json.dumps(response) + "\n").encode()) thread = threading.Thread(target=run) thread.start() yield path, state stop.set() thread.join(timeout=2) server.close() @pytest.fixture def adapter(peer, monkeypatch): path, state = peer monkeypatch.setattr( "coordination_engine.adapters.registry", lambda: {"demo": "/demo", "coordination-engine": "/coordination"}, ) return Tamq(Config(tamq_socket=path).validate()), state def test_socket_protocol_and_stable_send(adapter): tamq, state = adapter lease = {"repo": "demo", "lease_id": "lease", "trigger_id": "trigger"} first = tamq.wake(lease, "Inspect work") second = tamq.wake(lease, "Inspect work") assert first["message_id"] == second["message_id"] assert len(state["messages"]) == 1 send = next(r for r in state["requests"] if r["op"] == "send") assert send["metadata"] == {"lease_id": "lease", "trigger_id": "trigger"} assert send["endpoint_id"] == "tmux-amq-123" assert tamq.receipt(first["message_id"])["state"] == "injected" @pytest.mark.parametrize( "field,value", [ ("protocol", "1.0"), ("capabilities", []), ( "endpoints", [ {"endpoint_id": "a", "repos": '["demo"]'}, {"endpoint_id": "b", "repos": '["demo"]'}, ], ), ], ) def test_protocol_and_ambiguity_gate_before_send(adapter, field, value): tamq, state = adapter state[field] = value with pytest.raises(SafetyError): tamq.wake({"repo": "demo", "lease_id": "l", "trigger_id": "t"}, "Inspect") assert not state["messages"] def test_registry_refresh_each_wake(adapter, monkeypatch): tamq, state = adapter monkeypatch.setattr("coordination_engine.adapters.registry", lambda: {}) with pytest.raises(SafetyError): tamq.wake({"repo": "demo", "lease_id": "l", "trigger_id": "t"}, "Inspect") assert not state["requests"] def test_insecure_socket_rejected(peer): path, state = peer path.chmod(0o666) with pytest.raises(SafetyError): socket_request(path, {"op": "ping"}) assert not state["requests"] def test_hub_http_snapshot_and_sanitized_projection(): posts = [] class Handler(BaseHTTPRequestHandler): def log_message(self, *args): pass def do_GET(self): if self.path.startswith("/repos/"): data = [{"id": "r", "slug": "demo"}] elif self.path == "/workplans/": data = [{"id": "p", "repo_id": "r", "status": "active"}] elif self.path.startswith("/tasks/"): data = [{"id": "t", "workplan_id": "p", "status": "todo"}] else: data = [] self.send_response(200) self.end_headers() self.wfile.write(json.dumps(data).encode()) def do_POST(self): posts.append( json.loads(self.rfile.read(int(self.headers["Content-Length"]))) ) self.send_response(201) self.end_headers() self.wfile.write(b'{"id":"receipt"}') server = ThreadingHTTPServer(("127.0.0.1", 0), Handler) thread = threading.Thread(target=server.serve_forever) thread.start() try: hub = Hub( Config( api_base=f"http://127.0.0.1:{server.server_port}", repos=["demo"] ).validate() ) snapshot = hub.snapshot() assert snapshot["repos"] == {"r": "demo"} assert snapshot["tasks"][0]["id"] == "t" hub.project({"id": "r1", "state": "offered", "reason": "wake_attempt"}) assert posts[0]["detail"]["id"] == "r1" assert posts[0]["event_type"] == "coordination_receipt" finally: server.shutdown() thread.join() server.server_close() @pytest.mark.parametrize( "field,value", [ ("protocol", "0.invalid"), ("capabilities", None), ("endpoints", None), ("endpoints", [{"endpoint_id": "e", "repos": None}]), ], ) def test_malformed_peer_stops_before_send(adapter, field, value): tamq, state = adapter state[field] = value with pytest.raises(SafetyError): tamq.wake({"repo": "demo", "lease_id": "l", "trigger_id": "t"}, "Inspect") assert not state["messages"]