hermes-agent/tests/honcho_plugin/conftest.py
eapwrk bd1a850fa2 fix(honcho): network-hermetic unit tests + lazy async writer start
Re-port of PR #67576:
- plugins/memory/honcho/session.py: start the async writer thread lazily on
  first enqueue via _ensure_async_writer (idempotent, lock-guarded) instead
  of eagerly in __init__; shutdown tolerates a never-started thread
- tests/honcho_plugin/conftest.py: package-wide socket guard so no honcho
  unit test can reach a live server
- tests/honcho_plugin/test_network_isolation.py: regression tests
- test_async_memory.py / test_oauth_flow.py: rebased hunks onto the pruned
  suite (pruned tests not resurrected)

Salvaged-from: #67576
Co-authored-by: eapwrk <eapwrk@gmail.com>
2026-07-29 18:55:10 -07:00

77 lines
2.6 KiB
Python

"""Package-level isolation for Honcho unit tests.
Contract (B8): unit tests in this package make ZERO network requests, even
when a live local Honcho is reachable and ambient production config exists.
A real incident proved unique fixtures are not enough - test messages were
written into the production workspace. The guard below turns any connection
attempt into a hard failure; individually marked tests may opt out with
@pytest.mark.allow_network.
"""
import socket
import threading
import pytest
def pytest_configure(config):
config.addinivalue_line(
"markers",
"allow_network: opt-in marker for tests that intentionally touch the network",
)
config.addinivalue_line(
"markers",
"expect_network_attempts: test asserts on blocked attempts itself",
)
@pytest.fixture
def network_attempts():
"""Recorded connection attempts (visible to tests for assertions)."""
return []
@pytest.fixture(autouse=True)
def _no_network(request, monkeypatch, network_attempts):
"""Fail any test in this package that attempts a real socket connection.
Recording + teardown assert (not just raising) catches attempts even when
intermediate code swallows the exception (the async writer retries and
logs errors instead of propagating them).
"""
if request.node.get_closest_marker("allow_network"):
yield
return
def _blocked_connect(self, address, *args, **kwargs):
network_attempts.append(address)
raise RuntimeError(f"network disabled in honcho unit tests: {address!r}")
def _blocked_create_connection(address, *args, **kwargs):
network_attempts.append(address)
raise RuntimeError(f"network disabled in honcho unit tests: {address!r}")
monkeypatch.setattr(socket.socket, "connect", _blocked_connect)
monkeypatch.setattr(socket, "create_connection", _blocked_create_connection)
yield
leftover = list(network_attempts)
if request.node.get_closest_marker("expect_network_attempts"):
return
assert not leftover, (
f"unit test attempted network connections: {leftover} - "
"inject a fake client/factory before constructing the manager"
)
@pytest.fixture(autouse=True)
def _no_leaked_writer_threads():
"""Every async manager must be shut down by the test that created it."""
yield
leaked = [
t for t in threading.enumerate()
if t.name == "honcho-async-writer" and t.is_alive()
]
assert not leaked, (
f"leaked honcho-async-writer threads: {len(leaked)} - "
"call manager.shutdown() (use the make_manager fixture)"
)