Kristoffer Dalby c6736dd6d6 db: add sqlite "source of truth" schema
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2025-07-07 15:48:38 +01:00

212 lines
4.8 KiB
Go

package sqliteconfig
import (
"testing"
)
func TestJournalMode(t *testing.T) {
tests := []struct {
mode JournalMode
valid bool
}{
{JournalModeWAL, true},
{JournalModeDelete, true},
{JournalModeTruncate, true},
{JournalModePersist, true},
{JournalModeMemory, true},
{JournalModeOff, true},
{JournalMode("INVALID"), false},
{JournalMode(""), false},
}
for _, tt := range tests {
t.Run(string(tt.mode), func(t *testing.T) {
if got := tt.mode.IsValid(); got != tt.valid {
t.Errorf("JournalMode(%q).IsValid() = %v, want %v", tt.mode, got, tt.valid)
}
})
}
}
func TestAutoVacuum(t *testing.T) {
tests := []struct {
mode AutoVacuum
valid bool
}{
{AutoVacuumNone, true},
{AutoVacuumFull, true},
{AutoVacuumIncremental, true},
{AutoVacuum("INVALID"), false},
{AutoVacuum(""), false},
}
for _, tt := range tests {
t.Run(string(tt.mode), func(t *testing.T) {
if got := tt.mode.IsValid(); got != tt.valid {
t.Errorf("AutoVacuum(%q).IsValid() = %v, want %v", tt.mode, got, tt.valid)
}
})
}
}
func TestSynchronous(t *testing.T) {
tests := []struct {
mode Synchronous
valid bool
}{
{SynchronousOff, true},
{SynchronousNormal, true},
{SynchronousFull, true},
{SynchronousExtra, true},
{Synchronous("INVALID"), false},
{Synchronous(""), false},
}
for _, tt := range tests {
t.Run(string(tt.mode), func(t *testing.T) {
if got := tt.mode.IsValid(); got != tt.valid {
t.Errorf("Synchronous(%q).IsValid() = %v, want %v", tt.mode, got, tt.valid)
}
})
}
}
func TestConfigValidate(t *testing.T) {
tests := []struct {
name string
config *Config
wantErr bool
}{
{
name: "valid default config",
config: Default("/path/to/db.sqlite"),
},
{
name: "empty path",
config: &Config{
Path: "",
},
wantErr: true,
},
{
name: "negative busy timeout",
config: &Config{
Path: "/path/to/db.sqlite",
BusyTimeout: -1,
},
wantErr: true,
},
{
name: "invalid journal mode",
config: &Config{
Path: "/path/to/db.sqlite",
JournalMode: JournalMode("INVALID"),
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.config.Validate()
if (err != nil) != tt.wantErr {
t.Errorf("Config.Validate() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func TestConfigToURL(t *testing.T) {
tests := []struct {
name string
config *Config
want string
}{
{
name: "default config",
config: Default("/path/to/db.sqlite"),
want: "file:/path/to/db.sqlite?_pragma=busy_timeout=10000&_pragma=journal_mode=WAL&_pragma=auto_vacuum=INCREMENTAL&_pragma=wal_autocheckpoint=1000&_pragma=synchronous=NORMAL&_pragma=foreign_keys=ON",
},
{
name: "memory config",
config: Memory(),
want: ":memory:?_pragma=foreign_keys=ON",
},
{
name: "minimal config",
config: &Config{
Path: "/simple/db.sqlite",
WALAutocheckpoint: -1, // not set
},
want: "file:/simple/db.sqlite",
},
{
name: "custom config",
config: &Config{
Path: "/custom/db.sqlite",
BusyTimeout: 5000,
JournalMode: JournalModeDelete,
WALAutocheckpoint: -1, // not set
Synchronous: SynchronousFull,
ForeignKeys: true,
},
want: "file:/custom/db.sqlite?_pragma=busy_timeout=5000&_pragma=journal_mode=DELETE&_pragma=synchronous=FULL&_pragma=foreign_keys=ON",
},
{
name: "memory with custom timeout",
config: &Config{
Path: ":memory:",
BusyTimeout: 2000,
WALAutocheckpoint: -1, // not set
ForeignKeys: true,
},
want: ":memory:?_pragma=busy_timeout=2000&_pragma=foreign_keys=ON",
},
{
name: "wal autocheckpoint zero",
config: &Config{
Path: "/test.db",
WALAutocheckpoint: 0,
},
want: "file:/test.db?_pragma=wal_autocheckpoint=0",
},
{
name: "all options",
config: &Config{
Path: "/full.db",
BusyTimeout: 15000,
JournalMode: JournalModeWAL,
AutoVacuum: AutoVacuumFull,
WALAutocheckpoint: 1000,
Synchronous: SynchronousExtra,
ForeignKeys: true,
},
want: "file:/full.db?_pragma=busy_timeout=15000&_pragma=journal_mode=WAL&_pragma=auto_vacuum=FULL&_pragma=wal_autocheckpoint=1000&_pragma=synchronous=EXTRA&_pragma=foreign_keys=ON",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := tt.config.ToURL()
if err != nil {
t.Errorf("Config.ToURL() error = %v", err)
return
}
if got != tt.want {
t.Errorf("Config.ToURL() = %q, want %q", got, tt.want)
}
})
}
}
func TestConfigToURLInvalid(t *testing.T) {
config := &Config{
Path: "",
BusyTimeout: -1,
}
_, err := config.ToURL()
if err == nil {
t.Error("Config.ToURL() with invalid config should return error")
}
}