Files
kyle 8b26776cdd feat(search): P3.1 filters and multi-query merge
Native include/exclude domains, time_range, topic, exact_match, min_score,
and up to 4 queries fused with RRF. Honesty notes stay off the Warn/fail
path. Tavily keeps include_answer false and sets auto_parameters false.
Cache key is canonical JSON. CLI flags and skill recipe included.
2026-09-29 15:30:19 -07:00

136 lines
4.0 KiB
Go

package dcache
import (
"os"
"path/filepath"
"testing"
"time"
)
func TestHitMissAndNormalise(t *testing.T) {
d, err := Open(filepath.Join(t.TempDir(), "c.db"), 1<<20, nil)
if err != nil {
t.Fatal(err)
}
defer d.Close()
k1 := DigestKey("abc", "What Share?", "m", PromptVersion, ClassFleet)
k2 := DigestKey("abc", "what share?", "m", PromptVersion, ClassFleet)
if k1 != k2 {
t.Fatal("normalise")
}
if _, ok := d.GetDigest(k1); ok {
t.Fatal("miss")
}
d.PutDigest(k1, "forty two", 1)
h, ok := d.GetDigest(k2)
if !ok || h.Text != "forty two" {
t.Fatalf("%v %v", ok, h)
}
k3 := DigestKey("abd", "what share?", "m", PromptVersion, ClassFleet)
if _, ok := d.GetDigest(k3); ok {
t.Fatal("changed content must miss")
}
}
func TestCallerClassPartitions(t *testing.T) {
d, err := Open(filepath.Join(t.TempDir(), "c.db"), 1<<20, nil)
if err != nil {
t.Fatal(err)
}
defer d.Close()
fleet := DigestKey("h", "q", "m", PromptVersion, ClassFleet)
restr := DigestKey("h", "q", "m", PromptVersion, ClassRestricted)
if fleet == restr {
t.Fatal("classes must not share a key")
}
d.PutDigest(restr, "secret", 1)
if _, ok := d.GetDigest(fleet); ok {
t.Fatal("fleet must not hit restricted digest")
}
if h, ok := d.GetDigest(restr); !ok || h.Text != "secret" {
t.Fatalf("restricted hit %v %+v", ok, h)
}
if EmbedKey("h", 0, "e", ClassFleet) == EmbedKey("h", 0, "e", ClassRestricted) {
t.Fatal("embed class")
}
if QuestionEmbedKey("q", "e", ClassFleet) == QuestionEmbedKey("q", "e", ClassRestricted) {
t.Fatal("question embed class")
}
}
func TestPromptVersionInvalidates(t *testing.T) {
k1 := DigestKey("h", "q", "m", "1", ClassFleet)
k2 := DigestKey("h", "q", "m", "2", ClassFleet)
if k1 == k2 {
t.Fatal("version")
}
}
func TestNoContextTable(t *testing.T) {
d, err := Open(filepath.Join(t.TempDir(), "c.db"), 1<<20, nil)
if err != nil {
t.Fatal(err)
}
defer d.Close()
var n int
_ = d.sql.QueryRow(`SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='context'`).Scan(&n)
if n != 0 {
t.Fatal("must not store context")
}
}
func TestEvictUnderCap(t *testing.T) {
d, err := Open(filepath.Join(t.TempDir(), "c.db"), 200, nil)
if err != nil {
t.Fatal(err)
}
defer d.Close()
for i := 0; i < 20; i++ {
d.PutDigest(DigestKey("h", string(rune('a'+i)), "m", "1", ClassFleet), "xxxxxxxxxxxxxxxxxxxx", 1)
}
if d.Size() > 400 {
t.Fatalf("size %d", d.Size())
}
if d.Evictions == 0 {
t.Fatal("want evictions")
}
}
func TestSearchKeyPartitions(t *testing.T) {
now := time.Date(2026, 9, 23, 10, 0, 0, 0, time.UTC)
a := SearchKey("Hello", []string{"tavily", "searxng"}, 8, now, ClassFleet)
b := SearchKey("hello", []string{"searxng", "tavily"}, 8, now, ClassFleet)
if a != b {
t.Fatal("norm/sources order")
}
if SearchKey("hello", []string{"searxng"}, 8, now, ClassRestricted) == a {
t.Fatal("class")
}
if SearchKey("hello", []string{"searxng", "tavily"}, 8, now.Add(time.Hour), ClassFleet) == a {
t.Fatal("hour")
}
if EncodeSearchKey(SearchKeyInput{Queries: []string{"hello"}, Sources: []string{"searxng"}, K: 8, Hour: now, CallerClass: ClassFleet}) == EncodeSearchKey(SearchKeyInput{Queries: []string{"hello"}, Sources: []string{"searxng"}, K: 8, Hour: now, CallerClass: ClassFleet, IncludeDomains: []string{"a.com"}}) {
t.Fatal("include_domains")
}
if EncodeSearchKey(SearchKeyInput{Queries: []string{"hello"}, Sources: []string{"searxng"}, K: 8, Hour: now, CallerClass: ClassFleet}) != EncodeSearchKey(SearchKeyInput{Queries: []string{"hello"}, Sources: []string{"searxng"}, K: 8, Hour: now, CallerClass: ClassFleet, Topic: "general"}) {
t.Fatal("topic general")
}
}
func TestCorruptDBBypass(t *testing.T) {
p := filepath.Join(t.TempDir(), "c.db")
if err := os.WriteFile(p, []byte("not a database"), 0o600); err != nil {
t.Fatal(err)
}
d, err := Open(p, 1<<20, nil)
if err == nil {
_ = d.Close()
// Open may succeed then query fails — GetDigest must miss not panic
if _, ok := d.GetDigest("x"); ok {
t.Fatal("corrupt hit")
}
return
}
// open failed: caller warns and bypasses
}