- 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>
136 lines
5.3 KiB
Python
136 lines
5.3 KiB
Python
"""人设卡治理层纯函数测试
|
|
|
|
galgame_card 包 __init__ 会触发 NoneBot 运行时初始化(require orm),
|
|
无法裸导入,故用 importlib 按文件路径直接加载 processor 模块
|
|
(与 test_rate_limit.py 同款做法)。
|
|
"""
|
|
|
|
import importlib.util
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
_MODULE_PATH = (
|
|
Path(__file__).resolve().parents[1]
|
|
/ "hexi"
|
|
/ "plugins"
|
|
/ "nonebot_plugin_galgame_card"
|
|
/ "processor.py"
|
|
)
|
|
|
|
|
|
def _load_processor():
|
|
spec = importlib.util.spec_from_file_location("persona_processor", _MODULE_PATH)
|
|
module = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(module)
|
|
return module
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def processor():
|
|
return _load_processor()
|
|
|
|
|
|
COMMAND_STARTS = {"/", ""}
|
|
|
|
|
|
def test_filter_content_normal(processor):
|
|
assert processor.filter_content("草,又被加班干碎了", COMMAND_STARTS) == "草,又被加班干碎了"
|
|
|
|
|
|
def test_filter_content_command(processor):
|
|
assert processor.filter_content("/打卡", COMMAND_STARTS) is None
|
|
|
|
|
|
def test_filter_content_command_empty_prefix_not_fatal(processor):
|
|
# command_start 里空串不应导致全量误杀
|
|
assert processor.filter_content("正常说话", {""}) == "正常说话"
|
|
assert processor.filter_content("/打卡", {""}) == "/打卡"
|
|
|
|
|
|
def test_desensitize_clear_patterns(processor):
|
|
assert processor.desensitize("我电话是13800138000") == "我电话是[手机号]"
|
|
assert processor.desensitize("手机号138-0013-8000") == "手机号[手机号]" # 带分隔符变体
|
|
assert processor.desensitize("身份证110101199003070012") == "身份证[身份证]"
|
|
assert processor.desensitize("卡号6222021234567890") == "卡号[银行卡]"
|
|
assert processor.desensitize("联系我 a@b.com") == "联系我 [邮箱]"
|
|
assert processor.desensitize("服务器192.168.1.1") == "服务器[IP]"
|
|
assert processor.desensitize("加我 wxid_abc123xyz") == "加我 [微信号]"
|
|
assert processor.desensitize("车牌川A12345") == "车牌[车牌]"
|
|
assert processor.desensitize("定位30.5,104.0") == "定位[坐标]"
|
|
|
|
|
|
def test_desensitize_key_value(processor):
|
|
# 定位式:关键词 + 值,只替换值、保留关键词
|
|
assert processor.desensitize("密码是 xyz789") == "密码是 [密码]"
|
|
assert processor.desensitize("账号 abc123") == "账号 [账号]"
|
|
assert processor.desensitize("账号是 1122334455") == "账号是 [账号]"
|
|
assert processor.desensitize("激活码 ABCDE-12345") == "激活码 [兑换码]"
|
|
assert processor.desensitize("验证码是 123456") == "验证码是 [验证码]"
|
|
assert processor.desensitize("月薪 25000") == "月薪 [收入]"
|
|
assert processor.desensitize("VX: xiaoming123") == "VX: [微信号]"
|
|
|
|
|
|
def test_desensitize_fallback(processor):
|
|
# 兜底:裸长数字串 → [账号],字母数字混合 → [密码]
|
|
assert processor.desensitize("1122334455") == "[账号]"
|
|
assert processor.desensitize("abc1234") == "[密码]"
|
|
|
|
|
|
def test_desensitize_context_hint(processor):
|
|
# 语境强化:附近出现"验证码"关键词时,6-8 位数字 → [验证码]
|
|
assert processor.desensitize("123456", context_text="发下验证码") == "[验证码]"
|
|
assert processor.desensitize("验证码是 123456") == "验证码是 [验证码]"
|
|
# 无关键词时 6-8 位数字走兜底 → [账号]
|
|
assert processor.desensitize("123456") == "[账号]"
|
|
|
|
|
|
def test_desensitize_keeps_normal_text(processor):
|
|
# 正常文本不受影响(短数字、中文、英文单词)
|
|
assert processor.desensitize("今晚八点开黑,打了2800分") == "今晚八点开黑,打了2800分"
|
|
assert processor.desensitize("有一说一确实wwww") == "有一说一确实wwww"
|
|
assert processor.desensitize("房间码12345") == "房间码12345"
|
|
assert processor.desensitize("体重120斤") == "体重120斤"
|
|
|
|
|
|
def test_filter_content_url_flood(processor):
|
|
text = "看看这个 http://a.com http://b.com http://c.com"
|
|
assert processor.filter_content(text, COMMAND_STARTS) is None
|
|
|
|
|
|
def test_filter_content_truncate(processor):
|
|
long_text = "长" * 500
|
|
assert len(processor.filter_content(long_text, COMMAND_STARTS)) == processor.MAX_CONTENT_LEN
|
|
|
|
|
|
def test_is_command(processor):
|
|
assert processor.is_command("/查询", {"/"})
|
|
assert not processor.is_command("你好", {"/"})
|
|
|
|
|
|
def test_truncate(processor):
|
|
assert processor.truncate("abc", 2) == "ab"
|
|
assert processor.truncate("abc", 10) == "abc"
|
|
|
|
|
|
def test_image_hash_from_file(processor):
|
|
HASH32 = "0123456789abcdef0123456789abcdef"
|
|
# 优先 file 里的 32 位 hash
|
|
assert processor.image_hash_from_file(f"{HASH32}.image") == HASH32
|
|
# 回退 url 文件名里的 hash
|
|
assert (
|
|
processor.image_hash_from_file("", f"https://example.com/pics/{HASH32}.png")
|
|
== HASH32
|
|
)
|
|
# 都没有则截断文件名
|
|
assert processor.image_hash_from_file("somefile.png") == "somefile.png"
|
|
|
|
|
|
def test_build_content(processor):
|
|
assert processor.build_content("") is None
|
|
assert processor.build_content("草") == "草"
|
|
assert processor.build_content("", 2) == "[图片×2]"
|
|
assert processor.build_content("", 0, 3) == "[表情×3]"
|
|
assert processor.build_content("", 2, 3) == "[图片×2][表情×3]"
|
|
assert processor.build_content("草", 1) == "草 [图片×1]"
|