test(dingtalk): cover require_mention + allowed_users gating
Adds 16 regression tests for the gating logic introduced in the
salvaged commit:
* TestAllowedUsersGate — empty/wildcard/case-insensitive matching,
staff_id vs sender_id, env var CSV population
* TestMentionPatterns — compilation, case-insensitivity, invalid
regex is skipped-not-raised, JSON env var, newline fallback
* TestShouldProcessMessage — DM always accepted, group gating via
require_mention / is_in_at_list / wake-word pattern / free_response_chats
Also adds yule975 to scripts/release.py AUTHOR_MAP (release CI blocks
unmapped emails).
This commit is contained in:
@@ -84,6 +84,7 @@ AUTHOR_MAP = {
|
||||
"abdullahfarukozden@gmail.com": "Farukest",
|
||||
"lovre.pesut@gmail.com": "rovle",
|
||||
"kevinskysunny@gmail.com": "kevinskysunny",
|
||||
"xiewenxuan462@gmail.com": "yule975",
|
||||
"hakanerten02@hotmail.com": "teyrebaz33",
|
||||
"ruzzgarcn@gmail.com": "Ruzzgar",
|
||||
"alireza78.crypto@gmail.com": "alireza78a",
|
||||
|
||||
@@ -421,3 +421,157 @@ class TestExtractText:
|
||||
msg.rich_text = None
|
||||
assert DingTalkAdapter._extract_text(msg) == ""
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Group gating — require_mention + allowed_users (parity with other platforms)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _make_gating_adapter(monkeypatch, *, extra=None, env=None):
|
||||
"""Build a DingTalkAdapter with only the gating fields populated.
|
||||
|
||||
Clears every DINGTALK_* gating env var before applying the caller's
|
||||
overrides so individual tests stay isolated.
|
||||
"""
|
||||
for key in (
|
||||
"DINGTALK_REQUIRE_MENTION",
|
||||
"DINGTALK_MENTION_PATTERNS",
|
||||
"DINGTALK_FREE_RESPONSE_CHATS",
|
||||
"DINGTALK_ALLOWED_USERS",
|
||||
):
|
||||
monkeypatch.delenv(key, raising=False)
|
||||
for key, value in (env or {}).items():
|
||||
monkeypatch.setenv(key, value)
|
||||
from gateway.platforms.dingtalk import DingTalkAdapter
|
||||
return DingTalkAdapter(PlatformConfig(enabled=True, extra=extra or {}))
|
||||
|
||||
|
||||
class TestAllowedUsersGate:
|
||||
|
||||
def test_empty_allowlist_allows_everyone(self, monkeypatch):
|
||||
adapter = _make_gating_adapter(monkeypatch)
|
||||
assert adapter._is_user_allowed("anyone", "any-staff") is True
|
||||
|
||||
def test_wildcard_allowlist_allows_everyone(self, monkeypatch):
|
||||
adapter = _make_gating_adapter(monkeypatch, extra={"allowed_users": ["*"]})
|
||||
assert adapter._is_user_allowed("anyone", "any-staff") is True
|
||||
|
||||
def test_matches_sender_id_case_insensitive(self, monkeypatch):
|
||||
adapter = _make_gating_adapter(
|
||||
monkeypatch, extra={"allowed_users": ["SenderABC"]}
|
||||
)
|
||||
assert adapter._is_user_allowed("senderabc", "") is True
|
||||
|
||||
def test_matches_staff_id(self, monkeypatch):
|
||||
adapter = _make_gating_adapter(
|
||||
monkeypatch, extra={"allowed_users": ["staff_1234"]}
|
||||
)
|
||||
assert adapter._is_user_allowed("", "staff_1234") is True
|
||||
|
||||
def test_rejects_unknown_user(self, monkeypatch):
|
||||
adapter = _make_gating_adapter(
|
||||
monkeypatch, extra={"allowed_users": ["staff_1234"]}
|
||||
)
|
||||
assert adapter._is_user_allowed("other-sender", "other-staff") is False
|
||||
|
||||
def test_env_var_csv_populates_allowlist(self, monkeypatch):
|
||||
adapter = _make_gating_adapter(
|
||||
monkeypatch, env={"DINGTALK_ALLOWED_USERS": "alice,bob,carol"}
|
||||
)
|
||||
assert adapter._is_user_allowed("alice", "") is True
|
||||
assert adapter._is_user_allowed("dave", "") is False
|
||||
|
||||
|
||||
class TestMentionPatterns:
|
||||
|
||||
def test_empty_patterns_list(self, monkeypatch):
|
||||
adapter = _make_gating_adapter(monkeypatch)
|
||||
assert adapter._mention_patterns == []
|
||||
assert adapter._message_matches_mention_patterns("anything") is False
|
||||
|
||||
def test_pattern_matches_text(self, monkeypatch):
|
||||
adapter = _make_gating_adapter(
|
||||
monkeypatch, extra={"mention_patterns": ["^hermes"]}
|
||||
)
|
||||
assert adapter._message_matches_mention_patterns("hermes please help") is True
|
||||
assert adapter._message_matches_mention_patterns("please hermes help") is False
|
||||
|
||||
def test_pattern_is_case_insensitive(self, monkeypatch):
|
||||
adapter = _make_gating_adapter(
|
||||
monkeypatch, extra={"mention_patterns": ["^hermes"]}
|
||||
)
|
||||
assert adapter._message_matches_mention_patterns("HERMES help") is True
|
||||
|
||||
def test_invalid_regex_is_skipped_not_raised(self, monkeypatch):
|
||||
adapter = _make_gating_adapter(
|
||||
monkeypatch,
|
||||
extra={"mention_patterns": ["[unclosed", "^valid"]},
|
||||
)
|
||||
# Invalid pattern dropped, valid one kept
|
||||
assert len(adapter._mention_patterns) == 1
|
||||
assert adapter._message_matches_mention_patterns("valid trigger") is True
|
||||
|
||||
def test_env_var_json_populates_patterns(self, monkeypatch):
|
||||
adapter = _make_gating_adapter(
|
||||
monkeypatch,
|
||||
env={"DINGTALK_MENTION_PATTERNS": '["^bot", "^assistant"]'},
|
||||
)
|
||||
assert len(adapter._mention_patterns) == 2
|
||||
assert adapter._message_matches_mention_patterns("bot ping") is True
|
||||
|
||||
def test_env_var_newline_fallback_when_not_json(self, monkeypatch):
|
||||
adapter = _make_gating_adapter(
|
||||
monkeypatch,
|
||||
env={"DINGTALK_MENTION_PATTERNS": "^bot\n^assistant"},
|
||||
)
|
||||
assert len(adapter._mention_patterns) == 2
|
||||
|
||||
|
||||
class TestShouldProcessMessage:
|
||||
|
||||
def test_dm_always_accepted(self, monkeypatch):
|
||||
adapter = _make_gating_adapter(
|
||||
monkeypatch, extra={"require_mention": True}
|
||||
)
|
||||
msg = MagicMock(is_in_at_list=False)
|
||||
assert adapter._should_process_message(msg, "hi", is_group=False, chat_id="dm1") is True
|
||||
|
||||
def test_group_rejected_when_require_mention_and_no_trigger(self, monkeypatch):
|
||||
adapter = _make_gating_adapter(
|
||||
monkeypatch, extra={"require_mention": True}
|
||||
)
|
||||
msg = MagicMock(is_in_at_list=False)
|
||||
assert adapter._should_process_message(msg, "hi", is_group=True, chat_id="grp1") is False
|
||||
|
||||
def test_group_accepted_when_require_mention_disabled(self, monkeypatch):
|
||||
adapter = _make_gating_adapter(
|
||||
monkeypatch, extra={"require_mention": False}
|
||||
)
|
||||
msg = MagicMock(is_in_at_list=False)
|
||||
assert adapter._should_process_message(msg, "hi", is_group=True, chat_id="grp1") is True
|
||||
|
||||
def test_group_accepted_when_bot_is_mentioned(self, monkeypatch):
|
||||
adapter = _make_gating_adapter(
|
||||
monkeypatch, extra={"require_mention": True}
|
||||
)
|
||||
msg = MagicMock(is_in_at_list=True)
|
||||
assert adapter._should_process_message(msg, "hi", is_group=True, chat_id="grp1") is True
|
||||
|
||||
def test_group_accepted_when_text_matches_wake_word(self, monkeypatch):
|
||||
adapter = _make_gating_adapter(
|
||||
monkeypatch,
|
||||
extra={"require_mention": True, "mention_patterns": ["^hermes"]},
|
||||
)
|
||||
msg = MagicMock(is_in_at_list=False)
|
||||
assert adapter._should_process_message(msg, "hermes help", is_group=True, chat_id="grp1") is True
|
||||
|
||||
def test_group_accepted_when_chat_in_free_response_list(self, monkeypatch):
|
||||
adapter = _make_gating_adapter(
|
||||
monkeypatch,
|
||||
extra={"require_mention": True, "free_response_chats": ["grp1"]},
|
||||
)
|
||||
msg = MagicMock(is_in_at_list=False)
|
||||
assert adapter._should_process_message(msg, "hi", is_group=True, chat_id="grp1") is True
|
||||
# Different group still blocked
|
||||
assert adapter._should_process_message(msg, "hi", is_group=True, chat_id="grp2") is False
|
||||
|
||||
|
||||
Reference in New Issue
Block a user