cli + tui to publish to leaflet (wip) & manage tasks, notes & watch/read lists 馃崈
charm leaflet readability golang
at main 310 lines 8.1 kB view raw
1package store 2 3import ( 4 "database/sql" 5 "fmt" 6 "os" 7 "path/filepath" 8 "runtime" 9 "strings" 10 "testing" 11 12 "github.com/stormlightlabs/noteleaf/internal/shared" 13) 14 15func withTempDirs(t *testing.T) string { 16 t.Helper() 17 tempDir, cleanup := shared.CreateTempDir("noteleaf-db-test-*", t) 18 t.Cleanup(func() { cleanup() }) 19 20 origConfig, origData := GetConfigDir, GetDataDir 21 GetConfigDir = func() (string, error) { return tempDir, nil } 22 GetDataDir = func() (string, error) { return tempDir, nil } 23 t.Cleanup(func() { 24 GetConfigDir, GetDataDir = origConfig, origData 25 }) 26 27 return tempDir 28} 29 30func TestNewDatabase(t *testing.T) { 31 tempDir := withTempDirs(t) 32 33 t.Run("creates database file", func(t *testing.T) { 34 db, err := NewDatabase() 35 if err != nil { 36 t.Fatalf("NewDatabase failed: %v", err) 37 } 38 defer db.Close() 39 40 dbPath := filepath.Join(tempDir, "noteleaf.db") 41 if _, err := os.Stat(dbPath); os.IsNotExist(err) { 42 t.Errorf("expected database file at %s", dbPath) 43 } 44 }) 45 46 t.Run("foreign keys enabled", func(t *testing.T) { 47 db, _ := NewDatabase() 48 defer db.Close() 49 50 var fk int 51 if err := db.QueryRow("PRAGMA foreign_keys").Scan(&fk); err != nil { 52 t.Fatalf("query failed: %v", err) 53 } 54 if fk != 1 { 55 t.Errorf("expected foreign_keys=1, got %d", fk) 56 } 57 }) 58 59 t.Run("WAL enabled", func(t *testing.T) { 60 db, _ := NewDatabase() 61 defer db.Close() 62 63 var mode string 64 if err := db.QueryRow("PRAGMA journal_mode").Scan(&mode); err != nil { 65 t.Fatalf("query failed: %v", err) 66 } 67 if strings.ToLower(mode) != "wal" { 68 t.Errorf("expected wal, got %s", mode) 69 } 70 }) 71} 72 73func TestNewDatabase_ErrorPaths(t *testing.T) { 74 t.Run("sql.Open fails", func(t *testing.T) { 75 orig := sqlOpen 76 sqlOpen = func(driver, dsn string) (*sql.DB, error) { 77 return nil, fmt.Errorf("boom") 78 } 79 t.Cleanup(func() { sqlOpen = orig }) 80 81 _, err := NewDatabase() 82 if err == nil || !strings.Contains(err.Error(), "failed to open database") { 83 t.Errorf("expected open error, got %v", err) 84 } 85 }) 86 87 t.Run("foreign_keys pragma fails", func(t *testing.T) { 88 orig := pragmaExec 89 pragmaExec = func(db *sql.DB, stmt string) (sql.Result, error) { 90 if strings.Contains(stmt, "foreign_keys") { 91 return nil, fmt.Errorf("fk fail") 92 } 93 return orig(db, stmt) 94 } 95 t.Cleanup(func() { pragmaExec = orig }) 96 97 _, err := NewDatabase() 98 if err == nil || !strings.Contains(err.Error(), "failed to enable foreign keys") { 99 t.Errorf("expected foreign key error, got %v", err) 100 } 101 }) 102 103 t.Run("WAL pragma fails", func(t *testing.T) { 104 orig := pragmaExec 105 pragmaExec = func(db *sql.DB, stmt string) (sql.Result, error) { 106 if strings.Contains(stmt, "journal_mode") { 107 return nil, fmt.Errorf("wal fail") 108 } 109 return orig(db, stmt) 110 } 111 t.Cleanup(func() { pragmaExec = orig }) 112 113 _, err := NewDatabase() 114 if err == nil || !strings.Contains(err.Error(), "failed to enable WAL mode") { 115 t.Errorf("expected WAL error, got %v", err) 116 } 117 }) 118 119 t.Run("migration runner fails", func(t *testing.T) { 120 orig := createMigrationRunner 121 createMigrationRunner = func(db *sql.DB, fs FileSystem) *MigrationRunner { 122 return &MigrationRunner{runFn: func() error { return fmt.Errorf("migration fail") }} 123 } 124 t.Cleanup(func() { createMigrationRunner = orig }) 125 126 _, err := NewDatabase() 127 if err == nil || !strings.Contains(err.Error(), "failed to run migrations") { 128 t.Errorf("expected migration error, got %v", err) 129 } 130 }) 131 132 t.Run("permission denied on config dir", func(t *testing.T) { 133 if runtime.GOOS == "windows" { 134 t.Skip("not reliable on Windows") 135 } 136 dir := withTempDirs(t) 137 os.Chmod(dir, 0555) // make read-only 138 defer os.Chmod(dir, 0755) 139 140 GetConfigDir = func() (string, error) { return dir, nil } 141 142 _, err := NewDatabase() 143 if err == nil { 144 t.Error("expected mkdir fail due to permission denied") 145 } 146 }) 147} 148 149func TestGetConfigDir_AllBranches(t *testing.T) { 150 tmp := t.TempDir() 151 152 t.Run("windows success", func(t *testing.T) { 153 origGOOS := getRuntime 154 getRuntime = func() string { return "windows" } 155 defer func() { getRuntime = origGOOS }() 156 157 os.Setenv("APPDATA", tmp) 158 defer os.Unsetenv("APPDATA") 159 160 dir, err := GetConfigDir() 161 if err != nil { 162 t.Fatalf("unexpected error: %v", err) 163 } 164 expected := filepath.Join(tmp, "noteleaf") 165 if dir != expected { 166 t.Errorf("expected %s, got %s", expected, dir) 167 } 168 }) 169 170 t.Run("windows missing APPDATA", func(t *testing.T) { 171 origGOOS := getRuntime 172 getRuntime = func() string { return "windows" } 173 defer func() { getRuntime = origGOOS }() 174 175 os.Unsetenv("APPDATA") 176 177 _, err := GetConfigDir() 178 if err == nil || !strings.Contains(err.Error(), "APPDATA") { 179 t.Errorf("expected APPDATA error, got %v", err) 180 } 181 }) 182 183 t.Run("darwin success", func(t *testing.T) { 184 origGOOS, origHome := getRuntime, getHomeDir 185 getRuntime = func() string { return "darwin" } 186 getHomeDir = func() (string, error) { return tmp, nil } 187 defer func() { 188 getRuntime = origGOOS 189 getHomeDir = origHome 190 }() 191 192 dir, err := GetConfigDir() 193 if err != nil { 194 t.Fatalf("unexpected error: %v", err) 195 } 196 expected := filepath.Join(tmp, "Library", "Application Support", "noteleaf") 197 if dir != expected { 198 t.Errorf("expected %s, got %s", expected, dir) 199 } 200 }) 201 202 t.Run("linux default with XDG_CONFIG_HOME unset", func(t *testing.T) { 203 origGOOS, origHome := getRuntime, getHomeDir 204 getRuntime = func() string { return "linux" } 205 getHomeDir = func() (string, error) { return tmp, nil } 206 defer func() { 207 getRuntime = origGOOS 208 getHomeDir = origHome 209 }() 210 211 os.Unsetenv("XDG_CONFIG_HOME") 212 213 dir, err := GetConfigDir() 214 if err != nil { 215 t.Fatalf("unexpected error: %v", err) 216 } 217 expected := filepath.Join(tmp, ".config", "noteleaf") 218 if dir != expected { 219 t.Errorf("expected %s, got %s", expected, dir) 220 } 221 }) 222} 223 224func TestGetDataDir_AllBranches(t *testing.T) { 225 tmp := t.TempDir() 226 227 t.Run("NOTELEAF_DATA_DIR overrides", func(t *testing.T) { 228 os.Setenv("NOTELEAF_DATA_DIR", tmp) 229 defer os.Unsetenv("NOTELEAF_DATA_DIR") 230 231 dir, err := GetDataDir() 232 if err != nil { 233 t.Fatalf("unexpected error: %v", err) 234 } 235 if dir != tmp { 236 t.Errorf("expected %s, got %s", tmp, dir) 237 } 238 }) 239 240 t.Run("windows success", func(t *testing.T) { 241 origGOOS := getRuntime 242 getRuntime = func() string { return "windows" } 243 defer func() { getRuntime = origGOOS }() 244 245 os.Setenv("LOCALAPPDATA", tmp) 246 defer os.Unsetenv("LOCALAPPDATA") 247 248 dir, err := GetDataDir() 249 if err != nil { 250 t.Fatalf("unexpected error: %v", err) 251 } 252 expected := filepath.Join(tmp, "noteleaf") 253 if dir != expected { 254 t.Errorf("expected %s, got %s", expected, dir) 255 } 256 }) 257 258 t.Run("windows missing LOCALAPPDATA", func(t *testing.T) { 259 origGOOS := getRuntime 260 getRuntime = func() string { return "windows" } 261 defer func() { getRuntime = origGOOS }() 262 263 os.Unsetenv("LOCALAPPDATA") 264 265 _, err := GetDataDir() 266 if err == nil || !strings.Contains(err.Error(), "LOCALAPPDATA") { 267 t.Errorf("expected LOCALAPPDATA error, got %v", err) 268 } 269 }) 270 271 t.Run("darwin success", func(t *testing.T) { 272 origGOOS, origHome := getRuntime, getHomeDir 273 getRuntime = func() string { return "darwin" } 274 getHomeDir = func() (string, error) { return tmp, nil } 275 defer func() { 276 getRuntime = origGOOS 277 getHomeDir = origHome 278 }() 279 280 dir, err := GetDataDir() 281 if err != nil { 282 t.Fatalf("unexpected error: %v", err) 283 } 284 expected := filepath.Join(tmp, "Library", "Application Support", "noteleaf") 285 if dir != expected { 286 t.Errorf("expected %s, got %s", expected, dir) 287 } 288 }) 289 290 t.Run("linux default with XDG_DATA_HOME unset", func(t *testing.T) { 291 origGOOS, origHome := getRuntime, getHomeDir 292 getRuntime = func() string { return "linux" } 293 getHomeDir = func() (string, error) { return tmp, nil } 294 defer func() { 295 getRuntime = origGOOS 296 getHomeDir = origHome 297 }() 298 299 os.Unsetenv("XDG_DATA_HOME") 300 301 dir, err := GetDataDir() 302 if err != nil { 303 t.Fatalf("unexpected error: %v", err) 304 } 305 expected := filepath.Join(tmp, ".local", "share", "noteleaf") 306 if dir != expected { 307 t.Errorf("expected %s, got %s", expected, dir) 308 } 309 }) 310}