Add HeXi bot codebase: custom plugins, web frontends, tests
- hexi core: message handling, rate limiting, cooldown, plugin manager - Custom plugins: BF stats, daily check-in, quotes, persona cards, etc. - Community plugins vendored under hexi/plugins with local fixes - Web admin frontends (learning-chat, persona-admin), unified hexi/web - Tests for rate_limit/cooldown/memes/persona; poetry.lock Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
"""表情裸词操作补丁(hexi/plugins/memes_ops)的回归测试。
|
||||
|
||||
验证 `对称 右` 这类“表情 + 裸词操作”能被新 matcher 正确解析为选项,
|
||||
同时 `-右`/`--right` 旧形式仍然可用。
|
||||
|
||||
若 nonebot_plugin_memes 缺失或补丁因版本不兼容而失效,本模块整体跳过。
|
||||
"""
|
||||
|
||||
import nonebot
|
||||
import pytest
|
||||
|
||||
try:
|
||||
nonebot.init()
|
||||
from nonebot import get_loaded_plugins, load_plugin
|
||||
|
||||
load_plugin("hexi.plugins.memes_ops")
|
||||
_memes_loaded = True
|
||||
except Exception:
|
||||
_memes_loaded = False
|
||||
|
||||
if not _memes_loaded:
|
||||
pytest.skip(
|
||||
"无法加载 nonebot_plugin_memes / memes_ops,跳过裸词操作补丁测试",
|
||||
allow_module_level=True,
|
||||
)
|
||||
|
||||
_patchers = [
|
||||
matcher
|
||||
for plugin in get_loaded_plugins()
|
||||
for matcher in plugin.matcher
|
||||
if matcher.priority == 11 and matcher.module_name == "hexi.plugins.memes_ops"
|
||||
]
|
||||
|
||||
if not _patchers:
|
||||
pytest.skip(
|
||||
"memes_ops 补丁 matcher 未注册(版本不兼容?),跳过裸词操作补丁测试",
|
||||
allow_module_level=True,
|
||||
)
|
||||
|
||||
_cmd_start = list(nonebot.get_driver().config.command_start)[0]
|
||||
|
||||
|
||||
def _parse(msg: str):
|
||||
"""在所有新 matcher 中解析消息,返回 (选项, 文字参数) 命中集合。"""
|
||||
hits = []
|
||||
for matcher in _patchers:
|
||||
try:
|
||||
result = matcher.command().parse(msg)
|
||||
except Exception:
|
||||
continue
|
||||
if result.matched:
|
||||
options = {
|
||||
key: value.value
|
||||
for key, value in result.options.items()
|
||||
if value.value not in (None, False)
|
||||
}
|
||||
params = [
|
||||
seg.text for seg in result.query("meme_params", ()) if seg.text
|
||||
]
|
||||
hits.append((options, params))
|
||||
return hits
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("msg", "expected_options"),
|
||||
[
|
||||
(f"{_cmd_start}对称 右", {"right"}),
|
||||
(f"{_cmd_start}对称 左", {"left"}),
|
||||
(f"{_cmd_start}对称 上", {"top"}),
|
||||
(f"{_cmd_start}对称 下", {"bottom"}),
|
||||
(f"{_cmd_start}鬼畜 右", {"right"}),
|
||||
(f"{_cmd_start}循环 下", {"bottom"}),
|
||||
(f"{_cmd_start}摸 圆", {"circle"}),
|
||||
(f"{_cmd_start}小丑 爷", {"person"}),
|
||||
(f"{_cmd_start}小丑面具 前", {"front"}),
|
||||
(f"{_cmd_start}ba说 左", {"left"}),
|
||||
],
|
||||
)
|
||||
def test_bare_operation_maps_to_option(msg, expected_options):
|
||||
hits = _parse(msg)
|
||||
assert hits, f"{msg!r} 未被任何补丁 matcher 命中"
|
||||
# 新 matcher 优先(priority 11),取第一个命中
|
||||
options, params = hits[0]
|
||||
assert set(options) == expected_options
|
||||
assert params == []
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"msg",
|
||||
[f"{_cmd_start}对称 -右", f"{_cmd_start}对称 --right", f"{_cmd_start}鬼畜 --bottom"],
|
||||
)
|
||||
def test_dash_syntax_still_works(msg):
|
||||
options, params = _parse(msg)[0]
|
||||
assert options, f"{msg!r} 应解析出选项"
|
||||
assert params == []
|
||||
|
||||
|
||||
def test_non_alias_word_stays_text():
|
||||
msg = f"{_cmd_start}对称 任意文本"
|
||||
hits = _parse(msg)
|
||||
assert hits
|
||||
options, params = hits[0]
|
||||
assert options == {}
|
||||
assert params == ["任意文本"]
|
||||
Reference in New Issue
Block a user