55d9a49c1d
read_only_db_uri() replaces four inline mode=ro URI sites (two of which still used the raw f-string that truncates on ?/# in the home path: state_db_has_structural_damage and collect_state_db_stats). The doctor write probe now applies the live-holder gate in both modes: a quiet store is probed in place as on main, a held store is probed through a read-only snapshot, and a held store over 1 GB is skipped with an info line unless --fix is given (the unconditional copy cost one full DB write per plain doctor run). Connect/backup failures propagate to the existing classification instead of being reported as FTS write-health failures. Observational sessions commands print a migration hint instead of a raw traceback when a read-only opener meets an older schema. Co-authored-by: Ahmett101 <Ahmett101@users.noreply.github.com>
64 lines
2.6 KiB
Python
64 lines
2.6 KiB
Python
"""Regression coverage for #110173: observational `hermes sessions` readers stay read-only."""
|
|
|
|
from argparse import Namespace
|
|
from unittest.mock import MagicMock
|
|
|
|
import pytest
|
|
|
|
import hermes_cli.sessions_cmd as sessions_cmd
|
|
|
|
|
|
@pytest.mark.parametrize("action", ["list", "stats", "pinned"])
|
|
def test_observational_sessions_actions_open_a_read_only_store(monkeypatch, action):
|
|
factory = MagicMock()
|
|
monkeypatch.setattr("hermes_state.SessionDB", factory)
|
|
monkeypatch.setitem(sessions_cmd._DB_HANDLERS, action, lambda _db, _args: None)
|
|
|
|
sessions_cmd.cmd_sessions(Namespace(sessions_action=action))
|
|
|
|
factory.assert_called_once_with(read_only=True)
|
|
|
|
|
|
def test_sessions_observational_commands_on_missing_store_stay_empty(monkeypatch, tmp_path, capsys):
|
|
"""Fresh profile: list/stats/pinned report empty without creating a writable store."""
|
|
import hermes_state
|
|
|
|
db_path = tmp_path / "state.db"
|
|
monkeypatch.setattr(hermes_state, "_default_db_path", lambda: db_path)
|
|
|
|
list_args = Namespace(sessions_action="list", source=None, limit=20, workspace=None)
|
|
assert sessions_cmd.cmd_sessions(list_args) is None
|
|
assert "No sessions found." in capsys.readouterr().out
|
|
|
|
assert sessions_cmd.cmd_sessions(Namespace(sessions_action="stats")) is None
|
|
stats_out = capsys.readouterr().out
|
|
assert "Total sessions: 0" in stats_out
|
|
assert "Total messages: 0" in stats_out
|
|
|
|
pinned_args = Namespace(sessions_action="pinned", source=None, json=False)
|
|
assert sessions_cmd.cmd_sessions(pinned_args) is None
|
|
assert "No pinned sessions" in capsys.readouterr().out
|
|
assert not db_path.exists()
|
|
|
|
|
|
def test_doctor_write_probe_never_touches_a_store_a_live_writer_holds(monkeypatch, tmp_path):
|
|
"""The write probe runs against a read-only snapshot when a gateway holds state.db, in place otherwise."""
|
|
import sqlite3
|
|
|
|
import hermes_state_holders
|
|
import hermes_state_repair
|
|
from hermes_cli import doctor_state
|
|
|
|
state_db = tmp_path / "state.db"
|
|
sqlite3.connect(state_db).execute("CREATE TABLE sessions (id TEXT)").connection.close()
|
|
probed: list = []
|
|
monkeypatch.setattr(hermes_state_repair, "_db_opens_cleanly", lambda path: probed.append(path))
|
|
|
|
monkeypatch.setattr(hermes_state_holders, "live_writer_holds_db", lambda *_a, **_k: True)
|
|
assert doctor_state._write_health_reason(state_db, should_fix=False) is None
|
|
monkeypatch.setattr(hermes_state_holders, "live_writer_holds_db", lambda *_a, **_k: False)
|
|
assert doctor_state._write_health_reason(state_db, should_fix=False) is None
|
|
|
|
assert probed[0] != state_db and probed[0].name == "state.db"
|
|
assert probed[1] == state_db
|