Import AITURK IDE 1.0.0-beta.1 from Hermes 63279301; preserve MIT license
This commit is contained in:
@@ -0,0 +1,234 @@
|
||||
"""Messaging-gateway ownership tests for the hosted Group Chat worker."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import threading
|
||||
import time
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
|
||||
from gateway import hosted_room_driver, hosted_rooms
|
||||
from gateway.run import GatewayRunner
|
||||
from tui_gateway.hosted_room_service import HostedRoomService
|
||||
|
||||
|
||||
class _RPC:
|
||||
def __init__(self) -> None:
|
||||
self.sessions = {}
|
||||
self.submits = []
|
||||
|
||||
def resolve_exact(self, *, profile, title, source):
|
||||
del source
|
||||
return self.sessions.get((profile, title))
|
||||
|
||||
def create(self, *, profile, title, source):
|
||||
del source
|
||||
session = {"session_id": f"{profile}-session", "title": title}
|
||||
self.sessions[(profile, title)] = session
|
||||
return session
|
||||
|
||||
def resume(self, *, profile, session_id, source):
|
||||
del profile, source
|
||||
return {"session_id": session_id}
|
||||
|
||||
def submit(self, **kwargs):
|
||||
self.submits.append(kwargs["profile"])
|
||||
kwargs["on_terminal"]({
|
||||
"status": "settled",
|
||||
"text": f"reply from {kwargs['profile']}",
|
||||
})
|
||||
return {"accepted": True}
|
||||
|
||||
def history(self, **kwargs):
|
||||
del kwargs
|
||||
return []
|
||||
|
||||
def info(self, **kwargs):
|
||||
del kwargs
|
||||
return {"active": False, "task_id": None}
|
||||
|
||||
def interrupt(self, **kwargs):
|
||||
del kwargs
|
||||
raise AssertionError("gateway lifecycle must not interrupt room work")
|
||||
|
||||
|
||||
def _server():
|
||||
return SimpleNamespace(_methods={}, _sessions={}, _sessions_lock=threading.Lock())
|
||||
|
||||
|
||||
def _service(db_path, *, profiles=("default",)):
|
||||
service = HostedRoomService(_server(), db_path=db_path)
|
||||
rpc = _RPC()
|
||||
service.rpc = rpc
|
||||
service.runtime.rpc = rpc
|
||||
service.local_profiles = lambda: profiles
|
||||
return service, rpc
|
||||
|
||||
|
||||
def _wait_for(predicate, timeout=3.0):
|
||||
deadline = time.monotonic() + timeout
|
||||
while time.monotonic() < deadline:
|
||||
if predicate():
|
||||
return
|
||||
time.sleep(0.01)
|
||||
raise AssertionError("condition did not settle before timeout")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_messaging_gateway_supervisor_starts_without_dashboard(monkeypatch):
|
||||
from tui_gateway import methods_groups
|
||||
|
||||
state = {"running": False, "starts": 0}
|
||||
|
||||
class Runtime:
|
||||
def status(self):
|
||||
return {"running": state["running"], "stopping": False}
|
||||
|
||||
service = SimpleNamespace(runtime=Runtime())
|
||||
|
||||
def get_service():
|
||||
return service if state["running"] else None
|
||||
|
||||
def start_service():
|
||||
state["starts"] += 1
|
||||
state["running"] = True
|
||||
return service
|
||||
|
||||
monkeypatch.setattr(methods_groups, "get_hosted_room_service", get_service)
|
||||
monkeypatch.setattr(methods_groups, "start_hosted_room_service", start_service)
|
||||
|
||||
runner = GatewayRunner.__new__(GatewayRunner)
|
||||
started = await runner._ensure_hosted_room_worker()
|
||||
assert started is service
|
||||
assert state == {"running": True, "starts": 1}
|
||||
|
||||
# A dead child is restarted, while a healthy one is left alone.
|
||||
await runner._ensure_hosted_room_worker()
|
||||
assert state["starts"] == 1
|
||||
state["running"] = False
|
||||
await runner._ensure_hosted_room_worker()
|
||||
assert state["starts"] == 2
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_dead_room_worker_is_restarted_by_gateway_task_supervision(monkeypatch):
|
||||
from tui_gateway import methods_groups
|
||||
|
||||
starts = {"count": 0}
|
||||
|
||||
def fail_start():
|
||||
starts["count"] += 1
|
||||
raise RuntimeError("worker unavailable")
|
||||
|
||||
monkeypatch.setattr(methods_groups, "get_hosted_room_service", lambda: None)
|
||||
monkeypatch.setattr(methods_groups, "start_hosted_room_service", fail_start)
|
||||
monkeypatch.setattr(GatewayRunner, "_MAX_SUPERVISED_RESTARTS", 1)
|
||||
monkeypatch.setattr(
|
||||
GatewayRunner,
|
||||
"_supervised_backoff",
|
||||
staticmethod(lambda _attempt: 0),
|
||||
)
|
||||
|
||||
runner = GatewayRunner.__new__(GatewayRunner)
|
||||
runner._running = True
|
||||
runner._background_tasks = set()
|
||||
runner._spawn_supervised(
|
||||
lambda: runner._hosted_room_worker_watcher(interval=0),
|
||||
"hosted_room_worker",
|
||||
)
|
||||
|
||||
for _ in range(200):
|
||||
if starts["count"] == 2 and not runner._background_tasks:
|
||||
break
|
||||
await asyncio.sleep(0.01)
|
||||
runner._running = False
|
||||
|
||||
assert starts["count"] == 2
|
||||
assert runner._background_tasks == set()
|
||||
|
||||
|
||||
def test_gateway_restart_resumes_queued_room_for_multiplexed_profile(tmp_path):
|
||||
db = tmp_path / "state.db"
|
||||
first, _ = _service(db, profiles=("default", "ops"))
|
||||
first.create_room(
|
||||
room_id="room-1",
|
||||
name="Release room",
|
||||
members=[
|
||||
{
|
||||
"member_id": "default",
|
||||
"profile": "default",
|
||||
"handle": "hermes",
|
||||
},
|
||||
{"member_id": "ops", "profile": "ops", "handle": "ops"},
|
||||
],
|
||||
)
|
||||
first.send(
|
||||
room_id="room-1",
|
||||
event_id="user-1",
|
||||
payload={"text": "@ops inspect", "thread_id": "thread-1"},
|
||||
)
|
||||
assert (
|
||||
len(hosted_room_driver.list_tasks(db, room_id="room-1", status="queued")) == 1
|
||||
)
|
||||
|
||||
resumed, rpc = _service(db, profiles=("default", "ops"))
|
||||
resumed.start()
|
||||
try:
|
||||
_wait_for(
|
||||
lambda: any(
|
||||
event["kind"] == "message.member"
|
||||
for event in hosted_rooms.read_events(
|
||||
db, room_id="room-1", since_seq=0
|
||||
)["events"]
|
||||
)
|
||||
)
|
||||
finally:
|
||||
assert resumed.stop(timeout=5.0)
|
||||
|
||||
assert rpc.submits == ["ops"]
|
||||
assert hosted_room_driver.list_tasks(db, room_id="room-1", status="settled")
|
||||
|
||||
|
||||
def test_dashboard_and_gateway_workers_share_one_fenced_execution_owner(tmp_path):
|
||||
db = tmp_path / "state.db"
|
||||
gateway, gateway_rpc = _service(db, profiles=("default", "ops"))
|
||||
dashboard, dashboard_rpc = _service(db, profiles=("default", "ops"))
|
||||
gateway.create_room(
|
||||
room_id="room-1",
|
||||
name="Release room",
|
||||
members=[
|
||||
{
|
||||
"member_id": "default",
|
||||
"profile": "default",
|
||||
"handle": "hermes",
|
||||
},
|
||||
{"member_id": "ops", "profile": "ops", "handle": "ops"},
|
||||
],
|
||||
)
|
||||
gateway.send(
|
||||
room_id="room-1",
|
||||
event_id="user-1",
|
||||
payload={"text": "@ops inspect", "thread_id": "thread-1"},
|
||||
)
|
||||
|
||||
gateway.start()
|
||||
dashboard.start()
|
||||
try:
|
||||
_wait_for(
|
||||
lambda: any(
|
||||
event["kind"] == "message.member"
|
||||
for event in hosted_rooms.read_events(
|
||||
db, room_id="room-1", since_seq=0
|
||||
)["events"]
|
||||
)
|
||||
)
|
||||
time.sleep(0.05)
|
||||
finally:
|
||||
assert gateway.stop(timeout=5.0)
|
||||
assert dashboard.stop(timeout=5.0)
|
||||
|
||||
assert len(gateway_rpc.submits) + len(dashboard_rpc.submits) == 1
|
||||
events = hosted_rooms.read_events(db, room_id="room-1", since_seq=0)["events"]
|
||||
assert sum(event["kind"] == "message.member" for event in events) == 1
|
||||
Reference in New Issue
Block a user