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 "strings"
8 "testing"
9
10 "github.com/BurntSushi/toml"
11 "github.com/stormlightlabs/noteleaf/internal/shared"
12)
13
14func TestDefaultConfig(t *testing.T) {
15 config := DefaultConfig()
16
17 if config == nil {
18 t.Fatal("DefaultConfig should not return nil")
19 }
20
21 expectedDefaults := map[string]any{
22 "DateFormat": "2006-01-02",
23 "ColorScheme": "default",
24 "DefaultView": "list",
25 "AutoArchive": false,
26 "SyncEnabled": false,
27 "ExportFormat": "json",
28 }
29
30 if config.DateFormat != expectedDefaults["DateFormat"] {
31 t.Errorf("Expected DateFormat %s, got %s", expectedDefaults["DateFormat"], config.DateFormat)
32 }
33 if config.ColorScheme != expectedDefaults["ColorScheme"] {
34 t.Errorf("Expected ColorScheme %s, got %s", expectedDefaults["ColorScheme"], config.ColorScheme)
35 }
36 if config.DefaultView != expectedDefaults["DefaultView"] {
37 t.Errorf("Expected DefaultView %s, got %s", expectedDefaults["DefaultView"], config.DefaultView)
38 }
39 if config.AutoArchive != expectedDefaults["AutoArchive"] {
40 t.Errorf("Expected AutoArchive %v, got %v", expectedDefaults["AutoArchive"], config.AutoArchive)
41 }
42 if config.SyncEnabled != expectedDefaults["SyncEnabled"] {
43 t.Errorf("Expected SyncEnabled %v, got %v", expectedDefaults["SyncEnabled"], config.SyncEnabled)
44 }
45 if config.ExportFormat != expectedDefaults["ExportFormat"] {
46 t.Errorf("Expected ExportFormat %s, got %s", expectedDefaults["ExportFormat"], config.ExportFormat)
47 }
48}
49
50func TestConfigOperations(t *testing.T) {
51 tempDir, cleanup := shared.CreateTempDir("noteleaf-config-test-*", t)
52 defer cleanup()
53
54 originalGetConfigDir := GetConfigDir
55 GetConfigDir = func() (string, error) {
56 return tempDir, nil
57 }
58 defer func() { GetConfigDir = originalGetConfigDir }()
59
60 t.Run("SaveConfig creates config file", func(t *testing.T) {
61 config := DefaultConfig()
62 config.ColorScheme = "dark"
63 config.AutoArchive = true
64
65 err := SaveConfig(config)
66 if err != nil {
67 t.Fatalf("SaveConfig failed: %v", err)
68 }
69
70 configPath := filepath.Join(tempDir, ".noteleaf.conf.toml")
71 if _, err := os.Stat(configPath); os.IsNotExist(err) {
72 t.Error("Config file should exist after SaveConfig")
73 }
74 })
75
76 t.Run("LoadConfig reads existing config", func(t *testing.T) {
77 config, err := LoadConfig()
78 if err != nil {
79 t.Fatalf("LoadConfig failed: %v", err)
80 }
81
82 if config.ColorScheme != "dark" {
83 t.Errorf("Expected ColorScheme 'dark', got '%s'", config.ColorScheme)
84 }
85 if !config.AutoArchive {
86 t.Error("Expected AutoArchive to be true")
87 }
88 })
89
90 t.Run("LoadConfig creates default when file doesn't exist", func(t *testing.T) {
91 configPath := filepath.Join(tempDir, ".noteleaf.conf.toml")
92 os.Remove(configPath)
93
94 config, err := LoadConfig()
95 if err != nil {
96 t.Fatalf("LoadConfig failed: %v", err)
97 }
98
99 if config.ColorScheme != "default" {
100 t.Errorf("Expected default ColorScheme 'default', got '%s'", config.ColorScheme)
101 }
102 if config.AutoArchive {
103 t.Error("Expected AutoArchive to be false by default")
104 }
105
106 if _, err := os.Stat(configPath); os.IsNotExist(err) {
107 t.Error("Config file should be created when it doesn't exist")
108 }
109 })
110
111 t.Run("GetConfigPath returns correct path", func(t *testing.T) {
112 configPath, err := GetConfigPath()
113 if err != nil {
114 t.Fatalf("GetConfigPath failed: %v", err)
115 }
116
117 expectedPath := filepath.Join(tempDir, ".noteleaf.conf.toml")
118 if configPath != expectedPath {
119 t.Errorf("Expected config path %s, got %s", expectedPath, configPath)
120 }
121 })
122}
123
124func TestConfigPersistence(t *testing.T) {
125 tempDir, cleanup := shared.CreateTempDir("noteleaf-config-persist-test-*", t)
126 defer cleanup()
127
128 originalGetConfigDir := GetConfigDir
129 GetConfigDir = func() (string, error) {
130 return tempDir, nil
131 }
132 defer func() { GetConfigDir = originalGetConfigDir }()
133
134 t.Run("config values persist across save/load cycles", func(t *testing.T) {
135 originalConfig := &Config{
136 DateFormat: "01/02/2006",
137 ColorScheme: "custom",
138 DefaultView: "kanban",
139 DefaultPriority: "high",
140 AutoArchive: true,
141 SyncEnabled: true,
142 SyncEndpoint: "https://api.example.com",
143 SyncToken: "secret-token",
144 ExportFormat: "csv",
145 MovieAPIKey: "movie-key",
146 BookAPIKey: "book-key",
147 }
148
149 err := SaveConfig(originalConfig)
150 if err != nil {
151 t.Fatalf("SaveConfig failed: %v", err)
152 }
153
154 loadedConfig, err := LoadConfig()
155 if err != nil {
156 t.Fatalf("LoadConfig failed: %v", err)
157 }
158
159 if loadedConfig.DateFormat != originalConfig.DateFormat {
160 t.Errorf("DateFormat not preserved: expected %s, got %s", originalConfig.DateFormat, loadedConfig.DateFormat)
161 }
162 if loadedConfig.ColorScheme != originalConfig.ColorScheme {
163 t.Errorf("ColorScheme not preserved: expected %s, got %s", originalConfig.ColorScheme, loadedConfig.ColorScheme)
164 }
165 if loadedConfig.DefaultView != originalConfig.DefaultView {
166 t.Errorf("DefaultView not preserved: expected %s, got %s", originalConfig.DefaultView, loadedConfig.DefaultView)
167 }
168 if loadedConfig.DefaultPriority != originalConfig.DefaultPriority {
169 t.Errorf("DefaultPriority not preserved: expected %s, got %s", originalConfig.DefaultPriority, loadedConfig.DefaultPriority)
170 }
171 if loadedConfig.AutoArchive != originalConfig.AutoArchive {
172 t.Errorf("AutoArchive not preserved: expected %v, got %v", originalConfig.AutoArchive, loadedConfig.AutoArchive)
173 }
174 if loadedConfig.SyncEnabled != originalConfig.SyncEnabled {
175 t.Errorf("SyncEnabled not preserved: expected %v, got %v", originalConfig.SyncEnabled, loadedConfig.SyncEnabled)
176 }
177 if loadedConfig.SyncEndpoint != originalConfig.SyncEndpoint {
178 t.Errorf("SyncEndpoint not preserved: expected %s, got %s", originalConfig.SyncEndpoint, loadedConfig.SyncEndpoint)
179 }
180 if loadedConfig.SyncToken != originalConfig.SyncToken {
181 t.Errorf("SyncToken not preserved: expected %s, got %s", originalConfig.SyncToken, loadedConfig.SyncToken)
182 }
183 if loadedConfig.ExportFormat != originalConfig.ExportFormat {
184 t.Errorf("ExportFormat not preserved: expected %s, got %s", originalConfig.ExportFormat, loadedConfig.ExportFormat)
185 }
186 if loadedConfig.MovieAPIKey != originalConfig.MovieAPIKey {
187 t.Errorf("MovieAPIKey not preserved: expected %s, got %s", originalConfig.MovieAPIKey, loadedConfig.MovieAPIKey)
188 }
189 if loadedConfig.BookAPIKey != originalConfig.BookAPIKey {
190 t.Errorf("BookAPIKey not preserved: expected %s, got %s", originalConfig.BookAPIKey, loadedConfig.BookAPIKey)
191 }
192 })
193}
194
195func TestConfigErrorHandling(t *testing.T) {
196 t.Run("LoadConfig handles invalid TOML", func(t *testing.T) {
197 tempDir, cleanup := shared.CreateTempDir("noteleaf-config-error-test-*", t)
198 defer cleanup()
199
200 originalGetConfigDir := GetConfigDir
201 GetConfigDir = func() (string, error) {
202 return tempDir, nil
203 }
204 defer func() { GetConfigDir = originalGetConfigDir }()
205
206 configPath := filepath.Join(tempDir, ".noteleaf.conf.toml")
207 invalidTOML := `[invalid toml content`
208 if err := os.WriteFile(configPath, []byte(invalidTOML), 0644); err != nil {
209 t.Fatalf("Failed to write invalid TOML: %v", err)
210 }
211
212 if _, err := LoadConfig(); err == nil {
213 t.Error("LoadConfig should fail with invalid TOML")
214 }
215 })
216
217 t.Run("LoadConfig handles file read permission error", func(t *testing.T) {
218 if runtime.GOOS == "windows" {
219 t.Skip("Permission test not reliable on Windows")
220 }
221
222 tempDir, cleanup := shared.CreateTempDir("noteleaf-config-perm-test-*", t)
223 defer cleanup()
224
225 originalGetConfigDir := GetConfigDir
226 GetConfigDir = func() (string, error) {
227 return tempDir, nil
228 }
229 defer func() { GetConfigDir = originalGetConfigDir }()
230
231 configPath := filepath.Join(tempDir, ".noteleaf.conf.toml")
232 validTOML := `color_scheme = "dark"`
233 if err := os.WriteFile(configPath, []byte(validTOML), 0644); err != nil {
234 t.Fatalf("Failed to write config file: %v", err)
235 }
236
237 if err := os.Chmod(configPath, 0000); err != nil {
238 t.Fatalf("Failed to change file permissions: %v", err)
239 }
240 defer os.Chmod(configPath, 0644)
241
242 if _, err := LoadConfig(); err == nil {
243 t.Error("LoadConfig should fail when config file is not readable")
244 }
245 })
246
247 t.Run("LoadConfig handles GetConfigDir error", func(t *testing.T) {
248 originalGetConfigDir := GetConfigDir
249 GetConfigDir = func() (string, error) {
250 return "", os.ErrPermission
251 }
252 defer func() { GetConfigDir = originalGetConfigDir }()
253
254 _, err := LoadConfig()
255 if err == nil {
256 t.Error("LoadConfig should fail when GetConfigDir fails")
257 }
258 })
259
260 t.Run("LoadConfig handles SaveConfig failure when creating default", func(t *testing.T) {
261 tempDir, cleanup := shared.CreateTempDir("noteleaf-config-save-fail-test-*", t)
262 defer cleanup()
263
264 _ = filepath.Join(tempDir, ".noteleaf.conf.toml")
265
266 callCount := 0
267 originalGetConfigDir := GetConfigDir
268 GetConfigDir = func() (string, error) {
269 callCount++
270 if callCount == 1 {
271 return tempDir, nil
272 }
273 return "", os.ErrPermission
274 }
275 defer func() { GetConfigDir = originalGetConfigDir }()
276
277 if _, err := LoadConfig(); err == nil {
278 t.Error("LoadConfig should fail when SaveConfig fails during default config creation")
279 }
280 })
281
282 t.Run("SaveConfig handles directory creation failure", func(t *testing.T) {
283 originalGetConfigDir := GetConfigDir
284 GetConfigDir = func() (string, error) {
285 return "/invalid/path/that/cannot/be/created", nil
286 }
287 defer func() { GetConfigDir = originalGetConfigDir }()
288
289 config := DefaultConfig()
290 if err := SaveConfig(config); err == nil {
291 t.Error("SaveConfig should fail when config directory cannot be accessed")
292 }
293 })
294
295 t.Run("SaveConfig handles file write permission error", func(t *testing.T) {
296 if runtime.GOOS == "windows" {
297 t.Skip("Permission test not reliable on Windows")
298 }
299
300 tempDir, cleanup := shared.CreateTempDir("noteleaf-config-write-perm-test-*", t)
301 defer cleanup()
302
303 originalGetConfigDir := GetConfigDir
304 GetConfigDir = func() (string, error) {
305 return tempDir, nil
306 }
307 defer func() { GetConfigDir = originalGetConfigDir }()
308
309 if err := os.Chmod(tempDir, 0555); err != nil {
310 t.Fatalf("Failed to change directory permissions: %v", err)
311 }
312 defer os.Chmod(tempDir, 0755)
313
314 config := DefaultConfig()
315 if err := SaveConfig(config); err == nil {
316 t.Error("SaveConfig should fail when directory is not writable")
317 }
318 })
319
320 t.Run("GetConfigPath handles GetConfigDir error", func(t *testing.T) {
321 originalGetConfigDir := GetConfigDir
322 GetConfigDir = func() (string, error) {
323 return "", os.ErrPermission
324 }
325 defer func() { GetConfigDir = originalGetConfigDir }()
326
327 _, err := GetConfigPath()
328 if err == nil {
329 t.Error("GetConfigPath should fail when GetConfigDir fails")
330 }
331 })
332}
333
334func TestGetConfigDir(t *testing.T) {
335 t.Run("returns correct directory based on OS", func(t *testing.T) {
336 configDir, err := GetConfigDir()
337 if err != nil {
338 t.Fatalf("GetConfigDir failed: %v", err)
339 }
340
341 if configDir == "" {
342 t.Error("Config directory should not be empty")
343 }
344
345 if _, err := os.Stat(configDir); os.IsNotExist(err) {
346 t.Error("Config directory should be created if it doesn't exist")
347 }
348
349 if filepath.Base(configDir) != "noteleaf" {
350 t.Errorf("Config directory should end with 'noteleaf', got: %s", configDir)
351 }
352 })
353
354 t.Run("creates directory if it doesn't exist", func(t *testing.T) {
355 tempDir, cleanup := shared.CreateTempDir("noteleaf-test-*", t)
356 defer cleanup()
357
358 var originalEnv string
359 var envVar string
360 var expectedPath string
361 switch runtime.GOOS {
362 case "windows":
363 envVar = "APPDATA"
364 originalEnv = os.Getenv("APPDATA")
365 os.Setenv("APPDATA", tempDir)
366 expectedPath = filepath.Join(tempDir, "noteleaf")
367 case "darwin":
368 envVar = "HOME"
369 originalEnv = os.Getenv("HOME")
370 os.Setenv("HOME", tempDir)
371 expectedPath = filepath.Join(tempDir, "Library", "Application Support", "noteleaf")
372 default:
373 envVar = "XDG_CONFIG_HOME"
374 originalEnv = os.Getenv("XDG_CONFIG_HOME")
375 os.Setenv("XDG_CONFIG_HOME", tempDir)
376 expectedPath = filepath.Join(tempDir, "noteleaf")
377 }
378 defer os.Setenv(envVar, originalEnv)
379
380 configDir, err := GetConfigDir()
381 if err != nil {
382 t.Fatalf("GetConfigDir failed: %v", err)
383 }
384
385 if configDir != expectedPath {
386 t.Errorf("Expected config dir %s, got %s", expectedPath, configDir)
387 }
388
389 if _, err := os.Stat(configDir); os.IsNotExist(err) {
390 t.Error("Config directory should be created")
391 }
392 })
393
394 t.Run("handles missing environment variables", func(t *testing.T) {
395 switch runtime.GOOS {
396 case "windows":
397 originalAppData := os.Getenv("APPDATA")
398 os.Unsetenv("APPDATA")
399 defer os.Setenv("APPDATA", originalAppData)
400
401 _, err := GetConfigDir()
402 if err == nil {
403 t.Error("GetConfigDir should fail when APPDATA is not set on Windows")
404 }
405 case "darwin":
406 originalHome := os.Getenv("HOME")
407
408 tempHome, err := os.MkdirTemp("", "noteleaf-home-test-*")
409 if err != nil {
410 t.Fatalf("Failed to create temp home: %v", err)
411 }
412 defer os.RemoveAll(tempHome)
413 os.Setenv("HOME", tempHome)
414 defer os.Setenv("HOME", originalHome)
415
416 configDir, err := GetConfigDir()
417 if err != nil {
418 t.Fatalf("GetConfigDir should work with HOME on macOS: %v", err)
419 }
420
421 expectedPath := filepath.Join(tempHome, "Library", "Application Support", "noteleaf")
422 if configDir != expectedPath {
423 t.Errorf("Expected config dir %s, got %s", expectedPath, configDir)
424 }
425 default:
426 originalXDG := os.Getenv("XDG_CONFIG_HOME")
427 originalHome := os.Getenv("HOME")
428 os.Unsetenv("XDG_CONFIG_HOME")
429
430 tempHome, err := os.MkdirTemp("", "noteleaf-home-test-*")
431 if err != nil {
432 t.Fatalf("Failed to create temp home: %v", err)
433 }
434 defer os.RemoveAll(tempHome)
435 os.Setenv("HOME", tempHome)
436
437 defer func() {
438 os.Setenv("XDG_CONFIG_HOME", originalXDG)
439 os.Setenv("HOME", originalHome)
440 }()
441
442 configDir, err := GetConfigDir()
443 if err != nil {
444 t.Fatalf("GetConfigDir should work with HOME fallback: %v", err)
445 }
446
447 expectedPath := filepath.Join(tempHome, ".config", "noteleaf")
448 if configDir != expectedPath {
449 t.Errorf("Expected config dir %s, got %s", expectedPath, configDir)
450 }
451 }
452 })
453
454 t.Run("handles HOME directory lookup failure on Unix", func(t *testing.T) {
455 if runtime.GOOS == "windows" {
456 t.Skip("HOME directory test not applicable on Windows")
457 }
458
459 originalXDG := os.Getenv("XDG_CONFIG_HOME")
460 originalHome := os.Getenv("HOME")
461 os.Unsetenv("XDG_CONFIG_HOME")
462 os.Unsetenv("HOME")
463
464 defer func() {
465 os.Setenv("XDG_CONFIG_HOME", originalXDG)
466 os.Setenv("HOME", originalHome)
467 }()
468
469 _, err := GetConfigDir()
470 if err == nil {
471 t.Error("GetConfigDir should fail when both XDG_CONFIG_HOME and HOME are not available")
472 }
473 })
474
475 t.Run("handles directory creation permission failure", func(t *testing.T) {
476 if runtime.GOOS == "windows" {
477 t.Skip("Permission test not reliable on Windows")
478 }
479
480 if runtime.GOOS == "darwin" {
481 t.Skip("Permission test not reliable on macOS with nested Library/Application Support paths")
482 }
483
484 tempParent, err := os.MkdirTemp("", "noteleaf-parent-test-*")
485 if err != nil {
486 t.Fatalf("Failed to create temp parent directory: %v", err)
487 }
488 defer os.RemoveAll(tempParent)
489
490 err = os.Chmod(tempParent, 0555)
491 if err != nil {
492 t.Fatalf("Failed to change parent directory permissions: %v", err)
493 }
494 defer os.Chmod(tempParent, 0755)
495
496 var originalEnv string
497 envVar := "XDG_CONFIG_HOME"
498 originalEnv = os.Getenv("XDG_CONFIG_HOME")
499 os.Setenv("XDG_CONFIG_HOME", tempParent)
500 defer os.Setenv(envVar, originalEnv)
501
502 _, err = GetConfigDir()
503 if err == nil {
504 t.Error("GetConfigDir should fail when directory creation is not permitted")
505 }
506 })
507}
508
509func TestEnvironmentVariableOverrides(t *testing.T) {
510 t.Run("NOTELEAF_CONFIG overrides default config path for LoadConfig", func(t *testing.T) {
511 tempDir, cleanup := shared.CreateTempDir("noteleaf-env-config-test-*", t)
512 defer cleanup()
513
514 customConfigPath := filepath.Join(tempDir, "custom-config.toml")
515 originalEnv := os.Getenv("NOTELEAF_CONFIG")
516 os.Setenv("NOTELEAF_CONFIG", customConfigPath)
517 defer os.Setenv("NOTELEAF_CONFIG", originalEnv)
518
519 customConfig := DefaultConfig()
520 customConfig.ColorScheme = "custom-env-test"
521 if err := SaveConfig(customConfig); err != nil {
522 t.Fatalf("Failed to save custom config: %v", err)
523 }
524
525 loadedConfig, err := LoadConfig()
526 if err != nil {
527 t.Fatalf("LoadConfig failed: %v", err)
528 }
529
530 if loadedConfig.ColorScheme != "custom-env-test" {
531 t.Errorf("Expected ColorScheme 'custom-env-test', got '%s'", loadedConfig.ColorScheme)
532 }
533 })
534
535 t.Run("NOTELEAF_CONFIG overrides default config path for SaveConfig", func(t *testing.T) {
536 tempDir, cleanup := shared.CreateTempDir("noteleaf-env-save-test-*", t)
537 defer cleanup()
538
539 customConfigPath := filepath.Join(tempDir, "subdir", "config.toml")
540 originalEnv := os.Getenv("NOTELEAF_CONFIG")
541 os.Setenv("NOTELEAF_CONFIG", customConfigPath)
542 defer os.Setenv("NOTELEAF_CONFIG", originalEnv)
543
544 config := DefaultConfig()
545 config.DefaultView = "kanban-env"
546 if err := SaveConfig(config); err != nil {
547 t.Fatalf("SaveConfig failed: %v", err)
548 }
549
550 if _, err := os.Stat(customConfigPath); os.IsNotExist(err) {
551 t.Error("Config file should be created at custom NOTELEAF_CONFIG path")
552 }
553
554 data, err := os.ReadFile(customConfigPath)
555 if err != nil {
556 t.Fatalf("Failed to read config file: %v", err)
557 }
558
559 loadedConfig := DefaultConfig()
560 if err := toml.Unmarshal(data, loadedConfig); err != nil {
561 t.Fatalf("Failed to parse config: %v", err)
562 }
563
564 if loadedConfig.DefaultView != "kanban-env" {
565 t.Errorf("Expected DefaultView 'kanban-env', got '%s'", loadedConfig.DefaultView)
566 }
567 })
568
569 t.Run("NOTELEAF_CONFIG overrides default config path for GetConfigPath", func(t *testing.T) {
570 tempDir, cleanup := shared.CreateTempDir("noteleaf-env-path-test-*", t)
571 defer cleanup()
572
573 customConfigPath := filepath.Join(tempDir, "my-config.toml")
574 originalEnv := os.Getenv("NOTELEAF_CONFIG")
575 os.Setenv("NOTELEAF_CONFIG", customConfigPath)
576 defer os.Setenv("NOTELEAF_CONFIG", originalEnv)
577
578 path, err := GetConfigPath()
579 if err != nil {
580 t.Fatalf("GetConfigPath failed: %v", err)
581 }
582
583 if path != customConfigPath {
584 t.Errorf("Expected config path '%s', got '%s'", customConfigPath, path)
585 }
586 })
587
588 t.Run("NOTELEAF_CONFIG creates parent directories if needed", func(t *testing.T) {
589 tempDir, cleanup := shared.CreateTempDir("noteleaf-env-mkdir-test-*", t)
590 defer cleanup()
591
592 customConfigPath := filepath.Join(tempDir, "nested", "deep", "config.toml")
593 originalEnv := os.Getenv("NOTELEAF_CONFIG")
594 os.Setenv("NOTELEAF_CONFIG", customConfigPath)
595 defer os.Setenv("NOTELEAF_CONFIG", originalEnv)
596
597 config := DefaultConfig()
598 if err := SaveConfig(config); err != nil {
599 t.Fatalf("SaveConfig should create parent directories: %v", err)
600 }
601
602 if _, err := os.Stat(customConfigPath); os.IsNotExist(err) {
603 t.Error("Config file should be created with parent directories")
604 }
605 })
606}
607
608func TestGetDataDir(t *testing.T) {
609 t.Run("NOTELEAF_DATA_DIR overrides default data directory", func(t *testing.T) {
610 tempDir, cleanup := shared.CreateTempDir("noteleaf-data-dir-test-*", t)
611 defer cleanup()
612
613 customDataDir := filepath.Join(tempDir, "my-data")
614 originalEnv := os.Getenv("NOTELEAF_DATA_DIR")
615 os.Setenv("NOTELEAF_DATA_DIR", customDataDir)
616 defer os.Setenv("NOTELEAF_DATA_DIR", originalEnv)
617
618 dataDir, err := GetDataDir()
619 if err != nil {
620 t.Fatalf("GetDataDir failed: %v", err)
621 }
622
623 if dataDir != customDataDir {
624 t.Errorf("Expected data dir '%s', got '%s'", customDataDir, dataDir)
625 }
626
627 if _, err := os.Stat(customDataDir); os.IsNotExist(err) {
628 t.Error("Data directory should be created")
629 }
630 })
631
632 t.Run("GetDataDir returns correct directory based on OS", func(t *testing.T) {
633 originalEnv := os.Getenv("NOTELEAF_DATA_DIR")
634 os.Unsetenv("NOTELEAF_DATA_DIR")
635 defer os.Setenv("NOTELEAF_DATA_DIR", originalEnv)
636
637 dataDir, err := GetDataDir()
638 if err != nil {
639 t.Fatalf("GetDataDir failed: %v", err)
640 }
641
642 if dataDir == "" {
643 t.Error("Data directory should not be empty")
644 }
645
646 if filepath.Base(dataDir) != "noteleaf" {
647 t.Errorf("Data directory should end with 'noteleaf', got: %s", dataDir)
648 }
649 })
650
651 t.Run("GetDataDir handles NOTELEAF_DATA_DIR with nested path", func(t *testing.T) {
652 tempDir, cleanup := shared.CreateTempDir("noteleaf-nested-data-test-*", t)
653 defer cleanup()
654
655 customDataDir := filepath.Join(tempDir, "level1", "level2", "data")
656 originalEnv := os.Getenv("NOTELEAF_DATA_DIR")
657 os.Setenv("NOTELEAF_DATA_DIR", customDataDir)
658 defer os.Setenv("NOTELEAF_DATA_DIR", originalEnv)
659
660 dataDir, err := GetDataDir()
661 if err != nil {
662 t.Fatalf("GetDataDir should create nested directories: %v", err)
663 }
664
665 if dataDir != customDataDir {
666 t.Errorf("Expected data dir '%s', got '%s'", customDataDir, dataDir)
667 }
668
669 if _, err := os.Stat(customDataDir); os.IsNotExist(err) {
670 t.Error("Nested data directories should be created")
671 }
672 })
673
674 t.Run("GetDataDir uses platform-specific defaults", func(t *testing.T) {
675 originalEnv := os.Getenv("NOTELEAF_DATA_DIR")
676 os.Unsetenv("NOTELEAF_DATA_DIR")
677 defer os.Setenv("NOTELEAF_DATA_DIR", originalEnv)
678
679 tempHome, err := os.MkdirTemp("", "noteleaf-home-test-*")
680 if err != nil {
681 t.Fatalf("Failed to create temp home: %v", err)
682 }
683 defer os.RemoveAll(tempHome)
684
685 var envVar, originalValue string
686 switch runtime.GOOS {
687 case "windows":
688 envVar = "LOCALAPPDATA"
689 originalValue = os.Getenv("LOCALAPPDATA")
690 os.Setenv("LOCALAPPDATA", tempHome)
691 case "darwin":
692 envVar = "HOME"
693 originalValue = os.Getenv("HOME")
694 os.Setenv("HOME", tempHome)
695 default:
696 envVar = "XDG_DATA_HOME"
697 originalValue = os.Getenv("XDG_DATA_HOME")
698 os.Setenv("XDG_DATA_HOME", tempHome)
699 }
700 defer os.Setenv(envVar, originalValue)
701
702 dataDir, err := GetDataDir()
703 if err != nil {
704 t.Fatalf("GetDataDir failed: %v", err)
705 }
706
707 if !strings.Contains(dataDir, tempHome) {
708 t.Errorf("Data directory should be under temp home, got: %s", dataDir)
709 }
710 })
711}