repos
/ orchard main

orchard

mirror

Every 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

3.2 KB · 100 lines · Go Raw History
 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}