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 tools
2
3import (
4 "context"
5 "strings"
6 "testing"
7)
8
9type memStub struct {
10 facts []MemoryFact
11 deleted []int64
12 changed map[int64]string
13}
14
15func (m *memStub) Facts() ([]MemoryFact, error) { return m.facts, nil }
16func (m *memStub) Add(t string) (int64, error) {
17 id := int64(len(m.facts) + 1)
18 m.facts = append(m.facts, MemoryFact{ID: id, Text: t})
19 return id, nil
20}
21func (m *memStub) Replace(id int64, t string) error {
22 if m.changed == nil {
23 m.changed = map[int64]string{}
24 }
25 m.changed[id] = t
26 return nil
27}
28func (m *memStub) Delete(id int64) error { m.deleted = append(m.deleted, id); return nil }
29
30func withMem(m Memory) *Deps {
31 d := NewDeps()
32 d.Memory = m
33 return d
34}
35
36func TestRememberWritesReadsAndForgets(t *testing.T) {
37 m := &memStub{}
38 d := withMem(m)
39 ctx := context.Background()
40
41 if _, err := Remember.Run(ctx, d, map[string]any{"action": "add", "fact": "Isaac wants to watch End of Watch."}); err != nil {
42 t.Fatalf("add: %v", err)
43 }
44 if len(m.facts) != 1 || !strings.Contains(m.facts[0].Text, "End of Watch") {
45 t.Fatalf("facts = %+v", m.facts)
46 }
47
48 got, err := Remember.Run(ctx, d, map[string]any{"action": "list"})
49 if err != nil {
50 t.Fatalf("list: %v", err)
51 }
52 if got.(map[string]any)["count"].(int) != 1 {
53 t.Errorf("list did not report the one fact: %+v", got)
54 }
55
56 // The model renders an id as a number, and json puts it back as a float.
57 if _, err := Remember.Run(ctx, d, map[string]any{"action": "replace", "id": float64(1), "fact": "Isaac watched it."}); err != nil {
58 t.Fatalf("replace: %v", err)
59 }
60 if m.changed[1] != "Isaac watched it." {
61 t.Errorf("replace wrote %q", m.changed[1])
62 }
63
64 if _, err := Remember.Run(ctx, d, map[string]any{"action": "forget", "id": "1"}); err != nil {
65 t.Fatalf("forget: %v", err)
66 }
67 if len(m.deleted) != 1 || m.deleted[0] != 1 {
68 t.Errorf("forgot %v", m.deleted)
69 }
70}
71
72// A mode that writes nothing down cannot be the one that teaches it something
73// to write down later, so an incognito turn may read memory and not change it.
74func TestIncognitoCannotWriteMemory(t *testing.T) {
75 m := &memStub{facts: []MemoryFact{{ID: 1, Text: "Isaac camps."}}}
76 d := withMem(m)
77 d.Incognito = true
78 ctx := context.Background()
79
80 for _, action := range []string{"add", "replace", "forget"} {
81 if _, err := Remember.Run(ctx, d, map[string]any{"action": action, "fact": "x", "id": 1}); err == nil {
82 t.Errorf("%s was allowed in an incognito turn", action)
83 }
84 }
85 if len(m.facts) != 1 || len(m.deleted) != 0 {
86 t.Errorf("an incognito turn changed memory: %+v %v", m.facts, m.deleted)
87 }
88 if _, err := Remember.Run(ctx, d, map[string]any{"action": "list"}); err != nil {
89 t.Errorf("reading memory in incognito failed: %v", err)
90 }
91}
92
93// Missing arguments have to say what is missing, since the model reads the
94// error and tries again in the same turn.
95func TestRememberRefusesIncompleteCalls(t *testing.T) {
96 d := withMem(&memStub{})
97 ctx := context.Background()
98 for _, a := range []map[string]any{
99 {"action": "add"},
100 {"action": "replace", "id": 1},
101 {"action": "forget"},
102 {"action": "sing"},
103 } {
104 if _, err := Remember.Run(ctx, d, a); err == nil {
105 t.Errorf("%v was accepted", a)
106 }
107 }
108}
109
110// A site with no store must not panic a turn that calls this.
111func TestRememberWithNoStore(t *testing.T) {
112 if _, err := Remember.Run(context.Background(), NewDeps(), map[string]any{"action": "list"}); err == nil {
113 t.Error("a missing store was not reported")
114 }
115}