Artifact 7a544250c846acd781a357f5260d46538034c6ae2dfacd10283b7e64f7fb3b45:
- File
internal/index/reset_test.go
— part of check-in
[7cfce7761b]
at
2026-08-22 16:46:10
on branch ticket-list
— tickets: report filters, and chips that mute what every row shares
Six report axes as native selects in one GET form; the URL is the report, so a listing is shareable and scriptable with no query language. Counts run through the same filter and every header link preserves it.
Chips mute a value nearly every listed row shares — "Fixed" on 34 of 34 closed rows distinguished nothing. (user: dmestas, size: 3775) [annotate] [blame] [check-ins using] [more...]
package index import ( "strings" "testing" ) // Reset is the remedy the schema-version stamp points at, and a version // bump means THE SHAPE CHANGED — so a Reset that only deletes rows is a // remedy that cannot perform the one repair it exists for. That lie // caused the 2026-08-19 outage: a bump wiped every index, the refold // hit "no such column", and the pipeline ground against a table Reset // had emptied but never widened. // // The contract pinned here: whatever shape the file is in, Reset makes // it THIS code's shape, ready to refold. func TestResetRepairsShape(t *testing.T) { ix := openIndex(t) id := strings.Repeat("ab", 20) foldTicket(t, ix, "rs1", id, "2026-01-01T10:00:00", "title", "before", "status", "Open", "priority", "High") // The file now carries an OLDER shape, the way any index on disk // does the moment code adds a column. if _, err := ix.db.Exec(`ALTER TABLE ticket DROP COLUMN priority`); err != nil { t.Fatal(err) } if err := ix.Reset(); err != nil { t.Fatal(err) } // Refolding into the reset index must land, column included. foldTicket(t, ix, "rs2", id, "2026-01-02T10:00:00", "title", "after", "status", "Open", "priority", "Immediate") list, _, err := ix.TicketSummaries(10, "", TicketFilter{State: "open"}) if err != nil { t.Fatal(err) } if len(list) != 1 || list[0].Priority != "Immediate" { t.Fatalf("refold after Reset = %+v, want the ticket with its priority", list) } // And search still works: the fts table is derived state like any // other, and a Reset that leaves a stale corpus behind serves ghost // results for content the index no longer holds. if n, err := ix.SearchCount("before", "ticket"); err != nil || n != 0 { t.Fatalf("the pre-reset corpus survived (n=%d err=%v)", n, err) } if n, err := ix.SearchCount("after", "ticket"); err != nil || n != 1 { t.Fatalf("the refolded ticket is not searchable (n=%d err=%v)", n, err) } } // The search index is a cache over search_doc, and quarry adopts zit's // cache contract: verified on open, and any mismatch is a MISS that // rebuilds — never an error a reader sees. The fleet hit this live: // fts shadow state that had drifted somewhere in an index's history // (unsynced deletes reuse rowids) made every search on that repo a 500 // with "database disk image is malformed", while the content table // beside it was perfectly healthy. func TestOpenHealsDriftedSearchIndex(t *testing.T) { dir := t.TempDir() + "/idx.db" ix, err := Open(dir) if err != nil { t.Fatal(err) } foldTicket(t, ix, "fh1", strings.Repeat("aa", 20), "2026-01-01T10:00:00", "title", "findable pelican", "status", "Open") // Desync the way history did: a delete that bypasses the sync // triggers, then a reinsert that reuses the rowid for other text. for _, stmt := range []string{ `DROP TRIGGER search_doc_ad`, `DELETE FROM search_doc`, `CREATE TRIGGER search_doc_ad AFTER DELETE ON search_doc BEGIN INSERT INTO search_fts (search_fts, rowid, text) VALUES ('delete', old.rowid, old.text); END`, } { if _, err := ix.db.Exec(stmt); err != nil { t.Fatal(err) } } ix.Close() ix2, err := Open(dir) if err != nil { t.Fatalf("Open refused a repairable index: %v", err) } defer ix2.Close() foldTicket(t, ix2, "fh2", strings.Repeat("bb", 20), "2026-01-02T10:00:00", "title", "findable flamingo", "status", "Open") hits, err := ix2.Search("findable", "", 10) if err != nil { t.Fatalf("search after heal: %v", err) } if len(hits) != 1 { t.Fatalf("got %d hits, want exactly the refolded doc: %+v", len(hits), hits) } // The ghost must be gone, not merely off the first page. if n, _ := ix2.SearchCount("pelican", ""); n != 0 { t.Fatalf("a deleted document still matches after the heal: %d", n) } }