Import AITURK IDE 1.0.0-beta.1 from Hermes 63279301; preserve MIT license
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
"""E2E: ``hermes peer dm`` against a REAL api_server gateway whose canonical
|
||||
Bot Chat session is HIDDEN (issue #91583).
|
||||
|
||||
Two real HERMES homes in spirit: the "peer" side is a real
|
||||
:class:`APIServerAdapter` bound to a real loopback TCP socket over a real
|
||||
SQLite ``state.db`` (its own tmp HERMES_HOME) containing a hidden
|
||||
``Bot Chat`` row — exactly what Bot Mode leaves behind. The "local" side is
|
||||
the stock ``hermes peer dm`` client code (``hermes_cli.subcommands.peer``),
|
||||
untouched, talking real HTTP with the real API key auth.
|
||||
|
||||
Only the model turn itself is stubbed (``_run_agent``); every HTTP handler,
|
||||
auth check, and SQLite query in the resolution chain is the real thing.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import threading
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
from aiohttp import web
|
||||
|
||||
from gateway.config import PlatformConfig
|
||||
from gateway.platforms.api_server import APIServerAdapter
|
||||
from hermes_cli.subcommands import peer as peer_cmd
|
||||
from hermes_state import SessionDB
|
||||
|
||||
API_KEY = "sk-peer-e2e-key-123456"
|
||||
|
||||
|
||||
def _build_app(adapter: APIServerAdapter) -> web.Application:
|
||||
app = web.Application()
|
||||
app.router.add_get("/api/sessions", adapter._handle_list_sessions)
|
||||
app.router.add_post("/api/sessions", adapter._handle_create_session)
|
||||
app.router.add_post("/api/sessions/{session_id}/chat", adapter._handle_session_chat)
|
||||
return app
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def peer_gateway(tmp_path, monkeypatch):
|
||||
"""A real api_server gateway (own HERMES_HOME + state.db) on a real socket."""
|
||||
peer_home = tmp_path / "peer_home"
|
||||
peer_home.mkdir()
|
||||
db = SessionDB(peer_home / "state.db")
|
||||
|
||||
# Bot Mode's footprint: the canonical Bot Chat exists and is HIDDEN.
|
||||
hidden_id = db.create_session("botchat_hidden_1", "gateway_botmode")
|
||||
assert db.set_session_title(hidden_id, "Bot Chat")
|
||||
assert db.set_session_hidden(hidden_id, True)
|
||||
# And a decoy visible session, so the listing isn't trivially empty.
|
||||
other_id = db.create_session("ordinary_1", "api_server")
|
||||
db.set_session_title(other_id, "Ordinary Chat")
|
||||
|
||||
adapter = APIServerAdapter(PlatformConfig(enabled=True, extra={"key": API_KEY}))
|
||||
adapter._session_db = db
|
||||
|
||||
async def fake_run_agent(user_message, **kwargs):
|
||||
return (
|
||||
{
|
||||
"final_response": f"e2e reply to: {user_message}",
|
||||
"session_id": kwargs.get("session_id"),
|
||||
},
|
||||
{},
|
||||
)
|
||||
|
||||
adapter._run_agent = fake_run_agent
|
||||
|
||||
loop = asyncio.new_event_loop()
|
||||
started = threading.Event()
|
||||
state = {}
|
||||
|
||||
def _serve():
|
||||
asyncio.set_event_loop(loop)
|
||||
|
||||
async def _start():
|
||||
runner = web.AppRunner(_build_app(adapter))
|
||||
await runner.setup()
|
||||
site = web.TCPSite(runner, "127.0.0.1", 0)
|
||||
await site.start()
|
||||
state["runner"] = runner
|
||||
state["port"] = runner.addresses[0][1]
|
||||
started.set()
|
||||
|
||||
loop.run_until_complete(_start())
|
||||
loop.run_forever()
|
||||
|
||||
thread = threading.Thread(target=_serve, daemon=True)
|
||||
thread.start()
|
||||
assert started.wait(timeout=10), "peer gateway failed to start"
|
||||
try:
|
||||
yield SimpleNamespace(
|
||||
url=f"http://127.0.0.1:{state['port']}",
|
||||
db=db,
|
||||
hidden_id=hidden_id,
|
||||
)
|
||||
finally:
|
||||
async def _stop():
|
||||
await state["runner"].cleanup()
|
||||
|
||||
asyncio.run_coroutine_threadsafe(_stop(), loop).result(timeout=10)
|
||||
loop.call_soon_threadsafe(loop.stop)
|
||||
thread.join(timeout=10)
|
||||
close = getattr(db, "close", None)
|
||||
if callable(close):
|
||||
close()
|
||||
|
||||
|
||||
def test_peer_dm_reaches_hidden_canonical_bot_chat_e2e(peer_gateway, monkeypatch, capsys):
|
||||
"""The stock peer dm client resolves the HIDDEN canonical Bot Chat on a
|
||||
real gateway, runs the chat turn on it, and creates no duplicate row."""
|
||||
monkeypatch.setattr(peer_cmd, "_load_peers", lambda: {"spark": {"url": peer_gateway.url}})
|
||||
monkeypatch.setattr(peer_cmd, "_peer_secret", lambda name: API_KEY)
|
||||
|
||||
rc = peer_cmd.cmd_peer(
|
||||
SimpleNamespace(peer_action="dm", target="spark", message="disk status?", json=True)
|
||||
)
|
||||
|
||||
assert rc == 0
|
||||
payload = json.loads(capsys.readouterr().out)
|
||||
assert payload["reply"] == "e2e reply to: disk status?"
|
||||
assert payload["session_id"] == peer_gateway.hidden_id
|
||||
|
||||
# The real DB still holds exactly ONE Bot Chat row and it is still hidden.
|
||||
rows = peer_gateway.db.list_sessions_rich(limit=200, include_hidden=True)
|
||||
bot_chats = [r for r in rows if (r.get("title") or "").strip() == "Bot Chat"]
|
||||
assert [r["id"] for r in bot_chats] == [peer_gateway.hidden_id]
|
||||
assert bool(bot_chats[0].get("hidden"))
|
||||
|
||||
|
||||
def test_hidden_lookup_requires_title_filter_e2e(peer_gateway):
|
||||
"""Server-side bound: include_hidden without a title filter stays a
|
||||
visible-only listing (no blanket hidden exposure on this surface)."""
|
||||
import urllib.request
|
||||
|
||||
req = urllib.request.Request(
|
||||
f"{peer_gateway.url}/api/sessions?limit=200&include_hidden=1",
|
||||
headers={"Authorization": f"Bearer {API_KEY}"},
|
||||
)
|
||||
with urllib.request.urlopen(req, timeout=10) as resp:
|
||||
listing = json.loads(resp.read().decode())
|
||||
ids = [s["id"] for s in listing["data"]]
|
||||
assert peer_gateway.hidden_id not in ids
|
||||
assert "ordinary_1" in ids
|
||||
Reference in New Issue
Block a user