Release: gateway approval_id fallback (#6168) + update-check guard (#6180) + state-dir test isolation (#6305) (#6332)
* fix: catch unhandled exception in POST /api/updates/check (defensive hardening) (#6180) * fix: generate non-empty approval_id when gateway approval.request omits it (#6008) (#6168) * chore: mark update-check try/except as defensive-only guard, drop #6086 linkage Per maintainer review, the try/except wrapper is defense-in-depth only — it does NOT fix #6086 (root cause is signal/process-group reaping). Updated log message and added inline comment to make this explicit. Leave #6086 open. * test: isolate state-dir probes from user state * docs(changelog): stamp #6168 approval_id, #6180 update-check guard, #6305 test isolation --------- Co-authored-by: webtecnica <75556242+webtecnica@users.noreply.github.com> Co-authored-by: webtecnica <webtecnica@users.noreply.github.com> Co-authored-by: pxxD1998 <214340659+pxxD1998@users.noreply.github.com> Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
This commit is contained in:
4 files changed
+76
-15
No files matched your search
@@ -12,6 +12,7 @@ truly isolated import and can't pollute the test process.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
@@ -19,20 +20,27 @@ from pathlib import Path
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
_PROBE = (
|
||||
"import api.config as c; "
|
||||
"print(c.STATE_DIR)"
|
||||
)
|
||||
_PROBE = "import api.config as c; print(c.STATE_DIR)"
|
||||
|
||||
|
||||
def _state_dir_for_env(**env_overrides) -> Path:
|
||||
def _state_dir_for_env(platform_home: Path, **env_overrides) -> Path:
|
||||
"""Import api.config in a fresh subprocess under the given env and return
|
||||
the resolved STATE_DIR it computes. None-valued overrides unset the var."""
|
||||
env = dict(os.environ)
|
||||
# Start from a clean slate for the two vars under test so the parent
|
||||
# process's pytest values don't bleed in.
|
||||
# Keep every probe inside fixture-owned platform paths. Inheriting the real
|
||||
# user home lets an import-only probe reconcile pytest workspace defaults
|
||||
# into the user's settings.json before it prints STATE_DIR.
|
||||
env["HOME"] = str(platform_home)
|
||||
env["USERPROFILE"] = str(platform_home)
|
||||
env["LOCALAPPDATA"] = str(platform_home / "AppData" / "Local")
|
||||
# Start from a clean slate for the vars that can redirect state or trigger
|
||||
# startup settings reconciliation in the child process.
|
||||
env.pop("HERMES_HOME", None)
|
||||
env.pop("HERMES_BASE_HOME", None)
|
||||
env.pop("HERMES_WEBUI_STATE_DIR", None)
|
||||
env.pop("HERMES_WEBUI_TEST_STATE_DIR", None)
|
||||
env.pop("HERMES_WEBUI_DEFAULT_WORKSPACE", None)
|
||||
env.pop("HERMES_CONFIG_PATH", None)
|
||||
for key, value in env_overrides.items():
|
||||
if value is None:
|
||||
env.pop(key, None)
|
||||
@@ -50,20 +58,51 @@ def _state_dir_for_env(**env_overrides) -> Path:
|
||||
return Path(out.stdout.strip()).resolve()
|
||||
|
||||
|
||||
def _platform_default_state_dir() -> Path:
|
||||
def _platform_default_state_dir(platform_home: Path) -> Path:
|
||||
"""What STATE_DIR resolves to with HERMES_HOME + STATE_DIR both unset."""
|
||||
return _state_dir_for_env()
|
||||
return _state_dir_for_env(platform_home)
|
||||
|
||||
|
||||
def test_state_dir_probe_does_not_mutate_platform_default_settings(
|
||||
tmp_path, monkeypatch
|
||||
):
|
||||
"""The subprocess probe must not reconcile pytest defaults into user state."""
|
||||
platform_home = tmp_path / "platform-home"
|
||||
if os.name == "nt":
|
||||
settings_file = (
|
||||
platform_home / "AppData" / "Local" / "hermes" / "webui" / "settings.json"
|
||||
)
|
||||
else:
|
||||
settings_file = platform_home / ".hermes" / "webui" / "settings.json"
|
||||
settings_file.parent.mkdir(parents=True)
|
||||
persisted_workspace = tmp_path / "persisted-workspace"
|
||||
persisted_workspace.mkdir()
|
||||
original = (
|
||||
json.dumps({"default_workspace": str(persisted_workspace)}) + "\n"
|
||||
).encode()
|
||||
settings_file.write_bytes(original)
|
||||
|
||||
monkeypatch.setenv("HOME", str(platform_home))
|
||||
monkeypatch.setenv("USERPROFILE", str(platform_home))
|
||||
monkeypatch.setenv(
|
||||
"HERMES_WEBUI_DEFAULT_WORKSPACE", str(tmp_path / "pytest-workspace")
|
||||
)
|
||||
|
||||
state_dir = _platform_default_state_dir(platform_home)
|
||||
|
||||
assert settings_file.read_bytes() == original
|
||||
assert state_dir == settings_file.parent.resolve()
|
||||
|
||||
|
||||
def test_config_state_dir_defaults_to_hermes_home_webui(tmp_path):
|
||||
hermes_home = tmp_path / ".hermes" / "profiles" / "isolated"
|
||||
hermes_home.mkdir(parents=True)
|
||||
|
||||
state_dir = _state_dir_for_env(HERMES_HOME=hermes_home)
|
||||
state_dir = _state_dir_for_env(tmp_path / "platform-home", HERMES_HOME=hermes_home)
|
||||
assert state_dir == (hermes_home / "webui").resolve()
|
||||
|
||||
|
||||
def test_config_state_dir_unchanged_for_normal_install_hermes_home_unset():
|
||||
def test_config_state_dir_unchanged_for_normal_install_hermes_home_unset(tmp_path):
|
||||
"""Backward-compat: with HERMES_HOME unset, STATE_DIR stays at the platform
|
||||
default `<~/.hermes>/webui` — a normal install's state must NOT relocate
|
||||
(the #4449/#4454 state-dir move only affects an explicitly-set HERMES_HOME).
|
||||
@@ -71,12 +110,14 @@ def test_config_state_dir_unchanged_for_normal_install_hermes_home_unset():
|
||||
Cross-check: the unset-HERMES_HOME result must NOT equal the result of
|
||||
pointing HERMES_HOME at an arbitrary other base — i.e. the default is
|
||||
genuinely the platform home, not whatever the test environment injected."""
|
||||
default = _platform_default_state_dir()
|
||||
platform_home = tmp_path / "platform-home"
|
||||
default = _platform_default_state_dir(platform_home)
|
||||
assert default.name == "webui"
|
||||
# Pointing HERMES_HOME elsewhere produces a DIFFERENT dir, proving the unset
|
||||
# case resolves to the platform default rather than echoing an injected base.
|
||||
elsewhere = _state_dir_for_env(HERMES_HOME="/tmp/hermes-4449-elsewhere-base")
|
||||
assert elsewhere == Path("/tmp/hermes-4449-elsewhere-base/webui").resolve()
|
||||
elsewhere_base = tmp_path / "elsewhere-base"
|
||||
elsewhere = _state_dir_for_env(platform_home, HERMES_HOME=elsewhere_base)
|
||||
assert elsewhere == (elsewhere_base / "webui").resolve()
|
||||
assert default != elsewhere
|
||||
|
||||
|
||||
@@ -88,6 +129,7 @@ def test_config_state_dir_explicit_override_takes_precedence(tmp_path):
|
||||
explicit = tmp_path / "custom-state"
|
||||
|
||||
state_dir = _state_dir_for_env(
|
||||
tmp_path / "platform-home",
|
||||
HERMES_HOME=hermes_home,
|
||||
HERMES_WEBUI_STATE_DIR=explicit,
|
||||
)
|
||||
|
||||
Reference in new issue
Block a user