cli + tui to publish to leaflet (wip) & manage tasks, notes & watch/read lists 馃崈
charm leaflet readability golang
at main 1096 lines 30 kB view raw
1package main 2 3import ( 4 "context" 5 "os" 6 "path/filepath" 7 "slices" 8 "strings" 9 "testing" 10 11 "github.com/stormlightlabs/noteleaf/internal/handlers" 12 "github.com/stormlightlabs/noteleaf/internal/services" 13 "github.com/stormlightlabs/noteleaf/internal/shared" 14) 15 16func setupCommandTest(t *testing.T) func() { 17 tempDir, err := os.MkdirTemp("", "noteleaf-cmd-test-*") 18 if err != nil { 19 t.Fatalf("Failed to create temp dir: %v", err) 20 } 21 22 oldNoteleafConfig := os.Getenv("NOTELEAF_CONFIG") 23 oldNoteleafDataDir := os.Getenv("NOTELEAF_DATA_DIR") 24 os.Setenv("NOTELEAF_CONFIG", filepath.Join(tempDir, ".noteleaf.conf.toml")) 25 os.Setenv("NOTELEAF_DATA_DIR", tempDir) 26 27 cleanup := func() { 28 os.Setenv("NOTELEAF_CONFIG", oldNoteleafConfig) 29 os.Setenv("NOTELEAF_DATA_DIR", oldNoteleafDataDir) 30 os.RemoveAll(tempDir) 31 } 32 33 ctx := context.Background() 34 err = handlers.Setup(ctx, []string{}) 35 if err != nil { 36 cleanup() 37 t.Fatalf("Failed to setup database: %v", err) 38 } 39 40 return cleanup 41} 42 43func createTestTaskHandler(t *testing.T) (*handlers.TaskHandler, func()) { 44 cleanup := setupCommandTest(t) 45 handler, err := handlers.NewTaskHandler() 46 if err != nil { 47 cleanup() 48 t.Fatalf("Failed to create test task handler: %v", err) 49 } 50 return handler, func() { 51 handler.Close() 52 cleanup() 53 } 54} 55 56func createTestMovieHandler(t *testing.T) (*handlers.MovieHandler, func()) { 57 cleanup := setupCommandTest(t) 58 handler, err := handlers.NewMovieHandler() 59 if err != nil { 60 cleanup() 61 t.Fatalf("Failed to create test movie handler: %v", err) 62 } 63 return handler, func() { 64 handler.Close() 65 cleanup() 66 } 67} 68 69func createTestTVHandler(t *testing.T) (*handlers.TVHandler, func()) { 70 cleanup := setupCommandTest(t) 71 handler, err := handlers.NewTVHandler() 72 if err != nil { 73 cleanup() 74 t.Fatalf("Failed to create test TV handler: %v", err) 75 } 76 return handler, func() { 77 handler.Close() 78 cleanup() 79 } 80} 81 82func createTestNoteHandler(t *testing.T) (*handlers.NoteHandler, func()) { 83 cleanup := setupCommandTest(t) 84 handler, err := handlers.NewNoteHandler() 85 if err != nil { 86 cleanup() 87 t.Fatalf("Failed to create test note handler: %v", err) 88 } 89 return handler, func() { 90 handler.Close() 91 cleanup() 92 } 93} 94 95func createTestBookHandler(t *testing.T) (*handlers.BookHandler, func()) { 96 cleanup := setupCommandTest(t) 97 handler, err := handlers.NewBookHandler() 98 if err != nil { 99 cleanup() 100 t.Fatalf("Failed to create test book handler: %v", err) 101 } 102 return handler, func() { 103 handler.Close() 104 cleanup() 105 } 106} 107 108func createTestArticleHandler(t *testing.T) (*handlers.ArticleHandler, func()) { 109 cleanup := setupCommandTest(t) 110 handler, err := handlers.NewArticleHandler() 111 if err != nil { 112 cleanup() 113 t.Fatalf("Failed to create test article handler: %v", err) 114 } 115 return handler, func() { 116 handler.Close() 117 cleanup() 118 } 119} 120 121func createTestConfigHandler(t *testing.T) (*handlers.ConfigHandler, func()) { 122 cleanup := setupCommandTest(t) 123 handler, err := handlers.NewConfigHandler() 124 if err != nil { 125 cleanup() 126 t.Fatalf("failed to create test config handler: %v", err) 127 } 128 return handler, cleanup 129} 130 131func findSubcommand(commands []string, target string) bool { 132 return slices.Contains(commands, target) 133} 134 135func TestCommandGroup(t *testing.T) { 136 t.Run("Interface Implementations", func(t *testing.T) { 137 taskHandler, taskCleanup := createTestTaskHandler(t) 138 defer taskCleanup() 139 140 movieHandler, movieCleanup := createTestMovieHandler(t) 141 defer movieCleanup() 142 143 tvHandler, tvCleanup := createTestTVHandler(t) 144 defer tvCleanup() 145 146 noteHandler, noteCleanup := createTestNoteHandler(t) 147 defer noteCleanup() 148 149 bookHandler, bookCleanup := createTestBookHandler(t) 150 defer bookCleanup() 151 152 articleHandler, articleCleanup := createTestArticleHandler(t) 153 defer articleCleanup() 154 155 var _ CommandGroup = NewTaskCommand(taskHandler) 156 var _ CommandGroup = NewMovieCommand(movieHandler) 157 var _ CommandGroup = NewTVCommand(tvHandler) 158 var _ CommandGroup = NewNoteCommand(noteHandler) 159 var _ CommandGroup = NewBookCommand(bookHandler) 160 var _ CommandGroup = NewArticleCommand(articleHandler) 161 }) 162 163 t.Run("Create", func(t *testing.T) { 164 t.Run("TaskCommand", func(t *testing.T) { 165 handler, cleanup := createTestTaskHandler(t) 166 defer cleanup() 167 168 commands := NewTaskCommand(handler) 169 cmd := commands.Create() 170 171 if cmd == nil { 172 t.Fatal("Create returned nil") 173 } 174 if cmd.Use != "todo" { 175 t.Errorf("Expected Use to be 'todo', got '%s'", cmd.Use) 176 } 177 if len(cmd.Aliases) != 1 || cmd.Aliases[0] != "task" { 178 t.Errorf("Expected aliases to be ['task'], got %v", cmd.Aliases) 179 } 180 if cmd.Short != "task management" { 181 t.Errorf("Expected Short to be 'task management', got '%s'", cmd.Short) 182 } 183 if !cmd.HasSubCommands() { 184 t.Error("Expected command to have subcommands") 185 } 186 }) 187 188 t.Run("MovieCommand", func(t *testing.T) { 189 handler, cleanup := createTestMovieHandler(t) 190 defer cleanup() 191 192 commands := NewMovieCommand(handler) 193 cmd := commands.Create() 194 195 if cmd == nil { 196 t.Fatal("Create returned nil") 197 } 198 if cmd.Use != "movie" { 199 t.Errorf("Expected Use to be 'movie', got '%s'", cmd.Use) 200 } 201 if cmd.Short != "Manage movie watch queue" { 202 t.Errorf("Expected Short to be 'Manage movie watch queue', got '%s'", cmd.Short) 203 } 204 if !cmd.HasSubCommands() { 205 t.Error("Expected command to have subcommands") 206 } 207 208 subcommands := cmd.Commands() 209 subcommandNames := make([]string, len(subcommands)) 210 for i, subcmd := range subcommands { 211 subcommandNames[i] = subcmd.Use 212 } 213 214 expectedSubcommands := []string{ 215 "add [search query...]", 216 "list [--all|--watched|--queued]", 217 "watched [id]", 218 "remove [id]", 219 } 220 221 for _, expected := range expectedSubcommands { 222 if !findSubcommand(subcommandNames, expected) { 223 t.Errorf("Expected subcommand '%s' not found in %v", expected, subcommandNames) 224 } 225 } 226 }) 227 228 t.Run("TVCommand", func(t *testing.T) { 229 handler, cleanup := createTestTVHandler(t) 230 defer cleanup() 231 232 commands := NewTVCommand(handler) 233 cmd := commands.Create() 234 235 if cmd == nil { 236 t.Fatal("Create returned nil") 237 } 238 if cmd.Use != "tv" { 239 t.Errorf("Expected Use to be 'tv', got '%s'", cmd.Use) 240 } 241 if cmd.Short != "Manage TV show watch queue" { 242 t.Errorf("Expected Short to be 'Manage TV show watch queue', got '%s'", cmd.Short) 243 } 244 if !cmd.HasSubCommands() { 245 t.Error("Expected command to have subcommands") 246 } 247 248 subcommands := cmd.Commands() 249 subcommandNames := make([]string, len(subcommands)) 250 for i, subcmd := range subcommands { 251 subcommandNames[i] = subcmd.Use 252 } 253 254 expectedSubcommands := []string{ 255 "add [search query...]", 256 "list [--all|--queued|--watching|--watched]", 257 "watching [id]", 258 "watched [id]", 259 "remove [id]", 260 } 261 262 for _, expected := range expectedSubcommands { 263 if !findSubcommand(subcommandNames, expected) { 264 t.Errorf("Expected subcommand '%s' not found in %v", expected, subcommandNames) 265 } 266 } 267 }) 268 269 t.Run("NoteCommand", func(t *testing.T) { 270 handler, cleanup := createTestNoteHandler(t) 271 defer cleanup() 272 273 commands := NewNoteCommand(handler) 274 cmd := commands.Create() 275 276 if cmd == nil { 277 t.Fatal("Create returned nil") 278 } 279 if cmd.Use != "note" { 280 t.Errorf("Expected Use to be 'note', got '%s'", cmd.Use) 281 } 282 if cmd.Short != "Manage notes" { 283 t.Errorf("Expected Short to be 'Manage notes', got '%s'", cmd.Short) 284 } 285 if !cmd.HasSubCommands() { 286 t.Error("Expected command to have subcommands") 287 } 288 289 subcommands := cmd.Commands() 290 subcommandNames := make([]string, len(subcommands)) 291 for i, subcmd := range subcommands { 292 subcommandNames[i] = subcmd.Use 293 } 294 295 expectedSubcommands := []string{ 296 "create [title] [content...]", 297 "list [--archived] [--static] [--tags=tag1,tag2]", 298 "read [note-id]", 299 "edit [note-id]", 300 "remove [note-id]", 301 } 302 303 for _, expected := range expectedSubcommands { 304 if !findSubcommand(subcommandNames, expected) { 305 t.Errorf("Expected subcommand '%s' not found in %v", expected, subcommandNames) 306 } 307 } 308 }) 309 310 t.Run("BookCommand", func(t *testing.T) { 311 handler, cleanup := createTestBookHandler(t) 312 defer cleanup() 313 314 commands := NewBookCommand(handler) 315 cmd := commands.Create() 316 317 if cmd == nil { 318 t.Fatal("Create returned nil") 319 } 320 if cmd.Use != "book" { 321 t.Errorf("Expected Use to be 'book', got '%s'", cmd.Use) 322 } 323 if cmd.Short != "Manage reading list" { 324 t.Errorf("Expected Short to be 'Manage reading list', got '%s'", cmd.Short) 325 } 326 if !cmd.HasSubCommands() { 327 t.Error("Expected command to have subcommands") 328 } 329 330 subcommands := cmd.Commands() 331 subcommandNames := make([]string, len(subcommands)) 332 for i, subcmd := range subcommands { 333 subcommandNames[i] = subcmd.Use 334 } 335 336 expectedSubcommands := []string{ 337 "add [search query...]", 338 "list [--all|--reading|--finished|--queued]", 339 "reading <id>", 340 "finished <id>", 341 "remove <id>", 342 "progress <id> <percentage>", 343 "update <id> <status>", 344 } 345 346 for _, expected := range expectedSubcommands { 347 if !findSubcommand(subcommandNames, expected) { 348 t.Errorf("Expected subcommand '%s' not found in %v", expected, subcommandNames) 349 } 350 } 351 }) 352 353 t.Run("ArticleCommand", func(t *testing.T) { 354 handler, cleanup := createTestArticleHandler(t) 355 defer cleanup() 356 357 commands := NewArticleCommand(handler) 358 cmd := commands.Create() 359 360 if cmd == nil { 361 t.Fatal("Create returned nil") 362 } 363 if cmd.Use != "article" { 364 t.Errorf("Expected Use to be 'article', got '%s'", cmd.Use) 365 } 366 if cmd.Short != "Manage saved articles" { 367 t.Errorf("Expected Short to be 'Manage saved articles', got '%s'", cmd.Short) 368 } 369 if !cmd.HasSubCommands() { 370 t.Error("Expected command to have subcommands") 371 } 372 373 subcommands := cmd.Commands() 374 subcommandNames := make([]string, len(subcommands)) 375 for i, subcmd := range subcommands { 376 subcommandNames[i] = subcmd.Use 377 } 378 379 for _, expected := range []string{"add <url>", "list [query]", "view <id>", "remove <id>"} { 380 if !findSubcommand(subcommandNames, expected) { 381 t.Errorf("Expected subcommand '%s' not found in %v", expected, subcommandNames) 382 } 383 } 384 }) 385 386 t.Run("all command groups implement Create", func(t *testing.T) { 387 taskHandler, taskCleanup := createTestTaskHandler(t) 388 defer taskCleanup() 389 390 movieHandler, movieCleanup := createTestMovieHandler(t) 391 defer movieCleanup() 392 393 tvHandler, tvCleanup := createTestTVHandler(t) 394 defer tvCleanup() 395 396 noteHandler, noteCleanup := createTestNoteHandler(t) 397 defer noteCleanup() 398 399 bookHandler, bookCleanup := createTestBookHandler(t) 400 defer bookCleanup() 401 402 articleHandler, articleCleanup := createTestArticleHandler(t) 403 defer articleCleanup() 404 405 groups := []CommandGroup{ 406 NewTaskCommand(taskHandler), 407 NewMovieCommand(movieHandler), 408 NewTVCommand(tvHandler), 409 NewNoteCommand(noteHandler), 410 NewBookCommand(bookHandler), 411 NewArticleCommand(articleHandler), 412 } 413 414 for i, group := range groups { 415 cmd := group.Create() 416 if cmd == nil { 417 t.Errorf("CommandGroup %d returned nil from Create()", i) 418 continue 419 } 420 if cmd.Use == "" { 421 t.Errorf("CommandGroup %d returned command with empty Use", i) 422 } 423 } 424 }) 425 }) 426 427} 428 429func TestCommandExecution(t *testing.T) { 430 t.Run("Movie Commands", func(t *testing.T) { 431 handler, cleanup := createTestMovieHandler(t) 432 defer cleanup() 433 434 t.Run("list command - default", func(t *testing.T) { 435 cmd := NewMovieCommand(handler).Create() 436 cmd.SetArgs([]string{"list"}) 437 err := cmd.Execute() 438 if err != nil { 439 t.Errorf("movie list command failed: %v", err) 440 } 441 }) 442 443 t.Run("add command with empty args", func(t *testing.T) { 444 cmd := NewMovieCommand(handler).Create() 445 cmd.SetArgs([]string{"add"}) 446 err := cmd.Execute() 447 if err == nil { 448 t.Error("expected movie add command to fail with empty args") 449 } 450 }) 451 452 t.Run("add command with valid args - successful search", func(t *testing.T) { 453 cleanup := services.SetupSuccessfulMovieMocks(t) 454 defer cleanup() 455 456 cmd := NewMovieCommand(handler).Create() 457 cmd.SetArgs([]string{"add", "Fantastic Four"}) 458 err := cmd.Execute() 459 460 // NOTE: The command will find results but fail due to no user input in test environment 461 if err == nil { 462 t.Error("expected movie add command to fail due to no user input in test environment") 463 } 464 if !strings.Contains(err.Error(), "invalid input") { 465 t.Errorf("expected 'invalid input' error, got: %v", err) 466 } 467 }) 468 469 t.Run("add command with valid args - search failure", func(t *testing.T) { 470 cleanup := services.SetupFailureMocks(t, "search failed") 471 defer cleanup() 472 473 cmd := NewMovieCommand(handler).Create() 474 cmd.SetArgs([]string{"add", "some movie"}) 475 err := cmd.Execute() 476 if err == nil { 477 t.Error("expected movie add command to fail when search fails") 478 } 479 shared.AssertErrorContains(t, err, "search failed", "") 480 }) 481 482 t.Run("remove command with non-existent movie ID", func(t *testing.T) { 483 cmd := NewMovieCommand(handler).Create() 484 cmd.SetArgs([]string{"remove", "999"}) 485 err := cmd.Execute() 486 if err == nil { 487 t.Error("expected movie remove command to fail with non-existent ID") 488 } 489 }) 490 491 t.Run("remove command with non-numeric ID", func(t *testing.T) { 492 cmd := NewMovieCommand(handler).Create() 493 cmd.SetArgs([]string{"remove", "invalid"}) 494 err := cmd.Execute() 495 if err == nil { 496 t.Error("expected movie remove command to fail with non-numeric ID") 497 } 498 }) 499 500 t.Run("watched command", func(t *testing.T) { 501 handler, cleanup := createTestMovieHandler(t) 502 defer cleanup() 503 504 cmd := NewMovieCommand(handler).Create() 505 cmd.SetArgs([]string{"watched", "1"}) 506 err := cmd.Execute() 507 if err == nil { 508 t.Error("expected movie watched command to fail with non-existent ID") 509 } 510 }) 511 }) 512 513 t.Run("TV Commands", func(t *testing.T) { 514 handler, cleanup := createTestTVHandler(t) 515 defer cleanup() 516 517 t.Run("list command - default", func(t *testing.T) { 518 cmd := NewTVCommand(handler).Create() 519 cmd.SetArgs([]string{"list"}) 520 err := cmd.Execute() 521 if err != nil { 522 t.Errorf("tv list command failed: %v", err) 523 } 524 }) 525 526 t.Run("add command with empty args", func(t *testing.T) { 527 cmd := NewTVCommand(handler).Create() 528 cmd.SetArgs([]string{"add"}) 529 err := cmd.Execute() 530 if err == nil { 531 t.Error("expected tv add command to fail with empty args") 532 } 533 }) 534 535 t.Run("add command with valid args - successful search", func(t *testing.T) { 536 cleanup := services.SetupSuccessfulTVMocks(t) 537 defer cleanup() 538 539 cmd := NewTVCommand(handler).Create() 540 cmd.SetArgs([]string{"add", "Peacemaker"}) 541 err := cmd.Execute() 542 543 // NOTE: The command will find results but fail due to no user input in test environment 544 if err == nil { 545 t.Error("expected tv add command to fail due to no user input in test environment") 546 } 547 if !strings.Contains(err.Error(), "invalid input") { 548 t.Errorf("expected 'invalid input' error, got: %v", err) 549 } 550 }) 551 552 t.Run("add command with valid args - search failure", func(t *testing.T) { 553 cleanup := services.SetupFailureMocks(t, "tv search failed") 554 defer cleanup() 555 556 cmd := NewTVCommand(handler).Create() 557 cmd.SetArgs([]string{"add", "some show"}) 558 err := cmd.Execute() 559 if err == nil { 560 t.Error("expected tv add command to fail when search fails") 561 } 562 shared.AssertErrorContains(t, err, "tv search failed", "") 563 }) 564 565 t.Run("remove command with non-existent TV show ID", func(t *testing.T) { 566 cmd := NewTVCommand(handler).Create() 567 cmd.SetArgs([]string{"remove", "999"}) 568 err := cmd.Execute() 569 if err == nil { 570 t.Error("expected tv remove command to fail with non-existent ID") 571 } 572 }) 573 574 t.Run("remove command with non-numeric ID", func(t *testing.T) { 575 cmd := NewTVCommand(handler).Create() 576 cmd.SetArgs([]string{"remove", "invalid"}) 577 err := cmd.Execute() 578 if err == nil { 579 t.Error("expected tv remove command to fail with non-numeric ID") 580 } 581 }) 582 583 t.Run("watching command", func(t *testing.T) { 584 handler, cleanup := createTestTVHandler(t) 585 defer cleanup() 586 587 cmd := NewTVCommand(handler).Create() 588 cmd.SetArgs([]string{"watching", "1"}) 589 err := cmd.Execute() 590 if err == nil { 591 t.Error("expected tv watching command to fail with non-existent ID") 592 } 593 }) 594 595 t.Run("watched command", func(t *testing.T) { 596 handler, cleanup := createTestTVHandler(t) 597 defer cleanup() 598 599 cmd := NewTVCommand(handler).Create() 600 cmd.SetArgs([]string{"watched", "1"}) 601 err := cmd.Execute() 602 if err == nil { 603 t.Error("expected tv watched command to fail with non-existent ID") 604 } 605 }) 606 }) 607 608 t.Run("Book Commands", func(t *testing.T) { 609 handler, cleanup := createTestBookHandler(t) 610 defer cleanup() 611 612 t.Run("list command - default", func(t *testing.T) { 613 cmd := NewBookCommand(handler).Create() 614 cmd.SetArgs([]string{"list"}) 615 err := cmd.Execute() 616 if err != nil { 617 t.Errorf("book list command failed: %v", err) 618 } 619 }) 620 621 t.Run("remove command with non-existent book ID", func(t *testing.T) { 622 cmd := NewBookCommand(handler).Create() 623 cmd.SetArgs([]string{"remove", "999"}) 624 err := cmd.Execute() 625 if err == nil { 626 t.Error("expected book remove command to fail with non-existent ID") 627 } 628 }) 629 630 t.Run("remove command with non-numeric ID", func(t *testing.T) { 631 cmd := NewBookCommand(handler).Create() 632 cmd.SetArgs([]string{"remove", "invalid"}) 633 err := cmd.Execute() 634 if err == nil { 635 t.Error("expected book remove command to fail with non-numeric ID") 636 } 637 }) 638 639 t.Run("update command with removed status", func(t *testing.T) { 640 cmd := NewBookCommand(handler).Create() 641 cmd.SetArgs([]string{"update", "999", "removed"}) 642 err := cmd.Execute() 643 if err == nil { 644 t.Error("expected book update command to fail with non-existent ID") 645 } 646 }) 647 648 t.Run("update command with invalid status", func(t *testing.T) { 649 cmd := NewBookCommand(handler).Create() 650 cmd.SetArgs([]string{"update", "1", "invalid_status"}) 651 err := cmd.Execute() 652 if err == nil { 653 t.Error("expected book update command to fail with invalid status") 654 } 655 }) 656 657 t.Run("reading command", func(t *testing.T) { 658 cmd := NewBookCommand(handler).Create() 659 cmd.SetArgs([]string{"reading", "1"}) 660 err := cmd.Execute() 661 if err == nil { 662 t.Error("expected book reading command to fail with non-existent ID") 663 } 664 }) 665 666 t.Run("finished command", func(t *testing.T) { 667 cmd := NewBookCommand(handler).Create() 668 cmd.SetArgs([]string{"finished", "1"}) 669 err := cmd.Execute() 670 if err == nil { 671 t.Error("expected book finished command to fail with non-existent ID") 672 } 673 }) 674 675 t.Run("progress command", func(t *testing.T) { 676 cmd := NewBookCommand(handler).Create() 677 cmd.SetArgs([]string{"progress", "1", "50"}) 678 err := cmd.Execute() 679 if err == nil { 680 t.Error("expected book progress command to fail with non-existent ID") 681 } 682 }) 683 684 t.Run("progress command with invalid percentage", func(t *testing.T) { 685 cmd := NewBookCommand(handler).Create() 686 cmd.SetArgs([]string{"progress", "1", "invalid"}) 687 err := cmd.Execute() 688 if err == nil { 689 t.Error("expected book progress command to fail with invalid percentage") 690 } 691 }) 692 }) 693 694 t.Run("Article Commands", func(t *testing.T) { 695 handler, cleanup := createTestArticleHandler(t) 696 defer cleanup() 697 698 t.Run("list command - default", func(t *testing.T) { 699 cmd := NewArticleCommand(handler).Create() 700 cmd.SetArgs([]string{"list"}) 701 err := cmd.Execute() 702 if err != nil { 703 t.Errorf("article list command failed: %v", err) 704 } 705 }) 706 707 t.Run("help command", func(t *testing.T) { 708 cmd := NewArticleCommand(handler).Create() 709 cmd.SetArgs([]string{"help"}) 710 err := cmd.Execute() 711 if err != nil { 712 t.Errorf("article help command failed: %v", err) 713 } 714 }) 715 716 t.Run("add command with empty args", func(t *testing.T) { 717 cmd := NewArticleCommand(handler).Create() 718 cmd.SetArgs([]string{"add"}) 719 err := cmd.Execute() 720 if err == nil { 721 t.Error("expected article add command to fail with empty args") 722 } 723 }) 724 725 t.Run("add command with invalid URL", func(t *testing.T) { 726 cmd := NewArticleCommand(handler).Create() 727 cmd.SetArgs([]string{"add", "not-a-url"}) 728 err := cmd.Execute() 729 if err == nil { 730 t.Error("expected article add command to fail with invalid URL") 731 } 732 }) 733 734 t.Run("view command with non-existent article ID", func(t *testing.T) { 735 cmd := NewArticleCommand(handler).Create() 736 cmd.SetArgs([]string{"view", "999"}) 737 err := cmd.Execute() 738 if err == nil { 739 t.Error("expected article view command to fail with non-existent ID") 740 } 741 }) 742 743 t.Run("view command with non-numeric ID", func(t *testing.T) { 744 cmd := NewArticleCommand(handler).Create() 745 cmd.SetArgs([]string{"view", "invalid"}) 746 err := cmd.Execute() 747 if err == nil { 748 t.Error("expected article view command to fail with non-numeric ID") 749 } 750 }) 751 752 t.Run("read command with non-existent article ID", func(t *testing.T) { 753 cmd := NewArticleCommand(handler).Create() 754 cmd.SetArgs([]string{"read", "999"}) 755 err := cmd.Execute() 756 if err == nil { 757 t.Error("expected article read command to fail with non-existent ID") 758 } 759 }) 760 761 t.Run("read command with non-numeric ID", func(t *testing.T) { 762 cmd := NewArticleCommand(handler).Create() 763 cmd.SetArgs([]string{"read", "invalid"}) 764 err := cmd.Execute() 765 if err == nil { 766 t.Error("expected article read command to fail with non-numeric ID") 767 } 768 }) 769 770 t.Run("remove command with non-existent article ID", func(t *testing.T) { 771 cmd := NewArticleCommand(handler).Create() 772 cmd.SetArgs([]string{"remove", "999"}) 773 err := cmd.Execute() 774 if err == nil { 775 t.Error("expected article remove command to fail with non-existent ID") 776 } 777 }) 778 779 t.Run("remove command with non-numeric ID", func(t *testing.T) { 780 cmd := NewArticleCommand(handler).Create() 781 cmd.SetArgs([]string{"remove", "invalid"}) 782 err := cmd.Execute() 783 if err == nil { 784 t.Error("expected article remove command to fail with non-numeric ID") 785 } 786 }) 787 }) 788 789 t.Run("Note Commands", func(t *testing.T) { 790 t.Run("create command - non-interactive", func(t *testing.T) { 791 handler, cleanup := createTestNoteHandler(t) 792 defer cleanup() 793 794 cmd := NewNoteCommand(handler).Create() 795 cmd.SetArgs([]string{"create", "test title", "test content"}) 796 err := cmd.Execute() 797 if err != nil { 798 t.Errorf("note create command failed: %v", err) 799 } 800 }) 801 802 t.Run("list command - static mode", func(t *testing.T) { 803 handler, cleanup := createTestNoteHandler(t) 804 defer cleanup() 805 806 cmd := NewNoteCommand(handler).Create() 807 cmd.SetArgs([]string{"list", "--static"}) 808 err := cmd.Execute() 809 if err != nil { 810 t.Errorf("note list command failed: %v", err) 811 } 812 }) 813 814 t.Run("read command with valid note ID", func(t *testing.T) { 815 handler, cleanup := createTestNoteHandler(t) 816 defer cleanup() 817 818 err := handler.CreateWithOptions(context.Background(), "test note", "test content", "", false, false) 819 if err != nil { 820 t.Fatalf("failed to create test note: %v", err) 821 } 822 823 cmd := NewNoteCommand(handler).Create() 824 cmd.SetArgs([]string{"read", "1"}) 825 err = cmd.Execute() 826 if err != nil { 827 t.Errorf("note read command failed: %v", err) 828 } 829 }) 830 831 t.Run("edit command with valid note ID", func(t *testing.T) { 832 t.Skip("edit command requires interactive editor") 833 }) 834 835 t.Run("remove command with valid note ID", func(t *testing.T) { 836 handler, cleanup := createTestNoteHandler(t) 837 defer cleanup() 838 839 err := handler.CreateWithOptions(context.Background(), "test note", "test content", "", false, false) 840 if err != nil { 841 t.Fatalf("failed to create test note: %v", err) 842 } 843 844 cmd := NewNoteCommand(handler).Create() 845 cmd.SetArgs([]string{"remove", "1"}) 846 err = cmd.Execute() 847 if err != nil { 848 t.Errorf("note remove command failed: %v", err) 849 } 850 }) 851 852 t.Run("edit command with invalid ID", func(t *testing.T) { 853 handler, cleanup := createTestNoteHandler(t) 854 defer cleanup() 855 856 cmd := NewNoteCommand(handler).Create() 857 cmd.SetArgs([]string{"edit", "invalid"}) 858 err := cmd.Execute() 859 if err == nil { 860 t.Error("expected note edit command to fail with invalid ID") 861 } 862 }) 863 864 t.Run("remove command with invalid ID", func(t *testing.T) { 865 handler, cleanup := createTestNoteHandler(t) 866 defer cleanup() 867 868 cmd := NewNoteCommand(handler).Create() 869 cmd.SetArgs([]string{"remove", "invalid"}) 870 err := cmd.Execute() 871 if err == nil { 872 t.Error("expected note remove command to fail with invalid ID") 873 } 874 }) 875 876 t.Run("list command with static flag", func(t *testing.T) { 877 handler, cleanup := createTestNoteHandler(t) 878 defer cleanup() 879 880 cmd := NewNoteCommand(handler).Create() 881 cmd.SetArgs([]string{"list", "--static", "test query"}) 882 err := cmd.Execute() 883 if err != nil { 884 t.Errorf("note list command with query failed: %v", err) 885 } 886 }) 887 }) 888 889 t.Run("Task Commands", func(t *testing.T) { 890 t.Run("list command - static", func(t *testing.T) { 891 handler, cleanup := createTestTaskHandler(t) 892 defer cleanup() 893 894 cmd := NewTaskCommand(handler).Create() 895 cmd.SetArgs([]string{"list", "--static"}) 896 err := cmd.Execute() 897 if err != nil { 898 t.Errorf("task list command failed: %v", err) 899 } 900 }) 901 902 t.Run("add command with valid args", func(t *testing.T) { 903 handler, cleanup := createTestTaskHandler(t) 904 defer cleanup() 905 906 cmd := NewTaskCommand(handler).Create() 907 cmd.SetArgs([]string{"add", "test task"}) 908 err := cmd.Execute() 909 if err != nil { 910 t.Errorf("task add command failed: %v", err) 911 } 912 }) 913 914 t.Run("projects command - static", func(t *testing.T) { 915 handler, cleanup := createTestTaskHandler(t) 916 defer cleanup() 917 918 cmd := NewTaskCommand(handler).Create() 919 cmd.SetArgs([]string{"projects", "--static"}) 920 err := cmd.Execute() 921 if err != nil { 922 t.Errorf("task projects command failed: %v", err) 923 } 924 }) 925 926 t.Run("tags command - static", func(t *testing.T) { 927 handler, cleanup := createTestTaskHandler(t) 928 defer cleanup() 929 930 cmd := NewTaskCommand(handler).Create() 931 cmd.SetArgs([]string{"tags", "--static"}) 932 err := cmd.Execute() 933 if err != nil { 934 t.Errorf("task tags command failed: %v", err) 935 } 936 }) 937 938 t.Run("contexts command - static", func(t *testing.T) { 939 handler, cleanup := createTestTaskHandler(t) 940 defer cleanup() 941 942 cmd := NewTaskCommand(handler).Create() 943 cmd.SetArgs([]string{"contexts", "--static"}) 944 err := cmd.Execute() 945 if err != nil { 946 t.Errorf("task contexts command failed: %v", err) 947 } 948 }) 949 950 t.Run("timesheet command", func(t *testing.T) { 951 handler, cleanup := createTestTaskHandler(t) 952 defer cleanup() 953 954 cmd := NewTaskCommand(handler).Create() 955 cmd.SetArgs([]string{"timesheet"}) 956 err := cmd.Execute() 957 if err != nil { 958 t.Errorf("task timesheet command failed: %v", err) 959 } 960 }) 961 962 t.Run("view command", func(t *testing.T) { 963 handler, cleanup := createTestTaskHandler(t) 964 defer cleanup() 965 966 cmd := NewTaskCommand(handler).Create() 967 cmd.SetArgs([]string{"view", "1"}) 968 err := cmd.Execute() 969 if err == nil { 970 t.Error("expected task view command to fail with non-existent ID") 971 } 972 }) 973 974 t.Run("update command", func(t *testing.T) { 975 handler, cleanup := createTestTaskHandler(t) 976 defer cleanup() 977 978 cmd := NewTaskCommand(handler).Create() 979 cmd.SetArgs([]string{"update", "1"}) 980 err := cmd.Execute() 981 if err == nil { 982 t.Error("expected task update command to fail with non-existent ID") 983 } 984 }) 985 986 t.Run("start command", func(t *testing.T) { 987 handler, cleanup := createTestTaskHandler(t) 988 defer cleanup() 989 990 cmd := NewTaskCommand(handler).Create() 991 cmd.SetArgs([]string{"start", "1"}) 992 err := cmd.Execute() 993 if err == nil { 994 t.Error("expected task start command to fail with non-existent ID") 995 } 996 }) 997 998 t.Run("stop command", func(t *testing.T) { 999 handler, cleanup := createTestTaskHandler(t) 1000 defer cleanup() 1001 1002 cmd := NewTaskCommand(handler).Create() 1003 cmd.SetArgs([]string{"stop", "1"}) 1004 err := cmd.Execute() 1005 if err == nil { 1006 t.Error("expected task stop command to fail with non-existent ID") 1007 } 1008 }) 1009 1010 t.Run("edit command", func(t *testing.T) { 1011 handler, cleanup := createTestTaskHandler(t) 1012 defer cleanup() 1013 1014 cmd := NewTaskCommand(handler).Create() 1015 cmd.SetArgs([]string{"edit", "1"}) 1016 err := cmd.Execute() 1017 if err == nil { 1018 t.Error("expected task edit command to fail with non-existent ID") 1019 } 1020 }) 1021 1022 t.Run("delete command", func(t *testing.T) { 1023 handler, cleanup := createTestTaskHandler(t) 1024 defer cleanup() 1025 1026 cmd := NewTaskCommand(handler).Create() 1027 cmd.SetArgs([]string{"delete", "1"}) 1028 err := cmd.Execute() 1029 if err == nil { 1030 t.Error("expected task delete command to fail with non-existent ID") 1031 } 1032 }) 1033 1034 t.Run("done command", func(t *testing.T) { 1035 handler, cleanup := createTestTaskHandler(t) 1036 defer cleanup() 1037 1038 cmd := NewTaskCommand(handler).Create() 1039 cmd.SetArgs([]string{"done", "1"}) 1040 err := cmd.Execute() 1041 if err == nil { 1042 t.Error("expected task done command to fail with non-existent ID") 1043 } 1044 }) 1045 }) 1046 1047 t.Run("Config Command", func(t *testing.T) { 1048 handler, cleanup := createTestConfigHandler(t) 1049 defer cleanup() 1050 1051 cmd := NewConfigCommand(handler).Create() 1052 1053 if cmd.Use != "config" { 1054 t.Errorf("expected Use 'config', got %s", cmd.Use) 1055 } 1056 if cmd.Short == "" { 1057 t.Errorf("expected Short description to be set") 1058 } 1059 if len(cmd.Commands()) == 0 { 1060 t.Errorf("expected subcommands to be registered") 1061 } 1062 1063 t.Run("path command", func(t *testing.T) { 1064 cmd.SetArgs([]string{"path"}) 1065 if err := cmd.Execute(); err != nil { 1066 t.Errorf("config path failed: %v", err) 1067 } 1068 }) 1069 1070 t.Run("get with no args", func(t *testing.T) { 1071 cmd.SetArgs([]string{"get"}) 1072 if err := cmd.Execute(); err != nil { 1073 t.Errorf("config get with no args failed: %v", err) 1074 } 1075 }) 1076 1077 t.Run("set and get roundtrip", func(t *testing.T) { 1078 cmd.SetArgs([]string{"set", "editor", "vim"}) 1079 if err := cmd.Execute(); err != nil { 1080 t.Fatalf("config set failed: %v", err) 1081 } 1082 1083 cmd.SetArgs([]string{"get", "editor"}) 1084 if err := cmd.Execute(); err != nil { 1085 t.Errorf("config get after set failed: %v", err) 1086 } 1087 }) 1088 1089 t.Run("reset command", func(t *testing.T) { 1090 cmd.SetArgs([]string{"reset"}) 1091 if err := cmd.Execute(); err != nil { 1092 t.Errorf("config reset failed: %v", err) 1093 } 1094 }) 1095 }) 1096}