This commit is contained in:
Mobile-Crest 2025-12-14 09:53:47 +04:30 committed by GitHub
commit 7a0a7fe7e8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 960 additions and 0 deletions

View file

@ -0,0 +1,340 @@
#
# Copyright 2025 The InfiniFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import asyncio
import os
import time
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
import trio
import anyio
from common.connection_utils import timeout, construct_response, sync_construct_response
from common.constants import RetCode
class TestTimeoutDecorator:
"""Test cases for the timeout decorator"""
def test_sync_function_success(self):
"""Test timeout decorator with successful sync function"""
@timeout(seconds=5)
def fast_function():
return "success"
result = fast_function()
assert result == "success"
def test_sync_function_with_args(self):
"""Test timeout decorator with sync function that has arguments"""
@timeout(seconds=5)
def add_numbers(a, b):
return a + b
result = add_numbers(3, 4)
assert result == 7
def test_sync_function_raises_exception(self):
"""Test timeout decorator propagates exceptions from sync function"""
@timeout(seconds=5)
def failing_function():
raise ValueError("Test error")
with pytest.raises(ValueError, match="Test error"):
failing_function()
def test_sync_function_timeout_disabled(self):
"""Test sync function when timeout is disabled (no ENABLE_TIMEOUT_ASSERTION)"""
@timeout(seconds=1)
def slow_function():
time.sleep(0.1)
return "completed"
# Without ENABLE_TIMEOUT_ASSERTION, timeout should not trigger
result = slow_function()
assert result == "completed"
def test_sync_function_timeout_enabled(self):
"""Test sync function timeout when ENABLE_TIMEOUT_ASSERTION is set"""
@timeout(seconds=0.1, attempts=1)
def slow_function():
time.sleep(2)
return "should not reach"
with patch.dict(os.environ, {"ENABLE_TIMEOUT_ASSERTION": "1"}):
with pytest.raises(TimeoutError, match="timed out after 0.1 seconds and 1 attempts"):
slow_function()
def test_sync_function_with_string_seconds(self):
"""Test timeout decorator with string seconds parameter"""
@timeout(seconds="5")
def fast_function():
return "success"
result = fast_function()
assert result == "success"
def test_sync_function_multiple_attempts(self):
"""Test sync function with multiple attempts"""
call_count = 0
@timeout(seconds=0.1, attempts=3)
def function_with_attempts():
nonlocal call_count
call_count += 1
time.sleep(0.05)
return f"attempt_{call_count}"
result = function_with_attempts()
assert result == "attempt_1"
@pytest.mark.anyio
async def test_async_function_success(self):
"""Test timeout decorator with successful async function"""
@timeout(seconds=5)
async def fast_async_function():
await anyio.sleep(0.01)
return "async_success"
result = await fast_async_function()
assert result == "async_success"
@pytest.mark.anyio
async def test_async_function_with_args(self):
"""Test timeout decorator with async function that has arguments"""
@timeout(seconds=5)
async def async_multiply(x, y):
await anyio.sleep(0.01)
return x * y
result = await async_multiply(6, 7)
assert result == 42
@pytest.mark.anyio
async def test_async_function_none_timeout(self):
"""Test async function with None timeout (no timeout applied)"""
@timeout(seconds=None)
async def async_function():
await anyio.sleep(0.01)
return "no_timeout"
result = await async_function()
assert result == "no_timeout"
@pytest.mark.anyio
async def test_async_function_timeout_disabled(self):
"""Test async function when timeout is disabled"""
@timeout(seconds=1)
async def slow_async_function():
await anyio.sleep(0.1)
return "completed"
# Without ENABLE_TIMEOUT_ASSERTION, timeout should not trigger
result = await slow_async_function()
assert result == "completed"
@pytest.mark.anyio
async def test_async_function_multiple_attempts(self):
"""Test async function with multiple attempts"""
call_count = 0
@timeout(seconds=0.1, attempts=3)
async def function_with_attempts():
nonlocal call_count
call_count += 1
await anyio.sleep(0.05)
return f"attempt_{call_count}"
result = await function_with_attempts()
assert result == "attempt_1"
class TestConstructResponse:
"""Test cases for construct_response function"""
@pytest.mark.anyio
async def test_construct_response_default(self):
"""Test construct_response with default parameters"""
with patch('common.connection_utils.make_response', new_callable=AsyncMock) as mock_make_response:
with patch('common.connection_utils.jsonify') as mock_jsonify:
mock_response = MagicMock()
mock_response.headers = {}
mock_make_response.return_value = mock_response
mock_jsonify.return_value = {"code": RetCode.SUCCESS, "message": "success"}
response = await construct_response()
mock_jsonify.assert_called_once_with({"code": RetCode.SUCCESS, "message": "success"})
assert response.headers["Access-Control-Allow-Origin"] == "*"
assert response.headers["Access-Control-Allow-Method"] == "*"
assert response.headers["Access-Control-Allow-Headers"] == "*"
assert response.headers["Access-Control-Expose-Headers"] == "Authorization"
@pytest.mark.anyio
async def test_construct_response_with_data(self):
"""Test construct_response with data parameter"""
with patch('common.connection_utils.make_response', new_callable=AsyncMock) as mock_make_response:
with patch('common.connection_utils.jsonify') as mock_jsonify:
mock_response = MagicMock()
mock_response.headers = {}
mock_make_response.return_value = mock_response
test_data = {"key": "value"}
response = await construct_response(data=test_data)
call_args = mock_jsonify.call_args[0][0]
assert call_args["code"] == RetCode.SUCCESS
assert call_args["message"] == "success"
assert call_args["data"] == test_data
@pytest.mark.anyio
async def test_construct_response_with_error_code(self):
"""Test construct_response with error code"""
with patch('common.connection_utils.make_response', new_callable=AsyncMock) as mock_make_response:
with patch('common.connection_utils.jsonify') as mock_jsonify:
mock_response = MagicMock()
mock_response.headers = {}
mock_make_response.return_value = mock_response
response = await construct_response(
code=RetCode.ARGUMENT_ERROR,
message="Invalid argument"
)
call_args = mock_jsonify.call_args[0][0]
assert call_args["code"] == RetCode.ARGUMENT_ERROR
assert call_args["message"] == "Invalid argument"
@pytest.mark.anyio
async def test_construct_response_with_auth(self):
"""Test construct_response with auth token"""
with patch('common.connection_utils.make_response', new_callable=AsyncMock) as mock_make_response:
with patch('common.connection_utils.jsonify') as mock_jsonify:
mock_response = MagicMock()
mock_response.headers = {}
mock_make_response.return_value = mock_response
auth_token = "Bearer test_token"
response = await construct_response(auth=auth_token)
assert response.headers["Authorization"] == auth_token
@pytest.mark.anyio
async def test_construct_response_none_values_excluded(self):
"""Test that None values are excluded from response (except code)"""
with patch('common.connection_utils.make_response', new_callable=AsyncMock) as mock_make_response:
with patch('common.connection_utils.jsonify') as mock_jsonify:
mock_response = MagicMock()
mock_response.headers = {}
mock_make_response.return_value = mock_response
response = await construct_response(
code=RetCode.SUCCESS,
message=None,
data=None
)
call_args = mock_jsonify.call_args[0][0]
assert "code" in call_args
assert "message" not in call_args
assert "data" not in call_args
class TestSyncConstructResponse:
"""Test cases for sync_construct_response function"""
def test_sync_construct_response_default(self):
"""Test sync_construct_response with default parameters"""
with patch('flask.make_response') as mock_make_response:
with patch('flask.jsonify') as mock_jsonify:
mock_response = MagicMock()
mock_response.headers = {}
mock_make_response.return_value = mock_response
mock_jsonify.return_value = {"code": RetCode.SUCCESS, "message": "success"}
response = sync_construct_response()
mock_jsonify.assert_called_once_with({"code": RetCode.SUCCESS, "message": "success"})
assert response.headers["Access-Control-Allow-Origin"] == "*"
assert response.headers["Access-Control-Allow-Method"] == "*"
assert response.headers["Access-Control-Allow-Headers"] == "*"
assert response.headers["Access-Control-Expose-Headers"] == "Authorization"
def test_sync_construct_response_with_data(self):
"""Test sync_construct_response with data parameter"""
with patch('flask.make_response') as mock_make_response:
with patch('flask.jsonify') as mock_jsonify:
mock_response = MagicMock()
mock_response.headers = {}
mock_make_response.return_value = mock_response
test_data = {"result": "test"}
response = sync_construct_response(data=test_data)
call_args = mock_jsonify.call_args[0][0]
assert call_args["code"] == RetCode.SUCCESS
assert call_args["message"] == "success"
assert call_args["data"] == test_data
def test_sync_construct_response_with_error_code(self):
"""Test sync_construct_response with error code"""
with patch('flask.make_response') as mock_make_response:
with patch('flask.jsonify') as mock_jsonify:
mock_response = MagicMock()
mock_response.headers = {}
mock_make_response.return_value = mock_response
response = sync_construct_response(
code=RetCode.SERVER_ERROR,
message="Server error occurred"
)
call_args = mock_jsonify.call_args[0][0]
assert call_args["code"] == RetCode.SERVER_ERROR
assert call_args["message"] == "Server error occurred"
def test_sync_construct_response_with_auth(self):
"""Test sync_construct_response with auth token"""
with patch('flask.make_response') as mock_make_response:
with patch('flask.jsonify') as mock_jsonify:
mock_response = MagicMock()
mock_response.headers = {}
mock_make_response.return_value = mock_response
auth_token = "Bearer sync_token"
response = sync_construct_response(auth=auth_token)
assert response.headers["Authorization"] == auth_token
def test_sync_construct_response_none_values_excluded(self):
"""Test that None values are excluded from response (except code)"""
with patch('flask.make_response') as mock_make_response:
with patch('flask.jsonify') as mock_jsonify:
mock_response = MagicMock()
mock_response.headers = {}
mock_make_response.return_value = mock_response
response = sync_construct_response(
code=RetCode.SUCCESS,
message=None,
data=None
)
call_args = mock_jsonify.call_args[0][0]
assert "code" in call_args
assert "message" not in call_args
assert "data" not in call_args

View file

@ -0,0 +1,620 @@
#
# Copyright 2025 The InfiniFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import asyncio
import os
import time
from unittest.mock import AsyncMock, MagicMock, patch, call
import httpx
import pytest
import anyio
from common.http_client import (
_clean_headers,
_get_delay,
async_request,
sync_request,
DEFAULT_TIMEOUT,
DEFAULT_FOLLOW_REDIRECTS,
DEFAULT_MAX_REDIRECTS,
DEFAULT_MAX_RETRIES,
DEFAULT_BACKOFF_FACTOR,
DEFAULT_USER_AGENT,
)
class TestCleanHeaders:
"""Test cases for _clean_headers helper function"""
def test_clean_headers_none_input(self):
"""Test _clean_headers with None headers"""
result = _clean_headers(None)
assert result is not None
assert "User-Agent" in result
assert result["User-Agent"] == DEFAULT_USER_AGENT
def test_clean_headers_with_auth_token(self):
"""Test _clean_headers with auth token"""
result = _clean_headers(None, auth_token="Bearer test_token")
assert result["Authorization"] == "Bearer test_token"
assert result["User-Agent"] == DEFAULT_USER_AGENT
def test_clean_headers_with_custom_headers(self):
"""Test _clean_headers with custom headers"""
custom_headers = {"X-Custom-Header": "custom_value", "Content-Type": "application/json"}
result = _clean_headers(custom_headers)
assert result["X-Custom-Header"] == "custom_value"
assert result["Content-Type"] == "application/json"
assert result["User-Agent"] == DEFAULT_USER_AGENT
def test_clean_headers_merges_auth_and_custom(self):
"""Test _clean_headers merges auth token and custom headers"""
custom_headers = {"X-Custom": "value"}
result = _clean_headers(custom_headers, auth_token="Bearer token")
assert result["Authorization"] == "Bearer token"
assert result["X-Custom"] == "value"
assert result["User-Agent"] == DEFAULT_USER_AGENT
def test_clean_headers_filters_none_values(self):
"""Test _clean_headers filters out None values"""
custom_headers = {"X-Valid": "value", "X-None": None}
result = _clean_headers(custom_headers)
assert "X-Valid" in result
assert "X-None" not in result
def test_clean_headers_converts_to_string(self):
"""Test _clean_headers converts header values to strings"""
custom_headers = {"X-Number": 123, "X-Bool": True}
result = _clean_headers(custom_headers)
assert result["X-Number"] == "123"
assert result["X-Bool"] == "True"
def test_clean_headers_empty_dict(self):
"""Test _clean_headers with empty dict"""
result = _clean_headers({})
assert "User-Agent" in result
class TestGetDelay:
"""Test cases for _get_delay helper function"""
def test_get_delay_first_attempt(self):
"""Test _get_delay for first retry attempt"""
delay = _get_delay(0.5, 0)
assert delay == 0.5 # 0.5 * 2^0
def test_get_delay_second_attempt(self):
"""Test _get_delay for second retry attempt"""
delay = _get_delay(0.5, 1)
assert delay == 1.0 # 0.5 * 2^1
def test_get_delay_third_attempt(self):
"""Test _get_delay for third retry attempt"""
delay = _get_delay(0.5, 2)
assert delay == 2.0 # 0.5 * 2^2
def test_get_delay_exponential_backoff(self):
"""Test _get_delay exponential backoff calculation"""
backoff_factor = 1.0
delays = [_get_delay(backoff_factor, i) for i in range(5)]
assert delays == [1.0, 2.0, 4.0, 8.0, 16.0]
def test_get_delay_custom_backoff_factor(self):
"""Test _get_delay with custom backoff factor"""
delay = _get_delay(2.0, 3)
assert delay == 16.0 # 2.0 * 2^3
class TestAsyncRequest:
"""Test cases for async_request function"""
@pytest.mark.anyio
async def test_async_request_success(self):
"""Test successful async_request"""
mock_response = MagicMock()
mock_response.status_code = 200
with patch('httpx.AsyncClient') as mock_client_class:
mock_client = AsyncMock()
mock_client.request = AsyncMock(return_value=mock_response)
mock_client_class.return_value.__aenter__.return_value = mock_client
response = await async_request("GET", "https://example.com")
assert response.status_code == 200
mock_client.request.assert_called_once()
@pytest.mark.anyio
async def test_async_request_with_custom_timeout(self):
"""Test async_request with custom timeout"""
mock_response = MagicMock()
mock_response.status_code = 200
with patch('httpx.AsyncClient') as mock_client_class:
mock_client = AsyncMock()
mock_client.request = AsyncMock(return_value=mock_response)
mock_client_class.return_value.__aenter__.return_value = mock_client
response = await async_request("GET", "https://example.com", timeout=30.0)
mock_client_class.assert_called_once()
call_kwargs = mock_client_class.call_args[1]
assert call_kwargs["timeout"] == 30.0
@pytest.mark.anyio
async def test_async_request_with_headers(self):
"""Test async_request with custom headers"""
mock_response = MagicMock()
mock_response.status_code = 200
with patch('httpx.AsyncClient') as mock_client_class:
mock_client = AsyncMock()
mock_client.request = AsyncMock(return_value=mock_response)
mock_client_class.return_value.__aenter__.return_value = mock_client
custom_headers = {"X-Custom": "value"}
response = await async_request("GET", "https://example.com", headers=custom_headers)
mock_client.request.assert_called_once()
call_kwargs = mock_client.request.call_args[1]
assert "X-Custom" in call_kwargs["headers"]
assert call_kwargs["headers"]["X-Custom"] == "value"
@pytest.mark.anyio
async def test_async_request_with_auth_token(self):
"""Test async_request with auth token"""
mock_response = MagicMock()
mock_response.status_code = 200
with patch('httpx.AsyncClient') as mock_client_class:
mock_client = AsyncMock()
mock_client.request = AsyncMock(return_value=mock_response)
mock_client_class.return_value.__aenter__.return_value = mock_client
response = await async_request("GET", "https://example.com", auth_token="Bearer token")
mock_client.request.assert_called_once()
call_kwargs = mock_client.request.call_args[1]
assert call_kwargs["headers"]["Authorization"] == "Bearer token"
@pytest.mark.anyio
async def test_async_request_with_follow_redirects(self):
"""Test async_request with follow_redirects parameter"""
mock_response = MagicMock()
mock_response.status_code = 200
with patch('httpx.AsyncClient') as mock_client_class:
mock_client = AsyncMock()
mock_client.request = AsyncMock(return_value=mock_response)
mock_client_class.return_value.__aenter__.return_value = mock_client
response = await async_request("GET", "https://example.com", follow_redirects=False)
mock_client_class.assert_called_once()
call_kwargs = mock_client_class.call_args[1]
assert call_kwargs["follow_redirects"] is False
@pytest.mark.anyio
async def test_async_request_with_max_redirects(self):
"""Test async_request with max_redirects parameter"""
mock_response = MagicMock()
mock_response.status_code = 200
with patch('httpx.AsyncClient') as mock_client_class:
mock_client = AsyncMock()
mock_client.request = AsyncMock(return_value=mock_response)
mock_client_class.return_value.__aenter__.return_value = mock_client
response = await async_request("GET", "https://example.com", max_redirects=10)
mock_client_class.assert_called_once()
call_kwargs = mock_client_class.call_args[1]
assert call_kwargs["max_redirects"] == 10
@pytest.mark.anyio
async def test_async_request_with_proxies(self):
"""Test async_request with proxies parameter"""
mock_response = MagicMock()
mock_response.status_code = 200
with patch('httpx.AsyncClient') as mock_client_class:
mock_client = AsyncMock()
mock_client.request = AsyncMock(return_value=mock_response)
mock_client_class.return_value.__aenter__.return_value = mock_client
proxies = "http://proxy.example.com:8080"
response = await async_request("GET", "https://example.com", proxies=proxies)
mock_client_class.assert_called_once()
call_kwargs = mock_client_class.call_args[1]
assert call_kwargs["proxies"] == proxies
@pytest.mark.anyio
async def test_async_request_retry_on_failure(self):
"""Test async_request retries on failure"""
mock_response = MagicMock()
mock_response.status_code = 200
with patch('httpx.AsyncClient') as mock_client_class:
mock_client = AsyncMock()
# First two calls fail, third succeeds
mock_client.request = AsyncMock(
side_effect=[
httpx.RequestError("Connection error"),
httpx.RequestError("Connection error"),
mock_response
]
)
mock_client_class.return_value.__aenter__.return_value = mock_client
with patch('asyncio.sleep', new_callable=AsyncMock):
response = await async_request("GET", "https://example.com", retries=2)
assert response.status_code == 200
assert mock_client.request.call_count == 3
@pytest.mark.anyio
async def test_async_request_exhausted_retries(self):
"""Test async_request raises exception when retries exhausted"""
with patch('httpx.AsyncClient') as mock_client_class:
mock_client = AsyncMock()
mock_client.request = AsyncMock(side_effect=httpx.RequestError("Connection error"))
mock_client_class.return_value.__aenter__.return_value = mock_client
with patch('asyncio.sleep', new_callable=AsyncMock):
with pytest.raises(httpx.RequestError):
await async_request("GET", "https://example.com", retries=2)
assert mock_client.request.call_count == 3 # Initial + 2 retries
@pytest.mark.anyio
async def test_async_request_zero_retries(self):
"""Test async_request with zero retries"""
with patch('httpx.AsyncClient') as mock_client_class:
mock_client = AsyncMock()
mock_client.request = AsyncMock(side_effect=httpx.RequestError("Connection error"))
mock_client_class.return_value.__aenter__.return_value = mock_client
with pytest.raises(httpx.RequestError):
await async_request("GET", "https://example.com", retries=0)
assert mock_client.request.call_count == 1 # Only initial attempt
@pytest.mark.anyio
async def test_async_request_custom_backoff(self):
"""Test async_request with custom backoff factor"""
mock_response = MagicMock()
mock_response.status_code = 200
with patch('httpx.AsyncClient') as mock_client_class:
mock_client = AsyncMock()
mock_client.request = AsyncMock(
side_effect=[httpx.RequestError("Error"), mock_response]
)
mock_client_class.return_value.__aenter__.return_value = mock_client
with patch('asyncio.sleep', new_callable=AsyncMock) as mock_sleep:
response = await async_request(
"GET", "https://example.com",
retries=1,
backoff_factor=2.0
)
# Check that sleep was called with correct delay (2.0 * 2^0 = 2.0)
mock_sleep.assert_called_once()
assert mock_sleep.call_args[0][0] == 2.0
@pytest.mark.anyio
async def test_async_request_with_json_data(self):
"""Test async_request with JSON data"""
mock_response = MagicMock()
mock_response.status_code = 200
with patch('httpx.AsyncClient') as mock_client_class:
mock_client = AsyncMock()
mock_client.request = AsyncMock(return_value=mock_response)
mock_client_class.return_value.__aenter__.return_value = mock_client
json_data = {"key": "value"}
response = await async_request("POST", "https://example.com", json=json_data)
mock_client.request.assert_called_once()
call_kwargs = mock_client.request.call_args[1]
assert call_kwargs["json"] == json_data
@pytest.mark.anyio
async def test_async_request_post_method(self):
"""Test async_request with POST method"""
mock_response = MagicMock()
mock_response.status_code = 201
with patch('httpx.AsyncClient') as mock_client_class:
mock_client = AsyncMock()
mock_client.request = AsyncMock(return_value=mock_response)
mock_client_class.return_value.__aenter__.return_value = mock_client
response = await async_request("POST", "https://example.com", json={"data": "test"})
assert response.status_code == 201
call_args = mock_client.request.call_args
assert call_args[1]["method"] == "POST"
class TestSyncRequest:
"""Test cases for sync_request function"""
def test_sync_request_success(self):
"""Test successful sync_request"""
mock_response = MagicMock()
mock_response.status_code = 200
with patch('httpx.Client') as mock_client_class:
mock_client = MagicMock()
mock_client.request = MagicMock(return_value=mock_response)
mock_client_class.return_value.__enter__.return_value = mock_client
response = sync_request("GET", "https://example.com")
assert response.status_code == 200
mock_client.request.assert_called_once()
def test_sync_request_with_custom_timeout(self):
"""Test sync_request with custom timeout"""
mock_response = MagicMock()
mock_response.status_code = 200
with patch('httpx.Client') as mock_client_class:
mock_client = MagicMock()
mock_client.request = MagicMock(return_value=mock_response)
mock_client_class.return_value.__enter__.return_value = mock_client
response = sync_request("GET", "https://example.com", timeout=60.0)
mock_client_class.assert_called_once()
call_kwargs = mock_client_class.call_args[1]
assert call_kwargs["timeout"] == 60.0
def test_sync_request_with_headers(self):
"""Test sync_request with custom headers"""
mock_response = MagicMock()
mock_response.status_code = 200
with patch('httpx.Client') as mock_client_class:
mock_client = MagicMock()
mock_client.request = MagicMock(return_value=mock_response)
mock_client_class.return_value.__enter__.return_value = mock_client
custom_headers = {"X-Test": "test_value"}
response = sync_request("GET", "https://example.com", headers=custom_headers)
mock_client.request.assert_called_once()
call_kwargs = mock_client.request.call_args[1]
assert "X-Test" in call_kwargs["headers"]
assert call_kwargs["headers"]["X-Test"] == "test_value"
def test_sync_request_with_auth_token(self):
"""Test sync_request with auth token"""
mock_response = MagicMock()
mock_response.status_code = 200
with patch('httpx.Client') as mock_client_class:
mock_client = MagicMock()
mock_client.request = MagicMock(return_value=mock_response)
mock_client_class.return_value.__enter__.return_value = mock_client
response = sync_request("GET", "https://example.com", auth_token="Bearer sync_token")
mock_client.request.assert_called_once()
call_kwargs = mock_client.request.call_args[1]
assert call_kwargs["headers"]["Authorization"] == "Bearer sync_token"
def test_sync_request_with_follow_redirects(self):
"""Test sync_request with follow_redirects parameter"""
mock_response = MagicMock()
mock_response.status_code = 200
with patch('httpx.Client') as mock_client_class:
mock_client = MagicMock()
mock_client.request = MagicMock(return_value=mock_response)
mock_client_class.return_value.__enter__.return_value = mock_client
response = sync_request("GET", "https://example.com", follow_redirects=False)
mock_client_class.assert_called_once()
call_kwargs = mock_client_class.call_args[1]
assert call_kwargs["follow_redirects"] is False
def test_sync_request_with_max_redirects(self):
"""Test sync_request with max_redirects parameter"""
mock_response = MagicMock()
mock_response.status_code = 200
with patch('httpx.Client') as mock_client_class:
mock_client = MagicMock()
mock_client.request = MagicMock(return_value=mock_response)
mock_client_class.return_value.__enter__.return_value = mock_client
response = sync_request("GET", "https://example.com", max_redirects=5)
mock_client_class.assert_called_once()
call_kwargs = mock_client_class.call_args[1]
assert call_kwargs["max_redirects"] == 5
def test_sync_request_with_proxies(self):
"""Test sync_request with proxies parameter"""
mock_response = MagicMock()
mock_response.status_code = 200
with patch('httpx.Client') as mock_client_class:
mock_client = MagicMock()
mock_client.request = MagicMock(return_value=mock_response)
mock_client_class.return_value.__enter__.return_value = mock_client
proxies = "http://proxy.example.com:8080"
response = sync_request("GET", "https://example.com", proxies=proxies)
mock_client_class.assert_called_once()
call_kwargs = mock_client_class.call_args[1]
assert call_kwargs["proxies"] == proxies
def test_sync_request_retry_on_failure(self):
"""Test sync_request retries on failure"""
mock_response = MagicMock()
mock_response.status_code = 200
with patch('httpx.Client') as mock_client_class:
mock_client = MagicMock()
# First two calls fail, third succeeds
mock_client.request = MagicMock(
side_effect=[
httpx.RequestError("Connection error"),
httpx.RequestError("Connection error"),
mock_response
]
)
mock_client_class.return_value.__enter__.return_value = mock_client
with patch('time.sleep'):
response = sync_request("GET", "https://example.com", retries=2)
assert response.status_code == 200
assert mock_client.request.call_count == 3
def test_sync_request_exhausted_retries(self):
"""Test sync_request raises exception when retries exhausted"""
with patch('httpx.Client') as mock_client_class:
mock_client = MagicMock()
mock_client.request = MagicMock(side_effect=httpx.RequestError("Connection error"))
mock_client_class.return_value.__enter__.return_value = mock_client
with patch('time.sleep'):
with pytest.raises(httpx.RequestError):
sync_request("GET", "https://example.com", retries=2)
assert mock_client.request.call_count == 3 # Initial + 2 retries
def test_sync_request_zero_retries(self):
"""Test sync_request with zero retries"""
with patch('httpx.Client') as mock_client_class:
mock_client = MagicMock()
mock_client.request = MagicMock(side_effect=httpx.RequestError("Connection error"))
mock_client_class.return_value.__enter__.return_value = mock_client
with pytest.raises(httpx.RequestError):
sync_request("GET", "https://example.com", retries=0)
assert mock_client.request.call_count == 1 # Only initial attempt
def test_sync_request_custom_backoff(self):
"""Test sync_request with custom backoff factor"""
mock_response = MagicMock()
mock_response.status_code = 200
with patch('httpx.Client') as mock_client_class:
mock_client = MagicMock()
mock_client.request = MagicMock(
side_effect=[httpx.RequestError("Error"), mock_response]
)
mock_client_class.return_value.__enter__.return_value = mock_client
with patch('time.sleep') as mock_sleep:
response = sync_request(
"GET", "https://example.com",
retries=1,
backoff_factor=3.0
)
# Check that sleep was called with correct delay (3.0 * 2^0 = 3.0)
mock_sleep.assert_called_once()
assert mock_sleep.call_args[0][0] == 3.0
def test_sync_request_with_json_data(self):
"""Test sync_request with JSON data"""
mock_response = MagicMock()
mock_response.status_code = 200
with patch('httpx.Client') as mock_client_class:
mock_client = MagicMock()
mock_client.request = MagicMock(return_value=mock_response)
mock_client_class.return_value.__enter__.return_value = mock_client
json_data = {"test": "data"}
response = sync_request("POST", "https://example.com", json=json_data)
mock_client.request.assert_called_once()
call_kwargs = mock_client.request.call_args[1]
assert call_kwargs["json"] == json_data
def test_sync_request_put_method(self):
"""Test sync_request with PUT method"""
mock_response = MagicMock()
mock_response.status_code = 200
with patch('httpx.Client') as mock_client_class:
mock_client = MagicMock()
mock_client.request = MagicMock(return_value=mock_response)
mock_client_class.return_value.__enter__.return_value = mock_client
response = sync_request("PUT", "https://example.com", json={"update": "data"})
assert response.status_code == 200
call_args = mock_client.request.call_args
assert call_args[1]["method"] == "PUT"
def test_sync_request_delete_method(self):
"""Test sync_request with DELETE method"""
mock_response = MagicMock()
mock_response.status_code = 204
with patch('httpx.Client') as mock_client_class:
mock_client = MagicMock()
mock_client.request = MagicMock(return_value=mock_response)
mock_client_class.return_value.__enter__.return_value = mock_client
response = sync_request("DELETE", "https://example.com/resource/123")
assert response.status_code == 204
call_args = mock_client.request.call_args
assert call_args[1]["method"] == "DELETE"
class TestDefaultConstants:
"""Test cases for default constants"""
def test_default_timeout(self):
"""Test DEFAULT_TIMEOUT is set correctly"""
assert DEFAULT_TIMEOUT == 15.0 or isinstance(DEFAULT_TIMEOUT, float)
def test_default_follow_redirects(self):
"""Test DEFAULT_FOLLOW_REDIRECTS is set correctly"""
assert isinstance(DEFAULT_FOLLOW_REDIRECTS, bool)
def test_default_max_redirects(self):
"""Test DEFAULT_MAX_REDIRECTS is set correctly"""
assert DEFAULT_MAX_REDIRECTS == 30 or isinstance(DEFAULT_MAX_REDIRECTS, int)
def test_default_max_retries(self):
"""Test DEFAULT_MAX_RETRIES is set correctly"""
assert DEFAULT_MAX_RETRIES == 2 or isinstance(DEFAULT_MAX_RETRIES, int)
def test_default_backoff_factor(self):
"""Test DEFAULT_BACKOFF_FACTOR is set correctly"""
assert DEFAULT_BACKOFF_FACTOR == 0.5 or isinstance(DEFAULT_BACKOFF_FACTOR, float)
def test_default_user_agent(self):
"""Test DEFAULT_USER_AGENT is set correctly"""
assert DEFAULT_USER_AGENT == "ragflow-http-client" or isinstance(DEFAULT_USER_AGENT, str)