mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-21 03:39:54 +00:00
6d80aa80eb
- Remove dead _post_tool_call (body was only comments) - Remove _on_session_start (redundant — _ensure_service lazy-inits) - Remove _atexit_cleanup (duplicate of _on_session_end) - Switch _baselines from dict to set (presence sentinel only) - Remove redundant enabled_for recheck in transform_tool_result - Remove V4A guard (path-empty check already covers it) - Use modern type syntax (X | None, dict[], set[]) - Reduce from 322 → 217 lines, same behavior 77/77 tests pass.
155 lines
5.0 KiB
Python
155 lines
5.0 KiB
Python
"""Integration test: LSP plugin skips non-local paths.
|
|
|
|
The host-side LSP server can't see files inside Docker/Modal/SSH
|
|
sandboxes. The plugin's ``_pre_tool_call`` uses ``os.path.exists``
|
|
on the parent directory as a heuristic local-only gate. These tests
|
|
verify the plugin hooks skip when the path clearly doesn't exist on
|
|
the host filesystem.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import sys
|
|
from unittest.mock import patch
|
|
|
|
import pytest
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _isolate_plugin_state():
|
|
"""Reset plugin module state between tests."""
|
|
# Import the plugin and clear any service state
|
|
from plugins.lsp import _baselines
|
|
_baselines.clear()
|
|
yield
|
|
_baselines.clear()
|
|
|
|
|
|
def test_pre_tool_call_skips_nonexistent_parent_dir():
|
|
"""pre_tool_call returns early when the path's parent dir doesn't exist (Docker/SSH heuristic)."""
|
|
from plugins import lsp as lsp_plugin
|
|
|
|
# Simulate a path that doesn't exist on host (e.g., inside Docker)
|
|
fake_path = "/nonexistent-docker-container-fs/app/main.py"
|
|
|
|
# Mock _ensure_service to return a mock service
|
|
mock_service = type("MockService", (), {
|
|
"is_active": lambda self: True,
|
|
"enabled_for": lambda self, p: True,
|
|
"snapshot_baseline": lambda self, p: None,
|
|
})()
|
|
|
|
with patch.object(lsp_plugin, "_service", mock_service):
|
|
lsp_plugin._pre_tool_call(
|
|
tool_name="write_file",
|
|
args={"path": fake_path},
|
|
session_id="test-session",
|
|
tool_call_id="call-1",
|
|
)
|
|
|
|
# Baseline should NOT be captured because parent dir doesn't exist
|
|
assert ("test-session", os.path.normpath(fake_path)) not in lsp_plugin._baselines
|
|
|
|
|
|
def test_pre_tool_call_proceeds_for_local_path(tmp_path):
|
|
"""pre_tool_call captures baseline when path exists locally."""
|
|
from plugins import lsp as lsp_plugin
|
|
|
|
# Create a real file so the parent-dir check passes
|
|
test_file = tmp_path / "test.py"
|
|
test_file.write_text("x = 1\n")
|
|
|
|
mock_service = type("MockService", (), {
|
|
"is_active": lambda self: True,
|
|
"enabled_for": lambda self, p: True,
|
|
"snapshot_baseline": lambda self, p: None,
|
|
})()
|
|
|
|
with patch.object(lsp_plugin, "_service", mock_service):
|
|
lsp_plugin._pre_tool_call(
|
|
tool_name="write_file",
|
|
args={"path": str(test_file)},
|
|
session_id="test-session",
|
|
tool_call_id="call-2",
|
|
)
|
|
|
|
# Baseline SHOULD be captured because the local path exists
|
|
assert ("test-session", str(test_file)) in lsp_plugin._baselines
|
|
|
|
|
|
def test_pre_tool_call_skips_non_write_tools():
|
|
"""pre_tool_call is a no-op for tools other than write_file/patch."""
|
|
from plugins import lsp as lsp_plugin
|
|
|
|
lsp_plugin._pre_tool_call(
|
|
tool_name="terminal",
|
|
args={"command": "ls"},
|
|
session_id="test-session",
|
|
tool_call_id="call-3",
|
|
)
|
|
|
|
assert len(lsp_plugin._baselines) == 0
|
|
|
|
|
|
def test_pre_tool_call_skips_v4a_patch():
|
|
"""pre_tool_call skips V4A multi-file patches (has 'patch' key, no 'path' key)."""
|
|
from plugins import lsp as lsp_plugin
|
|
|
|
mock_service = type("MockService", (), {
|
|
"is_active": lambda self: True,
|
|
"enabled_for": lambda self, p: True,
|
|
"snapshot_baseline": lambda self, p: None,
|
|
})()
|
|
|
|
with patch.object(lsp_plugin, "_service", mock_service):
|
|
lsp_plugin._pre_tool_call(
|
|
tool_name="patch",
|
|
args={"patch": "*** Begin Patch\n*** Update File: foo.py\n..."},
|
|
session_id="test-session",
|
|
tool_call_id="call-4",
|
|
)
|
|
|
|
assert len(lsp_plugin._baselines) == 0
|
|
|
|
|
|
def test_transform_tool_result_injects_diagnostics(tmp_path):
|
|
"""transform_tool_result appends lsp_diagnostics field to JSON result."""
|
|
from plugins import lsp as lsp_plugin
|
|
|
|
test_file = tmp_path / "test.py"
|
|
abs_path = str(test_file)
|
|
|
|
# Pre-populate a baseline entry (simulating pre_tool_call ran)
|
|
lsp_plugin._baselines.add(("test-session", abs_path))
|
|
|
|
# Mock service that returns a diagnostic
|
|
mock_service = type("MockService", (), {
|
|
"is_active": lambda self: True,
|
|
"enabled_for": lambda self, p: True,
|
|
"get_diagnostics_sync": lambda self, p, delta=True, timeout=3.0: [
|
|
{
|
|
"severity": 1,
|
|
"range": {"start": {"line": 1, "character": 4}},
|
|
"message": "Type error: str is not int",
|
|
"code": "reportReturnType",
|
|
"source": "Pyright",
|
|
}
|
|
],
|
|
})()
|
|
|
|
with patch.object(lsp_plugin, "_service", mock_service):
|
|
result = lsp_plugin._transform_tool_result(
|
|
tool_name="write_file",
|
|
args={"path": abs_path},
|
|
result='{"bytes_written": 42, "dirs_created": false}',
|
|
session_id="test-session",
|
|
tool_call_id="call-5",
|
|
)
|
|
|
|
assert result is not None
|
|
import json
|
|
data = json.loads(result)
|
|
assert "lsp_diagnostics" in data
|
|
assert "reportReturnType" in data["lsp_diagnostics"]
|
|
assert "bytes_written" in data # Original fields preserved
|