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 }