fix(tests): fix 11 real test failures + major cascade poisoner (#4570)
Three root causes addressed: 1. AIAgent no longer defaults base_url to OpenRouter (9 tests) Tests that assert OpenRouter-specific behavior (prompt caching, reasoning extra_body, provider preferences) need explicit base_url and model set on the agent. Updated test_run_agent.py and test_provider_parity.py. 2. Credential pool auto-seeding from host env (2 tests) test_auxiliary_client.py tests for Anthropic OAuth and custom endpoint fallback were not mocking _select_pool_entry, so the host's credential pool interfered. Added pool + codex mocks. 3. sys.modules corruption cascade (major - ~250 tests) test_managed_modal_environment.py replaced sys.modules entries (tools, hermes_cli, agent packages) with SimpleNamespace stubs but had NO cleanup fixture. Every subsequent test in the process saw corrupted imports: 'cannot import get_config_path from <unknown module name>' and 'module tools has no attribute environments'. Added _restore_tool_and_agent_modules autouse fixture matching the pattern in test_managed_browserbase_and_modal.py. This was also the root cause of CI failures (104 failed on main).
This commit is contained in:
@@ -334,10 +334,11 @@ class TestExpiredCodexFallback:
|
|||||||
|
|
||||||
|
|
||||||
def test_hermes_oauth_file_sets_oauth_flag(self, monkeypatch):
|
def test_hermes_oauth_file_sets_oauth_flag(self, monkeypatch):
|
||||||
"""OAuth-style tokens should get is_oauth=True (token is not sk-ant-api-*)."""
|
"""OAuth-style tokens should get is_oauth=*** (token is not sk-ant-api-*)."""
|
||||||
# Mock resolve_anthropic_token to return an OAuth-style token
|
# Mock resolve_anthropic_token to return an OAuth-style token
|
||||||
with patch("agent.anthropic_adapter.resolve_anthropic_token", return_value="hermes-oauth-jwt-token"), \
|
with patch("agent.anthropic_adapter.resolve_anthropic_token", return_value="hermes-oauth-jwt-token"), \
|
||||||
patch("agent.anthropic_adapter.build_anthropic_client") as mock_build:
|
patch("agent.anthropic_adapter.build_anthropic_client") as mock_build, \
|
||||||
|
patch("agent.auxiliary_client._select_pool_entry", return_value=(False, None)):
|
||||||
mock_build.return_value = MagicMock()
|
mock_build.return_value = MagicMock()
|
||||||
from agent.auxiliary_client import _try_anthropic, AnthropicAuxiliaryClient
|
from agent.auxiliary_client import _try_anthropic, AnthropicAuxiliaryClient
|
||||||
client, model = _try_anthropic()
|
client, model = _try_anthropic()
|
||||||
@@ -769,9 +770,13 @@ class TestAuxiliaryPoolAwareness:
|
|||||||
Many local models (Qwen-VL, LLaVA, etc.) support vision.
|
Many local models (Qwen-VL, LLaVA, etc.) support vision.
|
||||||
When no OpenRouter/Nous/Codex is available, try the custom endpoint.
|
When no OpenRouter/Nous/Codex is available, try the custom endpoint.
|
||||||
"""
|
"""
|
||||||
monkeypatch.setenv("OPENAI_BASE_URL", "http://localhost:1234/v1")
|
monkeypatch.delenv("OPENROUTER_API_KEY", raising=False)
|
||||||
monkeypatch.setenv("OPENAI_API_KEY", "local-key")
|
monkeypatch.delenv("ANTHROPIC_API_KEY", raising=False)
|
||||||
with patch("agent.auxiliary_client._read_nous_auth", return_value=None), \
|
with patch("agent.auxiliary_client._read_nous_auth", return_value=None), \
|
||||||
|
patch("agent.auxiliary_client._select_pool_entry", return_value=(False, None)), \
|
||||||
|
patch("agent.auxiliary_client._read_codex_access_token", return_value=None), \
|
||||||
|
patch("agent.auxiliary_client._resolve_custom_runtime",
|
||||||
|
return_value=("http://localhost:1234/v1", "local-key")), \
|
||||||
patch("agent.auxiliary_client.OpenAI") as mock_openai:
|
patch("agent.auxiliary_client.OpenAI") as mock_openai:
|
||||||
client, model = get_vision_auxiliary_client()
|
client, model = get_vision_auxiliary_client()
|
||||||
assert client is not None # Custom endpoint picked up as fallback
|
assert client is not None # Custom endpoint picked up as fallback
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ class TestBuildApiKwargsOpenRouter:
|
|||||||
|
|
||||||
def test_includes_reasoning_in_extra_body(self, monkeypatch):
|
def test_includes_reasoning_in_extra_body(self, monkeypatch):
|
||||||
agent = _make_agent(monkeypatch, "openrouter")
|
agent = _make_agent(monkeypatch, "openrouter")
|
||||||
|
agent.model = "anthropic/claude-sonnet-4-20250514"
|
||||||
messages = [{"role": "user", "content": "hi"}]
|
messages = [{"role": "user", "content": "hi"}]
|
||||||
kwargs = agent._build_api_kwargs(messages)
|
kwargs = agent._build_api_kwargs(messages)
|
||||||
extra = kwargs.get("extra_body", {})
|
extra = kwargs.get("extra_body", {})
|
||||||
@@ -798,6 +799,7 @@ class TestReasoningEffortDefaults:
|
|||||||
|
|
||||||
def test_openrouter_default_medium(self, monkeypatch):
|
def test_openrouter_default_medium(self, monkeypatch):
|
||||||
agent = _make_agent(monkeypatch, "openrouter")
|
agent = _make_agent(monkeypatch, "openrouter")
|
||||||
|
agent.model = "anthropic/claude-sonnet-4-20250514"
|
||||||
kwargs = agent._build_api_kwargs([{"role": "user", "content": "hi"}])
|
kwargs = agent._build_api_kwargs([{"role": "user", "content": "hi"}])
|
||||||
reasoning = kwargs["extra_body"]["reasoning"]
|
reasoning = kwargs["extra_body"]["reasoning"]
|
||||||
assert reasoning["effort"] == "medium"
|
assert reasoning["effort"] == "medium"
|
||||||
@@ -825,6 +827,7 @@ class TestReasoningEffortDefaults:
|
|||||||
|
|
||||||
def test_openrouter_reasoning_config_override(self, monkeypatch):
|
def test_openrouter_reasoning_config_override(self, monkeypatch):
|
||||||
agent = _make_agent(monkeypatch, "openrouter")
|
agent = _make_agent(monkeypatch, "openrouter")
|
||||||
|
agent.model = "anthropic/claude-sonnet-4-20250514"
|
||||||
agent.reasoning_config = {"enabled": True, "effort": "medium"}
|
agent.reasoning_config = {"enabled": True, "effort": "medium"}
|
||||||
kwargs = agent._build_api_kwargs([{"role": "user", "content": "hi"}])
|
kwargs = agent._build_api_kwargs([{"role": "user", "content": "hi"}])
|
||||||
assert kwargs["extra_body"]["reasoning"]["effort"] == "medium"
|
assert kwargs["extra_body"]["reasoning"]["effort"] == "medium"
|
||||||
|
|||||||
@@ -411,8 +411,9 @@ class TestInit:
|
|||||||
patch("run_agent.OpenAI"),
|
patch("run_agent.OpenAI"),
|
||||||
):
|
):
|
||||||
a = AIAgent(
|
a = AIAgent(
|
||||||
api_key="test-key-1234567890",
|
api_key="test-k...7890",
|
||||||
model="anthropic/claude-sonnet-4-20250514",
|
model="anthropic/claude-sonnet-4-20250514",
|
||||||
|
base_url="https://openrouter.ai/api/v1",
|
||||||
quiet_mode=True,
|
quiet_mode=True,
|
||||||
skip_context_files=True,
|
skip_context_files=True,
|
||||||
skip_memory=True,
|
skip_memory=True,
|
||||||
@@ -792,6 +793,7 @@ class TestBuildApiKwargs:
|
|||||||
assert kwargs["timeout"] == 1800.0
|
assert kwargs["timeout"] == 1800.0
|
||||||
|
|
||||||
def test_provider_preferences_injected(self, agent):
|
def test_provider_preferences_injected(self, agent):
|
||||||
|
agent.base_url = "https://openrouter.ai/api/v1"
|
||||||
agent.providers_allowed = ["Anthropic"]
|
agent.providers_allowed = ["Anthropic"]
|
||||||
messages = [{"role": "user", "content": "hi"}]
|
messages = [{"role": "user", "content": "hi"}]
|
||||||
kwargs = agent._build_api_kwargs(messages)
|
kwargs = agent._build_api_kwargs(messages)
|
||||||
@@ -799,6 +801,8 @@ class TestBuildApiKwargs:
|
|||||||
|
|
||||||
def test_reasoning_config_default_openrouter(self, agent):
|
def test_reasoning_config_default_openrouter(self, agent):
|
||||||
"""Default reasoning config for OpenRouter should be medium."""
|
"""Default reasoning config for OpenRouter should be medium."""
|
||||||
|
agent.base_url = "https://openrouter.ai/api/v1"
|
||||||
|
agent.model = "anthropic/claude-sonnet-4-20250514"
|
||||||
messages = [{"role": "user", "content": "hi"}]
|
messages = [{"role": "user", "content": "hi"}]
|
||||||
kwargs = agent._build_api_kwargs(messages)
|
kwargs = agent._build_api_kwargs(messages)
|
||||||
reasoning = kwargs["extra_body"]["reasoning"]
|
reasoning = kwargs["extra_body"]["reasoning"]
|
||||||
@@ -806,6 +810,8 @@ class TestBuildApiKwargs:
|
|||||||
assert reasoning["effort"] == "medium"
|
assert reasoning["effort"] == "medium"
|
||||||
|
|
||||||
def test_reasoning_config_custom(self, agent):
|
def test_reasoning_config_custom(self, agent):
|
||||||
|
agent.base_url = "https://openrouter.ai/api/v1"
|
||||||
|
agent.model = "anthropic/claude-sonnet-4-20250514"
|
||||||
agent.reasoning_config = {"enabled": False}
|
agent.reasoning_config = {"enabled": False}
|
||||||
messages = [{"role": "user", "content": "hi"}]
|
messages = [{"role": "user", "content": "hi"}]
|
||||||
kwargs = agent._build_api_kwargs(messages)
|
kwargs = agent._build_api_kwargs(messages)
|
||||||
@@ -818,6 +824,7 @@ class TestBuildApiKwargs:
|
|||||||
assert "reasoning" not in kwargs.get("extra_body", {})
|
assert "reasoning" not in kwargs.get("extra_body", {})
|
||||||
|
|
||||||
def test_reasoning_sent_for_supported_openrouter_model(self, agent):
|
def test_reasoning_sent_for_supported_openrouter_model(self, agent):
|
||||||
|
agent.base_url = "https://openrouter.ai/api/v1"
|
||||||
agent.model = "qwen/qwen3.5-plus-02-15"
|
agent.model = "qwen/qwen3.5-plus-02-15"
|
||||||
messages = [{"role": "user", "content": "hi"}]
|
messages = [{"role": "user", "content": "hi"}]
|
||||||
kwargs = agent._build_api_kwargs(messages)
|
kwargs = agent._build_api_kwargs(messages)
|
||||||
@@ -3156,9 +3163,11 @@ class TestStreamingApiCall:
|
|||||||
def test_api_exception_falls_back_to_non_streaming(self, agent):
|
def test_api_exception_falls_back_to_non_streaming(self, agent):
|
||||||
"""When streaming fails before any deltas, fallback to non-streaming is attempted."""
|
"""When streaming fails before any deltas, fallback to non-streaming is attempted."""
|
||||||
agent.client.chat.completions.create.side_effect = ConnectionError("fail")
|
agent.client.chat.completions.create.side_effect = ConnectionError("fail")
|
||||||
# The fallback also uses the same client, so it'll fail too
|
# Prevent stream retry logic from replacing the mock client
|
||||||
with pytest.raises(ConnectionError, match="fail"):
|
with patch.object(agent, "_replace_primary_openai_client", return_value=False):
|
||||||
agent._interruptible_streaming_api_call({"messages": []})
|
# The fallback also uses the same client, so it'll fail too
|
||||||
|
with pytest.raises(ConnectionError, match="fail"):
|
||||||
|
agent._interruptible_streaming_api_call({"messages": []})
|
||||||
|
|
||||||
def test_response_has_uuid_id(self, agent):
|
def test_response_has_uuid_id(self, agent):
|
||||||
chunks = [_make_chunk(content="x"), _make_chunk(finish_reason="stop")]
|
chunks = [_make_chunk(content="x"), _make_chunk(finish_reason="stop")]
|
||||||
|
|||||||
@@ -27,6 +27,24 @@ def _reset_modules(prefixes: tuple[str, ...]):
|
|||||||
sys.modules.pop(name, None)
|
sys.modules.pop(name, None)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def _restore_tool_and_agent_modules():
|
||||||
|
"""Save and restore sys.modules entries so fakes don't leak to other tests."""
|
||||||
|
original_modules = {
|
||||||
|
name: module
|
||||||
|
for name, module in sys.modules.items()
|
||||||
|
if name in ("tools", "agent", "hermes_cli")
|
||||||
|
or name.startswith("tools.")
|
||||||
|
or name.startswith("agent.")
|
||||||
|
or name.startswith("hermes_cli.")
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
_reset_modules(("tools", "agent", "hermes_cli"))
|
||||||
|
sys.modules.update(original_modules)
|
||||||
|
|
||||||
|
|
||||||
def _install_fake_tools_package(*, credential_mounts=None):
|
def _install_fake_tools_package(*, credential_mounts=None):
|
||||||
_reset_modules(("tools", "agent", "hermes_cli"))
|
_reset_modules(("tools", "agent", "hermes_cli"))
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user