|
import time |
|
import pytest |
|
import asyncio |
|
import httpx |
|
from unittest.mock import patch, MagicMock |
|
from stackoverflow_mcp.api import StackExchangeAPI |
|
from stackoverflow_mcp.types import StackOverflowQuestion, StackOverflowAnswer, SearchResult |
|
|
|
|
|
@pytest.fixture |
|
def api(): |
|
"""Create A StackExchangeAPI instance for testing |
|
""" |
|
return StackExchangeAPI(api_key="test_key") |
|
|
|
@pytest.fixture |
|
def mock_response(): |
|
"""Create a mock response for httpx.""" |
|
|
|
response = MagicMock() |
|
response.raise_for_status = MagicMock() |
|
response.json = MagicMock(return_value={ |
|
"items" : [ |
|
{ |
|
"question_id": 12345, |
|
"title": "Test Question", |
|
"body": "Test body", |
|
"score": 10, |
|
"answer_count": 2, |
|
"is_answered": True, |
|
"accepted_answer_id": 54321, |
|
"creation_date": 1609459200, |
|
"tags": ["python", "testing"], |
|
"link": "https://stackoverflow.com/q/12345" |
|
} |
|
] |
|
}) |
|
return response |
|
|
|
@pytest.mark.asyncio |
|
async def test_search_by_query(api, mock_response): |
|
"""Test searching by query.""" |
|
with patch.object(httpx.AsyncClient, 'get', return_value=mock_response): |
|
results = await api.search_by_query("test query") |
|
|
|
assert len(results) == 1 |
|
assert results[0].question.question_id == 12345 |
|
assert results[0].question.title == "Test Question" |
|
assert isinstance(results[0], SearchResult) |
|
|
|
@pytest.mark.asyncio |
|
async def test_get_question(api, mock_response): |
|
"""Test getting a specific question.""" |
|
with patch.object(httpx.AsyncClient, 'get', return_value=mock_response): |
|
result = await api.get_question(12345) |
|
|
|
assert result.question.question_id == 12345 |
|
assert result.question.title == "Test Question" |
|
assert isinstance(result, SearchResult) |
|
|
|
@pytest.mark.asyncio |
|
async def test_rate_limiting(api): |
|
"""Test rate limiting mechanism.""" |
|
mock_resp = MagicMock() |
|
mock_resp.raise_for_status = MagicMock() |
|
mock_resp.json = MagicMock(return_value={"items": []}) |
|
|
|
with patch.object(httpx.AsyncClient, 'get', return_value=mock_resp): |
|
api.request_timestamps = [time.time() * 1000 * 1000 for _ in range(30)] |
|
|
|
with patch('asyncio.sleep') as mock_sleep: |
|
try: |
|
await api.search_by_query("test") |
|
except Exception as e: |
|
assert str(e) == "Maximum rate limiting attempts exceeded" |
|
mock_sleep.assert_called() |
|
|
|
@pytest.mark.asyncio |
|
async def test_retry_after_429(api): |
|
"""Test retry behavior after hitting rate limit.""" |
|
error_resp = MagicMock() |
|
error_resp.raise_for_status.side_effect = httpx.HTTPStatusError("Rate limited", request=MagicMock(), response=MagicMock(status_code=429)) |
|
|
|
success_resp = MagicMock() |
|
success_resp.raise_for_status = MagicMock() |
|
success_resp.json = MagicMock(return_value={"items": []}) |
|
|
|
with patch.object(httpx.AsyncClient, 'get', side_effect=[error_resp, success_resp]): |
|
with patch('asyncio.sleep') as mock_sleep: |
|
await api.search_by_query("test", retries=1) |
|
mock_sleep.assert_called_once() |