hermes-agent/tests/tools/test_managed_media_gateways.py
Teknium 39975613b1
test: prune wave 2 + speed fixes — 28,106 → 19,757 test functions, suite wall 315s → 294s
Second, deeper pass over tools/gateway/hermes_cli plus first pass over
the trees wave 1 missed (acp, acp_adapter, skills, computer_use, docker,
dashboard, conformance, monitoring, secret_sources, hermes_state,
providers). Same rubric as wave 1 (AGENTS.md test policy); security,
alternation/caching invariants, issue-number regressions, and E2E kept.

Real test-quality fixes found and rooted out along the way:
- tests/tools/test_command_guards.py made real auxiliary-LLM HTTPS calls
  (DEFAULT_CONFIG smart-approval leaked in) — pinned approval
  mode=manual via autouse fixture: 17.4s → 0.4s.
- test_model_switch_custom_providers.py / test_user_providers_model_switch.py
  silently probed live provider catalogs (~2s/test) — stubbed
  cached_provider_model_ids/provider_model_ids/fetch_api_models.
- test_telegram_noise_filter.py: 15-platform copy-paste matrix over
  shared gateway.run logic → 3 representative platforms (55s → 3.9s).
- test_gateway_shutdown.py: stop()'s 5s interrupt-deadline loop spun on
  MagicMock agents — interrupt.side_effect now clears _running_agents
  (22s → 1.0s).
- test_gateway_inactivity_timeout.py poll-harness timings shrunk 3-5x
  (24s → 1.1s); test_mcp_stability.py backoff/SIGTERM-grace sleeps
  patched (15.4s → 2.5s); test_async_delegation.py negative-drain wait
  5s → 0.5s.
- test_telegram_init_deadline.py: loop-block margin restored to 1.0s
  with rationale comment — the watchdog-dump assertion needs the loop
  blocked well past deadline+grace under parallel load (flaked once in
  the 40-worker verification run at a 0.2s margin).

Verification: full hermetic suite via scripts/run_tests.sh —
2,438 files, 21,718 tests passed, 0 failed, 293.9s wall.
Suite totals vs original baseline: 46,820 → 19,757 test functions
(−57.8%), wall 583.5s → 293.9s (−50%), subprocess CPU 13,564s → 11,623s.
2026-07-29 13:39:40 -07:00

341 lines
12 KiB
Python

import sys
import types
from importlib.util import module_from_spec, spec_from_file_location
from pathlib import Path
import pytest
from hermes_cli.nous_account import NousPortalAccountInfo
TOOLS_DIR = Path(__file__).resolve().parents[2] / "tools"
def _load_tool_module(module_name: str, filename: str):
spec = spec_from_file_location(module_name, TOOLS_DIR / filename)
assert spec and spec.loader
module = module_from_spec(spec)
sys.modules[module_name] = module
spec.loader.exec_module(module)
return module
@pytest.fixture(autouse=True)
def _restore_tool_and_agent_modules():
original_modules = {
name: module
for name, module in sys.modules.items()
if name == "tools"
or name.startswith("tools.")
or name == "agent"
or name.startswith("agent.")
or name in {"fal_client", "openai"}
}
try:
yield
finally:
for name in list(sys.modules):
if (
name == "tools"
or name.startswith("tools.")
or name == "agent"
or name.startswith("agent.")
or name in {"fal_client", "openai"}
):
sys.modules.pop(name, None)
sys.modules.update(original_modules)
@pytest.fixture(autouse=True)
def _enable_managed_nous_tools(monkeypatch):
"""Patch the source modules so managed_nous_tools_enabled() returns True
even after tool modules are dynamically reloaded."""
monkeypatch.setattr(
"hermes_cli.nous_account.get_nous_portal_account_info",
lambda: NousPortalAccountInfo(
logged_in=True,
source="jwt",
fresh=False,
paid_service_access=True,
),
)
def _install_fake_tools_package():
tools_package = types.ModuleType("tools")
tools_package.__path__ = [str(TOOLS_DIR)] # type: ignore[attr-defined]
sys.modules["tools"] = tools_package
sys.modules["tools.debug_helpers"] = types.SimpleNamespace(
DebugSession=lambda *args, **kwargs: types.SimpleNamespace(
active=False,
session_id="debug-session",
log_call=lambda *a, **k: None,
save=lambda: None,
get_session_info=lambda: {},
)
)
sys.modules["tools.managed_tool_gateway"] = _load_tool_module(
"tools.managed_tool_gateway",
"managed_tool_gateway.py",
)
def _install_fake_fal_client(captured):
def submit(model, arguments=None, headers=None):
raise AssertionError("managed FAL gateway mode should use fal_client.SyncClient")
class FakeResponse:
def json(self):
return {
"request_id": "req-123",
"response_url": "http://127.0.0.1:3009/requests/req-123",
"status_url": "http://127.0.0.1:3009/requests/req-123/status",
"cancel_url": "http://127.0.0.1:3009/requests/req-123/cancel",
}
def _maybe_retry_request(client, method, url, json=None, timeout=None, headers=None):
captured["submit_via"] = "managed_client"
captured["http_client"] = client
captured["method"] = method
captured["submit_url"] = url
captured["arguments"] = json
captured["timeout"] = timeout
captured["headers"] = headers
return FakeResponse()
class SyncRequestHandle:
def __init__(self, request_id, response_url, status_url, cancel_url, client):
captured["request_id"] = request_id
captured["response_url"] = response_url
captured["status_url"] = status_url
captured["cancel_url"] = cancel_url
captured["handle_client"] = client
class SyncClient:
def __init__(self, key=None, default_timeout=120.0):
captured["sync_client_inits"] = captured.get("sync_client_inits", 0) + 1
captured["client_key"] = key
captured["client_timeout"] = default_timeout
self.default_timeout = default_timeout
self._client = object()
fal_client_module = types.SimpleNamespace(
submit=submit,
SyncClient=SyncClient,
client=types.SimpleNamespace(
_maybe_retry_request=_maybe_retry_request,
_raise_for_status=lambda response: None,
SyncRequestHandle=SyncRequestHandle,
),
)
sys.modules["fal_client"] = fal_client_module
return fal_client_module
def _install_fake_openai_module(captured, transcription_response=None):
class FakeSpeechResponse:
def stream_to_file(self, output_path):
captured["stream_to_file"] = output_path
class FakeOpenAI:
def __init__(self, api_key, base_url, **kwargs):
captured["api_key"] = api_key
captured["base_url"] = base_url
captured["client_kwargs"] = kwargs
captured["close_calls"] = captured.get("close_calls", 0)
def create_speech(**kwargs):
captured["speech_kwargs"] = kwargs
return FakeSpeechResponse()
def create_transcription(**kwargs):
captured["transcription_kwargs"] = kwargs
return transcription_response
self.audio = types.SimpleNamespace(
speech=types.SimpleNamespace(
create=create_speech
),
transcriptions=types.SimpleNamespace(
create=create_transcription
),
)
def close(self):
captured["close_calls"] += 1
fake_module = types.SimpleNamespace(
OpenAI=FakeOpenAI,
APIError=Exception,
APIConnectionError=Exception,
APITimeoutError=Exception,
BadRequestError=type("BadRequestError", (Exception,), {}),
)
sys.modules["openai"] = fake_module
def test_managed_fal_submit_uses_gateway_origin_and_nous_token(monkeypatch):
captured = {}
_install_fake_tools_package()
_install_fake_fal_client(captured)
monkeypatch.delenv("FAL_KEY", raising=False)
monkeypatch.setenv("FAL_QUEUE_GATEWAY_URL", "http://127.0.0.1:3009")
monkeypatch.setenv("TOOL_GATEWAY_USER_TOKEN", "nous-token")
image_generation_tool = _load_tool_module(
"tools.image_generation_tool",
"image_generation_tool.py",
)
monkeypatch.setattr(image_generation_tool.uuid, "uuid4", lambda: "fal-submit-123")
image_generation_tool._submit_fal_request(
"fal-ai/flux-2-pro",
{"prompt": "test prompt", "num_images": 1},
)
assert captured["submit_via"] == "managed_client"
assert captured["client_key"] == "nous-token"
assert captured["submit_url"] == "http://127.0.0.1:3009/fal-ai/flux-2-pro"
assert captured["method"] == "POST"
assert captured["arguments"] == {"prompt": "test prompt", "num_images": 1}
assert captured["headers"] == {"x-idempotency-key": "fal-submit-123"}
assert captured["sync_client_inits"] == 1
def test_openai_tts_uses_managed_audio_gateway_when_direct_key_absent(monkeypatch, tmp_path):
captured = {}
_install_fake_tools_package()
_install_fake_openai_module(captured)
monkeypatch.delenv("VOICE_TOOLS_OPENAI_KEY", raising=False)
monkeypatch.delenv("OPENAI_API_KEY", raising=False)
monkeypatch.setenv("TOOL_GATEWAY_DOMAIN", "nousresearch.com")
monkeypatch.setenv("TOOL_GATEWAY_USER_TOKEN", "nous-token")
tts_tool = _load_tool_module("tools.tts_tool", "tts_tool.py")
monkeypatch.setattr(tts_tool.uuid, "uuid4", lambda: "tts-call-123")
output_path = tmp_path / "speech.mp3"
tts_tool._generate_openai_tts("hello world", str(output_path), {"openai": {}})
assert captured["api_key"] == "nous-token"
assert captured["base_url"] == "https://openai-audio-gateway.nousresearch.com/v1"
assert captured["speech_kwargs"]["model"] == "gpt-4o-mini-tts"
assert captured["speech_kwargs"]["extra_headers"] == {"x-idempotency-key": "tts-call-123"}
assert captured["stream_to_file"] == str(output_path)
assert captured["close_calls"] == 1
def test_openai_tts_accepts_openai_api_key_as_direct_fallback(monkeypatch, tmp_path):
captured = {}
_install_fake_tools_package()
_install_fake_openai_module(captured)
monkeypatch.delenv("VOICE_TOOLS_OPENAI_KEY", raising=False)
monkeypatch.setenv("OPENAI_API_KEY", "openai-direct-key")
monkeypatch.setenv("TOOL_GATEWAY_DOMAIN", "nousresearch.com")
monkeypatch.setenv("TOOL_GATEWAY_USER_TOKEN", "nous-token")
tts_tool = _load_tool_module("tools.tts_tool", "tts_tool.py")
output_path = tmp_path / "speech.mp3"
tts_tool._generate_openai_tts("hello world", str(output_path), {"openai": {}})
assert captured["api_key"] == "openai-direct-key"
assert captured["base_url"] == "https://api.openai.com/v1"
assert captured["close_calls"] == 1
def test_transcription_uses_model_specific_response_formats(monkeypatch, tmp_path):
whisper_capture = {}
_install_fake_tools_package()
_install_fake_openai_module(whisper_capture, transcription_response="hello from whisper")
monkeypatch.setenv("HERMES_HOME", str(tmp_path))
(tmp_path / "config.yaml").write_text("stt:\n provider: openai\n")
monkeypatch.delenv("VOICE_TOOLS_OPENAI_KEY", raising=False)
monkeypatch.delenv("OPENAI_API_KEY", raising=False)
monkeypatch.setenv("TOOL_GATEWAY_DOMAIN", "nousresearch.com")
monkeypatch.setenv("TOOL_GATEWAY_USER_TOKEN", "nous-token")
transcription_tools = _load_tool_module(
"tools.transcription_tools",
"transcription_tools.py",
)
transcription_tools._load_stt_config = lambda: {"provider": "openai"}
audio_path = tmp_path / "audio.wav"
audio_path.write_bytes(b"RIFF0000WAVEfmt ")
whisper_result = transcription_tools.transcribe_audio(str(audio_path), model="whisper-1")
assert whisper_result["success"] is True
assert whisper_capture["base_url"] == "https://openai-audio-gateway.nousresearch.com/v1"
assert whisper_capture["transcription_kwargs"]["response_format"] == "text"
assert whisper_capture["close_calls"] == 1
json_capture = {}
_install_fake_openai_module(
json_capture,
transcription_response=types.SimpleNamespace(text="hello from gpt-4o"),
)
transcription_tools = _load_tool_module(
"tools.transcription_tools",
"transcription_tools.py",
)
json_result = transcription_tools.transcribe_audio(
str(audio_path),
model="gpt-4o-mini-transcribe",
)
assert json_result["success"] is True
assert json_result["transcript"] == "hello from gpt-4o"
assert json_capture["transcription_kwargs"]["response_format"] == "json"
assert json_capture["close_calls"] == 1
PLUGINS_DIR = Path(__file__).resolve().parents[2] / "plugins"
def _load_video_gen_plugin(monkeypatch):
"""Load the FAL video gen plugin in isolation."""
_install_fake_tools_package()
# Also need the agent.video_gen_provider ABC
agent_dir = Path(__file__).resolve().parents[2] / "agent"
spec = spec_from_file_location(
"agent.video_gen_provider",
agent_dir / "video_gen_provider.py",
)
assert spec and spec.loader
mod = module_from_spec(spec)
sys.modules["agent.video_gen_provider"] = mod
spec.loader.exec_module(mod)
# Load the plugin
plugin_init = PLUGINS_DIR / "video_gen" / "fal" / "__init__.py"
spec = spec_from_file_location("plugins.video_gen.fal", plugin_init)
assert spec and spec.loader
plugin_mod = module_from_spec(spec)
sys.modules["plugins.video_gen.fal"] = plugin_mod
spec.loader.exec_module(plugin_mod)
return plugin_mod
def test_video_gen_happy_horse_uses_alibaba_namespace():
"""Verify the happy-horse family uses alibaba/ not fal-ai/ endpoints."""
_install_fake_tools_package()
# Load just the plugin module to check the catalog
plugin_init = PLUGINS_DIR / "video_gen" / "fal" / "__init__.py"
agent_dir = Path(__file__).resolve().parents[2] / "agent"
spec = spec_from_file_location(
"agent.video_gen_provider",
agent_dir / "video_gen_provider.py",
)
mod = module_from_spec(spec)
sys.modules["agent.video_gen_provider"] = mod
spec.loader.exec_module(mod)
spec = spec_from_file_location("plugins.video_gen.fal", plugin_init)
plugin_mod = module_from_spec(spec)
sys.modules["plugins.video_gen.fal"] = plugin_mod
spec.loader.exec_module(plugin_mod)
hh = plugin_mod.FAL_FAMILIES["happy-horse"]
assert hh["text_endpoint"] == "alibaba/happy-horse/text-to-video"
assert hh["image_endpoint"] == "alibaba/happy-horse/image-to-video"