cli + tui to publish to leaflet (wip) & manage tasks, notes & watch/read lists 馃崈
charm leaflet readability golang
at main 711 lines 22 kB view raw
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}