Gate r2: resetting last_bot_poll after a started delivery never re-polled early (the next tick re-probes while the turn is still running), so the plumbing is gone and the 5 s cadence is stated plainly. test_shared_session_attach denied inspection for the test's own pid, which the self-pid skip (#108005) legitimately never probes — it now denies a foreign owner.
132 lines
5.3 KiB
Python
132 lines
5.3 KiB
Python
"""Local owner discovery must fence profile and lease identity."""
|
|
|
|
import json
|
|
import os
|
|
import threading
|
|
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
|
|
import pytest
|
|
|
|
from hermes_cli.active_sessions import try_acquire_active_session
|
|
|
|
|
|
def test_discovery_uses_exact_profile_and_owner_handshake(tmp_path):
|
|
from hermes_cli.shared_session_attach import discover_attach_url
|
|
|
|
home = tmp_path / "profile"
|
|
other = tmp_path / "other"
|
|
reply = {}
|
|
requests = []
|
|
|
|
class Handler(BaseHTTPRequestHandler):
|
|
def do_GET(self):
|
|
requests.append(self.path)
|
|
payload = json.dumps(reply).encode()
|
|
self.send_response(200)
|
|
self.end_headers()
|
|
self.wfile.write(payload)
|
|
|
|
def log_message(self, *args):
|
|
pass
|
|
|
|
server = ThreadingHTTPServer(("127.0.0.1", 0), Handler)
|
|
thread = threading.Thread(target=server.serve_forever, daemon=True)
|
|
thread.start()
|
|
origin = f"http://127.0.0.1:{server.server_port}"
|
|
lease, error = try_acquire_active_session(
|
|
session_id="same-id", surface="desktop", config={}, registry_home=home,
|
|
metadata={"live_session_id": "live", "shared_runtime_url": origin},
|
|
)
|
|
assert error is None
|
|
reply.update(session_id="same-id", lease_id=lease.lease_id,
|
|
profile_home=str(home.resolve()), websocket_url=origin.replace("http:", "ws:") + "/api/ws?token=real-token")
|
|
try:
|
|
assert discover_attach_url("same-id", registry_home=other) is None
|
|
assert requests == []
|
|
assert discover_attach_url("same-id", registry_home=home) == reply["websocket_url"]
|
|
assert len(requests) == 1
|
|
from hermes_cli.shared_session_attach import configure_tui_attachment
|
|
env = {"HERMES_TUI_GATEWAY_URL": " "}
|
|
configure_tui_attachment(env, "same-id", registry_home=home)
|
|
assert env["HERMES_TUI_GATEWAY_URL"] == reply["websocket_url"]
|
|
reply["profile_home"] = str(other.resolve())
|
|
with pytest.raises(ValueError, match="identity"):
|
|
discover_attach_url("same-id", registry_home=home)
|
|
reply["profile_home"] = str(home.resolve())
|
|
reply["websocket_url"] = "ws://example.com/api/ws?token=secret"
|
|
with pytest.raises(ValueError, match="endpoint"):
|
|
discover_attach_url("same-id", registry_home=home)
|
|
finally:
|
|
lease.release()
|
|
server.shutdown()
|
|
server.server_close()
|
|
thread.join()
|
|
|
|
|
|
def test_discovery_refuses_unsupported_owner_without_releasing_lease(tmp_path, monkeypatch):
|
|
from hermes_cli.shared_session_attach import discover_attach_url
|
|
from hermes_cli.active_sessions import active_session_registry_snapshot
|
|
|
|
lease, error = try_acquire_active_session(
|
|
session_id="old", surface="desktop", config={}, registry_home=tmp_path,
|
|
)
|
|
assert error is None
|
|
try:
|
|
with pytest.raises(ValueError, match="not available in this build") as caught:
|
|
discover_attach_url("old", registry_home=tmp_path)
|
|
first, details = str(caught.value).splitlines()
|
|
assert "hermes --resume old" in first
|
|
assert details.startswith("Details: ")
|
|
assert active_session_registry_snapshot(tmp_path)[0]["lease_id"] == lease.lease_id
|
|
registry = tmp_path / "runtime" / "active_sessions.json"
|
|
with monkeypatch.context() as patch:
|
|
# Our own pid is never probed (#108005), so model a FOREIGN owner whose inspection is denied.
|
|
from hermes_cli.active_sessions import _read_entries, _write_entries
|
|
entries = _read_entries(registry)
|
|
entries[0]["pid"] = os.getpid() + 2**22
|
|
_write_entries(registry, entries)
|
|
before = registry.read_bytes()
|
|
|
|
def denied(pid):
|
|
raise PermissionError("process inspection denied")
|
|
patch.setattr("gateway.status._pid_exists", denied)
|
|
with pytest.raises(RuntimeError, match="liveness is unknown"):
|
|
discover_attach_url("old", registry_home=tmp_path)
|
|
assert registry.read_bytes() == before
|
|
finally:
|
|
lease.release()
|
|
|
|
|
|
def test_discovery_failure_message_names_state_and_resume_path(tmp_path):
|
|
"""A refused handshake keeps the lease and points at the working alternative."""
|
|
from hermes_cli.shared_session_attach import discover_attach_url
|
|
|
|
class Handler(BaseHTTPRequestHandler):
|
|
def do_GET(self):
|
|
self.send_response(500)
|
|
self.end_headers()
|
|
|
|
def log_message(self, *args):
|
|
pass
|
|
|
|
server = ThreadingHTTPServer(("127.0.0.1", 0), Handler)
|
|
thread = threading.Thread(target=server.serve_forever, daemon=True)
|
|
thread.start()
|
|
origin = f"http://127.0.0.1:{server.server_port}"
|
|
lease, error = try_acquire_active_session(
|
|
session_id="held", surface="desktop", config={}, registry_home=tmp_path,
|
|
metadata={"live_session_id": "live", "shared_runtime_url": origin},
|
|
)
|
|
assert error is None
|
|
try:
|
|
with pytest.raises(ValueError, match="just failed") as caught:
|
|
discover_attach_url("held", registry_home=tmp_path)
|
|
first, details = str(caught.value).splitlines()
|
|
assert "hermes --resume held" in first
|
|
assert details.startswith("Details: ")
|
|
finally:
|
|
lease.release()
|
|
server.shutdown()
|
|
server.server_close()
|
|
thread.join()
|