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.0 KB · 112 lines · Go Raw History
  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}