orchard
mirrorEvery site I host, in one repo, along with the Cloudflare Tunnel and Caddy that front them. It's all Go, Vite, and SQLite, and it runs on a desktop at home with nothing listening on an inbound port.
blogbuncaddycloudflare-tunneldockergogolanghomelabhtml-templatemonorepoself-hostedseosqlitestatic-sitetypstuptime-monitoringviteweb-analytics
1package main
2
3import (
4 "path/filepath"
5 "strings"
6 "testing"
7)
8
9// A store with two conversations that share a word, so ranking has something to
10// get wrong.
11func searchStore(t *testing.T) *Store {
12 t.Helper()
13 s, err := OpenStore(filepath.Join(t.TempDir(), "chat.db"))
14 if err != nil {
15 t.Fatal(err)
16 }
17 t.Cleanup(func() { s.Close() })
18
19 rifles, _ := s.NewConversation("Cheap bolt action rimfire rifles")
20 _ = s.Append(rifles, Stored{Role: RoleUser, Content: "what are the most common cheap bolt action 22 rifles for plinking"})
21 _ = s.Append(rifles, Stored{Role: RoleAssistant, Content: "The Savage Mark II and the Ruger American Rimfire are the two that show up everywhere."})
22
23 food, _ := s.NewConversation("Bojangles nutrition")
24 _ = s.Append(food, Stored{Role: RoleUser, Content: "is the dirty rice better than the pinto beans"})
25 _ = s.Append(food, Stored{Role: RoleAssistant, Content: "The pinto beans have 7g of protein and no saturated fat, so they beat the dirty rice."})
26 return s
27}
28
29func TestSearchHistoryFindsTheRightExchange(t *testing.T) {
30 s := searchStore(t)
31
32 hits, err := s.SearchHistory("pinto beans protein", 5)
33 if err != nil {
34 t.Fatal(err)
35 }
36 if len(hits) == 0 {
37 t.Fatal("a question that was answered found nothing")
38 }
39 if !strings.Contains(hits[0].Answer, "pinto beans") {
40 t.Errorf("the top hit is %q, want the beans exchange", hits[0].Answer)
41 }
42 // A hit carries what was asked as well as what was said, since an answer on
43 // its own reads as an assertion from nowhere.
44 if !strings.Contains(hits[0].Question, "dirty rice") {
45 t.Errorf("the hit has no question on it: %q", hits[0].Question)
46 }
47 if hits[0].Title == "" {
48 t.Error("the hit does not say which conversation it came from")
49 }
50}
51
52// The stemmer is what makes a real question reach a stored one. Without it
53// "rifle" never finds "rifles", which is the same failure memory hit first.
54func TestSearchHistoryStems(t *testing.T) {
55 s := searchStore(t)
56 hits, err := s.SearchHistory("rifle", 5)
57 if err != nil {
58 t.Fatal(err)
59 }
60 if len(hits) == 0 {
61 t.Fatal(`"rifle" did not reach "rifles"`)
62 }
63 if !strings.Contains(hits[0].Title, "rimfire") {
64 t.Errorf("top hit is %q, want the rifles conversation", hits[0].Title)
65 }
66}
67
68// Ten rows off one long thread is the same answer ten times, and it crowds out
69// every other conversation that matched.
70func TestSearchHistoryReturnsOneHitPerConversation(t *testing.T) {
71 s := searchStore(t)
72 id, _ := s.NewConversation("Rifles again")
73 for i := 0; i < 6; i++ {
74 _ = s.Append(id, Stored{Role: RoleUser, Content: "more about rifles and rifles"})
75 _ = s.Append(id, Stored{Role: RoleAssistant, Content: "still about rifles"})
76 }
77 hits, err := s.SearchHistory("rifles", 10)
78 if err != nil {
79 t.Fatal(err)
80 }
81 seen := map[string]int{}
82 for _, h := range hits {
83 seen[h.ConvID]++
84 }
85 for id, n := range seen {
86 if n > 1 {
87 t.Errorf("conversation %s came back %d times", id, n)
88 }
89 }
90}
91
92// A query of nothing but stopwords has no subject in it, and searching on that
93// would return whatever is newest and present it as a match.
94func TestSearchHistoryRefusesAQueryWithNoSubject(t *testing.T) {
95 s := searchStore(t)
96 if _, err := s.SearchHistory("what is the it", 5); err == nil {
97 t.Error("a query with no searchable word was accepted")
98 }
99}