Files
EvoScientist/tests/test_context_overflow_middleware.py
dinos 690b903f85 test: standardize async tests on pytest-asyncio auto mode (#338)
* chore: add pytest-asyncio in auto mode

* test: migrate channel and stream tests to native async

Convert run_async() wrapper tests to plain 'async def test_*' under
pytest-asyncio auto mode. collect_events() in stream_v3_fakes becomes a
coroutine awaited at every call site.

* test: migrate command and model/middleware tests to native async

Convert run_async() wrappers (import, alias, and fixture forms) to plain
'async def test_*'. Multi-call tests merge onto one loop as sequential
awaits; none asserted on loop identity.

* test: migrate TUI, notifier, gateway, and session tests to native async

TUI/notifier/gateway files convert run_async wrappers to plain async
tests. test_sessions.py's unittest.TestCase classes move to
unittest.IsolatedAsyncioTestCase (pytest-asyncio does not await async
methods on plain TestCase; converting blindly would have made ~70 tests
silently vacuous). Its setUpClass keeps a one-shot asyncio.run() since
IsolatedAsyncioTestCase has no async class-level hook. TestLoadingWidget
in test_tui_widgets.py drops its TestCase base for the same reason.

* test: replace direct asyncio.run() calls with native async tests

Convert tests that called asyncio.run() (directly or via a local _run
helper) to plain 'async def test_*'; delete the local helpers.

* test: drop undeclared anyio markers and delete run_async helper

The @pytest.mark.anyio tests relied on anyio being a transitive dep of
httpx; auto-mode pytest-asyncio collects them natively. run_async() and
its fixture are unreferenced after the migration, so remove them —
pytest-asyncio's per-test loop teardown covers the pending-task
cancellation the helper existed for (verified: full suite runs with no
'Event loop is closed' errors or destroyed-task warnings).
2026-07-08 18:37:48 +00:00

111 lines
3.5 KiB
Python

"""Tests for ContextOverflowMapperMiddleware."""
from unittest.mock import AsyncMock, MagicMock
import pytest
from langchain.agents.middleware.types import ModelRequest
from langchain_core.exceptions import ContextOverflowError
from langchain_core.messages import HumanMessage
from EvoScientist.middleware.context_overflow import ContextOverflowMapperMiddleware
def test_is_context_limit_error_openai():
mw = ContextOverflowMapperMiddleware()
exc = Exception(
"Error code: 400 - {'error': {'message': 'This model's maximum context length is 8192 tokens. However, your messages resulted in 10000 tokens.', 'type': 'invalid_request_error', 'param': 'messages', 'code': 'context_length_exceeded'}}"
)
assert mw._is_context_limit_error(exc) is True
def test_is_context_limit_error_anthropic():
mw = ContextOverflowMapperMiddleware()
exc = Exception("HTTP 400 Bad Request: Output too large")
assert mw._is_context_limit_error(exc) is True
def test_is_not_context_limit_error_without_400():
mw = ContextOverflowMapperMiddleware()
exc = Exception("context_length_exceeded, but no status code")
assert mw._is_context_limit_error(exc) is False
def test_is_not_context_limit_error_with_400_but_no_pattern():
mw = ContextOverflowMapperMiddleware()
exc = Exception("HTTP 400 Bad Request: Some other API error")
assert mw._is_context_limit_error(exc) is False
def test_is_not_context_limit_error_other_status():
mw = ContextOverflowMapperMiddleware()
exc = Exception("HTTP 401 Unauthorized")
assert mw._is_context_limit_error(exc) is False
def test_wrap_model_call_raises_context_overflow():
# Setup mocks
msgs = [HumanMessage(content=f"msg {i}") for i in range(10)]
request = ModelRequest(
messages=msgs,
model=MagicMock(),
state={},
runtime=MagicMock(),
system_message=MagicMock(),
)
# Mock handler that fails with context error
handler = MagicMock()
handler.side_effect = Exception("400 Bad Request: context_length_exceeded")
mw = ContextOverflowMapperMiddleware()
with pytest.raises(ContextOverflowError) as excinfo:
mw.wrap_model_call(request, handler)
assert "context_length_exceeded" in str(excinfo.value)
assert handler.call_count == 1
async def test_awrap_model_call_raises_context_overflow():
# Setup mocks
msgs = [HumanMessage(content=f"msg {i}") for i in range(10)]
request = ModelRequest(
messages=msgs,
model=MagicMock(),
state={},
runtime=MagicMock(),
system_message=MagicMock(),
)
# Mock handler that fails with context error
handler = AsyncMock()
handler.side_effect = Exception("400 Bad Request: context_length_exceeded")
mw = ContextOverflowMapperMiddleware()
with pytest.raises(ContextOverflowError) as excinfo:
await mw.awrap_model_call(request, handler)
assert "context_length_exceeded" in str(excinfo.value)
assert handler.call_count == 1
async def test_awrap_model_call_passes_through_other_errors():
request = ModelRequest(
messages=[],
model=MagicMock(),
state={},
runtime=MagicMock(),
system_message=MagicMock(),
)
handler = AsyncMock()
handler.side_effect = RuntimeError("Something else")
mw = ContextOverflowMapperMiddleware()
with pytest.raises(RuntimeError) as excinfo:
await mw.awrap_model_call(request, handler)
assert "Something else" in str(excinfo.value)
assert not isinstance(excinfo.value, ContextOverflowError)