cli + tui to publish to leaflet (wip) & manage tasks, notes & watch/read lists 馃崈
charm
leaflet
readability
golang
1package store
2
3import (
4 "os"
5 "path/filepath"
6 "runtime"
7 "testing"
8)
9
10func TestNewDatabase(t *testing.T) {
11 tempDir, err := os.MkdirTemp("", "noteleaf-db-test-*")
12 if err != nil {
13 t.Fatalf("Failed to create temp directory: %v", err)
14 }
15 defer os.RemoveAll(tempDir)
16
17 originalGetConfigDir := GetConfigDir
18 GetConfigDir = func() (string, error) {
19 return tempDir, nil
20 }
21 defer func() { GetConfigDir = originalGetConfigDir }()
22
23 t.Run("creates database successfully", func(t *testing.T) {
24 db, err := NewDatabase()
25 if err != nil {
26 t.Fatalf("NewDatabase failed: %v", err)
27 }
28 defer db.Close()
29
30 if db == nil {
31 t.Fatal("Database should not be nil")
32 }
33
34 dbPath := filepath.Join(tempDir, "noteleaf.db")
35 if _, err := os.Stat(dbPath); os.IsNotExist(err) {
36 t.Error("Database file should exist")
37 }
38
39 if db.GetPath() != dbPath {
40 t.Errorf("Expected database path %s, got %s", dbPath, db.GetPath())
41 }
42 })
43
44 t.Run("enables foreign keys", func(t *testing.T) {
45 db, err := NewDatabase()
46 if err != nil {
47 t.Fatalf("NewDatabase failed: %v", err)
48 }
49 defer db.Close()
50
51 var foreignKeys int
52 err = db.QueryRow("PRAGMA foreign_keys").Scan(&foreignKeys)
53 if err != nil {
54 t.Fatalf("Failed to check foreign keys: %v", err)
55 }
56
57 if foreignKeys != 1 {
58 t.Error("Foreign keys should be enabled")
59 }
60 })
61
62 t.Run("enables WAL mode", func(t *testing.T) {
63 db, err := NewDatabase()
64 if err != nil {
65 t.Fatalf("NewDatabase failed: %v", err)
66 }
67 defer db.Close()
68
69 var journalMode string
70 err = db.QueryRow("PRAGMA journal_mode").Scan(&journalMode)
71 if err != nil {
72 t.Fatalf("Failed to check journal mode: %v", err)
73 }
74
75 if journalMode != "wal" {
76 t.Errorf("Expected WAL journal mode, got %s", journalMode)
77 }
78 })
79
80 t.Run("runs migrations", func(t *testing.T) {
81 db, err := NewDatabase()
82 if err != nil {
83 t.Fatalf("NewDatabase failed: %v", err)
84 }
85 defer db.Close()
86
87 var count int
88 err = db.QueryRow("SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='migrations'").Scan(&count)
89 if err != nil {
90 t.Fatalf("Failed to check migrations table: %v", err)
91 }
92
93 if count != 1 {
94 t.Error("Migrations table should exist")
95 }
96
97 var migrationCount int
98 err = db.QueryRow("SELECT COUNT(*) FROM migrations").Scan(&migrationCount)
99 if err != nil {
100 t.Fatalf("Failed to count migrations: %v", err)
101 }
102
103 if migrationCount == 0 {
104 t.Error("At least one migration should be applied")
105 }
106 })
107
108 t.Run("creates migration runner", func(t *testing.T) {
109 db, err := NewDatabase()
110 if err != nil {
111 t.Fatalf("NewDatabase failed: %v", err)
112 }
113 defer db.Close()
114
115 runner := db.NewMigrationRunner()
116 if runner == nil {
117 t.Error("Migration runner should not be nil")
118 }
119 })
120
121 t.Run("closes database connection", func(t *testing.T) {
122 db, err := NewDatabase()
123 if err != nil {
124 t.Fatalf("NewDatabase failed: %v", err)
125 }
126
127 err = db.Close()
128 if err != nil {
129 t.Errorf("Close should not return error: %v", err)
130 }
131
132 err = db.Ping()
133 if err == nil {
134 t.Error("Database should be closed and ping should fail")
135 }
136 })
137}
138
139func TestDatabaseErrorHandling(t *testing.T) {
140 t.Run("handles GetConfigDir error", func(t *testing.T) {
141 originalGetConfigDir := GetConfigDir
142 GetConfigDir = func() (string, error) {
143 return "", os.ErrPermission
144 }
145 defer func() { GetConfigDir = originalGetConfigDir }()
146
147 _, err := NewDatabase()
148 if err == nil {
149 t.Error("NewDatabase should fail when GetConfigDir fails")
150 }
151 })
152
153 t.Run("handles invalid database path", func(t *testing.T) {
154 originalGetConfigDir := GetConfigDir
155 GetConfigDir = func() (string, error) {
156 return "/invalid/path/that/does/not/exist", nil
157 }
158 defer func() { GetConfigDir = originalGetConfigDir }()
159
160 _, err := NewDatabase()
161 if err == nil {
162 t.Error("NewDatabase should fail with invalid database path")
163 }
164 })
165
166 t.Run("handles invalid database connection", func(t *testing.T) {
167 originalGetConfigDir := GetConfigDir
168 GetConfigDir = func() (string, error) {
169 return "/dev/null", nil
170 }
171 defer func() { GetConfigDir = originalGetConfigDir }()
172
173 _, err := NewDatabase()
174 if err == nil {
175 t.Error("NewDatabase should fail when database path is invalid")
176 }
177 })
178
179 t.Run("handles migration failure during database creation", func(t *testing.T) {
180 tempDir, err := os.MkdirTemp("", "noteleaf-db-migration-fail-test-*")
181 if err != nil {
182 t.Fatalf("Failed to create temp directory: %v", err)
183 }
184 defer os.RemoveAll(tempDir)
185
186 originalGetConfigDir := GetConfigDir
187 GetConfigDir = func() (string, error) {
188 return tempDir, nil
189 }
190 defer func() { GetConfigDir = originalGetConfigDir }()
191
192 dbPath := filepath.Join(tempDir, "noteleaf.db")
193
194 // Create a corrupted database file that will cause issues
195 corruptedSQL := "this is not valid SQL and will cause failures"
196 err = os.WriteFile(dbPath, []byte(corruptedSQL), 0644)
197 if err != nil {
198 t.Fatalf("Failed to create corrupted database file: %v", err)
199 }
200
201 _, err = NewDatabase()
202 if err == nil {
203 t.Error("NewDatabase should fail when database file is corrupted")
204 }
205 })
206
207 t.Run("handles database file permission error", func(t *testing.T) {
208 if runtime.GOOS == "windows" {
209 t.Skip("Permission test not reliable on Windows")
210 }
211
212 tempDir, err := os.MkdirTemp("", "noteleaf-db-perm-test-*")
213 if err != nil {
214 t.Fatalf("Failed to create temp directory: %v", err)
215 }
216 defer os.RemoveAll(tempDir)
217
218 originalGetConfigDir := GetConfigDir
219 GetConfigDir = func() (string, error) {
220 return tempDir, nil
221 }
222 defer func() { GetConfigDir = originalGetConfigDir }()
223
224 err = os.Chmod(tempDir, 0555)
225 if err != nil {
226 t.Fatalf("Failed to change directory permissions: %v", err)
227 }
228 defer os.Chmod(tempDir, 0755)
229
230 _, err = NewDatabase()
231 if err == nil {
232 t.Error("NewDatabase should fail when database directory is not writable")
233 }
234 })
235}
236
237func TestDatabaseIntegration(t *testing.T) {
238 tempDir, err := os.MkdirTemp("", "noteleaf-db-integration-test-*")
239 if err != nil {
240 t.Fatalf("Failed to create temp directory: %v", err)
241 }
242 defer os.RemoveAll(tempDir)
243
244 originalGetConfigDir := GetConfigDir
245 GetConfigDir = func() (string, error) {
246 return tempDir, nil
247 }
248 defer func() { GetConfigDir = originalGetConfigDir }()
249
250 t.Run("multiple database instances use same file", func(t *testing.T) {
251 db1, err := NewDatabase()
252 if err != nil {
253 t.Fatalf("First NewDatabase failed: %v", err)
254 }
255 defer db1.Close()
256
257 db2, err := NewDatabase()
258 if err != nil {
259 t.Fatalf("Second NewDatabase failed: %v", err)
260 }
261 defer db2.Close()
262
263 if db1.GetPath() != db2.GetPath() {
264 t.Error("Both database instances should use the same file path")
265 }
266 })
267
268 t.Run("database survives connection close and reopen", func(t *testing.T) {
269 db1, err := NewDatabase()
270 if err != nil {
271 t.Fatalf("NewDatabase failed: %v", err)
272 }
273
274 _, err = db1.Exec("CREATE TABLE IF NOT EXISTS test_table (id INTEGER PRIMARY KEY, name TEXT)")
275 if err != nil {
276 t.Fatalf("Failed to create test table: %v", err)
277 }
278
279 _, err = db1.Exec("INSERT INTO test_table (name) VALUES (?)", "test_value")
280 if err != nil {
281 t.Fatalf("Failed to insert test data: %v", err)
282 }
283
284 db1.Close()
285
286 db2, err := NewDatabase()
287 if err != nil {
288 t.Fatalf("Second NewDatabase failed: %v", err)
289 }
290 defer db2.Close()
291
292 var name string
293 err = db2.QueryRow("SELECT name FROM test_table WHERE id = 1").Scan(&name)
294 if err != nil {
295 t.Fatalf("Failed to query test data: %v", err)
296 }
297
298 if name != "test_value" {
299 t.Errorf("Expected 'test_value', got '%s'", name)
300 }
301 })
302}