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 "database/sql"
5 "path/filepath"
6 "strings"
7 "testing"
8
9 _ "modernc.org/sqlite"
10)
11
12// The shape the database had before the ids became uuids, without the sources
13// column either, which is the state the deployed one is actually in.
14const oldSchema = `
15CREATE TABLE conversations (
16 id INTEGER PRIMARY KEY, title TEXT NOT NULL DEFAULT '',
17 created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL,
18 summary TEXT NOT NULL DEFAULT '', summarized INTEGER NOT NULL DEFAULT 0);
19CREATE TABLE messages (
20 id INTEGER PRIMARY KEY,
21 conv_id INTEGER NOT NULL REFERENCES conversations(id) ON DELETE CASCADE,
22 role TEXT NOT NULL, content TEXT NOT NULL,
23 tools TEXT NOT NULL DEFAULT '[]', display TEXT NOT NULL DEFAULT '',
24 files TEXT NOT NULL DEFAULT '[]', at INTEGER NOT NULL);
25INSERT INTO conversations(id,title,created_at,updated_at) VALUES(1,'First',10,10),(2,'Second',20,20);
26INSERT INTO messages(conv_id,role,content,at) VALUES
27 (1,'user','one question',11),(1,'assistant','one answer',12),
28 (2,'user','two question',21),(2,'assistant','two answer',22);
29`
30
31func TestMigrateToUUIDs(t *testing.T) {
32 path := filepath.Join(t.TempDir(), "chat.db")
33 db, err := sql.Open("sqlite", path)
34 if err != nil {
35 t.Fatal(err)
36 }
37 if _, err := db.Exec(oldSchema); err != nil {
38 t.Fatal(err)
39 }
40 db.Close()
41
42 st, err := OpenStore(path)
43 if err != nil {
44 t.Fatalf("opening a counter-id database: %v", err)
45 }
46 defer st.Close()
47
48 convs, err := st.List(10)
49 if err != nil {
50 t.Fatal(err)
51 }
52 if len(convs) != 2 {
53 t.Fatalf("want 2 conversations, got %d", len(convs))
54 }
55 seen := map[string]bool{}
56 for _, c := range convs {
57 if len(c.ID) != 36 {
58 t.Errorf("%q is not a uuid", c.ID)
59 }
60 if seen[c.ID] {
61 t.Errorf("two conversations share the id %q", c.ID)
62 }
63 seen[c.ID] = true
64
65 msgs, err := st.Messages(c.ID)
66 if err != nil {
67 t.Fatal(err)
68 }
69 if len(msgs) != 2 {
70 t.Fatalf("%s: want 2 messages, got %d", c.Title, len(msgs))
71 }
72 // The pair has to have stayed with its own conversation and in order.
73 want := map[string]string{"First": "one", "Second": "two"}[c.Title]
74 if msgs[0].Content != want+" question" || msgs[1].Content != want+" answer" {
75 t.Errorf("%s carries the wrong messages: %+v", c.Title, msgs)
76 }
77 }
78
79 // Reopening finds uuids already and leaves everything alone.
80 st.Close()
81 again, err := OpenStore(path)
82 if err != nil {
83 t.Fatal(err)
84 }
85 defer again.Close()
86 convs2, _ := again.List(10)
87 if len(convs2) != 2 {
88 t.Fatalf("a second open changed the count to %d", len(convs2))
89 }
90 for _, c := range convs2 {
91 if !seen[c.ID] {
92 t.Errorf("a second open handed out new ids, %q", c.ID)
93 }
94 }
95}
96
97func TestNewIDIsAUUID(t *testing.T) {
98 a, b := newID(), newID()
99 if a == b {
100 t.Fatal("two calls returned the same id")
101 }
102 if len(a) != 36 || a[8] != '-' || a[13] != '-' || a[18] != '-' || a[23] != '-' {
103 t.Fatalf("%q is not the canonical shape", a)
104 }
105 if a[14] != '4' {
106 t.Errorf("%q is not version 4", a)
107 }
108 if !strings.ContainsRune("89ab", rune(a[19])) {
109 t.Errorf("%q has the wrong variant nibble", a)
110 }
111}