package index
import (
"path/filepath"
"testing"
)
// searchConsistent runs FTS5's own integrity check against the external
// content table: the check Open runs, which production reported as
// "database disk image is malformed" after every restart on tcl and tk.
func searchConsistent(t *testing.T, ix *Index, label string) {
t.Helper()
if _, err := ix.db.Exec(`INSERT INTO search_fts(search_fts, rank) VALUES('integrity-check', 1)`); err != nil {
t.Fatalf("%s: search index disagrees with search_doc: %v", label, err)
}
}
// A check-in folded twice must leave exactly one search entry. The fold
// used INSERT OR REPLACE, whose delete fires no trigger without
// recursive_triggers, so the first text stayed behind in search_fts.
func TestRefoldingACheckinKeepsSearchConsistent(t *testing.T) {
ix, err := Open(filepath.Join(t.TempDir(), "index.db"))
if err != nil {
t.Fatal(err)
}
defer ix.Close()
rec := CheckinRecord{Hash: "aaaa000000000000000000000000000000000000000000000000000000000001",
Date: "2026-09-15T00:00:00", User: "dan", Comment: "first comment"}
if err := ix.FoldCheckins([]CheckinRecord{rec}); err != nil {
t.Fatal(err)
}
searchConsistent(t, ix, "first fold")
rec.Comment = "second comment"
if err := ix.FoldCheckins([]CheckinRecord{rec}); err != nil {
t.Fatal(err)
}
searchConsistent(t, ix, "refold")
var n int
if err := ix.db.QueryRow(`SELECT count(*) FROM search_fts WHERE search_fts MATCH 'first'`).Scan(&n); err != nil {
t.Fatal(err)
}
if n != 0 {
t.Fatalf("the replaced comment is still searchable (%d hits)", n)
}
}
// A ticket's search doc is rewritten on every change to the ticket, so
// this is the path a busy tracker drifted on.
func TestRefoldingATicketKeepsSearchConsistent(t *testing.T) {
ix := openIndex(t)
id := "ab" + "cdabcdabcdabcdabcdabcdabcdabcdabcdabcdab"
foldTicket(t, ix, "tk1", id, "2026-01-01T10:00:00", "title", "zebraword first title", "status", "Open")
searchConsistent(t, ix, "first change")
foldTicket(t, ix, "tk2", id, "2026-01-02T10:00:00", "title", "second title")
searchConsistent(t, ix, "second change")
var n int
if err := ix.db.QueryRow(`SELECT count(*) FROM search_fts WHERE search_fts MATCH 'zebraword'`).Scan(&n); err != nil {
t.Fatal(err)
}
if n != 0 {
t.Fatalf("the replaced title is still searchable (%d hits)", n)
}
}