package history import ( "context" "fmt" "testing" ) // TestRecentKeepsNewestOnTimestampTies 钉住「同一毫秒内的多条消息」时 Recent // 仍返回**最新**的 n 条且顺序等于真实插入顺序(裁决 13.4.5:上下文续接窗口 // 必须最新、完整、一致)。 // // 为什么同毫秒是常态而不是边角:Windows 上 time.Now() 的步长约 0.44ms // (工作小结「平台坑位」),用户连发多条消息时 created_at 大量并列; // design/15 §2.1 的「忙时连发多条」场景本身就会连续写入。若 Recent 的并列 // 顺序取「插入更早的优先」,续接窗口就会**丢掉刚发出的最新消息**,而 // 保留更旧的——上下文既不最新也不完整。 func TestRecentKeepsNewestOnTimestampTies(t *testing.T) { t.Parallel() st := newStore(t) ctx := context.Background() const session = "sess-ties" const ties = 25 const window = 20 // 全部 25 条共用同一个 created_at:只有插入顺序(rowid)能区分先后。 for i := 0; i < ties; i++ { m := Message{ SessionID: session, Role: RoleUser, Content: fmt.Sprintf("m%02d", i), CreatedAt: 1_700_000_000_000, } if err := st.Append(ctx, m); err != nil { t.Fatalf("append %d: %v", i, err) } } got, err := st.Recent(ctx, session, window) if err != nil { t.Fatalf("recent: %v", err) } if len(got) != window { t.Fatalf("len = %d, want %d", len(got), window) } // 最新优先被保住的窗口 = 最后插入的 window 条,按插入顺序(最旧在前)。 for i, m := range got { want := fmt.Sprintf("m%02d", ties-window+i) if m.Content != want { t.Fatalf("recent[%d] = %q, want %q (窗口必须取最新插入的那几条)", i, m.Content, want) } } if last := got[len(got)-1].Content; last != fmt.Sprintf("m%02d", ties-1) { t.Fatalf("recent tail = %q, want the newest inserted message", last) } } // TestRecentOrdersMixedTiesChronologically 证明并列与非并列混在一起时, // 窗口整体仍是「时间升序 + 同毫秒按插入顺序」,即上下文时序与真实对话一致。 func TestRecentOrdersMixedTiesChronologically(t *testing.T) { t.Parallel() st := newStore(t) ctx := context.Background() const session = "sess-mixed" type row struct { content string at int64 } rows := []row{ {"a0", 100}, {"a1", 100}, {"a2", 100}, {"b0", 200}, {"b1", 200}, {"c0", 300}, } for _, r := range rows { if err := st.Append(ctx, Message{SessionID: session, Role: RoleUser, Content: r.content, CreatedAt: r.at}); err != nil { t.Fatalf("append %s: %v", r.content, err) } } got, err := st.Recent(ctx, session, len(rows)) if err != nil { t.Fatalf("recent: %v", err) } if len(got) != len(rows) { t.Fatalf("len = %d, want %d", len(got), len(rows)) } for i, want := range rows { if got[i].Content != want.content { t.Fatalf("recent[%d] = %q, want %q (同毫秒必须按插入顺序)", i, got[i].Content, want.content) } } }