88 lines
3.1 KiB
Python
88 lines
3.1 KiB
Python
"""Tests for the managed Hermes response-transform lifecycle patch."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import importlib.util
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
PATCHER = ROOT / "hermes-agent" / "patches" / "apply_response_transform_hook.py"
|
|
|
|
|
|
def load_patcher():
|
|
spec = importlib.util.spec_from_file_location("hermes_response_transform_patcher", PATCHER)
|
|
assert spec and spec.loader
|
|
module = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(module)
|
|
return module
|
|
|
|
|
|
def upstream_fixture() -> str:
|
|
return '''class Agent:
|
|
def run(self, messages, user_message, completed, effective_task_id, conversation_history,
|
|
final_response, interrupted, original_user_message):
|
|
# Save trajectory if enabled
|
|
self._save_trajectory(messages, user_message, completed)
|
|
|
|
# Clean up VM and browser for this task after conversation completes
|
|
self._cleanup_task_resources(effective_task_id)
|
|
|
|
# Persist session to both JSON log and SQLite
|
|
self._persist_session(messages, conversation_history)
|
|
|
|
|
|
# Plugin hook: post_llm_call
|
|
if final_response and not interrupted:
|
|
from hermes_cli.plugins import invoke_hook as _invoke_hook
|
|
_invoke_hook(
|
|
"post_llm_call",
|
|
session_id=self.session_id,
|
|
user_message=original_user_message,
|
|
assistant_response=final_response,
|
|
)
|
|
|
|
# Extract reasoning from the last assistant message (if any)
|
|
last_reasoning = None
|
|
return final_response, last_reasoning
|
|
'''
|
|
|
|
|
|
def test_patch_moves_response_transform_before_persistence_and_is_idempotent() -> None:
|
|
module = load_patcher()
|
|
patched, changed = module.patch_source(upstream_fixture())
|
|
|
|
assert changed is True
|
|
assert module.PATCH_MARKER in patched
|
|
assert patched.index('_candidate = _post_result.get("assistant_response")') < patched.index(
|
|
"self._save_trajectory(messages, user_message, completed)"
|
|
)
|
|
assert patched.index("self._save_trajectory(messages, user_message, completed)") < patched.index(
|
|
"self._persist_session(messages, conversation_history)"
|
|
)
|
|
assert module.patch_source(patched) == (patched, False)
|
|
|
|
|
|
def test_patch_refuses_unknown_hermes_lifecycle() -> None:
|
|
module = load_patcher()
|
|
with pytest.raises(ValueError, match="anchors changed"):
|
|
module.patch_source("class Agent:\n pass\n")
|
|
|
|
|
|
def test_install_check_reports_patch_required_then_installed(tmp_path: Path) -> None:
|
|
module = load_patcher()
|
|
target = tmp_path / "run_agent.py"
|
|
target.write_text(upstream_fixture(), encoding="utf-8")
|
|
|
|
before, before_code = module.install(target, check=True)
|
|
assert before_code == 1
|
|
assert before["status"] == "patch_required"
|
|
|
|
applied, applied_code = module.install(target, check=False)
|
|
assert applied_code == 0
|
|
assert applied["status"] == "installed_now"
|
|
|
|
after, after_code = module.install(target, check=True)
|
|
assert after_code == 0
|
|
assert after["status"] == "installed"
|