Import AITURK IDE 1.0.0-beta.1 from Hermes 63279301; preserve MIT license
This commit is contained in:
@@ -0,0 +1,201 @@
|
||||
"""Behavioral tests for the live-DB test-isolation guard.
|
||||
|
||||
Forensic background (Aug 2026): pytest fixture rows (chat-1 / wx-chat
|
||||
sessions, gateway_routing scopes under /tmp/pytest-of-*) were found in the
|
||||
developer's REAL ~/.hermes/state.db, and a pytest-spawned process flipped
|
||||
the journal mode under the WAL-mode gateway writer, destroying committed
|
||||
transcripts. The guard under test makes any pytest-context ``SessionDB``
|
||||
construction that resolves to a production state.db fail hard instead of
|
||||
falling through.
|
||||
|
||||
These tests are behavioral: they construct real ``SessionDB`` objects (or
|
||||
drive the real guard function) and assert outcomes — no source reading.
|
||||
"""
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
import hermes_state
|
||||
from gateway.config import GatewayConfig
|
||||
from gateway.session import SessionStore
|
||||
from hermes_state import SessionDB
|
||||
|
||||
# Must match the root the guard itself computes. Hardcoding ``~/.hermes``
|
||||
# silently disarmed every assertion below on Windows, where the real root is
|
||||
# ``%LOCALAPPDATA%\hermes``: the paths under test were then *correctly*
|
||||
# classified as non-production, so the guard never raised and the whole
|
||||
# TestProductionPathRefused class failed for the wrong reason (#82770).
|
||||
REAL_ROOT = hermes_state._real_platform_state_root()
|
||||
if REAL_ROOT is None: # pragma: no cover - no resolvable home on this platform
|
||||
pytest.skip(
|
||||
"no real platform state root to assert against", allow_module_level=True
|
||||
)
|
||||
|
||||
|
||||
class TestProductionPathRefused:
|
||||
def test_explicit_production_db_path_raises(self):
|
||||
"""SessionDB pointed at the real ~/.hermes/state.db must fail hard."""
|
||||
with pytest.raises(RuntimeError, match="live-system guard"):
|
||||
SessionDB(db_path=REAL_ROOT / "state.db")
|
||||
|
||||
def test_production_profile_db_path_raises(self):
|
||||
"""Profile homes under the real root are production too."""
|
||||
with pytest.raises(RuntimeError, match="live-system guard"):
|
||||
SessionDB(db_path=REAL_ROOT / "profiles" / "work" / "state.db")
|
||||
|
||||
def test_read_only_open_of_production_db_raises(self):
|
||||
"""Read-only opens are refused too — tests must not READ live data."""
|
||||
with pytest.raises(RuntimeError, match="live-system guard"):
|
||||
SessionDB(db_path=REAL_ROOT / "state.db", read_only=True)
|
||||
|
||||
def test_unnormalized_production_path_raises(self):
|
||||
"""Symlink-free but unnormalized spellings still resolve and refuse."""
|
||||
sneaky = REAL_ROOT.parent / "subdir" / ".." / REAL_ROOT.name / "state.db"
|
||||
with pytest.raises(RuntimeError, match="live-system guard"):
|
||||
SessionDB(db_path=sneaky)
|
||||
|
||||
def test_default_resolution_to_production_raises(self, monkeypatch):
|
||||
"""The argless-construction path is guarded, not just explicit paths.
|
||||
|
||||
Simulates the escape vector: HERMES_HOME leaked/reset to the real
|
||||
home (subprocess child, stale worktree, gateway-launched shell) so
|
||||
``_default_db_path()`` resolves the production DB.
|
||||
"""
|
||||
monkeypatch.setenv("HERMES_HOME", str(REAL_ROOT))
|
||||
# Neutralize the conftest's DEFAULT_DB_PATH re-pin so the default
|
||||
# resolver follows the (production-pointing) env, as it would in a
|
||||
# process that never imported the hermetic conftest.
|
||||
monkeypatch.setattr(
|
||||
hermes_state, "DEFAULT_DB_PATH", hermes_state._IMPORT_DEFAULT_DB_PATH
|
||||
)
|
||||
with pytest.raises(RuntimeError, match="live-system guard"):
|
||||
SessionDB()
|
||||
|
||||
|
||||
class TestHermeticPathsAllowed:
|
||||
def test_tmp_db_path_works(self, tmp_path):
|
||||
db = SessionDB(db_path=tmp_path / "state.db")
|
||||
try:
|
||||
db.create_session("iso-guard-session", "cli")
|
||||
assert db.get_session("iso-guard-session") is not None
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
def test_tmp_hermes_home_default_resolution_works(self, tmp_path, monkeypatch):
|
||||
"""Argless SessionDB() under a hermetic HERMES_HOME must succeed."""
|
||||
monkeypatch.setenv("HERMES_HOME", str(tmp_path / "hermetic-home"))
|
||||
monkeypatch.setattr(
|
||||
hermes_state, "DEFAULT_DB_PATH", hermes_state._IMPORT_DEFAULT_DB_PATH
|
||||
)
|
||||
db = SessionDB()
|
||||
try:
|
||||
assert str(tmp_path) in str(db.db_path)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
class TestBypassMarker:
|
||||
@pytest.mark.live_system_guard_bypass
|
||||
def test_bypass_marker_disables_state_db_guard(self):
|
||||
"""The established escape-hatch marker must let production paths pass.
|
||||
|
||||
Drives the guard function directly (never actually opens the live
|
||||
DB) — with the bypass marker active it must not raise.
|
||||
"""
|
||||
hermes_state._ensure_test_isolation(REAL_ROOT / "state.db")
|
||||
|
||||
|
||||
class TestSessionStoreLoudFailure:
|
||||
def test_guard_error_is_not_swallowed_into_jsonl_fallback(
|
||||
self, tmp_path, monkeypatch
|
||||
):
|
||||
"""SessionStore must re-raise the guard error, not degrade to JSONL.
|
||||
|
||||
The historical failure mode: SessionDB() blew up (or silently
|
||||
opened the live DB) inside SessionStore.__init__'s blanket
|
||||
``except Exception`` and the gateway carried on. A guard trip must
|
||||
be loud.
|
||||
"""
|
||||
|
||||
def _boom(*args, **kwargs):
|
||||
raise RuntimeError(
|
||||
"live-system guard: test attempted to open production state.db"
|
||||
)
|
||||
|
||||
monkeypatch.setattr(hermes_state, "SessionDB", _boom)
|
||||
with pytest.raises(RuntimeError, match="live-system guard"):
|
||||
SessionStore(sessions_dir=tmp_path, config=GatewayConfig())
|
||||
|
||||
def test_ordinary_db_failure_still_degrades_to_jsonl(
|
||||
self, tmp_path, monkeypatch
|
||||
):
|
||||
"""Non-guard SQLite failures keep the existing graceful fallback."""
|
||||
|
||||
def _boom(*args, **kwargs):
|
||||
raise RuntimeError("disk on fire")
|
||||
|
||||
monkeypatch.setattr(hermes_state, "SessionDB", _boom)
|
||||
store = SessionStore(sessions_dir=tmp_path, config=GatewayConfig())
|
||||
assert store._db is None
|
||||
|
||||
|
||||
class TestSubprocessChildCovered:
|
||||
def test_child_without_hermes_home_is_refused(self, tmp_path):
|
||||
"""A subprocess child of a test (no HERMES_HOME) must be blocked.
|
||||
|
||||
This is the real leak vector: tests spawning ``python -m ...``
|
||||
children that never import the hermetic conftest. The guard is
|
||||
env-activated (PYTEST_CURRENT_TEST / PYTEST_VERSION are inherited),
|
||||
so the child's argless SessionDB() must fail hard instead of
|
||||
opening the developer's real state.db.
|
||||
"""
|
||||
env = {
|
||||
k: v
|
||||
for k, v in os.environ.items()
|
||||
if k not in ("HERMES_HOME", "PYTEST_PLUGINS", "PYTHONPATH")
|
||||
}
|
||||
env["PYTEST_CURRENT_TEST"] = "tests/fake.py::test_child (call)"
|
||||
env["PYTHONPATH"] = str(Path(__file__).resolve().parents[2])
|
||||
code = (
|
||||
"from hermes_state import SessionDB\n"
|
||||
"SessionDB()\n"
|
||||
)
|
||||
proc = subprocess.run(
|
||||
[sys.executable, "-c", code],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
env=env,
|
||||
timeout=120,
|
||||
)
|
||||
assert proc.returncode != 0
|
||||
assert "live-system guard" in proc.stderr
|
||||
|
||||
def test_child_with_tmp_hermes_home_succeeds(self, tmp_path):
|
||||
"""Same child, hermetic HERMES_HOME: must work — no false positive."""
|
||||
env = {
|
||||
k: v
|
||||
for k, v in os.environ.items()
|
||||
if k not in ("PYTEST_PLUGINS", "PYTHONPATH")
|
||||
}
|
||||
env["PYTEST_CURRENT_TEST"] = "tests/fake.py::test_child (call)"
|
||||
env["HERMES_HOME"] = str(tmp_path / "child-home")
|
||||
env["PYTHONPATH"] = str(Path(__file__).resolve().parents[2])
|
||||
code = (
|
||||
"from hermes_state import SessionDB\n"
|
||||
"db = SessionDB()\n"
|
||||
"db.close()\n"
|
||||
"print('OK', db.db_path)\n"
|
||||
)
|
||||
proc = subprocess.run(
|
||||
[sys.executable, "-c", code],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
env=env,
|
||||
timeout=120,
|
||||
)
|
||||
assert proc.returncode == 0, proc.stderr
|
||||
assert "OK" in proc.stdout
|
||||
Reference in New Issue
Block a user