package oltp
import "testing"
// A target nobody has named answers zero — "no chat about this" is an
// answer, not an error.
func TestChatRefsEmpty(t *testing.T) {
db := testDB(t)
count, seq, at, err := db.ChatRefs("r", "abc")
if err != nil {
t.Fatal(err)
}
if count != 0 || seq != 0 || at != "" {
t.Fatalf("empty target = (%d, %d, %q)", count, seq, at)
}
}
func TestChatRefsCountAndLatest(t *testing.T) {
db := testDB(t)
for _, r := range []struct {
seq uint64
at string
}{{4, "2026-08-29T10:00:00Z"}, {9, "2026-08-29T11:00:00Z"}} {
if err := db.RecordChatRef("r", "abc", r.seq, r.at); err != nil {
t.Fatal(err)
}
}
// A replay is idempotent on (repo, target, seq).
if err := db.RecordChatRef("r", "abc", 4, "2026-08-29T10:00:00Z"); err != nil {
t.Fatal(err)
}
count, seq, at, err := db.ChatRefs("r", "abc")
if err != nil {
t.Fatal(err)
}
if count != 2 || seq != 9 || at != "2026-08-29T11:00:00Z" {
t.Fatalf("refs = (%d, %d, %q), want (2, 9, 11:00)", count, seq, at)
}
// Another repository's rows are another repository's.
if c, _, _, _ := db.ChatRefs("other", "abc"); c != 0 {
t.Fatalf("repo isolation broken: %d", c)
}
}
// The rows live and die with the channel window: once the window's
// first seq passes a row, the row goes with the message.
func TestPruneChatRefsFollowsTheWindow(t *testing.T) {
db := testDB(t)
for _, seq := range []uint64{2, 5, 8} {
if err := db.RecordChatRef("r", "abc", seq, "2026-08-29T10:00:00Z"); err != nil {
t.Fatal(err)
}
}
if err := db.PruneChatRefs("r", 5); err != nil {
t.Fatal(err)
}
count, seq, _, err := db.ChatRefs("r", "abc")
if err != nil {
t.Fatal(err)
}
if count != 2 || seq != 8 {
t.Fatalf("after prune = (%d, %d), want (2, 8)", count, seq)
}
// A zero first-seq is "the window says nothing" and must delete
// nothing — an empty Bounds answer must not wipe the table.
if err := db.PruneChatRefs("r", 0); err != nil {
t.Fatal(err)
}
if c, _, _, _ := db.ChatRefs("r", "abc"); c != 2 {
t.Fatalf("an unknown window pruned rows: %d", c)
}
}
// The reply count is a cheap row, not a channel scan: a card that
// wants "3 replies" must not read the log to the tip to learn it.
func TestChatReplyCountsAndPrunes(t *testing.T) {
db := testDB(t)
for _, r := range []struct{ ref, seq uint64 }{{4, 5}, {4, 6}, {9, 10}} {
if err := db.RecordChatReply("r", r.ref, r.seq); err != nil {
t.Fatal(err)
}
}
// A replay is idempotent on (repo, ref_seq, seq).
if err := db.RecordChatReply("r", 4, 5); err != nil {
t.Fatal(err)
}
if n, err := db.ChatReplyCount("r", 4); err != nil || n != 2 {
t.Fatalf("replies to 4 = (%d, %v), want 2", n, err)
}
if n, _ := db.ChatReplyCount("other", 4); n != 0 {
t.Fatalf("repo isolation broken: %d", n)
}
// A deleted reply stops counting: the card and the page agree
// because they read the same row, and a tombstoned message is not
// a reply anybody can see.
if err := db.DropChatReply("r", 5); err != nil {
t.Fatal(err)
}
if n, _ := db.ChatReplyCount("r", 4); n != 1 {
t.Fatalf("after delete = %d, want 1", n)
}
// Rows die with the window, exactly like chat_ref.
if err := db.PruneChatReplies("r", 10); err != nil {
t.Fatal(err)
}
if n, _ := db.ChatReplyCount("r", 4); n != 0 {
t.Fatalf("prune left %d rows behind", n)
}
if n, _ := db.ChatReplyCount("r", 9); n != 1 {
t.Fatalf("prune took a row inside the window: %d", n)
}
if err := db.PruneChatReplies("r", 0); err != nil {
t.Fatal(err)
}
if n, _ := db.ChatReplyCount("r", 9); n != 1 {
t.Fatalf("an unknown window pruned rows: %d", n)
}
}