ForcePilot/backend/test/unit/channels/test_channels_imessage_utils.py
Kris 3264900bc9 test: 新增多渠道单元测试用例并配置测试环境变量
新增了Twitch、Telegram、Discord、Slack、Mattermost、WeChat、Zalo等多渠道的单元测试用例,覆盖了令牌处理、速率限制、消息去重、会话解析、格式转换、安全策略等模块
同时在测试配置中添加了测试用的OpenAI API密钥环境变量
2026-05-12 00:56:47 +08:00

224 lines
7.8 KiB
Python

from __future__ import annotations
import asyncio
import time
import pytest
from yuxi.channels.adapters.imessage.echo_cache import (
EchoCache,
TEXT_TTL_S,
MESSAGE_ID_TTL_S,
SELF_CHAT_TTL_S,
MAX_SELF_CHAT_ENTRIES,
)
from yuxi.channels.adapters.imessage.rate_limiter import LoopRateLimiter
from yuxi.channels.adapters.imessage.reflection_guard import ReflectionGuard
class TestEchoCache:
@pytest.fixture
def cache(self):
return EchoCache()
def test_initial_empty(self, cache):
assert cache.is_echo(message_id="any") is False
assert cache.is_echo(text="any") is False
def test_record_and_detect_by_message_id(self, cache):
cache.record_sent("msg_001", "Hello", "chat_001")
assert cache.is_echo(message_id="msg_001") is True
def test_record_and_detect_by_text(self, cache):
cache.record_sent("msg_001", "Hello world", "chat_001")
assert cache.is_echo(text="Hello world", chat_guid="chat_001") is True
def test_no_false_positive_text(self, cache):
cache.record_sent("msg_001", "Hello", "chat_001")
assert cache.is_echo(text="Different", chat_guid="chat_001") is False
def test_no_false_positive_different_chat(self, cache):
cache.record_sent("msg_001", "Hello", "chat_001")
assert cache.is_echo(text="Hello", chat_guid="chat_002") is False
def test_record_and_detect_by_text_backed_by_id(self, cache):
cache.record_sent("msg_001", "Hello", "chat_001")
cache._by_message_id.clear()
assert cache.is_echo(message_id="msg_001") is True
def test_text_ttl_expiry(self, cache):
cache._text_ttl = 0.01
cache.record_sent("msg_001", "Hello", "chat_001")
time.sleep(0.02)
assert cache.is_echo(text="Hello", chat_guid="chat_001") is False
def test_message_id_ttl_expiry(self, cache):
cache._text_ttl = 0.01
cache._text_backed_by_id_ttl = 0.01
cache._msg_id_ttl = 0.01
cache.record_sent("msg_001", "Hello", "chat_001")
time.sleep(0.02)
assert cache.is_echo(message_id="msg_001") is False
def test_is_self_chat_echo_same_handle(self, cache):
result = cache.is_self_chat_echo(
"+8613800138000", "+8613800138000", "chat_001"
)
assert result is True
def test_is_self_chat_echo_different_handle(self, cache):
result = cache.is_self_chat_echo(
"+8613800138000", "+8613900139000", "chat_001"
)
assert result is False
def test_is_self_chat_echo_no_self_handle(self, cache):
result = cache.is_self_chat_echo(
"+8613800138000", "", "chat_001"
)
assert result is False
def test_is_self_chat_echo_clean_handles(self, cache):
result = cache.is_self_chat_echo(
" +8613800138000 ", " +8613800138000 ", "chat_001"
)
assert result is True
def test_record_self_chat_text(self, cache):
cache.record_self_chat_text("Hello from self", "chat_001")
assert cache.is_in_self_chat_cache("Hello from self", "chat_001") is True
def test_record_self_chat_text_different_text(self, cache):
cache.record_self_chat_text("Hello", "chat_001")
assert cache.is_in_self_chat_cache("Different", "chat_001") is False
def test_self_chat_ttl_expiry(self, cache):
cache._self_chat_ttl = 0.01
cache.record_self_chat_text("Hello", "chat_001")
time.sleep(0.02)
assert cache.is_in_self_chat_cache("Hello", "chat_001") is False
def test_max_self_chat_entries(self, cache):
cache._max_self_chat_entries = 5
for i in range(10):
cache.record_self_chat_text(f"msg_{i}", "chat_001")
assert len(cache._self_chat_cache) <= 5
def test_cleanup_removes_expired_text_backed_by_id(self, cache):
cache._text_backed_by_id_ttl = 0.01
cache.record_sent("msg_001", "Hello", "chat_001")
time.sleep(0.02)
cache._cleanup()
assert "msg_001" not in cache._by_text_backed_by_id
def test_default_ttls(self):
cache = EchoCache()
assert cache._text_ttl == TEXT_TTL_S
assert cache._msg_id_ttl == MESSAGE_ID_TTL_S
assert cache._self_chat_ttl == SELF_CHAT_TTL_S
assert cache._max_self_chat_entries == MAX_SELF_CHAT_ENTRIES
def test_multiple_messages_same_text(self, cache):
cache.record_sent("msg_001", "Hello", "chat_001")
cache.record_sent("msg_002", "Hello", "chat_001")
assert cache.is_echo(message_id="msg_001") is True
assert cache.is_echo(message_id="msg_002") is True
assert cache.is_echo(text="Hello", chat_guid="chat_001") is True
def test_is_echo_none_params(self, cache):
assert cache.is_echo(message_id=None, text=None) is False
class TestLoopRateLimiter:
@pytest.fixture
def limiter(self):
return LoopRateLimiter(max_loops=3, window_s=60.0, cooldown_s=1.0)
def test_initial_not_suppressed(self, limiter):
assert limiter.is_suppressed("conv_001") is False
def test_single_hit_not_suppressed(self, limiter):
result = limiter.check("conv_001", "content")
assert result is False
def test_multiple_hits_suppressed(self, limiter):
for _ in range(3):
limiter.check("conv_001", "content")
result = limiter.check("conv_001", "content")
assert result is True
def test_different_conversation_separate(self, limiter):
for _ in range(3):
limiter.check("conv_001", "content")
result = limiter.check("conv_002", "content")
assert result is False
def test_is_suppressed_after_hits(self, limiter):
for _ in range(3):
limiter.check("conv_001", "content")
assert limiter.is_suppressed("conv_001") is True
def test_cooldown_expires(self, limiter):
limiter._cooldown_s = 0.01
for _ in range(3):
limiter.check("conv_001", "content")
assert limiter.is_suppressed("conv_001") is True
time.sleep(0.02)
assert limiter.is_suppressed("conv_001") is False
def test_reset(self, limiter):
for _ in range(2):
limiter.check("conv_001", "content")
limiter.reset("conv_001")
result = limiter.check("conv_001", "content")
assert result is False
def test_reset_unknown_key(self, limiter):
limiter.reset("nonexistent")
def test_window_sliding(self, limiter):
limiter._window_s = 0.01
for _ in range(2):
limiter.check("conv_001", "content")
time.sleep(0.02)
result = limiter.check("conv_001", "content")
assert result is False
def test_default_params(self):
limiter = LoopRateLimiter()
assert limiter._max_loops == 5
assert limiter._window_s == 30.0
assert limiter._cooldown_s == 60.0
def test_check_within_window(self, limiter):
for _ in range(2):
limiter.check("conv_001", "content")
result = limiter.check("conv_001", "content")
assert result is True
class TestReflectionGuard:
@pytest.fixture
def guard(self):
return ReflectionGuard()
def test_initial_state(self, guard):
assert guard.blocked_count == 0
def test_is_not_reflection_normal_text(self, guard):
assert guard.is_reflection("Hello world") is False
def test_is_not_reflection_short_text(self, guard):
assert guard.is_reflection("Hi") is False
def test_mark_blocked(self, guard):
guard.mark_blocked()
assert guard.blocked_count == 1
guard.mark_blocked()
assert guard.blocked_count == 2
def test_blocked_count_starts_at_zero(self, guard):
assert guard.blocked_count == 0
guard.mark_blocked()
guard.mark_blocked()
assert guard.blocked_count == 2