Claude_Code / tests /providers /test_open_router.py
Jainish1808
Move project files to repository root for Hugging Face Space
bf177ff
Raw
History Blame Contribute Delete
11.3 kB
"""Tests for OpenRouter provider."""
import json
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from providers.base import ProviderConfig
from providers.open_router import OpenRouterProvider
from providers.open_router.request import OPENROUTER_DEFAULT_MAX_TOKENS
class MockMessage:
def __init__(self, role, content):
self.role = role
self.content = content
class MockRequest:
def __init__(self, **kwargs):
self.model = "stepfun/step-3.5-flash:free"
self.messages = [MockMessage("user", "Hello")]
self.max_tokens = 100
self.temperature = 0.5
self.top_p = 0.9
self.system = "System prompt"
self.stop_sequences = None
self.tools = []
self.extra_body = {}
self.thinking = MagicMock()
self.thinking.enabled = True
for k, v in kwargs.items():
setattr(self, k, v)
@pytest.fixture
def open_router_config():
return ProviderConfig(
api_key="test_openrouter_key",
base_url="https://openrouter.ai/api/v1",
rate_limit=10,
rate_window=60,
)
@pytest.fixture(autouse=True)
def mock_rate_limiter():
"""Mock the global rate limiter to prevent waiting."""
with patch("providers.openai_compat.GlobalRateLimiter") as mock:
instance = mock.get_instance.return_value
instance.wait_if_blocked = AsyncMock(return_value=False)
async def _passthrough(fn, *args, **kwargs):
return await fn(*args, **kwargs)
instance.execute_with_retry = AsyncMock(side_effect=_passthrough)
yield instance
@pytest.fixture
def open_router_provider(open_router_config):
return OpenRouterProvider(open_router_config)
def test_init(open_router_config):
"""Test provider initialization."""
with patch("providers.openai_compat.AsyncOpenAI") as mock_openai:
provider = OpenRouterProvider(open_router_config)
assert provider._api_key == "test_openrouter_key"
assert provider._base_url == "https://openrouter.ai/api/v1"
mock_openai.assert_called_once()
def test_init_uses_configurable_timeouts():
"""Test that provider passes configurable read/write/connect timeouts to client."""
config = ProviderConfig(
api_key="test_openrouter_key",
base_url="https://openrouter.ai/api/v1",
http_read_timeout=600.0,
http_write_timeout=15.0,
http_connect_timeout=5.0,
)
with patch("providers.openai_compat.AsyncOpenAI") as mock_openai:
OpenRouterProvider(config)
call_kwargs = mock_openai.call_args[1]
timeout = call_kwargs["timeout"]
assert timeout.read == 600.0
assert timeout.write == 15.0
assert timeout.connect == 5.0
def test_build_request_body_has_reasoning_extra(open_router_provider):
"""Request body has extra_body.reasoning.enabled for thinking models."""
req = MockRequest()
body = open_router_provider._build_request_body(req)
assert body["model"] == "stepfun/step-3.5-flash:free"
assert body["temperature"] == 0.5
assert len(body["messages"]) == 2 # System + User
assert body["messages"][0]["role"] == "system"
assert body["messages"][0]["content"] == "System prompt"
assert "extra_body" in body
assert "reasoning" in body["extra_body"]
assert body["extra_body"]["reasoning"]["enabled"] is True
def test_build_request_body_base_url_and_model(open_router_provider):
"""Base URL and model are correct in provider config."""
assert open_router_provider._base_url == "https://openrouter.ai/api/v1"
req = MockRequest(model="stepfun/step-3.5-flash:free")
body = open_router_provider._build_request_body(req)
assert body["model"] == "stepfun/step-3.5-flash:free"
def test_build_request_body_default_max_tokens(open_router_provider):
"""max_tokens=None uses OPENROUTER_DEFAULT_MAX_TOKENS (81920)."""
req = MockRequest(max_tokens=None)
body = open_router_provider._build_request_body(req)
assert body["max_tokens"] == OPENROUTER_DEFAULT_MAX_TOKENS
assert body["max_tokens"] == 81920
@pytest.mark.asyncio
async def test_stream_response_text(open_router_provider):
"""Test streaming text response."""
req = MockRequest()
mock_chunk1 = MagicMock()
mock_chunk1.choices = [
MagicMock(
delta=MagicMock(content="Hello", reasoning_content=None),
finish_reason=None,
)
]
mock_chunk1.usage = None
mock_chunk2 = MagicMock()
mock_chunk2.choices = [
MagicMock(
delta=MagicMock(content=" World", reasoning_content=None),
finish_reason="stop",
)
]
mock_chunk2.usage = MagicMock(completion_tokens=10)
async def mock_stream():
yield mock_chunk1
yield mock_chunk2
with patch.object(
open_router_provider._client.chat.completions, "create", new_callable=AsyncMock
) as mock_create:
mock_create.return_value = mock_stream()
events = [e async for e in open_router_provider.stream_response(req)]
assert len(events) > 0
assert "event: message_start" in events[0]
text_content = ""
for e in events:
if "event: content_block_delta" in e and '"text_delta"' in e:
for line in e.splitlines():
if line.startswith("data: "):
data = json.loads(line[6:])
if "delta" in data and "text" in data["delta"]:
text_content += data["delta"]["text"]
assert "Hello World" in text_content
@pytest.mark.asyncio
async def test_stream_response_reasoning_content(open_router_provider):
"""Test streaming with reasoning_content delta."""
req = MockRequest()
mock_chunk = MagicMock()
mock_chunk.choices = [
MagicMock(
delta=MagicMock(content=None, reasoning_content="Thinking..."),
finish_reason=None,
)
]
mock_chunk.usage = None
async def mock_stream():
yield mock_chunk
with patch.object(
open_router_provider._client.chat.completions, "create", new_callable=AsyncMock
) as mock_create:
mock_create.return_value = mock_stream()
events = [e async for e in open_router_provider.stream_response(req)]
found_thinking = False
for e in events:
if (
"event: content_block_delta" in e
and '"thinking_delta"' in e
and "Thinking..." in e
):
found_thinking = True
assert found_thinking
@pytest.mark.asyncio
async def test_stream_response_empty_choices_skipped(open_router_provider):
"""Chunks with empty choices are skipped."""
req = MockRequest()
async def mock_stream():
yield MagicMock(choices=[], usage=None)
yield MagicMock(
choices=[
MagicMock(
delta=MagicMock(content="ok", reasoning_content=None),
finish_reason="stop",
)
],
usage=MagicMock(completion_tokens=2),
)
with patch.object(
open_router_provider._client.chat.completions, "create", new_callable=AsyncMock
) as mock_create:
mock_create.return_value = mock_stream()
events = [e async for e in open_router_provider.stream_response(req)]
assert any("content_block_delta" in e and "ok" in e for e in events)
@pytest.mark.asyncio
async def test_stream_response_delta_none_skipped(open_router_provider):
"""Chunks with delta=None are skipped."""
req = MockRequest()
async def mock_stream():
yield MagicMock(
choices=[MagicMock(delta=None, finish_reason=None)],
usage=None,
)
yield MagicMock(
choices=[
MagicMock(
delta=MagicMock(content="x", reasoning_content=None),
finish_reason="stop",
)
],
usage=MagicMock(completion_tokens=1),
)
with patch.object(
open_router_provider._client.chat.completions, "create", new_callable=AsyncMock
) as mock_create:
mock_create.return_value = mock_stream()
events = [e async for e in open_router_provider.stream_response(req)]
assert any("x" in e for e in events)
@pytest.mark.asyncio
async def test_stream_response_reasoning_details(open_router_provider):
"""Streaming with reasoning_details (stepfun format)."""
req = MockRequest()
mock_chunk = MagicMock()
mock_chunk.choices = [
MagicMock(
delta=MagicMock(
content=None,
reasoning_content=None,
reasoning_details=[{"text": "Step 1"}],
),
finish_reason=None,
)
]
mock_chunk.usage = None
async def mock_stream():
yield mock_chunk
yield MagicMock(
choices=[
MagicMock(
delta=MagicMock(
content=None,
reasoning_content=None,
reasoning_details=None,
),
finish_reason="stop",
)
],
usage=MagicMock(completion_tokens=5),
)
with patch.object(
open_router_provider._client.chat.completions, "create", new_callable=AsyncMock
) as mock_create:
mock_create.return_value = mock_stream()
events = [e async for e in open_router_provider.stream_response(req)]
assert any("Step 1" in e for e in events)
@pytest.mark.asyncio
async def test_stream_response_error_path(open_router_provider):
"""Stream raises exception -> error event emitted."""
req = MockRequest()
async def mock_stream():
raise RuntimeError("API failed")
yield # unreachable, makes it a generator
with patch.object(
open_router_provider._client.chat.completions, "create", new_callable=AsyncMock
) as mock_create:
mock_create.return_value = mock_stream()
events = [e async for e in open_router_provider.stream_response(req)]
# Error is emitted; message_stop/done indicates stream completed
assert any("API failed" in e for e in events)
assert any("message_stop" in e for e in events)
@pytest.mark.asyncio
async def test_stream_response_finish_reason_only(open_router_provider):
"""Chunk with finish_reason but no content still completes."""
req = MockRequest()
async def mock_stream():
yield MagicMock(
choices=[
MagicMock(
delta=MagicMock(content=None, reasoning_content=None),
finish_reason="stop",
)
],
usage=MagicMock(completion_tokens=0),
)
with patch.object(
open_router_provider._client.chat.completions, "create", new_callable=AsyncMock
) as mock_create:
mock_create.return_value = mock_stream()
events = [e async for e in open_router_provider.stream_response(req)]
assert any("message_delta" in e for e in events)
assert any("message_stop" in e for e in events)