180 lines
5.7 KiB
Python
180 lines
5.7 KiB
Python
"""Scoped grant UAT: home service to a real peer API adapter, no Desktop."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import threading
|
|
from pathlib import Path
|
|
from types import SimpleNamespace
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
from aiohttp import web
|
|
from aiohttp.test_utils import TestServer
|
|
|
|
from gateway.config import PlatformConfig
|
|
from gateway.hosted_rooms import local_authority_gateway_id
|
|
from gateway.platforms.api_server import APIServerAdapter
|
|
from tui_gateway.hosted_room_peer_http import PeerRunsHTTPClient
|
|
from tui_gateway.hosted_room_peer_transport import PeerMemberRoute
|
|
from tui_gateway.hosted_room_service import HostedRoomService
|
|
|
|
|
|
class _LocalRPC:
|
|
def resolve_exact(self, **kwargs):
|
|
return None
|
|
|
|
def create(self, **kwargs):
|
|
return {"session_id": "local-session"}
|
|
|
|
def resume(self, **kwargs):
|
|
return {"session_id": kwargs["session_id"]}
|
|
|
|
def submit(self, **kwargs):
|
|
kwargs["on_terminal"]({"status": "settled", "text": "local reply"})
|
|
return {"accepted": True}
|
|
|
|
def history(self, **kwargs):
|
|
return []
|
|
|
|
def info(self, **kwargs):
|
|
return {"active": False, "task_id": None}
|
|
|
|
def interrupt(self, **kwargs):
|
|
return {"interrupted": True}
|
|
|
|
|
|
def _server_module():
|
|
return SimpleNamespace(_methods={}, _sessions={}, _sessions_lock=threading.Lock())
|
|
|
|
|
|
def _target_app(adapter):
|
|
app = web.Application()
|
|
app.router.add_post(
|
|
"/v1/room-members/invitations",
|
|
adapter._handle_room_member_invitation,
|
|
)
|
|
app.router.add_get(
|
|
"/v1/room-members/capabilities",
|
|
adapter._handle_room_member_capabilities,
|
|
)
|
|
app.router.add_post("/v1/runs", adapter._handle_runs)
|
|
app.router.add_get("/v1/runs/{run_id}", adapter._handle_get_run)
|
|
app.router.add_post("/v1/runs/{run_id}/stop", adapter._handle_stop_run)
|
|
return app
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_in_process_scoped_transport_contract_finishes_headlessly(
|
|
tmp_path: Path,
|
|
):
|
|
target = APIServerAdapter(
|
|
PlatformConfig(enabled=True, extra={"key": "target-peer-key-1234567890"})
|
|
)
|
|
target._run_idempotency_store.close()
|
|
from gateway.platforms.api_server import RunIdempotencyStore
|
|
|
|
target._run_idempotency_store = RunIdempotencyStore(
|
|
str(tmp_path / "target-runs.db")
|
|
)
|
|
server = TestServer(_target_app(target))
|
|
await server.start_server()
|
|
client = PeerRunsHTTPClient(
|
|
base_url=str(server.make_url("")).rstrip("/"),
|
|
api_key="target-peer-key-1234567890",
|
|
)
|
|
home_install_id = local_authority_gateway_id()
|
|
invitation = await asyncio.to_thread(
|
|
client.issue_invitation,
|
|
room_id="room-1",
|
|
home_install_id=home_install_id,
|
|
authority_gateway_id=home_install_id,
|
|
authority_epoch=1,
|
|
member_id="member-peer",
|
|
grant_id="grant-room-1",
|
|
)
|
|
catalog = invitation["catalog"]
|
|
probe = await asyncio.to_thread(
|
|
client.probe,
|
|
grant=invitation["grant"],
|
|
)
|
|
assert probe["catalog"] == catalog
|
|
route = PeerMemberRoute(
|
|
home_install_id=home_install_id,
|
|
member_id="member-peer",
|
|
target_install_id=catalog["installation_id"],
|
|
target_profile="default",
|
|
capability_digest=catalog["catalog_digest"],
|
|
execution_policy_digest=catalog["execution_policy"]["policy_digest"],
|
|
cancellation_scope_id="cancel-room-1",
|
|
trace_id="trace-room-1",
|
|
grant=invitation["grant"],
|
|
)
|
|
home = HostedRoomService(
|
|
_server_module(),
|
|
db_path=tmp_path / "home-state.db",
|
|
peer_routes={("room-1", "member-peer"): route},
|
|
peer_clients={catalog["installation_id"]: client},
|
|
)
|
|
home.rpc = _LocalRPC()
|
|
home.runtime.rpc = home.rpc
|
|
home.local_profiles = lambda: ("local",)
|
|
home.create_room(
|
|
room_id="room-1",
|
|
name="Scoped room",
|
|
members=[
|
|
{"member_id": "local", "profile": "local", "handle": "local"},
|
|
{
|
|
"member_id": "member-peer",
|
|
"profile": "default",
|
|
"handle": "reviewer",
|
|
"target": {
|
|
"kind": "peer",
|
|
"peer_id": "peer-target",
|
|
"installation_id": catalog["installation_id"],
|
|
"profile": "default",
|
|
"capability_digest": catalog["catalog_digest"],
|
|
},
|
|
},
|
|
],
|
|
)
|
|
|
|
agent = MagicMock()
|
|
agent.run_conversation.return_value = {
|
|
"final_response": "Scoped peer response."
|
|
}
|
|
agent.session_prompt_tokens = agent.session_completion_tokens = (
|
|
agent.session_total_tokens
|
|
) = 0
|
|
with patch.object(target, "_create_agent", return_value=agent):
|
|
home.start()
|
|
home.send(
|
|
room_id="room-1",
|
|
event_id="user-1",
|
|
payload={"text": "@reviewer inspect", "thread_id": "thread-1"},
|
|
)
|
|
deadline = asyncio.get_running_loop().time() + 5
|
|
while asyncio.get_running_loop().time() < deadline:
|
|
if any(
|
|
event["kind"] == "message.member"
|
|
for event in home._events("room-1")
|
|
):
|
|
break
|
|
await asyncio.sleep(0.02)
|
|
else:
|
|
raise AssertionError(
|
|
"peer reply was not published: "
|
|
f"status={home.runtime.status()} events={home._events('room-1')}"
|
|
)
|
|
assert home.stop(timeout=1.0)
|
|
|
|
reply = next(
|
|
event
|
|
for event in home._events("room-1")
|
|
if event["kind"] == "message.member"
|
|
)
|
|
assert reply["payload"]["text"] == "Scoped peer response."
|
|
assert reply["actor"]["connection_id"] == "peer-target"
|
|
await server.close()
|
|
target._run_idempotency_store.close()
|