ForcePilot/backend/test/unit/channels/test_whatsapp_security_pairing.py
Kris 69fe97a90d test: 批量修复并新增单元测试用例
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重连相关测试
2026-05-13 16:43:01 +08:00

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