53db597201
hermes_state.py: delete every '# noqa: F401 (re-exported...)' import block (hermes_state_common/errors/guard/
readpool/sessions/fts/dbfile/wal/repair/registry + agent.context_compressor _DB_PERSISTED_MARKER_KEY); keep
only the names hermes_state.py itself uses, without noqa.
hermes_state_registry.py: drop get_shared_session_db/release_shared_session_db/close_shared_session_dbs
aliases; every caller (gateway/, tools/, tui_gateway/, cron/, mcp_serve, run_agent, tests) now imports
acquire/release/close_all/release_or_close from hermes_state_registry.
hermes_state_titles.py: drop set_auto_title_if_empty shim (title_generator keeps its getattr fallback).
Re-remove shim-only names restored by 34abf954bd: latest_user_message_row_id (tests call
latest_message_row_id(key, role='user'); role-targeting assertions kept) and get_session_activity (tests
build the snapshot via agent.session_activity.build_activity_snapshot over db.get_session(sid)).
hermes_state_wal._log_once resolves its dedupe sets as module globals instead of via hermes_state;
hermes_state_repair helpers call module globals directly (tests patch hermes_state_repair.<name>).
Frozen updater surface untouched (update_cmd_maint imports only SessionDB from hermes_state).
176 lines
6.8 KiB
Python
176 lines
6.8 KiB
Python
"""Tests for RC2: pre-publication lease refresh in publish_compression_child.
|
|
|
|
When the lease refresher stopped due to transient DB failures, the final
|
|
pre-publication refresh inside the same transaction gives one last chance
|
|
to extend the lease before the expiry check.
|
|
"""
|
|
import sqlite3
|
|
import threading
|
|
import time
|
|
from unittest.mock import patch
|
|
|
|
import pytest
|
|
|
|
from hermes_state import SessionDB
|
|
from hermes_state_errors import CompressionSessionBusyError
|
|
|
|
|
|
def _setup_db(tmp_path):
|
|
db = SessionDB(tmp_path / "state.db")
|
|
return db
|
|
|
|
|
|
def _seed_lock(conn, session_id, holder, expired=False):
|
|
now = time.time()
|
|
conn.execute(
|
|
"INSERT INTO compression_locks (session_id, holder, acquired_at, expires_at) VALUES (?, ?, ?, ?)",
|
|
(session_id, holder, now, (now - 10.0) if expired else (now + 300.0)),
|
|
)
|
|
|
|
|
|
class TestLeaseRefreshBeforePublish:
|
|
|
|
def test_refresher_stopped_final_refresh_succeeds(self, tmp_path):
|
|
db = _setup_db(tmp_path)
|
|
db.create_session("parent-1", source="test")
|
|
_seed_lock(db._conn, "parent-1", "holder-1", expired=True)
|
|
|
|
with patch.object(db, "_execute_write", side_effect=lambda fn: fn(db._conn)):
|
|
db.publish_compression_child(
|
|
parent_session_id="parent-1",
|
|
child_session_id="child-1",
|
|
source="test",
|
|
messages=[{"role": "user", "content": "hello"}],
|
|
compression_lock_holder="holder-1",
|
|
require_compression_lease=True,
|
|
require_lease_refresh=True,
|
|
lease_ttl_seconds=300.0,
|
|
)
|
|
|
|
lock = db._conn.execute(
|
|
"SELECT expires_at FROM compression_locks WHERE session_id = ?",
|
|
("parent-1",),
|
|
).fetchone()
|
|
assert lock is not None
|
|
assert lock[0] > time.time()
|
|
|
|
parent = db._conn.execute(
|
|
"SELECT ended_at FROM sessions WHERE id = ?",
|
|
("parent-1",),
|
|
).fetchone()
|
|
assert parent is not None
|
|
assert parent[0] is not None
|
|
|
|
def test_refresher_stopped_final_refresh_fails_wrong_holder(self, tmp_path):
|
|
db = _setup_db(tmp_path)
|
|
_seed_lock(db._conn, "parent-1", "other-holder", expired=True)
|
|
|
|
with patch.object(db, "_execute_write", side_effect=lambda fn: fn(db._conn)):
|
|
with pytest.raises(CompressionSessionBusyError, match="lease lost"):
|
|
db.publish_compression_child(
|
|
parent_session_id="parent-1",
|
|
child_session_id="child-1",
|
|
source="test",
|
|
messages=[{"role": "user", "content": "hello"}],
|
|
compression_lock_holder="holder-1",
|
|
require_compression_lease=True,
|
|
require_lease_refresh=True,
|
|
lease_ttl_seconds=300.0,
|
|
)
|
|
|
|
def test_refresher_healthy_no_duplicate_behavior(self, tmp_path):
|
|
db = _setup_db(tmp_path)
|
|
db.create_session("parent-1", source="test")
|
|
now = time.time()
|
|
future = now + 300.0
|
|
conn = db._conn
|
|
conn.execute(
|
|
"INSERT INTO compression_locks (session_id, holder, acquired_at, expires_at) VALUES (?, ?, ?, ?)",
|
|
("parent-1", "holder-1", now, future),
|
|
)
|
|
|
|
with patch.object(db, "_execute_write", side_effect=lambda fn: fn(db._conn)):
|
|
db.publish_compression_child(
|
|
parent_session_id="parent-1",
|
|
child_session_id="child-1",
|
|
source="test",
|
|
messages=[{"role": "user", "content": "hello"}],
|
|
compression_lock_holder="holder-1",
|
|
require_compression_lease=True,
|
|
require_lease_refresh=True,
|
|
lease_ttl_seconds=300.0,
|
|
)
|
|
|
|
lock = conn.execute(
|
|
"SELECT expires_at FROM compression_locks WHERE session_id = ?",
|
|
("parent-1",),
|
|
).fetchone()
|
|
assert lock is not None
|
|
assert lock[0] >= future
|
|
|
|
def test_stale_holder_cannot_refresh_and_publish(self, tmp_path):
|
|
db = _setup_db(tmp_path)
|
|
_seed_lock(db._conn, "parent-1", "new-holder", expired=False)
|
|
|
|
with patch.object(db, "_execute_write", side_effect=lambda fn: fn(db._conn)):
|
|
with pytest.raises(CompressionSessionBusyError, match="lease lost"):
|
|
db.publish_compression_child(
|
|
parent_session_id="parent-1",
|
|
child_session_id="child-1",
|
|
source="test",
|
|
messages=[{"role": "user", "content": "hello"}],
|
|
compression_lock_holder="old-holder",
|
|
require_compression_lease=True,
|
|
require_lease_refresh=True,
|
|
lease_ttl_seconds=300.0,
|
|
)
|
|
|
|
def test_no_refresh_when_require_lease_refresh_false(self, tmp_path):
|
|
db = _setup_db(tmp_path)
|
|
_seed_lock(db._conn, "parent-1", "holder-1", expired=True)
|
|
|
|
with patch.object(db, "_execute_write", side_effect=lambda fn: fn(db._conn)):
|
|
with pytest.raises(CompressionSessionBusyError, match="lease lost"):
|
|
db.publish_compression_child(
|
|
parent_session_id="parent-1",
|
|
child_session_id="child-1",
|
|
source="test",
|
|
messages=[{"role": "user", "content": "hello"}],
|
|
compression_lock_holder="holder-1",
|
|
require_compression_lease=True,
|
|
require_lease_refresh=False,
|
|
lease_ttl_seconds=300.0,
|
|
)
|
|
|
|
def test_refresh_and_lease_check_are_atomic(self, tmp_path):
|
|
db = _setup_db(tmp_path)
|
|
db.create_session("parent-1", source="test")
|
|
_seed_lock(db._conn, "parent-1", "holder-1", expired=True)
|
|
|
|
real_execute_write = SessionDB._execute_write
|
|
|
|
def intercepted_execute_write(self, fn, patience_s=None):
|
|
original_fn = fn
|
|
def wrapper(conn):
|
|
result = original_fn(conn)
|
|
lock = conn.execute(
|
|
"SELECT expires_at FROM compression_locks WHERE session_id = ?",
|
|
("parent-1",),
|
|
).fetchone()
|
|
assert lock is not None
|
|
assert lock[0] > time.time()
|
|
return result
|
|
return real_execute_write(self, wrapper, patience_s)
|
|
|
|
with patch.object(SessionDB, "_execute_write", intercepted_execute_write):
|
|
db.publish_compression_child(
|
|
parent_session_id="parent-1",
|
|
child_session_id="child-1",
|
|
source="test",
|
|
messages=[{"role": "user", "content": "hello"}],
|
|
compression_lock_holder="holder-1",
|
|
require_compression_lease=True,
|
|
require_lease_refresh=True,
|
|
lease_ttl_seconds=300.0,
|
|
)
|