Skip to content
quarry Artifact [1a97cd65c3]
Login

Artifact [1a97cd65c3]

Artifact 1a97cd65c37f464c70db1c26cb4bf10e92ab153148bd884308573e38dadf2639:


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)
	}
}