cli + tui to publish to leaflet (wip) & manage tasks, notes & watch/read lists 馃崈
charm leaflet readability golang
29
fork

Configure Feed

Select the types of activity you want to include in your feed.

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}