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.
136 lines
4.0 KiB
Go
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
|
|
}
|