cli + tui to publish to leaflet (wip) & manage tasks, notes & watch/read lists ๐Ÿƒ
charm leaflet readability golang

build: handler & UI tests

+492
+365
cmd/handlers/handlers_test.go
··· 4 "context" 5 "os" 6 "path/filepath" 7 "testing" 8 "time" 9 ··· 211 } 212 213 }) 214 } 215 216 func TestIntegration(t *testing.T) {
··· 4 "context" 5 "os" 6 "path/filepath" 7 + "runtime" 8 + "strings" 9 "testing" 10 "time" 11 ··· 213 } 214 215 }) 216 + } 217 + 218 + func TestSetupErrorHandling(t *testing.T) { 219 + t.Run("handles GetConfigDir error", func(t *testing.T) { 220 + originalXDG := os.Getenv("XDG_CONFIG_HOME") 221 + originalHome := os.Getenv("HOME") 222 + 223 + if runtime.GOOS == "windows" { 224 + originalAppData := os.Getenv("APPDATA") 225 + os.Unsetenv("APPDATA") 226 + defer os.Setenv("APPDATA", originalAppData) 227 + } else { 228 + os.Unsetenv("XDG_CONFIG_HOME") 229 + os.Unsetenv("HOME") 230 + } 231 + 232 + defer func() { 233 + os.Setenv("XDG_CONFIG_HOME", originalXDG) 234 + os.Setenv("HOME", originalHome) 235 + }() 236 + 237 + ctx := context.Background() 238 + err := Setup(ctx, []string{}) 239 + 240 + if err == nil { 241 + t.Error("Setup should fail when GetConfigDir fails") 242 + } 243 + if !strings.Contains(err.Error(), "failed to get config directory") { 244 + t.Errorf("Expected config directory error, got: %v", err) 245 + } 246 + }) 247 + 248 + t.Run("handles database creation error", func(t *testing.T) { 249 + tempDir, err := os.MkdirTemp("", "noteleaf-readonly-test-*") 250 + if err != nil { 251 + t.Fatalf("Failed to create temp dir: %v", err) 252 + } 253 + defer os.RemoveAll(tempDir) 254 + 255 + noteleafDir := filepath.Join(tempDir, "noteleaf") 256 + if err := os.MkdirAll(noteleafDir, 0755); err != nil { 257 + t.Fatalf("Failed to create noteleaf dir: %v", err) 258 + } 259 + 260 + if err := os.Chmod(noteleafDir, 0444); err != nil { 261 + t.Fatalf("Failed to make directory read-only: %v", err) 262 + } 263 + 264 + defer os.Chmod(noteleafDir, 0755) 265 + 266 + oldConfigHome := os.Getenv("XDG_CONFIG_HOME") 267 + os.Setenv("XDG_CONFIG_HOME", tempDir) 268 + defer os.Setenv("XDG_CONFIG_HOME", oldConfigHome) 269 + 270 + ctx := context.Background() 271 + err = Setup(ctx, []string{}) 272 + 273 + if err == nil { 274 + t.Error("Setup should fail when database creation fails") 275 + } 276 + if !strings.Contains(err.Error(), "failed to initialize database") { 277 + t.Errorf("Expected database initialization error, got: %v", err) 278 + } 279 + }) 280 + 281 + t.Run("handles config loading error", func(t *testing.T) { 282 + tempDir, err := os.MkdirTemp("", "noteleaf-config-error-test-*") 283 + if err != nil { 284 + t.Fatalf("Failed to create temp dir: %v", err) 285 + } 286 + defer os.RemoveAll(tempDir) 287 + 288 + noteleafDir := filepath.Join(tempDir, "noteleaf") 289 + if err := os.MkdirAll(noteleafDir, 0755); err != nil { 290 + t.Fatalf("Failed to create noteleaf dir: %v", err) 291 + } 292 + 293 + configPath := filepath.Join(noteleafDir, ".noteleaf.conf.toml") 294 + invalidTOML := `[invalid toml content` 295 + if err := os.WriteFile(configPath, []byte(invalidTOML), 0644); err != nil { 296 + t.Fatalf("Failed to write invalid config: %v", err) 297 + } 298 + 299 + oldConfigHome := os.Getenv("XDG_CONFIG_HOME") 300 + os.Setenv("XDG_CONFIG_HOME", tempDir) 301 + defer os.Setenv("XDG_CONFIG_HOME", oldConfigHome) 302 + 303 + ctx := context.Background() 304 + err = Setup(ctx, []string{}) 305 + 306 + if err == nil { 307 + t.Error("Setup should fail when config loading fails") 308 + } 309 + if !strings.Contains(err.Error(), "failed to create configuration") { 310 + t.Errorf("Expected configuration error, got: %v", err) 311 + } 312 + }) 313 + } 314 + 315 + func TestResetErrorHandling(t *testing.T) { 316 + t.Run("handles GetConfigDir error", func(t *testing.T) { 317 + originalXDG := os.Getenv("XDG_CONFIG_HOME") 318 + originalHome := os.Getenv("HOME") 319 + 320 + if runtime.GOOS == "windows" { 321 + originalAppData := os.Getenv("APPDATA") 322 + os.Unsetenv("APPDATA") 323 + defer os.Setenv("APPDATA", originalAppData) 324 + } else { 325 + os.Unsetenv("XDG_CONFIG_HOME") 326 + os.Unsetenv("HOME") 327 + } 328 + 329 + defer func() { 330 + os.Setenv("XDG_CONFIG_HOME", originalXDG) 331 + os.Setenv("HOME", originalHome) 332 + }() 333 + 334 + ctx := context.Background() 335 + err := Reset(ctx, []string{}) 336 + 337 + if err == nil { 338 + t.Error("Reset should fail when GetConfigDir fails") 339 + } 340 + if !strings.Contains(err.Error(), "failed to get config directory") { 341 + t.Errorf("Expected config directory error, got: %v", err) 342 + } 343 + }) 344 + 345 + t.Run("handles GetConfigPath error", func(t *testing.T) { 346 + tempDir, err := os.MkdirTemp("", "noteleaf-reset-error-test-*") 347 + if err != nil { 348 + t.Fatalf("Failed to create temp dir: %v", err) 349 + } 350 + defer os.RemoveAll(tempDir) 351 + 352 + // Set up a scenario where GetConfigPath might fail 353 + // This is tricky since GetConfigPath internally calls GetConfigDir 354 + // We'll create a scenario where the config dir exists but GetConfigPath fails 355 + oldConfigHome := os.Getenv("XDG_CONFIG_HOME") 356 + os.Setenv("XDG_CONFIG_HOME", tempDir) 357 + defer os.Setenv("XDG_CONFIG_HOME", oldConfigHome) 358 + 359 + noteleafDir := filepath.Join(tempDir, "noteleaf") 360 + if err := os.MkdirAll(noteleafDir, 0755); err != nil { 361 + t.Fatalf("Failed to create noteleaf dir: %v", err) 362 + } 363 + 364 + dbPath := filepath.Join(noteleafDir, "noteleaf.db") 365 + if err := os.WriteFile(dbPath, []byte("test"), 0644); err != nil { 366 + t.Fatalf("Failed to create test db file: %v", err) 367 + } 368 + 369 + if err := os.Chmod(noteleafDir, 0444); err != nil { 370 + t.Fatalf("Failed to make directory read-only: %v", err) 371 + } 372 + 373 + defer os.Chmod(noteleafDir, 0755) 374 + 375 + ctx := context.Background() 376 + err = Reset(ctx, []string{}) 377 + 378 + if err == nil { 379 + t.Error("Reset should fail when file removal fails") 380 + } 381 + if !strings.Contains(err.Error(), "failed to remove") && !strings.Contains(err.Error(), "failed to get config path") { 382 + t.Errorf("Expected removal or config path error, got: %v", err) 383 + } 384 + }) 385 + } 386 + 387 + func TestStatusErrorHandling(t *testing.T) { 388 + t.Run("handles GetConfigDir error", func(t *testing.T) { 389 + originalXDG := os.Getenv("XDG_CONFIG_HOME") 390 + originalHome := os.Getenv("HOME") 391 + 392 + if runtime.GOOS == "windows" { 393 + originalAppData := os.Getenv("APPDATA") 394 + os.Unsetenv("APPDATA") 395 + defer os.Setenv("APPDATA", originalAppData) 396 + } else { 397 + os.Unsetenv("XDG_CONFIG_HOME") 398 + os.Unsetenv("HOME") 399 + } 400 + 401 + defer func() { 402 + os.Setenv("XDG_CONFIG_HOME", originalXDG) 403 + os.Setenv("HOME", originalHome) 404 + }() 405 + 406 + ctx := context.Background() 407 + err := Status(ctx, []string{}) 408 + 409 + if err == nil { 410 + t.Error("Status should fail when GetConfigDir fails") 411 + } 412 + if !strings.Contains(err.Error(), "failed to get config directory") { 413 + t.Errorf("Expected config directory error, got: %v", err) 414 + } 415 + }) 416 + 417 + t.Run("handles GetConfigPath error after database exists", func(t *testing.T) { 418 + _ = createTestDir(t) 419 + ctx := context.Background() 420 + 421 + err := Setup(ctx, []string{}) 422 + if err != nil { 423 + t.Fatalf("Setup failed: %v", err) 424 + } 425 + 426 + // Now we have a database, but let's create a scenario where GetConfigPath fails 427 + // This is challenging since GetConfigPath uses GetConfigDir which we already tested 428 + // Instead, let's test the database connection error scenario 429 + 430 + // Remove the database to cause NewDatabase to fail in Status 431 + configDir, _ := store.GetConfigDir() 432 + dbPath := filepath.Join(configDir, "noteleaf.db") 433 + os.Remove(dbPath) 434 + 435 + // Create a directory with the same name as the database file 436 + // This will cause database connection to fail 437 + if err := os.MkdirAll(dbPath, 0755); err != nil { 438 + t.Fatalf("Failed to create directory: %v", err) 439 + } 440 + 441 + err = Status(ctx, []string{}) 442 + if err == nil { 443 + t.Error("Status should fail when database connection fails") 444 + } 445 + if !strings.Contains(err.Error(), "failed to connect to database") { 446 + t.Errorf("Expected database connection error, got: %v", err) 447 + } 448 + }) 449 + 450 + t.Run("handles migration errors", func(t *testing.T) { 451 + _ = createTestDir(t) 452 + ctx := context.Background() 453 + 454 + err := Setup(ctx, []string{}) 455 + if err != nil { 456 + t.Fatalf("Setup failed: %v", err) 457 + } 458 + 459 + // Corrupt the migrations table to cause GetAppliedMigrations to fail 460 + db, err := store.NewDatabase() 461 + if err != nil { 462 + t.Fatalf("Failed to connect to database: %v", err) 463 + } 464 + 465 + _, err = db.Exec("DROP TABLE migrations") 466 + if err != nil { 467 + t.Fatalf("Failed to drop migrations table: %v", err) 468 + } 469 + 470 + _, err = db.Exec("CREATE TABLE migrations (invalid_schema TEXT)") 471 + if err != nil { 472 + t.Fatalf("Failed to create corrupted migrations table: %v", err) 473 + } 474 + db.Close() 475 + 476 + err = Status(ctx, []string{}) 477 + if err == nil { 478 + t.Error("Status should fail when migration queries fail") 479 + } 480 + if !strings.Contains(err.Error(), "failed to get") && !strings.Contains(err.Error(), "migrations") { 481 + t.Errorf("Expected migration-related error, got: %v", err) 482 + } 483 + }) 484 + } 485 + 486 + func TestErrorScenarios(t *testing.T) { 487 + errorTests := []struct { 488 + name string 489 + setupFunc func(t *testing.T) (cleanup func()) 490 + handlerFunc func(ctx context.Context, args []string) error 491 + expectError bool 492 + errorSubstr string 493 + }{ 494 + { 495 + name: "Setup with invalid environment", 496 + setupFunc: func(t *testing.T) func() { 497 + if runtime.GOOS == "windows" { 498 + original := os.Getenv("APPDATA") 499 + os.Unsetenv("APPDATA") 500 + return func() { os.Setenv("APPDATA", original) } 501 + } else { 502 + originalXDG := os.Getenv("XDG_CONFIG_HOME") 503 + originalHome := os.Getenv("HOME") 504 + os.Unsetenv("XDG_CONFIG_HOME") 505 + os.Unsetenv("HOME") 506 + return func() { 507 + os.Setenv("XDG_CONFIG_HOME", originalXDG) 508 + os.Setenv("HOME", originalHome) 509 + } 510 + } 511 + }, 512 + handlerFunc: Setup, 513 + expectError: true, 514 + errorSubstr: "config directory", 515 + }, 516 + { 517 + name: "Reset with invalid environment", 518 + setupFunc: func(t *testing.T) func() { 519 + if runtime.GOOS == "windows" { 520 + original := os.Getenv("APPDATA") 521 + os.Unsetenv("APPDATA") 522 + return func() { os.Setenv("APPDATA", original) } 523 + } else { 524 + originalXDG := os.Getenv("XDG_CONFIG_HOME") 525 + originalHome := os.Getenv("HOME") 526 + os.Unsetenv("XDG_CONFIG_HOME") 527 + os.Unsetenv("HOME") 528 + return func() { 529 + os.Setenv("XDG_CONFIG_HOME", originalXDG) 530 + os.Setenv("HOME", originalHome) 531 + } 532 + } 533 + }, 534 + handlerFunc: Reset, 535 + expectError: true, 536 + errorSubstr: "config directory", 537 + }, 538 + { 539 + name: "Status with invalid environment", 540 + setupFunc: func(t *testing.T) func() { 541 + if runtime.GOOS == "windows" { 542 + original := os.Getenv("APPDATA") 543 + os.Unsetenv("APPDATA") 544 + return func() { os.Setenv("APPDATA", original) } 545 + } else { 546 + originalXDG := os.Getenv("XDG_CONFIG_HOME") 547 + originalHome := os.Getenv("HOME") 548 + os.Unsetenv("XDG_CONFIG_HOME") 549 + os.Unsetenv("HOME") 550 + return func() { 551 + os.Setenv("XDG_CONFIG_HOME", originalXDG) 552 + os.Setenv("HOME", originalHome) 553 + } 554 + } 555 + }, 556 + handlerFunc: Status, 557 + expectError: true, 558 + errorSubstr: "config directory", 559 + }, 560 + } 561 + 562 + for _, tt := range errorTests { 563 + t.Run(tt.name, func(t *testing.T) { 564 + cleanup := tt.setupFunc(t) 565 + defer cleanup() 566 + 567 + ctx := context.Background() 568 + err := tt.handlerFunc(ctx, []string{}) 569 + 570 + if tt.expectError && err == nil { 571 + t.Errorf("Expected error containing %q, got nil", tt.errorSubstr) 572 + } else if !tt.expectError && err != nil { 573 + t.Errorf("Expected no error, got: %v", err) 574 + } else if tt.expectError && err != nil && !strings.Contains(err.Error(), tt.errorSubstr) { 575 + t.Errorf("Expected error containing %q, got: %v", tt.errorSubstr, err) 576 + } 577 + }) 578 + } 579 } 580 581 func TestIntegration(t *testing.T) {
+127
internal/ui/ui_test.go
··· 35 expected string 36 }{ 37 {Cumin, "#BF976F"}, 38 {Cherry, "#FF388B"}, 39 {Julep, "#00FFB2"}, 40 {Butter, "#FFFAF1"}, 41 {Key(-1), ""}, 42 } 43 ··· 56 expected string 57 }{ 58 {Cumin, "Cumin"}, 59 {Cherry, "Cherry"}, 60 {Key(-1), "Key(-1)"}, 61 } 62
··· 35 expected string 36 }{ 37 {Cumin, "#BF976F"}, 38 + {Tang, "#FF985A"}, 39 + {Yam, "#FFB587"}, 40 + {Paprika, "#D36C64"}, 41 + {Bengal, "#FF6E63"}, 42 + {Uni, "#FF937D"}, 43 + {Sriracha, "#EB4268"}, 44 + {Coral, "#FF577D"}, 45 + {Salmon, "#FF7F90"}, 46 + {Chili, "#E23080"}, 47 {Cherry, "#FF388B"}, 48 + {Tuna, "#FF6DAA"}, 49 + {Macaron, "#E940B0"}, 50 + {Pony, "#FF4FBF"}, 51 + {Cheeky, "#FF79D0"}, 52 + {Flamingo, "#F947E3"}, 53 + {Dolly, "#FF60FF"}, 54 + {Blush, "#FF84FF"}, 55 + {Urchin, "#C337E0"}, 56 + {Mochi, "#EB5DFF"}, 57 + {Lilac, "#F379FF"}, 58 + {Prince, "#9C35E1"}, 59 + {Violet, "#C259FF"}, 60 + {Mauve, "#D46EFF"}, 61 + {Grape, "#7134DD"}, 62 + {Plum, "#9953FF"}, 63 + {Orchid, "#AD6EFF"}, 64 + {Jelly, "#4A30D9"}, 65 + {Charple, "#6B50FF"}, 66 + {Hazy, "#8B75FF"}, 67 + {Ox, "#3331B2"}, 68 + {Sapphire, "#4949FF"}, 69 + {Guppy, "#7272FF"}, 70 + {Oceania, "#2B55B3"}, 71 + {Thunder, "#4776FF"}, 72 + {Anchovy, "#719AFC"}, 73 + {Damson, "#007AB8"}, 74 + {Malibu, "#00A4FF"}, 75 + {Sardine, "#4FBEFE"}, 76 + {Zinc, "#10B1AE"}, 77 + {Turtle, "#0ADCD9"}, 78 + {Lichen, "#5CDFEA"}, 79 + {Guac, "#12C78F"}, 80 {Julep, "#00FFB2"}, 81 + {Bok, "#68FFD6"}, 82 + {Mustard, "#F5EF34"}, 83 + {Citron, "#E8FF27"}, 84 + {Zest, "#E8FE96"}, 85 + {Pepper, "#201F26"}, 86 + {BBQ, "#2d2c35"}, 87 + {Charcoal, "#3A3943"}, 88 + {Iron, "#4D4C57"}, 89 + {Oyster, "#605F6B"}, 90 + {Squid, "#858392"}, 91 + {Smoke, "#BFBCC8"}, 92 + {Ash, "#DFDBDD"}, 93 + {Salt, "#F1EFEF"}, 94 {Butter, "#FFFAF1"}, 95 + // Diffs: additions 96 + {Pickle, "#00A475"}, 97 + {Gator, "#18463D"}, 98 + {Spinach, "#1C3634"}, 99 + {Pom, "#AB2454"}, 100 + {Steak, "#582238"}, 101 + {Toast, "#412130"}, 102 + {NeueGuac, "#00b875"}, 103 + {NeueZinc, "#0e9996"}, 104 {Key(-1), ""}, 105 } 106 ··· 119 expected string 120 }{ 121 {Cumin, "Cumin"}, 122 + {Tang, "Tang"}, 123 + {Yam, "Yam"}, 124 + {Paprika, "Paprika"}, 125 + {Bengal, "Bengal"}, 126 + {Uni, "Uni"}, 127 + {Sriracha, "Sriracha"}, 128 + {Coral, "Coral"}, 129 + {Salmon, "Salmon"}, 130 + {Chili, "Chili"}, 131 {Cherry, "Cherry"}, 132 + {Tuna, "Tuna"}, 133 + {Macaron, "Macaron"}, 134 + {Pony, "Pony"}, 135 + {Cheeky, "Cheeky"}, 136 + {Flamingo, "Flamingo"}, 137 + {Dolly, "Dolly"}, 138 + {Blush, "Blush"}, 139 + {Urchin, "Urchin"}, 140 + {Mochi, "Mochi"}, 141 + {Lilac, "Lilac"}, 142 + {Prince, "Prince"}, 143 + {Violet, "Violet"}, 144 + {Mauve, "Mauve"}, 145 + {Grape, "Grape"}, 146 + {Plum, "Plum"}, 147 + {Orchid, "Orchid"}, 148 + {Jelly, "Jelly"}, 149 + {Charple, "Charple"}, 150 + {Hazy, "Hazy"}, 151 + {Ox, "Ox"}, 152 + {Sapphire, "Sapphire"}, 153 + {Guppy, "Guppy"}, 154 + {Oceania, "Oceania"}, 155 + {Thunder, "Thunder"}, 156 + {Anchovy, "Anchovy"}, 157 + {Damson, "Damson"}, 158 + {Malibu, "Malibu"}, 159 + {Sardine, "Sardine"}, 160 + {Zinc, "Zinc"}, 161 + {Turtle, "Turtle"}, 162 + {Lichen, "Lichen"}, 163 + {Guac, "Guac"}, 164 + {Julep, "Julep"}, 165 + {Bok, "Bok"}, 166 + {Mustard, "Mustard"}, 167 + {Citron, "Citron"}, 168 + {Zest, "Zest"}, 169 + {Pepper, "Pepper"}, 170 + {BBQ, "BBQ"}, 171 + {Charcoal, "Charcoal"}, 172 + {Iron, "Iron"}, 173 + {Oyster, "Oyster"}, 174 + {Squid, "Squid"}, 175 + {Smoke, "Smoke"}, 176 + {Ash, "Ash"}, 177 + {Salt, "Salt"}, 178 + {Butter, "Butter"}, 179 + {Pickle, "Pickle"}, 180 + {Gator, "Gator"}, 181 + {Spinach, "Spinach"}, 182 + {Pom, "Pom"}, 183 + {Steak, "Steak"}, 184 + {Toast, "Toast"}, 185 + {NeueGuac, "NeueGuac"}, 186 + {NeueZinc, "NeueZinc"}, 187 {Key(-1), "Key(-1)"}, 188 } 189