新增了Twitch、Telegram、Discord、Slack、Mattermost、WeChat、Zalo等多渠道的单元测试用例,覆盖了令牌处理、速率限制、消息去重、会话解析、格式转换、安全策略等模块 同时在测试配置中添加了测试用的OpenAI API密钥环境变量
276 lines
9.8 KiB
Python
276 lines
9.8 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
|
|
import pytest
|
|
|
|
from yuxi.channels.meta import ChannelMeta
|
|
from yuxi.channels.models import ChannelAccountSnapshot, TokenStatus
|
|
|
|
|
|
class TestChannelMetaNewFields:
|
|
def test_all_fields_have_defaults(self):
|
|
meta = ChannelMeta(id="test", label="Test")
|
|
assert meta.selection_docs_prefix == ""
|
|
assert meta.selection_docs_omit_label is False
|
|
assert meta.detail_label == ""
|
|
assert meta.prefer_session_lookup_for_announce_target is False
|
|
assert meta.prefer_over is False
|
|
|
|
def test_full_meta_with_new_fields(self):
|
|
meta = ChannelMeta(
|
|
id="discord",
|
|
label="Discord",
|
|
selection_label="Discord Bot",
|
|
blurb="Discord 渠道适配器",
|
|
order=3,
|
|
docs_path="docs/channels/discord",
|
|
docs_label="Discord 文档",
|
|
selection_docs_prefix="/channels",
|
|
selection_docs_omit_label=False,
|
|
system_image="discord",
|
|
markdown_capable=True,
|
|
detail_label="Discord 详情",
|
|
aliases=["dc"],
|
|
exposure="public",
|
|
show_configured=True,
|
|
show_in_setup=True,
|
|
quickstart_allow_from=["web", "mobile"],
|
|
force_account_binding=False,
|
|
prefer_session_lookup_for_announce_target=True,
|
|
prefer_over=True,
|
|
)
|
|
assert meta.id == "discord"
|
|
assert meta.selection_docs_prefix == "/channels"
|
|
assert meta.selection_docs_omit_label is False
|
|
assert meta.detail_label == "Discord 详情"
|
|
assert meta.prefer_session_lookup_for_announce_target is True
|
|
assert meta.prefer_over is True
|
|
|
|
def test_model_dump_includes_new_fields(self):
|
|
meta = ChannelMeta(
|
|
id="telegram",
|
|
label="Telegram",
|
|
detail_label="TG",
|
|
prefer_over=True,
|
|
)
|
|
dumped = meta.model_dump()
|
|
assert "detail_label" in dumped
|
|
assert "prefer_over" in dumped
|
|
assert "selection_docs_prefix" in dumped
|
|
assert "selection_docs_omit_label" in dumped
|
|
assert "prefer_session_lookup_for_announce_target" in dumped
|
|
|
|
def test_model_dump_serializable(self):
|
|
meta = ChannelMeta(
|
|
id="test",
|
|
label="Test",
|
|
prefer_over=True,
|
|
detail_label="Detail",
|
|
)
|
|
json.dumps(meta.model_dump())
|
|
|
|
def test_wechat_meta_backward_compat(self):
|
|
from yuxi.channels.adapters.wechat.capabilities import WECHAT_META, get_meta
|
|
|
|
meta = get_meta()
|
|
assert isinstance(meta, ChannelMeta)
|
|
assert meta.id == "wechat"
|
|
assert meta.label == "微信"
|
|
assert meta.order == 8
|
|
assert meta.aliases == ["weixin", "WeChat"]
|
|
assert meta.markdown_capable is False
|
|
assert meta.exposure == "configured"
|
|
|
|
dumped = WECHAT_META.model_dump()
|
|
assert dumped["id"] == "wechat"
|
|
assert dumped["label"] == "微信"
|
|
|
|
def test_roundtrip_serialization(self):
|
|
original = ChannelMeta(
|
|
id="line",
|
|
label="LINE",
|
|
selection_label="LINE Bot",
|
|
docs_path="docs/channels/line",
|
|
blurb="LINE 渠道适配器",
|
|
order=10,
|
|
aliases=["line_me"],
|
|
system_image="line",
|
|
markdown_capable=False,
|
|
exposure="public",
|
|
detail_label="LINE",
|
|
prefer_over=False,
|
|
prefer_session_lookup_for_announce_target=False,
|
|
)
|
|
data = original.model_dump()
|
|
restored = ChannelMeta(**data)
|
|
assert restored == original
|
|
|
|
|
|
class TestTokenStatus:
|
|
def test_default_values(self):
|
|
ts = TokenStatus()
|
|
assert ts.source == ""
|
|
assert ts.status == "unknown"
|
|
assert ts.last_verified_at == 0.0
|
|
|
|
def test_custom_values(self):
|
|
ts = TokenStatus(source="env", status="valid", last_verified_at=1715000000.0)
|
|
assert ts.source == "env"
|
|
assert ts.status == "valid"
|
|
assert ts.last_verified_at == 1715000000.0
|
|
|
|
def test_model_dump_serializable(self):
|
|
ts = TokenStatus(source="vault", status="expired", last_verified_at=1715000000.0)
|
|
json.dumps(ts.model_dump())
|
|
|
|
|
|
class TestChannelAccountSnapshotNewFields:
|
|
def test_default_token_fields(self):
|
|
snap = ChannelAccountSnapshot()
|
|
assert snap.token_source == ""
|
|
assert snap.token_status is None
|
|
assert snap.bot_token_source == ""
|
|
assert snap.bot_token_status is None
|
|
assert snap.app_token_source == ""
|
|
assert snap.app_token_status is None
|
|
assert snap.signing_secret_source == ""
|
|
assert snap.signing_secret_status is None
|
|
assert snap.user_token_status is None
|
|
assert snap.credential_source == ""
|
|
assert snap.secret_source == ""
|
|
|
|
def test_default_network_fields(self):
|
|
snap = ChannelAccountSnapshot()
|
|
assert snap.webhook_path == ""
|
|
assert snap.webhook_url == ""
|
|
assert snap.base_url == ""
|
|
assert snap.cli_path == ""
|
|
assert snap.db_path == ""
|
|
assert snap.port == 0
|
|
|
|
def test_default_probe_fields(self):
|
|
snap = ChannelAccountSnapshot()
|
|
assert snap.probe is None
|
|
assert snap.last_probe_at == 0.0
|
|
assert snap.audit is None
|
|
|
|
def test_default_bot_fields(self):
|
|
snap = ChannelAccountSnapshot()
|
|
assert snap.application is None
|
|
assert snap.bot is None
|
|
assert snap.public_key == ""
|
|
assert snap.profile is None
|
|
|
|
def test_token_status_with_data(self):
|
|
snap = ChannelAccountSnapshot(
|
|
account_id="acc_1",
|
|
token_source="env",
|
|
token_status=TokenStatus(source="env", status="valid"),
|
|
bot_token_source="config",
|
|
bot_token_status=TokenStatus(source="config", status="expired"),
|
|
)
|
|
assert snap.token_source == "env"
|
|
assert snap.token_status.source == "env"
|
|
assert snap.token_status.status == "valid"
|
|
assert snap.bot_token_status.status == "expired"
|
|
|
|
def test_network_fields_with_data(self):
|
|
snap = ChannelAccountSnapshot(
|
|
account_id="acc_1",
|
|
webhook_path="/webhook/whatsapp",
|
|
webhook_url="https://example.com/webhook",
|
|
base_url="https://api.example.com",
|
|
port=8443,
|
|
)
|
|
assert snap.webhook_path == "/webhook/whatsapp"
|
|
assert snap.webhook_url == "https://example.com/webhook"
|
|
assert snap.base_url == "https://api.example.com"
|
|
assert snap.port == 8443
|
|
|
|
def test_probe_fields_with_data(self):
|
|
snap = ChannelAccountSnapshot(
|
|
account_id="acc_1",
|
|
probe={"endpoint": "/health", "timeout": 5},
|
|
last_probe_at=1715000000.0,
|
|
audit={"last_audit": "2024-01-01"},
|
|
)
|
|
assert snap.probe == {"endpoint": "/health", "timeout": 5}
|
|
assert snap.last_probe_at == 1715000000.0
|
|
assert snap.audit == {"last_audit": "2024-01-01"}
|
|
|
|
def test_bot_fields_with_data(self):
|
|
snap = ChannelAccountSnapshot(
|
|
account_id="acc_1",
|
|
application={"name": "MyBot", "version": "1.0"},
|
|
bot={"username": "my_bot", "id": "123"},
|
|
public_key="ssh-rsa AAA...",
|
|
profile={"display_name": "My Bot"},
|
|
)
|
|
assert snap.application == {"name": "MyBot", "version": "1.0"}
|
|
assert snap.bot == {"username": "my_bot", "id": "123"}
|
|
assert snap.public_key == "ssh-rsa AAA..."
|
|
assert snap.profile == {"display_name": "My Bot"}
|
|
|
|
def test_snapshot_dump_includes_new_fields(self):
|
|
snap = ChannelAccountSnapshot(
|
|
account_id="acc_1",
|
|
token_source="env",
|
|
webhook_path="/wh",
|
|
probe={"key": "val"},
|
|
public_key="abc",
|
|
)
|
|
dumped = snap.model_dump()
|
|
assert "token_source" in dumped
|
|
assert "webhook_path" in dumped
|
|
assert "probe" in dumped
|
|
assert "public_key" in dumped
|
|
|
|
def test_snapshot_backward_compat_core_fields(self):
|
|
snap = ChannelAccountSnapshot(account_id="acc_1", name="test", configured=True)
|
|
assert snap.account_id == "acc_1"
|
|
assert snap.name == "test"
|
|
assert snap.configured is True
|
|
assert snap.enabled is True
|
|
assert snap.linked is False
|
|
|
|
def test_snapshot_extra_fields_still_allowed(self):
|
|
snap = ChannelAccountSnapshot(account_id="acc_1", custom_field="custom_value")
|
|
assert snap.custom_field == "custom_value"
|
|
|
|
def test_snapshot_json_serializable(self):
|
|
snap = ChannelAccountSnapshot(
|
|
account_id="acc_1",
|
|
token_status=TokenStatus(source="env", status="valid"),
|
|
probe={"key": "val"},
|
|
bot={"id": "123"},
|
|
)
|
|
json.dumps(snap.model_dump())
|
|
|
|
|
|
class TestWhatsAppChannelMetaMigration:
|
|
def test_whatsapp_channel_meta_import_works(self):
|
|
from yuxi.channels.adapters.whatsapp.channel_meta import (
|
|
ChannelCapability,
|
|
ChannelMeta,
|
|
ChannelMode,
|
|
WhatsAppChannelMeta,
|
|
)
|
|
assert ChannelMeta is WhatsAppChannelMeta
|
|
|
|
def test_whatsapp_channel_meta_has_capability(self):
|
|
from yuxi.channels.adapters.whatsapp.channel_meta import (
|
|
ChannelCapability,
|
|
WhatsAppChannelMeta,
|
|
)
|
|
meta = WhatsAppChannelMeta()
|
|
assert meta.has_capability(ChannelCapability.TEXT) is True
|
|
assert meta.has_capability(ChannelCapability.REACTION) is False
|
|
|
|
def test_whatsapp_adapter_has_display_meta(self):
|
|
from yuxi.channels.adapters.whatsapp.adapter import WhatsAppAdapter
|
|
from yuxi.channels.meta import ChannelMeta as DisplayMeta
|
|
|
|
assert isinstance(WhatsAppAdapter.meta, DisplayMeta)
|
|
assert WhatsAppAdapter.meta.id == "whatsapp"
|
|
assert WhatsAppAdapter.meta.label == "WhatsApp" |