mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-03 01:40:21 +08:00
- 删除 aether-hub、aether-proxy 独立项目及其 Dockerfile/配置 - 删除 crates/aether-executor 和 crates/aether-gateway 全部模块 - 新增 apps/ 目录作为应用入口 - 将 hub 概念重构为 gateway tunnel transport - 将 executor 重构为 execution runtime - 新增 tunnel.rs 合约定义和 testkit tunnel/execution_runtime 模块 - 更新 Python 服务层和测试适配新架构命名
132 lines
4.1 KiB
Python
132 lines
4.1 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
import struct
|
|
from typing import Any
|
|
|
|
import httpx
|
|
import pytest
|
|
|
|
from src.services.proxy_node.gateway_tunnel_transport import GatewayTunnelTransport
|
|
from src.services.proxy_node.tunnel_config import TunnelRelayConfig
|
|
|
|
|
|
class _FakeRelayClient:
|
|
def __init__(self, response: httpx.Response) -> None:
|
|
self.response = response
|
|
self.sent_request: httpx.Request | None = None
|
|
self.sent_body: bytes | None = None
|
|
|
|
def build_request(self, method: str, url: str, **kwargs: Any) -> httpx.Request:
|
|
return httpx.Request(method, url, **kwargs)
|
|
|
|
async def send(self, request: httpx.Request, *, stream: bool = False) -> httpx.Response:
|
|
_ = stream
|
|
self.sent_request = request
|
|
self.sent_body = await request.aread()
|
|
self.response.request = request
|
|
return self.response
|
|
|
|
async def aclose(self) -> None:
|
|
return None
|
|
|
|
|
|
def _relay_config() -> TunnelRelayConfig:
|
|
return TunnelRelayConfig(
|
|
enabled=True,
|
|
url="http://127.0.0.1:8084",
|
|
connect_timeout_seconds=1.0,
|
|
)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_transport_encodes_local_relay_envelope(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
transport = GatewayTunnelTransport("node-1", timeout=12.0)
|
|
fake_client = _FakeRelayClient(httpx.Response(200, content=b"ok"))
|
|
monkeypatch.setattr(
|
|
"src.services.proxy_node.gateway_tunnel_transport.get_tunnel_relay_config",
|
|
_relay_config,
|
|
)
|
|
monkeypatch.setattr(transport, "_relay_client", fake_client)
|
|
|
|
request = httpx.Request(
|
|
"POST",
|
|
"https://example.com/v1/chat/completions",
|
|
headers={"content-type": "application/json", "connection": "keep-alive"},
|
|
content=b'{"hello":"world"}',
|
|
)
|
|
|
|
response = await transport.handle_async_request(request)
|
|
assert response.status_code == 200
|
|
await response.aclose()
|
|
|
|
assert fake_client.sent_request is not None
|
|
payload = fake_client.sent_body
|
|
assert payload is not None
|
|
meta_len = struct.unpack("!I", payload[:4])[0]
|
|
meta = json.loads(payload[4 : 4 + meta_len].decode("utf-8"))
|
|
assert meta == {
|
|
"method": "POST",
|
|
"url": "https://example.com/v1/chat/completions",
|
|
"headers": {"content-type": "application/json"},
|
|
"timeout": 12,
|
|
}
|
|
assert payload[4 + meta_len :] == b'{"hello":"world"}'
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_transport_maps_relay_timeout_to_read_timeout(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
transport = GatewayTunnelTransport("node-1", timeout=12.0)
|
|
fake_client = _FakeRelayClient(
|
|
httpx.Response(
|
|
504,
|
|
headers={"x-aether-tunnel-error": "timeout"},
|
|
content=b"relay timed out",
|
|
)
|
|
)
|
|
monkeypatch.setattr(
|
|
"src.services.proxy_node.gateway_tunnel_transport.get_tunnel_relay_config",
|
|
_relay_config,
|
|
)
|
|
monkeypatch.setattr(transport, "_relay_client", fake_client)
|
|
|
|
request = httpx.Request("GET", "https://example.com")
|
|
|
|
with pytest.raises(httpx.ReadTimeout, match="relay timed out"):
|
|
await transport.handle_async_request(request)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_transport_streams_request_body_from_async_generator(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
transport = GatewayTunnelTransport("node-1", timeout=12.0)
|
|
fake_client = _FakeRelayClient(httpx.Response(200, content=b"ok"))
|
|
monkeypatch.setattr(
|
|
"src.services.proxy_node.gateway_tunnel_transport.get_tunnel_relay_config",
|
|
_relay_config,
|
|
)
|
|
monkeypatch.setattr(transport, "_relay_client", fake_client)
|
|
|
|
async def body() -> Any:
|
|
yield b'{"hello":'
|
|
yield b'"world"}'
|
|
|
|
request = httpx.Request(
|
|
"POST",
|
|
"https://example.com/v1/chat/completions",
|
|
headers={"content-type": "application/json"},
|
|
content=body(),
|
|
)
|
|
|
|
response = await transport.handle_async_request(request)
|
|
assert response.status_code == 200
|
|
await response.aclose()
|
|
|
|
payload = fake_client.sent_body
|
|
assert payload is not None
|
|
meta_len = struct.unpack("!I", payload[:4])[0]
|
|
assert payload[4 + meta_len :] == b'{"hello":"world"}'
|