Import AITURK IDE 1.0.0-beta.1 from Hermes 63279301; preserve MIT license
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
"""Legacy NULL-profile session owner backfill (#94724).
|
||||
|
||||
Pre-#95407 session rows carry ``profile_name = NULL``. Once a Desktop has
|
||||
registry topology (≥2 registered connections) the fail-closed owner ladder
|
||||
can no longer route those rows, making every pre-campaign session
|
||||
unresumable. POST /api/sessions/owner-backfill stamps each store's own
|
||||
serving-profile identity onto its legacy rows — single-match by construction
|
||||
(a profile's state.db belongs to exactly one profile), idempotent, and never
|
||||
overwriting a non-NULL owner.
|
||||
"""
|
||||
|
||||
import sqlite3
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def client(monkeypatch, _isolate_hermes_home):
|
||||
try:
|
||||
from starlette.testclient import TestClient
|
||||
except ImportError:
|
||||
pytest.skip("fastapi/starlette not installed")
|
||||
|
||||
import hermes_state
|
||||
from hermes_constants import get_hermes_home
|
||||
from hermes_cli.web_server import app, _SESSION_HEADER_NAME, _SESSION_TOKEN
|
||||
|
||||
monkeypatch.setattr(hermes_state, "DEFAULT_DB_PATH", get_hermes_home() / "state.db")
|
||||
|
||||
client = TestClient(app)
|
||||
client.headers[_SESSION_HEADER_NAME] = _SESSION_TOKEN
|
||||
return client
|
||||
|
||||
|
||||
def _seed(db_path, rows):
|
||||
"""Insert bare session rows the way a pre-ownership install left them."""
|
||||
from hermes_state import SessionDB
|
||||
|
||||
db = SessionDB(db_path=db_path)
|
||||
try:
|
||||
for session_id, profile_name in rows:
|
||||
db.create_session(session_id, source="cli", profile_name=profile_name)
|
||||
db.append_message(
|
||||
session_id, role="user", content=f"hello from {session_id}"
|
||||
)
|
||||
# create_session backfills nothing here, but be explicit: force the
|
||||
# legacy shape at the SQL level so the fixture cannot silently depend
|
||||
# on create_session's own COALESCE behavior.
|
||||
for session_id, profile_name in rows:
|
||||
if profile_name is None:
|
||||
db._conn.execute(
|
||||
"UPDATE sessions SET profile_name = NULL WHERE id = ?",
|
||||
(session_id,),
|
||||
)
|
||||
db._conn.commit()
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
def _profiles(db_path):
|
||||
conn = sqlite3.connect(str(db_path))
|
||||
try:
|
||||
return dict(conn.execute("SELECT id, profile_name FROM sessions").fetchall())
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def test_backfill_stamps_only_null_rows_and_is_idempotent(client):
|
||||
from hermes_constants import get_hermes_home
|
||||
|
||||
db_path = get_hermes_home() / "state.db"
|
||||
_seed(
|
||||
db_path,
|
||||
[
|
||||
("legacy-null-1", None),
|
||||
("legacy-null-2", None),
|
||||
("owned-other", "researcher"),
|
||||
],
|
||||
)
|
||||
|
||||
resp = client.post("/api/sessions/owner-backfill", json={})
|
||||
assert resp.status_code == 200, resp.text
|
||||
body = resp.json()
|
||||
assert body["ok"] is True
|
||||
# Exactly the two legacy rows were stamped; the count is reported so the
|
||||
# caller can log it.
|
||||
assert body["stamped"] == 2
|
||||
assert body["profile"] == "default"
|
||||
|
||||
stamped = _profiles(db_path)
|
||||
assert stamped["legacy-null-1"] == "default"
|
||||
assert stamped["legacy-null-2"] == "default"
|
||||
# Fail-closed contract: a non-NULL owner is NEVER overwritten, even when
|
||||
# it names a different profile than the serving store.
|
||||
assert stamped["owned-other"] == "researcher"
|
||||
|
||||
# One-shot-per-row: a second run finds nothing left to stamp and the rows
|
||||
# are byte-identical.
|
||||
resp2 = client.post("/api/sessions/owner-backfill", json={})
|
||||
assert resp2.status_code == 200
|
||||
assert resp2.json()["stamped"] == 0
|
||||
assert _profiles(db_path) == stamped
|
||||
|
||||
|
||||
def test_backfilled_rows_circulate_owned_on_the_list_endpoint(client):
|
||||
"""After the backfill, the durable stamp (not just the per-response
|
||||
serving-profile decoration) owns the rows: the raw DB column is non-NULL,
|
||||
which is what survives into any other consumer of state.db."""
|
||||
from hermes_constants import get_hermes_home
|
||||
|
||||
db_path = get_hermes_home() / "state.db"
|
||||
_seed(db_path, [("legacy-null-3", None)])
|
||||
|
||||
assert _profiles(db_path)["legacy-null-3"] is None
|
||||
|
||||
resp = client.post("/api/sessions/owner-backfill", json={})
|
||||
assert resp.status_code == 200
|
||||
assert resp.json()["stamped"] == 1
|
||||
|
||||
listed = client.get("/api/sessions?limit=50&offset=0").json()["sessions"]
|
||||
row = next(s for s in listed if s["id"] == "legacy-null-3")
|
||||
assert row["profile"] == "default"
|
||||
assert _profiles(db_path)["legacy-null-3"] == "default"
|
||||
|
||||
|
||||
def test_backfill_treats_empty_string_profile_as_legacy(client):
|
||||
"""TRIM('') rows are the same stranded class as NULL — stamp them too."""
|
||||
from hermes_constants import get_hermes_home
|
||||
|
||||
db_path = get_hermes_home() / "state.db"
|
||||
_seed(db_path, [("legacy-empty", None)])
|
||||
|
||||
conn = sqlite3.connect(str(db_path))
|
||||
conn.execute("UPDATE sessions SET profile_name = ' ' WHERE id = 'legacy-empty'")
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
resp = client.post("/api/sessions/owner-backfill", json={})
|
||||
assert resp.status_code == 200
|
||||
assert resp.json()["stamped"] == 1
|
||||
assert _profiles(get_hermes_home() / "state.db")["legacy-empty"] == "default"
|
||||
Reference in New Issue
Block a user