Files
dbx/sync_test.go
zeekay 6a5cd2c401 the driver builds the DSN, and the tests open the same one the code does
DefaultSQLiteConnect wrote its pragmas by hand in modernc's `_pragma=NAME(VALUE)`
form. This package names the driver "sqlite" and imports none, so it inherits
whichever the consumer registered — and the cgo backend does not recognise that
spelling and drops it silently, opening with journal_mode `delete` and foreign
keys off while the DSN asked for WAL and ON. sqlite.PragmaDSN emits the form the
linked backend reads.

sync_test.go registered modernc directly, so the test binary held two drivers
claiming the name "sqlite" and died at init before any test ran. It opens the
same door the package does now.

The new test opens a real file and asks the database what it got: the string is
what differs between backends, so a string assertion would pass on the build that
ignores it.
2026-08-21 02:53:58 -07:00

183 lines
4.5 KiB
Go

package dbx
import (
"os"
"path/filepath"
"testing"
"time"
_ "github.com/hanzoai/sqlite"
)
type syncTestUser struct {
Owner string `db:"pk" type:"varchar(100)"`
Name string `db:"pk" type:"varchar(100)"`
Email string `db:"email" notnull:"true"`
DisplayName string `db:"display_name"`
Age int `db:"age" default:"0"`
IsAdmin bool `db:"is_admin"`
CreatedAt time.Time
Data []byte `db:"data"`
Ignored string `db:"-"`
}
type syncTestApp struct {
Owner string `db:"pk" type:"varchar(100)"`
Name string `db:"pk" type:"varchar(100)"`
ClientID string `db:"client_id" unique:"true"`
Secret string `db:"secret"`
}
func testSyncDB(t *testing.T) *DB {
t.Helper()
dir := t.TempDir()
path := filepath.Join(dir, "sync_test.db")
db, err := DefaultSQLiteConnect(path)
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() {
db.Close()
os.Remove(path)
})
return db
}
func TestSync_CreateTable(t *testing.T) {
db := testSyncDB(t)
err := db.Sync(syncTestUser{})
if err != nil {
t.Fatal(err)
}
// Verify table exists
var count int
db.NewQuery("SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='sync_test_user'").Row(&count)
if count != 1 {
t.Fatal("table sync_test_user not created")
}
// Verify columns
cols, _ := db.existingColumns("sync_test_user")
expected := []string{"owner", "name", "email", "display_name", "age", "is_admin", "created_at", "data"}
for _, col := range expected {
if !cols[col] {
t.Fatalf("missing column: %s", col)
}
}
// Verify ignored field is NOT a column
if cols["ignored"] {
t.Fatal("ignored field should not be a column")
}
}
func TestSync_Idempotent(t *testing.T) {
db := testSyncDB(t)
// Call Sync twice — should not error
if err := db.Sync(syncTestUser{}); err != nil {
t.Fatal(err)
}
if err := db.Sync(syncTestUser{}); err != nil {
t.Fatal(err)
}
}
func TestSync_AddMissingColumn(t *testing.T) {
db := testSyncDB(t)
// Create table with fewer columns
db.NewQuery(`CREATE TABLE "sync_test_user" ("owner" TEXT, "name" TEXT, PRIMARY KEY ("owner", "name"))`).Execute()
// Sync should add missing columns
if err := db.Sync(syncTestUser{}); err != nil {
t.Fatal(err)
}
cols, _ := db.existingColumns("sync_test_user")
if !cols["email"] {
t.Fatal("email column should have been added")
}
if !cols["display_name"] {
t.Fatal("display_name column should have been added")
}
}
func TestSync_MultipleStructs(t *testing.T) {
db := testSyncDB(t)
err := db.Sync(syncTestUser{}, syncTestApp{})
if err != nil {
t.Fatal(err)
}
var count int
db.NewQuery("SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name IN ('sync_test_user','sync_test_app')").Row(&count)
if count != 2 {
t.Fatalf("expected 2 tables, got %d", count)
}
}
func TestSync_InsertAndQuery(t *testing.T) {
db := testSyncDB(t)
db.Sync(syncTestUser{})
// Insert a row
db.NewQuery(`INSERT INTO "sync_test_user" ("owner","name","email","display_name","age","is_admin") VALUES ('admin','alice','[email protected]','Alice',30,1)`).Execute()
// Query it back
var email string
db.NewQuery(`SELECT "email" FROM "sync_test_user" WHERE "owner"='admin' AND "name"='alice'`).Row(&email)
if email != "[email protected]" {
t.Fatalf("expected [email protected], got %s", email)
}
}
func TestSync_CompositePK(t *testing.T) {
db := testSyncDB(t)
db.Sync(syncTestUser{})
// Insert with composite PK
db.NewQuery(`INSERT INTO "sync_test_user" ("owner","name","email") VALUES ('org1','user1','[email protected]')`).Execute()
db.NewQuery(`INSERT INTO "sync_test_user" ("owner","name","email") VALUES ('org1','user2','[email protected]')`).Execute()
db.NewQuery(`INSERT INTO "sync_test_user" ("owner","name","email") VALUES ('org2','user1','[email protected]')`).Execute()
// Should have 3 distinct rows
var count int
db.NewQuery(`SELECT COUNT(*) FROM "sync_test_user"`).Row(&count)
if count != 3 {
t.Fatalf("expected 3 rows, got %d", count)
}
// Duplicate PK should fail
_, err := db.NewQuery(`INSERT INTO "sync_test_user" ("owner","name","email") VALUES ('org1','user1','[email protected]')`).Execute()
if err == nil {
t.Fatal("duplicate PK should fail")
}
}
func TestSync_SnakeCaseConversion(t *testing.T) {
type CamelCaseTest struct {
FirstName string `db:"pk"`
LastName string
EmailAddr string
}
db := testSyncDB(t)
db.Sync(CamelCaseTest{})
cols, _ := db.existingColumns("camel_case_test")
if !cols["first_name"] {
t.Fatal("expected first_name column")
}
if !cols["last_name"] {
t.Fatal("expected last_name column")
}
if !cols["email_addr"] {
t.Fatal("expected email_addr column")
}
}