from __future__ import annotations import pytest from yuxi.channels.adapters.imessage.security import ( DmPolicy, GroupPolicy, IMessageSecurityPolicy, SecurityCheckResult, resolve_dm_policy, resolve_group_policy, _match_entry, _normalize_handle, _strip_prefix, ) class TestResolveDmPolicy: def test_explicit_pairing(self): assert resolve_dm_policy({"dmPolicy": "pairing"}) == DmPolicy.PAIRING def test_explicit_open(self): assert resolve_dm_policy({"dm_policy": "open"}) == DmPolicy.OPEN def test_explicit_disabled(self): assert resolve_dm_policy({"dmPolicy": "disabled"}) == DmPolicy.DISABLED def test_explicit_allowlist(self): assert resolve_dm_policy({"dm_policy": "allowlist"}) == DmPolicy.ALLOWLIST def test_invalid_explicit_falls_back(self): assert resolve_dm_policy({"dm_policy": "invalid"}) == DmPolicy.OPEN def test_allow_from_with_wildcard(self): assert resolve_dm_policy({"allowFrom": ["*"]}) == DmPolicy.OPEN def test_allow_from_with_entries(self): assert resolve_dm_policy({"allowFrom": ["+8613800138000"]}) == DmPolicy.ALLOWLIST def test_empty_allow_from(self): assert resolve_dm_policy({"allowFrom": []}) == DmPolicy.OPEN def test_no_policy_specified(self): assert resolve_dm_policy({}) == DmPolicy.OPEN class TestResolveGroupPolicy: def test_explicit_open(self): assert resolve_group_policy({"groupPolicy": "open"}) == GroupPolicy.OPEN def test_explicit_disabled(self): assert resolve_group_policy({"group_policy": "disabled"}) == GroupPolicy.DISABLED def test_explicit_allowlist(self): assert resolve_group_policy({"groupPolicy": "allowlist"}) == GroupPolicy.ALLOWLIST def test_invalid_explicit_falls_back(self): assert resolve_group_policy({"groupPolicy": "invalid"}) == GroupPolicy.ALLOWLIST def test_no_policy_specified(self): assert resolve_group_policy({}) == GroupPolicy.ALLOWLIST class TestNormalizeHandle: def test_removes_plus(self): assert _normalize_handle("+8613800138000") == "8613800138000" def test_removes_spaces(self): assert _normalize_handle(" +86 138 0013 8000 ") == "8613800138000" def test_email_preserved(self): assert _normalize_handle("test@example.com") == "test@example.com" class TestStripPrefix: def test_no_prefix(self): assert _strip_prefix("+8613800138000") == ("+8613800138000", None) def test_imessage_prefix(self): assert _strip_prefix("imessage:+8613800138000") == ("+8613800138000", "imessage") def test_sms_prefix(self): assert _strip_prefix("sms:+8613800138000") == ("+8613800138000", "sms") def test_chat_id_prefix(self): assert _strip_prefix("chat_id:test123") == ("test123", "chat_id") def test_chat_guid_prefix(self): assert _strip_prefix("chat_guid:test456") == ("test456", "chat_guid") def test_prefix_case_insensitive(self): assert _strip_prefix("IMESSAGE:+8613800138000") == ("+8613800138000", "imessage") class TestMatchEntry: def test_wildcard(self): assert _match_entry("anyone", ["*"]) is True def test_exact_simple_match(self): assert _match_entry("+8613800138000", ["+8613800138000"]) is True def test_exact_match_with_imessage_prefix(self): assert _match_entry("+8613800138000", ["imessage:+8613800138000"]) is True def test_exact_match_with_sms_prefix(self): assert _match_entry("+8613800138000", ["sms:+8613800138000"]) is True def test_no_match(self): assert _match_entry("+8613800138000", ["+8613900139000"]) is False def test_email_match(self): assert _match_entry("test@example.com", ["test@example.com"]) is True def test_chat_guid_match(self): assert _match_entry("iMessage;-;group001", ["chat_guid:iMessage;-;group001"]) is True def test_empty_allow_list(self): assert _match_entry("+8613800138000", []) is False class TestSecurityCheckResult: def test_allowed(self): r = SecurityCheckResult(allowed=True) assert r.allowed is True assert r.reject_reason is None assert r.reply is None def test_blocked_with_reason(self): r = SecurityCheckResult(allowed=False, reject_reason="not_in_allowlist", reply="Unauthorized") assert r.allowed is False assert r.reject_reason == "not_in_allowlist" assert r.reply == "Unauthorized" class TestIMessageSecurityPolicy: def make_policy(self, **overrides): config = { "dmPolicy": "pairing", "groupPolicy": "allowlist", "allowFrom": ["+8613800138000"], "groupAllowFrom": ["iMessage;-;group001"], **overrides, } return IMessageSecurityPolicy(config) def test_initialization(self): policy = self.make_policy() assert policy.dm_policy == DmPolicy.PAIRING assert policy.group_policy == GroupPolicy.ALLOWLIST assert len(policy.allow_list) > 0 assert len(policy.group_allow_list) > 0 def test_check_dm_open(self): policy = self.make_policy(dmPolicy="open") result = policy.check_dm_access("+8613900139000") assert result.allowed is True def test_check_dm_disabled(self): policy = self.make_policy(dmPolicy="disabled") result = policy.check_dm_access("+8613800138000") assert result.allowed is False assert "disabled" in str(result.reject_reason) def test_check_dm_allowlist_allowed(self): policy = self.make_policy(dmPolicy="allowlist", allowFrom=["+8613800138000"]) result = policy.check_dm_access("+8613800138000") assert result.allowed is True def test_check_dm_allowlist_blocked(self): policy = self.make_policy(dmPolicy="allowlist", allowFrom=["+8613800138000"]) result = policy.check_dm_access("+8613900139000") assert result.allowed is False assert "not_in_dm_allowlist" in str(result.reject_reason) def test_check_dm_pairing(self): policy = self.make_policy(dmPolicy="pairing") result = policy.check_dm_access("+8613900139000") assert result.allowed is True def test_check_group_open(self): policy = self.make_policy(groupPolicy="open") result = policy.check_group_access("iMessage;-;group002") assert result.allowed is True def test_check_group_disabled(self): policy = self.make_policy(groupPolicy="disabled") result = policy.check_group_access("iMessage;-;group001") assert result.allowed is False def test_check_group_allowlist_allowed(self): policy = self.make_policy(groupPolicy="allowlist", groupAllowFrom=["iMessage;-;group001"]) result = policy.check_group_access("iMessage;-;group001") assert result.allowed is True def test_check_group_allowlist_blocked(self): policy = self.make_policy(groupPolicy="allowlist", groupAllowFrom=["iMessage;-;group001"]) result = policy.check_group_access("iMessage;-;group002") assert result.allowed is False def test_check_group_override_disabled(self): policy = self.make_policy( groupPolicy="open", groups={"iMessage;-;group001": {"enabled": False}}, ) result = policy.check_group_access("iMessage;-;group001") assert result.allowed is False def test_check_mention_required_not_required(self): policy = self.make_policy(requireMention=False) result = policy.check_mention_required("iMessage;-;group001", None, "") assert result.allowed is True def test_check_mention_required_bot_mentioned(self): policy = self.make_policy(requireMention=True) from yuxi.channels.models import MentionsInfo mentions = MentionsInfo(mentioned_user_ids=["+8613800138000"]) result = policy.check_mention_required( "iMessage;-;group001", mentions, "+8613800138000" ) assert result.allowed is True def test_check_mention_required_bot_not_mentioned(self): policy = self.make_policy(requireMention=True) from yuxi.channels.models import MentionsInfo mentions = MentionsInfo(mentioned_user_ids=["+8613900139000"]) result = policy.check_mention_required( "iMessage;-;group001", mentions, "+8613800138000" ) assert result.allowed is False def test_add_to_allow_list(self): policy = self.make_policy(allowFrom=["+8613800138000"]) policy.add_to_allow_list("+8613900139000") assert "8613900139000" in str(policy.allow_list) def test_add_duplicate_noop(self): policy = self.make_policy(allowFrom=["+8613800138000"]) length_before = len(policy.allow_list) policy.add_to_allow_list("+8613800138000") assert len(policy.allow_list) == length_before def test_remove_from_allow_list(self): policy = self.make_policy(allowFrom=["+8613800138000", "+8613900139000"]) assert policy.remove_from_allow_list("+8613800138000") is True assert policy.remove_from_allow_list("nonexistent") is False def test_add_to_group_allow_list(self): policy = self.make_policy(groupAllowFrom=[]) policy.add_to_group_allow_list("iMessage;-;group_new") assert "iMessage;-;group_new" in policy.group_allow_list def test_remove_from_group_allow_list(self): policy = self.make_policy( groupAllowFrom=["iMessage;-;group001", "iMessage;-;group002"] ) assert policy.remove_from_group_allow_list("iMessage;-;group001") is True assert policy.remove_from_group_allow_list("nonexistent") is False def test_collect_warnings_dm_open(self): policy = self.make_policy(dmPolicy="open") warnings = policy.collect_warnings() assert any("open" in w.lower() for w in warnings) def test_collect_warnings_allowlist_empty(self): policy = self.make_policy(dmPolicy="allowlist", allowFrom=[]) warnings = policy.collect_warnings() assert any("empty" in w.lower() for w in warnings) def test_collect_warnings_group_allowlist_empty(self): policy = self.make_policy(groupPolicy="allowlist", groupAllowFrom=[]) warnings = policy.collect_warnings() assert any("empty" in w.lower() for w in warnings) def test_collect_warnings_group_open(self): policy = self.make_policy(groupPolicy="open", groupAllowFrom=[]) warnings = policy.collect_warnings() assert len(warnings) >= 1 def test_collect_warnings_fallback_enabled(self): policy = self.make_policy(groupAllowFromFallback=True) warnings = policy.collect_warnings() assert any("fallback" in w.lower() for w in warnings) def test_resolve_runtime_group_policy_explicit(self): policy = self.make_policy( groups={"iMessage;-;group001": {"group_policy": "open"}} ) result = policy.resolve_runtime_group_policy("iMessage;-;group001") assert result == GroupPolicy.OPEN def test_resolve_runtime_group_policy_default(self): policy = self.make_policy(groupPolicy="allowlist") result = policy.resolve_runtime_group_policy("iMessage;-;nonexistent") assert result == GroupPolicy.ALLOWLIST def test_resolve_pinned_dm_owner(self): policy = self.make_policy(allowFrom=["+8613800138000"]) owner = policy.resolve_pinned_dm_owner() assert owner == "8613800138000" def test_resolve_pinned_dm_owner_with_prefix(self): policy = self.make_policy(allowFrom=["imsg:+8613800138000"]) owner = policy.resolve_pinned_dm_owner() assert owner is not None def test_context_visibility_open(self): policy = self.make_policy(contextVisibilityMode="open") assert policy.evaluate_context_visibility( "iMessage;+;chat001", "+8613900139000" ) is True def test_context_visibility_disabled(self): policy = self.make_policy(contextVisibilityMode="disabled") assert policy.evaluate_context_visibility( "iMessage;+;chat001", "+8613800138000" ) is False def test_context_visibility_allowlist_matched(self): policy = self.make_policy( contextVisibilityMode="allowlist", allowFrom=["+8613800138000"] ) assert policy.evaluate_context_visibility( "iMessage;+;chat001", "+8613800138000" ) is True def test_context_visibility_allowlist_not_matched(self): policy = self.make_policy( contextVisibilityMode="allowlist", allowFrom=["+8613800138000"] ) assert policy.evaluate_context_visibility( "iMessage;+;chat001", "+8613900139000" ) is False def test_require_mention_property(self): policy = self.make_policy(requireMention=True) assert policy.require_mention is True def test_should_update_last_route(self): policy = self.make_policy(allowFrom=["+8613800138000"]) assert policy.should_update_last_route("+8613800138000") is True def test_should_update_last_route_no_owner(self): policy = self.make_policy(allowFrom=[]) assert policy.should_update_last_route("+8613800138000") is True