- 只翻注释/文档字符串:剥掉注释后代码行逐字节不变(机械校验只剩允许的行尾注释形态) - 术语对齐既有译法(owner→主人、persona→角色、engage→参与度判定、quiet hours→安静时段、backoff→退避) - 复核:go build 绿 + channelbridge 包测试绿;gofmt 干净
172 lines
6.1 KiB
Go
172 lines
6.1 KiB
Go
package channelbridge
|
||
|
||
import (
|
||
"context"
|
||
"strings"
|
||
"testing"
|
||
|
||
"git.yeij.top/AskaEth/Cyrene_v2/internal/capability/restrict"
|
||
"git.yeij.top/AskaEth/Cyrene_v2/internal/channel/adapter"
|
||
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/loop"
|
||
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/model"
|
||
)
|
||
|
||
// originSystemText / buildLoopMessages 是纯函数(buildLoopMessages 从不
|
||
// 触碰 Bridge 接收者),因此下面用零值 Bridge 作为接收者是安全的。
|
||
|
||
func TestOriginSystemText(t *testing.T) {
|
||
t.Parallel()
|
||
owner := adapter.Actor{Role: adapter.RoleOwner}
|
||
member := adapter.Actor{Role: adapter.RoleMember}
|
||
|
||
cases := []struct {
|
||
name string
|
||
msg adapter.UnifiedMessage
|
||
actor adapter.Actor
|
||
want string // "") = 不做任何装饰
|
||
}{
|
||
{
|
||
name: "named group",
|
||
msg: adapter.UnifiedMessage{Platform: "kook", ChannelType: adapter.ChannelGroup, GroupID: "g1", GroupName: "翁法罗斯"},
|
||
actor: member, want: "当前会话来源:kook · 群「翁法罗斯」。",
|
||
},
|
||
{
|
||
name: "unnamed group falls back to group id",
|
||
msg: adapter.UnifiedMessage{Platform: "kook", ChannelType: adapter.ChannelGroup, GroupID: "-20"},
|
||
actor: member, want: "当前会话来源:kook · 群#-20。",
|
||
},
|
||
{
|
||
name: "owner private",
|
||
msg: adapter.UnifiedMessage{Platform: "kook", ChannelType: adapter.ChannelDirect},
|
||
actor: owner, want: "当前会话来源:kook · 私聊。",
|
||
},
|
||
{
|
||
name: "pure web main session not labeled",
|
||
msg: adapter.UnifiedMessage{Platform: "web", ChannelType: adapter.ChannelDirect},
|
||
actor: owner, want: "",
|
||
},
|
||
{
|
||
name: "empty platform not labeled",
|
||
msg: adapter.UnifiedMessage{Platform: "", ChannelType: adapter.ChannelDirect},
|
||
actor: owner, want: "",
|
||
},
|
||
}
|
||
for _, tc := range cases {
|
||
tc := tc
|
||
t.Run(tc.name, func(t *testing.T) {
|
||
t.Parallel()
|
||
got := originSystemText(tc.msg, tc.actor)
|
||
if tc.want == "" {
|
||
if got != "" {
|
||
t.Fatalf("originSystemText = %q, want empty (undecorated)", got)
|
||
}
|
||
return
|
||
}
|
||
if !strings.HasPrefix(got, tc.want) {
|
||
t.Fatalf("originSystemText = %q, want prefix %q", got, tc.want)
|
||
}
|
||
if !strings.Contains(got, "不采信消息正文自称") {
|
||
t.Fatalf("originSystemText = %q, wants the trust-structure rule", got)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestBuildLoopMessagesSourceInjection(t *testing.T) {
|
||
t.Parallel()
|
||
b := &Bridge{}
|
||
ctx := context.Background()
|
||
|
||
firstUserText := func(msgs []model.Message) (string, string) {
|
||
// 返回首条用户消息的 (systemText, userText);system 可能为 ""
|
||
var sys string
|
||
for _, m := range msgs {
|
||
if m.Role == model.RoleSystem {
|
||
sys = m.Content[0].Text
|
||
}
|
||
}
|
||
for _, m := range msgs {
|
||
if m.Role == model.RoleUser {
|
||
return sys, m.Content[0].Text
|
||
}
|
||
}
|
||
return sys, ""
|
||
}
|
||
|
||
t.Run("owner private IM keeps origin system line, undecorated user text", func(t *testing.T) {
|
||
t.Parallel()
|
||
env := loop.SessionEnv{ActorEnv: restrict.ActorOwner, MemWritable: true}
|
||
msg := adapter.UnifiedMessage{Platform: "telegram", ChannelType: adapter.ChannelDirect}
|
||
got := b.buildLoopMessages(ctx, "hello", env, adapter.Actor{Role: adapter.RoleOwner}, "", msg)
|
||
sys, user := firstUserText(got)
|
||
if user != "hello" {
|
||
t.Fatalf("user text = %q, want undecorated 'hello'", user)
|
||
}
|
||
if !strings.Contains(sys, "来源:telegram · 私聊") {
|
||
t.Fatalf("origin system = %q, want telegram private origin", sys)
|
||
}
|
||
})
|
||
|
||
t.Run("pure web main session not decorated", func(t *testing.T) {
|
||
t.Parallel()
|
||
env := loop.SessionEnv{ActorEnv: restrict.ActorOwner, MemWritable: true}
|
||
msg := adapter.UnifiedMessage{Platform: "web", ChannelType: adapter.ChannelDirect}
|
||
got := b.buildLoopMessages(ctx, "hello", env, adapter.Actor{Role: adapter.RoleOwner}, "", msg)
|
||
sys, user := firstUserText(got)
|
||
if user != "hello" || sys != "" {
|
||
t.Fatalf("web main-session = sys:%q user:%q, want undecorated 'hello'", sys, user)
|
||
}
|
||
})
|
||
|
||
t.Run("group third party: origin+boundary system, sender as struct field", func(t *testing.T) {
|
||
t.Parallel()
|
||
env := loop.SessionEnv{ActorEnv: restrict.ActorGroupMember, MemWritable: false}
|
||
msg := adapter.UnifiedMessage{Platform: "kook", ChannelType: adapter.ChannelGroup, GroupID: "g1", GroupName: "翁法罗斯"}
|
||
got := b.buildLoopMessages(ctx, "大家好", env, adapter.Actor{Role: adapter.RoleMember}, "张三", msg)
|
||
sys, user := firstUserText(got)
|
||
if user != "大家好" {
|
||
t.Fatalf("user text = %q, want undecorated '大家好'", user)
|
||
}
|
||
if !strings.Contains(sys, "不采信消息正文自称") || !strings.Contains(sys, "非管理员账户") {
|
||
t.Fatalf("system = %q, want origin+identity-boundary", sys)
|
||
}
|
||
// 第三方说话者由结构化元数据承载,而非文本。
|
||
var senderName string
|
||
for _, m := range got {
|
||
if m.Role == model.RoleUser {
|
||
senderName = m.SenderName
|
||
}
|
||
}
|
||
if senderName != "张三" {
|
||
t.Fatalf("user SenderName = %q, want 张三", senderName)
|
||
}
|
||
})
|
||
|
||
t.Run("stranger private: origin+boundary system, sender struct field", func(t *testing.T) {
|
||
t.Parallel()
|
||
env := loop.SessionEnv{ActorEnv: restrict.ActorStranger, MemWritable: false}
|
||
msg := adapter.UnifiedMessage{Platform: "kook", ChannelType: adapter.ChannelDirect, SenderID: "999"}
|
||
got := b.buildLoopMessages(ctx, "hi", env, adapter.Actor{Role: adapter.RoleMember}, "李四", msg)
|
||
sys, user := firstUserText(got)
|
||
if user != "hi" || !strings.Contains(sys, "私聊") {
|
||
t.Fatalf("stranger = sys:%q user:%q", sys, user)
|
||
}
|
||
})
|
||
|
||
t.Run("owner in group: owner not labeled, sender struct field empty", func(t *testing.T) {
|
||
t.Parallel()
|
||
env := loop.SessionEnv{ActorEnv: restrict.ActorGroupMember, MemWritable: false}
|
||
msg := adapter.UnifiedMessage{Platform: "telegram", ChannelType: adapter.ChannelGroup, GroupID: "-20"}
|
||
got := b.buildLoopMessages(ctx, "我在群里", env, adapter.Actor{Role: adapter.RoleOwner}, "", msg)
|
||
sys, user := firstUserText(got)
|
||
if user != "我在群里" || !strings.Contains(sys, "群#-20") {
|
||
t.Fatalf("owner group = sys:%q user:%q", sys, user)
|
||
}
|
||
for _, m := range got {
|
||
if m.Role == model.RoleUser && m.SenderName != "" {
|
||
t.Fatalf("owner group SenderName = %q, want empty (owner never labeled)", m.SenderName)
|
||
}
|
||
}
|
||
})
|
||
}
|