Files
Cyrene_v2/internal/channel/adapter/systemnotice_test.go
T
AskaEth 5139945bfe
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
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 侧改追加(不再重启即截断)
2026-10-05 11:55:05 +08:00

103 lines
3.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)
}
}
}
}