94 lines
3.7 KiB
Python
94 lines
3.7 KiB
Python
"""Real session router + disposable SQLite stores for the JS bridge contract.
|
|
|
|
Run by sessions-api.test.mjs. Only service dependencies are replaced; query
|
|
validation, route handling, SQL filters and pagination are production code.
|
|
"""
|
|
import os
|
|
from pathlib import Path
|
|
import socket
|
|
import sys
|
|
import tempfile
|
|
import threading
|
|
import time
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parents[3]))
|
|
|
|
with tempfile.TemporaryDirectory(prefix="aiturk-chrome-api-") as directory:
|
|
os.environ["HERMES_HOME"] = directory
|
|
from fastapi import FastAPI, HTTPException, WebSocket
|
|
from fastapi.responses import JSONResponse
|
|
import uvicorn
|
|
from hermes_state import SessionDB
|
|
from hermes_cli.web_routers import sessions
|
|
|
|
paths = {name: Path(directory) / f"{name}.db" for name in ("default", "small", "empty")}
|
|
for profile, count in (("default", 230), ("small", 3), ("empty", 0)):
|
|
db = SessionDB(db_path=paths[profile])
|
|
for index in range(count):
|
|
sid = f"{profile}-desktop-{index:03d}"
|
|
db.create_session(sid, source="desktop")
|
|
db.set_session_title(sid, sid)
|
|
db.append_message(sid, role="user", content="Fixture context")
|
|
if count:
|
|
for index in range(240):
|
|
sid = f"{profile}-cron-{index:03d}"
|
|
db.create_session(sid, source="cron")
|
|
db.set_session_title(sid, sid)
|
|
db.append_message(sid, role="user", content="Not a desktop conversation")
|
|
db.create_session(f"{profile}-blank", source="desktop")
|
|
db.create_session(f"{profile}-archived", source="desktop")
|
|
db.append_message(f"{profile}-archived", role="user", content="Archived")
|
|
db._conn.execute("UPDATE sessions SET archived=1 WHERE id=?", (f"{profile}-archived",))
|
|
db._conn.commit()
|
|
db.close()
|
|
|
|
def profile_home(profile):
|
|
if profile not in paths:
|
|
raise HTTPException(status_code=404)
|
|
return profile, Path(directory)
|
|
|
|
sessions._cron_profile_home = profile_home
|
|
sessions._cron_default_profile = lambda: "default"
|
|
sessions._maybe_auto_archive_for_profile = lambda profile: None
|
|
sessions._open_session_db_for_profile = lambda profile, read_only=True: SessionDB(
|
|
db_path=paths[profile or "default"], read_only=read_only
|
|
)
|
|
# The fixture stores no system/model secrets; list projection is unrelated
|
|
# to this contract and must not import the full web-server startup path.
|
|
sessions._strip_session_list_rows = lambda rows: None
|
|
app = FastAPI()
|
|
app.include_router(sessions.list_router)
|
|
|
|
@app.middleware("http")
|
|
async def auth(request, call_next):
|
|
if request.headers.get("X-Hermes-Session-Token") != "fixture-only":
|
|
return JSONResponse({}, status_code=401)
|
|
return await call_next(request)
|
|
|
|
@app.websocket("/api/ws")
|
|
async def ws(websocket: WebSocket):
|
|
await websocket.accept()
|
|
try:
|
|
while True:
|
|
await websocket.receive_text()
|
|
except Exception:
|
|
pass
|
|
|
|
sock = socket.socket()
|
|
sock.bind(("127.0.0.1", 0))
|
|
port = sock.getsockname()[1]
|
|
server = uvicorn.Server(uvicorn.Config(app, log_level="critical"))
|
|
thread = threading.Thread(target=server.run, kwargs={"sockets": [sock]}, daemon=True)
|
|
thread.start()
|
|
deadline = time.monotonic() + 20
|
|
while not server.started:
|
|
if not thread.is_alive() or time.monotonic() > deadline:
|
|
raise RuntimeError("Fixture failed to start")
|
|
time.sleep(0.01)
|
|
print(f"READY {port}", flush=True)
|
|
# EOF shuts down cleanly, including Windows venv launcher/worker pairs.
|
|
sys.stdin.read()
|
|
server.should_exit = True
|
|
thread.join(timeout=15)
|
|
sock.close()
|