feat(channels): 非消息事件接入(notice 归一化 + 传入前审查,fail-safe 静默)与入站脱敏帧日志/日志轮转
- notice:adapter 归一化 seam + 结构化分类 + 净化(ANSI/控制/零宽/双向字符、200 runes 截断) + fail-safe 写死为「静默」(未装配审查/超时/失败 ⇒ 仅登记、不驱动 loop) - 身份面零改动:notice 发送者恒 RoleSystem + SenderID=system - 复用策略四的同一个审查模型(Options.Review),未另造第二套 - 入站帧脱敏 DEBUG(post_type/message_type/sub_type/self_id/user_id/sender_id/group_id/段类型/at 目标) - ctrl.bat 启动前归档轮转(保留 20 份)+ bash 侧改追加(不再重启即截断)
This commit is contained in:
1 parent
85abb90067
commit
5139945bfe
15 files changed
+1604
-21
No files matched your search
@@ -0,0 +1,102 @@
|
||||
package adapter
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// 本文件覆盖 design/12 §6 策略一补充的归一化契约:结构分类(单一事实来源)、
|
||||
// 种类合法性,以及把平台原生通知文本剥成安全文本的净化器。
|
||||
|
||||
func TestSystemNoticeClassify(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
in SystemNotice
|
||||
want NoticeClass
|
||||
}{
|
||||
{"可忽略 → 仅登记", SystemNotice{Kind: NoticeJoin, Ignorable: true}, NoticeRegisterOnly},
|
||||
{"高价值 → 升级真会话", SystemNotice{Kind: NoticeOther, HighValue: true}, NoticeEscalate},
|
||||
{"其余 → 限定注入", SystemNotice{Kind: NoticeOther}, NoticeLimitedInject},
|
||||
// 两个标志同时为真时取保守侧(不打扰为第一原则)。
|
||||
{"既标可忽略又标高价值 → 仅登记", SystemNotice{Ignorable: true, HighValue: true}, NoticeRegisterOnly},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
if got := tc.in.Classify(); got != tc.want {
|
||||
t.Fatalf("Classify() = %v, want %v", got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoticeKindValid(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, k := range []NoticeKind{NoticeJoin, NoticeLeave, NoticeRecall, NoticeTyping, NoticeBotAdd, NoticeOther} {
|
||||
if !k.Valid() {
|
||||
t.Errorf("%q 应合法", k)
|
||||
}
|
||||
}
|
||||
if NoticeKind("weird").Valid() {
|
||||
t.Error("未知 kind 不该合法")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSanitizeNoticeText(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
in string
|
||||
want string
|
||||
}{
|
||||
{"空串", "", ""},
|
||||
{"折叠换行与制表", "有人\n加入\t了群聊", "有人 加入 了群聊"},
|
||||
{"吞掉 ANSI 序列", "\x1b[31m红\x1b[0m字", "红字"},
|
||||
{"丢掉零宽与双向覆盖字符", "群\u200b聊\u202e事", "群聊事"},
|
||||
{"丢掉其余控制字符", "系\x07统\x00通知", "系统通知"},
|
||||
{"折叠多余空白", " a b ", "a b"},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
if got := SanitizeNoticeText(tc.in); got != tc.want {
|
||||
t.Fatalf("SanitizeNoticeText(%q) = %q, want %q", tc.in, got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSanitizeNoticeTextTruncates(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
got := SanitizeNoticeText(strings.Repeat("啊", NoticeSafeTextMaxRunes+50))
|
||||
if runes := []rune(got); len(runes) != NoticeSafeTextMaxRunes+1 {
|
||||
t.Fatalf("截断后 %d runes, want %d+1(含省略号)", len(runes), NoticeSafeTextMaxRunes)
|
||||
}
|
||||
if !strings.HasSuffix(got, "…") {
|
||||
t.Errorf("截断未加省略号:%q", got)
|
||||
}
|
||||
}
|
||||
|
||||
// TestSanitizeNoticeTextNeverPanicsOnGarbage 钉住「畸形数据不炸流水线」:
|
||||
// 净化器对任意字节串都必须返回、不 panic,也绝不返回未净化的控制字符。
|
||||
func TestSanitizeNoticeTextNeverPanicsOnGarbage(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, in := range []string{
|
||||
"\x1b", "\x1b[", "\x1b[31", "正常\x1b[31m文本", "\u200b", "\ufeff", "\x7f", "a\x1b[1;2Hb",
|
||||
} {
|
||||
got := SanitizeNoticeText(in)
|
||||
for _, r := range got {
|
||||
if r == 0x1b || isInvisibleRune(r) {
|
||||
t.Fatalf("SanitizeNoticeText(%q) = %q 仍含异常字符", in, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user