package search import ( "context" "path/filepath" "strings" "testing" "time" "git.wntrmute.dev/kyle/sift/internal/dcache" ) type fakeSrc struct { name string hits []Hit warn string delay time.Duration credits int } func (f fakeSrc) Name() string { return f.name } func (f fakeSrc) Search(ctx context.Context, query string, k int) SourceOut { if f.delay > 0 { select { case <-time.After(f.delay): case <-ctx.Done(): return SourceOut{Warn: f.name + ": timeout"} } } return SourceOut{Hits: f.hits, Warn: f.warn, Credits: f.credits} } type stubEmb struct { ok bool reason string } func (s stubEmb) Embed(ctx context.Context, inputs []string) ([][]float64, error) { return nil, context.Canceled } func (s stubEmb) Acquire(ctx context.Context) (bool, string) { return s.ok, s.reason } func (s stubEmb) Release() {} func TestFanInPartial(t *testing.T) { e := &Engine{ Sources: []Source{ fakeSrc{name: NameSearxNG, hits: []Hit{{URL: "https://ok.example/a", Title: "ok", Snippet: "x", Source: NameSearxNG}}}, fakeSrc{name: NameBraveLLM, warn: "brave_llm: 500"}, fakeSrc{name: NameTavily, delay: 2 * time.Second}, }, Timeout: 50 * time.Millisecond, DefaultK: 8, RerankOn: false, } r := e.Run(context.Background(), "q", 8, false, "titan", dcache.ClassFleet) if len(r.Hits) != 1 || r.Hits[0].URL != "https://ok.example/a" { t.Fatalf("hits %+v", r.Hits) } joined := strings.Join(r.Warnings, " ") if !strings.Contains(joined, "500") || !strings.Contains(joined, "timeout") { t.Fatalf("warnings %v", r.Warnings) } } func TestCacheClassAndHour(t *testing.T) { db, err := dcache.Open(filepath.Join(t.TempDir(), "c.db"), 1<<20, nil) if err != nil { t.Fatal(err) } defer db.Close() now := time.Date(2026, 9, 23, 10, 0, 0, 0, time.UTC) src := fakeSrc{name: NameSearxNG, hits: []Hit{{URL: "https://ok.example/a", Title: "ok", Snippet: "x", Source: NameSearxNG}}} e := &Engine{Sources: []Source{src}, DefaultK: 8, Cache: db, RerankOn: false, Now: func() time.Time { return now }} r1 := e.Run(context.Background(), "Hello World", 8, false, "titan", dcache.ClassFleet) if r1.Cached { t.Fatal("first miss") } r2 := e.Run(context.Background(), "hello world", 8, false, "titan", dcache.ClassFleet) if !r2.Cached { t.Fatal("norm/hour hit") } r3 := e.Run(context.Background(), "hello world", 8, false, "gs", dcache.ClassRestricted) if r3.Cached { t.Fatal("restricted must miss fleet cache") } e.Now = func() time.Time { return now.Add(time.Hour) } r4 := e.Run(context.Background(), "hello world", 8, false, "titan", dcache.ClassFleet) if r4.Cached { t.Fatal("next hour must miss") } } func TestNoCacheOnDegraded(t *testing.T) { db, err := dcache.Open(filepath.Join(t.TempDir(), "c.db"), 1<<20, nil) if err != nil { t.Fatal(err) } defer db.Close() e := &Engine{ Sources: []Source{ fakeSrc{name: NameSearxNG, hits: []Hit{{URL: "https://ok.example/a", Source: NameSearxNG}}}, fakeSrc{name: NameBraveLLM, warn: "brave_llm: 500"}, }, DefaultK: 8, Cache: db, RerankOn: false, } e.Run(context.Background(), "q", 8, false, "titan", dcache.ClassFleet) r := e.Run(context.Background(), "q", 8, false, "titan", dcache.ClassFleet) if r.Cached { t.Fatal("degraded cached") } } func TestRerankFallbackStill200(t *testing.T) { e := &Engine{ Sources: []Source{fakeSrc{name: NameSearxNG, hits: []Hit{{URL: "https://ok.example/a", Source: NameSearxNG}}}}, DefaultK: 8, RerankOn: true, Embedder: stubEmb{ok: false, reason: "queue_full"}, } r := e.Run(context.Background(), "q", 8, true, "titan", dcache.ClassFleet) if len(r.Hits) != 1 { t.Fatal(r.Hits) } if !strings.Contains(strings.Join(r.Warnings, " "), "rerank fell back") { t.Fatalf("%v", r.Warnings) } } func TestDailyCapSkipsTavily(t *testing.T) { db, err := dcache.Open(filepath.Join(t.TempDir(), "c.db"), 1<<20, nil) if err != nil { t.Fatal(err) } defer db.Close() var n int tav := fakeSrc{name: NameTavily, hits: []Hit{{URL: "https://t.example/a", Source: NameTavily}}, credits: 1} e := &Engine{Sources: []Source{tav}, DefaultK: 8, Cache: db, TavilyCap: 1, RerankOn: false} e.Run(context.Background(), "q1", 8, false, "titan", dcache.ClassFleet) r := e.Run(context.Background(), "q2", 8, false, "titan", dcache.ClassFleet) for _, w := range r.Warnings { if strings.Contains(w, "daily cap") { n++ } } if n != 1 { t.Fatalf("warnings %v", r.Warnings) } } func TestDedupeLongerSnippet(t *testing.T) { in := []Hit{ {URL: "https://Ex.com/a#x", Snippet: "aa", Source: NameSearxNG}, {URL: "https://ex.com/a", Snippet: "aabbcc", Source: NameTavily}, } out := Dedupe(in) if len(out) != 1 || out[0].Source != NameSearxNG || out[0].Snippet != "aabbcc" { t.Fatalf("%+v", out) } }