package session // 本文件钉住 keys.go 导出的两个派生:`ChannelKey` 与会话层内部 `channelID` 是**同一个** // 实现,`Key` 与会话缓存键**逐字相同**。它们是「什么算同一个会话」的唯一定义 —— 车道 // (internal/services/channelbridge/lane.go)按会话串行就靠它,两处一旦分叉,同一个 // session 就会出现两条车道、两个回合并发(M22 复审 P1-1)。 import ( "testing" "git.yeij.top/AskaEth/Cyrene_v2/internal/channel/adapter" ) func TestChannelKeyDerivations(t *testing.T) { t.Parallel() cases := []struct { name string msg adapter.UnifiedMessage want string }{ { name: "群:按 group:,与频道无关", msg: adapter.UnifiedMessage{Platform: "kook", ChannelType: adapter.ChannelGroup, ChannelID: "c1", GroupID: "g1"}, want: "group:g1", }, { name: "群:缺 GroupID 时退回 ChannelID", msg: adapter.UnifiedMessage{Platform: "kook", ChannelType: adapter.ChannelGroup, ChannelID: "c1"}, want: "c1", }, { name: "私聊:按 ChannelID", msg: adapter.UnifiedMessage{Platform: "telegram", ChannelType: adapter.ChannelDirect, ChannelID: "200", SenderID: "200"}, want: "200", }, { name: "私聊:缺 ChannelID 时退回 SenderID", msg: adapter.UnifiedMessage{Platform: "telegram", ChannelType: adapter.ChannelDirect, SenderID: "200"}, want: "200", }, { name: "私聊:两者都缺 ⇒ 空(调用方据此以 ErrInvalidChannel 拒掉)", msg: adapter.UnifiedMessage{Platform: "telegram", ChannelType: adapter.ChannelDirect}, want: "", }, } for _, c := range cases { c := c t.Run(c.name, func(t *testing.T) { t.Parallel() if got := ChannelKey(c.msg); got != c.want { t.Fatalf("ChannelKey = %q, want %q", got, c.want) } }) } } // TestKeyIsTheSessionManagerCacheKey 钉住「导出的 Key 就是会话缓存键」: // 形状 `platform \x00 channelID`(NUL 分隔,杜绝拼接歧义),且两维度都参与 —— // 同平台不同渠道、不同平台同渠道都必须是**不同**的键。 func TestKeyIsTheSessionManagerCacheKey(t *testing.T) { t.Parallel() group1 := adapter.UnifiedMessage{Platform: "kook", ChannelType: adapter.ChannelGroup, ChannelID: "c1", GroupID: "g1"} group1OtherChannel := adapter.UnifiedMessage{Platform: "kook", ChannelType: adapter.ChannelGroup, ChannelID: "c2", GroupID: "g1"} group2 := adapter.UnifiedMessage{Platform: "kook", ChannelType: adapter.ChannelGroup, ChannelID: "c1", GroupID: "g2"} otherPlatform := adapter.UnifiedMessage{Platform: "qq-obv11", ChannelType: adapter.ChannelGroup, ChannelID: "c1", GroupID: "g1"} if got, want := Key(group1), "kook\x00group:g1"; got != want { t.Fatalf("Key = %q, want %q", got, want) } // 同一 guild 的两个 text channel 在会话层是**同一个**会话。 if Key(group1) != Key(group1OtherChannel) { t.Fatalf("same guild channels must share one session key: %q vs %q", Key(group1), Key(group1OtherChannel)) } // 不同群、不同平台必须是不同的会话。 if Key(group1) == Key(group2) { t.Fatalf("different groups must not share a session key: %q", Key(group1)) } if Key(group1) == Key(otherPlatform) { t.Fatalf("different platforms must not share a session key: %q", Key(group1)) } // 渠道缺失时键仍非空,但 ChannelKey 能为空(调用方据此区分不可用渠道)。 noChannel := adapter.UnifiedMessage{Platform: "telegram", ChannelType: adapter.ChannelDirect} if Key(noChannel) == "" || ChannelKey(noChannel) != "" { t.Fatalf("missing-channel case: Key=%q ChannelKey=%q", Key(noChannel), ChannelKey(noChannel)) } }