新增了Twitch、Telegram、Discord、Slack、Mattermost、WeChat、Zalo等多渠道的单元测试用例,覆盖了令牌处理、速率限制、消息去重、会话解析、格式转换、安全策略等模块 同时在测试配置中添加了测试用的OpenAI API密钥环境变量
297 lines
11 KiB
Python
297 lines
11 KiB
Python
from __future__ import annotations
|
|
|
|
import pytest
|
|
from pydantic import ValidationError
|
|
|
|
from yuxi.gateway.protocol.handshake import ConnectParams, HelloFeatures, HelloOk, handle_handshake
|
|
|
|
|
|
class TestConnectParams:
|
|
def test_default_construction(self):
|
|
params = ConnectParams()
|
|
assert params.min_protocol == 1
|
|
assert params.max_protocol == 1
|
|
assert params.client_id == ""
|
|
assert params.client_version == ""
|
|
assert params.client_platform == ""
|
|
assert params.auth_token is None
|
|
assert params.caps == []
|
|
assert params.client_display_name == ""
|
|
assert params.client_device_family == ""
|
|
assert params.client_model_identifier == ""
|
|
assert params.client_mode == "default"
|
|
assert params.client_instance_id == ""
|
|
assert params.role == ""
|
|
assert params.locale == ""
|
|
assert params.user_agent == ""
|
|
|
|
def test_full_construction(self):
|
|
params = ConnectParams(
|
|
min_protocol=1,
|
|
max_protocol=1,
|
|
client_id="web-client-001",
|
|
client_version="2.3.0",
|
|
client_platform="web",
|
|
auth_token="jwt-token-here",
|
|
caps=["streaming", "commands"],
|
|
client_display_name="TestBot",
|
|
client_device_family="desktop",
|
|
client_model_identifier="win-x64",
|
|
client_mode="default",
|
|
client_instance_id="inst-001",
|
|
commands=["/start", "/help"],
|
|
permissions={"admin": True},
|
|
path_env="/api/v1",
|
|
role="bot",
|
|
scopes=["messages.read", "messages.write"],
|
|
locale="zh-CN",
|
|
user_agent="ForcePilot/2.3.0",
|
|
)
|
|
assert params.client_id == "web-client-001"
|
|
assert params.client_version == "2.3.0"
|
|
assert params.client_platform == "web"
|
|
assert params.auth_token == "jwt-token-here"
|
|
assert params.caps == ["streaming", "commands"]
|
|
assert params.client_display_name == "TestBot"
|
|
assert params.client_device_family == "desktop"
|
|
assert params.scopes == ["messages.read", "messages.write"]
|
|
assert params.locale == "zh-CN"
|
|
|
|
def test_client_platform_values(self):
|
|
for platform in ["web", "cli", "android", "ios"]:
|
|
params = ConnectParams(client_platform=platform)
|
|
assert params.client_platform == platform
|
|
|
|
def test_min_protocol_int(self):
|
|
with pytest.raises(ValidationError):
|
|
ConnectParams(min_protocol="invalid")
|
|
|
|
def test_max_protocol_int(self):
|
|
with pytest.raises(ValidationError):
|
|
ConnectParams(max_protocol="invalid")
|
|
|
|
def test_auth_token_optional(self):
|
|
params = ConnectParams()
|
|
assert params.auth_token is None
|
|
|
|
def test_caps_empty_default(self):
|
|
params = ConnectParams()
|
|
assert params.caps == []
|
|
|
|
def test_caps_from_list(self):
|
|
params = ConnectParams(caps=["streaming", "reactions"])
|
|
assert params.caps == ["streaming", "reactions"]
|
|
|
|
def test_json_serialization(self):
|
|
params = ConnectParams(client_id="cli-1", client_platform="cli", locale="en")
|
|
data = params.model_dump()
|
|
assert data["client_id"] == "cli-1"
|
|
assert data["client_platform"] == "cli"
|
|
assert data["locale"] == "en"
|
|
|
|
def test_json_deserialization(self):
|
|
data = {
|
|
"min_protocol": 1,
|
|
"max_protocol": 1,
|
|
"client_id": "web-1",
|
|
"client_version": "1.0",
|
|
"client_platform": "web",
|
|
"auth_token": None,
|
|
"caps": ["streaming"],
|
|
"client_display_name": "WebBot",
|
|
}
|
|
params = ConnectParams.model_validate(data)
|
|
assert params.client_id == "web-1"
|
|
assert params.client_platform == "web"
|
|
assert params.client_display_name == "WebBot"
|
|
|
|
def test_field_count_gte_17(self):
|
|
params = ConnectParams()
|
|
fields = list(ConnectParams.model_fields.keys())
|
|
assert len(fields) >= 17, f"ConnectParams has {len(fields)} fields, need >= 17: {fields}"
|
|
|
|
|
|
class TestHelloFeatures:
|
|
def test_default_construction(self):
|
|
features = HelloFeatures()
|
|
assert features.methods == []
|
|
assert features.events == []
|
|
|
|
def test_full_construction(self):
|
|
features = HelloFeatures(
|
|
methods=["ping", "chat.send"],
|
|
events=["message.received", "typing"],
|
|
)
|
|
assert features.methods == ["ping", "chat.send"]
|
|
assert features.events == ["message.received", "typing"]
|
|
|
|
def test_json_serialization(self):
|
|
features = HelloFeatures(methods=["ping"], events=["tick"])
|
|
data = features.model_dump()
|
|
assert data["methods"] == ["ping"]
|
|
assert data["events"] == ["tick"]
|
|
|
|
|
|
class TestHelloOk:
|
|
def test_minimal_construction(self):
|
|
hello = HelloOk(protocol=1, server_version="1.0.0", server_id="gw-1", session_id="sess-1")
|
|
assert hello.protocol == 1
|
|
assert hello.server_version == "1.0.0"
|
|
assert hello.server_id == "gw-1"
|
|
assert hello.session_id == "sess-1"
|
|
assert hello.features.methods == []
|
|
assert hello.features.events == []
|
|
assert hello.type == "hello-ok"
|
|
assert hello.snapshot is None
|
|
assert hello.dm_policy == "pairing"
|
|
assert hello.group_policy == "allowlist"
|
|
assert hello.conn_id == ""
|
|
assert hello.max_payload == 1048576
|
|
assert hello.max_buffered_bytes == 10485760
|
|
assert hello.tick_interval_ms == 5000
|
|
|
|
def test_full_construction(self):
|
|
features = HelloFeatures(methods=["ping"], events=["tick"])
|
|
hello = HelloOk(
|
|
protocol=1,
|
|
server_version="2.0.0",
|
|
server_id="forcepilot-gw",
|
|
session_id="abc-123",
|
|
features=features,
|
|
snapshot={"threads": 5},
|
|
dm_policy="pairing",
|
|
group_policy="allowlist",
|
|
conn_id="conn-abc12345",
|
|
max_payload=2097152,
|
|
max_buffered_bytes=20971520,
|
|
tick_interval_ms=3000,
|
|
)
|
|
assert hello.features.methods == ["ping"]
|
|
assert hello.snapshot == {"threads": 5}
|
|
assert hello.conn_id == "conn-abc12345"
|
|
assert hello.max_payload == 2097152
|
|
assert hello.tick_interval_ms == 3000
|
|
|
|
def test_protocol_is_required(self):
|
|
with pytest.raises(ValidationError):
|
|
HelloOk(server_version="1.0.0", server_id="gw", session_id="s1")
|
|
|
|
def test_server_version_is_required(self):
|
|
with pytest.raises(ValidationError):
|
|
HelloOk(protocol=1, server_id="gw", session_id="s1")
|
|
|
|
def test_server_id_is_required(self):
|
|
with pytest.raises(ValidationError):
|
|
HelloOk(protocol=1, server_version="1.0.0", session_id="s1")
|
|
|
|
def test_session_id_is_required(self):
|
|
with pytest.raises(ValidationError):
|
|
HelloOk(protocol=1, server_version="1.0.0", server_id="gw")
|
|
|
|
def test_default_dm_policy(self):
|
|
hello = HelloOk(protocol=1, server_version="1.0.0", server_id="gw", session_id="s1")
|
|
assert hello.dm_policy == "pairing"
|
|
|
|
def test_default_group_policy(self):
|
|
hello = HelloOk(protocol=1, server_version="1.0.0", server_id="gw", session_id="s1")
|
|
assert hello.group_policy == "allowlist"
|
|
|
|
def test_json_serialization(self):
|
|
hello = HelloOk(protocol=1, server_version="1.0.0", server_id="gw-1", session_id="sess-1")
|
|
data = hello.model_dump()
|
|
assert data["protocol"] == 1
|
|
assert data["type"] == "hello-ok"
|
|
assert data["dm_policy"] == "pairing"
|
|
assert data["max_payload"] == 1048576
|
|
|
|
def test_deserialization(self):
|
|
data = {
|
|
"protocol": 1,
|
|
"server_version": "1.0.0",
|
|
"server_id": "gw-1",
|
|
"session_id": "sess-1",
|
|
"features": {"methods": [], "events": []},
|
|
"snapshot": None,
|
|
"dm_policy": "pairing",
|
|
"group_policy": "allowlist",
|
|
"conn_id": "",
|
|
"max_payload": 1048576,
|
|
"max_buffered_bytes": 10485760,
|
|
"tick_interval_ms": 5000,
|
|
}
|
|
hello = HelloOk.model_validate(data)
|
|
assert hello.server_version == "1.0.0"
|
|
assert hello.max_payload == 1048576
|
|
|
|
def test_field_count_gte_11(self):
|
|
hello = HelloOk(protocol=1, server_version="1.0.0", server_id="gw", session_id="s1")
|
|
fields = list(HelloOk.model_fields.keys())
|
|
assert len(fields) >= 11, f"HelloOk has {len(fields)} fields, need >= 11: {fields}"
|
|
|
|
|
|
class TestHandleHandshake:
|
|
@pytest.mark.asyncio
|
|
async def test_successful_handshake(self):
|
|
params = ConnectParams(min_protocol=1, max_protocol=1, client_id="cli-1")
|
|
hello = await handle_handshake(params)
|
|
assert isinstance(hello, HelloOk)
|
|
assert hello.protocol == 1
|
|
assert hello.server_version == "1.0.0"
|
|
assert hello.server_id == "forcepilot-gateway"
|
|
assert len(hello.session_id) > 0
|
|
assert hello.conn_id.startswith("conn-")
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_version_negotiation_fails(self):
|
|
params = ConnectParams(min_protocol=3, max_protocol=5)
|
|
with pytest.raises(ValueError, match="Protocol version negotiation failed"):
|
|
await handle_handshake(params)
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_features_structured(self):
|
|
params = ConnectParams(min_protocol=1, max_protocol=1)
|
|
hello = await handle_handshake(params)
|
|
assert isinstance(hello.features, HelloFeatures)
|
|
assert len(hello.features.methods) > 0
|
|
assert len(hello.features.events) > 0
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_session_id_unique(self):
|
|
params = ConnectParams(min_protocol=1, max_protocol=1)
|
|
hello1 = await handle_handshake(params)
|
|
hello2 = await handle_handshake(params)
|
|
assert hello1.session_id != hello2.session_id
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_session_id_is_uuid_format(self):
|
|
params = ConnectParams(min_protocol=1, max_protocol=1)
|
|
hello = await handle_handshake(params)
|
|
parts = hello.session_id.split("-")
|
|
assert len(parts) == 5
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_default_dm_policy(self):
|
|
params = ConnectParams(min_protocol=1, max_protocol=1)
|
|
hello = await handle_handshake(params)
|
|
assert hello.dm_policy == "pairing"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_default_group_policy(self):
|
|
params = ConnectParams(min_protocol=1, max_protocol=1)
|
|
hello = await handle_handshake(params)
|
|
assert hello.group_policy == "allowlist"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_hello_type_is_hello_ok(self):
|
|
params = ConnectParams(min_protocol=1, max_protocol=1)
|
|
hello = await handle_handshake(params)
|
|
assert hello.type == "hello-ok"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_policy_defaults(self):
|
|
params = ConnectParams(min_protocol=1, max_protocol=1)
|
|
hello = await handle_handshake(params)
|
|
assert hello.max_payload == 1048576
|
|
assert hello.max_buffered_bytes == 10485760
|
|
assert hello.tick_interval_ms == 5000
|