ForcePilot/backend/test/unit/channel/common/test_http.py
Kris bab30f2715
Some checks failed
Deploy VitePress site to Pages / build (push) Has been cancelled
Ruff Format Check / Ruff Format & Lint (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
feat:0715
2026-07-15 12:30:58 +08:00

191 lines
7.0 KiB
Python

from __future__ import annotations
import json as stdlib_json
from unittest.mock import AsyncMock, MagicMock, patch
import httpx
import pytest
from yuxi.channel.common.http import ChannelHttpClient
class TestChannelHttpClientLifecycle:
async def test_creates_internal_client_when_none_provided(self):
client = ChannelHttpClient()
assert client._own_client is True
assert isinstance(client._client, httpx.AsyncClient)
await client._client.aclose()
async def test_internal_client_closed_on_aexit(self):
client = ChannelHttpClient()
inner = client._client
async with client:
pass
assert inner.is_closed
async def test_external_client_not_closed_on_aexit(self):
external = httpx.AsyncClient()
client = ChannelHttpClient(client=external)
assert client._own_client is False
async with client:
pass
assert not external.is_closed
await external.aclose()
async def test_aenter_returns_self(self):
client = ChannelHttpClient()
async with client as entered:
assert entered is client
class TestChannelHttpClientRequestJson:
async def test_request_json_returns_parsed_response(self):
mock_client = MagicMock()
mock_response = MagicMock()
mock_response.json.return_value = {"ok": True}
mock_response.text = ""
mock_client.request = AsyncMock(return_value=mock_response)
client = ChannelHttpClient(client=mock_client)
result = await client.request_json("GET", "https://example.com/api")
assert result == {"ok": True}
mock_client.request.assert_awaited_once_with(
"GET",
"https://example.com/api",
json=None,
params=None,
headers=None,
files=None,
timeout=60.0,
)
mock_response.raise_for_status.assert_called_once()
async def test_post_json_forwards_arguments(self):
mock_client = MagicMock()
mock_response = MagicMock()
mock_response.json.return_value = {"id": "123"}
mock_response.text = ""
mock_client.request = AsyncMock(return_value=mock_response)
client = ChannelHttpClient(client=mock_client)
result = await client.post_json(
"https://example.com/api",
json={"foo": "bar"},
params={"k": "v"},
headers={"Authorization": "Bearer token"},
files=[("file", b"data")],
timeout=10.0,
)
assert result == {"id": "123"}
mock_client.request.assert_awaited_once_with(
"POST",
"https://example.com/api",
json={"foo": "bar"},
params={"k": "v"},
headers={"Authorization": "Bearer token"},
files=[("file", b"data")],
timeout=10.0,
)
async def test_get_json_forwards_arguments(self):
mock_client = MagicMock()
mock_response = MagicMock()
mock_response.json.return_value = {"items": []}
mock_response.text = ""
mock_client.request = AsyncMock(return_value=mock_response)
client = ChannelHttpClient(client=mock_client)
result = await client.get_json(
"https://example.com/api",
params={"page": "1"},
headers={"Accept": "application/json"},
timeout=5.0,
)
assert result == {"items": []}
mock_client.request.assert_awaited_once_with(
"GET",
"https://example.com/api",
json=None,
params={"page": "1"},
headers={"Accept": "application/json"},
files=None,
timeout=5.0,
)
async def test_request_json_raises_on_http_status_error(self):
mock_client = MagicMock()
mock_response = MagicMock()
mock_response.status_code = 500
mock_response.text = "internal error"
mock_client.request = AsyncMock(
side_effect=httpx.HTTPStatusError("error", request=MagicMock(), response=mock_response)
)
client = ChannelHttpClient(client=mock_client)
with pytest.raises(httpx.HTTPStatusError):
await client.request_json("GET", "https://example.com/api")
async def test_request_json_raises_on_request_error(self):
mock_client = MagicMock()
mock_client.request = AsyncMock(side_effect=httpx.RequestError("connection refused", request=MagicMock()))
client = ChannelHttpClient(client=mock_client)
with pytest.raises(httpx.RequestError):
await client.request_json("GET", "https://example.com/api")
async def test_request_json_raises_on_invalid_json(self):
mock_client = MagicMock()
mock_response = MagicMock()
mock_response.json.side_effect = stdlib_json.JSONDecodeError("boom", "not json", 0)
mock_response.text = "not json"
mock_client.request = AsyncMock(return_value=mock_response)
client = ChannelHttpClient(client=mock_client)
with pytest.raises(stdlib_json.JSONDecodeError):
await client.request_json("GET", "https://example.com/api", json=stdlib_json)
async def test_logs_on_http_status_error(self):
mock_client = MagicMock()
mock_response = MagicMock()
mock_response.status_code = 503
mock_response.text = "service unavailable"
mock_client.request = AsyncMock(
side_effect=httpx.HTTPStatusError("error", request=MagicMock(), response=mock_response)
)
client = ChannelHttpClient(client=mock_client)
with patch("yuxi.channel.common.http.logger") as mock_logger:
with pytest.raises(httpx.HTTPStatusError):
await client.request_json("POST", "https://example.com/api", json={"k": "v"})
mock_logger.error.assert_called_once()
message = mock_logger.error.call_args[0][0]
assert "Channel HTTP" in message
async def test_logs_on_request_error(self):
mock_client = MagicMock()
mock_client.request = AsyncMock(side_effect=httpx.RequestError("timeout", request=MagicMock()))
client = ChannelHttpClient(client=mock_client)
with patch("yuxi.channel.common.http.logger") as mock_logger:
with pytest.raises(httpx.RequestError):
await client.request_json("GET", "https://example.com/api")
mock_logger.error.assert_called_once()
async def test_logs_on_invalid_json(self):
mock_client = MagicMock()
mock_response = MagicMock()
mock_response.json.side_effect = stdlib_json.JSONDecodeError("boom", "x", 0)
mock_response.text = "x"
mock_client.request = AsyncMock(return_value=mock_response)
client = ChannelHttpClient(client=mock_client)
with patch("yuxi.channel.common.http.logger") as mock_logger:
with pytest.raises(stdlib_json.JSONDecodeError):
await client.request_json("GET", "https://example.com/api", json=stdlib_json)
mock_logger.error.assert_called_once()