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
9func memStore(t *testing.T) *Store {
10 t.Helper()
11 s, err := OpenStore(filepath.Join(t.TempDir(), "chat.db"))
12 if err != nil {
13 t.Fatal(err)
14 }
15 t.Cleanup(func() { _ = s.Close() })
16 return s
17}
18
19func TestAFactIsStoredOnceHoweverOftenProposed(t *testing.T) {
20 s := memStore(t)
21 for i := 0; i < 3; i++ {
22 if _, err := s.AddFact("Isaac drinks his coffee black"); err != nil {
23 t.Fatal(err)
24 }
25 }
26 facts, err := s.Facts()
27 if err != nil {
28 t.Fatal(err)
29 }
30 if len(facts) != 1 {
31 t.Fatalf("got %d facts, want 1", len(facts))
32 }
33}
34
35// Retrieval is the whole value of this. A fact that does not surface when the
36// question touches it may as well not be stored.
37func TestRelevantFindsWhatTheQuestionTouches(t *testing.T) {
38 s := memStore(t)
39 for _, f := range []string{
40 "Isaac drinks his coffee black",
41 "Isaac prefers a hammock to a tent",
42 "Isaac runs every site he owns from a desktop behind a Cloudflare tunnel",
43 "Isaac camps most weekends through the autumn",
44 } {
45 if _, err := s.AddFact(f); err != nil {
46 t.Fatal(err)
47 }
48 }
49
50 got := s.Relevant("what tent should I take camping this year", 3)
51 if len(got) == 0 {
52 t.Fatal("nothing matched a question about camping")
53 }
54 joined := ""
55 for _, f := range got {
56 joined += f.Text + " | "
57 }
58 if !strings.Contains(joined, "hammock") || !strings.Contains(joined, "camps") {
59 t.Errorf("the two facts that matter did not come back:\n%s", joined)
60 }
61 if strings.Contains(joined, "coffee") {
62 t.Errorf("an unrelated fact was pulled in:\n%s", joined)
63 }
64}
65
66// Without stopwords "my" and "the" match everything, so a question about
67// anything drags in the whole table.
68func TestRelevantIgnoresWordsThatMatchEverything(t *testing.T) {
69 s := memStore(t)
70 if _, err := s.AddFact("Isaac prefers the aisle seat"); err != nil {
71 t.Fatal(err)
72 }
73 if got := s.Relevant("what is the weather", 5); len(got) != 0 {
74 t.Errorf("matched on filler words: %+v", got)
75 }
76}
77
78func TestRelevantCountsUses(t *testing.T) {
79 s := memStore(t)
80 if _, err := s.AddFact("Isaac uses bun rather than npm"); err != nil {
81 t.Fatal(err)
82 }
83 s.Relevant("should I use bun here", 3)
84 facts, _ := s.Facts()
85 if facts[0].Used != 1 {
86 t.Errorf("used = %d, want 1", facts[0].Used)
87 }
88}
89
90// A model that ignores the rules once must not be able to write a credential
91// into a file that is read into every future turn.
92func TestCredentialsAreRefusedWhateverTheModelSays(t *testing.T) {
93 for _, bad := range []string{
94 "Isaac's password is hunter2",
95 "The api key for the gateway is orch-EXAMPLEEXAMPLEEXAMPLEEXAMPLEexample00",
96 "Isaac's token is eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9",
97 "His ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQ key",
98 } {
99 if !looksSecret(bad) {
100 t.Errorf("allowed through: %q", bad)
101 }
102 }
103 for _, fine := range []string{
104 "Isaac drinks his coffee black",
105 "Isaac prefers a hammock to a tent",
106 "Isaac runs Debian 13 trixie in his development container",
107 } {
108 if looksSecret(fine) {
109 t.Errorf("refused an ordinary fact: %q", fine)
110 }
111 }
112}
113
114func TestApplyChangesRefusesAnIdThatIsNotThere(t *testing.T) {
115 s := memStore(t)
116 id, err := s.AddFact("Isaac drinks his coffee black")
117 if err != nil {
118 t.Fatal(err)
119 }
120 existing, _ := s.Facts()
121 site := &site{store: s}
122
123 applied := site.applyChanges([]memChange{
124 {Op: "replace", ID: id + 999, Fact: "something else"},
125 {Op: "delete", ID: id + 998},
126 }, existing)
127 if len(applied) != 0 {
128 t.Errorf("acted on ids that do not exist: %+v", applied)
129 }
130 if facts, _ := s.Facts(); len(facts) != 1 {
131 t.Errorf("the real fact was disturbed: %+v", facts)
132 }
133}
134
135func TestApplyChangesReplacesRatherThanDuplicating(t *testing.T) {
136 s := memStore(t)
137 id, _ := s.AddFact("Isaac's car is a Subaru")
138 existing, _ := s.Facts()
139 site := &site{store: s}
140
141 site.applyChanges([]memChange{{Op: "replace", ID: id, Fact: "Isaac's car is a Toyota"}}, existing)
142
143 facts, _ := s.Facts()
144 if len(facts) != 1 {
145 t.Fatalf("got %d facts, want the one replaced", len(facts))
146 }
147 if facts[0].Text != "Isaac's car is a Toyota" {
148 t.Errorf("fact = %q", facts[0].Text)
149 }
150}
151
152// A small model wraps JSON in a fence or writes a sentence in front of it, and
153// neither is a reason to lose the edit.
154func TestParseChangesSurvivesTheUsualWrapping(t *testing.T) {
155 raw := "Sure, here is what should change:\n```json\n" +
156 `{"changes":[{"op":"add","fact":"Isaac drinks his coffee black"},{"op":"delete","id":4}]}` +
157 "\n```"
158 got := parseChanges(raw)
159 if len(got) != 2 {
160 t.Fatalf("got %d changes: %+v", len(got), got)
161 }
162 if got[0].Op != "add" || got[0].Fact != "Isaac drinks his coffee black" {
163 t.Errorf("first = %+v", got[0])
164 }
165 if got[1].Op != "delete" || got[1].ID != 4 {
166 t.Errorf("second = %+v", got[1])
167 }
168}
169
170func TestParseChangesDropsTheMalformed(t *testing.T) {
171 got := parseChanges(`{"changes":[{"op":"add"},{"op":"replace","fact":"no id"},{"op":"burn","id":1},{"op":"delete","id":0}]}`)
172 if len(got) != 0 {
173 t.Errorf("kept malformed changes: %+v", got)
174 }
175}
176
177// An empty block is left out entirely rather than saying it knows nothing,
178// which a model reads as an invitation to talk about not knowing things.
179func TestMemoryBlockIsEmptyWhenNothingMatched(t *testing.T) {
180 if got := memoryBlock(nil); got != "" {
181 t.Errorf("got %q, want nothing", got)
182 }
183 got := memoryBlock([]Fact{{Text: "Isaac drinks his coffee black"}})
184 if !strings.Contains(got, "coffee black") {
185 t.Errorf("the fact is missing:\n%s", got)
186 }
187 if !strings.Contains(got, "Do not list them back") {
188 t.Errorf("the instruction not to recite them is missing:\n%s", got)
189 }
190}
191
192func TestTidyFactCapsLength(t *testing.T) {
193 long := "Isaac " + strings.Repeat("x", maxFactChars+50)
194 if got := tidyFact(long); len(got) > maxFactChars {
195 t.Errorf("length = %d, want at most %d", len(got), maxFactChars)
196 }
197 if got := tidyFact(" - Isaac drinks coffee "); got != "Isaac drinks coffee" {
198 t.Errorf("got %q", got)
199 }
200}
201
202// The unique index only ever caught a fact proposed back word for word. On
203// 2026-09-08 one turn wrote two nearly identical facts about X post search and
204// left a third standing that contradicted both, because the model is asked to
205// replace rather than add and did not.
206func TestAddFactMergesARewording(t *testing.T) {
207 s := memStore(t)
208
209 first, err := s.AddFact("Isaac wants to build XCancel into the chat tooling in some way.")
210 if err != nil {
211 t.Fatal(err)
212 }
213 second, err := s.AddFact("Isaac is still looking for a way to integrate X post search into the chat tooling, " +
214 "but xcancel and Nitter are not the solutions since xcancel has no API.")
215 if err != nil {
216 t.Fatal(err)
217 }
218 if second != first {
219 t.Errorf("a rewording was stored as a new fact (%d then %d)", first, second)
220 }
221 facts, _ := s.Facts()
222 if len(facts) != 1 {
223 t.Fatalf("the table holds %d facts, want the one", len(facts))
224 }
225 // The newer wording is the one that stands, since it is the correction.
226 if !strings.Contains(facts[0].Text, "not the solutions") {
227 t.Errorf("the stored fact is the old wording: %q", facts[0].Text)
228 }
229}
230
231// A fact wholly contained in one already stored adds nothing, and replacing the
232// longer with the shorter would throw away what it knew.
233func TestAddFactKeepsTheFullerWording(t *testing.T) {
234 s := memStore(t)
235 full, _ := s.AddFact("Isaac camps and hikes in Yadkin Valley, North Carolina.")
236 again, err := s.AddFact("Isaac camps and hikes.")
237 if err != nil {
238 t.Fatal(err)
239 }
240 if again != full {
241 t.Errorf("the shorter fact was stored separately (%d then %d)", full, again)
242 }
243 facts, _ := s.Facts()
244 if len(facts) != 1 || !strings.Contains(facts[0].Text, "Yadkin") {
245 t.Errorf("the fuller wording was lost: %#v", facts)
246 }
247}
248
249// Two facts sharing only a name are not the same fact, and merging them would
250// lose one of them for good.
251func TestAddFactKeepsUnrelatedFactsApart(t *testing.T) {
252 s := memStore(t)
253 _, _ = s.AddFact("Isaac likes buttered chicken pizza.")
254 _, _ = s.AddFact("Isaac eats sausages but avoids ones containing nitrates or nitrites.")
255 _, _ = s.AddFact("Isaac regularly cooks sheet-pan meals of broccoli and potatoes with a single roasted protein.")
256 facts, _ := s.Facts()
257 if len(facts) != 3 {
258 t.Errorf("three unrelated facts collapsed to %d: %#v", len(facts), facts)
259 }
260}