216 lines
6.2 KiB
Python
216 lines
6.2 KiB
Python
"""Older Desktop clients cannot start a second driver for hosted rooms."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import sqlite3
|
|
import time
|
|
|
|
import pytest
|
|
|
|
from gateway import hosted_rooms
|
|
import tui_gateway.server as server
|
|
|
|
|
|
def _stub_session(monkeypatch, *, title, profile_home=None):
|
|
monkeypatch.setattr(
|
|
server,
|
|
"_sess_nowait",
|
|
lambda _params, _rid: (
|
|
{
|
|
"id": "session-1",
|
|
"title": title,
|
|
"source": "bot_room",
|
|
"profile_home": str(profile_home) if profile_home else None,
|
|
},
|
|
None,
|
|
),
|
|
)
|
|
|
|
|
|
def test_direct_prompt_to_hosted_group_session_is_rejected(tmp_path, monkeypatch):
|
|
home = tmp_path / ".hermes"
|
|
home.mkdir()
|
|
monkeypatch.setenv("HERMES_HOME", str(home))
|
|
hosted_rooms.create_room(
|
|
hosted_rooms.default_db_path(),
|
|
room_id="room-hosted",
|
|
name="Hosted room",
|
|
members=[
|
|
{"member_id": "one", "profile": "one", "handle": "one"},
|
|
{"member_id": "two", "profile": "two", "handle": "two"},
|
|
],
|
|
authority_gateway_id=hosted_rooms.local_authority_gateway_id(),
|
|
)
|
|
_stub_session(monkeypatch, title="Group: room-hosted")
|
|
|
|
result = server._methods["prompt.submit"](
|
|
"request-1", {"session_id": "session-1", "text": "continue"}
|
|
)
|
|
|
|
assert result["error"]["code"] == 4122
|
|
assert "managed by its gateway" in result["error"]["message"]
|
|
|
|
|
|
def test_direct_prompt_to_non_hosted_group_reaches_normal_admission(
|
|
tmp_path, monkeypatch
|
|
):
|
|
home = tmp_path / ".hermes"
|
|
home.mkdir()
|
|
monkeypatch.setenv("HERMES_HOME", str(home))
|
|
_stub_session(monkeypatch, title="Group: local-only")
|
|
monkeypatch.setattr(
|
|
server,
|
|
"_ensure_active_session_slot",
|
|
lambda _sid, _session: "normal admission reached",
|
|
)
|
|
|
|
result = server._methods["prompt.submit"](
|
|
"request-2", {"session_id": "session-1", "text": "continue"}
|
|
)
|
|
|
|
assert result["error"] == {"code": 4090, "message": "normal admission reached"}
|
|
assert not hosted_rooms.default_db_path().exists()
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"legacy_name",
|
|
(
|
|
"Launch room",
|
|
"Ceo, Product Designer, Cfo",
|
|
"Équipe",
|
|
"Alpha/Beta",
|
|
),
|
|
)
|
|
def test_direct_prompt_to_legacy_named_group_reaches_normal_admission(
|
|
tmp_path, monkeypatch, legacy_name
|
|
):
|
|
home = tmp_path / ".hermes"
|
|
home.mkdir()
|
|
monkeypatch.setenv("HERMES_HOME", str(home))
|
|
_stub_session(monkeypatch, title=f"Group: {legacy_name}")
|
|
monkeypatch.setattr(
|
|
server,
|
|
"_ensure_active_session_slot",
|
|
lambda _sid, _session: "normal admission reached",
|
|
)
|
|
result = server._methods["prompt.submit"](
|
|
"request-legacy", {"session_id": "session-1", "text": "continue"}
|
|
)
|
|
|
|
assert result["error"] == {"code": 4090, "message": "normal admission reached"}
|
|
|
|
|
|
def test_direct_prompt_to_peer_reserved_group_is_rejected_until_revoke(
|
|
tmp_path, monkeypatch
|
|
):
|
|
home = tmp_path / ".hermes"
|
|
home.mkdir()
|
|
profile_home = home / "profiles" / "reviewer"
|
|
profile_home.mkdir(parents=True)
|
|
monkeypatch.setenv("HERMES_HOME", str(home))
|
|
monkeypatch.setattr(server, "_current_profile_name", lambda: "reviewer")
|
|
now = time.time()
|
|
claims = {
|
|
"room_id": "room-peer",
|
|
"home_install_id": "install-home",
|
|
"authority_gateway_id": "install-home",
|
|
"authority_epoch": 1,
|
|
"member_id": "member-reviewer",
|
|
"target_install_id": "install-target",
|
|
"target_profile": "reviewer",
|
|
"issued_at": now,
|
|
}
|
|
hosted_rooms.reserve_peer_room(
|
|
hosted_rooms.default_db_path(),
|
|
claims=claims,
|
|
expires_at=now + 300.0,
|
|
now=now,
|
|
)
|
|
_stub_session(
|
|
monkeypatch,
|
|
title="Group: room-peer",
|
|
profile_home=profile_home,
|
|
)
|
|
|
|
rejected = server._methods["prompt.submit"](
|
|
"request-peer",
|
|
{"session_id": "session-1", "text": "continue"},
|
|
)
|
|
assert rejected["error"]["code"] == 4122
|
|
assert "home host" in rejected["error"]["message"]
|
|
|
|
hosted_rooms.revoke_room_grant_scope(
|
|
hosted_rooms.default_db_path(),
|
|
claims=claims,
|
|
expires_at=now + 300.0,
|
|
now=now + 150.0,
|
|
)
|
|
monkeypatch.setattr(
|
|
server,
|
|
"_ensure_active_session_slot",
|
|
lambda _sid, _session: "normal admission reached",
|
|
)
|
|
admitted = server._methods["prompt.submit"](
|
|
"request-peer-after-revoke",
|
|
{"session_id": "session-1", "text": "continue"},
|
|
)
|
|
assert admitted["error"] == {
|
|
"code": 4090,
|
|
"message": "normal admission reached",
|
|
}
|
|
|
|
|
|
def test_direct_prompt_is_refused_when_room_authority_cannot_be_verified(
|
|
tmp_path, monkeypatch
|
|
):
|
|
home = tmp_path / ".hermes"
|
|
home.mkdir()
|
|
monkeypatch.setenv("HERMES_HOME", str(home))
|
|
_stub_session(monkeypatch, title="Group: room-unknown")
|
|
monkeypatch.setattr(
|
|
hosted_rooms,
|
|
"probe_hosted_room",
|
|
lambda *_args, **_kwargs: (_ for _ in ()).throw(OSError("disk busy")),
|
|
)
|
|
|
|
result = server._methods["prompt.submit"](
|
|
"request-3", {"session_id": "session-1", "text": "continue"}
|
|
)
|
|
|
|
assert result["error"]["code"] == 5122
|
|
assert result["error"]["message"] == (
|
|
"Could not verify this group. Try again after the gateway recovers."
|
|
)
|
|
|
|
|
|
def test_contended_ownership_probe_fails_quickly_without_blocking_socket(
|
|
tmp_path, monkeypatch
|
|
):
|
|
home = tmp_path / ".hermes"
|
|
home.mkdir()
|
|
monkeypatch.setenv("HERMES_HOME", str(home))
|
|
db = hosted_rooms.default_db_path()
|
|
hosted_rooms.create_room(
|
|
db,
|
|
room_id="room-busy",
|
|
name="Busy room",
|
|
members=[],
|
|
authority_gateway_id=hosted_rooms.local_authority_gateway_id(),
|
|
)
|
|
_stub_session(monkeypatch, title="Group: room-busy")
|
|
|
|
blocker = sqlite3.connect(db)
|
|
blocker.execute("PRAGMA journal_mode=DELETE")
|
|
blocker.execute("BEGIN EXCLUSIVE")
|
|
started = time.monotonic()
|
|
try:
|
|
result = server._methods["prompt.submit"](
|
|
"request-busy", {"session_id": "session-1", "text": "continue"}
|
|
)
|
|
finally:
|
|
blocker.rollback()
|
|
blocker.close()
|
|
|
|
assert time.monotonic() - started < 0.5
|
|
assert result["error"]["code"] == 5122
|