package memory import ( "strings" "testing" "git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/model" ) // TestWorkingMemory_RecordSession_Sliding_滑窗 证明会话窗口与默认窗口一样 // 是「最近 N 条」的滑窗,且只保留窗口内的最后 max 条。 func TestWorkingMemory_RecordSession_Sliding_滑窗(t *testing.T) { t.Parallel() w := NewWorkingMemory(3) msgs := make([]model.Message, 0, 5) for i := 0; i < 5; i++ { msgs = append(msgs, model.TextMessage(model.RoleUser, string(rune('a'+i)))) } w.RecordSession("sess-1", msgs) win := w.SessionWindow("sess-1") if len(win) != 3 { t.Fatalf("会话窗口长度 = %d, want 3(滑窗)", len(win)) } for i, want := range []string{"c", "d", "e"} { if got := win[i].Content[0].Text; got != want { t.Errorf("窗口[%d] = %q, want %q", i, got, want) } } // 摘要由该会话窗口推导,因此非空且带消息条数。 summary := w.SummarizeSession("sess-1") if !strings.Contains(summary, "3 条消息") { t.Fatalf("SummarizeSession = %q, want 含 3 条消息", summary) } if w.Summarize() != "" { t.Fatalf("默认窗口被会话写入污染: %q", w.Summarize()) } // 返回的切片与内部状态脱离。 win[0].Content[0].Text = "mutated" if again := w.SessionWindow("sess-1"); again[0].Content[0].Text != "c" { t.Fatalf("SessionWindow 返回了共享切片: %q", again[0].Content[0].Text) } } // TestWorkingMemory_SessionWindow_IsolatedPerSession_会话隔离 证明窗口 // 按会话 id 隔离 —— 这是群聊/陌生人的工作记忆不混进主人会话的前提。 func TestWorkingMemory_SessionWindow_IsolatedPerSession_会话隔离(t *testing.T) { t.Parallel() w := NewWorkingMemory(10) w.RecordSession("web:owner", []model.Message{model.TextMessage(model.RoleUser, "主人的话")}) w.RecordSession("sess-group", []model.Message{model.TextMessage(model.RoleUser, "群里的话")}) owner := w.SessionWindow("web:owner") if len(owner) != 1 || owner[0].Content[0].Text != "主人的话" { t.Fatalf("owner 窗口 = %+v, want 只有主人的话(会话隔离)", owner) } group := w.SessionWindow("sess-group") if len(group) != 1 || group[0].Content[0].Text != "群里的话" { t.Fatalf("group 窗口 = %+v, want 只有群里的话", group) } if s := w.SummarizeSession("web:owner"); strings.Contains(s, "群里的话") { t.Fatalf("owner 摘要混入了别的会话内容: %q", s) } if got := w.SessionWindow("never-seen"); len(got) != 0 { t.Fatalf("未知会话窗口 = %+v, want 空", got) } if got := w.SummarizeSession("never-seen"); got != "" { t.Fatalf("未知会话摘要 = %q, want 空", got) } // 空会话 id 是空操作:无键的写入无从隔离,绝不落到默认窗口。 w.RecordSession("", []model.Message{model.TextMessage(model.RoleUser, "无键写入")}) if got := w.Summarize(); got != "" { t.Fatalf("空会话 id 的写入污染了默认窗口: %q", got) } } // TestWorkingMemory_Sessions_Bounded_会话窗口有界 证明保留的会话窗口数 // 有上限:会话数随渠道消息无界增长,最久未用的窗口被淘汰,内存不失控。 func TestWorkingMemory_Sessions_Bounded_会话窗口有界(t *testing.T) { t.Parallel() w := NewWorkingMemory(10) for i := 0; i < maxWorkingSessions; i++ { w.RecordSession(sessionName(i), []model.Message{model.TextMessage(model.RoleUser, "x")}) } // 超出上限后再写入一个新会话:最久未用的第一个会话被淘汰。 w.RecordSession("overflow", []model.Message{model.TextMessage(model.RoleUser, "y")}) if got := len(w.SessionWindow(sessionName(0))); got != 0 { t.Fatalf("最久未用的会话窗口仍在(%d 条): 会话窗口数没有上限", got) } if got := len(w.SessionWindow("overflow")); got != 1 { t.Fatalf("新会话窗口 = %d 条, want 1", got) } if got := len(w.SessionWindow(sessionName(maxWorkingSessions - 1))); got != 1 { t.Fatalf("最近使用的会话窗口被误淘汰: %d 条", got) } } // sessionName 生成第 i 个会话 id。 func sessionName(i int) string { return "sess-" + string(rune('A'+i%26)) + string(rune('a'+i/26)) }