1. 移除Telegram格式化测试中未使用的导入项 2. 修复Teams测试用例,添加monkeypatch参数并配置通配符开关 3. 更新钉钉适配器测试,替换弃用的流属性检查 4. 修正Twitch规范化测试,更新ROOMSTATE测试逻辑 5. 重构会话映射测试,完善数据库执行结果模拟 6. 格式化Slack块构建测试的长参数调用 7. 修复LINE适配器测试,更新能力断言和异步锁使用 8. 修正Slack会话解析测试,修复聊天类型判断错误 9. 更新能力测试,补充缺失的字段检查 10. 修复Matrix适配器测试,修正位置参数和配置校验逻辑 11. 为飞书分析模块测试添加跳过标记 12. 新增微信能力、限流、链接格式、会话路由等模块的单元测试 13. 修复Twitch适配器导入路径和测试断言 14. 新增Discord Webhook、Nextcloud Talk、Signal多账户等模块的单元测试 15. 修复Manager阶段测试的导入路径 16. 新增iMessage异常和命令处理的单元测试 17. 新增Nostr健康检查和相关模块的单元测试 18. 新增Signal守护进程和SSE重连相关测试
364 lines
14 KiB
Python
364 lines
14 KiB
Python
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import time
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
from yuxi.channels.adapters.whatsapp.error_policy import ErrorPolicy, ErrorPolicyConfig
|
|
from yuxi.channels.adapters.whatsapp.group_gating import GroupGating
|
|
from yuxi.channels.adapters.whatsapp.pairing import PairRequest, PairingManager
|
|
from yuxi.channels.adapters.whatsapp.security import (
|
|
DmPolicy,
|
|
GroupPolicy,
|
|
WhatsAppSecurityConfig,
|
|
WhatsAppSecurityPolicy,
|
|
)
|
|
|
|
|
|
class TestErrorPolicy:
|
|
def test_enum_values(self):
|
|
assert ErrorPolicy.OFF == "off"
|
|
assert ErrorPolicy.FULL == "full"
|
|
assert ErrorPolicy.MINIMAL == "minimal"
|
|
|
|
|
|
class TestErrorPolicyConfig:
|
|
def test_from_config_default(self):
|
|
cfg = ErrorPolicyConfig.from_config({})
|
|
assert cfg.policy == ErrorPolicy.OFF
|
|
assert cfg.cooldown_ms == 5000
|
|
assert cfg.max_errors_per_window == 5
|
|
assert cfg.window_seconds == 60.0
|
|
|
|
def test_from_config_explicit(self):
|
|
cfg = ErrorPolicyConfig.from_config({
|
|
"errorPolicy": "full",
|
|
"errorCooldownMs": 3000,
|
|
"errorMaxPerWindow": 10,
|
|
"errorWindowSeconds": 120.0,
|
|
})
|
|
assert cfg.policy == ErrorPolicy.FULL
|
|
assert cfg.cooldown_ms == 3000
|
|
assert cfg.max_errors_per_window == 10
|
|
assert cfg.window_seconds == 120.0
|
|
|
|
def test_from_config_invalid_fallback(self):
|
|
cfg = ErrorPolicyConfig.from_config({"errorPolicy": "invalid"})
|
|
assert cfg.policy == ErrorPolicy.OFF
|
|
|
|
def test_should_notify_off_policy(self):
|
|
cfg = ErrorPolicyConfig(policy=ErrorPolicy.OFF)
|
|
assert cfg.should_notify("chat1", "test error") is False
|
|
|
|
def test_should_notify_full_policy(self):
|
|
cfg = ErrorPolicyConfig(policy=ErrorPolicy.FULL, window_seconds=60.0)
|
|
assert cfg.should_notify("chat1", "test error") is True
|
|
|
|
def test_should_notify_rate_limited(self):
|
|
cfg = ErrorPolicyConfig(policy=ErrorPolicy.FULL, max_errors_per_window=2, window_seconds=60.0)
|
|
cfg.should_notify("chat1", "error1")
|
|
cfg.should_notify("chat1", "error2")
|
|
assert cfg.should_notify("chat1", "error3") is False
|
|
|
|
def test_format_error_message_full(self):
|
|
cfg = ErrorPolicyConfig(policy=ErrorPolicy.FULL)
|
|
assert "Error:" in cfg.format_error_message("something broke")
|
|
|
|
def test_format_error_message_minimal(self):
|
|
cfg = ErrorPolicyConfig(policy=ErrorPolicy.MINIMAL)
|
|
msg = cfg.format_error_message("something broke")
|
|
assert "Error:" not in msg
|
|
assert "try again later" in msg
|
|
|
|
|
|
class TestDmPolicy:
|
|
def test_enum_values(self):
|
|
assert DmPolicy.PAIRING == "pairing"
|
|
assert DmPolicy.ALLOWLIST == "allowlist"
|
|
assert DmPolicy.OPEN == "open"
|
|
assert DmPolicy.DISABLED == "disabled"
|
|
|
|
|
|
class TestGroupPolicy:
|
|
def test_enum_values(self):
|
|
assert GroupPolicy.OPEN == "open"
|
|
assert GroupPolicy.ALLOWLIST == "allowlist"
|
|
assert GroupPolicy.DISABLED == "disabled"
|
|
|
|
|
|
class TestWhatsAppSecurityConfig:
|
|
def test_default_values(self):
|
|
cfg = WhatsAppSecurityConfig()
|
|
assert cfg.dm_policy == DmPolicy.PAIRING
|
|
assert cfg.group_policy == GroupPolicy.ALLOWLIST
|
|
assert cfg.allow_from == []
|
|
assert cfg.group_allow_from == []
|
|
|
|
|
|
class TestWhatsAppSecurityPolicy:
|
|
def test_default_resolution(self):
|
|
policy = WhatsAppSecurityPolicy({})
|
|
assert policy.dm_policy == DmPolicy.OPEN
|
|
|
|
def test_dm_policy_pairing(self):
|
|
policy = WhatsAppSecurityPolicy({"dmPolicy": "pairing"})
|
|
assert policy.dm_policy == DmPolicy.PAIRING
|
|
|
|
def test_dm_policy_allowlist(self):
|
|
policy = WhatsAppSecurityPolicy({"dmPolicy": "allowlist", "allowFrom": ["+86138"]})
|
|
assert policy.dm_policy == DmPolicy.ALLOWLIST
|
|
|
|
def test_dm_policy_disabled(self):
|
|
policy = WhatsAppSecurityPolicy({"dmPolicy": "disabled"})
|
|
assert policy.dm_policy == DmPolicy.DISABLED
|
|
|
|
def test_group_policy_open(self):
|
|
policy = WhatsAppSecurityPolicy({"groupPolicy": "open"})
|
|
assert policy.group_policy == GroupPolicy.OPEN
|
|
|
|
def test_group_policy_allowlist_default(self):
|
|
policy = WhatsAppSecurityPolicy({})
|
|
assert policy.group_policy == GroupPolicy.ALLOWLIST
|
|
|
|
def test_group_policy_disabled(self):
|
|
policy = WhatsAppSecurityPolicy({"groupPolicy": "disabled"})
|
|
assert policy.group_policy == GroupPolicy.DISABLED
|
|
|
|
def test_check_dm_access_open(self):
|
|
policy = WhatsAppSecurityPolicy({"dmPolicy": "open"})
|
|
ok, reason = policy.check_dm_access("8613800138000")
|
|
assert ok is True
|
|
assert reason is None
|
|
|
|
def test_check_dm_access_disabled(self):
|
|
policy = WhatsAppSecurityPolicy({"dmPolicy": "disabled"})
|
|
ok, reason = policy.check_dm_access("8613800138000")
|
|
assert ok is False
|
|
assert reason == "dm_disabled"
|
|
|
|
def test_check_dm_access_allowlist_match(self):
|
|
policy = WhatsAppSecurityPolicy({"dmPolicy": "allowlist", "allowFrom": ["8613800138000"]})
|
|
ok, reason = policy.check_dm_access("8613800138000")
|
|
assert ok is True
|
|
|
|
def test_check_dm_access_allowlist_no_match(self):
|
|
policy = WhatsAppSecurityPolicy({"dmPolicy": "allowlist", "allowFrom": ["8613800138000"]})
|
|
ok, reason = policy.check_dm_access("8613800000000")
|
|
assert ok is False
|
|
assert reason == "not_in_allowlist"
|
|
|
|
def test_check_dm_access_pairing(self):
|
|
policy = WhatsAppSecurityPolicy({"dmPolicy": "pairing"})
|
|
ok, reason = policy.check_dm_access("8613800138000")
|
|
assert ok is True
|
|
|
|
def test_check_group_access_open(self):
|
|
policy = WhatsAppSecurityPolicy({"groupPolicy": "open"})
|
|
ok, reason = policy.check_group_access("123456789@g.us")
|
|
assert ok is True
|
|
|
|
def test_check_group_access_disabled(self):
|
|
policy = WhatsAppSecurityPolicy({"groupPolicy": "disabled"})
|
|
ok, reason = policy.check_group_access("123456789@g.us")
|
|
assert ok is False
|
|
assert reason == "group_disabled"
|
|
|
|
def test_check_group_access_allowlist_match(self):
|
|
policy = WhatsAppSecurityPolicy(
|
|
{"groupPolicy": "allowlist", "groupAllowFrom": ["123456789@g.us"]}
|
|
)
|
|
ok, reason = policy.check_group_access("123456789@g.us")
|
|
assert ok is True
|
|
|
|
def test_check_group_access_allowlist_no_match(self):
|
|
policy = WhatsAppSecurityPolicy(
|
|
{"groupPolicy": "allowlist", "groupAllowFrom": ["123456789@g.us"]}
|
|
)
|
|
ok, reason = policy.check_group_access("999999999@g.us")
|
|
assert ok is False
|
|
|
|
def test_add_to_allow_list(self):
|
|
policy = WhatsAppSecurityPolicy({"dmPolicy": "allowlist", "allowFrom": []})
|
|
policy.add_to_allow_list("8613800138000")
|
|
assert "8613800138000" in policy.allow_list
|
|
|
|
def test_add_to_allow_list_duplicate(self):
|
|
policy = WhatsAppSecurityPolicy({"dmPolicy": "allowlist", "allowFrom": ["8613800138000"]})
|
|
policy.add_to_allow_list("8613800138000")
|
|
assert len(policy.allow_list) == 1
|
|
|
|
def test_remove_from_allow_list(self):
|
|
policy = WhatsAppSecurityPolicy({"dmPolicy": "allowlist", "allowFrom": ["8613800138000"]})
|
|
assert policy.remove_from_allow_list("8613800138000") is True
|
|
assert "8613800138000" not in policy.allow_list
|
|
|
|
def test_remove_from_allow_list_nonexistent(self):
|
|
policy = WhatsAppSecurityPolicy({"dmPolicy": "allowlist", "allowFrom": []})
|
|
assert policy.remove_from_allow_list("nonexistent") is False
|
|
|
|
def test_wildcard_allow_list(self):
|
|
policy = WhatsAppSecurityPolicy({"dmPolicy": "allowlist", "allowFrom": ["*"]})
|
|
ok, _ = policy.check_dm_access("any_number")
|
|
assert ok is True
|
|
|
|
def test_normalize_allow_list_strips_plus(self):
|
|
result = WhatsAppSecurityPolicy._normalize_allow_list(["+8613800138000"])
|
|
assert "8613800138000" in result
|
|
|
|
def test_match_allow_list_wildcard(self):
|
|
assert WhatsAppSecurityPolicy._match_allow_list("anything", ["*"]) is True
|
|
|
|
def test_match_allow_list_exact(self):
|
|
assert WhatsAppSecurityPolicy._match_allow_list("8613800138000", ["8613800138000"]) is True
|
|
|
|
def test_collect_warnings_group_open_no_list(self):
|
|
policy = WhatsAppSecurityPolicy({"groupPolicy": "open"})
|
|
warnings = policy.collect_warnings()
|
|
assert len(warnings) > 0
|
|
|
|
def test_apply_config_fixes(self):
|
|
policy = WhatsAppSecurityPolicy({"groupPolicy": "open"})
|
|
fixes = policy.apply_config_fixes()
|
|
assert len(fixes) > 0
|
|
|
|
def test_allow_list_property(self):
|
|
policy = WhatsAppSecurityPolicy({"allowFrom": ["8613800138000", "8613800138001"]})
|
|
assert len(policy.allow_list) == 2
|
|
|
|
def test_group_allow_list_property(self):
|
|
policy = WhatsAppSecurityPolicy({"groupAllowFrom": ["123456789@g.us"]})
|
|
assert len(policy.group_allow_list) == 1
|
|
|
|
|
|
class TestPairRequest:
|
|
def test_create_pair_request(self):
|
|
req = PairRequest(phone_number="8613800138000", pairing_code="ABC123", expires_at=9999999999.0)
|
|
assert req.phone_number == "8613800138000"
|
|
assert req.pairing_code == "ABC123"
|
|
assert req.expires_at == 9999999999.0
|
|
|
|
def test_not_expired_future(self):
|
|
req = PairRequest(phone_number="86138", pairing_code="ABC", expires_at=9999999999.0)
|
|
assert req.is_expired is False
|
|
|
|
def test_not_expired_no_expiry(self):
|
|
req = PairRequest(phone_number="86138")
|
|
assert req.is_expired is False
|
|
|
|
def test_expired(self):
|
|
req = PairRequest(phone_number="86138", pairing_code="ABC", expires_at=0)
|
|
assert req.is_expired is True
|
|
|
|
|
|
class TestPairingManager:
|
|
def test_create_pair_request(self):
|
|
mgr = PairingManager()
|
|
req = mgr.create_pair_request("8613800138000", "ABC123", timeout=300)
|
|
assert req.phone_number == "8613800138000"
|
|
assert req.pairing_code == "ABC123"
|
|
|
|
def test_confirm_pair_success(self):
|
|
mgr = PairingManager()
|
|
mgr.create_pair_request("8613800138000", "ABC123")
|
|
assert mgr.confirm_pair("8613800138000") is True
|
|
|
|
def test_confirm_pair_nonexistent(self):
|
|
mgr = PairingManager()
|
|
assert mgr.confirm_pair("nonexistent") is False
|
|
|
|
def test_confirm_pair_expired(self):
|
|
mgr = PairingManager()
|
|
mgr.create_pair_request("8613800138000", "ABC123", timeout=0)
|
|
time.sleep(0.01)
|
|
assert mgr.confirm_pair("8613800138000") is False
|
|
|
|
def test_is_paired_after_confirm(self):
|
|
mgr = PairingManager()
|
|
mgr.create_pair_request("8613800138000", "ABC123")
|
|
mgr.confirm_pair("8613800138000")
|
|
assert mgr.is_paired("8613800138000") is True
|
|
|
|
def test_is_paired_false(self):
|
|
mgr = PairingManager()
|
|
assert mgr.is_paired("unknown") is False
|
|
|
|
def test_revoke_pair_pending(self):
|
|
mgr = PairingManager()
|
|
mgr.create_pair_request("8613800138000", "ABC123")
|
|
assert mgr.revoke_pair("8613800138000") is True
|
|
assert mgr.confirm_pair("8613800138000") is False
|
|
|
|
def test_revoke_pair_paired(self):
|
|
mgr = PairingManager()
|
|
mgr.create_pair_request("8613800138000", "ABC123")
|
|
mgr.confirm_pair("8613800138000")
|
|
assert mgr.is_paired("8613800138000") is True
|
|
assert mgr.revoke_pair("8613800138000") is True
|
|
assert mgr.is_paired("8613800138000") is False
|
|
|
|
def test_get_pending_phones(self):
|
|
mgr = PairingManager()
|
|
mgr.create_pair_request("8613800138000", "ABC")
|
|
mgr.create_pair_request("8613800138001", "DEF")
|
|
phones = mgr.get_pending_phones()
|
|
assert len(phones) == 2
|
|
assert "8613800138000" in phones
|
|
|
|
def test_cleanup_expired(self):
|
|
mgr = PairingManager()
|
|
mgr.create_pair_request("8613800138000", "ABC", timeout=0)
|
|
mgr.create_pair_request("8613800138001", "DEF", timeout=300)
|
|
time.sleep(0.01)
|
|
cleaned = mgr.cleanup_expired()
|
|
assert cleaned == 1
|
|
phones = mgr.get_pending_phones()
|
|
assert "8613800138001" in phones
|
|
assert "8613800138000" not in phones
|
|
|
|
|
|
class TestGroupGating:
|
|
def test_default_activated(self):
|
|
gg = GroupGating()
|
|
assert gg.is_activated("123456789@g.us") is True
|
|
|
|
def test_require_mention_default_false(self):
|
|
gg = GroupGating()
|
|
assert gg.require_mention("123456789@g.us") is False
|
|
|
|
def test_require_mention_configured(self):
|
|
gg = GroupGating({"groups": {"123456789": {"requireMention": True}}})
|
|
assert gg.require_mention("123456789@g.us") is True
|
|
|
|
def test_require_mention_by_full_jid(self):
|
|
gg = GroupGating({"groups": {"123456789@g.us": {"requireMention": True}}})
|
|
assert gg.require_mention("123456789@g.us") is True
|
|
|
|
def test_is_activated_configured_false(self):
|
|
gg = GroupGating({"groups": {"123456789": {"activated": False}}})
|
|
assert gg.is_activated("123456789@g.us") is False
|
|
|
|
def test_activate(self):
|
|
gg = GroupGating()
|
|
gg.deactivate("123456789@g.us")
|
|
assert gg.is_activated("123456789@g.us") is False
|
|
gg.activate("123456789@g.us")
|
|
assert gg.is_activated("123456789@g.us") is True
|
|
|
|
def test_deactivate(self):
|
|
gg = GroupGating()
|
|
gg.deactivate("123456789@g.us")
|
|
assert gg.is_activated("123456789@g.us") is False
|
|
|
|
def test_empty_config(self):
|
|
gg = GroupGating({})
|
|
assert gg.is_activated("any@g.us") is True
|
|
|
|
def test_invalid_groups_config(self):
|
|
gg = GroupGating({"groups": "not_a_dict"})
|
|
assert gg.is_activated("test@g.us") is True
|
|
|
|
def test_non_dict_group_entry(self):
|
|
gg = GroupGating({"groups": {"g1": "not_a_dict"}})
|
|
assert gg.is_activated("g1@g.us") is True |