ForcePilot/backend/test/unit/channels/plugins/qqbot/test_constants.py
Kris 532e7c59ec test: add and fix multi-channel unit tests
1. 新增QQBot、Wecom、Dingtalk、Instagram、WechatMp等渠道的单元测试夹具与测试用例
2. 修复WechatILink的缓存键顺序与测试用例
3. 补全Feishu适配器的日志断言与测试逻辑
4. 清理WechatILink冗余的clear_context_token测试代码
2026-07-08 22:59:24 +08:00

259 lines
10 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""qqbot 插件 _constants.py 模块测试。
验证共享常量模块的正确性:
- 模块导出完整(``__all__`` 与实际定义一致)。
- 常量值与 ``manifest.json`` 中对应字段对齐F-03 单真相源):
- ``CHANNEL_TARGET`` 与 manifest ``channel_type`` 一致。
- ``HTTP_TIMEOUT_SECONDS`` 与 manifest ``http_timeout_ms.default`` 对齐。
- ``HTTP_MAX_CONNECTIONS`` 与 manifest ``resource_quota.max_connections`` 对齐。
- ``WS_HEARTBEAT_INTERVAL_MS`` 与 manifest ``ws_heartbeat_interval_ms.default`` 对齐。
- ``DEFAULT_INTENTS`` 与 manifest ``intents.default`` 对齐。
- ``MAX_MESSAGE_LENGTH`` 与 manifest ``max_message_length`` 对齐。
_constants.py 作为 ConfigPort 未命中时的本地回退默认值,其值必须与
manifest 声明保持同步,避免双源不一致。
"""
from __future__ import annotations
import os
import pytest
from yuxi.channels.application.lifecycle.manifest_loader import load_manifest_from_dir
from yuxi.channels.plugins.qqbot import _constants
pytestmark = pytest.mark.unit
@pytest.fixture
def qqbot_manifest():
"""加载 qqbot 插件真实 manifest.json。"""
plugin_dir = os.path.join(
os.path.dirname(__file__),
"..",
"..",
"..",
"..",
"..",
"package",
"yuxi",
"channels",
"plugins",
"qqbot",
)
return load_manifest_from_dir(os.path.abspath(plugin_dir))
# ------------------------------------------------------------------
# 模块导出完整性
# ------------------------------------------------------------------
@pytest.mark.unit
class TestConstantsExports:
"""验证 _constants.py 模块导出完整。"""
def test_all_contains_channel_target(self):
assert "CHANNEL_TARGET" in _constants.__all__
def test_all_contains_qqbot_api_dep(self):
assert "QQBOT_API_DEP" in _constants.__all__
def test_all_contains_http_constants(self):
assert "HTTP_TIMEOUT_SECONDS" in _constants.__all__
assert "HTTP_MAX_CONNECTIONS" in _constants.__all__
assert "HTTP_MAX_KEEPALIVE_CONNECTIONS" in _constants.__all__
def test_all_contains_ws_constants(self):
assert "WS_HEARTBEAT_INTERVAL_MS" in _constants.__all__
assert "WS_STALL_TIMEOUT_MS" in _constants.__all__
def test_all_contains_cache_constants(self):
assert "USER_CACHE_TTL" in _constants.__all__
assert "IDENTITY_CACHE_TTL" in _constants.__all__
assert "TOKEN_DEFAULT_TTL" in _constants.__all__
def test_all_matches_defined_names(self):
"""``__all__`` 应恰好包含模块中定义的全部公开常量。"""
expected = {
"CHANNEL_TARGET",
"QQBOT_API_DEP",
"API_BASE_URL",
"TOKEN_ENDPOINT_PATH",
"GATEWAY_ENDPOINT_PATH",
"USERS_ENDPOINT_PREFIX",
"C2C_MESSAGES_PREFIX",
"GROUP_MESSAGES_PREFIX",
"MESSAGES_SUFFIX",
"FILES_SUFFIX",
"INTERACTIONS_PREFIX",
"HTTP_TIMEOUT_SECONDS",
"HTTP_MAX_CONNECTIONS",
"HTTP_MAX_KEEPALIVE_CONNECTIONS",
"WS_HEARTBEAT_INTERVAL_MS",
"WS_STALL_TIMEOUT_MS",
"CONNECT_TIMEOUT_MS",
"TOKEN_REFRESH_LEAD_TIME_SEC",
"TOKEN_DEFAULT_TTL",
"USER_CACHE_TTL",
"IDENTITY_CACHE_TTL",
"MSG_CACHE_TTL_SECONDS",
"DEFAULT_INTENTS",
"PASSIVE_REPLY_LIMIT_C2C",
"PASSIVE_REPLY_LIMIT_GROUP",
"PASSIVE_REPLY_TTL_C2C_MS",
"PASSIVE_REPLY_TTL_GROUP_MS",
"GROUP_ACTIVE_QPM_ACCOUNT_VERIFIED",
"GROUP_ACTIVE_QPM_ACCOUNT_UNVERIFIED",
"GROUP_ACTIVE_QPM_PER_GROUP",
"RECALL_TIME_LIMIT_SECONDS",
"INTERACTION_ACK_TIMEOUT_SECONDS",
"MAX_MESSAGE_LENGTH",
}
assert set(_constants.__all__) == expected
# ------------------------------------------------------------------
# 常量值与 manifest 对齐F-03 单真相源)
# ------------------------------------------------------------------
@pytest.mark.unit
class TestConstantsAlignWithManifest:
"""验证 _constants.py 常量值与 manifest.json 声明对齐。"""
def test_channel_target_matches_manifest_channel_type(self, qqbot_manifest):
assert _constants.CHANNEL_TARGET == qqbot_manifest.channel_type
def test_channel_target_matches_manifest_provides(self, qqbot_manifest):
"""CHANNEL_TARGET 也应出现在 manifest provides 中。"""
assert _constants.CHANNEL_TARGET in qqbot_manifest.provides
def test_http_timeout_aligns_with_http_timeout_ms(self, qqbot_manifest):
"""HTTP_TIMEOUT_SECONDS与 manifest http_timeout_ms.default毫秒对齐。"""
field = next(f for f in qqbot_manifest.config_schema if f.key == "http_timeout_ms")
assert _constants.HTTP_TIMEOUT_SECONDS == field.default / 1000.0
def test_http_max_connections_aligns_with_resource_quota(self, qqbot_manifest):
"""HTTP_MAX_CONNECTIONS 与 manifest resource_quota.max_connections 对齐。"""
assert qqbot_manifest.resource_quota is not None
assert _constants.HTTP_MAX_CONNECTIONS == qqbot_manifest.resource_quota.max_connections
def test_ws_heartbeat_aligns_with_ws_heartbeat_interval_ms(self, qqbot_manifest):
"""WS_HEARTBEAT_INTERVAL_MS 与 manifest ws_heartbeat_interval_ms.default 对齐。"""
field = next(f for f in qqbot_manifest.config_schema if f.key == "ws_heartbeat_interval_ms")
assert _constants.WS_HEARTBEAT_INTERVAL_MS == field.default
def test_ws_stall_timeout_aligns_with_ws_stall_timeout_ms(self, qqbot_manifest):
"""WS_STALL_TIMEOUT_MS 与 manifest ws_stall_timeout_ms.default 对齐。"""
field = next(f for f in qqbot_manifest.config_schema if f.key == "ws_stall_timeout_ms")
assert _constants.WS_STALL_TIMEOUT_MS == field.default
def test_default_intents_aligns_with_intents_default(self, qqbot_manifest):
"""DEFAULT_INTENTS 与 manifest intents.default 对齐。"""
field = next(f for f in qqbot_manifest.config_schema if f.key == "intents")
assert _constants.DEFAULT_INTENTS == field.default
def test_max_message_length_aligns_with_manifest(self, qqbot_manifest):
"""MAX_MESSAGE_LENGTH 与 manifest max_message_length 对齐。"""
assert _constants.MAX_MESSAGE_LENGTH == qqbot_manifest.max_message_length
def test_user_cache_ttl_aligns_with_user_cache_ttl(self, qqbot_manifest):
"""USER_CACHE_TTL 与 manifest user_cache_ttl.default 对齐。"""
field = next(f for f in qqbot_manifest.config_schema if f.key == "user_cache_ttl")
assert _constants.USER_CACHE_TTL == field.default
def test_token_default_ttl_aligns_with_token_cache_ttl(self, qqbot_manifest):
"""TOKEN_DEFAULT_TTL 与 manifest token_cache_ttl.default 对齐。"""
field = next(f for f in qqbot_manifest.config_schema if f.key == "token_cache_ttl")
assert _constants.TOKEN_DEFAULT_TTL == field.default
# ------------------------------------------------------------------
# 常量值合理性
# ------------------------------------------------------------------
@pytest.mark.unit
class TestConstantsSanity:
"""验证 _constants.py 常量值的基本合理性。"""
def test_channel_target_is_qqbot(self):
assert _constants.CHANNEL_TARGET == "qqbot"
def test_qqbot_api_dep_is_string(self):
assert isinstance(_constants.QQBOT_API_DEP, str)
assert _constants.QQBOT_API_DEP == "qqbot_api"
def test_api_base_url_starts_with_https(self):
assert _constants.API_BASE_URL.startswith("https://")
def test_http_timeout_positive(self):
assert _constants.HTTP_TIMEOUT_SECONDS > 0
def test_http_max_connections_positive(self):
assert _constants.HTTP_MAX_CONNECTIONS > 0
def test_http_keepalive_le_max_connections(self):
"""keepalive 连接数上限不应超过最大连接数。"""
assert _constants.HTTP_MAX_KEEPALIVE_CONNECTIONS <= _constants.HTTP_MAX_CONNECTIONS
def test_ws_heartbeat_positive(self):
assert _constants.WS_HEARTBEAT_INTERVAL_MS > 0
def test_ws_stall_timeout_gt_heartbeat(self):
"""卡死检测超时应大于心跳间隔。"""
assert _constants.WS_STALL_TIMEOUT_MS > _constants.WS_HEARTBEAT_INTERVAL_MS
def test_default_intents_positive(self):
assert _constants.DEFAULT_INTENTS > 0
def test_token_default_ttl_positive(self):
assert _constants.TOKEN_DEFAULT_TTL > 0
def test_token_refresh_lead_time_positive(self):
assert _constants.TOKEN_REFRESH_LEAD_TIME_SEC > 0
def test_token_refresh_lead_time_lt_default_ttl(self):
"""刷新提前量应小于默认 TTL。"""
assert _constants.TOKEN_REFRESH_LEAD_TIME_SEC < _constants.TOKEN_DEFAULT_TTL
def test_user_cache_ttl_positive(self):
assert _constants.USER_CACHE_TTL > 0
def test_identity_cache_ttl_positive(self):
assert _constants.IDENTITY_CACHE_TTL > 0
def test_identity_cache_ttl_le_user_cache_ttl(self):
"""身份解析缓存 TTL 应小于等于用户缓存 TTL。"""
assert _constants.IDENTITY_CACHE_TTL <= _constants.USER_CACHE_TTL
def test_max_message_length_positive(self):
assert _constants.MAX_MESSAGE_LENGTH > 0
def test_msg_cache_ttl_positive(self):
assert _constants.MSG_CACHE_TTL_SECONDS > 0
def test_recall_time_limit_positive(self):
assert _constants.RECALL_TIME_LIMIT_SECONDS > 0
def test_interaction_ack_timeout_positive(self):
assert _constants.INTERACTION_ACK_TIMEOUT_SECONDS > 0
def test_passive_reply_limits_positive(self):
assert _constants.PASSIVE_REPLY_LIMIT_C2C > 0
assert _constants.PASSIVE_REPLY_LIMIT_GROUP > 0
def test_passive_reply_ttls_positive(self):
assert _constants.PASSIVE_REPLY_TTL_C2C_MS > 0
assert _constants.PASSIVE_REPLY_TTL_GROUP_MS > 0
def test_group_active_qpm_positive(self):
assert _constants.GROUP_ACTIVE_QPM_ACCOUNT_VERIFIED > 0
assert _constants.GROUP_ACTIVE_QPM_ACCOUNT_UNVERIFIED > 0
assert _constants.GROUP_ACTIVE_QPM_PER_GROUP > 0
def test_group_active_qpm_verified_gt_unverified(self):
"""认证账号 QPM 应大于等于未认证账号 QPM。"""
assert _constants.GROUP_ACTIVE_QPM_ACCOUNT_VERIFIED >= _constants.GROUP_ACTIVE_QPM_ACCOUNT_UNVERIFIED