2026-10-14 20:02:55 -07:00
package store
import (
"database/sql"
"path/filepath"
"slices"
"testing"
)
func openTestDB ( t * testing . T , path string ) * sql . DB {
t . Helper ()
db , err := sql . Open ( "sqlite" , "file:" + path + "?_pragma=foreign_keys(1)&_txlock=immediate" )
if err != nil {
t . Fatalf ( "open db: %v" , err )
}
t . Cleanup ( func () { db . Close () })
return db
}
func appliedNames ( t * testing . T , db * sql . DB ) [] string {
t . Helper ()
rows , err := db . Query ( `SELECT name FROM migrations ORDER BY rowid` )
if err != nil {
t . Fatalf ( "list migrations: %v" , err )
}
defer rows . Close ()
names := [] string {}
for rows . Next () {
name := ""
err = rows . Scan ( & name )
if err != nil {
t . Fatalf ( "scan migration: %v" , err )
}
names = append ( names , name )
}
if rows . Err () != nil {
t . Fatalf ( "list migrations: %v" , rows . Err ())
}
return names
}
func migrationNames ( ms [] migration ) [] string {
names := [] string {}
for _ , m := range ms {
names = append ( names , m . name )
}
return names
}
func insertReport ( t * testing . T , db * sql . DB , id , payload string ) {
t . Helper ()
_ , err := db . Exec ( `
INSERT OR IGNORE INTO users (login, display_name, created_at) VALUES ('alice', 'Alice', 'x');
INSERT OR IGNORE INTO projects (id, name, repo_path, default_branch, remote, created_by, created_at) VALUES ('p1', 'demo', '/nonexistent', 'main', 'none', 'alice', 'x');
INSERT OR IGNORE INTO tasks (id, project_id, title, branch, worktree_path, base_ref, status, created_by, created_at) VALUES ('t1', 'p1', 't1', 't1', '/nonexistent', 'x', 'active', 'alice', 'x');
INSERT OR IGNORE INTO agent_sessions (id, task_id, vendor, model, vendor_session_id, state, role, created_by, created_at) VALUES ('s1', 't1', 'claude', '', 's1', 'idle', 'implementer', 'alice', 'x');
` )
if err != nil {
t . Fatalf ( "insert parents: %v" , err )
}
_ , err = db . Exec ( `
INSERT INTO inbox_items (id, project_id, task_id, agent_session_id, thread_id, kind, priority, blocking, payload, state, created_at)
VALUES (?, 'p1', 't1', 's1', ?, 'report', 0, 0, ?, 'open', 'x')
` , id , id , payload )
if err != nil {
t . Fatalf ( "insert report: %v" , err )
}
}
func reportPayload ( t * testing . T , db * sql . DB , id string ) string {
t . Helper ()
payload := ""
err := db . QueryRow ( `SELECT payload FROM inbox_items WHERE id = ?` , id ). Scan ( & payload )
if err != nil {
t . Fatalf ( "read report: %v" , err )
}
return payload
}
func TestMigrateFreshDatabase ( t * testing . T ) {
db := openTestDB ( t , filepath . Join ( t . TempDir (), "byre.db" ))
err := migrate ( db , migrations )
if err != nil {
t . Fatalf ( "migrate: %v" , err )
}
err = migrate ( db , migrations )
if err != nil {
t . Fatalf ( "migrate again: %v" , err )
}
got := appliedNames ( t , db )
want := migrationNames ( migrations )
if ! slices . Equal ( got , want ) {
t . Fatalf ( "applied %v, want %v" , got , want )
}
}
func TestMigrateFromUserVersion ( t * testing . T ) {
path := filepath . Join ( t . TempDir (), "byre.db" )
db := openTestDB ( t , path )
for _ , m := range migrations [: 9 ] {
_ , err := db . Exec ( m . sql )
if err != nil {
t . Fatalf ( "old migration %s: %v" , m . name , err )
}
}
_ , err := db . Exec ( `PRAGMA user_version = 9` )
if err != nil {
t . Fatalf ( "set user_version: %v" , err )
}
insertReport ( t , db , "r1" , `{"text":"- shipped"}` )
db . Close ()
db = openTestDB ( t , path )
err = migrate ( db , migrations )
if err != nil {
t . Fatalf ( "migrate: %v" , err )
}
got := appliedNames ( t , db )
want := migrationNames ( migrations )
if ! slices . Equal ( got , want ) {
t . Fatalf ( "applied %v, want %v" , got , want )
}
payload := reportPayload ( t , db , "r1" )
if payload != `{"text":"- shipped"}` {
t . Fatalf ( "report payload %s, want it unchanged" , payload )
}
}
func TestMigrateRunsMigrationAddedBeforeApplied ( t * testing . T ) {
db := openTestDB ( t , filepath . Join ( t . TempDir (), "byre.db" ))
first := migration { "create-a" , `CREATE TABLE a (x TEXT)` }
other := migration { "create-b" , `CREATE TABLE b (x TEXT)` }
last := migration { "create-c" , `CREATE TABLE c (x TEXT)` }
err := migrate ( db , [] migration { first , last })
if err != nil {
t . Fatalf ( "migrate: %v" , err )
}
err = migrate ( db , [] migration { first , other , last })
if err != nil {
t . Fatalf ( "migrate with other branch: %v" , err )
}
got := appliedNames ( t , db )
want := [] string { "create-a" , "create-c" , "create-b" }
if ! slices . Equal ( got , want ) {
t . Fatalf ( "applied %v, want %v" , got , want )
}
_ , err = db . Exec ( `INSERT INTO b (x) VALUES ('y')` )
if err != nil {
t . Fatalf ( "table from other branch: %v" , err )
}
}
2026-10-14 20:07:26 -07:00
func TestBlobsMigrationOverPositionalCopy ( t * testing . T ) {
db := openTestDB ( t , filepath . Join ( t . TempDir (), "byre.db" ))
err := migrate ( db , migrations [: 9 ])
if err != nil {
t . Fatalf ( "migrate: %v" , err )
}
insertReport ( t , db , "r1" , `{"text":"x"}` )
_ , err = db . Exec ( `
CREATE TABLE blobs (id TEXT PRIMARY KEY, content_type TEXT NOT NULL, path TEXT NOT NULL, created_by TEXT NOT NULL REFERENCES users(login), created_at TEXT NOT NULL);
INSERT INTO blobs VALUES ('b1', 'image/png', 'blobs/b1', 'alice', 'x');
ALTER TABLE turn_queue ADD COLUMN images TEXT NOT NULL DEFAULT '[]';
INSERT INTO turn_queue (session_id, actor, text, created_at) VALUES ('s1', 'alice', 'queued', 'x');
` )
if err != nil {
t . Fatalf ( "positional copy: %v" , err )
}
err = migrate ( db , migrations )
if err != nil {
t . Fatalf ( "migrate: %v" , err )
}
text := ""
images := ""
err = db . QueryRow ( `SELECT text, images FROM turn_queue WHERE session_id = 's1'` ). Scan ( & text , & images )
if err != nil {
t . Fatalf ( "queued turn: %v" , err )
}
if text != "queued" || images != "[]" {
t . Fatalf ( "queued turn %q %q" , text , images )
}
blobs := 0
err = db . QueryRow ( `SELECT count(*) FROM blobs` ). Scan ( & blobs )
if err != nil || blobs != 1 {
t . Fatalf ( "blobs %d: %v" , blobs , err )
}
}
2026-10-14 23:34:39 -07:00
func TestInlineQuestionChoicesMigration ( t * testing . T ) {
db := openTestDB ( t , filepath . Join ( t . TempDir (), "byre.db" ))
i := slices . IndexFunc ( migrations , func ( m migration ) bool { return m . name == "inline-question-choices" })
err := migrate ( db , migrations [: i ])
if err != nil {
t . Fatalf ( "migrate: %v" , err )
}
insertReport ( t , db , "r1" , `{"text":"x"}` )
_ , err = db . Exec ( `
INSERT INTO inbox_items (id, project_id, task_id, agent_session_id, thread_id, kind, priority, blocking, payload, state, created_at)
VALUES ('q1', 'p1', 't1', 's1', 'q1', 'question', 0, 1,
'{"context":"Pick a color.","question":"Which?","why_blocked":"w","options":["red","blue"],"recommended_option":"blue","attachments":[]}', 'answered', 'x');
INSERT INTO inbox_responses (id, item_id, user_login, type, option, text, attachments, created_at) VALUES ('a1', 'q1', 'alice', 'option', 'red', NULL, '[]', 'x');
INSERT INTO inbox_responses (id, item_id, user_login, type, option, text, attachments, created_at) VALUES ('a2', 'q1', 'alice', 'other', NULL, 'green', '[]', 'x');
` )
if err != nil {
t . Fatalf ( "insert question: %v" , err )
}
err = migrate ( db , migrations )
if err != nil {
t . Fatalf ( "migrate: %v" , err )
}
payload := reportPayload ( t , db , "q1" )
want := `{"context":"Pick a color.\n\n- ( ) red\n- (*) blue","question":"Which?","why_blocked":"w","options":["red","blue"],"attachments":[],"multiple":false,"recommended":["blue"]}`
if payload != want {
t . Fatalf ( "question payload\n got %s\nwant %s" , payload , want )
}
if reportPayload ( t , db , "r1" ) != `{"text":"x"}` {
t . Fatalf ( "report payload changed" )
}
options := map [ string ] sql . NullString {}
rows , err := db . Query ( `SELECT id, options FROM inbox_responses` )
if err != nil {
t . Fatalf ( "responses: %v" , err )
}
defer rows . Close ()
for rows . Next () {
id := ""
o := sql . NullString {}
err = rows . Scan ( & id , & o )
if err != nil {
t . Fatalf ( "scan: %v" , err )
}
options [ id ] = o
}
if options [ "a1" ]. String != `["red"]` || options [ "a2" ]. Valid {
t . Fatalf ( "response options %+v" , options )
}
}
2026-10-14 20:02:55 -07:00
func TestFlattenReportPayloadsRunsTwice ( t * testing . T ) {
db := openTestDB ( t , filepath . Join ( t . TempDir (), "byre.db" ))
err := migrate ( db , migrations )
if err != nil {
t . Fatalf ( "migrate: %v" , err )
}
insertReport ( t , db , "r1" , `{"done":["one","two"]}` )
i := slices . IndexFunc ( migrations , func ( m migration ) bool { return m . name == "flatten-report-payloads-to-text" })
for range 2 {
_ , err = db . Exec ( migrations [ i ]. sql )
if err != nil {
t . Fatalf ( "flatten: %v" , err )
}
payload := reportPayload ( t , db , "r1" )
if payload != `{"text":"- one\n- two"}` {
t . Fatalf ( "report payload %s" , payload )
}
}
}