复审 r1 的 P1-1 被证实:车道键(a.Name()+ConversationKey,含频道与账号维度)与会话键 (platform \x00 channelID)是两套没有共同来源的判据,于是 kook 同一 guild 的两个 text channel、以及同平台两个账号对同一对端,都会得到「一个 session、两条车道、两个回合并发」 —— 两条 run 共享 sessionID,checkpoint 行 id 互相覆盖 / flow 桶被先收尾者删掉。 - P1-1 车道键与会话键同源:`internal/channel/session` 导出 ChannelKey / Key(channelID 与原缓存键派生**原样**导出,manager 内部改为走同一个 sessionKey,语义一字未改); `laneKey` 现在逐字是 `session.Key(msg)`(主人私聊仍是 `main|web` 这个目的地键), **去掉 a.Name() 维度**。同平台多账号对同一对端因此共用一条车道(它们是同一个 session), 出站路由不受影响:回复取每条消息自己的 platform/ChannelID 与它自己的适配器。 - P2-1 溢出区保序:`spill` 非空后新条目一律追加 spill、不再回写 `ch`,修掉复审实测的 `[m1 m2 m3 m5 m4]` 超车(现为到达顺序)。 - P2-2 口径修正:lane_test 文件头改指 `TestLaneRejectsNewestWhenFull`(语义是「门前拒收 最新、队列不动」);laneKey 注释改写「多账号必须共用一条车道」并写明取舍。 - P2-3 生产可见性:丢弃 WARN 带上 `dropped_stale_total` / `dropped_overflow_total` 累计 计数(不新开端点);主人私聊积压改成按倍率 16/32/64… 告警,不再只响一次。 断言:TestLaneKeyNeverCollidesAcrossSessions(表驱动,含 kook c1/c2@同 guild、 telegram conf0/conf1@同对端两行 + 不同群仍并行的控制行)、 TestLaneOwnerPrivateSpillKeepsArrivalOrder(逐条放行,钉住到达顺序)、 internal/channel/session/keys_test.go(键派生与会话缓存键形状)。
90 lines
3.6 KiB
Go
90 lines
3.6 KiB
Go
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:<GroupID>,与频道无关",
|
||
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))
|
||
}
|
||
}
|