336 lines
10 KiB
Python
336 lines
10 KiB
Python
"""Peer transport tests for hosted-room member turns."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from gateway.hosted_room_driver import TaskIdentity
|
|
from tui_gateway.hosted_room_driver import HostedRoomBinding, ROOM_SESSION_SOURCE
|
|
from tui_gateway.hosted_room_peer_transport import (
|
|
FailoverHostedRoomPeerClient,
|
|
PeerHostedRoomTransport,
|
|
PeerMemberRoute,
|
|
RoomLinkCandidate,
|
|
)
|
|
from tui_gateway.hosted_room_peer_http import PeerRunsHTTPError
|
|
|
|
|
|
BINDING = HostedRoomBinding("room-1", "gateway-home", 2)
|
|
ROUTE = PeerMemberRoute(
|
|
home_install_id="install-home",
|
|
member_id="member-reviewer",
|
|
target_install_id="install-peer",
|
|
target_profile="reviewer",
|
|
capability_digest="a" * 64,
|
|
execution_policy_digest="b" * 64,
|
|
cancellation_scope_id="cancel-1",
|
|
trace_id="trace-1",
|
|
grant="signed-room-grant",
|
|
)
|
|
|
|
|
|
class FakePeerClient:
|
|
def __init__(self) -> None:
|
|
self.calls: list[tuple[str, dict[str, Any]]] = []
|
|
self.session = {"session_id": "group-session"}
|
|
self.messages = []
|
|
self.active = False
|
|
self.task_id = None
|
|
|
|
def prepare(self, **kwargs):
|
|
self.calls.append(("prepare", kwargs))
|
|
return (
|
|
self.session
|
|
if kwargs["create"] or kwargs.get("expected_session_id")
|
|
else None
|
|
)
|
|
|
|
def dispatch(self, **kwargs):
|
|
self.calls.append(("dispatch", kwargs))
|
|
dispatch = kwargs["dispatch"]
|
|
self.active = True
|
|
self.task_id = dispatch["task_id"]
|
|
return {"status": "accepted", "task_id": self.task_id}
|
|
|
|
def history(self, **kwargs):
|
|
self.calls.append(("history", kwargs))
|
|
return list(self.messages)
|
|
|
|
def status(self, **kwargs):
|
|
self.calls.append(("status", kwargs))
|
|
return {"active": self.active, "task_id": self.task_id}
|
|
|
|
def stop(self, **kwargs):
|
|
self.calls.append(("stop", kwargs))
|
|
self.active = False
|
|
return {"status": "cancelled", "task_id": self.task_id}
|
|
|
|
|
|
class FailingPeerClient(FakePeerClient):
|
|
def __init__(self, *, method, retryable=True, not_admitted=False):
|
|
super().__init__()
|
|
self.method = method
|
|
self.error = PeerRunsHTTPError(
|
|
f"{method} failed",
|
|
retryable=retryable,
|
|
ambiguous=method == "dispatch" and not not_admitted,
|
|
not_admitted=not_admitted,
|
|
)
|
|
|
|
def prepare(self, **kwargs):
|
|
if self.method == "prepare":
|
|
raise self.error
|
|
return super().prepare(**kwargs)
|
|
|
|
def dispatch(self, **kwargs):
|
|
if self.method == "dispatch":
|
|
self.calls.append(("dispatch", kwargs))
|
|
raise self.error
|
|
return super().dispatch(**kwargs)
|
|
|
|
def status(self, **kwargs):
|
|
if self.method == "status":
|
|
raise self.error
|
|
return super().status(**kwargs)
|
|
|
|
|
|
def _transport(client=None, *, source_event_seq=1):
|
|
return PeerHostedRoomTransport(
|
|
binding=BINDING,
|
|
route=ROUTE,
|
|
client=client or FakePeerClient(),
|
|
source_event_seq=source_event_seq,
|
|
)
|
|
|
|
|
|
def test_peer_transport_prepares_group_session_not_canonical_bot_chat():
|
|
client = FakePeerClient()
|
|
transport = _transport(client)
|
|
assert (
|
|
transport.resolve_exact(
|
|
profile="reviewer",
|
|
title="Group: room-1",
|
|
source=ROOM_SESSION_SOURCE,
|
|
)
|
|
is None
|
|
)
|
|
created = transport.create(
|
|
profile="reviewer",
|
|
title="Group: room-1",
|
|
source=ROOM_SESSION_SOURCE,
|
|
)
|
|
assert created["session_id"] == "group-session"
|
|
prepare = [params for method, params in client.calls if method == "prepare"]
|
|
assert all(params["room_id"] == "room-1" for params in prepare)
|
|
assert all(params["source"] == "bot_room" for params in prepare)
|
|
|
|
|
|
def test_peer_transport_dispatches_full_fenced_coordinates_and_exact_stop():
|
|
client = FakePeerClient()
|
|
transport = _transport(client)
|
|
transport.create(
|
|
profile="reviewer",
|
|
title="Group: room-1",
|
|
source=ROOM_SESSION_SOURCE,
|
|
)
|
|
terminal = []
|
|
task = TaskIdentity("room-1", "task-1", "thread-1", "turn-1")
|
|
result = transport.submit(
|
|
profile="reviewer",
|
|
session_id="group-session",
|
|
prompt="Review this change.",
|
|
source=ROOM_SESSION_SOURCE,
|
|
task=task,
|
|
execution_generation=3,
|
|
on_terminal=terminal.append,
|
|
)
|
|
assert result["status"] == "accepted"
|
|
dispatch = next(params for method, params in client.calls if method == "dispatch")
|
|
assert dispatch["dispatch"]["authority_epoch"] == 2
|
|
assert dispatch["dispatch"]["execution_generation"] == 3
|
|
assert dispatch["dispatch"]["target_profile"] == "reviewer"
|
|
assert dispatch["dispatch"]["capability_digest"] == "a" * 64
|
|
assert terminal == []
|
|
|
|
assert (
|
|
transport.interrupt(
|
|
profile="reviewer",
|
|
session_id="group-session",
|
|
source=ROOM_SESSION_SOURCE,
|
|
expected_task_id="other-task",
|
|
)
|
|
is None
|
|
)
|
|
stopped = transport.interrupt(
|
|
profile="reviewer",
|
|
session_id="group-session",
|
|
source=ROOM_SESSION_SOURCE,
|
|
expected_task_id="task-1",
|
|
)
|
|
assert stopped["status"] == "cancelled"
|
|
assert len([call for call in client.calls if call[0] == "stop"]) == 1
|
|
|
|
|
|
def test_peer_transport_carries_each_turns_real_source_event_sequence():
|
|
observed = []
|
|
for index, source_event_seq in enumerate((7, 42), start=1):
|
|
client = FakePeerClient()
|
|
transport = _transport(client, source_event_seq=source_event_seq)
|
|
transport.create(
|
|
profile="reviewer",
|
|
title="Group: room-1",
|
|
source=ROOM_SESSION_SOURCE,
|
|
)
|
|
transport.submit(
|
|
profile="reviewer",
|
|
session_id="group-session",
|
|
prompt=f"Turn {index}",
|
|
source=ROOM_SESSION_SOURCE,
|
|
task=TaskIdentity("room-1", f"task-{index}", "thread-1", f"turn-{index}"),
|
|
execution_generation=1,
|
|
on_terminal=lambda _receipt: None,
|
|
)
|
|
dispatch = next(
|
|
params for method, params in client.calls if method == "dispatch"
|
|
)
|
|
observed.append(dispatch["dispatch"]["source_event_seq"])
|
|
assert observed == [7, 42]
|
|
|
|
|
|
def test_peer_transport_rejects_profile_source_and_room_title_mismatch():
|
|
transport = _transport()
|
|
for kwargs in (
|
|
{"profile": "other", "title": "Group: room-1", "source": "bot_room"},
|
|
{"profile": "reviewer", "title": "Bot Chat", "source": "bot_room"},
|
|
{"profile": "reviewer", "title": "Group: room-1", "source": "cli"},
|
|
):
|
|
try:
|
|
transport.resolve_exact(**kwargs)
|
|
except ValueError:
|
|
continue
|
|
raise AssertionError(f"mismatch was accepted: {kwargs}")
|
|
|
|
|
|
def test_roomlink_falls_back_to_relay_on_retryable_prepare_failure():
|
|
direct = FailingPeerClient(method="prepare")
|
|
relay = FakePeerClient()
|
|
client = FailoverHostedRoomPeerClient([
|
|
RoomLinkCandidate("direct", "direct", "install-peer", direct),
|
|
RoomLinkCandidate("relay", "relay", "install-peer", relay),
|
|
])
|
|
|
|
session = client.prepare(
|
|
room_id="room-1",
|
|
profile="reviewer",
|
|
source="bot_room",
|
|
grant="grant",
|
|
create=True,
|
|
)
|
|
|
|
assert session["session_id"] == "group-session"
|
|
assert client.active_link.name == "relay"
|
|
|
|
|
|
def test_roomlink_never_falls_back_after_ambiguous_direct_failure():
|
|
direct = FailingPeerClient(method="dispatch")
|
|
relay = FakePeerClient()
|
|
client = FailoverHostedRoomPeerClient([
|
|
RoomLinkCandidate("direct", "direct", "install-peer", direct),
|
|
RoomLinkCandidate("relay", "relay", "install-peer", relay),
|
|
])
|
|
dispatch = {"task_id": "task-1", "execution_generation": 1}
|
|
|
|
try:
|
|
client.dispatch(dispatch=dispatch, grant="grant")
|
|
except PeerRunsHTTPError as exc:
|
|
assert exc.ambiguous is True
|
|
else:
|
|
raise AssertionError("ambiguous dispatch was automatically replayed")
|
|
|
|
assert direct.calls[0][1]["dispatch"] is dispatch
|
|
assert relay.calls == []
|
|
assert client.active_link.name == "direct"
|
|
|
|
|
|
def test_roomlink_falls_back_after_proven_not_admitted_direct_failure():
|
|
direct = FailingPeerClient(method="dispatch", not_admitted=True)
|
|
relay = FakePeerClient()
|
|
client = FailoverHostedRoomPeerClient([
|
|
RoomLinkCandidate("direct", "direct", "install-peer", direct),
|
|
RoomLinkCandidate("relay", "relay", "install-peer", relay),
|
|
])
|
|
dispatch = {"task_id": "task-1", "execution_generation": 1}
|
|
|
|
result = client.dispatch(dispatch=dispatch, grant="grant")
|
|
|
|
assert result["status"] == "accepted"
|
|
assert direct.calls[0][1]["dispatch"] is dispatch
|
|
assert relay.calls[0][1]["dispatch"] is dispatch
|
|
assert client.active_link.name == "relay"
|
|
|
|
|
|
def test_roomlink_never_falls_back_after_nonretryable_rejection():
|
|
rejected = FailingPeerClient(method="prepare", retryable=False)
|
|
relay = FakePeerClient()
|
|
client = FailoverHostedRoomPeerClient([
|
|
RoomLinkCandidate("direct", "direct", "install-peer", rejected),
|
|
RoomLinkCandidate("relay", "relay", "install-peer", relay),
|
|
])
|
|
|
|
try:
|
|
client.prepare(
|
|
room_id="room-1",
|
|
profile="reviewer",
|
|
source="bot_room",
|
|
grant="grant",
|
|
create=True,
|
|
)
|
|
except PeerRunsHTTPError:
|
|
pass
|
|
else:
|
|
raise AssertionError("nonretryable rejection was silently bypassed")
|
|
assert relay.calls == []
|
|
|
|
|
|
def test_roomlink_reprobes_and_upgrades_back_to_primary_after_cooldown():
|
|
now = [0.0]
|
|
direct = FailingPeerClient(method="prepare")
|
|
relay = FakePeerClient()
|
|
client = FailoverHostedRoomPeerClient(
|
|
[
|
|
RoomLinkCandidate("direct", "direct", "install-peer", direct),
|
|
RoomLinkCandidate("relay", "relay", "install-peer", relay),
|
|
],
|
|
reprobe_interval_seconds=30,
|
|
clock=lambda: now[0],
|
|
)
|
|
client.prepare(
|
|
room_id="room-1",
|
|
profile="reviewer",
|
|
source="bot_room",
|
|
grant="grant",
|
|
create=True,
|
|
)
|
|
assert client.active_link.name == "relay"
|
|
|
|
direct.method = "none"
|
|
now[0] = 10
|
|
client.prepare(
|
|
room_id="room-1",
|
|
profile="reviewer",
|
|
source="bot_room",
|
|
grant="grant",
|
|
create=True,
|
|
)
|
|
assert client.active_link.name == "relay"
|
|
|
|
now[0] = 31
|
|
client.prepare(
|
|
room_id="room-1",
|
|
profile="reviewer",
|
|
source="bot_room",
|
|
grant="grant",
|
|
create=True,
|
|
)
|
|
assert client.active_link.name == "direct"
|